> 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();
}
}
```