- 原文地址:The truth is in the code
- 原文做者:Bertil Muth
- 譯文出自:掘金翻譯計劃
- 譯者:loveky
- 校對者:sunui yzgyyang
購物應用模型,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 控制了對用戶可見的軟件行爲。
經過 requirementsascodeextract, 你能夠從同一個配置運行程序的用例模型中生成文檔。這樣,文檔就能夠始終表達軟件的行爲了。
Requirementsascodeextract 使用了 FreeMarker 模板引擎。它容許你生成任何你喜歡的純文本文檔。例如 HTML 頁面。進一步的處理能夠生成其它格式的文檔,例如 PDF。
我從幾年前就開始了 requirementsascode 這個項目,直到最近纔將它公開。與最初相比,它已經獲得了極大的改善。
爲了瞭解這種方法是否具備可擴展性,我嘗試在一個有數千行代碼的項目中使用。被證實是有效的,我也在一些更加小型的應用中嘗試過。
到目前爲止,requirementsascode 一直都是個人業餘項目。
這就是爲何我須要大家的幫助。請給我一些反饋。
你以爲這個想法怎麼樣?你能想象它在你的軟件上下文中有效嗎?還有其餘的反饋意見嗎?
你能夠在評論區給我留言或是在 Twitter 或 LinkedIn 和我聯繫。
你能夠 clone 這個項目並親自嘗試一下。
也能夠幫助在代碼中記錄真相。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。