# 大話重構
Part3 進階篇
###### 閱讀人:薛威明

###### [重點整理](https://hackmd.io/6aH8UWWQTdanAcNcYp4JlQ)、[投影片影片](https://www.youtube.com/playlist?list=PLDE-E73wU5urgMZvpCEUhbvYvq1-n1Z0Z)、[博客來連結](https://www.books.com.tw/products/0010687580)
---
# Ch12 什麼時候重構
---
## 重構是一種習慣
* 程式可讀性增高
* 程式複用性提高
* 與敏捷開發相輔相成
---
## 兩頂帽子 先重構再擴展
* ∵新功能加入 -> 系統品質下降 -> 重構 / 重頭做
* 務必先重構讓系統維持品質,遵循OCP原則
---
## 緊急任務時 又如何重構
時間不夠、範圍又太深太廣
----
### 做完整的重構設計
----
### 只完成最緊急的部分
* 紀錄未完成開發部分
* 紀錄其他沒時間處理的重複部分
----
#### 遏制糟糕設計蔓延
---
# CH13 測試驅動開發
---
* TDD = Test-Driven Development
* 極限程式設計的重要部分
* XP = eXtreme Programming
---
* 同一次開發,不必刻意遵循OCP原則
* 針對遺留系統重點測試即可(範圍限縮)
* 經常維護
* 複雜度高
* 易出錯
* 建立靜態類別圖分析關係
---
## 個人淺見
* 測試是重構的保險
* 先寫測試後寫測試都比沒有好
* 易出錯、複雜的地方盡量佈滿測試
---
# CH14
# 全面的升級任務
---
## 計畫式設計 vs 演進式設計
* 計畫式設計 = 瀑布式開發。
* 更動規則須寫在合約
* 閉門造車,風險最後顯現
* 計畫趕不上變化
* 演進式設計 = 反覆式開發。
* 搭配自動化測試、系統重構
* 走一步算一步,缺乏長遠的規劃
----
## 恰如其分規劃的演進式開發
* 個人見解
* 大架構粗略的計畫式設計
* 頻繁的演進式設計,每次恰又是小架構的稍縝密計畫式設計
* 為敏捷訂好時程目標,每次敏捷就是小瀑布開發
---
## 風險驅動設計 -> 全面升級
1. 採集和識別風險
2. 每個風險制定解決方案
3. 評估優先順序
4. 有計畫的一步一步改進
---
# CH15
# 我們怎麼擁抱變化
---
## 領域模型分析方法
* 領域模型
* 學習、掌握業務領域的規則
* 圖形化抽象模型
* 一系列類別圖
* 誰來繪製?整個團隊討論
* 分類
1. 原文分析法
2. 領域驅動設計
---
### 1. 原文分析法
Textual Analysis
----
#### 1.1. 產生案例
* 觸發事件
* 前置條件
* 事件流
* 基流
* 分支流
* 替代流
* 後置條件
----

----
#### 1.2. 分析事件流文字描述
* 提取名詞
* 刪除名詞 (不符合核心領域)
----
#### 1.3. 轉換名詞
* 類別
* 類別的屬性
----
#### 1.4. 原文動詞分析
* 類別的方法
* 類別的關聯
----
#### 個人見解
* ~UML開發設計
* User Case -> Class Diagram
* Other UML Diagram
---
### 2. 領域驅動設計
Domain-Driven Design
DDD
----
#### v.s. 原文分析
* 可以客戶參與建模,一起形成統一語言
* 混和語言 = 軟體技術元素 + 業務領域術語
* 圖形化模型
* 類別圖方式描述問題
* 業務視角繪製
----
#### 範例
----
#### 客戶:
* 考核系統是由許多個考核指標組成的
* 每個考核指標代表某項工作的完成情況
指標中有
1. 分母數:時間段應完成的工作量
2. 分子數:時間段正確完成的工作量
3. 過錯數:那些錯誤或沒有按時完成的工作量。
----
#### 需求分析員:
為什麼是分子分母?
#### 客戶:
因為最後要計算**正確率**
用正確率考核單位完成工作的情況
* 畫出考核指標類別,屬性寫下分母數、分子數、過錯數、正確率
----
#### 需求分析員:
每個考核指標都有一個過錯判斷標準,是嗎?
#### 客戶:
* 當然,每個考核指標都有它的**過錯判斷標準**。
* 一個考核指標可能會有多個**過錯行為**
* 每一個過錯行為都有各自的過錯判斷標準
* 任何一個錯了,這個**執法行為**就算過錯啦。
----
#### 需求分析員:
先等等,剛才提到執法行為。
執法行為和考核指標是什麼關係?
#### 客戶:
哦,執法行為嘛
就是執法人員對某個使用者執行的一次業務操作
考核指標中
分母數是所有執法行為個數
分子數就是正確的執法個數
過錯數就是錯誤的執法個數
----
#### 草圖繪製

##### 書中關係圖有誤
----
#### 客戶:
(看了這個草圖有些不太明白)
這個過錯類型是什麼東西?
#### 需求分析員:
過錯類型就是某種類型的過錯行為呀
感覺我們在討論過錯行為的時候似乎有兩重含義
1. 某種過錯類型
2. 這個過錯類型下的具體過錯行為
為了區分這兩重含義
我把它分為過錯類型和過錯行為啦
----
#### 客戶:
哦,不錯不錯。
嗯,這兩個箭頭怎麼跟其他箭頭不一樣
後面還跟了個菱形框?
#### 需求分析員:
哦,這代表的是包含關係
表示一個考核指標可以包含多個類型的過錯行為
----
#### 利用統一語言與客戶交流

----
執法行為與過錯行為描述為一對多的包含關係
透過對大量的考核指標具體需求的分析
發現事情其實並非如此簡單
----
當考核指標只有一種過錯行為時
那非常簡單,這個過錯行為對就是對,錯就是錯
但當考核指標存在多種過錯行為時,情況就複雜了
----
#### 1. 一個執法行為同時包含多種過錯行為
過錯行為可能同時出現在某個執法行為中
每個過錯行為就是一個考核點,錯了整個就判錯
所有的考核點都正確才判正確
eq 填寫表單,所有資料填對了才算對
----

##### 書中關係圖有誤
----
#### 2. 考核指標定義多個過錯行為
#### 但各過錯行為不相容
即每個執法行為只能對應一個過錯行為
不可能同時對應多個過錯行為
----

----
#### 持續學習的開發旅途
* 研發人員不必是專家,也不用一夜成專家
* 透過專家,需要時間學習領域知識
* 軟體隨時間進化升級
----
#### 領域模型 vs 領域層
* 領域模型
* 只考慮需求
* 不考慮技術實現
* 領域層
* 屬於軟體系統的分層
* 盡可能與領域模型相似
* 考慮到技術實現的可行性
---
## 遺留系統中如何應用
----
1. 從遺留使用者手冊開始,操作系統,認識系統
2. 記錄過程,寫案例說明
* 逐漸改善的過程
* 專注在閱讀函式,而非程式碼
* 做**閱讀用**的重構
* 很快會被遺棄,when真重構時
3. 小步快跑,小範圍實施領域模型
4. 系統運行的好好的,就不用修改
---
# CH16 測試的困境
---
## 安全重構
* 不會帶來新Bug的重構
* 不能則永遠只能看熱鬧,無法參與其中
* 尤其遺留系統
* +自動化測試
----
* 資料庫狀態變化
1. 資料庫與被測程式解偶
* 遺留系統難馬上開始
3. 保持每次測試資料庫狀態一致
* WEB、硬體
1. 合理剔除WEB層,再BUS層測試
* 遺留系統難馬上開始
3. 系統測試
* 模擬使用者操作
* QTE (from HP)
---
# 自動化測試
# 無法
# 完全取代
# 手工測試
---
* 大量的Bug依然手工測試發現
* 自動化目的
* 即使內部發生變化
* 外部環境依然一致
* 結果還是正確
* 當條件被打破
1. 手工測試
2. 調整測試程式碼
3. 通過測試
---
## 寫測試
## 開發人員
## 還是測試人員
----
### 開發人員寫
* 單元測試級別上與程式設計實作息息相關
* 可能會忙死 (**測試開發工程師** 新職位的誕生)
* 天生樂天,很少思考什麼情況不OK,不全面
----
### 測試人員寫
* 天生悲觀。測試全面,挑Bug找毛病
* 懸崖測試
* 覆蓋性測試
* 隨機測試
* 探索性測試
* 可能不善寫程式
----
### 敏捷開發團隊
* 打破了開發與測試之間的牆,一起工作
* 職責不斷交替
* 但大多公司,開發就是開發,測試就是測試
----
### 截長補短
* 初期由開發人員,甚至測試驅動開發
* 中期交測試人員
* 先手工測試
* 有價值的測試轉開發人員
* 自動化測試
---
# CH17
# 系統重構的評價
---
## 1.超級大函數數量
* 網路參考值:10、20、50行(不算括號、註解)
* JAVA工具:checkstyle、PMD、FindBugs
---
## 2.大物件數據
* NPM:Number of Public Methods for a class
* 一個類別有多少個公共方法
* JAVA工具:checkstyle、PMD、FindBugs
---
## 3.相同程式碼再多處複製
* JAVA工具:checkstyle、PMD、FindBugs
---
## 4.低耦合、高內聚
* 内聚難測量
* 耦合可測量
----
### a.圈複雜度 (cyclomatic complexity)
* ~至少需要多少測試案例,才能跑遍某函式所有程式分支
* if複雜度高->該方法較高耦合與較低內聚
* 流程圖,計算公式V(G) = e - n + 2
* e:邊、n:點
* 網路建議不要超過10
* 複雜度反映判定節點上,即流程圖的區域數
* V(G) = 區域數 = 判定節點數 + 1
----
#### 範例

----
* 分析上常常是類別級別
* 總圈複雜度
* 平均圈複雜度
* JAVA工具:JavaNCSS、Cobertura、ckjm
----
### b.傳入/傳出耦合
* 傳入耦合 afferent coupling
* 被import
* 較有價值
* 高表示設計合理
* 傳出耦合 efferent coupling
* import別人
* 評價簡單
* 高表示分拆與解偶
* 不穩定性 = 傳出耦合度 / 總耦合度
* 依賴其他類別程度
----
### c.繼承
* DIT:Depth of Inheritance Tree
* 繼承樹深度
* NOC:Number of Children
* 繼承樹子節點的數量
* 兩指標高表示**繼承氾濫**
---
# 附錄 重構方法
---
## 1 重新組織函數
1. 抽取方法
2. 行內方法
* 抽取方法反程序
4. 行內臨時變數
* 不透過中間變數直接使用方法
5. 以查詢替換臨時變數
* 將運算邏輯抽成方法直接使用
7. 引入解釋性變數
* 複雜運算式拆斷並命名變數
----
6. 分解臨時變數
* 同一變數多次使用,改為創造許多個解釋性變數使用
7. 移除對參數的設定
* 方法傳入的參數不修改,而是回傳一份新的
8. 以方法物件替代方法
* 將方法拉出成為方法類別使用
9. 替換演算法
---
## 2 在物件間遷移
1. 遷移方法
* 依職責把方法搬到對應的類別中
2. 遷移欄位
* 依職責把欄位搬到對應的類別中
3. 抽取類別
4. 行內類別
* 抽取類別反向操作
----
5. 隱藏的委託關係
* 同時使用有潛藏關係的兩類別
* 僅使用其中一個,透過其控制另一個
6. 移除中間人
* 隱藏的委託關係的反向操作
7. 引入外加的方法
* 方法多載等,使操作方法的程序簡化
8. 引入本地擴展
* 繼承類別達到某些方法類別的存取
* OCP
---
## 3 重新組織資料
1. 自封裝欄位
* 使用getter & setter
2. 以物件取代數值
* eq 地址 address、city、street
3. 將值物件改為物件的參考
* 提供Repository,避免大量創造物件
4. 將物件的參考改為值物件
* 避免操作上干擾,降低複雜度
----
5. 以物件取代陣列
* object[] -> class
6. 複製被觀察資料
7. 單向關聯變為雙向
* 員工有部門欄位
* 讓部門也有員工欄位
* 易搜尋
8. 雙向關聯變為單向
* 避免閉環
----
9. 特殊數字改為特定符號常數
10. 封裝欄位
11. 封裝集合物件
* add & remove
12. 將記錄替換成值物件
* xml、json轉換
----
13. 由類別替代類型代碼
* 使用類別或列舉取代類型代碼(string)
14. 由子類別替代類型代碼
* eq 員工類型:程式設計師、銷售員
15. 由狀態/策略模式替代類型代碼
* eq 員工類型算薪水,運用不同策略發薪
16. 替換子類別為欄位
* 繼承氾濫
* eq 男&女類別 -> 人類(性別)
---
## 4 簡化條件運算式
1. 分解條件運算式
* 條件式封裝方法或類別,用解釋語句命名
2. 合併條件運算式
* 返回結果相同的分支合併
3. 合併條件中重覆的部分
* 分支內重覆部分拉到條件式外
4. 移除控制標記
* 迴圈中常透過變數當Flag,將其改回判斷式
```csharp=
bool flag = true ; while( ... ) {
while( flag ){ ...
... if( ... ) break ;
if( ... ) flag = false ; }
}
```
----
5. 將巢狀條件替換為多條檢查並返回之語句
* retutn寫在各分支,而非函式尾端
6. 用多型替代條件
* 條件式程式封裝子類繼承共同父類或介面
7. 引入null物件
* 執行結果可能為null,但後續不但被操作
* 會需要一值被檢查null,不如製做null物件
8. 引入斷言
* 將複雜判斷式封裝成函式
---
## 5. 簡化呼叫方法
1. 重新命名方法名稱
2. 增加參數
3. 縮減參數
4. 分離查詢與更新
* 將函式分拆成查詢函式及更新函式
----
5. 參數化方法
* 類別中多個方法相似,將其合併
* 建立一個參數類別囊括所有變數
* 透過參數來表達不同的地方
6. 將參數替換成明確的方法
* 利用解析參數達到執行不同功能
* 直接分拆成多個方法,解析留給外部
7. 保持物件完整
* 某物件被讀取多個屬性
* 不如整個物件被讀取
8. 將參數替換成方法
* A()->B()->C()
* 不如 A( B( C() ) )
* 函式簡潔
----
9. 引入參數物件
* 眾多的參數包成值物件
10. 移除set
* 建構時設定就不會在動的屬性
11. 隱藏方法
* 不被外部用->private
12. 將建構函數替換為工廠方法
* 工廠透過擴充決定new、clone、singleton
----
13. 封裝向下轉型
* 竟量將轉型封裝在函式內部
* 避免客戶程式完成
14. 將錯誤碼替換為拋出例外
* 與其回傳魔鬼數字或字串不如拋例外
* 可讀性
15. 將例外代碼替換為測試代碼
* 用判斷語句避免例外發生
---
## 6. 有關繼承處理
1. 上移欄位
* 欄位移到父類
2. 上移方法
3. 上移建構函數
4. 下移方法
5. 下移欄位
----
6. 抽取子類別
* 擴展功能
7. 抽取父類別
8. 抽取介面
9. 摺疊繼承體
* 抽取父類別反向程序
* 父子類別差異過小
* 降低程式廣度或深度
----
10. 塑造樣板函數
* SOP化
* 抽取父類執行順序
* 實作子類完成不同需求
11. 將繼承替換為代理
* 解決繼承氾濫、多繼承的方法
* 取消繼承改用屬性變數參考到原本父類別
* 額外寫函式執行原本父類public和protected方法
12. 將代理替換為繼承
* 將繼承替換為代理反向程序
* 代理會造成方法過多
---
# 結語
* 面對變化:兩頂帽子
* 開發
* X 大設計
* O 小設計 -> 測試 -> 整個系統 -> 重構
* 延伸
* 敏捷開發
* 測試驅動設計
* 持續整合
* 往復式開發
---
[Part2 實踐篇 <<](https://hackmd.io/@voxar/S1efbNudDh)
{"title":"大話重構 Part3 進階篇","contributors":"[{\"id\":\"3877c546-06f0-440b-a33d-99383a2ceb45\",\"add\":8499,\"del\":581}]"}