散文裡夾了一段程式,就把程式與逐行解釋移進區塊;推導留在散文
散文段落的職責是推導,程式碼區塊的職責是讓讀者整段看、整段複製。一段散文把同一段查詢或指令的零件——條件、子句、關鍵字、執行計畫的輸出——一個個用行內程式碼嵌進句子,讀者就得一邊讀推導、一邊在腦中把那些零件組回原本的程式,而組回來的程式在頁面上從來沒有完整出現過。修法是分兩層:程式與只屬於某一行的解釋進區塊,寫成那一行旁邊的註解;跨行的推導與它對論證的意義留在散文。
失效的形態
一篇 SQL 教材說明靜態分析判得出哪一種錯法,原文是一整段散文,行內程式碼十幾處:
「不等於五」的錯法是它判得出來的例子。
WHERE 評價.星等 <> 5對外連接補上NULL的列判成未知,那些列全數被WHERE濾掉,所以LEFT JOIN為了保留沒有評價的訂單而補出來的列一列都留不住:這段查詢與把LEFT JOIN改成JOIN的版本在任何資料上都回同一批列。……PostgreSQL 的EXPLAIN對「不等於五」那段查詢印的是Hash Join,對正解印的是Hash Left Join。……評價表的訂單編號在建表時沒有宣告NOT NULL,表結構已經說了那一欄可能是NULL,而子查詢只要交出一個NULL,NOT IN對每一列都判不成真。
這一段講的是兩段查詢、兩份執行計畫、一份建表語句,五段程式沒有一段完整出現,全部拆成零件散在句子裡。每一句單獨看都正確,讀者要做的卻是把 WHERE 評價.星等 <> 5、LEFT JOIN、Hash Join 分別放回它們所屬的那段程式,再回頭理解句子在說什麼。
改寫後,散文只留推導(這段查詢寫了外連接而篩選條件讓補出來的列留不住、等價由三值邏輯決定、最佳化器改寫時不產生警告),兩段查詢與它們的 EXPLAIN 並列在一個區塊,每一行的註解寫這一行判成什麼、這份計畫代表什麼;建表語句與 NOT IN 查詢並列在另一個區塊,因為風險由兩者合起來決定。
跟既有規則的衝突與調和
compositional-writing 的〈示範指令〉原本寫「推導與機制:註解裝標籤,機制寫散文」。照字面,上面那段的每一句都是機制,應該留在散文——那正是失效的來源。規則要分的是解釋的作用範圍:只屬於某一行的解釋(這一行做什麼、這個值判成什麼、這行輸出代表什麼)離開那一行就要讀者自己對回去,所以寫成那一行的註解;跨行成立的道理與它對論證的意義沒有單一的一行可以附著,所以寫散文。
同一處另有一條「錯誤用法不進區塊」,理由是區塊是可以複製的位置。錯法本身是主題的時候(診斷、錯法與正解的對照)這條不適用:錯的寫法要完整出現才讀得出它錯在哪,處置是進區塊、用上方整行註解標明它是錯的、與正解並列。
偵測
計數掃得到候選:去掉程式碼區塊之後,一個散文段落裡的行內程式碼達到十處以上。在全站教學內容跑一次有 57 段,linux、backend、llm 最多。
計數分不出兩種情形,命中之後逐段問:這些行內程式碼拼起來,是不是一段讀者該整塊看的程式(一個查詢、一條指令、一段輸出)。 各自是獨立名字的不必移——欄位名、函式名、設定鍵、檔案路徑被當成名詞提到;一整串工具名被列舉的,是清單的問題,改成條列或表格。
全站掃過一次之後
候選段落 45 段(去區塊後單段行內程式碼十處以上、且在檔案裡唯一),分四批交給改寫者,判定要改 16 段,主 session 退回一段、實際改 15 段;不改的多數是欄位名、列舉值、設定鍵、按鍵這類各自獨立的名字,以及已經整條寫在行內的單一指令。
改寫者回報了幾種規則原先沒寫到的形態,下面三種的處置已寫進 demonstrating-commands.md:同一段程式已經在前後文的區塊裡(逐行解釋併進既有區塊的註解,不重貼);原文只描述了程式、沒給完整寫法(區塊只放原文給得出的部分,選項清單與測試結果用 text 區塊,不補指令);幾條沒有操作對象的短指令(chmod 440 各給不同的檔而原文沒寫檔名,放進區塊會變成跑不起來的指令,這一段退回不改)。
最值得記的是另一種:把散文裡的零件搬回區塊之後,程式與散文對不上。 一篇 Firestore 安全規則的散文寫「update 兩者都要看」(寫入前的 resource.data 與寫入後的 request.resource.data),而上方骨架的 allow update, delete 只檢查了寫入前的 ownerId——擁有者可以把 ownerId 改成別人,這是一個授權漏洞。散文把程式拆成零件的時候,兩者的矛盾沒有位置並排出現;逐行解釋要寫成那一行的註解時,註解與它旁邊的條件對不上。修法是把骨架的 update 與 delete 拆開,update 加上寫入後的檢查。這跟 #367 命名時發現寫反的論斷是同一種現象:排版與命名都逼作者把一個原本可以不決定的對應寫死。
計數掃不到的形態:散文描述了一段頁面上沒出現的程式
同一篇的第二輪改寫找到另一種形態,行內程式碼每段最多五處,十處的門檻掃不到。這一種描述一段頁面上從沒出現的程式;〈失效的形態〉那段靜態分析的例子則是把現成的程式拆成零件。這一種的例子:「訂單 101 多一則三星之後,同一段回的是 101」(改過資料的那個版本沒寫出來)、「補 OR 評價.星等 IS NULL 也修不掉」(補上條件的那段查詢沒寫出來)、「NOT IN 展開之後是『不等於 101,而且不等於 NULL』」(展開式用散文轉述)、「同一句換成 NOT EXISTS 回的仍然是 102」、「替 101 再加一則評價之後把訂單接上評價再加總……總額變成 1100」。讀者要自己寫出那段程式才驗證得了句子,而寫的人也沒有被逼著寫出來:最後一例寫成程式時才要決定連接是 LEFT 還是內連接——內連接在原本的資料上加總是 300,不是句中的 800;同一篇另一處提到「五張訂單加七則評價」的反例資料,那份資料在頁面上根本不存在,寫出來、跑過之後才發現原文列的資料狀態與三種錯法需要的對不上。
偵測靠句型:資料變體(「多一則……之後」「再加一則……之後」)、改寫變體(「換成 X 回」「補 X 也修不掉」)與展開式(「展開之後」「展開成」)。rg '(多|加|再加|刪掉?|改成)[一兩]?[則列筆張個][^。]{0,20}之後|換成[^。]{1,20}回|展開(之後|成)|也修不掉' 在那一篇的改前版本命中全部五段;全站教學內容 89 檔 113 處,是候選不是判決——描述的變體已經寫在鄰近區塊裡的、或只是一句敘事的不必動。
處置是把那段程式與那份資料寫出來、實際跑一次,各資料變體的結果寫成區塊註解(-- 共用資料:102、-- 訂單 101 多一則三星之後:101),散文只留這些結果對論證的意義。這跟上面「原文只描述了程式、沒給完整寫法時不補」的差別在能不能跑:改寫者手上沒有環境時,補出來的程式讀起來可以執行而從來沒被跑過;作者能跑的時候,寫出來並跑過就是修法,而跑的這一步常常就是找到錯的那一步。
SQL 分類其餘 23 篇接著逐篇檢查一次(與抽變數一起查),改了 138 處,多數是這一種:「換成 X 回」「改放 WHERE」「多一張訂單之後」「給出兩個不同的計畫」這類句子描述的程式都寫成了區塊,而句型 pattern 只抓到其中一小部分——「換成 JOIN,結果一個字都沒有變」「若換成 INNER」「加上引號之後」都不在 pattern 裡,要逐篇讀。寫成程式跑過之後對出十幾處內容錯,每一處在散文裡都讀得通:自連接找每組最大值時「在子查詢裡加一句 IS NOT NULL」攔不住金額是 NULL 的那一列(要寫在外層);CREATE INDEX ON 顧客(姓名 COLLATE NOCASE) 在 SQLite 是語法錯誤;NO ACTION 允許同一交易裡先刪父列,只在約束宣告成 DEFERRABLE 時成立;一段 MySQL 範例的 INSERT 給了三個值而表有四欄;權限範例照頁面順序執行時 GRANT 由剛切換過去的角色自己發出而被拒絕;「漏掉分區」的錯版固定少報,其實取決於同一天兩列誰排在前面,換個排法就同時多報與少報;「兩則錯誤訊息各自寫出了理由」而實際的訊息只說了不准。
論述基礎與限制
依據是一段改寫與一次全站掃描(45 段候選、15 段改寫),加上同一篇第二輪找到的五段描述式形態;描述式的句型 pattern 只在那一篇上驗過命中率,全站 113 處候選還沒逐處判定。十處是取來縮小候選的門檻,不是判定條件:五處行內程式碼就拼成一段查詢的段落一樣違規,二十處檔案路徑的段落可能合規。
跟其他抽象層原則的關係
- #360 示範指令的引數——那一張管區塊裡的字面值有沒有說明;本卡管該進區塊的程式有沒有進去。
- #367 切開一個集合之前先寫切分的依據——同一段改寫先做的那件事:先替「靜態分析」命名,再把它判得出什麼示範出來;本卡處理的是那個示範怎麼排。
- #302 定位決定體例——那一張記下規則的觸發條件比判斷標準先生效;「推導與機制寫散文」照字面觸發在逐行解釋上,是同一種失效。
判讀徵兆
- 一個散文段落裡,同一段查詢或指令的條件、子句與輸出分散在好幾句的行內程式碼裡。
- 讀者要回到上方的區塊或自己拼湊,才知道句子裡的
WHERE ...是哪一段查詢的哪一行。 - 執行計畫或指令輸出只以行內程式碼出現,旁邊沒有產生它的那段程式。
- 散文寫「多一則……之後回的是」「換成 X 回」「展開之後是」,而頁面上沒有那段程式或那份資料。
Case
2026-09-29,SQL 教材 SQL.13〈「問對了沒有」的判定:靜態分析、約束與反例資料〉的靜態分析段。使用者指出「這裡程式碼跟文字夾雜是不好的做法,純文字解說的部分留在文章段落中,程式碼內容跟對應的解釋應該用 code 區域用註解的方式呈現,不然現在難以閱讀」。