商業邏輯論述要 self-contained:不依賴 code 才能被理解
論述基礎與限制
本卡的論述基於 1 個 case(dart Stream 事故的 review 中讀者指出「事件 payload 第二段」依賴 code 看過)抽出來的觀察。具體限制:
- Scope 限縮在概念說明 / 架構決策 / 設計檢討類文章:教學類、tutorial 類、code walkthrough 類文章的讀者本來就會逐行對映 code、本卡不適用——這幾類文章的論述跟 code 緊密交織是合理 narrative
- 「翻譯成業務角色」也有讀者熟悉度的邊界:用「鏡像訂閱者」「狀態變更 service」這類角色名詞替代「那個 controller」、對熟悉 POS 跟 pub-sub 的讀者通順、對不熟的讀者仍是空名詞。修法是「換 reference 類型」、不是「徹底解決 self-contained」
- 跟規則二(商業邏輯先於 CASE)的關係要精準:規則二講「層次順序」(先商業邏輯後 CASE)、本卡講「論述自包含」(不依賴 code reference)。兩者有重疊但不完全相同——本卡是規則二在「字句層 reference 處理」的子場景、不是規則二的全面延伸
讀者使用本卡時、先判斷文章類型——概念說明 / 架構決策 / 設計檢討 → 套用;教學 / tutorial → 評估「跟 code 交織」是否合理 narrative。
核心原則
概念說明 / 架構決策 / 設計檢討類文章的論述段(不放 code 的段落)要 self-contained——用名詞 / 角色 / 條件描述業務邏輯、不依賴讀者去翻附近的 code block。讀者跳過所有 code block 仍能理解論述、是「商業邏輯先於 CASE」(compositional-writing 規則二)在「字句層 reference 處理」的子場景應用。
| 維度 | 依賴 code 的論述 | Self-contained 論述 |
|---|---|---|
| 引用方式 | 「事件 payload 第二段帶了那個欄位」 | 「事件 payload 包含『當前完整列表』+『最後變動品項』兩段」 |
| 主詞 | 「那個 controller」「剛才的 service」 | 「副螢幕鏡像 controller」「狀態變更 service」 |
| 讀者前提 | 已經看過 code、記得結構 | 不需要看 code、只看論述 |
| 失敗模式 | 讀者翻不到 reference 對應位置 → 卡住或誤讀 | 讀者直接 parse 論述、不被 code 結構綁住 |
| 修補擴散 | code 改了、論述自動 outdated | code 改了、論述仍然有效 |
self-contained 論述的價值在於論述本身就是完整的、code 是 case 補充而非依賴。讀者用論述就能 reproduce 思考過程、code 提供具體驗證。
為什麼依賴 code 的論述會出現
來源 1:寫的人有 code 在腦中、預設讀者也有
寫作者通常已經看過或寫過 code、所以在論述段用「那個 payload 第二段」這類 reference 對自己沒負擔。但讀者可能:
- 跳過 code 直接讀論述(多數讀者習慣)
- 看了 code 但沒記住具體結構
- 不熟 Dart / 該專案、看 code 也 parse 不出 payload 結構
依賴 code 的論述把這些讀者擋在門外、強迫他們翻 code 對映、認知負擔顯著上升。
來源 2:把對話風格搬進文章
「現在只要有人訂閱、把它記錄下來、UI 就能用」——這是對話風格、預設聽者跟說話者共享 context。寫成文章時要把 context 補進去:「需要新增一個訂閱者讀這段資訊、再把它對應到 UI 上的視覺標記」。
來源 3:論述跟 code 段過於緊密交織
文章在寫「先給 code、然後論述、然後再給 code」的交織結構時、論述容易自然 reference 上面 code 的具體行。讀者跳過 code 就斷掉。
來源 4:誤把「業務邏輯」當成「code 行為」
「業務邏輯」是「為什麼這件事存在 / 服務什麼需求」、「code 行為」是「具體怎麼跑」。依賴 code 的論述把兩者混在一起、讀者難以分離兩個層次。
識別訊號:什麼時候你寫了依賴 code 的論述
訊號 1:論述用「那個」「這個」「剛才的」「上面的」當主詞
「那個 service」「這個 payload」「剛才的 controller」——這類代詞依賴讀者剛才看過 code。
修法:把代詞換成具體名詞 + 角色描述(「狀態變更 service」「事件 payload」「副螢幕鏡像 controller」)。
訊號 2:用 code 結構描述(「第二段」「那個欄位」)
「payload 第二段」「那個 nullable 欄位」「上面 method 的 return value」——這類描述依賴讀者看過 code 的具體結構。
修法:把 code 結構描述翻譯成業務角色描述。「payload 第二段」→「最後變動品項欄位(給需要追蹤單筆變動的訂閱者用)」。
訊號 3:時序連接詞依賴 code 順序
「先…然後…接著…」如果這個時序對應上面 code 的執行順序、論述跟 code 綁太緊。
修法:把時序敘述為「在 X 條件下、Y 動作觸發 Z 結果」、不依賴 code 的具體順序。
訊號 4:論述只有「就好」「就能」「就行」
「現在只要有人訂閱、UI 就能用」「修改一行就好」——這類「就」字句在依賴 code 補足背景條件時有問題。但「修一行就好」如果背景條件已在前文說明、用「就」表達精簡是合理的——區分標準是「沒有 code 也讀得通嗎」、不是字面有「就」就違規。
修法:背景條件不在前文時、把省略的條件補回去;背景條件已在前文時、保留「就」沒問題。「在訂閱端讀取這段資訊、加一個視覺標記的綁定、即可在 UI 上呈現」。
訊號 5:跳過 code block 後段落讀不通
最直接的測試方法:把所有 code block 拿掉、再讀一次論述、看是否仍能理解。
讀不通 → 論述依賴 code、要修補。
修法:把 reference 翻成 self-contained 描述
修法 1:用名詞替代代詞
修補前:
「那個 service 對外發送事件、payload 第二段帶了這次變動是哪筆。」
修補後:
「狀態變更 service 對外發送的事件 payload 包含兩段:當前完整商品列表、最後變動的具體品項。第二段是『最後變動品項』。」
修法 2:用角色替代「位置」
修補前:
「上面 code 第三行那個 listener 拿到的是 nullable。」
修補後:
「副螢幕的訂閱者收到的事件 payload 中、第二段(最後變動品項)可能為 null(移除或清空操作的情境)。」
修法 3:用條件替代時序
修補前:
「先建立 controller、然後 listen、接著 add 事件、再 cancel。」
修補後:
「在 controller 建立後、訂閱者可呼叫
.listen()註冊;註冊完成後、controller 才能.add()事件給訂閱者;訂閱者呼叫.cancel()解除註冊後、後續.listen()對 single-subscription 仍然違反契約。」
修法 4:把「就好」展開成具體條件
修補前:
「現在只要有人訂閱、把它記錄下來、UI 就能用。」
修補後:
「新需求只要新增一個訂閱者讀這段資訊、再把它對應到 UI 上的視覺標記即可——介面不需要變動、payload 結構不需要調整、實作範圍只限於新增訂閱端。」
何時論述可以依賴 code
「論述要 self-contained」這條原則在概念說明 / 架構決策 / 設計檢討類文章成立、但有合理例外:
| 情境 | 為什麼可以依賴 code |
|---|---|
| Code walkthrough / line-by-line | 文章本身就是 code 解說、讀者預設會逐行對映 |
| 簡短 inline 引用 specific 行為 | 「stream.listen() 第一個參數是 callback」這類引用本身就是 code 字面 |
| Tutorial 教學步驟 | 「跑這個指令、你會看到 X 輸出、接著做 Y」是 hands-on 教學風格 |
| Code review 評論 | 評論本身就是針對某行 code、上下文是 inline 共享的 |
判讀:寫之前自問「我的文章是『教讀者怎麼讀這份 code』還是『教讀者一個概念 / 框架』?」——前者可依賴 code、後者要 self-contained。
跟其他抽象層原則的關係
| 原則 | 跟本卡的關係 |
|---|---|
| Compositional-writing 規則二:商業邏輯先於 CASE | 本卡是規則二在「字句層 reference 處理」的子場景——規則二講層次順序、本卡講論述不依賴 code reference。兩者有重疊但不完全相同 |
| #67 寫作便利度跟意圖對齊反相關 | 用「那個 payload」是寫作便利、self-contained 論述需要刻意翻譯——同骨展現 |
| #111 口語化修辭會稀釋技術精度 | 「就好」「就能」這類字句既是口語也是依賴 code、本卡跟 #111 在這層重疊 |
| #110 設計檢討用當下三軸論證、不依賴 hindsight | 三卡都是「論述要能讓讀者拿來用」的不同層次——當下視角 / 精度 / self-contained |
判讀徵兆
| 訊號 | 該做的行動 |
|---|---|
| 論述用「那個 / 這個 / 剛才」當主詞 | 換成具體名詞 + 角色描述 |
| 論述提到「第 X 段 / 第 Y 行 / 那個欄位」 | 翻譯成業務角色描述 |
| 段落用「就好 / 就能 / 就行」結尾 | 把省略的條件補回去 |
| 把 code block 拿掉後論述讀不通 | 整段重寫成 self-contained |
| 寫的時候有 code 在記憶中、覺得「不用解釋」 | 預設讀者跳過 code、強迫自己用文字描述 |
核心原則:技術文章的論述要 self-contained——讀者跳過所有 code block 仍能理解論述、是「商業邏輯先於 CASE」的延伸實踐。寫完後跑一次「拿掉 code block 還讀得通嗎」自測、讀不通 → 翻譯成 self-contained 描述。
Self-case:本卡的觸發來源
本卡的觸發是修 Dart StreamController:single-subscription vs broadcast 的事故實錄 時、讀者指出某段論述「重點混淆 + 過於口語 + 預設讀者看過原始碼」。
問題段(修補前):
「技術上這需求很乾淨:service 早就在事件 payload 第二段帶了『這次變動是哪筆』,現在只要有人訂閱、把它記錄下來,UI 就能用。於是收銀主畫面的 controller 加了第二個訂閱。」
問題分析:
- 「事件 payload 第二段」依賴讀者看過 code 結構
- 「就能用」「就好」省略了具體條件
- 「於是 X 加了 Y」對話風格、不是論述風格
修補後:
「這個需求剛好對應 service 已經備妥但尚未被消費的資訊——service 對外的事件 payload 從原始設計就分兩段:一段是『當前完整的商品列表』、另一段是『這次變動的具體品項』。第二段是當初為『需要追蹤單筆變動的訂閱者』預留的擴充欄位、過去幾個月一直沒被消費。新需求只要新增一個訂閱者讀這段資訊、再把它對應到 UI 上的視覺標記即可——介面不需要變動、payload 結構不需要調整、實作範圍只限於新增訂閱端。」
修補後的論述用「角色描述(『需要追蹤單筆變動的訂閱者』)」替代了「位置描述(『第二段』)」、補回了省略的條件(「介面不需要變動、payload 結構不需要調整、實作範圍只限於新增訂閱端」)。讀者即使跳過所有 code block 也能理解這段論述。
對應本卡:論述依賴 code 是「寫的人有 code 在腦中」的便利選擇——multi-pass review 要在輪 5「反例 / 邊界」加掃 self-contained frame、用「拿掉 code 還讀得通嗎」自測。