細節和架構同等重要

發表於2016-06-29

你能在網上找到「軟體架構」的定義,比我要在本文列出的還要多。但是,我希望你能認同我的觀點,「軟體架構」是系統的較高層次結構,而「軟體設計」關乎細節,屬於較低層次。

我對此思考越多,就越發意識到:如果你不處理好細節,你也就無法擁有優秀的較高層次結構。如果你沒有良好的軟體設計,你的軟體架構也不會優秀。

有哪些細節呢?

我要討論微小的設計方面的決定,這也是我們每天、甚至每時每刻都在應對的。

我該怎樣命名某個變數、函式或類?我應該拆開某個函式或類嗎?我能夠保持某個函式是純函式、或它讓這裡涉及到的某些狀態更加清晰了嗎?其它模組通過什麼方式訪問或使用該模組?我這裡漏掉抽象了?開發人員不得不明白這塊程式碼涉及到的概念,那麼其它地方還有同樣的概念嗎?為了便於測試,我該怎樣設計該模組的介面;接下來的測試要做什麼?這次測試滿足輕量、專注、獨立和清晰的標準嗎?

在寫程式碼時,我們一直都在設計。當你程式設計時,你不可能沒有設計。你不能不回答上面的問題。但是,如果不深入思考,你的答案就算不上最佳的解決方案——或許連良好都算不上。

謹慎

對於保持設計的整潔和良好,如果有團隊成員不給予足夠重視,那麼他們也不會足夠重視架構所要保持的良好狀態。

程式碼不會真地「腐爛」。只要我們不碰它,它就一直是原來的樣子。我們一旦改動了某處,它好像就變質了。那是因為每個粗心的設計決定造成的,你將因此帶來破壞。有時候,危害不大;有時候,卻是災難性的。

保持程式碼整潔,往往需要大量努力和小心。但是,你不得不這樣做,否則,隨著時間的流逝,你的進度會變得越來越慢。團隊每個成員都要參與,每個人都要謹慎。

良好的命名、和其它設計原則

如果你不能為某些「小東東」找到合適的名字,那麼當你不得不修改程式碼時,你就無從下手。架構文件寫得再好,也無濟於事。

如果你不堅持依賴反轉原則注1,你的技術細節將和業務程式碼交織在一起。整潔、獨立的分層架構也保護不了你。

如果你的類、模組和方法具備多重功能和直接依賴,修改就會波及整個系統。你在架構圖裡看不到這一點,即使它有著明確的元件。

還有其它設計原則,主要影響著較低層次的設計,並且仍然能夠破壞你的架構。我本來想談談,但是,你可能會說,「如果它能破壞我的架構,那麼它一定屬於架構方面的決定」。但是,我認為,這是一種滑坡謬誤注2。因為,按照你的說法,每一種設計決定都將成為架構決定

測試和文件

良好的設計,關係到良好的測試。它是介於測試和生產程式碼之間的、經過良好設計的介面。

測試,尤其是「微測試(micro test)」,可以被軟體設計作為優秀的技術文件。但前提是,你編寫了良好、獨立的測試,且有著良好的命名。你需要給生產環境的程式碼、以及程式設計師,設計測試的介面,而程式設計師不得不解釋異常的測試結果!

但是,如果你處置得當,測試就能勝過你所寫過的任何文件。它們還不會過期,它們要麼相關,要麼沒有使用價值。警告:即使你的測試達到了文件的良好水平,你也需要其它形式的技術文件!

你能把測試用作架構文件嗎?你使用自動化測試,或許能夠為架構的某些地方寫文件。但是,良好的架構文件,除了記錄了系統的較高層次的結構,還要記錄你做出的所有決定、及其原因。

因此,為了真正地為架構寫文件,你仍然需要額外的文件。Arc42 網站的模板算是好的開始。但要適度:保持文件簡短,只增加絕對必需的東東!

當你找不到設計細節方面的文件(生產環境模組/函式,及其微測試)時,就會再一次破壞架構:你的架構文件會告訴你,在哪一個大致區域查詢某個東東。但是,當你找到那片區域時,你需要整潔的程式碼、文件和測試,把你定位到精確的位置。

闡述重要性

我經歷過很多難以維護的程式碼庫(因為幫助客戶優化難以維護的程式碼庫,屬於我提供的服務範疇)。據我經驗看,帶來大部分痛苦的根源不在於一些架構上的重大問題。誠然,它們往往也會帶來很大的問題。

但是,大部分痛苦源於成千上萬個微小的設計問題。某個方法的命名不合適;呼叫某個合作者(collaborator)的方法,而它應該在別的地方;臃腫的模組和函式;非獨立的測試;命名不當的測試;在測試套件注3裡,對生產環境程式碼做出一點點改動,就能破壞 10 個、20 個、或更多的測試。

有時候你不得不按時完成任務,隨後再修復。但是,當你長期忽視設計時,那些小問題必定減緩你的進度。並且這種減緩在大多數開發人員還沒有預料到時,就提前到來了。當你走了一次「捷徑」之後,它就會在幾星期、甚至幾天、數小時內出現。

用微服務來拯救?

當我們做微服務時,還需要良好的內部設計嗎?我們是為了替代、而非複用才寫它們的,對吧?我們正按計劃定期地去掉一部分吧……

嗯,看情況,主要取決於微服務的規模。就我目前看,關於微服務「正確」的一面,網上還沒有一個結論……

如果你的微服務「夠大」(相對來說)、屬於垂直整合注4,你就應該確保內部設計的整潔。因為每一個「獨立系統」的程式碼都十分龐大,只要一點點混亂,必定減緩將來的開發!

但是,如果微服務真地很小(僅有一少部分,由函式或類構成),那麼你仍然要注意微小的地方。不過,某些小的設計方面的決定,就影響著各種微服務協同執行的方式,而非微服務內部的模組。

良好設計 != 良好架構

為了擁有良好的架構,你需要處理好細節。但是,具有了良好的、較低層次的設計,並不意味著擁有了良好的架構。

你不得不兼顧二者。但是,如果你想開始,就從設計著手:在程式碼的某一小塊、甚至在培訓中,你都能輕易練習。你可以重構越來越多的程式碼,使其具有良好的內部設計。當你熟練之後,再開始考慮更大的架構方面的事情:開始設計你的架構。

開始優化!

如果你願意,現在就行動。測試驅動開發,看是否適合你。瞭解一些良好的設計,比如:SOLID 原則注5、簡單設計的四個法則、耦合和內聚等……

但是,在某個階段,你需要讓整個團隊處於同一節奏。你不得不向他們兜售更好的設計。然而,常常有人破壞你正在做出的設計優化。


作者簡介

我叫 David Tanzer,從 2006 年起,一直做獨立軟體顧問。我通過培訓、指導、以及為團隊和個人提供諮詢,來幫助客戶正確地開發軟體、以及開發合適的軟體。更多詳情,請移步我的網站


註釋

  1. 在物件導向程式設計領域中,依賴反轉原則(Dependency inversion principle,DIP)是指一種特定的解耦(傳統的依賴關係建立在高層次上,而具體的策略設定則應用在低層次的模組上)形式,使得高層次的模組不依賴於低層次的模組的實現細節,依賴關係被顛倒(反轉),從而使得低層次模組依賴於高層次模組的需求抽象。https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99
  2. 滑坡謬誤(Slippery slope)是一種非形式謬誤,使用連串的因果推論,卻誇大了每個環節的因果強度,而得到不合理的結論。滑坡謬誤的典型形式為“如果發生A,接著就會發生B,接著就會發生C,接著就會發生D,……,接著就會發生Z”,而後通常會明示或暗示地推論“Z不應該發生,因此我們不應允許A發生”。https://zh.wikipedia.org/wiki/%E6%BB%91%E5%9D%A1%E8%AC%AC%E8%AA%A4
  3. 軟體工程中的測試套件(test suite)有時也稱為驗證套件(validation suite),是許多測試用例的集合,測試用例可用來測試一程式是否正確工作,測試套件包括許多測試用例,一般也會有針對測試用例及其測試目的的詳細說明,在進行測試時的系統組態資訊以及測試前需進行的步驟。https://zh.wikipedia.org/wiki/%E6%B5%8B%E8%AF%95%E5%A5%97%E4%BB%B6
  4. 垂直整合(Vertical Integration): 一個產品從原料到成品,最後到消費者手中經過許多階段。如果一個公司原本負責某一階段,當公司開始生產過去由其供貨商供應的原料,或當公司開始生產過去由其所生產原料製成的產品時,謂垂直整合。http://wiki.mbalib.com/wiki/%E5%9E%82%E7%9B%B4%E6%95%B4%E5%90%88
  5. 在 程式設計領域, SOLID(單一功能、開閉原則、里氏替換、介面隔離以及依賴反轉)是由羅伯特·C·馬丁在21世紀早期引入的記憶術首字母縮略字,指代了物件導向程式設計和麵向物件設計的五個基本原則。https://zh.wikipedia.org/wiki/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1)

相關文章