1 minute read     Posted on:     Updated on:     Views: Loading...

Preface

這個議題我覺的是軟體工程師的必經之路啦
本篇文章基本上就是紀錄我目前學到的一些東西以及方法

Mindset

有意識的要自己寫出良好的程式碼是個重要的事情
無論是多麼小的專案,你都必須要 “意識” 到自己在寫什麼
但是要自己意識到什麼是好的程式碼,什麼是壞的
需要一點練習

Low Coupling and High Cohesion

一個基本的概念是 耦合性 與 聚合性

剛開始寫程式的時候你可能會寫出耦合性很高的程式
意思指的是說,你寫出來的程式碼會有很多個相互依賴的東西
白話點就是,牽一髮動全身

這種程式碼他的可維護性就會大幅度的降低
因為每一次的更改你都需要花時間修改大量的程式碼

一個好的架構應該要是 低耦合性 與 高聚合性
怎麼做到呢 一起看看吧

本文將專注在程式碼層級的解耦合,事實上還有部屬層級以及服務層級的解耦合

Start with SOLID

我稍微調整了一下 SOLID 的順序,這樣講起來比較簡單

Dependency Inversion Principle

interface 是個很神奇的東西
學校教的時候你會覺的它沒啥用處
但工作上卻又用一大堆

Example

看回熟悉的例子
In Unix, everything is a file
作業系統其中一個功能就是讀寫檔案
硬體在這幾十年間發生了許多的變化
從 磁帶、軟碟、硬碟再到固態硬碟,每個硬體的物理特性都有所不同
因此你可以期待它操作檔案的方式 “肯定是會有所不同的”

這意味著,你需要使用不同的實作對吧
比方說硬碟需要 seek, 固態硬碟則不需要
檔案系統天生需要支援多種 “硬體” 的實作
但如果直接將檔案系統寫死,這將會是個很麻煩且災難的事情

每新增一個不同的硬體,你都需要新增相對應的程式碼在 “檔案系統” 的實作當中
你可以預期程式碼的數量將會暴漲,維護會越加的困難

取而代之的應該是
檔案系統 不應該需要知道你怎麼讀檔案,寫檔案,它甚至不應該關心你需不需要移動 “硬碟讀寫投”
這就是 抽象化 的概念


看完例子,你應該多少可以感受到 “抽象化” 的重要性
但在軟體工程裡面要怎麼做到抽象化?

主要就是透過 interface 來實現了
兩個實體(class, object) 都必須依賴於 interface
彼此之間不應該知道彼此的任何細節
這樣的好處是,他們兩個之間的 耦合性 會降低
我改我的,你改你的,都不會影響到彼此(只要介面沒有改變)

軟體的演進是很快速的,所以實作是容易改變的
但是 interface 不應該被改變
所以你可以放心的隨便改你的 “實作”, 只要它符合 interface 的標準都是可以的

舉個例子來說
你的 service layer 與 database layer 之間應該要有一層 interface
這層 interface 就是兩個不同的實體的公約

service layer 不會管你是怎麼 “儲存我的東西的”
你要用 database, redis cache 還是簡單的 in-memory cache 都跟我沒關係(I don’t care)
只要符合 interface 的標準,我 service layer 都能夠兼容

實務上,你也看到了
這對測試是有極大的幫助的
在測試的時候我可以替換成 mock 的實作,這樣測試就會更輕鬆了(你不需要真的連線到資料庫)

有關測試的部份可以參考 DevOps - 單元測試 Unit Test | Shawn Hsu

Open/Closed Principle

Dependency Inversion Principle 中我們提到
使用 interface 當成兩個實體之間的守則是個好的 practice
我們也提到,軟體開發常常會更改東西,只要改動符合 interface,我們就不需要更改太多部份的程式碼(因為低耦合)
實作很常會改動,但是 interface 不應該常常改變(應該說 modify)

其實我已經講完 Open/Closed Principle 了

他想表達的東西就如上所述
對於 "新增" 功能,我們是樂見的,因為軟體很常會有新的功能需要加入
但是對於 "修改" 功能,如果新增功能會 導致必須要修改現有的實作,我們是不樂見的

修改指的是,refactor 現有的實作以兼容新的功能
好的架構應該是你可以在原本的基礎上直接擴充功能的
但這建立在這個基礎是好的
不過適度的重構我覺的並沒有違反這個原則(當然一次改一堆的不算就是)

Interface Segregation Principle

依賴於共同的 interface 的好處我們已經了解的差不多了
只不過要小心 interface 與實體之間的必要性

簡單講就是,如果實體內部包含了一個它根本不需要的東西
我們就不應該去依賴於它

好比說一個大學生的包包裡面帶了小學英文課本
這根本是不需要的,所以你可以把它拿掉

interface 裡面包含了不必要的定義的時候
那就代表你應該定義一個新的 interface 而不是使用這個巨大的 interface

Single Responsibility Principle

一次只對一個人負責,什麼意思呢

舉個我真實遇到的例子
我要替新的 API endpoint 撰寫 middleware 進行 validation
一開始我把全部的欄位的驗證都寫在一個 function 裡面
很明顯的他是有問題的
因為它對不只一個人負責
不同的欄位應該要由不同的驗證 function 進行處理

分開寫好處當然就是好維護對吧
並且解耦合了

Liskov Substitution Principle

這個原則相對起來我覺的比較抽象
不過他的重點在於,所有衍生的子類別,都必須要符合父類別所定義的 “正確行為”

書中提到的例子是 正方形長方形
正方形是長方形的子類別,但是正方形的特性是 四邊長相等
兩個不同的子類別,他們的特性是不同的
所以你不能夠將正方形當成長方形使用,因此不應該存在這樣的繼承關係

如今這個原則已經不再限訂於 class 了
interface 也同樣試用

Avoid Anti-pattern

我自己在寫 code 的時候,有時後會踩到一些 anti-pattern
簡單的方法是,閱讀其他人寫的 code
學習他們的寫法,你可以了解基本的 best practice

自己學很容易學歪,所以看別人的程式碼很重要
參與 open source 的專案也是一個方法

像我自己有時後會誤用了某個東西
這很常是因為不足夠熟悉所造成的
除了多練習以外,你也應該要多唸書

當然 code review 也能夠及早的發現這些 anti-pattern 的存在
並且能夠即時的修正

Utilize AI Tools

2024 的今天,已經有許多 AI 可以輔助我們寫程式了
善用工具除了可以提高開發速度之外,也能夠幫助我們寫出更好的程式碼
比如說我現在就在使用 GitHub Copilot 自動補齊程式碼
也能夠利用它做基本的 code review
即使你是自己開發,也能夠有一個基本的 code review 功能

References

  • Clean Architecture 無瑕的程式碼(ISBN: 978-986-434-294-5)
  • Clean Code 無瑕的程式碼(ISBN: 978-986-201-705-0)
  • The Clean Coder 無瑕的程式碼(ISBN: 978-986-201-788-3)
  • 軟體架構原理 工程方法(ISBN: 978-986-502-661-5)

Leave a comment