ExplainThis 全端開發雙週報 #81 透過依賴注入 (Dependency Injection) 讓程式碼更容易維護
透過依賴注入 (Dependency Injection) 讓程式碼更容易維護
嗨~歡迎閱讀第 81 期 ExplainThis 全端開發雙週報!
進到本期的主題之前,想分享我們在 E+ 製作的《AI Coding 201:從實戰到最佳實踐》課程 (連結),近六個小時的課程已全數上架完畢。
去年在 《AI Coding 101:Cursor 入門到實戰》 課程 (連結) 中,我們講了使用 AI 工具協助開發時最基本應該知道的概念,以及在實戰中如何透過 AI 驅動的工具來提升開發效率與生產力。
經過一年回頭看,那門基礎課程的內容仍然適用。如果過去還沒有實際使用過 AI 工具來協助開發,仍然推薦先回頭從那門課打好基礎。不過在過去一年業界的快速發展下,AI 工具相關領域,特別是在開發上的進展非常快速,衍生出了許多新的最佳實踐。
因此,我們進一步推出這門 AI Coding 201 課程,從實戰延伸,講解有哪些最佳實踐能幫助大家在使用這些 AI 工具時,以更精準、有效且節省成本的方式讓開發做得更好。
這們 201 課程在 E+ 的社群中被讀者回饋非常實用有幫助,感興趣的讀者,歡迎加入 E+ 觀看課程 (連結)。
透過依賴注入 (Dependency Injection) 讓程式碼更容易維護
在 vgod 前輩的《軟體工程師的修煉與成長》系列文中,他用「接力跑馬拉松」來比喻大型軟體開發,這個比喻非常的生動。相信多數人會有感,假如今天不是只有自己一個人寫,而是要跟很多人合作開發軟體,就會像接力一樣,遇到許多只有自己開發不會遇到的問題。
事實上在軟體開發的合作,不僅僅是跟目前會接觸到的同事合作,往往是要跟已經離開團隊的人合作,例如處理歷史程式碼,往往就是要面對某個多年前就離開團隊的人寫的東西。同時,也會需要跟還沒加入團隊的人合作,例如某個你多年前寫的東西,一個新加入團隊的人要維護,於是來找你。
在這些片刻,會讓人覺得苦惱或生氣的,往往是那些不好維護的程式碼。因此在這篇主題文中,我們會聚焦在如何透過依賴注入,有效寫出好維護的程式碼。
以下的程式碼,為什麼不好維護?
要培養出能夠寫出好維護程式碼的直覺,我們要先從能辨別「哪類程式碼不好維護」開始。以下是一個簡化的程式碼段落,該段落的程式碼是在處理使用者註冊後,會發送電子郵件確認信。
讀者們看過去,會覺得程式碼有什麼問題? 為什麼不好維護?
// --- 電子信箱相關服務 ---
class EmailService {
send(email: string, message: string): void {
// (發送電子郵件相關的程式碼,細節忽略)
}
}
// --- 使用者相關服務 ---
class UserService {
private emailService: EmailService;
constructor() {
this.emailService = new EmailService();
}
registerUser(email: string, password: string, name: string): void {
// (使用者註冊相關程式碼,細節忽略)
// 成功註冊後,發送電子郵件
this.emailService.send(email, `Welcome, ${name}!`);
}
}在讀上面這一段程式碼時,推薦讀者們可以邊讀邊問以下的問題
這段程式碼好測試嗎? 為什麼?
假如想在不同環境用不同的電子信箱服務(例如在測試環境用模擬的信箱),容易做到嗎?
之後有其他的服務 (例如傳送簡訊),容易加嗎?
如果有情境是註冊後不要傳電子郵件,容易改嗎?
相信如果有問上面這些問題,會發現這段程式碼在可維護性上不是太理想。最核心的原因是把 emailService 寫死在註冊使用者的方法中,這會讓測試相對麻煩 (因為跑測試時就會真的發電子郵件)、讓要改要加都不容易 (因為會需要寫很多特定的邏輯,會讓 registerUser 這個方法變得肥厚)。
舉例來說,我們可能要寫下面的條件判斷,才能加上不同的通知傳送方式,當越多種方式,這個條件判斷就要有越多的 if...else..。
if (notificationType === “email”) {
this.emailService.sendEmail(email, `Welcome, User ${id}!`);
} else if (notificationType === “sms” && phone) {
this.smsService.sendSMS(phone, `Welcome, User ${id}!`);
}相信這時讀者們會問,該如何解決這個問題呢? 依賴注入 (Dependency Injection 或簡稱 DI) 是在這種情境下,特別能派上用場的手段。
什麼是依賴注入 (Dependency Injection)?
在實際講什麼是依賴注入前,先讓我們看看如果要重構上面段落的程式碼,可以怎麼做。
class UserService {
// 通知服務是被傳進來,而不是寫死的
constructor(notificationService) {
this.notificationService = notificationService;
}
registerUser(userData: IUserData, password: string): void {
// (使用者註冊相關程式碼,細節忽略)
// 成功註冊後,發送通出
this.notificationService.sendWelcomeMessage(userData, userData.name);
}
}試著觀察一下重構後的程式碼,我們可以發現重送通知的服務是被傳入的,而不是像原本的emailService 是寫死在 registerUser。
這時候如果我們想要用不同的通知傳送方式,可以像這樣
// 有不同的傳送通知方式
const emailService = new EmailService();
const smsService = new SMSService();
// 將不同的方式聚合在一起
const multiChannelService = new MultiChannelNotificationService([
emailService,
smsService,
]);
// 然後要傳哪類通知,只要在使用 userService 時決定就好
const userService = new UserService(multiChannelService);在上面的例子,可以看到 UserService 裡面完全不會有要選擇哪一個傳送方式的邏輯,因為要用哪一種方式傳,是在上一層決定的,所以不同的場合用 UserService 時,可以自由搭配選定 multiChannelService 中的陣列,要放哪類的通知傳送方式。
這種做法不僅解決原本版本如果要修改,會導致 registerUser 中的邏輯越變越臃腫導致難以維護的狀況,更可以讓測試變得更容易。因為不同的傳訊息服務是被傳進去的,所以我們可以很輕易地傳入 mock,例如 MockEmailService 或 MockSmsService,這時如果實際呼叫 registerUser,就裡頭呼叫的就會是 mock,非常方便。
事實上,我們在 軟體測試 — 好測試的程式碼與好維護的測試 這篇主題文中,就有提到可以透過這種方式,來讓程式碼更容易測試。而當時我們稱這種方式為依賴注入。
所以,什麼是依賴注入?
如果要試著拆解依賴注入這個詞,會像這樣
依賴 (dependency): 某個在方法或函式中會用到的、所依賴的東西
注入 (injection):** 只要某個東西是做為參數傳入某個方法或函式,就會被稱為注入
把上面這兩個詞結合在一起,所謂的依賴注入,就是某個在方法或函式會用到的東西,不是寫死在該方法或函式中,而是用傳入的方式,傳進去讓該方法與函式可以使用。
閱讀更多
如果你對於這類深度內容感興趣,歡迎加入 E+ 成長計畫 (連結),除了全系列所有完整深度文章外,也可觀看所有 E+ 的線上課程與過去所有的直播回放。
本期推薦
Oxide Computer 開源了一個有趣的小工具 Mitos,可以把圖片、GIF,甚至自訂 JavaScript 程式變成 ASCII 文字圖 (連結)。如果你想做比較有個性的 README、CLI 歡迎畫面、終端機小彩蛋,或幫自己的專案加一點工程師味,這個工具可以拿來玩看看
Evan Bacon 離開 Expo 後,寫了一篇回顧自己九年經驗的文章,從早期產品開發、社群經營,一路談到開發者工具如何長大 (連結)
比較值得看的是他談自己成為產品重度使用者、理解需求背後的問題,以及開發者工具如何長期累積信任Pilcrow 整理了一本免費的 Auth book,系統介紹 Web 應用程式中的登入、驗證與身份認證實作,包含 session、密碼、CSRF、Passkey、WebAuthn 等主題 (連結) 。這系列文不只講概念,也附了用 Go 寫的完整開源範例,適合想把 Auth 基礎補起來的開發者
atlas9 在《The challenges of soft delete》一文中,討論為什麼「刪除資料但先不要真的刪掉」這件事,做久了會讓系統變難維護 (連結) 文章從查詢、備份、資料還原到日後維護成本切入,如果是正在設計刪除機制,或曾經被資料狀態搞到很痛苦的人,推薦一讀
最近 Corgi 這間剛拿到 1.06 億美元融資的新創科技保險公司,執行長 Nico Laqua 在訪談提到團隊一週工作七天、創辦人睡在辦公室,且直白地說「如果你把週六、週日固定當成休息日,那這間公司不適合你」,引起社群討論。對此,我們認為要成功不代表一週要工作 7 天,同時彙整了社群中的反面觀點 (連結)
Meta AI 出事故,讓多個知名 IG 帳號被駭,我們整理了事件始末。對攻擊手法感興趣的讀者,可以一讀 (連結)
前陣子看到,由 OpenAI 董事會主席,同時也是前 Facebook 技術長 Bret Taylor 創辦的 AI 新創公司,不再繼續使用過去常見的「演算法、系統設計、行為面試」標準面試流程,而是採用 AI 原生的面試 (連結)

