天天看點

《代碼大全》第二版--第二部分第二部分:建立高品質代碼

第二部分:建立高品質代碼

    第五章:軟體建構中的設計

     5.1 設計

        在編碼前進行,比如畫圖,畫xml,想好邏輯怎麼做,新增哪些資料結構,命名;

        設計可能會考慮不周,并且設計過程是非常艱難的,會犯一些錯誤 ,但是在設計階段犯錯的代價遠低于編碼階段;

        設計是易變的;

    5.2 設計的重要目标:管理複雜度

        複雜度是設計的重要名額之一,現代軟體通常比較龐大,一個人的腦力遠遠不能裝下這麼多邏輯和設計。好的設計會将整個系統按照邏輯關系劃分為子系統,在将子系統分解,最終劃分為一個個小的在我們大腦記憶範圍内的功能點,我們能夠較容易的了解這些功能點,再将功能點聯系起來,由小到大的了解整個系統。降低系統複雜度,每個最小集複雜度達到最小。

        目标:最小複雜度,易于維護擴充、耦合性, 重用,移植,層次性。

    5.3 分層設計

        橫向和縱向上的設計,縱向上分層設計,從系統->子系統->子產品->類->子程式。縱向上的設計有自上而下和自下而上,自上而下的好處是相對比較容易,從大局逐漸剖析到細節,不好的地方是局部變化因素對上層影響較大,因為是固定好上層,如果下層變化需要影響到上層,會發生連鎖反應,自上而下設計,通常自下而上的開發,先搭建基礎元件。  自下而上的設計好處是先把影響最大并且最基礎的部分設計好,在設計高層,高層可以随意更改而不至于影響下層,這樣設計的難點在于要時刻和最終目标對齊,和最終整體目标對齊,否則容易走偏或者設計了一些多餘的用不上的底層接口。

        橫向子產品之間的互動一定要注意接口之間的通信限制,如果沒有限制,邏輯上其實就是一個接口,并沒有分開,子產品間要做到功能解耦,隻通過小部分暴露的接口做互動,而且某一子產品不能和太多子產品打交道,比如資料庫通路子產品就隻和資料庫打交道,并且向外暴露接口,在資料庫變化的時候隻需要适配接口就行了。

    5.4 設計來源(啟發式)

        将現實生活中的問題抽象成設計中的元素

        将生活中的相似實體抽象出共同的抽象特征

        采用資訊隐藏、

        區分系統易變和不易變部分。找出一個最小的不易變部分,逐漸擴大這個部分并檢測這個部分的易變性。直到易變性突破了某個可接受範圍,那麼外邊的更大範圍就是易變的。

        适當采用現有的設計方法:常見的設計模式

        補充:幾個代碼設計的原則:

            單一職責原則SRP: 就一個類而言,應該僅有一個引起它變化的原因

            開放封閉原則ASD:類,子產品,函數等等應該是可以擴充的,但是不可修改的

            裡式替換原則LSP:所有引用基類的地方必須透明的使用其子類的對象

            依賴倒置原則DIP:高層子產品不應該依賴低層子產品,兩個都應該依賴于抽象。抽象不應該依賴于細節,細節應該依賴于抽象    

            迪米特原理LOD:一個軟體實體應當盡可能少地與其他實體發生互相作用

            接口隔離原則ISP:一個類對于另一個類的依賴應該建立在最小的接口上。

        總結:設計是個很重要的過程,并且需要大量的工作經驗,不僅需要有全局觀,還有關注具體子產品功能的實作可行性,需要拆分目标系統,降低複雜度,需要關注的點很多,評估沖突妥協。這塊後面有相關經驗了再回來補充筆記。(普通開發人員在編碼之前也要做se給出的設計進行思考,看有沒有更優秀的方案,實作過程的邏輯僞代碼也最好寫一寫,畫一畫邏輯流程圖)

    第六章:可以工作的類

        6.1 類的基礎:抽象資料類型ADTs

            抽象資料類型是指一些資料以及對這些資料所進行的操作的集合。

            類 = ADTs + 繼承 + 多态

        6.2 良好的接口類型

            類的接口應該展現一緻的抽象層次:一個類僅實作一個ADT(單一職責);建立類時明确要實作的抽象是什麼,類中的服務也就是方法通常是成對的。

            類中的資料應該做好封裝,隻需要暴露部分以表達這個類的屬性特征的接口(資訊隐藏), 讓調用方針對接口程式設計而不是去看接口中的具體實作來程式設計。

             《抽象單一,職責單一,服務單一》

        6.3 有關設計和實作的問題

            1. 包含:has a的關系, 類中的成員資料,可以聲明也可以繼承得來,最好不要超過7個成員變量

            2. 繼承: is a 的關系;繼承可以重用父類的資料和成員,繼承體系不能過多,過多意味着複雜度過高,最好不超過6層。注意繼承的權限:private,protected,public; 避免菱形繼承。

                繼承規則: 如果需要共享資料,則将這部分資料提取出來形成一個資料結構; 如果需要共享行為,将這些行為提取出來形成一個基類; 如果都要共享,提取基類并将資料結構填充到基類中去。

                基類控制接口:繼承 

                自己控制接口:包含(包含字段,包含方法,包含對象)

            3.構造函數: 最好在構造函數裡進行成員初始化動作,構造函數要注意深淺拷貝

        6.4 建立類的原因

            1. 為現實世界中的對象模組化

            2. 為抽象的對象模組化(比如物體的形狀可以建立一個基類)

            3. 為了降低複雜度,建立多個類,類之間有互相互動; 類中可以隐藏資訊,比如一個具體的複雜算法或者一個協定,隐藏在類中,對類外暴露接口即可。

            4. 減少參數的傳遞:對于多參數的函數,如果将這些參數封裝成一個類,直接可以傳遞一個類的對象。

            5. 代碼可重用,将重用的部分代碼封裝成一個類,其他個性化的類可以繼承這個父類來擷取那些公用的方法; 

            6. 首先做好系統分析工作,将動态的和非動态的資料分别放到不同的類中,動态的資料組成的類可以随時變化。

            應該避免的類:萬能類(上帝類), 無關緊要的類(将這些類降級或者合并到其他類中), 類的命名不要使用動詞(如果類中隻包含動作而沒有具體資料對象,這個類通常為一個工具類,名字一定要使用名詞)

        補充總結:對類的思考,首先類的作用要單一,和函數一樣,職責要單一,類中應該儲存的是某一個ADT,對外暴露的接口也應該和這個ADT的抽象等級相同,對接口的暴露也要三思,最後類的名字要想好,用一個名字來表達。

    第七章:高品質的子程式

        子程式:routine, 方法:method, 過程:procedure, 宏:macro

        子程式是為實作一個特定目标而編寫的一個可被調用的方法或者過程。函數有傳回值,過程無傳回值。

        7.1 建立子過程的正當理由: 降低系統的複雜度,避免代碼重複, 封裝一個複雜的算法或者不易懂的協定之類的過程, 簡化複雜的布爾判斷, 改善性能,確定每個子程式都比較小

        7.2 在子程式上設計: 着重内聚性,子程式内部操作緊密,子程式對外變現的功能單一;

        7.3 好的子程式名字:描述子程式所做的功能, 不要使用數字如part1, part2 ; 使用動賓短語。

        7.4 子程式可以寫多長: 50-100 行, 最好在50行以内、函數越短小,功能越單一,可重用性越高

        7.5 如何使用子程式的參數:

                入參在前,出參在後; 使用宏定義來表明結構體中的出入參;參數最好不要超過5個,參數過多的時候可以考慮封裝成資料結構或者對象的方式傳入,參數命名也很重要; 形參名和實參名盡量保持一緻。

  #define  IN #define  OUT     struct {      IN  int putIn;      OUT  int putOut;  }    

        7.6 使用函數時要特别注意的事情:什麼時候用函數,什麼時候用過程,函數有傳回值,過程無傳回值,有的時候表示過程的一個函數可以傳回一個值用來表示這個過程是否執行成功。

        7.7 宏子函數與内聯子函數:

            宏子函數中的參數和計算過程一定要用括号包起來,因為宏展開的時候各種符号的優先級可能會出現非定義情況,

            宏中多條語句的時候要用大括号包起來,防止在if後面緊跟宏時,宏展開隻會有一條語句在for, if循環作用範圍内;

            取代宏的手段:const , inline , template, enum, typedef

  #define club(a)   ((a)*(a)*(a))   // a可能為x+1  -->   ((x+1)*(x+1)*(x+1))      

        總結:子程式,目的在于降低複雜度,提高可讀性,可靠性,可修複性,可重用性,封裝隐藏資訊,對于子程式,要注意内部的内聚性,一個子程式隻做一件事情,注意子程式行數盡量保持在50行内,最後,要給子程式取個好名字,達到從名字就能看出這個子程式完成的功能是啥。

    第八章:防禦式程式設計

        子程式不因傳入的錯誤資料而被破壞

    8.1 保護程式免遭非法輸入資料的破壞:檢查所有來源于外部的資料, 決定如何處理錯誤的輸入資料。

    8.2 斷言:開發和維護階段使用,生産代碼不要編譯進去。斷言檢查的是不該發生的情況,錯誤碼用來檢查不太可能發生的異常場景。

    8.3 錯誤處理技術:傳回中立值,換用下一個正确的資料, 傳回與前一次相同的資料, 記錄日志檔案,傳回一個錯誤碼; 應該具體場景具體分析,對于正确性要求較高的場景應確定正确性,對于健壯性要求較高的時候應着重健壯性。

    8.4 異常:把代碼中的錯誤或者異常事件傳遞給調用方代碼的一種特殊手段。提出異常,交出控制權,或者自己處理異常。高健壯性要求對異常處理要全面。避免在構造函數和析構函數中抛出異常,比如在構造函數中抛出異常,異常之前配置設定的資源就得不到釋放,這個時候就有記憶體洩漏問題; 了解庫函數的異常情況。

    8.5 隔離程式,使之包容由錯誤造成的損害: 調用子程式前,先做preCheck動作

    8.6 輔助調試代碼:目的是快速的檢測錯誤

        進攻式程式設計:在開發階段讓異常顯示出來,而在産品代碼運作時讓它能夠自我恢複。

        對于輔助的調試代碼,在正式産品中可以通過工具或者編譯選項将他們剔除于生産代碼中。

    8.7 确定在産品代碼中保留多少防禦式代碼:如果每個子程式在正式代碼之前都先運作防禦代碼,那麼系統将會變得臃腫,首先第一點,去掉一些細微錯誤的檢查代碼,去掉硬體因素引起的異常代碼,保留讓程式穩妥的崩潰的代碼; 第二,制定個産品規則,對于大多數的子程式,可以通過調用方來保證資料的正确性,有的子程式内部調用了幾個子程式,這幾個子程式的防禦代碼都是一緻的,在這些子程式外部進行一次資料校驗後就可以給多個子程式使用,如果每個子程式都去做一遍有點多餘,個人覺得大多數子程式的資料正确性由調用方負責比較靈活。

    8.8 對防禦式程式設計的态度:過多的防禦式代碼會讓系統變得臃腫和緩慢,增加系統複雜度,可适當根據防禦等級決定代碼的編寫。

    總結: 可以根據具體的項目要求來,有的項目要求調用方負責資料的正确性,有的要求被調用者負責資料的正确性,還是應該按需來。    

    第九章:僞代碼程式設計過程

        9.1 關于僞代碼:與具體語言無關,可以當成注釋來寫,盡量保持與邏輯一緻

        9.2 通過僞代碼建立子程式:檢查先決條件;定義子程式要解決的問題:輸入,輸出,功能; 為子程式命名; 決定如何測試子程式; 搜尋重用的代碼; 代碼效率性能考慮; 算法與資料結構的安排;編寫僞代碼;評審僞代碼;編寫代碼;

        總結: 僞代碼可以看成是具體代碼的稍上層表現,包含了大概的實作過程,又通過簡易的預覽來描述實作過程,在寫代碼之前進行僞代碼編寫可以提前預估到困難點以及應對方法。

轉載于:https://www.cnblogs.com/Zhangyq-yard/p/10874862.html