跳至主要內容

🧱: 重構(Refactoring)

🤔Why It’s Worth Reading

  軟體建構出一個世界,釐清出各元件要負的責任。這些輪廓和分工會隨著外在需求變動而對應調整。除此之外,撰寫軟體的是會犯錯、見識有限的人類,軟體品質要能提升,也得等作者提升知識與技術後才能發生。換言之,持續變動是軟體必有的行為,也是軟體的價值所在-它能像個有機體,即便長大、成熟,仍能富有彈性與韌性。

  重構(Refactoring),是指不改變可見行為變動軟體內部,為增進程式碼的可讀性,降低維護成本。作者Martin Fowler列舉出他常用的61條重構手法,搭配詳細解釋的範例,讓讀者深刻了解這些重構手法的操作動機,做法,和注意事項。

  善於重構,如同善於呈現事物簡潔美好的那一面;學習大師如何重構,就如同學習如何畫出一幅美麗畫作。對於「重構」這本書,我的推薦指數⭐⭐⭐⭐⭐,每位有志將程式碼寫好的工程師都適合買來收藏😊。

ℹ️

📖1: 前言

🔖1: 重構:第一個範例

🎯:

  作者使用一個簡單範例來示範如何重構,其中原則是獨立切分成小幅度的變動。在範例中的處理順序如下:

  1. 準備好測試程式碼:能確認重構後的程式仍運作正常
  2. 拆接程式碼為不同完整行為的區塊,每次皆用測試驗證功能仍正常
    • 若能讓變數意思更清楚,重新命名(像把函式回傳值命為result
    • 調整或移除區域變數,讓程式碼更易被提取
  3. 將處理邏輯依據不同任務階段(像是計算與格式化)拆成兩個檔案
  4. 用資料類型(Type)區隔相異計算狀況,再用多型計算器(Calculator)處理對應任務

💡:

  • 函式名稱取得好,人不需看內文就能了解其功能
  • 要新增功能卻因結構複雜而困難重重時,代表要先重構
  • 重構時不需考慮性能,完成後再來調整
  • 判斷程式好壞的關鍵為是否清楚明瞭,而易修改

🔖2: 重構的原理

🎯:

  作者討論重構的意思(what)、為何需要它(why)、何時該發生(when)、和與其相關議題(who):

  • 什麼是重構:
    • 名詞:不改變可見行為的前提下變動軟體內部-增進可讀性降低修改成本
    • 動詞:用一系列的手法重新架構軟體,過程中不應加入新功能
  • 重構的好處:
    • 改善架構設計:程式碼結構會逐漸劣化,不重構只會加快劣化速度
    • 軟體更易被理解:將軟體被期待要做的,跟軟體被告知要做的保持一致
    • 幫助找出bug:在過程中理解結構,釐清假設,進而找出bug
    • 提高開發速度:良好的模組化能讓開發者理解一小處後就能加以修改
  • 何時該重構:
    • 新增功能前(最佳時機)
    • 修改程式碼前(了解作用)
    • 打掃程式碼(當前結構糟糕)
    • 伺機性重構(與其他工作一同進行)
    • 長時程重構(一段時間內碰到相關區域時都稍微重構)
    • Code Review(自己重構後,就能給出更好建議)
  • 重構時會遇到的難題
    • 減慢功能新增速度:依據專業做對應判斷,真相往往是人們過少而非過度重構
    • 不具備完整存取權限:保留舊介面並標註棄用(deprecated),之後再完整刪除
    • 在分支(Branch)合併時所產生衝突(Conflicts)實施CI防止各分支版本過大差異
    • 程式碼尚未具備自檢(self-testing)性質:確認每次重構皆合法,是CD要素之一
    • 程式碼過於老舊:先嘗試尋找可插入測試的接縫,提高重構的信心
    • 處理資料庫:將變動放入綱目(Schema),用migration script存取

💡:

  • 小步驟地重構能快速寫出好程式,且不需花時間除錯
  • 醜陋程式碼須重構,優秀程式碼也需大量重構

🔖3: 程式碼異味

🎯

  作者列出常見的程式碼怪味,和建議的處理方法:

怪味道說明處理方法附註
Mysterious Name好名稱是程式碼清楚的關鍵重新命名若對某物苦思不出好名稱,往往代表一定程度的設計不良
Duplicated Code在不同處看到相同的程式結構提取相同邏輯,統一起來
Long Function長度不是標準,而是考量它的What與How的距離有多遠拆分成小函式並給予良好名稱,你因此不需查看內容小函式具備間接關係的所有好處(解釋性、共用性與選擇性)
Long Parameter List將多個參數組成一個類別
Global Data它可在任何地方被修改,卻無法輕易找出用存取函式封裝它,進而知道誰修改了它。這樣也進階限縮範圍,讓模組內的程式碼才能看到他
Mutable Data作用域越大,風險也越大用存取函式封裝
與其他邏輯清楚區隔
像是Functional Programming的理念就是永遠不該更改資料
Divergent Change模組常因不同原因而被用不同方式修改拆分邏輯成不同階段(Phase)或類別(Class),建立明確的領域邊界
Shotgun Surgery每次的更動都散落在各處使用Inline重構,即便方法變冗長或類別變大。之後再提取拆分即可
Feature Envy不同函式或資料間的互動過於頻繁將會一起變化的放在一起
Data Clumps資料項目成群結隊地出場將它們集合成一個類別
Primitive Obession過度使用基本型態,就無法:賦予明確意義、重用對應所需函式改為具備意義的型態
Repeated SwitchesSwtich本身沒有壞處,但我們要加入一個子句時,就得修改每個同群件的切換邏輯使用多型,它能幫你打造更文明的樣貌
Loops往往有更好的做法來達成目的用pipeline operation,像是filter和map
Lazy Element為了將來可能有用而存在的元素使用Inline 函式或類別
使用繼承
Speculative Generality函式或類別只被測試程式使用移除它們
Temporary Field類別中的一些欄位只在特定情況下被使用將它們放入一個新類別或替代類別類別的欄位任何情況下都該被用到
Message Chains用值處與值形成緊密的長條結構。當物件關係變化,使用方也就得做對應修改初步可以重構,讓長鏈各點都能執行此動作。
最好則是先瞭解生物件的最終用途,再把過程抽取成函式
Middle Man封裝往往伴隨著委託,而委託可能會被濫用,中間存在無意義也沒貢獻的中間人用Inline函式將它們加到呼叫方,或用繼承塞到物件裡
Insider Trading模組間過度且隱諱地交換資料用第三個或中介模組來斷開緊密耦合
Large Class類別具有太多欄位依據使用方法拆解數個子類別
Alternative Classes with Different Interfaces有些類別可藉繼承互有關係更改函式簽名、
移動函式、
拆出父類別
Data Class資料類別過於單純,沒有任何相關行為封裝資料,限制存取權限
提出存取它的共同行為納為當中函式
Refused Bequest父類別有子類別不需要的方法或資料* 建立旁系(sibiling)類別
* 將重複內容組成委託,取代父子類別關係
Comments它們往往用來掩飾程式碼的低劣品質將邏輯抽為函式,
或用斷言說明狀態規則

🔖4: 建構測試程式

🎯:

  作者強調編寫自檢測試的重要:我們的測試往往不足,而非過度撰寫。

💡:

  • 所有測試都該完全自動化,且能檢查結果
  • 測試是強大Bug偵測器,減少尋找它們的時間
  • 所有測試都得在該失敗時失敗
  • 思考可能出錯的邊界條件,集中測試它們
  • 別因所寫測試無法找出所有Bug而不寫
  • 收到Bug報告時,先寫出能反映它的測試

📖2: 常見重構

🔖1: Extract Function(提取函式)

📋

  這重構很常見:在了解一段程式碼的功用後,提取為一個函式並據其目的來命名。何時該做此重構,有人依據長度、重用性,作者認為關鍵為「分開意圖與實作」-如果我們得費心查看程式碼內容才能了解它在做什麼,那就適合提取。

  基於此原則,程式碼超過六行都會讓作者考慮提取為函式,而當今編譯器也讓我們不需擔心這會影響效能。做得好,函式名稱會讓程式碼本身即為一份文件

if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5]
    sum = 0
    for number in numbers:
        sum += number
    print(sum)
def calculate_sum(numbers):
    sum = 0
    for number in numbers:
        sum += number
    return sum

if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5]
    result = calculate_sum(numbers)
    print(result)

🔖2: Inline Function(內聯函式)

📋

  必要的間接層很有幫助,沒必要的反而礙眼。當程式碼有太多間接層時(各函式僅委託工作給其他函式,讓人迷失方向),就是內聯函式的時機。

def calculate_product(x, y):
    return x * y


if __name__ == "__main__":
    result = calculate_product(5, 10)
    print(result)
if __name__ == "__main__":
    result = 5 * 10
    print(result)

🔖3: Extract Variable(提取變數)

📋

  遇到複雜且難以理解的運算式時,用區域變數來命名就能幫助了解其目的。

if __name__ == "__main__":
    print((85 * 10) / 5 + 20)
if __name__ == "__main__":
    base_calculation = (85 * 10) / 5
    result = base_calculation + 20
    print(result)

🔖4: Inline Variable(內聯變數)

📋

  若運算式本身已傳達充足資訊,就不需要一個名稱來解釋,適合將其內聯。

if __name__ == "__main__":
    product = 85 * 10
    result = product / 5 + 20
    print(result)
if __name__ == "__main__":
    print((85 * 10) / 5 + 20)

🔖5: Change Function Declaration(修改宣告式)

📋

  函示宣告式顯出程式如何互相結合,像是身體的關節,具備很大影響力。好名稱可讓人只藉它就知其函式作用,進而找出改善整體程式碼的方向。

def add_numbers(x, y):
    return x + y


if __name__ == "__main__":
    result = add_numbers(5, 3)
    print(result)
def calculate_sum(x, y):
    return x + y


if __name__ == "__main__":
    result = calculate_sum(5, 3)
    print(result)

🔖6: Encapsulate Variable(封裝變數)

📋

  相比重構函式,重構資料較為困難,我們沒法用類似轉傳函式來簡化過程。封裝資料成為移動運用廣泛資料的一種做法,我們因此能將工作從「重新組織資料」轉為較易的「重新組織函式」。

  封裝資料也得到「在明確地點監視資料的變動與使用」這好處,這也是OOP強調將物件資料維持私用(減少能見度)的原因。不可變的資料不用特別封裝,因為它不會在任何過程中被改變,也沒有程式碼會取得過期資料,不變性是強大的防腐劑

config_value = 50

if __name__ == "__main__":
    print(config_value)
class Configuration:
    def __init__(self):
        self._config_value = 50

    @property
    def config_value(self):
        return self._config_value

    @config_value.setter
    def config_value(self, value):
        self._config_value = value


if __name__ == "__main__":
    config = Configuration()
    print(config.config_value)
    config.config_value = 100
    print(config.config_value)

🔖7: Rename Variable(重新命名變數)

📋

  具好名稱的變數能解釋它要做什麼。取錯名稱的原因很多(沒仔細思考、閱讀經驗不足導致不夠理解問題、程式碼目的會隨使用者需求而變),但我們仍得堅持住品質,這重要性取決於變數有多被廣泛使用

if __name__ == "__main__":
    x = "Hello, world!"
    print(x)
if __name__ == "__main__":
    message = "Hello, world!"
    print(message)

🔖8: Introduce Parameter Object(引入參數物件)

📋

  當一群資料持續在不同函式間成群出現,就很適合組成一個資料結構。這樣做可以顯出它們的關係,也可縮短呼叫函數的參數列,更能幫助之後的深層修改

  組織起資料群與呼叫函式後,就能提升結構的抽象樣貌,大幅簡化我們對此領域的理解。這些都得先從引入參數物件開始。

def process_data(name, age, email):
    print(f"Name: {name}, Age: {age}, Email: {email}")


if __name__ == "__main__":
    process_data("Alice", 30, "alice@example.com")
class PersonData:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email


def process_data(person):
    print(f"Name: {person.name}, Age: {person.age}, Email: {person.email}")


if __name__ == "__main__":
    alice = PersonData("Alice", 30, "alice@example.com")
    process_data(alice)

🔖9: Combine Functions into Class(將函式移入類別)

📋

  類別已為一種基本架構,能將資料與函式放到同個環境裡。當有組函式密切合作,處理同個資料體時,組成類別能清楚展示呼叫環境移除對應引數,並再找出其他計算方法,重構到新類別中。

def add_numbers(x, y):
    return x + y


def subtract_numbers(x, y):
    return x - y


if __name__ == "__main__":
    sum_result = add_numbers(10, 5)
    subtract_result = subtract_numbers(10, 5)
    print(f"Sum: {sum_result}, Subtract: {subtract_result}")
class Calculator:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def add(self):
        return self.x + self.y

    def subtract(self):
        return self.x - self.y


if __name__ == "__main__":
    calc = Calculator(10, 5)
    sum_result = calc.add()
    subtract_result = calc.subtract()
    print(f"Sum: {sum_result}, Subtract: {subtract_result}")

🔖10: Combine Functions into Transform(組函式為轉換函式)

📋

  面對資料被轉成衍生資訊,被多處使用的狀況,我們可將這類重複運算邏輯放在一起,組函式為轉換函式就是其一作法。

  這樣避免了重複邏輯,驗證整個計算過程也只需檢查此函式。相比於將函式移入類別,此做法適用於來源資料不會被更新

def to_upper(text):
    return text.upper()


def reverse_text(text):
    return text[::-1]


if __name__ == "__main__":
    input_text = "hello"
    transformed_text = reverse_text(to_upper(input_text))
    print(f"Transformed Text: {transformed_text}")
def transform_text(text):
    return text.upper()[::-1]


if __name__ == "__main__":
    input_text = "hello"
    transformed_text = transform_text(input_text)
    print(f"Transformed Text: {transformed_text}")

🔖11: Split Phase(拆成不同階段)

📋

  遇到一段處理不同事的程式碼時,拆成不同模組。這樣能在之後變動中獨立處理單一主題,不需在意另個模組細節。編譯器即應用此一原則,它不斷地接受一段文字(高階程式語言)並轉化成可執行形式(像是特定硬體的目的碼)。

  這樣做的另個好處,是能凸顯出一段程式會在不同階段不同的資料與函式

def process_order(data):
    quantity = data["quantity"]
    price = data["price"]
    total = quantity * price
    if data["type"] == "book":
        tax_rate = 0.1
    else:
        tax_rate = 0.2
    tax = total * tax_rate
    return total + tax


if __name__ == "__main__":
    order_data = {"type": "book", "quantity": 4, "price": 150}
    total_cost = process_order(order_data)
    print(f"Total cost: {total_cost}")
def calculate_base_total(quantity, price):
    return quantity * price


def calculate_total_with_tax(base_total, item_type):
    if item_type == "book":
        tax_rate = 0.1
    else:
        tax_rate = 0.2
    tax = base_total * tax_rate
    return base_total + tax


if __name__ == "__main__":
    order_data = {"type": "book", "quantity": 4, "price": 150}
    base_total = calculate_base_total(order_data["quantity"], order_data["price"])
    total_cost = calculate_total_with_tax(base_total, order_data["type"])
    print(f"Total cost: {total_cost}")

📖3: 封裝類的重構

🔖12: Encapsulate Record(封裝記錄)

📋

  儲存資料的選擇上,作者建議用物件封裝可變資料,使用者就不用在乎裡面樣貌,更改相關名稱也能階段式完成;用記錄封裝不可變資料,像是用hashmaphashmapdictionary等。

  在小範圍用這些記錄不太會有問題,但隨著被用範圍擴大,結構隱晦這缺點就值得封裝它們為資料類別(Data Class)

employee = {
    "name": "John Doe",
    "age": 30,
    "department": "Finance"
}


if __name__ == "__main__":
    print(f"Employee Name: {employee['name']}, Department: {employee['department']}")
class Employee:
    def __init__(self, name, age, department):
        self.name = name
        self.age = age
        self.department = department

if __name__ == "__main__":
    employee = Employee("John Doe", 30, "Finance")
    print(f"Employee Name: {employee.name}, Department: {employee.department}")

🔖13: Encapsulate Collection(封裝集合)

📋

  封裝可變資料有很多好處,但面對集合類別時得避免提供get即得到集合本身的錯誤。使用者可能藉此繞開設計干預,直接更改當中成員。

  我們可以提供替代get,回傳集合副本。這類設計的重點是保持一致性:只採取一種機制,讓任何集合存取函式被呼叫時,使用者都能習慣和預期其行為。

project_tasks = ["Define requirements", "Develop prototype", "Conduct testing"]

if __name__ == "__main__":
    project_tasks.append("Deployment")
    print("Project Tasks:", project_tasks)
class Project:
    def __init__(self):
        self._tasks = ["Define requirements", "Develop prototype", "Conduct testing"]

    def add_task(self, task):
        self._tasks.append(task)

    def get_tasks(self):
        return list(self._tasks)  # 返回任務列表的副本以防止外部修改


if __name__ == "__main__":
    project = Project()
    project.add_task("Deployment")
    print("Project Tasks:", project.get_tasks())

🔖14: Replace Primitive with Object(替換基本元素為物件)

📋

  初期的簡單資料,日後可能會出現對它的多種操作,這時替換它們成為一個新類別。乍看之下可能不必要也沒什麼,但對程式的基礎影響可能超乎想像。

email = "john.doe@example.com"

if __name__ == "__main__":
    if "@example.com" in email:
        print("Email is from example.com")
    else:
        print("Email is from another domain")
class Email:
    def __init__(self, address):
        self.address = address

    def is_from_domain(self, domain):
        return domain in self.address


if __name__ == "__main__":
    email = Email("john.doe@example.com")
    if email.is_from_domain("@example.com"):
        print("Email is from example.com")
    else:
        print("Email is from another domain")

🔖15: Replace Temp with Query(替換暫時變數為查詢函式)

📋

  暫時變數很方便:能存程式碼所生值,讓其被引用;名稱也能解釋其意義,防止重複程式碼出現。如果能把它改成函式會更好:函式減少引入一個參數其邏輯與原始函式間也設下了明確邊界,並避免此計算邏輯在類似函式中重複出現。

  此做法最能有效處理類別,因為成員都位於共用環境。但它僅適合處理「被計算一次,之後皆為讀取」的暫時變數,不適合處理快照(snapshot)變數

if __name__ == "__main__":
    original_price = 200
    discount_rate = 0.15
    discount_amount = original_price * discount_rate
    discounted_price = original_price - discount_amount
    print(f"Discounted price: {discounted_price}")
class PriceCalculator:
    def __init__(self, original_price, discount_rate):
        self.original_price = original_price
        self.discount_rate = discount_rate

    def discount_amount(self):
        return self.original_price * self.discount_rate

    def discounted_price(self):
        return self.original_price - self.discount_amount()


if __name__ == "__main__":
    calculator = PriceCalculator(200, 0.15)
    print(f"Discounted price: {calculator.discounted_price()}")

🔖16: Extract Class(提取類別)

📋

  類別初期都很守規矩,隨著時間它會責任變重,複雜到失去彈性。拆開它的時機有二:看到資料或方法該放一起有群資料同時改變和互相依賴

  我們也可藉自問「移除某段資料或方法,是否會讓其他欄位或方法變不合理」來做提取依據。

class Employee:
    def __init__(self, name, email, street, city, zip_code):
        self.name = name
        self.email = email
        self.street = street
        self.city = city
        self.zip_code = zip_code

    def print_employee_details(self):
        print(f"Name: {self.name}")
        print(f"Email: {self.email}")
        print(f"Address: {self.street}, {self.city}, {self.zip_code}")


if __name__ == "__main__":
    employee = Employee("John Doe", "john@example.com", "123 Elm St", "Metropolis", "90210")
    employee.print_employee_details()
class Address:
    def __init__(self, street, city, zip_code):
        self.street = street
        self.city = city
        self.zip_code = zip_code

    def print_address(self):
        print(f"Address: {self.street}, {self.city}, {self.zip_code}")


class Employee:
    def __init__(self, name, email, address):
        self.name = name
        self.email = email
        self.address = address

    def print_employee_details(self):
        print(f"Name: {self.name}")
        print(f"Email: {self.email}")
        self.address.print_address()


if __name__ == "__main__":
    address = Address("123 Elm St", "Metropolis", "90210")
    employee = Employee("John Doe", "john@example.com", address)
    employee.print_employee_details()

🔖17: Inline Class(內聯類別)

📋

  這相對於提取類別是個反向操作,會用到它的時機有二:類別因重構而空無一物、產生一對不同功能類別前的暫時做法

class Email:
    def __init__(self, address):
        self.address = address

    def print_email(self):
        print(f"Email: {self.address}")


class Contact:
    def __init__(self, name, email):
        self.name = name
        self.email = Email(email)

    def print_contact_details(self):
        print(f"Name: {self.name}")
        self.email.print_email()


if __name__ == "__main__":
    contact = Contact("John Doe", "john@example.com")
    contact.print_contact_details()
class Contact:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def print_contact_details(self):
        print(f"Name: {self.name}")
        print(f"Email: {self.email}")


if __name__ == "__main__":
    contact = Contact("John Doe", "john@example.com")
    contact.print_contact_details()

🔖18: Hide Delegate(隱藏委託)

📋

  封裝是實現良好模組化的關鍵,它讓模組不需知道系統其他部分,減少系統變化時要修改的地方。

  除了常見的封裝欄位,我們也可以用某物件(Department封裝另個物件Manager)。這樣當Manager被修改時,對應修改只會延伸到Department為止。

class Department:
    def __init__(self, manager):
        self.manager = manager


class Employee:
    def __init__(self, name, department):
        self.name = name
        self.department = department


if __name__ == "__main__":
    department = Department("Alice")
    employee = Employee("Bob", department)
    print(f"Manager: {employee.department.manager}")
class Department:
    def __init__(self, manager):
        self.manager = manager

    def get_manager(self):
        return self.manager


class Employee:
    def __init__(self, name, department):
        self.name = name
        self.department = department

    def get_manager(self):
        return self.department.get_manager()


if __name__ == "__main__":
    department = Department("Alice")
    employee = Employee("Bob", department)
    print(f"Manager: {employee.get_manager()}")

🔖19: Remove Middle Man(移除中間人)

📋

  使用隱藏委託有其代價:為封裝類別加入方法時,外部類別也得新增簡單的委託方法。

  我們不需永遠忍受這類轉傳機制:隨著程式碼變化,我們也可視情況再度移除中間人。

class Department:
    def __init__(self, manager):
        self.manager = manager


class Person:
    def __init__(self, name, department):
        self.name = name
        self.department = department

    def get_manager(self):
        return self.department.manager


if __name__ == "__main__":
    department = Department("Alice")
    person = Person("Bob", department)
    print(f"Manager: {person.get_manager()}")
class Department:
    def __init__(self, manager):
        self.manager = manager


class Person:
    def __init__(self, name, department):
        self.name = name
        self.department = department


if __name__ == "__main__":
    department = Department("Alice")
    person = Person("Bob", department)
    print(f"Manager: {person.department.manager}")

🔖20: Substitute Algorithm(替換演算法)

📋

  許多事都有替代作法,演算法也是如此。直接替換整個演算法會有難度:先拆分成方便修改的東西,之後就能輕鬆替換。

def find_minimum(numbers):
    if not numbers:
        return None
    min_value = numbers[0]
    for number in numbers[1:]:
        if number < min_value:
            min_value = number
    return min_value


if __name__ == "__main__":
    number_list = [5, 2, 9, 1, 5, 6]
    minimum = find_minimum(number_list)
    print(f"The minimum number is: {minimum}")
def find_minimum(numbers):
    return min(numbers) if numbers else None


if __name__ == "__main__":
    number_list = [5, 2, 9, 1, 5, 6]
    minimum = find_minimum(number_list)
    print(f"The minimum number is: {minimum}")

📖4: 移動類的重構

🔖21: Move Function(移動函式)

📋

  要讓軟體具備模組特質,相關元素得在一起。這項任務會動態發生:越了解要做的事,就越知道如何妥善聚集元素。持續地聚集顯出我們理解的持續提升

  移動函式會發生在其參考其他環境的元素量大於當前環境,而這樣做通常能改善封裝。這決定不容易,而越困難的選擇往往越不重要。

class Employee:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def print_employee_details(self):
        print(f"Name: {self.name}, Email: {self.email}")


def send_email(employee, message):
    print(f"Sending email to {employee.email} with message: '{message}'")


if __name__ == "__main__":
    employee = Employee("John Doe", "john@example.com")
    employee.print_employee_details()
    send_email(employee, "Welcome to the team!")
class Employee:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def print_employee_details(self):
        print(f"Name: {self.name}, Email: {self.email}")

    def send_email(self, message):
        print(f"Sending email to {self.email} with message: '{message}'")


if __name__ == "__main__":
    employee = Employee("John Doe", "john@example.com")
    employee.print_employee_details()
    employee.send_email("Welcome to the team!")

🔖22: Move Field(移動欄位)

📋

  程式強健度取決於當中的資料結構。適當的資料結構會讓行為程式碼簡單直接,判斷方法之一是採用領域驅動設計(Domain-driven Design)

  傳遞某記錄給函式時也得傳遞另筆記錄修改某記錄也會更動另筆記錄時,就得考慮移動欄位了。這類重構在類別環境中較易執行,因為資料都被封裝在存取方法後面。

class Employee:
    def __init__(self, name, email, salary):
        self.name = name
        self.email = email
        self.salary = salary


class Payroll:
    def __init__(self, tax_rate):
        self.tax_rate = tax_rate

    def calculate_net_salary(self, employee):
        return employee.salary - (employee.salary * self.tax_rate)


if __name__ == "__main__":
    employee = Employee("John Doe", "john@example.com", 50000)
    payroll = Payroll(0.15)
    net_salary = payroll.calculate_net_salary(employee)
    print(f"Net Salary: {net_salary}")
class Employee:
    def __init__(self, name, email):
        self.name = name
        self.email = email


class Payroll:
    def __init__(self, tax_rate, salary):
        self.tax_rate = tax_rate
        self.salary = salary

    def calculate_net_salary(self):
        return self.salary - (self.salary * self.tax_rate)


if __name__ == "__main__":
    employee = Employee("John Doe", "john@example.com")
    payroll = Payroll(0.15, 50000)
    net_salary = payroll.calculate_net_salary()
    print(f"Net Salary: {net_salary}")

🔖23: Move Statements into Function(移入陳述式至函式)

📋

  移除重複程式碼能有效維護程式碼健康,將每次都隨之執行的程式碼移至函式,以後修改就僅需更動一處,之後調整再移入陳述式至呼叫方即可。

if __name__ == "__main__":
    from datetime import datetime
    current_date = datetime.now()
    formatted_date = current_date.strftime("%Y-%m-%d")
    print(f"Today's date is: {formatted_date}")
def get_formatted_date():
    from datetime import datetime
    current_date = datetime.now()
    return current_date.strftime("%Y-%m-%d")


if __name__ == "__main__":
    formatted_date = get_formatted_date()
    print(f"Today's date is: {formatted_date}")

🔖24: Move Statements to Callers(移入陳述式至呼叫方)

📋

  函式是抽象的基本元素,抽象邊界會隨程式改變跟者移動。當某個行為已被多處使用,但有些地方想改變呼叫方式,我們就可把改變行為從函式移到呼叫方。

def display_greeting():
    greeting = "Hello, World!"
    print(greeting)


if __name__ == "__main__":
    display_greeting()
def get_greeting():
    return "Hello, World!"


if __name__ == "__main__":
    greeting = get_greeting()
    print(greeting)

🔖25: Replace Inline Code with Function Call(替換內聯程式碼為函式呼叫式)

📋

  函式可以包裝行為,用命名解釋目的,消除重複程式碼。

if __name__ == "__main__":
    x1, y1 = 1, 2
    x2, y2 = 4, 6
    distance = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
    print(f"Distance between points: {distance}")
def calculate_distance(x1, y1, x2, y2):
    return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5


if __name__ == "__main__":
    distance = calculate_distance(1, 2, 4, 6)
    print(f"Distance between points: {distance}")

🔖26: Slide Statements(移動陳述式)

📋

  放置相關東西在一起會讓程式碼更易理解,像是在用變數的程式碼前面宣告變數。

if __name__ == "__main__":
    print("Starting process...")
    data = "Data loaded"
    calculations = data + " with calculations"
    print("Data initialized.")
    print(calculations)
if __name__ == "__main__":
    print("Starting process...")
    data = "Data loaded"
    print("Data initialized.")
    calculations = data + " with calculations"
    print(calculations)

🔖27: Split Loop(拆開迴圈)

📋

  有時迴圈一次會做兩件事,只因剛好能同時完成,但這會增加日後修改難度。拆開迴圈後,我們每次就只需了解要修改的行為即可。

  分別考慮重構與優化,先釐清(重構)再優化。即便最後拆開迴圈成為效能瓶頸,再重新放一起也不難。迭代串列往往不會成為瓶頸,但拆開迴圈能引導你做出更好優化

if __name__ == "__main__":
    numbers = [10, 20, 30, 40, 50]
    total = 0
    count = 0
    for number in numbers:
        total += number
        count += 1
    average = total / count
    print(f"Total: {total}, Average: {average}")
if __name__ == "__main__":
    numbers = [10, 20, 30, 40, 50]
    total = 0
    count = 0
    for number in numbers:
        total += number
    for number in numbers:
        count += 1
    average = total / count
    print(f"Total: {total}, Average: {average}")

🔖28: Replace Loop with Pipeline(替換迴圈為流程)

📋

  過往我們會用迴圈迭代物件集合,現代語言則提供集合流程(Collection Pipeline)來改善處理:裡面每項操作(像mapfilter)都接收送出一個集合,讓人從上而下地了解物件流經的步驟。

if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    even_squares = []
    for number in numbers:
        if number % 2 == 0:
            even_squares.append(number * number)
    print(f"Even squares: {even_squares}")
if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    even_squares = [number * number for number in numbers if number % 2 == 0]
    print(f"Even squares: {even_squares}")

🔖29: Remove Dead Code(移除死透程式碼)

📋

  沒用到的程式碼應被移除,因工程師會花額外心力理解它為何存在。即便日後需要它,我們也可用版本控制系統輕鬆將其回復。

if __name__ == "__main__":
    a = 10
    b = 20
    c = a + b
    unused_variable = 100
    print(f"Sum of a and b is: {c}")
if __name__ == "__main__":
    a = 10
    b = 20
    c = a + b
    print(f"Sum of a and b is: {c}")

📖5: 資料類的重構

🔖30: Split Variable(拆開變數)

📋

  變數有多種用途,如果它多次被重賦值,其責任可能已不只一個。

  讓變數只有一個責任,具備多重責任的變數只會讓讀者加深困惑。

if __name__ == "__main__":
    temp = 0
    for i in range(5):
        temp += i
    average = temp / 5
    print(f"Average: {average}")
    temp = "All done"
    print(temp)
if __name__ == "__main__":
    total = 0
    for i in range(5):
        total += i
    average = total / 5
    print(f"Average: {average}")
    message = "All done"
    print(message)

🔖31: Rename Field(更改欄位名稱)

📋

  欄位名稱很重要,它們幫助人們了解程式。隨著經驗增加,對資料的了解也增加時,將新見解植入程式就是件非常重要的事。

class Book:
    def __init__(self, title, author, numPages):
        self.title = title
        self.author = author
        self.numPages = numPages

    def print_book_details(self):
        print(f"Title: {self.title}, Author: {self.author}, Number of Pages: {self.numPages}")


if __name__ == "__main__":
    book = Book("1984", "George Orwell", 328)
    book.print_book_details()
class Book:
    def __init__(self, title, author, number_of_pages):
        self.title = title
        self.author = author
        self.number_of_pages = number_of_ages

    def print_book_details(self):
        print(f"Title: {self.title}, Author: {self.author}, Number of Pages: {self.number_of_pages}")


if __name__ == "__main__":
    book = Book("1984", "George Orwell", 328)
    book.print_book_details()

🔖32: Replace Derived Variable with Query(替換衍生變數為查詢函式)

📋

  可變資料易造成問題:當它與許多程式碼有偶合關係,修改某處就會對另處產生連鎖反應。我們應盡量減少可變資料的作用域

  替換衍生變數為查詢函式是種對應作法。如果來源資料會變或衍生資料的生命週期長,使用物件包含計算式與衍生資料是個好作法;如果來源資料不會再變或衍生資料生命週期短,那用物件或用函式直接轉出新資料都可被接受。

class Inventory:
    def __init__(self):
        self.items = []
        self.item_count = 0

    def add_item(self, item):
        self.items.append(item)
        self.item_count += 1

    def print_inventory(self):
        print(f"Total items: {self.item_count}")
        for item in self.items:
            print(f"Item: {item}")


if __name__ == "__main__":
    inventory = Inventory()
    inventory.add_item("Laptop")
    inventory.add_item("Phone")
    inventory.print_inventory()
class Inventory:
    def __init__(self):
        self.items = []

    def add_item(self, item):
        self.items.append(item)

    def get_item_count(self):
        return len(self.items)

    def print_inventory(self):
        print(f"Total items: {self.get_item_count()}")
        for item in self.items:
            print(f"Item: {item}")


if __name__ == "__main__":
    inventory = Inventory()
    inventory.add_item("Laptop")
    inventory.add_item("Phone")
    inventory.print_inventory()

🔖33: Change Reference to Value(將參考改成值)

📋

  物件中的物件/資料結構,可以是個參考或是獨立值。值物件因為不可變,所以較易理解和處理:外部物件在其改變時必會知道,整體設計也不需管理記憶體連結。

  這在分散(Distributed)與並行(Concurrent)系統特別好用,但不適用於多物件引用同個物件,並想讓其變動都被其他物件看到的情境中。

class Author:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return f"Author({self.name})"


class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def print_book_details(self):
        print(f"Title: {self.title}, Author: {self.author}")


if __name__ == "__main__":
    author = Author("George Orwell")
    book = Book("1984", author)
    book.print_book_details()
class Author:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return f"Author({self.name})"

    def __eq__(self, other):
        if not isinstance(other, Author):
            return NotImplemented
        return self.name == other.name

    def __hash__(self):
        return hash(self.name)


class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = Author(author)

    def print_book_details(self):
        print(f"Title: {self.title}, Author: {self.author}")


if __name__ == "__main__":
    book = Book("1984", "George Orwell")
    book.print_book_details()

🔖34: Change Value to Reference(將值改為參考)

📋

  有些情況(像是訂單)會需要多筆記錄連到同個資料結構,為能讓他們都得到最新數值。使用多筆副本是種方法,但易缺漏更新而讓資料不一致

  此時將值改為參考就是個好選擇:設定好存放區,在其中建立物件,之後就可從任意處到存放區來讀取它。

class Customer:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return f"Customer({self.name})"


class Order:
    def __init__(self, customer_name):
        self.customer = Customer(customer_name)

    def print_order_details(self):
        print(f"Order for: {self.customer}")


if __name__ == "__main__":
    order1 = Order("John Doe")
    order2 = Order("John Doe")
    print(f"Order 1 customer: {order1.customer}")
    print(f"Order 2 customer: {order2.customer}")
class Customer:
    _instances = {}

    @staticmethod
    def get_instance(name):
        if name not in Customer._instances:
            Customer._instances[name] = Customer(name)
        return Customer._instances[name]

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return f"Customer({self.name})"


class Order:
    def __init__(self, customer_name):
        self.customer = Customer.get_instance(customer_name)

    def print_order_details(self):
        print(f"Order for: {self.customer}")


if __name__ == "__main__":
    order1 = Order("John Doe")
    order2 = Order("John Doe")
    print(f"Order 1 customer: {order1.customer}")
    print(f"Order 2 customer: {order2.customer}")

📖6: 條件式的重構

🔖35: Decompose Conditional(分解條件邏輯)

📋

  複雜性往往源於複雜的條件邏輯。我們可對條件各部分,依據其目的來命名:這實屬拆分函式的一類,但因很有價值而再次強調。

if __name__ == "__main__":
    hour = 20  # 當前小時

    if hour < 12:
        print("Good morning!")
    elif hour < 18:
        print("Good afternoon!")
    else:
        print("Good evening!")
def is_morning(hour):
    return hour < 12


def is_afternoon(hour):
    return 12 <= hour < 18


def is_evening(hour):
    return hour >= 18


if __name__ == "__main__":
    hour = 20

    if is_morning(hour):
        print("Good morning!")
    elif is_afternoon(hour):
        print("Good afternoon!")
    else:
        print("Good evening!")

🔖36: Consolidate Conditional Expression(合併條件式)

📋

  結合條件程式碼有兩個好處:表明這些檢查相合導出進階的拆分函式。這樣能把「我在做什麼」的意義拉升到「我為什麼要做」。

  相反地,互相獨立的檢查就不該合併。

def should_send_notification(day, is_holiday, is_weekend):
    if day == "Sunday":
        return False
    if is_holiday:
        return False
    if is_weekend:
        return False
    return True


if __name__ == "__main__":
    notification_status = should_send_notification("Monday", False, False)
    print("Send notification:", notification_status)
def should_send_notification(day, is_holiday, is_weekend):
    if day == "Sunday" or is_holiday or is_weekend:
        return False
    return True


if __name__ == "__main__":
    notification_status = should_send_notification("Monday", False, False)
    print("Send notification:", notification_status)

🔖37: Replace Nested Conditional with Guard Clauses(替換內嵌條件式為防衛敘句)

📋

  條件式通常現於兩種情境:兩分支皆屬正常行為、和一分支為正常,另分支為異常。在一正常一異常的情況,就可以採用防衛敘句(提早return),顯出異常情況非此函式的邏輯核心

def calculate_pay(employee):
    if employee.is_retired:
        if employee.age > 65:
            return 0
        else:
            return employee.pension
    else:
        if employee.is_on_leave:
            return 0
        else:
            return employee.salary


if __name__ == "__main__":
    class Employee:
        def __init__(self, is_retired, age, pension, salary, is_on_leave):
            self.is_retired = is_retired
            self.age = age
            self.pension = pension
            self.salary = salary
            self.is_on_leave = is_on_leave


    employee = Employee(False, 34, 0, 5000, False)
    pay = calculate_pay(employee)
    print(f"Employee pay: {pay}")
def calculate_pay(employee):
    if employee.is_retired:
        if employee.age > 65:
            return 0
        return employee.pension
    if employee.is_on_leave:
        return 0
    return employee.salary


if __name__ == "__main__":
    class Employee:
        def __init__(self, is_retired, age, pension, salary, is_on_leave):
            self.is_retired = is_retired
            self.age = age
            self.pension = pension
            self.salary = salary
            self.is_on_leave = is_on_leave


    employee = Employee(False, 34, 0, 5000, False)
    pay = calculate_pay(employee)
    print(f"Employee pay: {pay}")

🔖38: Replace Conditional with Polymorphism(替換條件式為多型)

📋

  複雜的條件邏輯很難理解,一種改善法是拆解成不同環境,用類別與多型會讓這種分解更明確。

class Vehicle:
    def __init__(self, vehicle_type):
        self.vehicle_type = vehicle_type

    def get_speed(self):
        if self.vehicle_type == "car":
            return 100
        elif self.vehicle_type == "bicycle":
            return 20
        elif self.vehicle_type == "boat":
            return 30
        else:
            return 0


if __name__ == "__main__":
    car = Vehicle("car")
    print(f"Car speed: {car.get_speed()}")

    bicycle = Vehicle("bicycle")
    print(f"Bicycle speed: {bicycle.get_speed()}")

    boat = Vehicle("boat")
    print(f"Boat speed: {boat.get_speed()}")
class Vehicle:
    def get_speed(self):
        return 0


class Car(Vehicle):
    def get_speed(self):
        return 100


class Bicycle(Vehicle):
    def get_speed(self):
        return 20


class Boat(Vehicle):
    def get_speed(self):
        return 30


if __name__ == "__main__":
    car = Car()
    print(f"Car speed: {car.get_speed()}")

    bicycle = Bicycle()
    print(f"Bicycle speed: {bicycle.get_speed()}")

    boat = Boat()
    print(f"Boat speed: {boat.get_speed()}")

🔖39: Introduce Special Case(引入特例)

📋

  當某資料結構被用時,大都檢查其中某值採取同樣行動使用特例元素就能把這類情境換成簡單呼叫式。

  特例能有多種方式呈現:

  • 某物件只會被讀取,就可設為物件常值(Literal),放入所需值
  • 需要存值還要其他行為,就建立一個特殊物件來包含
  • Null Object就是種特例

class Customer:
    def __init__(self, name, plan, is_active):
        self.name = name
        self.plan = plan
        self.is_active = is_active

    def get_billing_plan(self):
        if not self.is_active:
            return "basic"
        return self.plan


if __name__ == "__main__":
    active_customer = Customer("Alice", "premium", True)
    inactive_customer = Customer("Bob", "premium", False)
    print(f"Active customer plan: {active_customer.get_billing_plan()}")
    print(f"Inactive customer plan: {inactive_customer.get_billing_plan()}")
class Customer:
    def __init__(self, name, plan, is_active):
        self.name = name
        self.plan = plan
        self.is_active = is_active

    def get_billing_plan(self):
        return self.plan


class InactiveCustomer(Customer):
    def __init__(self):
        super().__init__(name="N/A", plan="basic", is_active=False)

    def get_billing_plan(self):
        return "basic"


if __name__ == "__main__":
    active_customer = Customer("Alice", "premium", True)
    inactive_customer = InactiveCustomer()
    print(f"Active customer plan: {active_customer.get_billing_plan()}")
    print(f"Inactive customer plan: {inactive_customer.get_billing_plan()}")

🔖40: Introduce Assertion(引入斷言)

📋

  有些程式只該在條件滿足時才執行,但這些條件往往不會具體說明,只能藉由閱讀演算法來推導。比起使用註解,用斷言明言假設是更好的技術做法。

  斷言永遠假定為真,所以系統其他部分不該檢查失敗情況,移除所有斷言程式仍該能正常運作。斷言是個寶貴的溝通工具,雖然用逐步縮小範圍的單元測試會有更好表現,但它們仍提供系統執行到當前會有的假定狀態

def calculate_area(length, width):
    return length * width


if __name__ == "__main__":
    length = 10
    width = -5
    area = calculate_area(length, width)
    print(f"Area: {area}")
def calculate_area(length, width):
    assert length > 0, "Length must be positive"
    assert width > 0, "Width must be positive"
    return length * width


if __name__ == "__main__":
    length = 10
    width = -5
    area = calculate_area(length, width)
    print(f"Area: {area}")

📖7: API類的重構

🔖41: Separate Query from Modifier(分離查詢與修改函式)

📋

  只提供值且無可見副作用的函式,非常寶貴,這也是命令查詢分離原則的精神。之所以強調可見,是因像快取這類雖會改變物件狀態,仍讓每個查詢都回傳相同結果就是被允許的。

def send_and_confirm_email(emails, message):
    sent_emails = []
    for email in emails:
        print(f"Sending email to {email}")
        sent_emails.append(email)
    return sent_emails


if __name__ == "__main__":
    emails = ["john@example.com", "jane@example.com"]
    sent_emails = send_and_confirm_email(emails, "Hello!")
    print(f"Sent emails: {sent_emails}")
def send_email(emails, message):
    for email in emails:
        print(f"Sending email to {email}")


def confirm_sent_emails(emails):
    return emails


if __name__ == "__main__":
    emails = ["john@example.com", "jane@example.com"]
    send_email(emails, "Hello!")
    sent_emails = confirm_sent_emails(emails)
    print(f"Sent emails: {sent_emails}")

🔖42: Parameterize Function(參數化函式)

📋

  當有不同函式具備相似邏輯,僅差在用不同常數值,則可合成同個函式,用參數顯出差異,移除重複。

def print_hello():
    print("Hello!")


def print_goodbye():
    print("Goodbye!")


if __name__ == "__main__":
    print_hello()
    print_goodbye()
def print_greeting(message):
    print(message)


if __name__ == "__main__":
    print_greeting("Hello!")
    print_greeting("Goodbye!")

🔖43: Remove Flag Argument(移除旗標引數)

📋

  旗標引數用來指示函式該執行哪段邏輯,但這種設計會加深理解難度,用明確函式代表我想做的事較易讓人理解

  但是如果有多個旗標引數,則不該直接移除,提升複雜度。先建立簡單函式,並用它們組合出原先邏輯。

def print_message(is_hello):
    if is_hello:
        print("Hello!")
    else:
        print("Goodbye!")


if __name__ == "__main__":
    print_message(True)
    print_message(False)
def print_hello():
    print("Hello!")


def print_goodbye():
    print("Goodbye!")


if __name__ == "__main__":
    print_hello()
    print_goodbye()

🔖44: Preserve Whole Object(保留整個物件)

📋

  當函式需要物件中某些值時,作者喜歡提供整個物件,讓函式自行取用。參數列長度會減少,日後引數更動也不用再做大幅度變動。若發現很多地方都僅用部分記錄呼叫不同函式,就代表這類邏輯為重複,可搬為一個整體邏輯。有時不要做這類重構,當函式與整個記錄處於不同模組,希望它們不互相依賴

  從單一物件拉出值並執行邏輯,通常代表這邏輯應移入整體邏輯;若有些程式都只用同物件的同組子功能,就代表該提取類別

class Range:
    def __init__(self, start, end):
        self.start = start
        self.end = end


def is_in_range(value, start, end):
    return start <= value <= end


if __name__ == "__main__":
    range = Range(1, 10)
    value = 5
    if is_in_range(value, range.start, range.end):
        print(f"{value} is within the range.")
    else:
        print(f"{value} is not within the range.")
class Range:
    def __init__(self, start, end):
        self.start = start
        self.end = end

    def contains(self, value):
        return self.start <= value <= self.end


if __name__ == "__main__":
    range = Range(1, 10)
    value = 5
    if range.contains(value):
        print(f"{value} is within the range.")
    else:
        print(f"{value} is not within the range.")

🔖45: Replace Parameter with Query(替換參數為查詢程式)

📋

  參數列應避免重複和保持簡短,若有資訊能自己取得,且不新增依賴關係,就不用當成引數傳入。

  讓函式具備引用透明性很重要(同參數會得到同行為),這類函式易理解與測試,所以別用會變的全域變數來取代引入參數。

class Employee:
    def __init__(self, name, department):
        self.name = name
        self.department = department

    def calculate_bonus(self, performance_rating):
        if self.department == "Sales":
            if performance_rating > 3:
                return 1000
            return 500
        else:
            if performance_rating > 3:
                return 500
            return 200


if __name__ == "__main__":
    employee = Employee("John Doe", "Sales")
    bonus = employee.calculate_bonus(4)
    print(f"Bonus for {employee.name}: ${bonus}")
class Employee:
    def __init__(self, name, department, performance_rating):
        self.name = name
        self.department = department
        self.performance_rating = performance_rating

    def calculate_bonus(self):
        if self.department == "Sales":
            if self.performance_rating > 3:
                return 1000
            return 500
        else:
            if self.performance_rating > 3:
                return 500
            return 200


if __name__ == "__main__":
    employee = Employee("John Doe", "Sales", 4)
    bonus = employee.calculate_bonus()
    print(f"Bonus for {employee.name}: ${bonus}")

🔖46: Replace Query with Parameter(替換查詢式為參數)

📋

  當函式有不理想的依賴時,適合採取這手法。「將東西都轉為參數」與「共享大量範圍」是兩個極端,我們能藉知識增長而調整出好的平衡。

  替換查詢式為參數,就是請呼叫方自己提供此值,這容易違反介面應方便被用的原則。如何在程式中安排責任是個關鍵,這不容易也非不可變,這也是我們得熟悉它與替換參數為查詢程式的原因。

class Customer:
    def __init__(self, name, location):
        self.name = name
        self.location = location

    def get_location(self):
        return self.location


def calculate_shipping():
    customer = Customer("Alice", "Taipei")
    if customer.get_location() == "Taipei":
        return 10
    else:
        return 20


if __name__ == "__main__":
    shipping_cost = calculate_shipping()
    print(f"Shipping Cost: ${shipping_cost}")
class Customer:
    def __init__(self, name, location):
        self.name = name
        self.location = location


def calculate_shipping(location):
    if location == "Taipei":
        return 10
    else:
        return 20


if __name__ == "__main__":
    customer = Customer("Alice", "Taipei")
    shipping_cost = calculate_shipping(customer.location)
    print(f"Shipping Cost: ${shipping_cost}")

🔖47: Remove Setting Method(移除Set方法)

📋

  物件具備set就代表此欄位可被改變,移除它則代表此物件被建構後就不可被改變

class Account:
    def __init__(self, id):
        self.id = id

    def set_id(self, id):
        self.id = id


if __name__ == "__main__":
    account = Account(123)
    print(f"Initial Account ID: {account.id}")
    account.set_id(456)
    print(f"Updated Account ID: {account.id}")
class Account:
    def __init__(self, id):
        self.id = id


if __name__ == "__main__":
    account = Account(123)
    print(f"Account ID: {account.id}")

🔖48: Replace Constructor with Factory Function(替換建構式為工廠函式)

📋

  建構式有許多限制,也無法用名稱表達意涵,但工廠函式可以。

class Book:
    def __init__(self, title, author, genre):
        self.title = title
        self.author = author
        self.genre = genre


if __name__ == "__main__":
    book = Book("1984", "George Orwell", "Dystopian")
    print(f"Book: {book.title} by {book.author}, Genre: {book.genre}")
class Book:
    def __init__(self, title, author, genre):
        self.title = title
        self.author = author
        self.genre = genre

    @classmethod
    def create_book(cls, title, author, genre):
        return cls(title, author, genre)


if __name__ == "__main__":
    book = Book.create_book("1984", "George Orwell", "Dystopian")
    print(f"Book: {book.title} by {book.author}, Genre: {book.genre}")

🔖49: Replace Function with Command(替換函式為命令物件)

📋

  將函式封裝成命令物件有時很有用,它大多僅有一種方法,其功能就是對這方法的請求與執行。相比於一般函式,命令更加靈活,但也會帶來更多複雜度。

  通常選擇函式即可,在簡單作法仍無法提供所需功能時才改用命令。

def print_details(name, age):
    print(f"Name: {name}, Age: {age}")


if __name__ == "__main__":
    print_details("Alice", 30)
class PrintDetailsCommand:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def execute(self):
        print(f"Name: {self.name}, Age: {self.age}")


if __name__ == "__main__":
    cmd = PrintDetailsCommand("Alice", 30)
    cmd.execute()

🔖50: Replace Command with Function(替換命令物件為函式)

📋

  命令善於複雜的計算,因它能具備不同方法,並讓欄位共享狀態。但若你只要函式去做它該做的,就不用去維護命令對應的複雜度,轉成一般函式即可。

class PrintDetailsCommand:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def execute(self):
        print(f"Name: {self.name}, Age: {self.age}")


if __name__ == "__main__":
    cmd = PrintDetailsCommand("Alice", 30)
    cmd.execute()
def print_details(name, age):
    print(f"Name: {name}, Age: {age}")


if __name__ == "__main__":
    print_details("Alice", 30)

📖8: 繼承類的重構

🔖51: Pull Up Method(提升方法)

📋

  移除重複程式碼很重要,其功能再完善也只是孳生Bug的溫床。提升方法因此重要,但需要穩健的測試來驗證。通常我們會需先做參數化函式提升欄位,才會再採用此重構。

class Vehicle:
    pass


class Car(Vehicle):
    def start_engine(self):
        print("Car engine started.")


class Truck(Vehicle):
    def start_engine(self):
        print("Truck engine started.")


if __name__ == "__main__":
    car = Car()
    truck = Truck()
    car.start_engine()
    truck.start_engine()
class Vehicle:
    def start_engine(self):
        print("Engine started.")


class Car(Vehicle):
    pass


class Truck(Vehicle):
    pass


if __name__ == "__main__":
    car = Car()
    truck = Truck()
    car.start_engine()
    truck.start_engine()

🔖52: Pull Up Field(提升欄位)

📋

  若兩子類別有名稱相似欄位,被使用的方式也相似時,就可採用此手法。這樣除了移除重複的資料宣告外,也將對應行為統一移至超類別中。

class Vehicle:
    pass


class Car(Vehicle):
    def __init__(self):
        self.vehicle_type = "Car"


class Truck(Vehicle):
    def __init__(self):
        self.vehicle_type = "Truck"


if __name__ == "__main__":
    car = Car()
    truck = Truck()
    print(f"This is a {car.vehicle_type}.")
    print(f"This is a {truck.vehicle_type}.")
class Vehicle:
    def __init__(self, vehicle_type):
        self.vehicle_type = vehicle_type


class Car(Vehicle):
    def __init__(self):
        super().__init__("Car")


class Truck(Vehicle):
    def __init__(self):
        super().__init__("Truck")


if __name__ == "__main__":
    car = Car()
    truck = Truck()
    print(f"This is a {car.vehicle_type}.")
    print(f"This is a {truck.vehicle_type}.")

🔖53: Pull Up Constructor Body(提升建構式內文)

📋

  建構式不是普通方法,所以有許多限制。若當下情境用此手法會過於複雜,可試著採用替換建構式為工廠函式

class Vehicle:
    pass


class Car(Vehicle):
    def __init__(self, make, model):
        self.make = make
        self.model = model
        self.wheels = 4


class Truck(Vehicle):
    def __init__(self, make, model):
        self.make = make
        self.model = model
        self.wheels = 6


if __name__ == "__main__":
    car = Car("Toyota", "Corolla")
    truck = Truck("Ford", "F-150")
    print(f"Car: {car.make} {car.model}, Wheels: {car.wheels}")
    print(f"Truck: {truck.make} {truck.model}, Wheels: {truck.wheels}")
class Vehicle:
    def __init__(self, make, model, wheels):
        self.make = make
        self.model = model
        self.wheels = wheels


class Car(Vehicle):
    def __init__(self, make, model):
        super().__init__(make, model, 4)


class Truck(Vehicle):
    def __init__(self, make, model):
        super().__init__(make, model, 6)


if __name__ == "__main__":
    car = Car("Toyota", "Corolla")
    truck = Truck("Ford", "F-150")
    print(f"Car: {car.make} {car.model}, Wheels: {car.wheels}")
    print(f"Truck: {truck.make} {truck.model}, Wheels: {truck.wheels}")

🔖54: Push Down Method(下移方法)

📋

  若超類別中有個方法只與某子類別有關,那下移它是個好選擇,但僅限於呼叫方已知它要用哪個特定子類別時。

class Vehicle:
    def start_engine(self):
        print("Engine started.")


class Car(Vehicle):
    pass


class Motorcycle(Vehicle):
    pass
class Vehicle:
    pass


class Car(Vehicle):
    def start_engine(self):
        print("Car engine started.")


class Motorcycle(Vehicle):
    def start_engine(self):
        print("Motorcycle engine started.")

🔖55: Push Down Field(下移欄位)

📋

  若欄位只被一個子類別使用,那就下移吧。

class Vehicle:
    def __init__(self, name):
        self.name = name


class Car(Vehicle):
    def display_name(self):
        print(f"This car is named: {self.name}")


class Motorcycle(Vehicle):
    def display_name(self):
        print(f"This motorcycle is named: {self.name}")
class Vehicle:
    pass


class Car(Vehicle):
    def __init__(self, name):
        self.name = name

    def display_name(self):
        print(f"This car is named: {self.name}")


class Motorcycle(Vehicle):
    def __init__(self, name):
        self.name = name

    def display_name(self):
        print(f"This motorcycle is named: {self.name}")

🔖56: Replace Type Code with Subclasses(替換型別程式碼為子類別)

📋

  軟體中常會遇到需要描述相似的東西:初期可用型別區分,但更好的做法是使用子類別。這樣可以善用多型功能,也可讓某些欄位或函式限縮在特定子類別中

  要替換的對象是整體類別還是當中型別?我們可先將型別換成物件類別,再對其用此手法。

class Employee:
    def __init__(self, name, type_code):
        self.name = name
        self.type_code = type_code

    def get_role(self):
        if self.type_code == 0:
            return "Manager"
        elif self.type_code == 1:
            return "Engineer"
class Employee:
    def __init__(self, name):
        self.name = name


class Manager(Employee):
    def get_role(self):
        return "Manager"


class Engineer(Employee):
    def get_role(self):
        return "Engineer"

🔖57: Remove Subclass(移除子類別)

📋

  子類別具備提供資料結構的變體多型行為等優點。但隨著時間演變,它可能已失去其變異性(Difference),移除它,轉為超類別欄位才能有效降低程式碼複雜度。

class Animal:
    pass


class Dog(Animal):
    def bark(self):
        print("Bark!")


class Cat(Animal):
    def meow(self):
        print("Meow!")
class Animal:
    def __init__(self, animal_type):
        self.animal_type = animal_type

    def make_sound(self):
        if self.animal_type == "dog":
            print("Bark!")
        elif self.animal_type == "cat":
            print("Meow!")

🔖58: Extract Superclass(提取超類別)

📋

  若有兩類別都做相同事,將相似處拉到超類別是個好主意。我們不用在設計初期就依據真實世界的架構來定義繼承關係,在程式演變的過程中逐漸實作也很不錯。

  提取類別委託)也是種做法,而提取超類別(繼承)比較簡單。我們可以先執行後者,日後再用替換子類別為委託類別來解決繼承長期會衍生的混亂。

class Employee:
    def __init__(self, name):
        self.name = name

    def report_hours(self):
        print("Reporting hours")


class Manager:
    def __init__(self, name):
        self.name = name

    def create_report(self):
        print("Creating report")
class StaffMember:
    def __init__(self, name):
        self.name = name

    def report_hours(self):
        print("Reporting hours")


class Employee(StaffMember):
    pass


class Manager(StaffMember):
    def create_report(self):
        print("Creating report")

🔖59: Collapse Hierarchy(摺疊類別階層)

📋

  發現某類別與其父類別差異已小到沒必要分開時,合併它們吧。

class Person:
    def __init__(self, name):
        self.name = name

    def greet(self):
        print(f"Hello, my name is {self.name}")


class Employee(Person):
    def work(self):
        print("Working...")
class Person:
    def __init__(self, name):
        self.name = name

    def greet(self):
        print(f"Hello, my name is {self.name}")

    def work(self):
        print("Working...")

🔖60: Replace Subclass with Delegate(替換子類別為委託類別)

📋

  繼承有其缺點:它只能順著單一路線演化,且會因關係緊密而難以修改

  委託可以解決這類問題,有人因此更偏好它。但多數情況下繼承都已能妥善解決問題,之後將其換成委託也毫無困難,作者建議採用「先繼承後改委託」這策略來設計軟體。

class Renderer:
    def render(self, content):
        print(f"Rendering: {content}")


class HTMLRenderer(Renderer):
    def render(self, content):
        print(f"<html>{content}</html>")


class JSONRenderer(Renderer):
    def render(self, content):
        print(f"{{'content': '{content}'}}")
class Renderer:
    def __init__(self, rendering_strategy):
        self.rendering_strategy = rendering_strategy

    def render(self, content):
        self.rendering_strategy.render(content)


class HTMLRendering:
    def render(self, content):
        print(f"<html>{content}</html>")


class JSONRendering:
    def render(self, content):
        print(f"{{'content': '{content}'}}")

🔖61: Replace Superclass with Delegate(替換超類別為委託類別)

📋

  繼承強大而簡單,但實務上常會帶來混亂與複雜。像是堆疊(Stack)曾被做為串列(List)的子類別,但實應把串列當成堆疊的欄位,將必要操作委託給它即可,因為許多串列的操作並不屬於堆疊範疇。

  使用委託可以明確表達它是個獨立僅保留某功能的東西。權衡之下,作者建議先用繼承,出問題時再用此重構改為委託。

class Logger:
    def log(self, message):
        print(f"Log: {message}")


class FileLogger(Logger):
    def log_to_file(self, message):
        print(f"Writing to file: {message}")
class Logger:
    def log(self, message):
        print(f"Log: {message}")


class FileLogger:
    def __init__(self):
        self.logger = Logger()

    def log_to_file(self, message):
        self.logger.log(message)
        print(f"Writing to file: {message}")

🚀我的回顧

  一個好的軟體開發團隊,程式碼審查(Code Review)為流程要素之一。而好的程式碼審查,往往是相信開發者的改動已符合功能需求,討論會聚焦於將程式碼寫得更好更正確。這些內容大抵就是在討論重構不改變可見行為變動軟體內部,為增進程式碼的可讀性,降低維護成本。依據本書介紹的重構手法去給予建議,有許多好處。除了使用專業術語能加快溝通速度,弭平認知差距之外,更好的做法往往也會在一步步的重構中才逐漸顯露出來。

  儘管作者已用包含多個重構步驟的經典例子來說明每個手法,掌握它們的最好途徑,還是在實際的開發任務中感受重構前後的差異,進而於其他情境中辨識出施展時機。當我們能瞭解作者所附微小提點的意思,就代表我們更像大師一些,而這實屬人生最美好的經驗之一。

分類:BricksComputers & Technology
由 Compete Themes 設計的 Author 佈景主題