文檔代碼化實踐
文檔代碼化,將文檔以類代碼的領域特定語言的方式編寫,並借鑑軟件開發的方式(如源碼管理、部署)進行管理。它可以藉助於特定的工具進行編輯、預覽、查看,又或者是通過專屬的系統部署到服務器上。面向非技術人員的文檔代碼化的一種常見架構模式是: 編輯 - 發佈 - 開發分離 ,
最近一個月裏,我在開發一個基於 Git + Markdown 的全新文檔系統。我定製了一個基於 markdown 的標記語言,以支持起雷達圖、條形統計圖、思維導圖等圖表的文檔系統。這個系統將在未來幾個月內發佈。當然了,視進度而看,也可能是月底。
過去的幾年裏,我們一直在討論各種各樣的代碼化,基礎設施代碼化、設計代碼化、需求代碼化……。在我的那一篇《 雲研發:研發即代碼 》中,設計了一個完全代碼化的軟件開發流程。而今天我們將討論另外一個有趣的存在:文檔。
在《 架構金字塔 》中,我將文檔定義爲支撐五層架構模型的一種存在。因爲文檔在一個系統中是非常重要的存在,我們用它來指導開發工作,用它來記錄問題,用它來寫下規範……。總而言之,它很重要,所以我們重新討論一下這個話題。
引子 1:架構決策記錄:格式化文檔
三年前,當我第一次接觸到『 架構決策記錄 』的概念時,我被它的理念所吸引:
- 使用輕量級文本格式化語言描述重大決策
- 跟隨代碼一起版本化
- 使用某種特定的文檔格式(標題、上下文、決策、狀態、後果)
隨後,我使用 Node.js + TypeScript 寫了一個 ADR 工具。現在,在我的大部分開源薦中,我都會使用它來管理一些技術決策。因爲基於這個理論設計的這個文檔系統真非常棒,我可以查詢到:
- 一個技術決策發生的時間和架構改變,對應的修改人
- 回溯所有的技術決策,從中整理出架構發展過程
- 所有的決策都是記錄在版本控制系統中,可恢復
- 易於管理和維護
對於一個長期開發的系統來說,它真的非常有用。
引子 2:靜態站點生成:數據代碼化
靜態站點生成是一種混合式的 Web 開發方法,它通過部署預先構建的靜態文件進行部署,來讓開發者在本地構建基於服務器的網站。
當 GitHub Pages 成爲了程序員首選的 博客 / 內容 / 文檔 服務器時,他 / 她也採用了靜態站點生成這一項技術。靜態站點生成有各種各樣的優點:
- 可靠性、安全性、穩定性、可用性等更好
- 可版本控制
- 易於測試
- 易於實踐持續部署。提交即可上線
- 靈活,易於定製
而事實上,靜態站點生成所做的最主要的一件事是:將數據庫中的數據進行代碼化。採用諸如 Wordpress 這樣的 CMS 時,我們是將數據存儲在數據庫中,以實現對於數據的 CRUD。一篇文章變爲數據庫二進制文件中的一個片段。
隨後,靜態站點生成工具做了第二件事情便是將文本內容可視化出來,便於人們閱讀。這樣一來,我們便實現了發佈 - 開發分離。
引子 3:定製的標記語言:擴充
將數據代碼化時,我們面臨了一個非常大的挑戰:易於編寫、閱讀的標記語言(如 markdown)只設計了內容的形式,缺少了內容相關的其它信息,諸如於創建時間、作者、修改時間等等。
於是各個靜態站點生成器定製了自己的 markdown,添加了一些額外的信息,如 hexo 採用 :year-:month-:day-:title.md
的形式來管理文章的日期和標題等。這樣一來說,就不需要通過讀取這個文章的 Git 信息來構建出整個信息。
我們所熟悉的 GitHub Flavored Markdown 也是如此,通過不明顯破壞內容格式的兼容模式來擴展 markdown 數據字段。
除此,我們可以定製基於 markdown 數據的圖表、思維導圖等內容。
引子 4:編輯 - 發佈 - 開發分離:面向非技術人員
面向非技術人員設計是代碼文檔化的一大挑戰。作爲一個程序員,我們覺得 markdown 語法再簡單不過了,但是對於非技術人員來說並非如此。他 / 她需要:一個易於上手的可視化編程器。而要實現這樣一個目的,我們需要在架構上做一些轉變,我們可以嘗試使用 『編輯 - 發佈 - 開發分離』 模式來解決這個問題。
即,我們將過程拆爲了三步:
- 編輯人員,可以使用常用的編輯器或者是定製的編輯器
- 開發人員,編寫內容的展示
- 發佈的時候,集成這兩部分代碼
我們依舊可以選擇用源碼管理的方式來管理內容。只需要將數據庫接口,轉變爲 Git 服務器接口即可 —— 當然它們是稍有不同的。不過呢,把本地的 Git 轉換爲 Git remote 那就基本一致了。
如此一來,最後我們的成本就落在改造出一個基於 Git 的 markdown 編輯器。
文檔代碼化
完美,我又一次在引子裏,把中心思想表達完了。
爲什麼你需要將文檔代碼化?
主要原因有:文檔不代碼化,就沒有重構的可能性。
剩下的原因有:
- 二進制的文檔難以進行版本管理。想象一下
2020-02-30.docx
和2020-02-31.docx
。 - 無法準確地知道誰是文檔的修改者,大家可能都是 admin,又或者是會議上的張三
- 找不到哪個是最新的文檔
- 文檔寫得很爛,但是你沒辦法重構二進制文檔
- 供應商綁定
- ……
應該還有更多。
什麼是文檔代碼化?
回到正題上:
文檔代碼化,將文檔以類代碼的領域特定語言的方式編寫,並借鑑軟件開發的方式(如源碼管理、部署)進行管理。它可以藉助於特定的工具進行編輯、預覽、查看,又或者是通過專屬的系統部署到服務器上。
它具備這麼一些特徵:
- 使用標記語言編寫內容。如 markdown
- 可通過版本控制系統進行版本控制。如 git
- 與編程一致的編程體驗(除了內容寫不了測試)
而一個高效的文檔代碼化系統,還具備這麼一些特徵:
- 持續部署,即修改完內容可自動發佈。
- 與特定的形式組織內容索引。如以知識庫的形式來組織內容。
- 特定的文本格式。如架構決策記錄、靜態內容生成,以用於以提供更好的用戶體驗
- 可支持 REST API。以通過編輯器來修改內容
- 可以支持多種方式的輸出。如網站標準 HTML,又或者是 Docx、Latex 等
- 支持編輯、校對工作流
- 支持搜索
- 多人協作
而事實上,大部分的團隊並不需要上述的高級功能,而且它們都已經有了成熟的方案。
如何設計一個文檔代碼化系統?
事實上,我們在四個引子中標明瞭我們所需要的要素:
- 使用格式化的文檔
- 藉助靜態站點生成技術來發布系統
- 通過定製標記語言擴充能力
- 面向非技術人員實現編輯器
設計一個標記語言及其擴展語法,然後實現它即可。
1. 確立關鍵因素
考慮到我和我的同事們最近實現了這麼一個系統,我還是忍受一下手的痛楚,簡單說一下怎麼做這樣一個系統。我們所考慮的主要因素是:
- 圖表渲染
- 流程圖渲染
- 可視化展示
因爲由 DSL 轉換成的圖表易於修改,並且可以索引。於是乎,我們:
- 通過 markdown 的 Code 語法來擴充這些能力
- 使用 markdown 的 table 和 list 來提供數據
- 使用 D3.js 來支持流程圖繪製
- 使用 Echarts 來進行圖表繪製
- 儘量使用 SVG 圖片
- ……
2. 實現一個 MVP
我們使用 Angular + GitHub,快速實現了一個 MVP:
- 我們使用 Git 作爲數據庫. 它就可以實現多人協作的目的,並且可以追蹤所有的變化
- 我們使用 GitHub Pages 作爲服務器。只要一修改文檔或者代碼,就會部署最新的文檔。
- 我們使用 marked.js,它可以讓我們快速擴展語法。
- 使用 textarea 結合 markdown 製作了一個簡易的編輯器。
隨後,我們在這個基礎上進行了快速的迭代。
3. 擴展語法
我們使用了 markdown 的 code
作爲圖表的 DSL,擴展了這麼一些語法:
-
echarts。直接渲染 Echarts 圖表
-
mindmap。Markdown List 轉爲思維導圖
-
radar。Markdown List 轉爲雷達圖
-
process-table。帶流程的圖表
-
process-step。另外一種帶流程的圖表
-
pyramid。金字塔圖形
-
quadrant。四象限圖
-
class。直接調用 CSS 的 class
-
graphviz。使用 Dot 渲染圖片
-
mermaid。使用 mermaid 可視化
-
webcomponents。調用 WebComponents 組件
-
toolset。調用 Toolset 相關的組件
- slider。權衡滑塊
- line-chart。表圖
所以,對於使用者來說,只需要編寫下面的代碼:
複製代碼
<pre class="prettyprint linenums prettyprinted"data-anchor-id="ki5w"style=""> 1. `````radar`` 2. `- 質量成熟度評估模型` 3. ` - 質量內建:3->4` 4. ` - 優化業務價值:2->2` 5. ` - 質量統一,可視化:1->5` 6. ` - 全員參與:3->4` 7. ` - 快速交付:4->5` 8. ` - 測試作爲資產:2->3` 9. ` - 快速反饋:5->5` 10. `` 11. `config: {"legend": [" 當前 "," 未來 "]}` 12. `` ````` </pre>
就可以生成對應的圖表:
我們還通過 config 來輸入 JSON,進行一定的懶惰化處理(不要累死自己)。
3.1 重寫 markdown 渲染器
我們在這個過程中,遇到的最大的挑戰是,隨着我們對 markdown 語法的不斷擴充,相關的代碼已經變成了一坨大泥球。所以,我們不得不重寫了這部分代碼:
- 藉助於 marked.js 的 lexer 解析出 token
- 根據 token 修改生成新的 token
- 遍歷新生成的 token,渲染出元素
- 結合虛擬滾動,解決性能問題
已經開源在 GitHub,併發布對應的 npm 包: @ledge-framework/render
。
4. 發佈這個項目
我們已經在 GitHub 上發佈了這個文檔化系統,你可以參與到其中的使用和開發。
總結
然後,你就成爲了一個 Markdown 工程師,D3.js 設計師,Echart 配置管理員。