程式碼腐化似乎註定的
如此迴圈往復。然而腐化了之後,是無法起死回生的。
雖然很多人醉心於遺留程式碼改造之道。筆者也從事鏟屎業務很多年,仍未掌握此項技術。 還是讓程式碼一直保持在未腐化的狀態更簡單一些。那麽程式碼如何防腐呢?不靠 Code Review 又靠什麽呢?
透過資訊隱藏,我們把代分碼為「可以隨便寫的」和「不可以隨便寫的」兩部份。從而控制程式碼腐化的蔓延。
class 的資訊隱藏
我們都知道 class 有一個叫封裝的概念。把 Field 和 Method 分為 Public / Private / Protected 三種。透過只暴露 Public 成員,我們就把 Private 和 Protected 成員給隱藏起來了。
如果有如下的程式碼
public
double
Area
(
object
[]
shapes
)
{
double
area
=
0
;
foreach
(
var
shape
in
shapes
)
{
if
(
shape
is
Rectangle
)
{
Rectangle
rectangle
=
(
Rectangle
)
shape
;
area
+=
rectangle
.
Width
*
rectangle
.
Height
;
}
else
{
Circle
circle
=
(
Circle
)
shape
;
area
+=
circle
.
Radius
*
circle
.
Radius
*
Math
.
PI
;
}
}
return
area
;
}
根據「開閉原則」(Open Closed Principle),好的程式碼應該盡量 Open for extension,Close for modification。也就是說,如果
if
(
shape
is
Rectangle
)
{
// ...
}
else
if
(
shape
is
Circle
)
{
// ...
}
else
if
// ...
}
這樣一直修改這個 if/else 是不好的程式碼。好的做法是把 Rectangle 的 Width/Height 隱藏起來,把 Circle 的 Radius 也隱藏起來。
public
double
Area
(
Shape
[]
shapes
)
{
double
area
=
0
;
foreach
(
var
shape
in
shapes
)
{
area
+=
shape
.
Area
();
}
return
area
;
}
從依賴關系上來看,就是如下圖所示
這樣我們就把代分碼為上下兩部份。對於 Rectangle,Circle 以及 AreaCalculator 來說,彼此都不知道對方的存在。 也就是大家都依賴 Shape,但是彼此沒有依賴關系。
Git 倉庫的資訊隱藏
class 有封裝和依賴關系。Git 倉庫也有封裝和依賴關系。
在上圖中,B和C是互相隱藏的。B的實作細節對C隱藏了,C的實作細節對B也隱藏了。 我們都使用過 visual studio code,其外掛程式化架構就類似上面的依賴關系。透過新增外掛程式來實作功能的擴充套件。
我們可以把這種做法更一般的描述為「主機板+外掛程式」。
容易寫出幺蛾子的程式碼都是集中在一個(主機板)Git倉柯瑞的。 在做 Code Review 的時候,只需要重點觀照倒置到底層的整合Git倉庫是否合理。 所謂合理,就是能不改就不改。除非不開槽,不開擴充套件點,需求在外掛程式中無法實作了。
「主機板+外掛程式」不僅僅可以寫 Visual Studio Code 這樣的 IDE,對於各種型別的業務系統都是同樣適用的。 只是 VsCode 可能一個外掛程式點上可以有多個外掛程式,而業務系統上一般不會有那麽多彼此可替換的外掛程式,更多是一個蘿蔔一個坑的搞法。 主機板部份一定要盡可能的小,要不然就會變成所有的需求都要堆到主機板裏去實作了。
這種寫法和上面的「 class資訊隱藏」是不是一回事?從倒置的角度是一回事。但是區別是基於 class 的依賴倒置缺乏編譯器的保障,無法確保外掛程式之間不互相參照。
這兩個禁止才是關鍵。
如何實踐,這玩意真能寫業務?
理論上看起來很美好,然而有兩個問題
最後我們來看一個亞馬遜商城的綜合案例。
不倒置可不可以?
是不是不依賴倒置就不能寫外掛程式呢?比如,這個樣子是不是也是一樣的
這樣不一樣可以把程式碼都寫在外掛程式裏嗎?在最上層加一個「業務編排」,和所謂的「主機板」不是同樣的概念嗎? 這樣的問題是外掛程式與外掛程式之間沒有互相的依賴關系怎麽能實作業務需求呢?比如在成交了之後分傭,分傭外掛程式怎麽知道啥叫成交呢?這種寫法的大機率後果是更頻繁地需要改動「業務編排」Git倉庫,從而使之成為瓶頸。然後又逐步發展成下面這個模式:
如果不搞出一個主機板,允許彼此外掛程式之間互相呼叫那會更加混亂。相比上面有一個「業務編排」,下邊有一個「主機板」。那似乎把「業務編排」去掉更簡單一些。
那我們只要一個主機板嗎?並不是這樣的,主機板的需求來自於 UI 界面的耦合,以及混合流程的耦合。 如果兩個業務需求都有完全獨立的界面,流程上可以拆分為事件驅動的,那完全可以寫兩個主機板,彼此獨立的發展。 比如說我們有一個主機板承載了商城訂單的界面和流程,來了一個淘寶商品搬家的需求。 這個淘寶商品搬家有獨立的UI,只需要商城開個寫入商品的介面就可以完成需求。 類似這樣的需求就沒有必要寫成外掛程式,插到商城主機板上,而是獨立有一個「商品搬家」的主機板。
為何資訊隱藏可以程式碼防腐?
David Parnas 在 1971 年就寫了 On the Criteria To Be Used in Decomposing Systems into Modules,以上資訊隱藏的做法基本上已經得到程式設計師的認可。但是我們看下圖
50年來,大量的新人不斷地湧入這個行業。如果指望一個計畫上所有的人都能遵守很高的標準,能夠獨立對「好」和「壞」的設計做正確的判斷,這是不現實的。並不是說軟體工程教育不重要,教育仍然是要做的。但是僅僅靠教育來提高軟體工程的品質是不現實的。
如上圖所示的「資訊隱藏」的做法,其實質是為了「程式碼防腐」。在這樣的依賴關系下,外掛程式的 Git 倉庫是不會造成全域的影響的。這樣我們就可以放心的把新人分配去寫一個獨立的外掛程式,而不用擔心其設計選擇造成大面積的程式碼腐化。
【業務邏輯拆分模式】
全文請存取 https:// autonomy.design