跳至主要內容
蒼時弦也
蒼時弦也
資深軟體工程師
發表於

Sumitsubo:用文件來檢查實作

Sumitsubo 是我用來將「規格」變成實際可驗證程式碼的工具,在 Sumitsubo:把規格做成 Linter 的初期試做中採用了 JSON 來描述規格,然而在調查有沒有相似工具時,意外發現直接讓 Markdown 變成結構化的資訊來源,就可以用寫文件的方式來設計工具。

結構化資料

最初選擇 JSON 的考量有很多,也包括 LLM(大型語言模型)很擅長維護這類型的檔案格式,同時需要有結構化資料才能做機械式的處理,但我卻忘記 Markdown 本身也具備這樣的特性。

舉例來說,使用 ### 就可以表達深度,如果用 \n\n(兩次換行)就可以表達一個文字段落,基本上每一段都是可被解析的,這也是為什麼可以很輕鬆的將 Markdown 轉換成網頁,更好的是語法非常的少,解析起來並不困難。

也因此,我又花了一週的時間,將原本使用 JSON 定義規格改為使用 Markdown 並且移除 JSON 渲染為 Markdown 的能力,因為現在不需要兩者共存,只需要有 Markdown 就可以完整的表達我原本想達到的規格資訊。

但是也有新的困難出現,解析後的 Markdown 都是相同的格式,要怎麼表達不同規格上的需求呢?

抽象化設計

要解決 Markdown 撰寫結構化的規格,比預想中還算簡單。實際上就是軟體開發常做的「抽象化」處理,如果我們能把規格轉換成某個概念,那麼應該有一種資料型態可以把所有能檢查的規格統一的描述。

在確認概念時,意外的發現 ReqIf 這個標準,完整的名字是 Requirements Interchange Format 簡單說就是用來交換「需求」的標準格式,而這個需求剛好就是 PRD(Product Requirement Document)的需求,可以看作是規格文件中的一種類型。

也就是說,把規格用結構化的資料表示並不是一個全新的概念,只是以往很少被拿來設計成檢查工具(Linter)而已,在 Sumitsubo 則借用 ReqIf 的概念,把規格定義為

  • Specification - 一個規格檔案
  • Statement - 一條規格項目,可以巢狀表示

至於如何巢狀,只要簡單地用 ### 就可以區分出來,而且 Markdown 幾乎是天然的支援這樣的設計,把標題當作識別(Identity)也非常自然,因此就可以寫出像是這樣的規格。

1## `MD-001` 解析 Markdown 規格
2
3當使用者提供符合條件的 .md 檔案時,則解析成結構化的規格。
4
5| Given | 當 .spec/glossary.md 存在時 |
6| When  | 使用者呼叫 `sumi verify`    |
7| Then  | 回傳 `exit 0`              |

因為撰寫跟維護都非常容易,而且能很好的傳遞行為的意圖,對 AI 在參考規格實作時,就可以減少漏掉某條需求的問題,對人來說也算是相當好維護的。

語法樹比較

會轉換成 Markdown 實際上是因為語法樹(Syntax Tree)的需求,透過 JSON 表達一個介面契約(Contract)的時候,很難滿足不同語言的需要,這也讓使用 Tree Sitter 支援多語言的特性變得意義不大。

幸好已經有像是 Semgrepast-grep 這樣使用 Tree Sitter 的工具給出例子,透過 Tree Sitter 是可以用來「尋找某段符合條件的程式碼」這樣的目的,也因此就可以利用這個特性,來限制 AI 在實作時,可以穩定的定義某些公開方法,而不是隨時在改變。

 1## `Sumitsubo::Place.of`
 2
 3產生一個 Place 物件,用來表示某一個規格所定義的位置。
 4
 5```ruby
 6class Sumitsubo::Place
 7  def self.of(path, line)
 8  end
 9end
10```

像這個樣子,我就可以要求在搜尋範圍內的檔案中,必須要有一個 Place 物件,並且具備一個 Class Method 叫做 of(path, line) 如何實作的不重要,但是我必須看到這樣的程式碼存在。

當某一天 AI 在修改時破壞了這個約定,就會在 sumi verify 看到這樣的訊息

1$ sumi verify
2.spec/contract/place.md:3 ruby Sumitsubo::Place.of is defined nowhere this specification includes, and one the reading cannot see never is
31 difference

來告訴 AI 這個是規格上的約定不該去修改,那麼在驗證 AI 生成的實作,所需要確認的就可以縮減到文件的範圍,而不是在整個專案層級中檢查跟搜尋。

很多時候,我們關心的是公開方法跟行為,內部的私有方法本身是被封裝的時候不一定是最優先要確認的,在 AI 大量產生程式碼的時代,檢查就會更加的吃力,但有這樣的機制做防護,除錯的範圍就會變小,也更容易溝通。

現階段 Sumitsubo 的功能還處於很初期的階段,但我相信未來應該也會陸續出現這類型的工具,來應對未來軟體開發的需求,不單純是測試、語法檢查,可能也會逐漸把規格(意圖)和實作對齊,即使是像 Sumitsubo 這樣只能檢查少部分,但也會比完全不檢查或者難以檢查再更好一些。