重構 改善既有程式的設計第二版
讀書心得(未完
本書不厚,第一版範例語言為 Java,
第二版改為大家熟悉的 JavaScript
而且為中文版本(雖然某些地方翻譯怪怪的
總共有 12 個章節,2、3 章節談論心法、
有異味的程式碼。
剩餘章節都在談論重構的範例及方法。
睡前讀書有助於睡眠
重構:第一個範例
作者秀一波
不太適合從第一章開始讀,建議先讀後面章節再回來看第一章
先看文字重點
良言金句
- 在重構之前,請先準備好堅實的測試程式,測試程式必須是自檢 (self-check) 的。
- 重構就是用小步驟修改程式,讓你在犯錯時,輕鬆的找到 bug 的位置。
- 呆子都寫得出電腦可以了解的程式,但只有優秀的工程師寫得出人看得懂的程式。
- 請在修改程式的時候,遵守 “營地規則”:務必讓基礎程式比你初次見到它時還要健康。
- 決定程式好壞的關鍵在於它有多麽容易被修改。(
罵髒話的次數
夏令營結束後孩子們離開營地,要打掃衛生保持整潔,讓營地比來時更乾淨。
重構的原理
心念
何謂重構
- 對軟體內部結構進行變動,不改變可見行為前提下,提高理解性,降低修改成本。
- 在不改變軟體可見行為前提下,用一系列的重構手法重新架構軟體。
- 如果有人說他在重構時程式碼好幾天不能運作,他肯定不是在重構XD
在重構過程中發現的 bug 也應該在重構之後繼續存在(即使你可以修好它XD)
兩頂帽子
- 加入功能
- 重構
不會在加入功能時修改既有的程式碼,只會加入新的功能。
也會加入測試程式,藉著讓測試正常執行來了解進度。
重構時,只會重組程式碼,刻意不加入功能。不加入任何測試(除非漏了)
除非為了配合介面的變動而更改測試。
新功能
發現重構後新功能更容易加入
重構
加入新功能
新功能正常後發現程式寫法難以理解
重構
為何重構?
跟著做就對了,免得人家笑我不懂
重構可以改善軟體的設計
- 程式碼結構的劣化是累積性的
- 定期重構有助於維持程式碼的好身材
- 消除重複的程式碼
重構可讓軟體更容易了解
我希望他做的事情 與 我告訴他做的事情
花一些時間重構,可讓程式碼更明確的傳達他的意圖,讓未來開發者更開心。
重構可幫我找出 bug
我不是偉大的工程師,我只是有一些好習慣的工程師而已。
重構可以協助提升程式編寫速度?
Design Stamina Hypothesis
何時該進行重構?
無時無刻
三次法則
當你第一次做某件事情,儘管去做;
第二次做類似的事情時,因為已經做過了,雖然你有些猶豫,但還是去做那件重複的事情。
當你第三次做類似的事情時,就重構他吧!
重構種類
- 預備性重構
- 理解性重構
- 打掃性重構
- 計畫性重構與伺機性重構
- 長期重構
- 在程式碼複審時重構
預備性重構
- 最佳時機是在加入新的功能之前
- 不符合你的需求,你就複製貼上
- 複製貼上 這種做法沒有什麼幫助
理解性重構
- 問問自己,是不是可以重構這段程式?
- 藉由重構將腦海中的理解寫到程式碼本身
- 擦去窗戶的灰塵以看清窗外的景色
- 重構對原本可能錯過的事物有更高層次的理解
打掃性重構
- 採取小步驟來避免破壞程式碼
- 就算只完成一半,程式也絕對不會損壞。
計畫性重構與伺機性重構
重構 與 添加新功能 分成兩個不同的版本,分別提交至系統。優點是可以分別 review
跟批准他們
作者認為重構與新功能是交織再一起的,這種作法會移除重構的背後原因
我該如何向主管報告?
如果主管真的懂技術,也可以理解設計耐力假說,向他們證明重構合理性並不難。
或是…不要說出來
何時不該重構?
重構的難題
減緩新功能的完成速度?
測試
老舊的程式碼
資料庫
程式碼異味
“If it stinks, change it.”
暗示
- Mysterious Name(神秘的名稱)
- Duplicated Code(重複的程式碼)
- Long Function(冗長的函式)
- Long Parameter List(冗長的參數列)
- Global Data(全域資料)
- Divergent Change(發散式修改)
- Repeated Switches(重複的切換邏輯)
- Loops(迴圈)
- Comments(過多的註解)
簡單的重構範例
Extract Fuction(提取函式)
function printOwing(ivoce) {
printBanner();
let outstanding = calculateOutstanding();
console.log(`name: ${invoice.customer}`);
console.log(`amountL ${outstaning}`);
}
function printOwing(ivoce) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(outstanding)
function printDetails(outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amountL ${outstaning}`);
}
}
逆操作:Inline Function
Inline Function(將函式內聯)
function getRatig(driver) {
return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}
function moreThanFiveLateDeliveries(driver) {
return driver.numberOfLateDeliveries > 5;
}
function getRatig(driver) {
return (driver.numberOfLateDeliveries > 5) ? 2 : 1;
}
逆操作:Extract Fuction
Extract Variable(提取變數)
return order.quantity * order.itemPrice -
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
Math.min(order.quantity * order.itemPrice * 0.1, 100);
const basePrice = order.quantity * order.itemPrice
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping
Extract Variable(提取變數)
處理類別
class Order {
constructor(aRecord){
this._data = aRecord;
}
get quantity() {return this._data.quantity;}
get itemPrice() {return this._data.itemPrice;}
get price() {
return this.quantity * this.itemPrice -
Math.max(0, this.quantity - 500) * this.itemPrice * 0.05 +
Math.min(this.quantity * this.itemPrice * 0.1, 100);
}
}
class Order {
constructor(aRecord){
this._data = aRecord;
}
get quantity() {return this._data.quantity;}
get itemPrice() {return this._data.itemPrice;}
get price() {
return this.basePrice - this.quantityDiscount + this.shipping;
}
get basePrice() {return this.quantity * this.itemPrice;}
get quantityDiscount() {return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05;}
get shipping() {return Math.min(this.quantity * this.itemPrice * 0.1, 100)}
}
同一段程式,改成類別範例
Inline Variable(內聯變數)
let basePrice = anOrder.basePrice;
return (basePrice > 1000);
return anOrder.basePrice > 1000;
逆操作:Extract Variable
Change Function Declaration
(修改函式宣告式)
function circum(radius) {...}
function circumference(radius) {...}
Encapsulate Variable(封裝變數)
let defaultOwner = {firstName: 'Martin', lastName: 'Fowler'};
let defaultOwnerData = {firstName: 'Martin', lastName: 'Fowler'};
export function defaultOwner() {return defaultOwnerData;}
export function setDeafultOwner(arg) {defaultOwnerData = arg;}
let defaultOwnerData = {firstName: 'Martin', lastName: 'Fowler'};
export function defaultOwner() {return new Person(defaultOwnerData);}
export function setDeafultOwner(arg) {defaultOwnerData = arg;}
class Person {
constructor(data){
this._lastName = data.lastName;
this._firstName = data.firstName;
}
get lastName() {return this._lastName;}
get firstName() {return this._firstName;}
}
Rename Variable(更改變數名稱)
let a = height * width;
let area = height * width;
Introduce Parameter Object
(使用參數物件)
function amountInvoiced(starDate, endDate) {...}
function amountReceived(starDate, endDate) {...}
function amountOverdue(starDate, endDate) {...}
function amountInvoiced(aDateRange)) {...}
function amountReceived(aDateRange)) {...}
function amountOverdue(aDateRange)) {...}
Combine Functions into Class
(將函式移入類別)
function base(aReading) {...}
function taxableCharge(aReading) {...}
function calculateBaseCharge(aReading) {...}
class Reading {
base() {...}
taxableCharge() {...}
calculateBaseCharge() {...}
}
下集待續
心得
重構 改善計有程式的設計(第二版)讀書心得
By jimmy8646
重構 改善計有程式的設計(第二版)讀書心得
- 14