第七章,高質量的子程序
前言
子程序是爲實現一個特定的目的而編寫的一個可被調用的方法或過程。
7.1 創建子程序的正當的理由
1、 降低複雜度。當內部循環或內部嵌套層次很深的時候,就要考慮從子程序中提取出新的子程序了。子程序以其抽象,封裝,信息、細節的隱藏來實現管理複雜度的功能。
2、 引入中間、易懂的抽象。通過命名將一段代碼重組織,使其更易理解。
3、 避免代碼重複。
4、 支持子類化。便於子類覆蓋。
5、 隱藏順序。
6、 隱藏指針操作。指針操作可讀性通常很差,且容易出錯。
7、 提高可移植性。
8、 簡化複雜的布爾判斷。
9、 改善性能。隱藏實現的細節,有助於後面用更好的算法進行重新實現。
10、 確保子程序都很小
總之,創建高質量的子程序,有助於使用抽象、封裝,信息細節隱藏的手段,來降低複雜度。
除此之外的理由:
1、 隔離複雜度。
2、 隱藏實現細節。
3、 限制變化帶來的影響。
4、 隱藏全局數據:是對全局數據的範圍都通過子程序來實現。避免直接對全局數據的範圍。這個要注意一下的,是個非常好的建議。
5、 形成中央控制點。
6、 促進可重用的代碼。
7、 達到特定的重構的目的。
編寫有效子程序時,一個最大的心裏障礙是不情願爲一個簡單的目的而編寫一個簡單的子程序。事實證明,一個很好而有小巧的子程序會非常有用:既可以提高可讀性,又便於擴展。
7.2 在子程序上的設計
在設計類的時候,抽象和封裝已經很大程度上取代了內聚性。但是在子程序這一層次上,內聚性仍是常用的啓發式方法。
對與子程序而言,內聚性是指子程序中各個操作之間的聯繫的緊密程度。
我們的目標是讓子程序只把一件事情做好,不再做任何其他事情。
內聚性是針對操作的概念。即操作具有內聚性。通常,一個操作指一個語句,或一個函數調用。一個是簡單的操作,一個是複雜的操作。
內聚性的一些概念,理解概念有助於思考如何讓子程序儘可能的內聚。
1、 功能的內聚性:讓一個子程序僅執行一項操作。這是最強的也是最好的內聚性。其他的內聚性則不夠理想。
2、 順序上的內聚性:子程序內包含按照特定順序執行的操作,這些步驟需要共享數據,而只有全部執行完畢後,才完成了一下完整的功能。順序上的內聚性設計成功能上的內聚性需要對順序進行分解,形成多個功能更加單一的子程序。
3、 通信上的內聚性:子程序中的不同操作使用了同樣的數據,當不存在其他任何聯繫。優化的方法是將子程序分解爲多個子程序。
4、 臨時的內聚性:子程序中含有一些因爲需要同時執行才放到一起的操作。優化方法是把臨時性的子程序看成是一系列事件的組織者,即讓它直接調用其他的子程序,而不是直接執行所有的操作。另外,可以通過函數的命名,表達子程序完成的功能。避免使用多個操作疊加的方法命名,因爲它暗示子程序只有巧合的內聚性。
除功能的內聚性外,其他類型的內聚性都是不可取的。要想辦法避免。
1、 過程上的內聚性:子程序中的操作是按特定的順序進行的,除此外沒有任何聯繫。對子程序分解,把不同的功能納入不同的子程序中,讓調用方的子程序具有單一而完整的功能。
2、 邏輯上的內聚性:若干操作被放入同一個子程序中,通過傳入的控制標誌選擇執行其中的一項操作。各個操作間沒有任何的邏輯關係,這些操作只是被包含在一個很大的case或if語句中。如果此子程序只是分發命令,根據命令調用不同的子程序,則這種做法也是不錯的。其他的不好的情況,可以通過分解來進行優化。分解爲多個獨立的子程序。
3、 巧合的內聚性:子程序內部的各個操作間沒有任何的關聯。
內聚性考慮的是在一個子程序內部,操作的關聯程度。
編寫功能上的內聚性的子程序幾乎總是可能的,因此把注意力集中於功能上的內聚性,從而得到最大的收穫。
使用上面的建議對我的流程引擎程序進行內聚性的優化,發現確實很有價值。值得在後面的設計和編碼中堅持這種優化。
如果一個子程序中,局部變量的個數超過7個,則有可能是此子程序設計出現問題,有可能要優化一下。
7.3 好的子程序名字
1、 好的子程序的名字能夠清晰的描述子程序所做的一切。
2、 描述子程序所做的所有事情 子程序的名字應該能夠描述其所有的輸出結果及副作用。
3、 避免使用無意義的,模糊或表述不清的動詞——handle,perform,output,process,dealwith等。很多情況下,使用模糊不清的動詞作爲子程序的名稱的根本原因是程序本身就不是完成一個清晰的功能,這個時候,就要考慮重新組織該程序,並且選擇準確的詞語描述子程序。
4、 不要僅通過數字來形成不同的子程序名稱。
5、 根據需要確定子程序名稱的長度。研究表名,變量名的最佳長度是9到15個字符(過短則無法表達準確其含義,過長則有礙閱讀)。子程序命名的重點是含義清晰。長短要視名字是否清晰而定。
6、 給函數命名是要對返回值有所描述。最好能精確描述返回的結果。
7、 給過程起名時使用語氣強烈的動詞加賓語的形式。如果是面向對象,且動詞是對對象的操作,則不用加賓語。
8、 準確使用仗詞(成對的詞語):close/open。
9、 爲常用操作確立命名規則。
7.4 子程序可以有多長
子程序的長度儘量控制在200行以內。在這個範圍內,代碼長度不是子程序的關鍵因素,其他的降低複雜度的因素纔是關鍵的。
7.5 如何使用子程序的參數
子程序間的接口是程序中最容易出錯的部分之一。
1、 按照輸入-輸出-修改的順序排列參數:僅作輸入參數-即做輸入又作輸出-僅作輸出。
2、 考慮創建自己的OUT和IN關鍵字:有弊端,C++中可以使用const來表達只是輸入的含義。
3、 如果幾個程序都用了類似的一些參數,應該讓這些參數的排列順序保持一致。
4、 使用所有的參數。如果沒有使用,則從接口中刪除。
5、 把狀態或出錯變量放在最後。
6、 不要把子程序的參數用作工作變量。
7、 在接口中對參數的假定加以說明。比註釋更好的做法是加斷言:1)參數是僅用於輸入,輸出還是修改;2)表示數量的參數的單位;3)如果沒有用枚舉,則要說明狀態代碼和錯誤值的含義。4)所能接收的範圍。5)不應該出現的特定數值。
8、 把子程序的參數限定在大約7個以內。7是一個神奇的數值,人很難記住超過7個單位的信息。如果子程序的參數過多,則要考慮使用設計的方法進行優化。
9、 可以考慮採用某種表示輸入,輸出,修改的命名規則。
10、 爲子程序傳遞用以維持其接口抽象的變量或對象。考慮這個問題的時候,角度應該是這個子程序的接口要表達何種抽象。如果傳遞對象能夠表達子程序的抽象,就傳遞對象,如果傳遞變量能夠表達抽象,就傳遞變量。如果傳遞對象,但是在調用子程序前後經常需要進行裝配和卸載,或者傳遞變量,但是經常要修改參數表,則要考慮是否要換一種方式來設計接口。
11、 使用具名參數?
12、 確保形式參數和實際參數匹配。C語言存在這個問題,c++,java不存在。因爲他們是強類型的語言。
7.6 使用函數時要特別考慮的問題
在現代編程語言中,函數是指有返回值的子程序,過程是指沒有返回值的子程序。
1、 什麼時候使用函數,什麼時候使用過程:把子程序的調用和對狀態值的判斷清楚的分開,降低此語句的複雜度。如果一個子程序的主要用途就是返回其名字所指明的返回值,那麼就使用函數,否則,使用過程。
2、 設置函數的返回值:1)檢查所有的路徑,設置一個默認的函數返回值。2)不要返回指向局部的應用和指針。
7.7 宏子程序和內聯子程序
1、把宏表達式包含在整個括號內:#define cube(a) ((a)*(a)*(a))
2、把多條語句的宏用括號括起來。
3、用子程序的命名方法來命名宏,便於後面替換。
4、不到萬不得已,不要用宏來替換子程序。
5、限制使用內聯子程序。Inline關鍵字可以支持提高性能。
CHECKLIST: High-Quality Routines覈對表:高質量的子程序大局事項 1、 創建子程序的理由充分嗎? 2、 一個子程序中所用適用於單獨提出的部分是不是已經被提出到單獨的子程序中了? 3、 過程的名字中是否用了強烈、清晰的“動詞+賓語”詞組?函數的名字是否描述了其返回值? 4、 子程序的名字是否描述了其所做的全部的事情? 5、 是否給常用的操作建立了命名規則? 6、 子程序是否具有強烈的功能上的內聚性?即它是否做且製作一件事情,並且把它做得很好? 7、 子程序間是否有較鬆散的耦合?子程序和其他子程序之間的連接是否是小的(small)、明確的(intimate)、可見的(viaible)和靈活的(flexible)? 8、 子程序的長度是否是由其功能和邏輯自然決定,而非遵循任何人爲的編碼標準? 參數傳遞事宜 1、 整體來看,接口的參數是否表現出一種具有整體性且一致的接口抽象? 2、 子程序的參數排列順序是否合理?是否與類似的子程序的參數排列順序一致? 3、 接口假定是否已經在文檔中說明? 4、 子程序的接口是否沒有超過7個? 5、 是否用到了每一個輸入參數? 6、 是否用到了每一個輸出參數? 7、 子程序是否避免了把輸入參數用做工作變量? 8、 如果子程序是一個函數,那麼它在所有可能的情況下都能返回一個合法的值? |
本章要點
1、 創建子程序的最主要的目的是提高程序的可管理性,當然也有其他一些好的理由。其中,節省代碼空間只是一個次要的原因;提供可讀性、可靠性和可修改性等原因更重要一些。
2、 有時候,把一些簡單的操作寫成獨立的函數也非常有價值。
3、 子程序可以按照其內聚性分爲很多種類,而你應該讓大多數子程序具有功能上的內聚性,這是最佳的一種內聚性。
4、 子程序的名字是它的質量的指示器,如果名字糟糕但恰如其分,說明這個子程序的設計很差勁。如果名字糟糕而且又不準確,那麼它就反映不出是幹什麼的。不管怎麼樣,糟糕的名字都意味着程序需要修改。
5、 只有在某個子程序的主要目的是返回由其名字所描述的特定結果時,才使用函數。
6、 細心的程序員會非常謹慎的使用宏,而且只用在萬不得已時。
本章,我認爲最重要的部分是7.2 在子程序上的設計,這裏可以學習一下子程序內聚性的知識。應用其中的一些原則對我的代碼進行優化,發現效果非常不錯。