1. 程式人生 > >學而不思則罔,思而不學則殆。

學而不思則罔,思而不學則殆。

                                                                                    前言

面向物件程式設計有三個特徵:封裝、繼承、多型,這三個特徵即是語法也是手段,23種設計模板其實就是對這三種手段的靈活應用,今天不談任何高大上的設計模式,僅僅談談程式碼封裝。

                                                                                    引子

從三個特徵的排序上,封裝是第一位的,個人認為也是最重要的,且是后里面兩個的基礎。很長時間以來封裝很容易被忽視,可能是有人覺得這個太簡單了,封裝不就是組織程式碼嗎?不就是構造一個個類,一個個方法嗎?現在是一個基於框架開發的時代,開發只是往框架裡“填充”業務程式碼,似乎不太需要考慮太多東西,這時我覺得“封裝”就很重要了,“封裝”就是開發層面的“微設計

”,今天說主要說下方法/函式(以下簡寫為F)的封裝這一點,以類為單位的設計往往都上升了模組設計的高度。

最近一段時間以來,發現一些不是很令人賞心的程式碼,總覺得跟這個東西有關。

1、認清關係

首先來考慮一個問題,FA呼叫FB,那麼他們是什麼關係?理解他們之前的關係重要嗎?如果你覺得不重要、就是呼叫者與被呼叫者關係(等於沒說),就大錯特錯了。如果你的程式碼一直寫的不好,我覺得很有可能就是這一點沒認識清楚。想要回答這個問題,只要考慮另外一個問題,FA為什麼要呼叫FB?——因為FA不呼叫FB就無法完成自己的工作,FB能幫助自己完成一些事情,也就是說FA依賴FB,他們之間的關係是依賴。這個認識會直接影響到你程式碼的組織。

1、1          向下原則

舉例之前,再考慮另外一個問題,依賴者之間的位置或者層次是什麼樣的?答案是上下的,不是平行的。也就是說FB在FA下層,為FA提供服務,這個可以參照ISO七層模型來理解,也可以參考我們的框架,web層->service層->dao層。《程式碼整潔之道》裡稱之為向下原則。

這些東西容易理解,自己寫程式碼時候確容易考慮不到。一個例子,假設有個方法有300行程式碼,裡面有些東西可以封裝子F來,你會怎麼做?我相信大部分人做的都還可以。但有這麼個做法,他/她把方法拆成了三個方法,F1,F2,F3,F1呼叫F2,F2呼叫F3,每個方法體裡面基本上是之前方法的一段程式碼,比如F1是1~100行程式碼,F2是101~200行,F3是201到300行,三個方法需要的引數幾乎都是一樣的,通過形參依次傳遞下去,而且返回值都是void型別,

/她給方法的名字和註釋也很尷尬意思都很接近

大家覺得這個做法怎麼樣?這個做法僅從形式上看就不行,每個方法都是原來方法身體的一部分從邏輯上他們是平行的,可以認為它還是一個方法,他們之間不是依賴關係。這樣封裝程式碼僅僅是把程式碼挪了個地方,還不如不拆分,閱讀者得自己腦補,把程式碼合併起來才能知道到底想幹什麼。把一個方法比喻成一個人,那麼如果這個方法需要重構,抽離出來的方法仍然必須是個人,是個五臟六武俱全的mini小人,而不是這個人的手或者腳。

1、2          封裝不是簡簡單單的挪動程式碼

上述例子中,F1呼叫F2,F2呼叫F3,或者F1呼叫F2、F3都可能是合理的,關鍵看幾個F封裝的符不符合我們常見的原則,後續我們將均使用上面的例子,因為它很巧的違反了很多原則,可以幫助我們理解這些原則,一般來說有以下幾個原則。

2、單一職責原則

最重要的原則,見名知意。單一職責,強調的是職責的分離,一個方法只幹一件事情,只因為一個原因做修改。很多程式碼之所以需要重構,因為有職責擴散。所謂職責擴散,就是因為某種原因,職責P被分化為粒度更細的職責P1和P2。

從微觀上講,單一職責的方法一目瞭然,職責明確,利於維護。從巨集觀上講是設計的要求,單一職責原則可以看作是低耦合、高內聚在面向物件原則上的引申,將職責定義為引起變化的原因,職責過多,可能引起它變化的原因就越多,從而極大的損傷其內聚性和耦合度。

這個原則非常好理解,最常見的違反這個原則的例子就是寫“大而全”的方法,一個方法搞定各種邏輯,各種flag,各種分支判斷,到最後程式碼看不出主邏輯是什麼了。

如果單一職責原則做的好,程式碼都不會太長,隨便翻開Apache的開源軟體原始碼,大部分的方法都沒超過100行,當然有些核心類、管理類等,長程式碼還是有的。

除了大而全的方法,想想上面的F1~F3的例子,有沒有違反單一原則?與大多數人職責過多相反,它是因沒有職責而違反。這裡有個“詭辯”,只要不是空方法,只要方法裡有程式碼、有邏輯就有職責,這一點我不太同意。這是個職責範圍的認定問題,雖然職責的認定是仁者見仁智者見智的,但是我覺得有些基本的東西是確切的,比如職責必須很明確、完整。 上述F1~F3,每個方法都沒有完整明確的職責,看程式碼的感受就是不知道這個方法想幹什麼,感覺每個方法的存在意義都不大,邏輯合併起開才勉強知道想幹什麼,所以我認為職責模糊的方法是違反單一原則的。還有就是返回值,有返回值並且設定void的,也屬於職責不明確。除非真的沒有返回值可以用void,否都應該返回值。隱式返回容易讓呼叫者遺忘哪些變數被改變了,進而引起程式設計bug。

3、最少知道原則

         這也是個非常重要的原則。直接借用定義:一個物件應該對其他物件保持最少的瞭解。通俗的來講,就是一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類來說,無論邏輯多麼複雜,都儘量地的將邏輯封裝在類的內部,對外除了提供的public方法,不對外洩漏任何資訊。

函式層次上也要遵循次原則,方法的開口儘可能的小,入參之間不要有依賴,一個極端的例子:一個方法有3個入參,但是第1個和第2個能計算出第3個,就是違反最少知道原則,第三個引數應該在方法體裡計算出來。

入參過多很可能會違反最少知道原則,上面的F1,F2,F3的例子,形參個數多達10個。有些很複雜的場景,傳遞的引數如果真的必須很多的話,考慮用有意義的實體封裝,這個封裝其實是對入參“歸類”,雖然需要的引數還是需要一一“拿到”然後放進實體裡,但是歸成一類邏輯上可以看成“一個”。很多公司的規範對形參個數都有要求。

 4、考慮執行緒安全問題

執行緒安全問題平時很難測試到,需要特別注意,時時注意。靜態變數、單例的成員變數都是可能被多個執行緒訪問的、資源的非原子操作(例如資料庫一個值讀取出來後,再update+1)等等,設計類時把考慮執行緒是否安全當成一個習慣

5、好的程式碼是什麼樣的

好多人常說優雅,優雅是程式碼的最高境界。我們不說優雅,一般來說比較優秀的程式碼是什麼樣的?根據之前的解釋,以函式為單位,個人覺得好的程式碼是一顆樹,一顆主幹分支分明、錯落有致的樹。入口函式可以看成樹的主幹,呼叫的函式是分支,樹是逐漸細化的。如果單獨看一個分支的話,它還是一顆樹。試想一下,沒有分支、只有主幹的樹是否美觀?主幹和分支一樣粗的樹是否美觀?一個不平衡的樹是否美觀?

其實編寫程式碼是需要一定的審美的,如果你的程式碼不美觀,很可能你的審美有問題。