物件為什麼要不可變?
Immutability 觀念在近幾年漸漸的成為主流,例如快速崛起的 Rust 語言,除非特別標記 mut 否則物件存在即不可變,又或者是 PHP 8.1 加入的 readonly 修飾符,都顯示出開發領域逐漸重視不可變的重要性。
這其實是非常反開發直覺的,大多數開發者通常認為物件和其他變數一樣生成後應該要可以被修改,但缺點也非常明顯,沒有人能保證資料沒有被異動,就像Email字串可能在通過驗證後就被錯誤的邏輯修改了,最終在持久化儲存時可能根本就不再是正確格式。
前言
不同語言的作法有所差異,接下來代碼範例部份僅一律以 PHP 示範。
快速連結
資料可變,很嚴重嗎?
是的,在沒有規則約束、不可控的情況下很嚴重,資料可變代表誰都可以任意修改資料,因此需要配置更多人力或邏輯檢驗資料正確性。設想一下,某用戶提交了一組 Email,明明資料正確也收到驗證信了,卻在應用程式中經過幾層傳遞後變了,誰能保證資料是由用戶親手提交的?又該如何追蹤哪個環節被修改了?
php
class User{
public function __construct(
public string $email
){}
}
$user = new User('[email protected]');
$user->email = 'NOT A EMAIL'; // ⚠️ 資料被異動了
在這個範例中可以看到,未加入 readonly 修飾符的情況下,任何的邏輯錯誤都可能導致資料不如預期。
怎麼保證資料不可變?
說實話,在不可變觀點還沒被重視之前很難達成這件事,因為語言或工具支援不完善,透過任何口頭約定都可能被有意或無意的異動,單靠口頭約定非常不牢靠,因為惡意修改者根本不管約定,新進維護者也可能因為交接遺漏或文件模糊而導致資料可變,這就像是用鉛筆寫了一段文字後跟別人說不可以擦掉一樣,對我而言最真實也最牢靠的不可變保證,始終來自語言限制。
php
readonly class User{
public function __construct(
public string $email
){}
}
$user = new User('[email protected]');
$user->email = 'NOT A EMAIL'; // ⛔ Fatal Error,PHP 根本不給異動
真的需要異動怎麼辦?
很簡單,回想一下當我們提交正式文件需要塗改時會怎麼做?用立可帶塗改就可以了嗎?那確實也是一種方法,部份現實情境支持塗改後蓋章,用來表示這是異動是被認可的,但在系統上我們有更嚴謹高效的方法,就是把異動的資料連同其他原始資料重新生成一次物件實例,並且新實例同樣不可變,換作實際案例的話,就等同塗改後重新影印或複印一份。
我在 ImmutableBase 這個 PHP package 中設計的with()正是為此而生,物件呼叫此函式並傳入期望異動的值後會重新建構相同物件實例,但資料被異動了,以下以 ImmutableBase 作為示範。
php
use ReallifeKip\ImmutableBase\DataTransferObject;
readonly class User extends DataTransferObject{
public string $email;
}
$user = User::fromArray([
'email' => '[email protected]'
]);
$user2 = $user->with([
'email' => '[email protected]'
]); // ✅ 異動成功,與原實例完全不同,保持原實例不變
保障資料絕對是完全獨立、互不相關的,這很好,但可以想想,這樣做有什麼缺點?以及這樣的設計很簡單,為什麼以前沒人在乎?答案留到後面的 你可能也想知道
為什麼以前沒人在乎?
不是沒人在乎,而是不易在乎、在乎的人太少,實際上 Functional programming 社群、Haskell、Erlang 語言等早就在實作不可變概念了。
把時間軸拉長一點,在記憶體還很小的年代,每次異動都產生一個新實例是成本非常高的。時間軸拉近一點的話,則是痛點不夠明顯,在 MVC 就能打天下的背景下,物件生命週期短,通常被非預期異動的可能性不大,自然就不太會需要保證不可變,然而微服務、事件驅動、快取開始主流後生命週期被拉的很長,傳輸可能會經過好幾層,不可變就開始得到重視了。
你可能也想知道
Q. 不可變就能保證資料合法?
A. 並不是,資料能不能異動與是否合法本就無關,原因是資料可能在提交時本就有問題,我在 ImmutableBase 中設計了一套自動驗證鏈,用以層層疊加值的驗證規則,有興趣的話可以點進連結參考看看我的設計,但這不在本篇討論的重點範疇內,故不在此討論。
Q. 記憶體開銷真的無法避免嗎?
A. 是也不是,這取決於你用什麼語言開發,Rust 透過所有權機制在編譯期就管控了可變性,有興趣可以深入了解。
Q. 所有資料都應不可變嗎?
A. 不,所有設計都還是需要以服務業務為優先,若一項資料的嚴謹度很低(例如備註),就不要死命追求不可變,一是記憶體雖然變便宜了,但仍然不是免費,二是不論額外設計或加裝套件,都還是存在學習成本。
結論
保證資料正確性是系統內外都重要的議題,不論是實際提交的紙本資料,或是線上提交的數位資料,正式文件定稿後不允許直接塗改,就等同物件生成後不可修改;需要修正時重新編輯後輸出一份新文件,就等同呼叫返回新實例。原件始終保持不變,異動前後的差異也因此可以被追蹤與比對。
最後編輯:2026-03-16 04:28:43
