Demonstrating Commands — 示範指令:程式碼區塊裡放什麼
示範指令:程式碼區塊裡放什麼
這份管區塊裡放什麼——哪些東西進區塊、註解裝什麼、什麼時候要並列兩條、什麼時候要另外開一張參數表。排版的機械規則(語言標示、前後空行、tab 處理)由各專案的 markdown 規範與 lint 工具管,不在這裡。
動手之前先定這一節要讀者做什麼,三個答案對應三種形態:
| 讀者在這一節要做的 | 形態 | 產出 |
|---|---|---|
| 照著跑 | 單條指令 | 一行指令,引數在散文裡交代 |
| 從兩種裡選一種 | 並列對照 | 兩條並列、各帶上方註解、中間空一行 |
| 查一個參數 | 參數表 | 區塊給常用變體,表格逐個解釋,讀者逐列查 |
判斷標準問句:讀者在這一節要做的是照著跑、選一種、還是查一個。 三個答案互斥,答不出來代表這一節的職責還沒定。
註解在渲染之後是另一層
語法高亮會把註解上一種與指令不同的顏色,所以讀者掃過區塊時看到的是兩層:指令是可以複製的東西,註解是關於它的標籤。這個視覺分層決定註解該裝什麼——標籤式的短句(這一條是什麼、做完會怎樣、它是哪一種變體),而推導與機制寫在散文裡。散文是讀者停下來讀的地方,註解是他掃過去的地方。
兩種位置各有用途:
1## 上方整行註解:標示一條指令的身分或變體,並列對照一律用這種
2adb -s <serial> exec-out screencap -p > screen.png
3
4adb -s <serial> logcat -c # 行尾註解:短標籤,十幾個字以內行尾註解超過十幾個字會把那一行推寬,在窄螢幕上換行之後與指令混在一起,視覺分層消失。並列對照用上方註解,因為兩條要上下對齊著比較。
渲染端若開了行號,區塊裡的空行同樣佔一個行號,而行號隨任何一次增刪位移。 所以散文不要用「第 3 行」指涉區塊裡的內容,改用指令本身或註解文字指涉(「寫路徑那一條」)。
並列對照:引數或選項的有無改變行為時
同一個子命令有兩種正當用法而讀者要選時,兩種各給一條指令,並列在同一個區塊裡。並列是修法本身,文字說明是它的補充——只給一條、另一條用散文描述時,讀者要從那段文字重建出另一條指令長什麼樣,而他來這一節就是為了拿指令。
1## 不寫路徑:PNG 的位元組送到標準輸出,由本機的 > 存檔,裝置端不留中繼檔
2adb -s <serial> exec-out screencap -p > screen.png
3
4## 寫路徑:PNG 存成裝置上的那個檔,標準輸出是空的,檔案要另外取回
5adb -s <serial> shell screencap -p /sdcard/screen.png散文接在區塊後面,只需要說那個差別的意義與各自的完整流程去哪裡看:兩條的差別在版面上已經直接看得見。
這個形態最容易漏掉,因為兩種都是正確用法。錯法對正確、設定對還原這兩種對照作者會自然寫成並列(讀者照順序跑就好);兩種都正確而該用哪一種取決於環境時,作者知道兩種都在腦子裡,於是寫一條加一段文字——而那正是讀者要選、最需要並列的情形。
參數表:讀者逐列查的時候
同一個工具有三個以上的選項或引數,而讀者的動作是「我有一個需求,哪個參數對應」時,用表格。讀者照順序跑的時候不用——行尾註解就夠。
區塊給常用變體、表格逐個解釋,兩者分工:
1adb -s <serial> logcat -G 16M # 放大緩衝區
2adb -s <serial> logcat -c # 清空,時間窗起點
3adb -s <serial> logcat -d -v threadtime > install.log # 倒出,時間窗終點| 參數 | 用途 |
|---|---|
-G 16M | 放大緩衝區。常駐服務多的機器在重現過程中就可能把最早的行覆蓋掉,而關鍵行往往正是操作之前那幾行。對現況調整值 |
-c | 清空 |
-v threadtime | 每行帶 PID / TID 與精確時間戳 |
用途欄的深度隨那個參數的判斷難度變化。 -c 寫「清空」就夠,-G 要寫什麼機器需要放大、值怎麼定。為了整齊把每列拉成等長是反模式——等長的表格讀起來像模板,而讀者停在某一列是因為他要那一列的判斷依據。
參數進了表格之後,區塊裡的註解可以短到剩一個詞或省略,兩邊不重複同一句話。
表格的消費單位是逐列查詢,所以每列要自足(讀者不會為了讀懂一列而去讀鄰列),而這類表格不必為每列補延伸段落。
佔位符與字面值
讀者要換的位置用 <描述性名稱>,名稱說出那個位置填什麼:<serial>、<package>、<行數>、<SurfaceFlinger 的編號>。
換不掉的字面值照寫(協定常數、固定的系統路徑、op 名稱)。
邊界情形是「可以換、而這一篇選了一個」——示範裡的 /sdcard/screen.png 就是。它既不是佔位符也不是不可換的常數,處置是照寫字面值,並在散文交代選它的理由;理由決定讀者能換去哪裡。這一類最容易漏掉說明,因為尖括號自帶「這裡要換」的訊號而字面值長得像一個完整可用的答案,於是同時逃過作者的說明義務與讀者的警覺。判斷標準見 literal-operand-hides-the-change-point。
散文裡夾了一段程式,就把程式與逐行解釋移進區塊
散文段落的職責是推導,程式碼的職責是讓讀者整段看、整段複製。一段散文把同一段查詢或指令的零件(條件、子句、關鍵字、輸出)一個個用行內程式碼嵌進句子裡,讀者就要一邊讀推導、一邊在腦中把那些零件組回原本的程式——兩件事搶同一份注意力,而組回來的程式在頁面上從來沒有完整出現過。
判斷問句:這一段散文的行內程式碼,拼起來是不是一段讀者該整塊看的程式(一個查詢、一條指令、一段輸出);讀者要不要在腦中把它們組回去,才讀得懂這一段。 是的話,分成兩層:
- 程式與逐行解釋進區塊:完整的程式寫出來,每一行的解釋寫在那一行旁邊或下方的註解——這一行做什麼、這個值判成什麼、這行輸出代表什麼。對照的兩段(錯法與正解、改前與改後)並列在同一個區塊,各用一行上方註解標身分。
- 推導留在散文:跨行成立的道理(為什麼兩段等價、這個結果對論證意味著什麼、這個方法判不出什麼)寫在區塊前後的段落裡,行內程式碼降到單一關鍵字或識別字。
改前(一整段散文,行內程式碼十幾處):
「不等於五」的錯法是它判得出來的例子。
WHERE 評價.星等 <> 5對外連接補上NULL的列判成未知,那些列全數被WHERE濾掉,所以LEFT JOIN為了保留沒有評價的訂單而補出來的列一列都留不住……PostgreSQL 的EXPLAIN對「不等於五」那段查詢印的是Hash Join,對正解印的是Hash Left Join。
改後,散文只留推導,程式與逐行解釋進區塊:
「不等於五」的錯法是它判得出來的例子:這段查詢寫了外連接,而篩選條件讓外連接補出來的列全數留不住,結果與內連接在任何資料上都相同。最佳化器做這個改寫時不產生任何警告,要讀執行計畫才看得到:
1-- 「不等於五」的錯法
2EXPLAIN
3SELECT 訂單.訂單編號 FROM 訂單
4LEFT JOIN 評價 ON 評價.訂單編號 = 訂單.訂單編號
5WHERE 評價.星等 <> 5;
6-- 外連接替沒有評價的訂單補一列,那一列的星等是 NULL
7-- NULL <> 5 的真值是未知,WHERE 只留判成真的列,補出來的列全數被濾掉
8-- PostgreSQL 16 印出 Hash Join:最佳化器已經把 LEFT JOIN 改成內連接
9
10-- 正解
11EXPLAIN
12SELECT 訂單.訂單編號 FROM 訂單
13LEFT JOIN 評價 ON 評價.訂單編號 = 訂單.訂單編號 AND 評價.星等 = 5
14WHERE 評價.評價編號 IS NULL;
15-- PostgreSQL 16 印出 Hash Left Join:外連接保留下來全站實跑時遇到的形態,處置各不相同:
- 同一段程式已經在前後文的區塊裡:不重貼一份,把散文裡的逐行解釋併進那個既有區塊,寫成對應那一行的註解,散文改成指向區塊。
- 原文只描述了程式、沒給完整寫法,而改寫者沒有環境可以跑:區塊裡只放原文給得出的部分,不補指令、選項或輸出。選項從沒組成完整指令的,用
text區塊列成選項清單;只有測試條件與結果的,用text區塊寫條件與各自的結果。補出來的程式讀起來可以執行,而它從來沒被跑過。 - 散文描述了一段頁面上沒出現的程式(改一列資料之後回什麼、補一個條件之後回什麼、換成另一種寫法回什麼、展開之後長什麼樣):行內程式碼可能只有兩三處,十處的門檻掃不到,偵測靠句型(
rg '(多|加|再加|刪掉?|改成)[一兩]?[則列筆張個][^。]{0,20}之後|換成[^。]{1,20}回|展開(之後|成)|也修不掉')。能跑的時候把那段程式與那份資料寫出來、實跑一次,各變體的結果寫成區塊註解(-- 原本的資料:102、-- 多一則三星之後:101),散文只留結果對論證的意義;沒有環境可以跑,照「原文只描述了程式」那一條處理。寫出來常會逼出散文沒決定的事(連接是外連接還是內連接、資料實際長什麼樣),那正是要核對的地方。 - 幾條沒有操作對象的短指令(
chmod 440、chmod 600各給不同的檔,而原文沒寫檔名):放進區塊會變成複製了也跑不起來的指令,留在散文。 - 搬進區塊之後,程式與散文對不上:散文說
update要同時看寫入前後的狀態,區塊裡的規則只檢查了寫入前——散文把程式拆成零件的時候,兩者的矛盾沒有位置並排出現;並排之後才看得到。以正確的那一方為準修另一方,修的是程式時要回規格核對。
不必移的:行內程式碼各自是一個獨立的名字,不是同一段程式的零件——欄位名、函式名、設定鍵、檔案路徑被當成名詞提到;以及一整串工具或指令名被列舉時(那是清單的問題,改成條列或表格,不是改成區塊)。
不進區塊的內容
- 錯誤用法:區塊是可以複製的位置。錯的寫法寫在散文裡用行內程式碼標出來,連同它的回報訊息一起。例外是錯誤本身就是主題(診斷、錯法與正解的對照):這時錯的寫法進區塊,用上方整行註解標明它是錯的,並與正解並列
- shell 提示符(行首的
$或%):讀者整段複製貼進終端機會出錯 - 跨行的推導與它對論證的意義:寫散文。只屬於某一行的解釋(這一行做什麼、判成什麼、輸出代表什麼)反過來要進區塊,寫成那一行旁邊的註解——見〈散文裡夾了一段程式,就把程式與逐行解釋移進區塊〉
預期輸出可以進,形式是跟在指令下方的註解:
1adb -s <serial> exec-out "printf 'a\nb\n'" | xxd
2## 610a 620a — 沒有轉換一次走查
改前——一條指令,另一種用法活在文字裡,引數沒有說明:
1adb -s <serial> exec-out screencap -p > screen.png
screencap -p在裝置上把當前畫面編碼成 PNG 寫到標準輸出。需要留在裝置上時改成給檔案路徑的形式,再取回本機。
讀者拿不到第二條指令長什麼樣,也不知道 screen.png 這個位置能換成什麼。
改後——兩條並列、各帶註解,散文只留差別的意義:
1## 不寫路徑:PNG 的位元組送到標準輸出,由本機的 > 存檔,裝置端不留中繼檔
2adb -s <serial> exec-out screencap -p > screen.png
3
4## 寫路徑:PNG 存成裝置上的那個檔,標準輸出是空的,檔案要另外取回
5adb -s <serial> shell screencap -p /sdcard/screen.png
screencap在裝置上抓當前畫面,-p指定編碼成 PNG。它另外接受一個選用的引數——輸出的檔案路徑——上面兩條的差別只在寫不寫這個引數,而它決定畫面往哪裡去。
檢查清單
- 這一節的形態定了:讀者是照著跑、選一種、還是查一個
- 每一條指令的每一個引數在文章裡有說明——逐條問「讀者用在自己的環境上要改哪幾個位置」
- 讀者要換的位置用了
<描述性名稱>;選了一個字面值的位置在散文交代了選它的理由 - 同一個子命令的兩種正當用法各自出現在區塊裡,沒有一種只活在文字裡
- 註解是標籤不是推導;行尾註解在十幾個字以內
- 三個以上選項而讀者逐列查的地方有參數表;用途欄的深度隨判斷難度變化,沒有為了整齊拉成等長
- 區塊裡沒有錯誤用法、沒有 shell 提示符
- 散文沒有用行號指涉區塊裡的內容
- 散文段落裡沒有一段要讀者在腦中組回去的程式:同一段查詢或指令的零件被嵌成一串行內程式碼的,程式與逐行解釋移進區塊、推導留在散文