前言

最近更新日期

2026年10月9日

你有沒有遇過這種情況:追一個 bug 追了很久,最後發現問題不在你懷疑的那段程式碼,而是某個物件在傳遞過程中被一些程式碼修改了,而你一直都不知道其實很多地方都會改變那個物件的狀態。

或這種情況:你的 Web API 收到一個 HTTP 請求,請求內容被轉成一個 request DTO;你需要把它轉換成領域物件,再轉成資料庫用的實體物件,之後再轉成 response DTO。在此過程中,每一層都使用不同的類別,每一層的物件都可能在你沒發現的地方被修改。

備註:這裡的 DTO 是 data transfer object(資料傳輸物件)的縮寫。request DTO 承載外部請求的資料,response DTO 則承載 API 回應資料。

下圖描繪了從 HTTP 請求到回應的物件流動過程。當每一層都能修改物件狀態時,資料來源與修改責任會變得越來越難追蹤。

備註:圖中的 Domain Object 是用來表達核心業務概念與規則;Persistence Entity 則是配合資料庫或資料存取工具使用的儲存模型。這只是常見的責任分工,不表示每個系統都必須使用不同型別或採用特定的分層架構。

在此流程中:

那麼,某物件的某個屬性現在是什麼值?是誰改的?在哪一層改的?

這些問題來自一個共同的根源:可變狀態(mutable state)在程式中流動,讓你很難確定資料在某個時間點究竟是什麼值、是誰改的、在哪裡改的。

本書欲探討和解決的正是這個問題。

不是銀彈

這本書不會建議你捨棄物件導向(OOP)。物件導向有它的強項與適合場景:封裝複雜的業務規則、管理有生命週期的實體、維護物件在任何時候都必須滿足的規則。這些場景仍然需要物件導向的封裝。

這也不是一本講函數式程式設計(functional programming)的書。你不需要先熟悉函數式程式設計,也能閱讀本書並運用其中的設計方法。書中也會討論與「物件-關聯對應」工具(ORM,例如 Entity Framework Core)整合時的實務考量。

這本書想要講的是:

C# 近年來加入了許多語言特性,使得「讓資料保持透明、不可變、可追蹤」變得容易許多。然而,可能有不少開發者知道這些語法,卻不知道它們組合在一起能帶來什麼樣的設計改變。

C# 的 record、init、with、ImmutableList<T>、switch 運算式——這些特性不只是個別的語法或 API,更是一套能用來建構不可變資料模型的工具組。本書的目的,就是讓你知道如何善用這套工具組來改善軟體的設計,而不是只看一些語法範例。或者,如果你主要透過 AI 協助撰寫程式,也可以運用本書的觀念,引導 AI 進行軟體設計與開發。

培養判斷力

為了避免誤解,這裡先釐清一件事:本書不會告訴你「所有東西都改成 record 就對了」,也不會說「把方法全部搬到服務類別(service class)裡就是好的設計」。

實際上,這本書的一個主要目的,是希望幫你建立一種判斷力:看到一段程式碼或一個設計問題時,能夠判斷這裡適不適合用不可變設計(immutable design);如果適合,又該怎麼做。

接下來,我們要從一個最基本的問題開始:可變物件到底帶來了什麼麻煩?