我們用 SMART——Specific(具體)、Measurable(可衡量)、Achievable(可達成)、Relevant(具關聯)、Time-bound(有時限)——把每個故事拆成五天,作為每次動手造輪子前的五個檢查問題。
本篇是故事三「你的 I²C、SPI 我看不懂,先幫我包成 AT Command」的 Measurable 篇:什麼證據可以證明問題已經解決?
本篇定位:讓讀者看見「只包一層」實際增加的契約與測試成本。
組長沒有否決 AT Command 提案,他做了更狠的事:「當作正式提案評估。你們兩個把這層介面的驗收條件寫出來——不是能不能包,是包完之後怎麼證明它做完了。」
我和那位資深工程師真的坐下來寫,第一行就卡住。命令要從哪裡進來?兩邊在同一顆晶片裡,沒有現成的線;要讓這套命令成立,第一件事是憑空生出一條通道:硬拉一組 UART 自己接回自己,或在晶片內部做一條字串佇列,讓兩個模組隔著它收發。通道一旦生出來,後面所有問題都是從它身上長出來的。
清單於是自己開始長。命令總表一條一列,很快就寫完了;接在後面的全是圍著它長出來的東西:
寫到後來,桌上安靜下來。命令本身很短,圍著它長出來的東西很長,而那些都是為了讓命令本身能被信任。
那幾頁紙揭露的是:函式呼叫的世界裡,很多保證是語言免費送的。傳錯型別、少帶參數,編譯器當場擋下;同步呼叫回來時事情已經做完,不存在「送出去了但不知道有沒有做」;掛上除錯器(debugger),呼叫堆疊一目瞭然。
得補一句公道話,免得把驅動說得太乾淨:走直接記憶體存取(DMA,direct memory access)或中斷完成通知的驅動,一樣有「已下達但尚未完成」的狀態;差別在於它關在同一份程式裡,用旗標與回呼就能表達,不必跨過一條會遺失、會亂序、會斷線的通道,兩端各寫一套。
換成字串協定,免費的保證不會憑空消失,是轉成自己要定義、實作、測試的功能:型別檢查變成解析器(parser)的錯誤處理,呼叫語意變成逾時與重試規則,堆疊追蹤變成側錄加一支自製工具。抽象層是一份契約,而契約的成本不在簽署那天,在往後每次改版都得回頭驗證它沒被打破。
錯誤碼這一項倒是不必從零發明:現成標準的慣例抄得到——通訊模組那套命令集的結果碼、作業系統的錯誤碼分類,都是能直接借用的成品。但抄省下的只是命名的力氣,借了哪一套語意,就得照那套語意實作、測試與維護,不能只挑編號好看的來用。抄讓清單少寫幾行,不會讓它消失。
把「要不要包一層」從品味之爭變回成本計算,方法很土:新增抽象層的提案先寫完驗收清單再決定做不做,而清單要能一項一項勾。
配套最常被當成附贈品,但沒有工具的協定,等於逼使用者自己再發明一次除錯方法。
接著做一次對照:同樣的功能改用驅動 API 交付,每一列變成什麼?
| 要驗收的事 | 字串協定 | 驅動 API |
|---|---|---|
| 型別與參數 | 自寫解析器與錯誤處理 | 編譯器檢查函式簽章 |
| 錯誤表達 | 自訂錯誤碼表與處置文件 | 回傳值與既有錯誤列舉 |
| 非同步通知 | 主動回報訊息與交錯規則 | 回呼 |
| 逾時與重試 | 需定義語意、實作並測試 | 只有匯流排層級的逾時,不需要跨端重送與冪等語意 |
| 版本相容 | 命令集版本與相容規則 | 標頭檔與函式庫版本 |
差額不需要形容詞就站在那裡,它就是這層字串介面的真實標價。剩下的問題只有一個:這個價格買到什麼?如果答案是「他不用讀暫存器表」,右邊那欄用零頭買到同一件事。
「只是包一層」的「只是」,大概是工程裡最貴的副詞。一層不是幾個函式,是一份要有人長期守住的契約——命令格式、錯誤碼、逾時語意、版本相容全都是。驗收清單的價值不在阻止人做事,在讓成本現形:當每一項都得有人具名負責測試與維護,很多「順手包一下」會自己安靜下來。
不過帳算完只證明了完整的命令協定很貴,應用端「不想碰暫存器」的訴求仍然成立,總得給人一條路。好消息是從一個函式到一套協定之間不是懸崖,是一道階梯。那道階梯有幾階、每往下一階要多付什麼、憑什麼證據才能往下踩?下一輪就處理這件事。