<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>不變式 on Tarragon</title><link>https://tarrragon.github.io/blog/tags/%E4%B8%8D%E8%AE%8A%E5%BC%8F/</link><description>Recent content in 不變式 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 10 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E4%B8%8D%E8%AE%8A%E5%BC%8F/index.xml" rel="self" type="application/rss+xml"/><item><title>Invariant</title><link>https://tarrragon.github.io/blog/ddd/knowledge-cards/invariant/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/ddd/knowledge-cards/invariant/</guid><description>&lt;p>不變式是在物件整個生命週期都必須為真的業務規則。狀態只能沿流程轉換、某幾個欄位被同一條規則綁住必須一起換、錯誤代碼必須屬於對應分類——這些條件只要有一條被違反、物件就處於業務上不該存在的狀態。不變式的存在是判定型別為領域模型而非&lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/data-bag/" data-link-title="Data Bag" data-link-desc="判斷一個型別要不要投資領域模型設計時使用。資料袋是欄位組合全部合法、沒有不變式要守的型別——DTO、API model、UI state 都屬於這一類。">資料袋&lt;/a>的關鍵依據：有不變式的型別需要領域模型的設計投資，沒有的就是資料袋。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>不變式跟輸入驗證的責任不同。不變式守「這個物件能不能存在」——違反時物件不該被建出來；輸入驗證守「使用者輸入對不對」——違反時要好好告訴使用者。兩者混放會讓格式驗證塞進建構子、或存在條件放進 validator。不變式跟 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/snapshot/" data-link-title="Snapshot" data-link-desc="歷史記錄是否應該凍結當時狀態時使用。Snapshot 是某一時刻的狀態複本——歷史不隨現在的資料漂移。">snapshot&lt;/a> 有交集——歷史 snapshot 凍結的是過去規則下合法的狀態、回讀時不一定通過新規則的不變式。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>型別上出現「請用某方法修改」「此欄位勿直接改」的註解時，不變式已經到場、強制還停在文件層。建構子或方法的 &lt;code>throw&lt;/code> / &lt;code>assert&lt;/code> 是不變式在執行層工作的證據；grep 得到零個對應檢查時，是宣稱約束但未強制。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>同一條不變式可以落在文件層（註解）、型別層（介面簽名）或執行層（建構子檢查），層次決定違反時發生什麼——靜默通過、編譯失敗、當場拒絕。選擇依違反代價與建置成本折算。教學層展開見 &lt;a href="https://tarrragon.github.io/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>不變式是在物件整個生命週期都必須為真的業務規則。狀態只能沿流程轉換、某幾個欄位被同一條規則綁住必須一起換、錯誤代碼必須屬於對應分類——這些條件只要有一條被違反、物件就處於業務上不該存在的狀態。不變式的存在是判定型別為領域模型而非<a href="/blog/ddd/knowledge-cards/data-bag/" data-link-title="Data Bag" data-link-desc="判斷一個型別要不要投資領域模型設計時使用。資料袋是欄位組合全部合法、沒有不變式要守的型別——DTO、API model、UI state 都屬於這一類。">資料袋</a>的關鍵依據：有不變式的型別需要領域模型的設計投資，沒有的就是資料袋。</p>
<h2 id="概念位置">概念位置</h2>
<p>不變式跟輸入驗證的責任不同。不變式守「這個物件能不能存在」——違反時物件不該被建出來；輸入驗證守「使用者輸入對不對」——違反時要好好告訴使用者。兩者混放會讓格式驗證塞進建構子、或存在條件放進 validator。不變式跟 <a href="/blog/ddd/knowledge-cards/snapshot/" data-link-title="Snapshot" data-link-desc="歷史記錄是否應該凍結當時狀態時使用。Snapshot 是某一時刻的狀態複本——歷史不隨現在的資料漂移。">snapshot</a> 有交集——歷史 snapshot 凍結的是過去規則下合法的狀態、回讀時不一定通過新規則的不變式。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>型別上出現「請用某方法修改」「此欄位勿直接改」的註解時，不變式已經到場、強制還停在文件層。建構子或方法的 <code>throw</code> / <code>assert</code> 是不變式在執行層工作的證據；grep 得到零個對應檢查時，是宣稱約束但未強制。</p>
<h2 id="設計責任">設計責任</h2>
<p>同一條不變式可以落在文件層（註解）、型別層（介面簽名）或執行層（建構子檢查），層次決定違反時發生什麼——靜默通過、編譯失敗、當場拒絕。選擇依違反代價與建置成本折算。教學層展開見 <a href="/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次</a>。</p>
]]></content:encoded></item></channel></rss>