---
# System prepended metadata

title: 使用 EBNF 來撰寫通訊協定的人讀、機讀共用文件
tags: [持續開發, 系統設計]

---

# 使用 EBNF 來撰寫通訊協定的人讀、機讀共用文件

* Git repo：https://github.com/linahbei/continues-protocol-development
* 共筆版本：https://hackmd.io/JreKVtPZR-uD78Pe1tY2Tg?view
 
 ###### tags `持續開發`, `系統設計`
 
---

## 1. 概觀

### 什麼是 BNF / EBNF

BNF 是一種用來「描述語法」的表達式，可以理解為**描述程式語言怎麼定義的語言**。身為工程師一定對 BNF 的用法不陌生，只是熟悉度可能不同。例如命令列指令、說明文件中，表示指令、參數**可選 (option)** 的中括號 “``[ … ]``”，或是**必要**的大括號 “``< … >``”。

### 問題描述

設計、實作通訊協定的時候，花費最多人力重複工作 (routines)、重工 (re-work) 的任務，就是人讀文件，和機讀 (可程式化) 文件的同步、一致性維護。尤其是身為軟體開發團隊，這種*非***持續開發 (continues)**，需求、文件、實作不能反應真實狀況的方式，不僅僅是勞心勞力，也不太聰明。

特別是開發非標準、工具尚未完善的通訊協定時，這種不一致，追著**歷史文件**考古的現象就會更嚴重。舉例來說，如果是開發 RESTful API，我們還有現成的 [OpenAPI](https://swagger.io/specification/) 可以救場。但是開發自訂的通訊協定時，各種缺少工程意義的討論方式、文件產出，就會讓規格變得難以閱讀 (不論是過於精簡或是囉嗦)。

### 解決方案

我們選擇使用 EBNF，來撰寫通訊協定的人讀、機讀共用文件；更重要的是，開始練習像個工程師一樣的工作。


## 2. 準備工作

### 風險評估與專案資源盤點

雖然說建置一個持續開發、持續文件的模式是這麼的美好，但是身為合格的從業工程師，別忘了首要工作是交付可用的軟體。至於什麼是可用的軟體，就要考量你的通訊協定究竟有多大、後續會怎麼變動、是誰來用，還有專案可用的資源。

即使是開發標準的通訊協定專案，使用現成熟悉的工具，例如 OpenAPI、Jupyter Notebook，重點仍然是交出可用的東西。

### 背景知識與工具

#### 正規表示式 (Regular Expression)

如果你已經閱讀過 BNF / EBNF 的資料了，請再了解[正規表示式](https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F)。後續在撰寫 EBNF 中會用到。

#### 參考現有的撰寫方式

我們在設計通訊協定的時候，可能會遇到不同類型的格式，例如編碼格式 -- binary、plain text，或是欄位格式 -- fixed、標記語言等等。因此可以參考現有、類似規格的 EBNF 撰寫方法。

|編碼格式|欄位格式|參考範例|
|:------|:------|:-----|
|Plain text|標記語言|[JSON](https://github.com/cierelabs/yaml_spirit/blob/master/doc/specs/json-ebnf.txt)|
|Binary|Fixed|Modbus|
|Plain text|Fixed|Modbus RTU|

#### 工具程式

##### 詞法分析、語法分析

**機讀**工具，用來產生分析 BNF 的程式碼

##### 語意分析 (？)

##### 文件產生器

**人讀**工具，用來產生 user manual。雖然說 BNF 已經是**可以人讀**的格式了，不過我們還是需要一份合格、可用的 user (development) manual。

* [EBNF Visualizer](https://people.cs.vt.edu/~kafura/ComputationalThinking/Class-Notes/Manual/manual.html)


## 3. 實作練習

### 設計一個簡單的 plain text / fixed protocol

首先，我們先用一般的開發方法 -- 也就是用程式實作 + 文件撰寫的思維，規劃一個簡單的**遠端開關控制** protocol。這是個一問一答、一對一的 protocol，用途是通知遠端的某個控制器，開啟或是關閉開關 (relay)，並且回復成功與否。(要設計一個簡單又有明顯錯誤的範例真不容易)

如果這個 protocol 是由一個不愛寫文件的工程師來規劃，第一版的**文件**、規格書可能長這樣：

```c=
struct packet_s {
    char header;     //'C' = commanding, 'R' = responding
    short int sn;    //Serial number / tag, 0 ~ 32767
    short int relay; //Relay ID, 0 ~ 32767
    short int flag;  //Commanding: 0 = turn OFF, 1 = turn ON
                     //Responding: 2 = OK, 3 = Fail
};
```

### 開始寫規格書，同步需求

從以上的規格，我們可以發現一個溝通上的問題，既然這是個 plain text 的 protocol，那麼整數型別的 ``sn``, ``relay``，和 ``flag`` 是怎麼回事？實作的時候會不會產生什麼誤會？不過既然 PoC 程式也寫好了，只能從規格書修正、補完這段設計。

首先，先用正規表示式，把 protocol 格式明確的定義下來；這裡的明確定義，指的是讓 reviewer 的大腦冷靜下來，好好思考怎麼定義出合法、可以使用的 protocol。

```
^(C|R)[0-9]{5}[0-9]{5}[0-3]{1}$
```

:::info
 一個合法的 protocol、指令會是這樣：``R00201001001``
:::

然後再依照 plain text 的技術限制、建置需求，把規格稍微整理清楚：

```
Protocl 格式：<標頭><Tag><Relay ID><動作 Flag>

* 標頭：'C' = 控制指令，'R' = 執行結果
* Tag：流水號 tag，user defined
* Relay ID: 要控制的 Relay ID
* 動作 Flag
  - 控制用：'0' = ON，'1' = OFF
  - 執行結果：'2' = 成功，'3' = 失敗
* 總長度：22 個字元
* Tag and ID range：'00000' ~ '32767' (5 digits ASCII decimal char with leading zeros)
```

如果再講究一點，後面還有文書排版工作、範例程式說明要寫。


### 用 EBNF 描述這段 protocol

看完以上各版本阿哩拉雜的規格，還有共通文件、溝通上的問題後 (例如：相依於特定程式語言，繁瑣的文字描述，精簡但是不知道在幹嘛的正規表示式)，我們可以將 protocol  改寫為語言無關的 EBNF。

:::info
* 測試資料：``R00201001001``
* [線上工具](https://mdkrajnak.github.io/ebnftest/)
:::

```=
Packet ::= (Header)(Tag)(Relay)(Flag)

(* Fields *)
Header ::= #'^'((Command)|(Response))
Tag    ::= #'[0-9]{5}'
Relay  ::= #'[0-9]{5}'
Flag   ::= ((SendOff)|(SendOn)|(ResultOk)|(ResultFail))#'$'

(* Headers *)
Command    ::= 'C'
Response   ::= 'R'

(* Flags *)
SendOff    ::= '0'
SendOn     ::= '1'
ResultOk   ::= '2'
ResultFail ::= '3'
```

這樣改寫完後，雖然沒有很漂亮，而且 protocol 本身也是虛構、無法用於實務問題上。但是也初步完成了一個可以人讀、機讀，用於描述通訊協定的文件了。


### 改寫為 ``Lex`` 看得懂的格式

:::info
* [說明](https://zh.wikipedia.org/wiki/Lex)
* [WIndows 10 安裝](http://gnuwin32.sourceforge.net/packages/flex.htm)
:::

:::warning
如果我們只是要對 protocol 的封包做詞法分析，或者說 protocol 並沒有複雜到像是一個腳本語言，那麼改寫、撰寫 ``Lex`` 規則其實成本有點高。因此下一步是，尋找 EBNF 的詞法分析程式產生器。
:::

*待續...*
