在功能穩定後,你對程式碼還有要求嗎?
「沒有最好的程式,只有更好的程式。
」在完成功能後 Code Review 是非常重要的事情;不只看自己的程式碼,也要多觀摩其他人的程式碼,這個動作除了可以優化程式碼品質
外,還可以幫助團隊了解彼此的工作進度
。
人無完人,很多時候第一時間想到的解法未必是最好的;解決問題的方法不只一種
,可能有更簡潔的寫法或是更好的 Pattern 可以套用,只是開發者不具備這塊的知識儲備;此時 Code Review 就是提升團隊程式水平
的方式。
找到需要改善的問題後,Refactoring(重構)
就是另一個開始;如果為了盡快完成功能而埋頭狂寫,容易導致日後維護以及交接的困難性;專案越大這個問題會越明顯,所以在完成功能後對程式做 Refactoring 是必要的任務,它能提升你日後解決 Bug、需求變更時的工作效率
。
Code Review 會注意哪些事?會依照什麼原則對程式做 Refactoring?
回答問題所需具備的知識
衍伸問題
只要是軟體工程師的職位,這題可以說是超級常見面試題
;因為它能問得很淺也能夠問得很深,如果是相對資淺的職位,求職者只要能說出關鍵字並給出基本解釋
就能過關;如果是資深的位置,除了關鍵字外,面試官會繼續詢問你在專案中是否有實踐
。
我會先觀察變數與函式的命名
是否符合團隊規範、變數的宣告
是否合理(var/let/const 的使用時機);然後看程式在設計上是否有符合 SOLID 原則
;在功能完成後如果發現有合適的 Pattern 可以更好地解決問題
,就會再對程式做 Refactoring。
其他章節的知識,可能換一個時空背景就用不到了;但今天這個章節的內容,是每位開發人員都需要將其內化成為本能的
。
SOLID 是一個物件導向
的設計原則,核心目標是為了讓程式碼更乾淨、易讀、好維護,讓程式朝著 Clean Code
(無瑕的程式碼)的方向前進。
一次只做好一件事
;如果把系統比喻成樂高,那單一責任原則就是樂高的每一塊積木。功能的擴充是新增程式,而非修改過去的程式
,這麼做可以維持原有程式的穩定性。完全繼承父類別的功能
;實作時還需顧慮到單一責任原則,避免日後功能拆分或刪減時的困擾
。版本從 1.0 ➞ 1.1 時行為是一致的,不會更新完舊功能就壞掉
。
子類別 Override 時要注意
- 不可改變父類別先決條件
假設籃球比賽的參與者年齡限制在 15~80 歲;裡面可能區分為青年組、中年組、老年組,青年組的年齡「可以是 15~30」;但「不可以是 14~30」。- 子類別的後置條件不該被削弱
籃球的得分有「2」分球、「3」分球,都是數字;如果某個比賽改用「三分球」這個字串紀錄得分就是不可以的。
不要讓一個 API 可以做好多事情,但做每件事情時只用到其中幾個參數
。子類別一定會依賴父類別
,這樣單向的關係是乾淨的;如果是雙向的依賴就會導致程式碼邏輯難以追蹤。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 來舉例:
Builder 建造者
簡述:依照自己的需求客製化產品
一台筆電是由許多零件組裝而成,你可以選擇標配
也能夠用選配
來客製化它;為了讓選配的零件規格有彈性,Builder
就是一個很棒的解決方案;它讓你先選完自己所需的零件後,再產生最後的產品。
備註:如果你熟悉的 Pattern 剛好能解決目前遇到的問題那是皆大歡喜;
萬一沒有合適的 Pattern 可以套用,千萬不要為了設計而設計
;開發人員的目標應該是想透過 Pattern 解決問題,而不是從 Pattern 出發去設計問題。
除非是新創團隊或是接案類型的公司;不然絕大多數新人入職後都要接手前人遺留的程式碼(Legacy Code),如果求職者只有從零開始的經驗,在一開始會有陣痛期。
考點:了解求職者是否接觸過 Legacy Code、碰到時如何快速掌握
之前有因為專案擴編
而去支援的經驗,因為當時有時程上的壓力;所以我在加入團隊後除了重點了解自己要負責的 Feature、團隊的 Coding Style
外,會主動向定下這些需求的 PM 討論
,以此了解因果關係並快速融入團隊。
也曾接手過要維護及擴充的專案
,因為當時沒有相關文件可以參考,為了快速掌握專案,我一邊操作系統一邊畫心智圖
幫助自己釐清功能;了解系統架構後再去找合作窗口確認需求
,避免實作方向與客戶需求不同。
在修改 Legacy Code 時,我會特別注意前人是否有撰寫測試案例
,在程式能有效測試的狀態下才能安心的修改與擴充。
以正在運行的專案來說,我會希望建立團隊 Code Review 機制
,讓每個人熟悉彼此的程式、使用共同的 Coding Style,當知識共享與風格統一後,就能減少 Legacy Code 的產生
。
考點:是否有善用工具解決問題
我在 VSCode 有安裝一個 Prettier 的外掛,靠它就能非常有效的將程式碼格式化;不過有時為了保證團隊的 Coding Style 會導入 ESLint 來統一風格,但 Prettier 與 ESLint 一起使用時會遇到衝突,所以我有再安裝一個 Prettier ESLint 的外掛來解決這個問題;讓程式不僅符合團隊的 Coding Style,還可以透過快速鍵將程式碼格式化。
寫完這篇文章時,我的心裡突然冒出一段話:「一開始我們在追求問題的解答;到後來,我們在學習解決問題的方法。」
感謝大家的閱讀,如果喜歡我的文章可以訂閱
接收通知;如果有幫助到你,按Like
可以讓我更有寫文的動力,我們明天見~
我在 Medium 平台 也分享了許多技術文章
❝ 主題涵蓋「MIS & DEVOPS、資料庫、前端、後端、MICROSFT 365、GOOGLE 雲端應用、自我修煉」希望可以幫助遇到相同問題、想自我成長的人。❞
在許多人的幫助下,本系列文章已成功出版,除了添加新的篇章,更完善了每個案例的應對進退;如果對現在的職涯感到迷茫,也許這本書能帶給你不一樣的觀點~