重構 - 讀書筆記
去年十二月, 重讀時, 輸出了幾篇博文, 主要幾章重構技巧梳理6/7/8/9/10/11, 這周重讀時, 從另一個角度總結一下
我們總是想著, 找個時間重構, 額, 其實, 重構更應該放在平時, 每一次去變更代碼時處理. 畢竟, 所謂的重構契機有時候太過遙遠; 而如果不做重構, 痛苦的是每時每刻維護代碼的自己
如果你發現自己需要為程序添加一個特性, 而代碼結構使你無法很方便地達成目的, 那就先重構那個程序, 使特性的添加比較容易進行, 然後再添加特性
另外, 如果可能, 盡量加單元測試, 哪怕一次只增加一兩個, 一段時間後, 你會發現, 你會感謝過去的自己
原則
小步前進, 頻繁測試
隔離變化
控制可見範圍, 讓變數/常量/函數/類等, 在最小的範圍內可見. 例如設為私有變數/私有函數, 移除不必要的設值函數
重構時, 不要關注性能. 到性能優化階段, 再關注性能. 不同階段關注點不一樣, 不要過早優化. 很多時候, 性能並不是瓶頸, 可讀性和可維護性更重要
任何時候, 都不要拷貝代碼, 拷貝類, 甚至拷貝源碼文件
1. 命名
好的名字, 清晰表達其含義. 命名至關重要
好的代碼應該清楚表達出自己的功能, 變數名稱是代碼清晰的關鍵
如果為了提高代碼的可讀性, 需要修改某些名字, 大膽去改!
IDE/單元測試/好的查找替換工具
建議讀 這本書.
2. 常量和臨時變數提取常量
你有一個字面數值, 帶有特別含義. 創建一個常量, 根據其意義為它命名, 並將上述字面數值替換為這個常量
def potential_energy(mass, height): return mass * 9.81 * height
to
GRAVITATIONAL_CONSTANT = 9.81 def potential_energy(mass, height): return mass * GRAVITATIONAL_CONSTANT * height
任何時候, 都不要拷貝常量, 當你發現要改一個數據, 要到非常多的文件去改字面值時, 你就需要意識到, 該提取常量了
加入: 引入解釋性變數
一個複雜的表達式, 將複雜表達式或其中一部分放入臨時變數, 以變數名稱來解釋表達式用途
if "MAC" in platform.upper() and "IE" in browser.upper() and was_initialized() and resize > 0: #do something
is_macos = "MAC" in platform.upper() is_ie_browser = "IE" in browser.upper() was_resized = resize > 0 if is_macos and is_ie_browser and was_initialized() and was_resized: # do something分解: 分解臨時變數
某個臨時變數被賦值超過一次, 非循環變數, 也不用於收集計算結果.每次賦值, 創砸一個獨立, 對應的臨時變數
單一職責原則
tmp = 2 * (height * width) print tmp tmp = height * width print tmp
perimeter = 2 * (height * width) print perimeter area = height * width print area去除: 移除臨時變數
臨時變數僅被一個簡單表達式賦值一次, 可以去除這個臨時變數
臨時變數, 簡單表達式, 另外, 需要考慮使用次數, 如果僅使用一次, 可以去除, 如果多次, 則需謹慎考慮對可讀性的而影響
best_price = order.base_price() return best_price > 1000
return order.base_price > 1000移除: 控制標記
在一系列布爾表達式中, 某個變數帶有"控制標記"(control flag)的作用. 以break語句或return取代控制標記
def dosomething(): is_success = False if xxx: is_success = True if yyy: is_success = False ... return is_success
def dosomething(): if xxx: return True if yyy: return True ... return False # 一定不要忘記
注意力相關.
這類邏輯中, 很痛苦的是, 你必須無時無刻關注這些控制標記的值, 變數在每一個邏輯之後的變化, 會帶來額外的思考負擔, 從而讓代碼變得不易讀.
3. 函數拆分: Extract Method提煉函數
你有一段代碼可以被組織在一起並獨立出來, 將這段代碼放進一個獨立函數中, 並讓函數名稱解釋該函數的用途
def print_owing(double amount): print_banner() // print details print "this is the detail: " print "amnount: %s" % amount
def print_details(amount): print "this is the detail: " print "amnount: %s" % amount def print_owing(double amount): print_banner() print_details(amount)去除: Inline Method內聯函數
一個函數的本體與名稱同樣清楚易懂, 在函數調用點插入函數本體, 然後移除該函數
小型函數, 函數太過簡單了, 可能只有一個表達式, 去除函數!
def is_length_valid(x): return len(x) > 10 print the length is %s % ( valid if is_length_valid(x) else invalid )
print the length is %s % ( valid if len(x) > 10 else invalid)合并: 合并多個函數, 使用參數
若干函數做了類似的工作. 但在函數本體中卻包含了不同的值. 建立單一函數, 以參數表達那些不同的值
def five_percent_raise(): pass def ten_percent_raise(): pass
def percent_raise(percent): pass副作用: 函數不應該有副作用
某個函數既返回對象狀態值, 又修改對象狀態. 建立兩個不同函數, 一個負責查詢, 一個負責修改.
單一職責原則, 一個函數不應該做兩件事, 函數粒度盡量小.
4. 表達式guard(注意力相關)
過多的條件邏輯, 難以理解正常的執行路徑. 在python中的特徵是, 縮進太深
coolshell中曾經討論過的問題如何重構「箭頭型」代碼, 而在python中的現象是, 縮進嵌套層級太深, 有時候甚至有十幾層縮進, 整體難以理解
而減少嵌套縮進的方式是, 使用 語句, 儘早返回,
注意力相關, 儘早 , 你也就不用關心已經過去的邏輯了, 只需關注後面代碼的邏輯.
if _is_dead: result = dead_amount() else: if _is_separated: result = separated_amount() else: if _is_retired: result = retired_amount() else: result = normal_payamount() return result
if _is_dead: return dead_amount() if _is_separated: return separated_amount() if _is_retired: return retired_amount() return normal_payamount()合并: 合并條件表達式
你有一系列條件測試, 都得到相同結果. 將這些測試合并成一個條件表達式, 並將這個條件表達式提煉成為一個獨立函數
if _seniority
10: return 0 if _is_part_time: return 0
if is_not_eligible_for_disability: return 0分解: 分解複雜條件表達式
你有一個複雜的條件語句(if-then-else). 從if, the, else三個段落中分別提煉出獨立函數
if date
SUMMER_END: charge = quantity * _winter_rate + _winter_servioce_charge else: charge = quantity * _summer_rate
if not_summber(date): charge = winter_charge(quantity) else: charge = summber_charge(quantity)提取: 合并重複的條件片段
在條件表達式的每個分支上有著相同的一段代碼. 將這段重複代碼搬移到條件表達式之外
if is_special: total = price * 0.95 send() else: total = price * 0.98 send()
if is_special: total = price * 0.95 else: total = price * 0.98 send()
這是維護系統, 特別是中後期很容易忽略的問題. 很容易在代碼中出現, 特別是遇到那種 的地方, 通常, 會選擇不動原來的代碼, 加個分支, 複製代碼下來改. 但這樣的後果是, 逐步地, 會發現每個分支中都有重複代碼.
5. 參數及返回值參數和返回值: 提取對象
如果參數/返回值是一組相關的數值, 且總是一起出現, 可以考慮提取成一個對象.
def get_width_height(): .... return width, height def get_area(width, height): return width, height
class Rectangle(object): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height def get_shape(): .... return Rectangle(height, width)
類似的還有: /
減少參數
對象調用了某個函數, 並將所得結果作為參數, 傳遞給另一個函數. 而接受該參數的函數本身也能調用前一個函數. 讓參數接收者去除該參數, 並直接調用前一個函數
base_price = quantity * item_price discount_level = get_discount_level() final_price = discounted_price(base_price, discount_level)
base_price = quantity * item_price final_price = discounted_price(base_price)6. 類搬移: 函數/欄位
搬移函數: 某個函數與所在類之外的另一個類有更多的交互, 調用或被調用(例如: 使用另一個對象的次數比使用自己所在對象的次數還多). 即, 跟另一個類更相關. 則搬移過去
搬移欄位: 某個欄位被其所在類之外的另一個類更多地用到
拆分: 拆分類
某個類做了應該由兩個類做的事. 類太大/太臃腫. 建立一個新類, 將相關欄位和函數從舊類版移到新類
特徵: 類中某些欄位是有關係的整體, 或者有相同的前綴
class Persion(object): def __init__(self, name, age, office_area_code, office_number): self.name = name self.age = age self.office_area_code = office_area_code self.office_number = office_number def get_phone_number(self): return "%s-%s" % (self.office_area_code, self.office_number)
class Person(object): def __init__(self, name, age, office_area_code, office_number): self.name = name self.age = age self.phone_number = PhoneNumber(office_area_code, office_number) def get_phone_number(self): return self.phone_number.get_number() class PhoneNumber(object): def __init__(self, area_code ,number): self.area_code = area_code self.number = number def get_number(self): return "%s-%s" % (self.area_code, self.number)去除
一個類沒有做太多的事情, 不再有獨立存在的理由.
7. 模式
原則:
慎用
只使用你理解的模式
只在符合的業務場景使用對應模式
adapter
你需要為提供服務的類增加功能, 但是你無法修改這個類.
使用組合(推薦, 持有對象)/繼承(加子類), 持有該對象, 增加對應附加功能
adapter思維.
使用場景: 使用一些第三方庫處理外部依賴, 例如依賴一個系統, (requests)/ (Elasticsearch)/ (redispy), 但是, 基於第三方系統, 你需要有自己業務相關的統一處理邏輯, 此時, 你可以建立一個 , 持有第三方組件底層調用邏輯, 同時封裝自身業務邏輯, 在上層直接調用
facade
適配模式中舉的例子, 也有 的思想, 將複雜的東西, 統一封裝, 對外提供相對簡單清晰地介面
template method
出現的次數也很高
裝飾器
python中最常用
其他
根據使用場景, 應用策略/橋樑/工廠/觀察者等等, 具體看業務場景
舉例
重構一個相對較大的 項目
明確業務對象, 對象概念, 對象邊界
明確分層
明確代碼目錄結構, 劃分模塊, 明確每個模塊可以放入的東西
粗粒度重構: 移動模塊/類/函數, 根據前幾步的劃分, 將模塊/類/函數等, 移動到對應模塊中, 同時, 修改 和調用點
中粒度重構: 根據 項目本身劃分, 移動函數
中粒度重構: Extract Method. 讀具體函數代碼, 遇到 等, 思考, 提煉函數, 放入對應模塊
細粒度重構: 提取常量 / 提取枚舉 / 修改模塊名類名函數名變數名
舉例:
對於 項目, 原則
, 將對象本身相關的, 盡量放入 , 這個對象相關的, 可以加入補充一系列 / / , 可以有效地降低使用這個對象時調用處的代碼複雜度. 例如, 每次取兌現改一個欄位都需要進行轉換, 則搞個 替換每次都需要的轉換邏輯. (找拿到 對象後的處理邏輯代碼中那些反覆出現的, 重複的)
將對象查詢相關的, 全部遷移到 中, 需要先通過 查詢然後做各種事情的, 遷移放入到 中
, 將業務邏輯無關的工具函數等, 統一歸入 模塊中; 將業務有關但多個 共用的 放入到 模塊中, 而將 依賴的局部 , 放入到 中
, 同上, 區分通用, 還是某個 中使用
, 業務邏輯, 盡量瘦小簡短
, 模板, 盡量傻瓜, 不要包含複雜計算/判斷邏輯, 將複雜遷移到後端代碼
其他
善用工具, 有方案設計評審, 平時通過 , 走 , 有代碼風格自動檢查, 要求單元測試, 走cicd流程. 在平時, 就有意識地控制代碼質量
※[原]談一談閉包
※Webapp執行reload後內存泄漏之SSLSocketFactory
※提升生活品質的良品
※均衡之道—ThinkPad T470 上手體驗
※講真,如今的好設計還是離不開數據的支持
TAG:推酷 |
※讀書筆記 關於讀書
※我的讀書筆記-NJ姍姍
※【讀書筆記】讀書×自尊
※關於學習讀書筆記
※診余讀書筆記
※讀書筆記分享
※隨筆:讀書筆記(一)
※《原則》讀書筆記
※讀書筆記.莊子
※《誦讀課》讀書筆記
※《高效閱讀》讀書筆記
※讀書筆記-《菊與刀》
※中醫讀書筆記
※讀書筆記-關於王陽明
※讀書 宋瓷筆記
※三月讀書筆記
※一月讀書筆記
※五月 讀書筆記
※《練習》讀書筆記
※《定位》讀書筆記