2010年6月29日 星期二

寫出好程式的好習慣

在寫程式時,哪些習慣必須建立呢?以下列出我認為一個好的程式設計師應該養成的好習慣。

1 Test Driven Development

第一名的,就是我非常推的 TDD了。TDD不只是先寫測試再寫程式這麼簡單的規定而已,背後還包涵了一些情境。

首先,在寫測試程式時,其實就必須先要了解需求。不知道需求,當然寫不出測試。

其二,寫測試時式時,其實我就會開始想「要怎麼讓客戶端呼叫我的程式」,方法是要建立在 instance上呢?還是實作成類別的靜態方法。此時,我們就必須從由使用者的觀點來看,客戶端應該如何使用我們的程式。

其三,寫測試程式時,也會想到客戶端如何與我的程式互動?由兩者的互動關係,進而衍生出 dependent objects,dependency injections, IoC 等。而撰寫測試時,也需要開始建立  mocking object。

最後,TDD將修正我們所寫的類別,不再自行建立物件,而改由客戶端傳入,因此可得到較佳的相依性。好處是物件的生命週期可規劃在IoC一致處理,避免物件到處都是。

2 善用 Static analysis 工具

Visual Studio 中有個 Calculate Code Metrics工具,可計算程式的可維護性指數。此可維護性指數是由循環複雜度、繼承深度、類別結合程度、程式碼行數等四者組合而成。每次重構後,我也習慣來這裡看看可維護性指數是否增加了。如果可維護性指數小於10,那就代表完全沒辦法維護,這樣的程式是連當初的作者也會看不懂的。

FxCopCode analysis 又是另一種常用的功能。它將常見的程式規則寫成 coding rule 並進行分析。分析完後產生如同編譯的錯誤或警告,讓開發人員可以得到提醒並進行修正。

3 使用開發輔助工具

使用良好的開發輔助工具,會讓我們寫程式時事半功倍。Visual Studio 已經內建了許多工具,如 Refactor,Code Snippet等。第三方工具如 CodeRush, ReSharper 等更提供強大的功能,幫助我在搜尋、重構、程式碼分析等地方。

不得不提一下ReSharperCode Analysis功能,可直接在編輯區中背景地指出程式哪裡需要改進。就好像請了一個超強的大師,在身邊耳提面命,不斷地提出修正。久而久之,程式功力當然會進步。

image

4 不要太多的預設計 (Big Design Up Front)

在開發程式時,僅需要符合當下的需求就好。不要預想未來「可能」的需求為何?效能問題?應用程式架構怎樣分才能符合「未來」的需求。

要知道未來不見得發生這些預想的事。做這些預設計不但浪費時間,並可能造成未來程式難以重構,成本自然增加不少。

之前我也犯下不少這樣的錯誤。其中一項頗令我汗顏。當時我將某網站設計成三層式(3-tier)的架構,其中應用程式層(application tier)使用了 remoting的分散式架構。當時想:未來系統一定拆開到不同的伺服器,以符合高擴展性的需求,這些設計未來一定會用到。結果,開發兩年後,不但沒有發生,更糟的是網站還交給另給一組人維護。因為我做了多餘的設計,而這個設計已過了5年還沒發生當時預想的狀況,新進人員都會問當時為何這樣設計?造成交接、維護上的困擾。

5 奧坎剃刀原則 (Occam’s razor)

Occam’s razor 說明了下面的原則:
     假如一個問題有很多種解決辦法,選最簡單的那個

造成系統複雜的原因,其實還可再細分成兩種類型:本質複雜及意外複雜。

本質複雜(essential complexity)

本質上的複雜,是該問題本質上就比較複雜,我們應該採取科學上的方法讓它變簡單。常見科學方法,再分成兩種。

一個是將大而複雜的問題,拆解成多個簡單的小問題,進而一個個解決並得到整體行為。例如微積分,有限元素法。專案管理中的WBS 也是採用了這類的方法。這類方法有個缺點:小問題的總體行為等同於原大問題的行為嗎?

另一種是承認該大而複雜的問題難以拆解成小而簡單的問題。因此使用統計學的方法來統計大而複雜問題的行為。缺點是相當費時秏力,方法不正確,有時反而會得到相反的結果。

意外複雜(accidental complexity)

問題本質性的複雜外,有時候(也是常常)是我們採取的解決方法錯了,反而使得問題更複雜,稱為意外複雜。例如上述我所犯的預設計問題。此時,正確的方法則是將這些意外複雜因素移除掉。

本質複雜與意外複雜有時難以分辨,相當依賴設計人員的經驗。經典常見的意外複雜原因,如下。

  • 我們實作了自己的 Web/Persistence/Messaging/Caching 的framework, 因為找不到比較好用的!!
  • 買整個工具包,即使我們只用到10%。
  • 為了效能,我們將所有的商業邏輯寫到 Stored procedure。(常見吧!)
  • 我們不寫單元測試,因為我們已經花了很多時間在 debug。(真是天才)

這些理由看起來頭頭是道,似乎解決了問題,卻都使用問題更意外複雜。

6 學新的技術

為何要發明新技術?新技術往往是用來解決以前難以解決的問題,進而使用更簡單的方式來解決。以下是一些新的技術,您學會了嗎?
•Reflection
•Regular expressions
•Dependency injection
•Lambda expressions
Extension methods

雖然使用新技術解決問題往往更加容易,但並非一定要採用這些新技術才能解決問題。因此許多「上班族」不願花時間學習,只願使用已經習慣的舊技術來解決。這樣的心態,稱為舒適區(Comfort zone)。這裡不適合討論心理學,話題就此打住。

7 獨立思考

並非所有新技術都能存活下來,故並非所有新技術都值得學習。那要如何分辨呢?

相同的道理,網路上的訊息已經多到「知識爆炸」也難以形容,但我們還是要學習/吸取新知。那要學習哪些知識呢?使用什麼方式學習較有效率呢?

答案就是獨立思考,不要隨廣告/行銷等手法矇騙。要常常想這樣做,比較快嗎?比較有效率嗎?沒有其他方法了嗎?沒有其他觀點了嗎?

結論

寫著寫著,就跳脫程式寫作的範圍了。要如何寫出好程式,其實與個人的思考習慣有密切關係。希望這一篇能對大家有幫助。

後記:感謝保哥提醒,應為 Extension Methods

3 則留言:

Will 保哥 提到...

寫的真好耶,大推!

小建議:
Extension classes --> Extension methods

ben 提到...

不好意思
將所有商業邏輯寫到SP中所造成意外複雜是指?
因為非常常這樣做
但不了解這樣做會造成系統複雜的原因?

秉程 提到...

首先,在資料庫中儲存的是資料,如果有邏輯,也是「資料存取邏輯」而非商業邏輯。

記得 Presentation Layer 吧。ASP 時代最讓人垢病的是容易寫出 Presentation Logic 與 Business Logic 混在一起的程式,導致難以維護。

同理,若商業邏輯寫到Stored Procedure,就代表了 Stored Procedure 的資料存取邏輯同時包含了商業邏輯。同理,商業邏輯與資料存取邏輯混在一起,仍然難以維護。

為了效能,使用 Stored Procedure 來改善「資料存取」的速度,這是正確的。但是,不要試者將「商業邏輯」寫到 Stroed Procedure 中,這導致設計上的複雜,也就造成意外複雜。

Share with Facebook