> Just like we have grammar rules and linguistic structures to frame our words and feelings into comprehensible sentences, we have design patterns and principles to shape our code. > > ... > > SOLID, DRY, KISS, and YAGNI are not merely principles but are cornerstones of crafting good code. > > [Good code is like a love letter to the next developer who will maintain it.](https://addyosmani.com/blog/good-code/) 文章中提及的 SOLID, DRY, KISS, YAGNI 都是設計原則,這些設計原則目的都是為了提高程式碼可讀性、提升可維護性。 ## KISS - Keep It Simple & Stupid > The simplest explanation tends to be the right one. **讓你的程式碼盡可能簡單。** Martin Flower 在 [Refactoring](https://martinfowler.com/books/refactoring.html) 這本書中有一句經典的話: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." 任何一個傻瓜都能寫出計算機可以理解的程式,只有寫出人類容易理解的程式才是優秀的工程師。其實不光是程式,這個原則也可以延伸到產品的設計、業務的設計、專案結構的設計上。 ```js // 不符合 KISS function validateUserInput(input) { // 驗證邏輯包含多層巢狀條件 if (input && input.length >= 5 && input.length <= 20 && /^[A-Za-z0-9]+$/.test(input)) { return true; } return false; } // 符合 KISS function validateUserInput(input) { // 驗證邏輯簡單明瞭 return /^[A-Za-z0-9]+$/.test(input); } ``` ## DRY - Don't Repeat Yourself > A basic strategy for reducing complexity to managable units is to divide a system into pieces. **避免程式中過多重複的程式碼**。 如果是相同的邏輯在不同的地方使用,可以抽象出來複用,而不是將一樣的程式碼每個地方都寫一遍。 ```js // 不符合 DRY function calculateCircleArea(radius) { return Math.PI * radius * radius; } function calculateCircleCircumference(radius) { return 2 * Math.PI * radius; } // 符合 DRY function calculateCircleArea(radius) { const pi = Math.PI; const area = pi * radius * radius; const circumference = 2 * pi * radius; return { area, circumference }; } ``` 但以這個例子來說,修改後的函數又不太符合 KISS 了,所以實際開發中需要在 DRY, KISS 之間找到平衡。 ## YAGNI - You Ain't Gonna Need It > Coding is about building things. **只有當你需要的時候才去實現功能,不要過早設計、優化。** 這個原則鼓勵開發時專注於及時提供最重要的功能,透過避免實現不必要的功能以優化開發時間、降低複雜性並提高靈活性。 ## SOLID SOLID 原則又包含了五個原則: - **S**RP — Single Responsibility Principle - **O**CP — Open Closed Principle - **L**SP — Liskov Substitution Principle - **I**SP — Interface Segregation Principle - **D**IP — Dependency Inversion Principle ### SRP — Single Responsibility Principle **單一責任原則。** 一個類別或模組應該僅有一個單一的責任,這有助於確保程式碼結構清晰,更容易維護。 可以通過將不同的功能抽離到不同的類別或模組中來實現。 ```js // 不符合 SRP class User { constructor(name) { this.name = name; } calculateTotalScore() { // 計算使用者的總分數 } sendEmail() { // 發送郵件給使用者 } } // 符合 SRP class User { constructor(name) { this.name = name; } calculateTotalScore() { // 計算使用者的總分數 } } class EmailService { sendEmail(user) { // 發送郵件給使用者 } } ``` ### OCP — Open Closed Principle **開放/封閉原則。** - close: 要有一個不會改變的抽象 - open: 要能夠提供彈性的實作 大概意思為當需要添加新功能或進行變更時,不應該修改現有的程式碼,而應該通過擴展現有程式碼來實現。 ```js // 定義一個抽象類別 class Shape { constructor() { if (new.target === Shape) { throw new Error("Shape 類別是抽象的,不能直接實例化。"); } } // 定義抽象方法 calculateArea() { throw new Error("子類別必須實現 calculateArea 方法。"); } } // 具體的子類別實現抽象方法 class Circle extends Shape { constructor(radius) { super(); this.radius = radius; } calculateArea() { return Math.PI * this.radius * this.radius; } } class Rectangle extends Shape { constructor(width, height) { super(); this.width = width; this.height = height; } calculateArea() { return this.width * this.height; } } ``` > 推薦閱讀:[深入淺出開放封閉原則 Open-Closed Principle](https://www.jyt0532.com/2020/03/19/ocp/) ### LSP — Liskov Substitution Principle **里氏替換原則。** 子類別應該能夠替換其父類別,而不會影響系統的正確性。 這有助於確保繼承層次結構的一致性。 ```js // 不符合 LSP class Bird { fly() { // 實現飛行邏輯 } } class Ostrich extends Bird { // 鴕鳥無法飛行,但必須實現 fly 方法 } // 符合 LSP class Bird { // 父類別不強制實現 fly 方法 } class Sparrow extends Bird { fly() { // 實現飛行邏輯 } } class Ostrich extends Bird { // 鴕鳥不需要實現 fly 方法 } ``` ### ISP — Interface Segregation Principle **介面分離原則。** 不應強迫類別實現它們不需要使用的介面。 這有助於避免過多的依賴和對不必要方法的實現。 ```js // 不符合 ISP class Worker { work() { // 執行工作 } eat() { // 這個方法不應該存在於 Worker 類別 } } // 符合 ISP class Worker { work() { // 執行工作 } } class Eater { eat() { // 執行進食 } } ``` ### DIP — Dependency Inversion Principle **相依反轉原則。** 高層次模組不應該依賴於低層次模組,兩者都應該依賴於抽象。 這有助於實現**鬆散耦合**。 ```js // 不符合 DIP class LightBulb { turnOn() { // 打開燈泡 } } class Switch { constructor(bulb) { this.bulb = bulb; } operate() { this.bulb.turnOn(); } } // 符合 DIP class Device { operate() {} } class LightBulb extends Device { turnOn() { // 打開燈泡 } } class Switch { constructor(device) { this.device = device; } operate() { this.device.operate(); } } ```