# [面試][設計模式]Code Review 會注意哪些事？會依照什麼原則對程式做 Refactoring？

- 作者：林鼎淵（Dean Lin）
- 網址：https://deanlin.net/blog/202110/fullstack-career-notes-day-21/
- 發布：2021-10-06
- 主題：職涯成長
- 系列：全端工程師生存筆記
- 標籤：iT 邦幫忙鐵人賽、面試、設計模式、Design Pattern、Code Review
- 摘要：「沒有最好的程式，只有更好的程式。」在完成功能後 Code Review 是非常重要的事情；不只看自己的程式碼，也要多觀摩其他人的程式碼，這個動作除了可以優化程式碼品質外，還可以幫助團隊了解彼此的工作進度。

> 在功能穩定後，你對程式碼還有要求嗎？

「沒有最好的程式，只有更好的程式。」在完成功能後 Code Review 是非常重要的事情；不只看自己的程式碼，也要多觀摩其他人的程式碼，這個動作除了可以優化程式碼品質外，還可以幫助團隊了解彼此的工作進度。

人無完人，很多時候第一時間想到的解法未必是最好的；解決問題的方法不只一種，可能有更簡潔的寫法或是更好的 Pattern 可以套用，只是開發者不具備這塊的知識儲備；此時 Code Review 就是提升團隊程式水平的方式。

找到需要改善的問題後，`Refactoring（重構）`就是另一個開始；如果為了盡快完成功能而埋頭狂寫，容易導致日後維護以及交接的困難性；專案越大這個問題會越明顯，所以在完成功能後對程式做 Refactoring 是必要的任務，它能提升你日後解決 Bug、需求變更時的工作效率。

## 大綱

1. **Code Review 會注意哪些事？會依照什麼原則對程式做 Refactoring？**

2. 1.1 面試官為什麼會問？
3. 1.2 面試官想從答案確認什麼？
4. 1.3 筆者提供的簡答

5. **回答問題所需具備的知識**

6. 2.1 SOLID 原則
7. 2.2 Design Pattern 是什麼？解決了什麼問題？

8. **衍伸問題**

9. 3.1 有處理 Legacy Code 的經驗嗎？
10. 3.2 有用什麼工具讓團隊有一致的 Coding Style

## 1. Code Review 會注意哪些事？會依照什麼原則對程式做 Refactoring？

### 1.1 面試官為什麼會問？

只要是軟體工程師的職位，這題可以說是超級常見面試題；因為它能問得很淺也能夠問得很深，如果是相對資淺的職位，求職者只要能說出關鍵字並給出基本解釋就能過關；如果是資深的位置，除了關鍵字外，面試官會繼續詢問你在專案中是否有實踐。

### 1.2 面試官想從答案確認什麼？

- 瞭解求職者 Code Review 會在意的點
- 有使用哪些方法對程式做 Refactoring
- 對 SOLID 原則有基礎認知
- 知道 Design Pattern 且能舉出實務上的應用
- 有用過什麼工具讓團隊有一致的 Coding Style

### 1.3 筆者提供的簡答

我會先觀察變數與函式的命名是否符合團隊規範、變數的宣告是否合理（var/let/const 的使用時機）；然後看程式在設計上是否有符合 SOLID 原則；在功能完成後如果發現有合適的 Pattern 可以更好地解決問題，就會再對程式做 Refactoring。

## 2. 回答問題所需具備的知識

其他章節的知識，可能換一個時空背景就用不到了；但今天這個章節的內容，是每位開發人員都需要將其內化成為本能的。

### 2.1 SOLID 原則

SOLID 是一個物件導向的設計原則，核心目標是為了讓程式碼更乾淨、易讀、好維護，讓程式朝著 `Clean Code`（無瑕的程式碼）的方向前進。

- **S（Single Responsibility Principle）單一責任原則**
一次只做好一件事；如果把系統比喻成樂高，那單一責任原則就是樂高的每一塊積木。

- **O（Open-Closed Principle）開放封閉原則**
程式的架構要容易擴充功能；而這個功能的擴充是新增程式，而非修改過去的程式，這麼做可以維持原有程式的穩定性。

- **L（Liskov Substitution Principle）里氏替換原則**
在繼承中衍生的子類別，要能完全繼承父類別的功能；實作時還需顧慮到單一責任原則，避免日後功能拆分或刪減時的困擾。
以軟體的角度來說，使用者期待`版本從 1.0 ➞ 1.1 時行為是一致的，不會更新完舊功能就壞掉`。

> **子類別 Override 時要注意**
> **不可改變父類別先決條件**
> 假設籃球比賽的參與者年齡限制在 15~80 歲；裡面可能區分為青年組、中年組、老年組，青年組的年齡「可以是 15~30」；但「不可以是 14~30」。
> **子類別的後置條件不該被削弱**
> 籃球的得分有「2」分球、「3」分球，都是數字；如果某個比賽改用「三分球」這個字串紀錄得分就是不可以的。

- **I（Interface Segregation Principles）介面隔離原則**
設計程式時不應該預設使用者一定會使用到介面中的多個功能，我們應該盡量讓介面的功能單一；以後端 API 來舉例，我們不要讓一個 API 可以做好多事情，但做每件事情時只用到其中幾個參數。

- **D（Dependency Inversion Principle）依賴反轉原則**
子類別一定會依賴父類別，這樣單向的關係是乾淨的；如果是雙向的依賴就會導致程式碼邏輯難以追蹤。

### 2.2 Design Pattern 是什麼？解決了什麼問題？

`Pattern` 是指在不同場景下的解釋，包含 Context（情境敘述）、Problem（問題）、Solution（解決方案）。

而 `Design Pattern` 就是過去人們發現解決問題的套路；學習越多 Design Pattern 在遇到問題時可以有更多的思路，並縮短與其他工程師討論的時間，因爲 Pattern 本身就包含了對問題的基礎解決方案，可以省下說明的時間。

下面分享一些常見的 Design Pattern 幫大家建立基礎概念，在看完後讀者也許會恍然大悟，原來我一直都有在使用 Pattern！

- **Factory Method 工廠方法**
**簡述**：工廠負責生產客戶需要的產品
假設今天有一個飲料`工廠`，他會生產很多種類的`產品`，像是「紅茶、綠茶、烏龍茶」；也許這些飲料的製作過程很複雜，但對客戶來講只要說出自己想要什麼口味的飲料就好了。

- **Strategy 策略**
**簡述**：一個策略介面底下有很多靈活的方法供選擇
把計算機的`計算`當成一個介面，這個介面提供加減乘除的方法；如果想加入次方、開根號的功能也能夠輕鬆擴充。

- **Factory Method 與 Strategy 混合運用**
**簡述**：有時解決方案是多個 Pattern 的組合
假設今天開一間飲料店，飲料會分成茶類、咖啡；每杯飲料又有冰量、甜度的選擇，這裡我們可以用 `Factory Method` 來建立產品；而飲料在販售時，不同組合有對應的折扣，這裡就可以用 `Strategy` 提供對應的方法來計算總金額。

- **Singleton 單例**
**簡述**：希望在程式每個位置都能呼叫統一的 Instance
在寫程式時會希望某些資源可以重複利用，並且在不同檔案引用時這些資料是`一致`的，像是共用的計時器、資料庫等物件。

- **Decorator 裝飾**
**簡述**：將需求獨立，依據實際需求取得動態的結果
假設一間餐廳提供三種餐點

- 商業午餐（主餐）
- 簡餐（主餐 + 飲料）
- 套餐（主餐 + 飲料 + 甜點）

我們發現上面餐點是有疊加性的，所以可以用繼承的方式來撰寫餐點間的關係；但如果今天老闆想要更換簡餐的內容組合（ex：贈送水果），而不想改變套餐的內容，就會導致程式必須改寫。
面對這個問題，我們可以用 `Decorator` 作為解決方案，將主餐、飲料、甜點、水果各自獨立，依據商業午餐、簡餐、套餐實際的內容來做組合；這樣就能減少耦合性的問題。

- **Observer 觀察者**
**簡述**：觀察者要能掌握被觀察者的狀態改變，較常用於 GUI 的設計
像是 Vue.js 的 watch 就是用來監聽一個「值」的變化去做一系列的事情（ex：UI 上的調整）。

- **Command 命令**
**簡述**：將請求的物件和執行的物件分開
路邊攤的老闆通常是一個人負責備料、點餐、料理、清潔…等到生意做起來有店面了，他才會考慮雇用一些人手來幫忙內場、外場，如果把這個解決方案用 Command 來舉例：

- **客人（Client）**：跟服務生說自己想點什麼餐（Concrete Command）。
- **服務生（Invoker）**：紀錄客人的餐點（Concrete Command），並呼叫命令。
- **廚師（Receiver）**：收到命令後，開始料理餐點（Concrete Command）。

- **Builder 建造者**
**簡述**：依照自己的需求客製化產品
一台筆電是由許多零件組裝而成，你可以選擇`標配`也能夠用`選配`來客製化它；為了讓選配的零件規格有彈性，`Builder` 就是一個很棒的解決方案；它讓你先選完自己所需的零件後，再產生最後的產品。

> **備註**：如果你熟悉的 Pattern 剛好能解決目前遇到的問題那是皆大歡喜；萬一沒有合適的 Pattern 可以套用，千萬不要為了設計而設計；開發人員的目標應該是想透過 Pattern 解決問題，而不是從 Pattern 出發去設計問題。

## 3. 衍伸問題

### 3.1 有處理 Legacy Code 的經驗嗎？

> 除非是新創團隊或是接案類型的公司；不然絕大多數新人入職後都要接手前人遺留的程式碼（Legacy Code），如果求職者只有從零開始的經驗，在一開始會有陣痛期。

考點：了解求職者是否接觸過 Legacy Code、碰到時如何快速掌握

之前有因為專案擴編而去支援的經驗，因為當時有時程上的壓力；所以我在加入團隊後除了重點了解自己要負責的 Feature、團隊的 Coding Style 外，會主動向定下這些需求的 PM 討論，以此了解因果關係並快速融入團隊。

也曾接手過要維護及擴充的專案，因為當時沒有相關文件可以參考，為了快速掌握專案，我一邊操作系統一邊畫`心智圖`幫助自己釐清功能；了解系統架構後再去找合作窗口確認需求，避免實作方向與客戶需求不同。

在修改 Legacy Code 時，我會特別注意前人是否有撰寫測試案例，在程式能有效測試的狀態下才能安心的修改與擴充。

以正在運行的專案來說，我會希望建立團隊 Code Review 機制，讓每個人熟悉彼此的程式、使用共同的 Coding Style，當知識共享與風格統一後，就能減少 Legacy Code 的產生。

### 3.2 有用什麼工具讓團隊有一致的 Coding Style

考點：是否有善用工具解決問題

我在 VSCode 有安裝一個 [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 的外掛，靠它就能非常有效的將程式碼格式化；不過有時為了保證團隊的 Coding Style 會導入 ESLint 來統一風格，但 Prettier 與 ESLint 一起使用時會遇到衝突，所以我有再安裝一個 [Prettier ESLint](https://marketplace.visualstudio.com/items?itemName=rvest.vs-code-prettier-eslint) 的外掛來解決這個問題；讓程式不僅符合團隊的 Coding Style，還可以透過快速鍵將程式碼格式化。

寫完這篇文章時，我的心裡突然冒出一段話：「一開始我們在追求問題的解答；到後來，我們在學習解決問題的方法。」

感謝大家的閱讀，如果喜歡我的文章可以`訂閱`接收通知；如果有幫助到你，按`Like`可以讓我更有寫文的動力，我們明天見～

### 參考資源

1. [【翻譯】JavaScript 設計模式](https://wyattkidd.medium.com/fca4e2e16752)
2. [什麼是 Design Pattern?](https://ithelp.ithome.com.tw/articles/10234830)
3. [我為什麼想學設計模式 ( Design Pattern )](https://ithelp.ithome.com.tw/articles/10201706)
4. [設計模式五大基本原則 SOLID](https://web.archive.org/web/20210226100242/http://www.andrewchen.tw/2017/04/09/20170409_NOTE_%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F%E4%BA%94%E5%A4%A7%E5%9F%BA%E6%9C%AC%E5%8E%9F%E5%89%87SOLID/)

> 在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～
> **天瓏書局：** [https://www.tenlong.com.tw/products/9786263334571](https://www.tenlong.com.tw/products/9786263334571)

---

關於作者：林鼎淵（Dean Lin），台灣的生成式 AI 企業內訓講師、外商資安公司軟體專家，也是生成式 AI 創新學院發起人。出版 7 本書（包含全台第一本 ChatGPT 應用專書），兩度獲得 iThome 鐵人賽佳作，累積上百場授課與顧問經驗。（https://deanlin.net/about/）
引用時請附上原文網址：https://deanlin.net/blog/202110/fullstack-career-notes-day-21/
