# WORKING EFFECTIVLY WITH LEGACY CODE
<!-- .slide: data-background="https://i.imgur.com/7Kyk8U4.png" -->
---
<!--
telling the details
quoate some text in the book
unit test
solitary. or sociable
補充教材
舉例說明 unit test
add some ddd terms
--->
## About me
<div>
<div style="text-align: left; float: left;">
<p data-markdown>- I'm River</p>
<p data-markdown>- 正在學習 DDD </p>
<p data-markdown>- 後端技術</p>
<p data-markdown>- 及想變的更敏捷</p>
<!-- more Elements -->
</div>
<div style="text-align: right; float: right;">
<!-- <img src= "https://avatars2.githubusercontent.com/u/1509781?s=400&v=4"/> -->
<img src="https://i.imgur.com/7F2RlAr.png" width="400px"/>
</div>
</div>
---
主題
---
- Ch6 時間緊迫,但必須修改
- Ch7 漫長的修改時間
---
## 真的超急的,快幫我改
![老闆進門圖](https://i2.kknews.cc/SIG=38pqlie/rss0004r5117n975rnr.jpg)
<span>
## 我現在就要 RIGHT NOW!!
</span>
---
## 來自老闆 / 客戶 的壓力
<aside class="notes">
說故事,客戶有個很急的需求,今天就要!!!
不管三七二十一,先改再說。遺留技術債。但真的會有時間回去修改嗎?
在有期限的壓力之下,是不是要寫測試就成了難題,無法評估添加特性而要花多少時間。[16 章 - 對程式碼的理解不足]
很多人都會先改再說,心魔告訴自已,以後再來加上測試及重構。但實際上做到的有多少?
久而久之系統變的更難應付。
</aside>
---
## 面對壓力之下的策略
對舊有程式碼以最少的方式變動
撰寫新需求並進行測試
---
## 還記得 legacy code 的定義?
<span>
遺留程式碼就是從其他人那裡得來的程式碼??
</span>
<span>
無法理解,難以修改的程式碼??
</span>
<span>
legacy code is simply code without tests
</span>
---
### 但修改只要 15 分鐘,但編寫測試卻要 2 個小時 ??
<aside class="notes">
但你不會知道當初沒有寫測試的話,後面的工作會花多少時間
況且你不確定你在修改的時後會不會又多了一些 bug
測試的功能 - 節省時間
補捉修改時不小心引入的錯誤(節省時間)
找出程式碼的錯誤
</aside>
<aside class="notes">
### 寫測試的時間成本
超級簡單的變動,但又寫了測試。我們所花的時間是否值得呢
best case - 下一個 iteration 就用到
worst case - 我們幾年後才去變動這一塊程式碼,但修改常常是相對集中的。
### ask adients "pro and con of unit test"
PRO
1. Makes the Process Agile
2. Quality of Code
3. Finds Software Bugs Early
4. Facilitates Changes and Simplifies Integration
5. Provides Documentation
6. Debugging Process
7. Design
8. Reduce Costs
CON
what if the
</aside>
---
## 請小心,好好的寫測試才是王道
<aside class="notes">
試著在 測試控工具中實例化 這個類別。要是不能請參考 Chapter 9, I Can’t Get This Class into a Test Harness, or Chapter 10, I Can’t Run This Method in a Test Harness
如果還是覺得無法承受去解依賴並安置好測試的代價,仔細分析要進行的修改
問自已:
Can you make them by writing fresh code? (with testing code)
但在用的時後還是要小心,隨然新增的程式碼有寫測試,但舊的程式碼還是沒有!!!
</aside>
---
## 6.1 新生方法 Sprout Method
---
:::info
場景:某知名電商辦公室-行銷部
:::
- `行銷 A` : 唉,今天訂單爆量耶,爽
- `行銷 B` : 真的耶,怎麼比昨天多了快一倍
- `行銷 A` : 怪了,今天又沒有行銷活動
- `PM` : [開始檢查訂單] - 完蛋了,怎麼這麼多重複訂單,快跟工程師講
---
:::info
場景:某知名電商辦公室-工程部
:::
- PM: 挫塞,今天超多重複訂單 <br/>
- PM: 這個應該很簡單,快動動你的小指頭。<br/>
- 工程師 S:........
---
![](https://i.imgur.com/LRC1M.jpg)
---
### 找到有問題的程式碼
```java
public class TransactionGate
{
public void postEntries(List entries) {
for (Iterator it = entries.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
entry.postDate();
}
transactionBundle.getListManager().add(entries);
}
...
}
```
---
### 暴力解
```java
public class TransactionGate
{
public void postEntries(List entries) {
List entriesToAdd = new LinkedList();
for (Iterator it = entries.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
if (!transactionBundle.getListManager().hasEntry(entry) {
entry.postDate();
entriesToAdd.add(entry);
}
}
transactionBundle.getListManager().add(entriesToAdd);
}
...
}
```
---
### 改了那些?
重複項目的檢查
![](https://i.imgur.com/ZLQ1L1G.png)
---
比較好一點的解:
加了新生方法 `uniqueEntries`
```java
public class TransactionGate
{
...
List uniqueEntries(List entries) {
List result = new ArrayList();
for (Iterator it = entries.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
if (!transactionBundle.getListManager().hasEntry(entry) {
result.add(entry);
}
}
return result;
}
```
---
原有程式沒有進行太大的修改。
```java
public void postEntries(List entries) {
//********/
List entriesToAdd = uniqueEntries(entries);
//********/
for (Iterator it = entriesToAdd.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
entry.postDate();
}
transactionBundle.getListManager().add(entries);
}
...
}
```
把更新時間及檢查重復的程式碼分離了
---
Steps:
1. 確定修改點
2. 在原方法中寫一個假的呼叫,並把他註解掉
3. 確定你需要原方法中的那些局部變數,並把他作為參數傳給新方法。
4. 確定是否需要回傳什麼值給原方法
5. 使用測試驅動的方式來開發
6. 將原方法中被註解掉的呼叫重新生效
---
## 如果還是不行?
- 依賴關係太惡劣的時後
- 採用 Pass Null (p.123 ~~p.141~~) 的方式
- 再不行還可以用 static function
<aside class="notes">
累積了一堆 static function 之後,有機會建立另外一個新的類別
static 可以不用建立 instance
</aside>
<span>
都在解決難以在測試工具中建立 Instance 的問題
</span>
<aside class="notes">
TODO: static code example
</aside>
---
:::info
Advantages
:::
- 新舊程式碼被清楚的隔離
- 新舊程式碼中有清楚的介面
:::info
Disadvantages
:::
- 放棄了原方法及所屬類別,暫時不打算寫測試且改善他們
- 後人會不理解,為何一個小小的功能要另外寫一個新生方法來實現
---
## 6.2 新生類別
- 依賴關係過於複雜
- 無法在合理的時間內將類別在`測試控制工具(Test harness)`被實例化。
- 在原有的程式中加入全新的職責
<aside class="notes">
cd /Users/river.lin/Documents/workspace/playground/wewl
git checkout a86010452ac40e3987a9063a22afc2fe7a679c2f
git checkout ch6.2.1
npm run test
</aside>
---
下面有一段產生報表的程式
現在要幫這個報表加入表頭,該如何進行?
---
```typescript
class QuarterlyReportGenerator {
...
generate(beginDate: string, endDate: string) {
const reportRule6 = new ReportRule6();
const results: Array<Result> = database.queryResults(beginDate, endDate);
let pageText: string = "<html><head><title>"
"Quarterly Report"
"</title></head><body><table>";
if (results.length != 0) {
for (const it of results){
pageText += "<tr>";
pageText += "<td>" + it.department + "</td>";
pageText += "<td>" + it.manager + "</td>";
pageText += "<td>" + it.netProfit / 100 + "</td>";
pageText += "<td>" + it.operatingExpense / 100 + "</td>";
pageText += "</tr>";
}
} else {
pageText += "No results for this period";
}
pageText += "</table>";
pageText += "</body>";
pageText += "</html>";
return pageText;
}
}
```
---
## 用 Sproud Class 試試
---
:::info
Demo Code
:::
<aside class="notes">
說明 constructor 的參數很多
:::spoiler
branch 6.2
git checkout a86010452ac40e3987a9063a22afc2fe7a679c2f
:::
</aside>
---
### steps
1. 確定修改點
2. 在原方法中建立一個類別的`物件`,並呼叫它的方法,並把他註解掉
3. 確定你需要原方法中的那些局部變數,並把他作為參數傳給新方法。
4. 確定是否需要回傳什麼值給原方法
5. 使用測試驅動的方式來開發
6. 將原方法中被註解掉的呼叫重新生效
---
### 來自同事們的挑戰
<div>
<div style="text-align: left; float: left;">
<p data-markdown>- 這個類別也太好笑了吧,這麼小?</p>
<p data-markdown>- 設計上也沒有好處?</p>
<p data-markdown>- 又引入全新概念,變的更亂。</p>
<!-- more Elements -->
</div>
<img src="http://relationology.co.uk/wp-content/uploads/2014/02/Arguement-e1391789035932.jpg" width="300px"/>
</div>
</div>
---
:::info
Advantages
:::
- 進行侵入性比較強的修改時更有自信。
- 利用抽象化的 interface, 將會有機會把大部份的工作移到有被測試過的新生類別中
- 一個系統的特異點,刺激思考
---
:::info
Disadvantages
:::
- 概念複雜化
- 破壞 programmer 對 code base 中的認知
---
## 6.3 外覆方法
- 在沒有接縫的情況下
- Sproud Method 的另一種選擇
---
在員工計算薪水的程式中加入 `日誌記錄`
```java
public class Employee {
...
public void pay() {
Money amount = new Money();
for (Iterator it = timecards.iterator(); it.hasNext();) {
Timecard card = (Timecard)it.next();
if (payPeriod.contains(date)) {
amount.add(card.getHours() * payRate);
}
}
payDispatcher.pay(this, date, amount);
}
}
```
---
### Wrap Method - 一之型
- 建立一個與原方法同名的新方法,並在新方法中呼叫更名後的原方法
- 外部的呼叫無需進行修改便可添加新行為
---
:::info
demo
:::
---
steps:
1. 確定要修改的方法(method)並將舊方法改名
2. 建立新方法,要注意新方法的簽章(Signatures)要與舊方法的相同
3. 在新方法中呼叫更名後的舊方法
4. 把要添加的新特性編寫一個方法,並在第 2 步中建立的方法中進行呼叫(要以TDD 的方式進行開發)
---
### Wrap Method - 二之型
- 新增一個無人呼叫的新方法
- 修改舊程式呼叫的對象
:::info
demo
:::
---
steps:
1. 確定要修改的方法(method)
2. 把要添加的新特性編寫一個方法(要以TDD 的方式進行開發)
3. 建立另一個新方法來呼叫新舊兩個方法
---
### Kent Beck 說
![](https://i.imgur.com/1A4rVl0.png)
<aside class="notes">
新舊方法無法交融在一起
取名字可能會是一件有點困難的事
</aside>
---
### dispatchPayment ??
---
:::info
Advantages
:::
- 與新生方法比起來外覆方法並不會增加舊方法的`體積`
- 很明顯的讓新功能獨立於既有功能
::: info
Disadvantages
:::
- 有可能會有糟榚的命名
---
## 外覆類別 (Wrap Class)
- 概念上跟 Wrap method 幾乎一模一樣
- 原類別已經太大了
- 要在測試中實例化原類別的成本太高
---
```java
public class Employee {
...
public void pay() {
Money amount = new Money();
for (Iterator it = timecards.iterator(); it.hasNext();) {
Timecard card = (Timecard)it.next();
if (payPeriod.contains(date)) {
amount.add(card.getHours() * payRate);
}
}
payDispatcher.pay(this, date, amount);
}
}
```
---
### 裝飾模式 decorator pattern
![](https://www.codeproject.com/KB/architecture/468951/DecoratorDesignPatternGeneric.gif)
[from www.codeproject.com](https://www.codeproject.com/Tips/468951/Decorator-Design-Pattern-in-Java)
---
![](https://i.imgur.com/CPGBDtg.png)
---
:::info
demo code
:::
---
呼叫的型式會變成
```java
Employee employee
= new BuddyEmployee(
new LoggingEmployee(
new EmployeeImpl()
));
```
<aside class="notes">
The really neat thing is that we can nest the subclasses of ToolControllerDecorator:
但壞處也很明顯,太多層的包覆會讓除錯變的困難。
</aside>
---
### 比較不 Decorator 的方式
```javascript=
class LoggingPayDispatcher
{
private Employee e;
public LoggingPayDispatcher(Employee e) {
this.e = e;
}
public void pay() {
employee.pay();
logPayment();
}
private void logPayment() {
...
}
...
}
```
---
呼叫的型式會變成
```javascript
const employee = new Employee();
const dispatcher = new LoggingPayDispatcher(employee);
dispatcher.pay();
```
---
### steps
1. 確定修改點
2. 建立新類別,Extract Implementation(p.372) or Extract Interface(p.362)
3. 在新的外覆類別中編寫新的方法去實作想要新增的功能,另外再寫一個方法,去執行剛剛的新方法,並呼叫舊的。
4. 在系統中需要新行為的地方進行修改
---
方法 | 使用時機|
---|---|---
Sprout Method |<ul><li>如果現行的方法已經可以清楚的表達它的功能時</li></ul>
Sprout Class |<ul><li>如果現行的方法已經可以清楚的表達它的功能</li><li>原類別在建立時有大量的依賴,無法在測試中實例化。</li></ul>
---
方法 | 使用時機|
---|---|---
Wrap Method|<ul><li>添加新特性的重要性與原方法不相上下時</li><li>所要新增的功能適合放在原類別中</li></ul>
Wrap Class|<ul><li>添加的行為是完全獨立的,不想要讓不相關的行為污染到現有類別</li><li>原類別已經太大了</li></ul>
<aside class="notes">
Wrap class 會多很多檔案,複雜度會高很多
</aside>
---
## 阻力
這麼小的功能,卻要用一個新的 class / method 來實作?
<aside class="notes">
同事間的阻力
主管的壓力
</aside>
---
## 雖小,但好切入
## 一天一點讓自已變更好
- 勿以惡小而為之,勿以善小而不為
- 對於現況的不滿足
- 想要把舊程式碼變的更好!!
---
當真的要面對 Lagecy Code 的時後
:::info
Ch9, I Can’t Get This Class into a Test Harness
Ch20, This Class Is Too Big and I Don’t Want It to Get Any Bigger, are good places to start.
:::
---
# Ch 7. 漫長的修改
---
## 造成修改需要大量時間的原因
- 理解程式碼
- 時滯(Lag Time)
---
## 理解程式碼
因為要理解舊程式碼而需要花上大量的時間
:::info
Ch16, I Don’t Understand the Code Well Enough to Change It
Ch17, My Application Has No Structure, to get some ideas about how to proceed.
:::
---
# 時滯
---
## 時滯的影響 - 大腦的特性
愈短的等待時間,能夠有愈快的反應
<span>
愈小的 lag time 有愈好的效率
</span>
<aside class="notes">
為何要有好的設備?
![](blob:https://www.jetbrains.com/f6606d55-93b8-4d2d-b97f-db1f7ee3c6ab)
[phpstorm live edit](blob:https://www.jetbrains.com/f6606d55-93b8-4d2d-b97f-db1f7ee3c6ab)
[scratchpad](http://scratchpad.io/reflective-police-7934)
</aside>
---
## 造成時滯(lag time)的原因
每次的 compile 都要花上大量的時間
單元測試過於肥大
<aside class="notes">
mogento
</aside>
---
## 如何減少時滯?
- 減小 package 的大小
- 換用效能較高的設備
---
# 解依賴
---
## The Dependency Inversion Principle
[依賴反轉原則](https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99)
<aside class="notes">
[SOLID](https://zh.wikipedia.org/wiki/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1))
</aside>
---
![](https://i.imgur.com/RxqLGwt.png)
---
![](https://i.imgur.com/QrvBGa0.png)
---
![](https://i.imgur.com/lMAYptt.png)
---
![](https://i.imgur.com/fibn3BQ.png)
---
## Pro and Con
減少了 Package 的大小
但增加了概念的複雜度
<aside class="notes">
會增加很多檔案
</aside>
---
## 練習
https://github.com/FongX777/trip-service-kata
![](https://i.imgur.com/DzTzTpU.png)
---
讀書會 Telegram Group
https://t.me/joinchat/L7y14xOmJCgK3Qs3ccykTQ
![](https://i.imgur.com/GbAky5W.png)
---
```
https://github.com/FongX777/trip-service-kata
```
請利用剛剛講的四種方式
1. 請用 ch6 介紹的四種方式,在每次的查詢上加上 log 的功能
2. 請用 ch6 介紹的四種方式,在每次查詢反回 Trip 時,用熱門程度進行排序
(假設 Trip 有個欄位叫作 Popularity)
3. 請用 ch7 介紹的解依賴做法來將 DAO 進行依賴反轉
---
## Resource
DDD Taiwan
https://www.facebook.com/groups/dddtaiwan/?ref=br_rs
讀書會 Telegram Group
https://t.me/joinchat/L7y14xOmJCgK3Qs3ccykTQ
Study Group Github Repo
https://github.com/ddd-tw/2020-legacycode-studygroup
---
# The End
{"metaMigratedAt":"2023-06-15T04:52:31.132Z","metaMigratedFrom":"YAML","title":"WORKING EFFECTIVLY WITH LEGACY CODE","breaks":true,"slideOptions":"{\"allottedMinutes\":40,\"slideNumber\":true}","contributors":"[{\"id\":\"92af3a5a-1c07-45af-9459-135e33c0da3c\",\"add\":25593,\"del\":12984}]"}