高質量的子程序
在討論高質量的子程序的細節之前,明確下面這兩個基本術語會很有幫助。首先,什么是“子程序(routine)”? 子程序是為實現一個特定的目的而編寫的一個可被調用的方法(method)或過程(procedure)。
拋開計算機本身,子程序也算得上是計算機科學中一項最重大的發明了。子程序的使用使得程序變得更加易讀,更易于理解,比在任何編程語言的任何功能特性都更容易。
子程序也是迄今為止發明出來的用以節約空間和提高性能的最重要手段。
3.1 ?創建子程序的正當理由
下面概況了創建子程序的一些理由
- ?降低復雜度;
- ?引入中間的、易懂從抽象;
- ?避免代碼代碼重復;
- ?支持子類化;
- ?隱藏順序;
- ?隱藏指針操作;
- ?提高可移植性;
- ?簡化復雜的邏輯判斷;
- ?改善性能;
除此之外,創建類的很多理由也是創建子程序的理由:
- ?隔離復雜度;
- ?隱藏實現細節;
- ?限制變化所帶來的影響;
- ?隱藏全局數據;
- ?形成中央控制點;
- ?促成可重用的代碼;
- ?達到特定的重構目的;
3.2 ?在子程序層上設計
首先來理解一個概念,內聚性。對子程序而言,內聚性是指子程序中各種操作之間聯系的緊密程度。我們的目標是讓每一個子程序只把一件事情做好,不再做任何其他事情。這樣做的好處是得到更高的可靠性。
關于內聚性的討論一般會涉及到內聚性的幾個層次。理解一些概念要比記住一些特定的術語更重要。這些概念可以幫助你思考如何讓子程序盡可能地內聚。
功能的內聚性:是最強也是最好的一種內聚性,也就是說讓一個子程序僅執行一項操作。
當然,以這種方式來評估內聚性,前提是子程序所執行的操作與其名字相符——如果它還做了其他的操作,那么它就不夠內聚,同時其命名也有問題。
除此之外,還有其他一些種類的內聚性人們卻通常認為是不夠理想的。比如:順序上的內聚性、通信上的內聚性、臨時的內聚性。
為了得到更好的內聚性,可以把不同的操作納入各自的子程序中。讓調用方的子程序具有單一而完整的功能。為了讓所有的子程序都具有功能上的內聚性,對兩個或更多的原有子程序進行修改是很常見的。
這些術語中沒有哪個是神秘的或者圣神不可侵犯的。需要理解的是其中的想法,而不是那些術語。編寫具有功能上的內聚性的子程序幾乎總是可能的,因此把注意力集中于功能上的內聚性,從而得到最大的收獲。
3.3 ?好的子程序名字
好的子程序名字能清晰地描述子程序所做的一切。這里是有效地給子程序命名的一些指導原則。
- 描述子程序所做的所有事情;
- 避免使用無意義的、模糊或表述不清的動詞;
- 不要僅通過數字來形成不同的子程序名字;
- 根據需要確定子程序名字的長度;
- ?給函數命名時要返回值有所描述
- 給過程起名時使用語氣強烈的動詞加賓語的形式;
- 準確使用對仗詞;
- 為常用操作確立命名規則。
3.4 ?如何使用子程序參數
- ?按照輸入-修改-輸出的順序排列參數。不要隨機地或按字母順序排列參數,而應該先列出僅作為輸入用途的參數,然后是既作為輸入又作為輸出用途的參數,最后才是僅作為輸出用途的參數。這種排列方法暗含了子程序的內部操作所發生的順序——先是輸入數據,然后修改數據,最后輸出結果。
- 如果幾個子程序都用了類似的一些參數,應該讓這些參數的排列順序保持一致。
- 使用所有的參數。
- 把狀態或出錯變量放在最后。按照習慣做法,狀態變量和那些用于指示發生錯誤的變量應該放在參數表的最后。它們只是附屬于程序的主要功能,而且它們是僅用于輸出的參數,因此這是一種很有道理的規則。
- 不要把子程序的參數用做工作變量。把傳入子程序的參數用做工作變量是很危險的。應該使用局部變量。
- 在接口中對參數的假定加以說明。如果你假定了傳遞給子程序的參數具有某種特征,那就要對這種假定加以說明。在子程序內部和調用子程序的地方同時對所做的假定進行說是值得的。不要等到把子程序寫完了之后再回過頭去寫注釋——你是不會記住所有這些假定的。一種比用注釋還好的方法,是在代碼中使用斷言(assertions)。
應該對這些接口參數的假定進行說明:參數是僅用于輸入的、要被修改的、還是僅用于輸出的;表示數量的參數的單位;如果沒有枚舉類型的話,應該說明狀態代碼和錯誤值的含義;所能接受的數值的范圍;不該出現的特定數值。
- 把子程序的參數個數限制在大約7個以內。在實踐中,子程序中參數的個數到底應該限制在多少,取決于你所使用的編程語言如何支持復雜的數據類型。如果你使用的是一種支持結構化數據的現代編程語言,你就可以傳遞一個含有13個成員的合成數據類型,并將它看作一個大數據塊。如果你使用的是一種更為原始的編程語言,那你可能需要分別傳遞全部13個成員。
如果你發現自己一直需要傳遞很多參數,這就說明子程序之間的耦合太過緊密了。應該重新設計這個或這組子程序,降低其間的耦合度。如果你向很多不同的子程序傳遞相同的數據,就請把這些子程序組成一個類,并把那些經常使用的數據用作類的內部數據。
- 考慮對參數采用某種表示輸入、修改、輸出的命名規則。
- 為子程序傳遞用以維持其接口抽象的變量或對象。
- 確保實際參數與形式參數相匹配。
核對表: ?高質量的子程序
大局事項
- ?創建子程序的理由充分嗎?
- ?一個子程序中所有適于單獨提出的部分是不是已經被提出到單獨的子程序中了?
- ?過程的名字中是否用了強烈、清晰的“動詞+賓語”詞組?函數的名字是否描述了其返回值?
- ?子程序的名字是否描述了它所做的全部事情?
- ?是否給常用的操作建立了命名規則?
- ?子程序是否具有強烈的功能上的內聚性?即它是否做且只做一件事情,并且把它做得很好?
- ?子程序之間是否有較松的耦合?子程序與其他子程序之間的連接是否是小的、明確的、可見的和靈活的?
- ?子程序的長度是否是由其功能和邏輯自熱確定,而非遵循任何人為的編碼標準?
參數傳遞適宜
- ?整體來看,子程序的參數表是否表現出一種具有整體性且一致的接口抽象?
- ?子程序參數的排列順序是否合理?是否與類似的子程序的參數排列順序相符?
- ?接口假定是否已在文檔中說明?
- ?子程序的參數個數是否沒超過7個?
- ?是否用到了每一個輸入參數?是否用到了每一個輸出參數?
- ?子程序是否避免了把輸入參數用做工作變量?
- ?如果子程序是一個函數,那么它是否在所有可能的情況下都能返回一個合法的值?
要點
- ?創建子程序最主要的目的是提高程序的可管理性,當然也有其他一些好的理由。其中,節省代碼空間只是一個次要的原因;提高可讀性、可靠性和可修改性等原因都更重一些。
- ?有時候,把一些簡單的操作寫成獨立的子程序也非常有價值。
- ?子程序可以按照其內聚性分為很多類,而你應該讓大多數子程序具有功能上的內聚性,這是最佳的一種內聚性。
- ?子程序的名字是它的質量的指示器。如果名字糟糕但恰如其分,那就說明這個子程序設計得很差勁。如果名字糟糕且又不準確,那么它就反映不出程序是干什么的。不管怎樣,糟糕的名字都意味著程序需要修改。
- ?只有在某個子程序的主要目的是返回有其名字所描述的特定結果時,才應該使用函數。
- ?細心的程序員會非常謹慎地使用宏,而且只在萬不得已時才用。