[譯] 真相就在代碼中

真相就在代碼中

購物應用模型,requirementsascode 的示例代碼前端

遲早有一天,每一個程序員都會聽到這樣一句話:java

「真相只能在一處找到:代碼中。」react

—— Robert C. Martin,代碼整潔之道android

但這句話是什麼意思呢?ios

敏捷宣言中指出「可工做的軟件賽過繁瑣的文檔」。git

即使開發人員一直都在撰寫繁瑣的文檔以描述軟件的行爲。代碼也在作着相同的事。程序員

代碼註釋、外部規範也在記錄軟件的行爲,可是當代碼被修改時它們可能不會被同步更新。而後它們很快就再也不能表達代碼的行爲了。github

相反,代碼始終都能表達軟件的行爲。由於正是它定義了這些行爲。數據庫

這就是爲何說真相存在於代碼中。編程

爲閱讀你代碼的人考慮

代碼是一種文檔。任何文檔都應該可以被它的讀者理解。

代碼的讀者多是一個編譯器,解釋器或是其餘開發人員。

因此你的代碼僅能編譯經過是不夠的。你還要保證其餘開發人員可以讀懂它。將來他們須要在你代碼的基礎上工做,修改它,擴展它。

一個關於使代碼容易閱讀的常見建議是編寫整潔的代碼。整潔的代碼指的是使用易懂的語言命名變量和方法的代碼。這也使得不少代碼註釋變得沒必要要了。

整潔的代碼應該能表達意圖:使用者經過調用該方法能作什麼。而不是怎麼作

猜想一下這個方法是作什麼的:

BigDecimal addUp(List<BigDecimal> ns){..}複製代碼

若是是這麼寫呢:

BigDecimal calculateTotal(List<BigDecimal> individualPrice){..}複製代碼

整潔的代碼是一個好主意。但我認爲這還不夠。

知識共享的重要性

當有一個新需求時,你須要評估實現該需求對現有代碼的影響。

若是你的軟件已經存在了一段時間,這可能會是個挑戰。我常常聽到這樣的對話:

X:咱們不能繼續開發 foo 特性了。

Y:爲何?

X:由於 Z 是惟一瞭解這塊代碼的人。咱們要改動的代碼就是他開發的。

Y:好吧,爲何不去問問他呢?

X:由於他病了/休假了/在開會/離職了。

Y:額……

事情就是這樣。要想知道你的代碼是否容易被理解,至少要有我的去嘗試閱讀它。

有這方面的技術。結對編程就是一個不錯的選擇。或者是和其餘開發人員坐下來,一塊兒過一遍你寫的代碼。

然而,若是參與一個項目的開發人員太多呢?若是一個研發團隊的成員變化了呢?這使得編寫容易被其餘人理解的代碼變得更困難了。

故事

整潔代碼帶給你正確的用語

問題是:你在代碼中使用它們講述怎樣的故事呢?

我不知道。

可是對於一個典型的業務應用,我很清楚我但願在代碼中讀到怎樣的故事。

在介紹一個簡單的例子以後,我會簡要描述那個故事。

手套商店的例子

做爲一個軟件的用戶,我想要達到指望的目的。好比,我想擁有一雙新手套在冬天能夠給個人手保暖。

所以我上網找到了一家新開的線上手套專賣店。該店鋪的網站可讓我購買手套。「基本流程」(也被稱爲「正經常使用例」)大概是這樣的:

  • 系統以一個空購物車開始。
  • 系統展現一個手套的列表。
  • 我添加喜歡的手套到購物車。系統將這些手套加入到個人訂單中。
  • 我選擇結帳。
  • 我輸入配送信息和支付詳情。系統保存這些信息。
  • 系統展現一份訂單的詳細信息。
  • 我確認信息。系統開始配送個人訂單。

幾天之後,我收到手套。

下面是我想在代碼中讀到的故事

第一章: 用例

故事的第一章是關於用例的。當我閱讀代碼時,我但願在代碼中按照某個用例一步一步的達到指望的結果。

從一個用戶的角度來看,我想弄明白當出現錯誤時系統是如何應對的。

我還想搞清楚流程中可能的分支。例如,用戶企圖從支付詳情頁回到配送信息頁時會發生什麼?用戶能夠這樣操做嗎?

我想知道每個用例中的不一樣部分對應哪塊代碼。

那麼一個用例由哪些零件構成呢?

用例的基礎零件是使用戶離指望結果更近一步的步驟。好比:「系統展現一個手套的列表。」

不是全部用戶均可以執行某一步驟,只有特定用戶組的成員(「行爲者」)才能夠這麼作。例如,終端消費者買手套。銷售人員向系統中添加新款手套的報價。

某些步驟由系統主動執行。例如在展現手套列表時。無需用戶交互。

而有的步驟則是用戶交互的結果。系統響應某些用戶事件。例如:用戶輸入配送信息。系統保存這些信息。

我想知道這些事件中包含哪些數據。配送信息包含用戶的姓名,地址等等。

用戶在任何給定的時間點上只能執行一部分步驟。用戶只有在輸入配送信息後才能夠填寫支付詳情。所以每個用例中都有一個定義了該用例中全部步驟執行順序的流程。以及一個根據系統當前狀態,表示系統是否能夠響應用戶的操做的條件

要理解代碼,你須要一個簡單方法來了解幾件事情。

對於一個用例(例如「買手套」):

  • 步驟流程

對於每一個步驟:

  • 哪些行爲者有執行的權限(也就是哪一個用戶組)
  • 在哪些條件下,系統能夠響應
  • 該步驟是自發的仍是基於用戶交互
  • 系統響應

對於每一個基於用戶交互的步驟:

  • 用戶事件(例如「用戶輸入配送信息」)
  • 伴隨事件而來的數據

一旦我知道在哪裏能夠找到用例以及它的零件以後,我就能夠深刻研究了。

第二章: 經過組件分解步驟

讓咱們把你軟件中一個封裝的,可替換的組成單位稱爲一個組件。一個組件的職責能夠被該組件以外的世界訪問。

一個組件多是:

  • 一個技術組件,好比數據庫
  • 一個服務,好比「購物車服務」
  • 你的領域模型中的一個實體

這取決於你的軟件設計。但不論你的組件是什麼:你一般都須要若干個組件配合來實現用例中的某個步驟。

讓咱們來看看「系統展現一個手套的列表」這一步驟中的系統響應。你極可能須要開發至少兩項職責。一個用來在數據庫中查找手套,另外一個用來把這些數據轉變爲一個頁面。

當閱讀代碼時,我但願能瞭解如下這些內容:

  • 一個組件的職責是什麼。例如:對數據庫來講是「查找手套」。
  • 每一個職責的輸入/輸出是什麼。輸入的例子:查找手套的規則。輸出的例子:手套列表。
  • 誰來協調這些職責。例如:首先查找手套,而後將結果轉換成一個網頁。

第三章:組件作什麼

組件的代碼用來實現它的職責。

這一般出如今領域模型中。領域模型使用和業務領域相關的術語。

舉例來講,手套能夠是一個術語。訂單也能夠是一個術語。

領域模型用於描述每一個術語的數據。每一個手套都有顏色,品牌,尺碼,價格等數據。

領域模型還用來描述基於這些數據的運算。一個訂單的總價是該訂單中用戶購買的全部手套價格的總和。

一個組件還能夠是相似數據庫這樣的技術組件。該組件的代碼就要解決如何在數據庫中建立、查找、更新、刪除數據的問題。

講述你的故事

你的故事可能看起來和上面提到的故事很類似,也可能徹底不一樣。不論你的故事是怎樣的,編程語言都給了你極大的自由來說述你的故事。

這是件好事情,由於它容許開發人員適應不一樣的情景與需求。

這也承擔了由開發人員講述太多不一樣故事(哪怕是針對同一個產品)帶來的風險,使得理解其餘人編寫的代碼變得更困難了。

解決這個問題的一個辦法是使用設計模式。它們能夠幫你合理的組織代碼。你能夠在團隊中甚至是團隊間就這種通用結構達成一致。

例如:Rails 框架就是基於衆所周知的模型、視圖、控制器模式的。

模型用於放置領域數據

視圖是客戶端用戶界面,好比 HTML 頁面。這是用戶事件的來源。

控制器在服務器端接收用戶事件。它負責流程

所以,若是多個開發人員使用 Rails,他們就知道在哪裏能找到和故事中特定部分相關的代碼。

他們能夠在分享他們的看法時找出缺失的東西。而後,他們就能夠進一步的就約定在哪裏放置故事的模塊達成一致。

若是這些適用於你,那就行了。但我想比這更進一步。

代碼即需求

不少客戶問我如何處理長期的軟件文檔。

在敏捷開發中,如何建立軟件維護文檔?

到目前爲止都實現了哪些需求?

在哪裏能找到它們在代碼中的實現?

好久以來我都沒有滿意的答案。固然,除了良好編寫的自動化測試,整潔的線上代碼,以及共同認知的重要性以外。

可是在幾年前,我開始思考:

若是真相在代碼中,那麼代碼也應該能講述真相。

換句話說:若是你很是當心的在代碼中講述你的故事,爲什麼還要再說一遍呢?

須要有更好的方法。它能夠提取故事,並基於它生成文檔。非技術相關方也能理解的文檔。

始終保持最新狀態的文檔,由於正是它的來源定義了軟件行爲。

惟一可靠的來源:代碼自身。

在許屢次嘗試以後,我有了一些成果。我把它們發佈在一個名爲 requirementsascode 的 GitHub 項目中。

它是如何工做的

  • UseCaseModel 實例用於定義行爲者用例,它們的流程以及步驟。它講述故事的第一章。在本文的開頭你能找到一個這種模型的例子。
  • 用例模型配置 UseCaseModelRunner 實例。每一個用戶都有本身的運行程序,由於每一個用戶選擇執行的用例路徑可能不一樣。
  • 運行程序經過調用後端的系統響應來響應前端的用戶事件。前端只能經過運行程序和後端通訊。
  • 可是運行程序只有當用戶在流程中的正確位置且知足步驟的條件時纔會響應用戶。例如:運行程序只有在用戶已經輸入了配送信息以後纔會響應「進入支付詳情頁」事件。
  • system reaction 是一個單例方法。方法內部負責協調不一樣組件以實現該步驟,就像在第二章中描述的那樣。
  • 第三章已經超出了 requirementsascode 的範疇。它留給應用程序決定。這使得 requirementsascode 能夠兼容任意的軟件設計。

所以,基於 UseCaseModel,UseCaseModelRunner 控制了對用戶可見的軟件行爲。

經過 requirementsascodeextract, 你能夠從同一個配置運行程序的用例模型中生成文檔。這樣,文檔就能夠始終表達軟件的行爲了。

Requirementsascodeextract 使用了 FreeMarker 模板引擎。它容許你生成任何你喜歡的純文本文檔。例如 HTML 頁面。進一步的處理能夠生成其它格式的文檔,例如 PDF。

你的反饋能夠幫我改進這個項目

我從幾年前就開始了 requirementsascode 這個項目,直到最近纔將它公開。與最初相比,它已經獲得了極大的改善。

爲了瞭解這種方法是否具備可擴展性,我嘗試在一個有數千行代碼的項目中使用。被證實是有效的,我也在一些更加小型的應用中嘗試過。

到目前爲止,requirementsascode 一直都是個人業餘項目。

這就是爲何我須要大家的幫助。請給我一些反饋。

你以爲這個想法怎麼樣?你能想象它在你的軟件上下文中有效嗎?還有其餘的反饋意見嗎?

你能夠在評論區給我留言或是在 TwitterLinkedIn 和我聯繫。

你能夠 clone 這個項目並親自嘗試一下。

也能夠幫助在代碼中記錄真相。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃

相關文章
相關標籤/搜索