重構 改善既有程式的設計第二版

讀書心得(未完

本書不厚,第一版範例語言為 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