《代碼大全2》讀書筆記

第7章 高質量的子程序

7.1 創建子程序的正當理由

  • 降低/隔離複雜度,隱藏實現細節,引入中間的、易懂的抽象
  • 避免代碼重複,支持子類化
  • 提高可移植性,限制變化所帶來的影響
  • 簡化複雜的邏輯判斷,改善性能

7.2 在子程序層上設計

  • 功能的內聚性:只做了一件事並把它做得很好,操作與名稱相符
  • 順序上的內聚性:包含需按特定順序執行的操作,它們共享數據且只有全部執行完功能才完整
  • 通信上的內聚性:不同操作使用了同樣的數據,但不存在其他任何聯繫
  • 臨時的內聚性:包含一些需要同時執行才放在一起的操作
  • 其他類型的內聚性:基本是不可取的,它們會導致代碼組織混亂、難於調試、不便修改
  • 子程序間耦合鬆散,連接小、明確、可見並靈活

7.3 好的子程序名稱

  • 能準確描述子程序所做的全部事情:避免使用表述不清的動詞;避免用數字區分不同子程序
  • 函數命名時應針對返回值有所描述
  • 過程命名時用語氣強烈清晰的"動詞+賓語"形式:類的過程不用加入對象名(賓語)
  • 準確使用對仗詞:add/remove,source/target,next/previous等
  • 爲常用操作確立命名規則:如統一命名方式 x.Id() y.GetID() 爲其中一種

7.4 子程序可以寫多長

  • 允許有序增長到100~200行(不含註釋),該長度與小而美的子程序一樣不易出錯,超過200行易遇到可讀性等問題
  • 限制長度不如關注 – 內聚性、嵌套層次、變量數量、決策點數量、註釋數量來決定長度

7.5 如何使用子程序參數

子程序間的接口是最易出錯的部分之一:39%錯誤源於互相通信時

  • 參數保持順序統一:如輸入-修改-輸出-狀態參數最後;多個子程序使用類似參數順序保持一致
  • 參數使用語義提示:如const標記輸入、&標記修改,或加i/m/o/s等前綴
  • 使用所有的參數且避免用輸入參數做工作變量:傳遞了就要用到,否則刪掉;避免混淆
  • 對特徵參數的假定加以說明:如輸入、修改或輸出;參數單位;非枚舉狀態值含義;數值範圍;不該出現的特定值
  • 參數個數限制在7個以內:心理學研究表明超過7個單位的信息很難記住,多個參數考慮合成數據類
  • 傳遞對象還是成員做參數 – 取決於子程序接口的抽象:
    1. 參數傳遞:期望的幾項特定數據碰巧來自一個對象;
    2. 對象傳遞:想持有某個特定對象並要進行某些操作(經常修改參數列表且都來自同一個對象)
  • 確保實參和形參相匹配:檢查參數類型並留意編譯器警告

7.6 使用函數時要特別考慮的問題

  • 若用途爲返回其名稱所指明的返回值就應用函數,否則用過程
  • 設置函數返回值:
    1. 檢查所有可能的返回路徑並在開頭用默認值初始化;2. 不要返回指向局部數據的引用或指針
  • 宏/內聯子程序儘量避免使用宏
    1. ()包含整個宏表達式;2. {}括起含有多條語句的宏;3. 展開後形同子程序的宏命名同子程序以便替換
  • 節制使用inline子程序:除非剖測(profile)時得到不錯的性能收益

第11章 變量名的力量

11.1 選擇好變量名的注意事項

  • 準則:變量名要完全準確地描述其所代表的的事物(可讀、易記、無歧義、長度適中)
  • 問題爲導向:好的命名錶達的是“什麼”(what),而不是“如何”(how)
  • 恰當的名字長度:
    1. 平均長度在8~20個字符最易於調試 – 若發現很多短名時應檢查其含義是否足夠清晰
    2. 較長的名字適用於很少用到的、全局變量,較短的適用於局部、循環變量
  • 對全局變量名加以限定詞:如命名空間(namespace)、包名(package)或帶有子系統特徵的前綴
  • 限定詞前置突出含義:爲變量賦予主要含義的部分應位於最前面

11.2 爲特定類型的數據命名

  • 循環變量:短循環用i、j等;長循環或循環外變量應使用可讀性高的命名
  • 狀態變量:使用枚舉、具名常量;當某段代碼不易讀懂時就應考慮重命名
  • 臨時變量:即時“臨時”最好也取個可讀性高的名稱
  • 布爾變量:使用肯定且隱含"真/假"含義的名字,如found等而非notDone等難於閱讀的否定詞
  • 枚舉類型:使用組前綴;若使用須冠以枚舉名則無需前綴如Color.Red
  • 具名常量:應根據常量表示的含義而非具有的值爲其命名

11.3 命名規則的力量

  • 何時採用命名規則:
    • 多人合作、維護或需他人評估的項目時
    • 大規模程序,腦海裏無法同時瞭解事情全貌必須分而治之時
    • 生命週期長的項目,長到擱置幾星期/月後又需重啓工作時
    • 當項目中存在一些不常見術語,希望在編碼階段使用標準術語或縮寫的時候

11.4 非正式命名規則

  • 前綴標識全局變量g_、具名常量c_、參數變量a、局部變量l、成員變量m_、類型聲明T
  • 語法標明並限制只讀參數const、可修改參數&、*
  • 格式化命名保持一致風格:如一直單用駝峯命名或匈牙利命名法

11.5 標準前綴

  1. 用戶自定義類型(UDT):縮寫標識出數據類型,如字符ch、文檔doc
  2. 語義前綴(不隨項目變化):如指針p、全局變量g

11.6 創建可讀的縮寫

  • 使用標準縮寫(列在字典的常見縮寫)
  • 去掉虛詞and/or/the等,去除無用後綴ing/ed
  • 統一在某處截斷或約定成俗的縮寫如src/succ
  • 使用名字中每一個重要單詞且最多不超過三個
  • 縮寫要一致;可讀出來;避免易看/讀錯的字符組合;
  • 項目級縮寫辭典:創建新縮寫時加以說明並歸檔,只有不惜花費精力寫文檔的縮寫才的的確確因當被創建

11.7 應該避免的名稱

  • 避免容易令人誤解或混淆(相似含義/易拼錯/數字)的名稱或縮寫,如I/1/l
  • 避免含義不同卻名字相似,避免僅靠大小寫區分
  • 避免使用發音相近的名稱,不易於討論
  • 避免混合多種自然語言(中英混雜)

第8章 防禦式編程

8.1 保護程序免遭非法輸入數據的破壞

  • 核心思想:子程序不會因傳入錯誤實參而被破壞,哪怕是由其他子程序產生的錯誤數據
  • 檢查所有外部來源數據的值;檢查所有輸入參數的值;決定如何處理錯誤的輸入數據;

8.2 斷言(Assertion)

  • 斷言主要用於開發和維護階段:
    • 檢查輸入或輸出值在預期範圍內(如指針非空;數組或容器容量足夠;表已初始化存儲着有效數據)
    • 檢查子程序開始(結束)執行時文件或流處於打開(關閉)狀態,且讀寫位置位於開頭(結尾),檢查讀寫模式
    • 檢查僅作爲輸入的參數值是否被子程序所修改
  • 使用斷言的指導建議:
    • 用錯誤代碼處理預期內(或系統內部)狀況,用斷言處理絕不應該發生的狀況(觸發則修改源碼)
    • 避免將需要執行的代碼放到斷言中
    • 用斷言註解並驗證前條件(調用方確保參數正確)和後條件(被調用方確保返回正確)
    • 對於高健壯性的代碼(大規模長週期複雜項目),應先使用斷言再使用錯誤處理代碼

8.3 錯誤處理技術

  • 處理預期內可能發生的錯誤方式:
    • 返回中立值/最接近的合法值:如指針操作返回NULL,超出範圍返回最大值
    • 換用下一個正確的數據,返回與前次相同的數據
    • 將警告信息顯示/記錄到日誌文件中(注意數據隱私)
    • 返回一個錯誤碼:只處理部分錯誤其餘報告調用方有錯誤
    • 關閉程序:適用於安全攸關傾向於正確性的程序
  • 在架構層次確定錯誤參數處理方式,並始終如一地採用該種處理方式

8.4 異常

  • 異常 – 將不甚瞭解的出錯轉交給調用鏈其他子程序更好地解釋,使用建議:
    • 發生了不可忽略的錯誤需要通知程序其他部分
    • 只在發生真正罕見或無法解決的問題下才拋出異常,可局部處理的直接處理掉
    • 避免在構造/析構函數中拋出異常
    • 拋出的異常應該與接口的抽象層次一致
    • 在異常消息中加入關於導致異常發生的全部信息
    • 瞭解所用函數庫可能拋出的異常,未能捕獲將導致程序崩潰
    • 創建一個集中的(統一存儲格式化)標準化的(規定使用場合/異常對象類型等)異常報告機制

8.5 隔離程序,使之包容由錯誤造成的損害

  • 隔欄(手術室)技術 – 數據進入前"消毒",之後都認爲是安全的
    • 只在類的公開方法檢查並清理數據,而類的私有方法不再承擔校驗職責
    • 在輸入數據後立即將其轉換爲恰當的類型
  • 隔欄外部的程序使用錯誤處理技術,而隔欄內部應使用斷言(數據已清理,出錯爲程序問題)

8.6 輔助調試的代碼

  • 不要把產品版的限制強加於開發版:在開發期犧牲某些,用以換取讓開發更順暢的內置工具
  • 儘早引入輔助調試的代碼
  • 採用進攻式編程:
    • 確保斷言語句使程序終止運行 – 問題引起的麻煩越大越易被修復
    • 完全填充分配到的內存/文件/流 – 易於排查內存分配/文件格式錯誤
    • 確保case語句的default/else分支都產生不可忽視的提示
    • 在刪除一個對象前把它填滿垃圾數據
    • 可以的話將錯誤日誌文件發送到email
  • 計劃移除調試輔助的代碼
    • 使用類似ant和make的編譯工具
    • 使用預處理器(內置/自定義的編譯條件)
    • 使用調試存根:stub存根子程序開發階段用於各種校驗日誌輸出,發佈時立即將控制權交還調用方

8.7 確定在代碼中該保留多少防禦式代碼

  • 保留那些檢查重要錯誤的代碼,去掉檢查影響細微錯誤的代碼
  • 保留能讓程序穩妥地崩潰的代碼,去掉會導致硬性崩潰/數據丟失的調試代碼
  • 爲技術支持記錄並保存錯誤日誌
  • 確認錯誤顯示消息對用戶而言是友好的

8.8 對防禦式編程採取防禦的姿態

  • 避免過度使用,因地制宜的調整防禦式編程的優先級

第18章 表驅動法

  • 從表裏查找信息而不使用邏輯語句if和case; 將複雜的邏輯鏈/繼承結構用查表法替代

18.1 表驅動法使用總則

  1. 如何從表中查詢條目:直接訪問,索引訪問,階梯訪問
  2. 應該在表裏存些什麼:數據還是函數

18.2 直接訪問表

  • 無須繞很多複雜的圈子就能在表裏找到需要的信息
  • 構造查詢鍵值:
    • 複製重複信息直接使用鍵值;
    • 轉換鍵值使其能直接使用,將鍵值轉換獨立成子程序

18.3 索引訪問表

  • 將基本數據映射爲索引表的一個鍵值,再用鍵值關聯主數據表
  • 優點一:避免主查詢表單條記錄過大和重複造成的空間浪費:如商品碼099→商品類型AE→A~E商品信息
  • 優點二:操作索引中的記錄比操作主表中的記錄更方便更廉價:如員工姓名索引,薪水索引
  • 優點三:良好的可維護性:將索引查詢提取爲單獨的子程序,方便更換查詢技術等

18.4 階梯訪問表

  • 表中的記錄對不同數據範圍有效,而非不同的數據點 – 適合處理無規則數據
  • 留心端點:確認已考慮到每個階梯區間的上界
  • 超多階梯考慮用二分查找取代順序查找
  • 將查詢操作提取爲單獨的子程序; 考慮用索引替代

第4章 關鍵的"構建"決策

4.1 選擇編程語言

  • 更熟悉或更高級的編程語言將達到更好的生產率和質量
  • 瞭解諸如面向對象、面向過程、腳本等語言的明確優點和弱點

4.2 編程約定

  • 高質量軟件其"架構的概念完整性"與"底層實現"保持着內在的固有的一致性
  • 架構的指導方針使得程序的結構平衡:如繪畫設計,其中一部分古典主義而一部分印象主義是不可能具有"概念完整性"的
  • 針對"構建活動"的指導方針(格式約定等)提供了底層的協調,將每個類都銜接到一種完整的設計中,成爲可靠的部件

4.3 你在技術浪潮中的位置

  • 編程工具不應該決定你的編程思想:首先決定要表達的思想,再決定如何去表達出來
  • 編程原則並不依賴特定的語言,如果語言缺乏就應該試着去彌補它,發明自定的編碼約定、標準、類庫及其他改進措施

4.4 選擇主要的構建實踐方法

  • 編碼
    • 是否確定哪些設計工作要預先進行,哪些設計在編碼時進行?
    • 是否規定了諸如名稱、註釋、代碼格式等"編碼約定"?
    • 有無規定特定的由軟件架構確定的編碼實踐:如何處理錯誤條件/安全性事項、類接口有哪些約定、考慮多少性能因素等
    • 是否確定你在技術浪潮中的位置並有相應的調整計劃和預期目標?是否知道如何不受限於某種編程語言?
  • 質量保證
    • 編碼前是否要先編寫測試用例?需要爲自己的代碼編寫單元測試麼?
    • check in代碼前,會用調試器單步跟蹤整個代碼流程嗎?是否進行集成測試?
    • 會複審(review)或檢查別人的代碼嗎?
  • 工具
    • 是否選用SVN/GIT版本控制及相關工具?
    • 是否選定了一種語言(版本)或編譯器版本?是否允許使用非標準的語言特性?
    • 是否選定了某個編程框架(J2EE 或 .NET),或明確決定不使用框架?
    • 是否選定並擁有了將要用到的工具——IDE、測試框架、重構工具、CI等?

第33章 個人性格

33.1 個人性格是否和本書話題無關

  • 下定決心成爲出色的程序員:聰明無法提升,性格卻可改進,而個人性格對造就高手有決定性意義

33.2 聰明和謙虛

  • 如何專注你的才智比你有多聰明更重要:越瞭解自己的侷限性越能保持謙虛,進步也就越快
    • 將系統"分解",使之易於理解
    • 進行審查、評審和測試,減少人爲失誤; 應和他人溝通(三人行必有吾師),以提高軟件質量
    • 通過各種各樣的規範,將思路從相對繁瑣的編程事務中解放出來

33.3 求知慾

  • 在成長爲高手的過程中,對技術的求知慾具有壓倒一切的重要性 – 技術不斷更新,跟不上就會落伍
  • 在開發過程中建立自我意識:閱讀學習並實踐,若工作中學不到新東西就應考慮換新工作
  • 對編程和開發過程做試驗:學會迅速編寫小實例去試錯,並能從中有所收穫
  • 閱讀問題的相關解決方法:人們並不總能自行找出解決問題的巧妙方法,所以要學習他人的解法
  • 在行動之前做分析和計劃:不要擔心分析太久,等鐘擺走到"行動"快中央的位置再說
  • 學習成功項目的開發經驗,研究高手的程序:如《編程珠璣》; 找些一流程序員評論你的代碼
  • 閱讀文檔:文檔中有許多有用的東西值得花時間去看 – 例如每兩月翻翻函數庫文檔
  • 閱讀其他書本期刊:每兩月看本計算機好書(35頁/周),過不了多久就能掌握本行業脈搏並脫穎而出
  • 同專業人士交往:與希望提高技術的人爲伍,參加交流會/羣
  • 向專業開發看齊
    • 1入門級:會利用某語言基本功能或特性編寫類、流程語句
    • 2中級:能利用多種語言的基本功能,並會得心應手地使用至少一種語言
    • 3熟練級:對語言或環境(或兩者兼具)有着專業技能,或精通J2EE的盤根錯節,或對C++引用如數家珍
    • 4技術帶頭人級:具有第3級的專業才學,並明白工作中85%的時間都是與人打交道 – “爲人寫代碼,而非機器,所寫代碼晶瑩剔透還配有文檔”

33.4 誠實

  • 承認自己"不知道",不假裝是高手 – 聽聽別人說法,學到新內容,並瞭解他們是否清楚討論的東西
  • 犯了錯誤應立即主動承認 – 複雜的智力活動有潮起潮落,錯誤情有可原,這也是強調測試的原因之一
  • 力圖理解編譯器的警告而非棄之不理 – 忽略警告,時間就很可能會浪費在調試上
  • 透徹理解自己的程序,而不只是編譯看看能否運行 – 測試只能找出錯誤,不能確保"不存在錯誤"
  • 提供實際的狀況報告 – 深思熟慮後冷靜地在私下彙報項目真實狀態,管理者需要準確的信息以便協調
  • 提供現實的進度方案,在上司前堅持自己的意見 – 如"無法商量項目該花多少時間,就像不能商量確定一里路有幾米一樣,自然規律是不能商量的。但我們可協商影響項目進度的其他方面,比如減少些特性,降低性能,分階段開發,少些人時間延長些,或多些人時間短些。"

33.5 交流與合作

  • 真正優秀的程序員知道怎樣同別人融洽地工作和娛樂
  • 代碼便於看懂是對團隊成員的基本要求:編程首先是與人交流,其次纔是與計算機交流
  • 作爲一項可讀性原則,應感激修改你代碼的人

33.6 創造力和紀律

  • 大型項目若無標準和規範,完成都有困難,更談不上創新了
  • 在非關鍵之處建立規範,在重要之處傾力發揮創造性 – "Form is liberating"形式就是解放
  • 如果編程前不分析需求也不設計,工作成果與其說是藝術品不如說是幼兒塗鴉

33.7 偷懶

  • 拖延不喜歡的任務 – "實在懶"沒有任何益處
  • 迅速完成不喜歡的任務,以擺脫之 – "開明懶"至少解決了問題
  • 編寫工具來完成不喜歡的任務,以便再也不用做這樣的事了 – "一勞永逸的懶"最具產值
  • 不要"硬幹"或"苦幹" – 有效編程最重要的是思考; 行動不等於進展,忙碌也不意味着多產

33.8 不如你想象中那樣起作用的性格因素

  • 堅持 – 根據環境不同,堅持可能是財富也可能是負擔
    • 在某段代碼卡殼時,不妨重新設計類,或繞過去回頭再試,一種方法不通時換個方法
    • 調試時,超過一段時間(15m)沒有進展,就應放棄排錯過程; 想法子繞開,如重頭編寫,理清思緒再做
    • 遭受挫折時設置底線:要是某方法30分鐘內還不能解決,就花10分鐘想其他方法,再用1個鐘頭嘗試最可行辦法
  • 經驗 – 軟件開發行業的經驗比書本價值要小,因爲基礎知識更新快,思維模式發展快
    • 檢討自己的行爲並不懈地學習,才能獲得真正的經驗:工作10年得到10年經驗還是1年經驗的10次重複?
  • 編程狂人 – 徹夜編程並不能讓你感受到編程中激動人心的東西,熱愛不能代替熟練的能力

33.9 習慣

  • 好習慣非常重要,發展歸因於習慣 – 行爲養成習慣,年復日久的好壞習慣決定了你是優秀還是拙劣
    • 培養先以僞代碼編寫類再改用實際代碼
    • 編譯前認真檢查代碼
    • 使用不易出錯易於理解的格式和規範
    • 謙虛、旺盛的求知慾、誠實、創造性和紀律、高明的偷懶

第2章 用隱喻來更充分地理解軟件開發

2.1 隱喻的重要性

  • 重要的研發成功常常產自類比 – 把開發過程與熟悉的活動聯繫在一起,產生更深刻的理解
  • 模型的威力在於其生動性,讓你能夠把握整個概念 – 隱隱暗示各種屬性、關係以及需要補充查證的部分
  • 好的隱喻 – 足夠簡單,與另一些相關的隱喻聯繫密切,且能解釋大部分實驗證據及其他已觀測到的現象

2.2 如何使用軟件隱喻

  • 隱喻是啓示而非算法 – 像探照燈,不會告訴你去哪裏尋找答案,而僅是告訴你該如何去尋找答案
  • 編程最大的挑戰是如何將問題概念化,很多錯誤都是概念性的錯誤
  • 用隱喻提高對編程問題和編程過程的洞察力,幫助你思考編程過程中的活動,想象出更好的做事情的方法

2.3 常見的軟件隱喻

  • 養殖觀點:系統生長 – “增量、迭代、自適應、演進的”; 學會以增量方式進行設計、編譯和測試
    • 構建足夠強壯的骨架,支撐將要開發的真實系統:基本起點是先做出儘可能簡單但能運行的版本
    • 附着肌肉和皮膚:將虛假類替換爲真類,接收真實輸入,產生真實輸出,不停迭代直至得到一個可工作的系統
  • 軟件構建:建造軟件 – 計劃、準備以及執行,學會使用現成庫"裝修"
    • 軟件架構(建設)、支撐性測試代碼(腳手架)、構建(建設)、基礎類及分離代碼
    • 類比房屋建設過程,可以發現仔細的裝修是必要的,而大型項目和小型項目之間也是有差異的
  • 智慧工具箱 – 實踐積累分析工具(技術、技巧)到工具箱,知道該何時何地正確使用
  • 不同隱喻彼此並不排斥,應當使用對你最有益處的某種隱喻組合

第3章 三思而後行:前期準備

3.1 前期準備的重要性

  • 準備工作的根本目標在於降低風險:集中改進降低需求分析和項目規劃等最常見的風險
  • 準備不周全的誘因:做很多但不能做好的前期工作毫無意義; 未能抵抗"儘快開始編碼"的慾望
  • 關於構建前要做前期準備絕對有力且簡明的論據
    • 訴諸邏輯:弄清楚真實需求,即要做什麼,如何去做,項目週期及人員等需求
    • 訴諸類比:比如食物鏈,架構師吃掉需求,設計師吃掉架構,程序員消化設計
    • 訴諸數據:發現錯誤的時間要儘可能接近引入該錯誤的時間,時間越長修復成本越大
    • "老闆就緒"測試:確認老闆明白了"構建前進行前期準備"的重要性

3.2 辨明所從事的軟件的類型

  • 網站/遊戲:敏捷開發,隨需測試與QA計劃,設計與編碼結合,測試先行開發,開發者自測試,非正式部署
  • 在序列式和迭代法之間做出選擇,但前期準備不可忽略
    • 序列式:需求穩定,設計透徹,熟悉該領域,"長期可預測性"很重要,後期改變需求/設計/編碼代價高昂
    • 迭代法:需求不穩定,設計複雜有挑戰性,不熟悉該領域,"長期可預測性"不重要,改動代價小

3.3 問題定義的先決條件

  • 產品設想/問題定義 – 清楚的陳述系統要解決的問題,是構建前要滿足的先決條件
  • “未能定義問題” – 雙重懲罰:方向錯誤;浪費大量時間解決錯誤問題

3.4 需求的先決條件

  • 需求分析 – 詳細描述系統應該做什麼,在定義問題後對問題的深入調查
  • 爲什麼要有正式的需求: 充分詳盡地描述需求是項目成功的關鍵
    • 避免去猜測用戶想要的是什麼 – 需求明確用戶即可自行評審並覈准
    • 避免爭論,確定系統範圍 – “程序應該做什麼”
    • 減少改動,降低變更成本 – 需求變更將導致設計及相應的編碼和測試用例變更,發現越晚耗費越大
  • 穩定的需求是開發的聖盃,但"需求不會變更"只是美好的願望,要採取措施讓變更的負面影響最小化
  • 在構建期間處理需求變更
    • 使用覈對表評估需求的質量:若不夠好退回去做好再繼續前進
    • 確保每一個人都知道需求變更的代價:"進度"和"成本"這兩字比冰水更令人清醒
    • 建立一套變更控制程序:成立正式的委員會,評審提交上來的更改方案
    • 使用能適應變更的開發方法:演進原型(先探索好需求),演進交付(先建造小部分根據反饋調整再建造小部分)
    • 注意項目的商業案例:考慮決定所帶來的的商業影響
  • 功能需求覈對表
    • 是否詳細定義了系統的全部輸入/輸出及其格式,來源/目的地、精度、範圍、出現頻率等?
    • 是否詳細定義了所有硬件及軟件的外部(通信)接口,如握手、糾錯、協議等?
    • 是否列出了用戶想要做的全部事情?
  • 非功能(質量)需求覈對表
    • 是否爲全部必要的操作,從用戶的視角,詳細描述了期望響應時間(處理時間/數據傳輸率/系統吞吐量)?
    • 是否詳細定義了安全級別,可靠性,失靈的後果,故障時需要保護的關鍵信息,錯誤檢測及恢復的策略等?
    • 是否詳細定義了機器內存和剩餘磁盤空間的最小值?
    • 是否詳細定義了系統的可維護性,包括適應特定功能的變更、OS變更、與其他系統接口的變更能力?
  • 需求的質量覈對表
    • 需求是用用戶的語言書寫的嗎?用戶也這麼認爲嗎?
    • 每條需求都不與其他需求衝突嗎?
    • 是否詳細定義了競爭特性間的權衡–例如健壯性與正確性的權衡?
    • 是否避免在需求中規定設計(方案)?
    • 需求是否在詳細程度上保持相當一致的水平?哪些應該更詳細?哪些應該粗略些?
    • 需求是否清晰,即使轉交給另一小組構建也能理解嗎?開發者也這麼想嗎?
    • 每個條款都與待解決的問題及其解決方案相關嗎?能從每個條款追溯到它在問題域中對應的根源嗎?
    • 是否每條需求都是可測試的?是否可進行獨立的測試,以檢驗是否滿足各項需求?
    • 是否詳細描述了所有可能的對需求的改動,包括各項改動的可能性?
  • 需求的完備性覈對表
    • 對於開發前無法獲得的信息,是否詳細描述了信息不完全的區域?
    • 完備度是否達到該程度 – 如果軟件滿足所有需求,那麼它就是可接受的?
    • 你對全部需求都感到很舒服嗎?是否已去掉了那些不可實現的需求–只爲了安撫客戶/老闆的東西?

3.5 架構的先決條件

  • 系統架構/頂層設計 – 軟件設計的高層部分用於支撐更細節的設計的框架
  • 一個慎重考慮的架構爲"從頂層到底層維護系統的概念完整性"提供了必備的結構和體系,爲程序員提供了指引
  • 架構的典型組成部分
    • 程序組織:應概括的綜述相關係統,定義主要構造塊(子系統),明確其責任和它們間的通信規則
    • 主要的類:應詳細定義所用的主要類的責任、交互關係、繼承體系、持久化等,給出類設計方案的選用理由
    • 數據設計:應描述所用到的主要文件和數據表的設計,數據結構選用理由,數據庫高層組織結構和內容
    • 業務規則:如果結構依賴於特定的業務規則,就應詳細描述這些規則及其對系統的影響
    • 用戶界面設計:需求階段未做則應在架構中詳細說明,UI要對用戶友好,並模塊化便於更換或測試
    • 資源管理:應描述對稀缺資源(數據庫連接/線程等)的管理計劃,估算正常和極端情況的使用量
    • 安全性:應描述實現設計和代碼層面安全性的方法,建立威脅模型,處理緩衝區/非受信數據的規則/加密等
    • 性能:應詳細定義性能目標,可包括詳細定義資源(速度/內存/成本)間的優先順序
    • 可伸縮性:應描述如何應對用戶數、服務器數、網絡節點數、數據庫記錄數/長度等的增長
    • 互用性:如果會與其他軟件/硬件共享數據/資源,應描述如何完成這一任務
    • 本地化:是否支持多個地域/文化,如何封裝便於本地化操作
    • 輸入輸出:應詳細定義讀取策略是先做/後做還是即時做,以及在哪層檢測I/O錯誤
    • 錯誤處理:應清楚說明一種"一致地處理錯誤"的策略–僅檢測?主動被動?如何傳播?處理約定?如何校驗?
    • 容錯性:定義期望的容錯種類–容錯恢復/部分運轉/功能退化等
    • 過度工程:避免某些類異常健壯,而其他類勉強健壯的現象
    • 關於買還是造的決策:不使用開源/購買組件時應說明自己定製組件的優勢
    • 關於複用的決策:若要複用,應說明如何對已有庫加工使之符合其他架構目標
    • 變更策略:讓架構更靈活以適應可能的變化,列出可能變更或增強的功能
  • 架構的總體質量
    • 是否解決了全部需求?
    • 有沒有哪個部分"過度架構"或"欠架構"?是否明確宣佈了在這方面的預期指標?
    • 整個架構是否在概念上協調一致?
    • 頂層設計是否獨立於OS和語言?
    • 是否說明了所有主要決策的動機?
    • 作爲一名實現該系統的程序員,是否對該架構感覺良好?

3.6 花費在前期準備上的時間長度

  • 運作良好的項目會在需求、架構及其他前期計劃投入10%20%的工作量和20%30%的時間
  • 將需求定義足夠清晰,讓需求不穩定性對構建活動的負面影響降至最低

第5章 軟件構建中的設計

5.1 設計中的挑戰

  • 設計是一個險惡的問題:只有通過解決(部分)問題才能明確地定義它,然後再次解決,形成有效方案
  • 設計是個了無章法的過程(即使結果清晰):很容易誤入歧途,也很難判斷怎麼纔算"足夠好"
  • 設計就是確定取捨和調整順序的過程:衡量並盡力平衡競態的各設計特性,快速反應尤爲重要
  • 設計受到諸多限制:在有限資源的限制中,產生簡單方案,並最終改善它
  • 設計是一個不確定的、啓發式過程:充滿不確定性(條條道路通羅馬)和探索性,沒有什麼萬能藥
  • 設計是自然而然形成的:在不斷的評估、討論、試驗代碼、修改試驗代碼中演化和完善的

5.2 關鍵的設計概念

  • 軟件的首要技術使命:管理複雜度(本質性困難)
    • 管理複雜度的重要性:項目失敗多數由差強人意的需求、規劃和管理導致,若是技術因素,則通常來自失控的複雜度
    • 如何應對複雜度:1.把任何人在同一時間需要處理的本質複雜度的量減到最少;2.不要讓偶然性的複雜度無謂地快速增長
  • 理想的設計特徵
    • 最小的複雜度:設計應該簡單且易於理解,複雜問題應分拆成簡單的部分
    • 易於維護:時刻想着維護程序員可能對代碼提出的問題,將他們當成聽衆,進而設計出能自明的系統
    • 鬆散耦合:減少子系統間的依賴和關聯提升專注點
    • 可擴展性:能增強系統的功能而無須破壞其底層結構
    • 可重用性:系統組成部分能在其他系統中重複使用
    • 高扇入:大量類利用到了低層次上的工具類; 低扇出:少量適中的使用其他平行類
    • 精簡性:伏爾泰說,一本書的完成,不在它不能再加入任何內容的時候,而在不能再刪去任何內容的時候
    • 層次性:系統應該能在任意層次上觀察而無須進入其他層次
    • 標準技術:儘量引入標準化的、常見的方法庫或類
  • 設計的層次:
    1. 整個軟件系統
    2. 分解爲子系統或包(單元):確定各主要子系統(UI/數據庫/業務規則等),及其通信規則(簡化交互)儘量無環圖
    3. 分解爲類:對各子系統進行恰到好處的分解,並確保能用單個的類實現
    4. 分解成子程序:把類細分爲子程序,注意保持短小精悍意義清晰
    5. 子程序內部的設計:佈置詳細功能,包括編寫僞代碼、選擇算法、組織內部代碼塊以及編碼

5.3 設計構造塊:啓發式方法

  • 找出現實世界中的對象:
    • 辨識對象及其屬性
    • 定義可對對象執行的操作
    • 確定各個對象可以對其他對象進行的操作
    • 確定對象的哪些部分對其他對象可見 – 公有和私有
    • 定義每個對象的公開接口 – 暴露的數據和方法
  • 形成一致的抽象:關注某一概念的同時可放心忽略其中一些細節(在子程序、類、包接口層次抽象)
  • 封裝實現細節:抽象讓你"從高層細節來看待對象",而封裝讓你"無法深入對象的其他細節"
  • 當繼承能簡化設計時就繼承:能很好地輔佐抽象的概念
  • 信息隱藏:結構化和麪向對象設計的根基,能激發出有效的設計方案,養成問"該隱藏些什麼?"的習慣
    • 隱藏複雜度,無需應付它除非要特別關注的時候
    • 隱藏變化源,將變化的影響限制在局部範圍內,如布爾判斷/晦澀算法等
  • 找出容易改變的區域:找出→分離→隔離在類內部
    • 業務規則; 對硬件的依賴性; 輸入和輸出; 非標準的語言特性; 困難的設計區域和構建區域; 狀態變量
  • 保持鬆散耦合: 規模(模塊間的連接數是否小而美); 可見性(連接顯著程度); 靈活性(連接是否容易改動)
    • 簡單數據參數耦合:模塊間通過參數(簡單數據類型)傳遞數據,正常耦合可接受
    • 簡單對象耦合:一個模塊實例化一個對象,還不錯的耦合關係
    • 對象參數耦合:Obj1Obj2傳給它一個Obj3,耦合較緊密
    • 語義上的耦合:一個模塊不僅使用了另一模塊的語法元素還使用了模塊內部工作細節的語義知識
  • 查閱常用的設計模式:提供現成抽象減少複雜度; 方案制度化減少出錯; 帶來啓發性; 把設計提升到更高層次簡化交流
    • 抽象工廠 – 通過指定對象組的種類而非單個對象的類型來支持創建一組相關對象
    • 適配器 – 把一個類的接口轉變成爲另一個接口
    • 橋接 – 把接口和實現分離開來,使它們可以獨立地變化
    • 組合 – 創建包含一組同類對象的對象,使得只需與最上層對象交互而無須考慮所有的細節對象
    • 裝飾器 – 給一個對象動態地添加職責,而無須爲每種可能的職責配置情況去創建特定的子類(派生類)
    • 外觀 – 爲沒有提供一致接口的代碼提供一個一致的接口
    • 工廠方法 – 實例化特定基類的派生類時,無須在除了Factor Method內部之外瞭解各派生對象的具體類型
    • 迭代器 – 提供一個服務對象來順序的訪問一組元素中的各個元素
    • 觀察者 – 爲了使A組相關對象相互同步,由另一對象負責廣播A組任一對象的變化
    • 單件 – 保證有且僅有一個類的實例,並提供全局訪問的功能
    • 策略 – 定義一組算法或行爲,使得它們可以動態地相互替換
    • 模板方法 – 定義一個操作的算法結構,但把部分實現的細節留給子類(派生類),如抽象虛方法
  • 高內聚性; 構造分層結構; 嚴格描述類契約(接口); 分配職責; 爲測試而設計; 避免失誤; 選擇好數據的綁定時間; 創建中央控制點; 畫個圖; 保持設計的模塊化(類似黑盒子)

5.4 設計實踐

  • 迭代:多次嘗試,同時從高層和底層的不同視角去審視問題 – 理解問題→形成計劃→執行計劃→回顧做法
  • 分而治之:將複雜問題分解爲不同的關注區域再分別處理
  • 自上而下(分解)和自下而上(合成)的設計方法
  • 建立試驗性原型:寫出用於回答特定設計問題、量最少且隨時可拋棄的代碼
  • 合作設計:三個臭皮匠頂個諸葛亮,推薦高度結構化的檢查實踐(正式檢察)
  • 要做多少設計纔夠:將80%的設計精力用於創建和探索大量備選方案,而20%精力用於創建不怎麼精美的文檔
  • 記錄你的設計成果:嵌入到代碼; Wiki記錄設計討論和決策; 寫總結郵件; 數碼拍照; 在適當的細節層創建UML圖

第6章 可以工作的類

  • 在開發任一部分的代碼時,都能安全地忽視程序中儘可能多的其餘部分 – 類是實現該目標的首要工具

6.1 類的基礎:抽象數據類型(ADTs)

  • ADT – 數據集與相關操作的集合,是構成"類/class"概念的基礎
  • 使用ADT的益處:隱藏細節; 降低改動影響; 更豐富的接口信息; 更易優化性能; 更顯而易見的正確性; 更具自我說明性; 無須在程序內到處傳遞數據; 你可以像在現實世界中一樣操作實體,而不必在低層的實現上擺弄實體
  • 把常見的底層數據類型創建爲ADT並使用它們,而不再使用底層數據類型
  • 把像文件這樣的常用對象當成ADT,並採用類似(磁盤-文件)的做法對ADT分層
  • 簡單的事物也可當做ADT,提高代碼的自我說明能力,也更易於修改
  • 不要讓ADT依賴於其存儲介質(磁盤/內存)

6.2 良好的類接口

  • 好的抽象:
    • 類的接口應展現出一致的抽象層次:實現並且僅實現一個ADT
    • 一定要理解類所實現的抽象是什麼:一些類非常相似,仔細地理解類的接口應該捕捉的抽象到底是哪一個
    • 提供成對的服務:檢查並決定是否爲某個操作提供另一互補操作(如開→關)
    • 把不相關的信息轉移到其他類中
    • 儘可能讓接口可編程(編譯時可檢查的數據類型和其他屬性),而不是表達語義(會被怎樣使用)
    • 謹防在修改時破壞接口清晰的抽象; 不要添加與接口抽象不一致的公用成員
    • 同時考慮抽象性和內聚性:關注類接口表現出來的抽象更易於深入理解類的設計
  • 良好的封裝:
    • 儘可能地限制類和成員的可訪問性:避免公開成員數據,少用protected降低派生類和基類間的耦合
    • 避免把私用的實現細節放入類的接口中:私有數據使用指針指向創建的局部數據類
    • 避免使用友元類
    • 要格外警惕從語義上破壞封裝性
    • 不要對類的使用者做出任何假設
    • 不要因爲一個子程序裏僅僅只用了公用子程序就將其歸入公開接口,而應考慮暴露後展現的抽象是否一致
    • 提高代碼可讀性比加快編寫代碼更重要

6.3 有關設計和實現的問題

  • 包含(“has a…” 的關係)
    • 通過包含來實現"有一個/has a"的關係,如類數據成員
    • 萬不得已時通過private繼承來實現,主要原因是讓外層的包含類可訪問內層被包含類的protected成員
    • 警惕超過7個數據成員的類,7±2是人們在做其他事情時能記住的離散項目個數
  • 繼承(“is a…” 的關係)
    • 繼承用於表明派生類是基類的一個更爲特殊的版本,要麼使用以進行詳細說明,否則不使用
    • 遵循Liskov替換原則 – 派生類必須能通過基類的接口而被使用,且使用者無須瞭解兩者間的差異
    • 確保只繼承需要繼承的部分,考慮是否提供方法的默認實現(可覆蓋),只需要類的實現應採用包含方式
    • 避免派生類的方法與基類中不可覆蓋的方法重名
    • 把公用的接口、數據及操作放在繼承樹中儘可能高的位置
    • 只有一個實例的類考慮下能否創建一個新的對象而非新的類,單例模式除外
    • 只有一個派生類的基類也值得懷疑 – 避免"提前設計",不要創建任何非絕對必要的繼承結構
    • 派生後覆蓋了某個子程序,但其中無任何操作,這也值得懷疑,考慮其他實現方式如包含
    • 避免過深的繼承體系
    • 儘量使用多態,避免大量的類型檢查
  • 成員函數和數據成員
    • 儘量減少類中的子程序數量,減少類所調用的不同子程序的數量,減少對其他類子程序的間接調用
    • 禁止隱式地產生不需要的成員函數和運算符
    • 儘量減小類和類之間相互合作的範圍
  • 構造函數
    • 應在所有的構造函數中初始化所有的數據成員
    • private構造函數來強制實現單例屬性(singleton property)
    • 優先採用深層複本(deep copies),除非論證可行,才採用淺層複本(shallow copies)

6.4 創建類的理由

  • 爲現實世界中的對象建模;爲抽象的對象建模
  • 降低/隔離複雜度,隱藏實現細節,限制變化所影響的範圍
  • 隱藏全局數據 – 摒棄直接訪問,通過訪問子程序來操控全局數據
  • 讓參數傳遞更順暢 – 大量數據到處傳遞暗示換一種類的組織方式可能更好
  • 建立中心控制點;把相關操作放一起 – 集中控制諸如文件、數據庫連接等
  • 讓代碼易於重用 – 將代碼放入精心分解的一組類中,比塞進更大的類更容易被重用
  • 爲程序族(family of programs)做計劃 – 考慮整個程序族的可能情況,而非單一程序的可能情況
  • 實現特定的重構 – 比如單個類轉換爲多個、隱藏委託、去掉中間人以及引入擴展類等

6.5 與具體編程語言相關的問題

  • 注意構造/析構函數的行爲:1.在繼承層次中被覆蓋時; 2.在異常處理時
  • 默認構造函數(無參數)的重要性
  • 析構函數或終結器(finalizer)的調用時機
  • 覆蓋語言內置運算符(包括賦值和等號)相關的知識
  • 當對象被創建和銷燬時,或當其被聲明時,或其所在作用域退出時,處理內存的方式

6.6 超越類:包

  • 遵循下列標準來強制實施自己創建的包:
    • 命名規則區分"公用類"和"某個包的私用類"
    • 命名規則和/或代碼組織規則(即項目結構)區分每個類所屬的包
    • 規定什麼包可以用其他什麼包的規則,包括是否可以用繼承和/或包含等

第9章 僞代碼編程過程(PPP)

  • 關注創建單獨的類及其子程序的特定步驟
  • PPP有助於減少設計和編寫文檔所需的工作量,並提高它們的質量

9.1 創建類和子程序的步驟概述

  • 創建一個類的步驟:
    1. 創建類的總體設計 – 定義職責、隱藏祕密、抽象接口、派生關係、指出公用方法、設計重要數據成員
    2. 創建類中的子程序 – 根據設計創建,並引出更多或重要、或次要子程序,再反過來影響類的設計
    3. 複審並測試整個類 – 除開子程序創建經過的測試,再對整體複查和測試
  • 創建子程序的步驟:設計、檢查設計、編寫代碼、檢查代碼直至完成

9.2 僞代碼

  • 使底層設計的評審更容易,並減少對代碼本身進行評審的需要
  • 支持反覆迭代精化的思想:高層設計 → 僞代碼 → 源碼,層次清晰更易於檢查各層次錯誤
  • 使變更更加容易 – 在產品最可塑階段進行變更
  • 使給代碼做註釋的工作量減到最少
  • 比其他形式的設計文檔更易維護

9.3 通過僞代碼編程過程創建子程序

  • 檢查先決條件 – 是否功能清晰,匹配整體設計,是否必需
  • 詳細定義要解決的問題 – 隱藏的細節、輸入、輸出、前後條件(如文件打開和關閉)
  • 高層次的設計是否足夠清晰?能命名好這個類中的每一個子程序嗎?
  • 考慮過該如何測試這個類及其中每一個子程序了嗎?
  • 關於效率,主要是從穩定的接口和可讀的實現這考慮?還是滿足資源和速度的預期目標?
  • 在標準函數庫或其他代碼庫中尋找過可用的子程序或組件了嗎?
  • 在參考資料中查找過有用的算法了嗎?
  • 是否用詳盡的僞代碼設計好每一個子程序?
  • 已在腦海裏檢查過僞代碼麼?它們容易被理解麼?
  • 關注過那些可能會讓你重返設計的警告信息了嗎?(比如全局數據、一些更適合放在其他類的操作等)
  • 是否把僞代碼正確地翻譯成代碼了?刪除冗餘的註釋了嗎?
  • 在做出假定的時候有沒有對它們加以說明?
  • 是否採取了幾次迭代中最好的結果?還是在第一次迭代後就停止了?
  • 你完全理解你的代碼了嗎?這些代碼是否容易理解?

第10章 使用變量的一般事項

  • 變量在此同時指代對象和內置數據類型

10.2 輕鬆掌握變量定義

  • 關閉隱式聲明
  • 聲明全部的變量
  • 遵循某種命名規則並檢查

10.3 變量初始化原則

  • 就近原則 – 在靠近第一次使用變量的位置聲明並初始化;在類的構造函數裏初始化數據成員
  • 檢查是否需要重新初始化 – 計數器和累加器;需重複執行的代碼裏的變量
  • 利用編譯器的警告信息(建議啓用所有警告選項)
  • 檢查輸入參數的正確性
  • 使用內存訪問檢查工具來檢查錯誤的指針; 在程序開始時初始化工作內存

10.x 使用數據的其他事項

  • 儘量縮小變量的作用域(存活時間)
  • 儘量局部化(集中)各變量的引用點
  • 控制結構符合數據類型嗎? – 序列型、選擇型、迭代型
  • 每個變量只用於單一用途,避免具有隱含含義,確保用到所有聲明的變量

第12章 基本數據類型

  • 數值概論
    • 避免使用"魔數" - 用具名常量/全局變量替代程序中未經解釋的字面數值(非0/1)
    • 顯式的類型轉換; 避免混合類型的比較
    • 預防除零錯誤; 注意編譯器警告
  • 整數 – 檢查整數除法、整數溢出、中間結果溢出
  • 浮點數 – 舍入問題:避免對數量級相差巨大的數加減; 避免等量判斷;使用特定數據類型如currency
  • 字符和字符串
    • 避免使用神祕字符/串 – 字面字符不易修改、本地化難、含義模糊
    • 避免off-by-one錯誤 – 下標索引超出
    • 瞭解開發環境如何支持Unicode; 儘早決定本地化策略
    • 採用某種一致的字符串類型轉換策略 – 比如都保存爲一種格式
  • C-style字符串
    • 注意字符串指針和字符數組的差異 – 警惕等號賦值表達式; 通過命名規則加以區分; 數組取代指針
    • 把C-style字符串的長度聲明爲CONSTANT+1 – 長度n字符串需n+1(結尾空結束符)字節內存
    • null(0)初始化字符串以避免沒有結束符0的字符串; strncpy()取代stccpy
  • 布爾變量 – 使用額外的布爾變量來說明、簡化複雜的布爾表達式
  • 枚舉類型
    • 用枚舉來提高可讀性、可靠性、可修改性;
    • 用枚舉替代布爾變量,擴展程序表達含義
    • 務必在選擇語句中檢查非法數值
    • 將枚舉首個元素留作非法值; 警惕枚舉元素明確賦值而帶來的失誤
  • 具名常量 – 名字替代字面量,單點控制方便修改; 避免混用具名常量和字面量
  • 數組 – 務必檢查邊界、多維數組下標是否正確; 用容器替代需要隨機訪問的數組
  • 類型別名 – 對每種可能變化的數據分別採用不同的類型(如字符串或數組)
    • 原因:便於修改; 避免過多的信息分發; 增加可靠性
    • 指導原則:取現實實體導向的名字; 避免使用/重定義預定義類型; 考慮創建新類替代增加靈活度

第13章 不常見的數據類型

13.1 結構體

  • 使用理由:明確數據關係; 簡化對數據塊的操作; 簡化參數列表; 語句與整體聯繫而非個體,減少維護

13.2 指針

  • 指針包含:內存中的某處位置,以及如何解釋該位置中的內容
  • 使用技巧:
    • 簡化複雜的指針表達式,集中實現指針操作將其限制在子程序或類裏面
    • 聲明的同時定義指針,且避免一個指針變量多種用途,在分配的相同作用域釋放
    • 使用前檢查指針及其所引用的變量;
    • 刪除前做非NULL檢查,並用垃圾數據覆蓋指向的內存區域,刪除後置爲NULL
    • 按照正確的順序刪除鏈表中的指針; 畫個圖解釋複雜指針操作
    • 跟蹤分配情況 – 維護一份已分配指針列表,使用或釋放校驗有效性
  • C++指針:
    • 理解指針和引用的區別 – 引用必須總是引用一個對象,而指針可指向空值
    • 引用傳參避免大對象拷貝,const引用可避免參數對象被修改
    • 使用auto_ptr – 離開作用域時會自動釋放內存
    • 靈活運用智能指針 – 針對資源管理、拷貝、複製、對象構造和析構提供更多的控制

13.3 全局數據

  • 常見問題:無意間的修改; 別名; 代碼重入; 阻礙代碼重用; 初始化順序事宜; 破壞模塊化和可管理性
  • 使用理由:保存全局數值; 簡化對極其常用的數據的使用; 清除流浪數據
  • 用訪問子程序取代全局數據:
    • 優勢:集中控制數據; 保護變量的所有引用; 隱藏信息; 易於抽象(如if l>ml轉爲if PageFull())
    • 方法:要求不可直接訪問全局數據; 歸類; 鎖定控制訪問; 構建抽象層; 將訪問統一在同一抽象層
  • 降低風險: 命名規則(g_前綴)明確標識; 創建良好註釋; 避免存放中間結果; 避免堆砌到大對象中

第14章 組織直線型代碼

14.1 必須有明確順序的語句

  • 組織代碼順序來明確; 子程序名突顯; 利用參數明確; 註釋說明; 用斷言或錯誤處理檢查

14.2 順序無關的語句

  • 使代碼易於自上而下地閱讀; 相關的語句組織在一起; 相對獨立的語句放進各自的子程序

第15章 使用條件語句

15.1 if語句

  • 首先處理正常/常見情況(if之後),再處理不常見情況(else之後)
  • 確保等量分支的正確性,測試else子句的正確性,檢查ifelse是否弄反
  • 確保所有情況都考慮到了
  • if子句後跟隨一個有意義的語句
  • 利用布爾函數簡化複雜的檢測

15.2 case語句

  • 選擇最有效的排列順序: 正常情況放在前面; 按執行頻率排列; 按字母/數字順序排列
  • 使用訣竅:簡化情況處理代碼; 避免爲使用case而刻意製造一個變量; default只檢查真正的默認/錯誤情況

第16章 控制循環

  • 在合適的情況下用while取代for循環
  • 由內到外創建循環
  • 內務處理(如i++這類表達式)集中放在循環開始/結束處
  • 初始化代碼直接位於循環前,從循環頭部進入循環
  • 避免空循環,保持結構清晰,嵌套不多於3層,最好短的一目瞭然(長循環內容提取成單獨子程序)
  • 避免for循環內修改下標值,需在循環外使用的下標保存至另外的變量裏
  • 循環下標使用有意義的名字,避免使用非序數(整數/枚舉)類型,檢查是否串話
  • 保證所有可能條件都能終止循環; 保證退出條件清晰明瞭; 使用安全計數器

第17章 不常見的控制結構

  • 子程序中的return: 僅在必要時使用(如增強可讀性時); 早返回以簡化複雜的錯誤處理
  • 遞歸: 確保能停止(如安全計數器); 限制在一個子程序; 留心棧空間; 不用於計算階乘或斐波那契數列
  • goto: 避免使用 – 用狀態變量或try-finally替代; 提取方法到子程序簡化代碼使邏輯清晰

第19章 一般控制問題

19.1 布爾表達式

  • 僅用truefalse而非0和1做判斷,且隱式地比較(使讀起來類似英語中的對話)
  • 簡化複雜的表達式: 拆分並引入新的可讀布爾變量; 用清晰的布爾函數替代; 用決策表代替複雜條件
  • 使用肯定形式的表達式,用狄摩根定理簡化否定判斷,如not A and not Bnot (A or B)
  • 用括號使表達式更清晰
  • 理解布爾表達式是如何求值的 – 如"短路"或"惰性"求值,並利用這些特性
  • 按照數軸的順序寫數值表達式: 如(min≤i)and(i≤max); 單項比較則根據判斷成功後i的位置定

19.2 複合語句(語句塊)

  • 括號成對寫出; 用括號把條件表達清楚

19.3 空語句

  • 小心使用; 替換爲DoNothing()預處理宏或內聯函數; 空循環體轉換爲非空語句

19.4 馴服危險的深層嵌套

  • 嵌套if轉換成一組if-then-elsecase語句
  • 將深層嵌套的代碼提取出來放進單獨的子程序
  • 使用對象和多態特性
  • 視深層嵌套爲警告,重新設計這部分代碼

19.5 編程基礎:結構化編程

  • 三個組成部分: 順序 – 先後按序執行的; 選擇 – 有選擇的執行; 迭代 – 多次執行

第20章 軟件質量概述

20.1 軟件質量的特性

  • 外在特性: 完整性; 正確性; 可靠性; 健壯性; 精確性; 可用性; 適應性; 效率;
  • 內在特性: 可讀性; 可維護性; 可重用性; 靈活性; 可移植性; 可測試性;

20.2 改善軟件質量的技術

  • 要素: 目標; 明確定義質量保證工作; 測試策略; 軟件工程指南; 非/正式技術複審; 外部審查
  • 開發過程: 對變更(需求、設計、代碼)進行控制的過程; 結果的量化; 製作原型(UI、算法、典型數據集)
  • 設置目標: 明確的質量目標效用巨大(性能效率、可讀性、資源佔用等)

20.3 不同質量保障技術的相對效能

  • 缺陷檢出率: 設計複查-55%; 代碼複查-60%; 原型-65%; 單元測試-30%; 集成測試-35%; 迴歸測試-25%
  • 實驗證明 – 混合使用多種缺陷檢測方法,效果遠好於某種方法單打獨鬥
  • 極限編程 – 設計複查 + 代碼複查 + 個人代碼檢查 + 單元測試 + 集成測試 + 迴歸測試 ≈ 90-97%
  • 檢出缺陷代碼複查(review)比測試(test)高出80%,修正效益review高出test好幾倍

20.4 什麼時候進行質量保證工作

  • 需求 → 架構設計 → 類 → 子程序,在早期階段(需求設計時)就應該強調質量保障工作

20.5 軟件質量的普遍原理

  • 改善質量以降低開發成本 - 只要避免引入錯誤,就可減少調試時間,從而提高生產效率 – “三思而後行”

第21章 協同構建

21.1 協同開發實踐概要

  • 包含結對編程、正式檢察、非正式技術複查、文檔閱讀,以及其他讓開發人員共同擔責的技術
  • 協同構建是其他質量保證技術的補充 – 結對編程成本高10~25%開發週期縮短約45%; 正式review三倍效能
  • 協同構建有利於傳授公司文化以及編程專業知識 – review提供技術交流同時提高水平,加快新人培養
  • 集體所有權適用於所有形式的協同構建 – 代碼屬於團隊,結合正式和非正式複審,輪換指派修正缺陷,結對
  • 在構建前後都應保持協作 – 協同構建思想適用與評估、計劃、需求、架構、測試以及維護工作等階段

21.2 結對編程

  • 成功關鍵:
    • 統一編碼規範 – 避免爭論代碼風格
    • 確認都能看清顯示器,結對而非旁觀 – 非編碼者應分析代碼、提前思考後續、評估設計、做測試計劃
    • 不強迫在簡單問題上結對 – 解決複雜問題時,一起白板畫15分鐘,更有利
    • 規律輪換人員和任務 – 讓不同人熟悉系統的不同部分,也助於知識互相傳播
    • 鼓勵雙方步伐一致 – 速度太快的人應放慢步伐
    • 避免關係緊張或新手組合 – 對個性匹配問題保持警覺
    • 指定一個組長 – 指定組長協調工作的分配,對結果負責以及與項目外人員的聯繫

21.3 正式檢查

  • 詳查 – 集中於曾經產生過問題的領域; 備好覈對表; 制定視角或場景明確賦予參與者角色; 專注於檢測而非修正; 主持人不是被檢查產品作者; 主持人應接受過詳查會議相關培訓; 只在與會者充分準備後開啓會議; 每次詳查收集的數據要用於之後的會議以改進; 高層管理者不參加詳查會議除非是詳查項目計劃;
  • 詳查優勢 – 獨立的詳查能捕獲60%缺陷,設計和代碼聯合詳查將達到70~85%; 提高約20%生產效率
  • 詳查人員角色:
    • 主持人 – 保證會議速度,分派複查任務,分發覈對表,預定會議室,報告結果,跟蹤任務結果
    • 作者 – 向評論員陳述概況,解釋不清晰的部分
    • 評論員 – 測試或架構師也可參與,負責找出缺陷
    • 記錄員 – 記錄發現的錯誤,以及指派的任務
    • 經理 – 避免出現,詳查更關注純技術相關,而非評估
  • 詳查一般步驟:
    1. 計劃 – 作者提交代碼給主持人 → 決定參與人員 → 預定會議 → 分發覈對表
    2. 概述 – 評論員不熟悉項目時,作者用大約1h描述技術背景
    3. 準備 – 評論員獨立詳查,約500行/h,儘量賦予特定視角(維護/客戶/設計師)
    4. 詳查會議 – 挑選人員闡述,包括所有邏輯分支,確認錯誤時停止並記錄,避免太快或太慢,避免討論解決方案,集中在識別缺陷上,避免超過2h
    5. 詳查報告 – email,列出每個缺陷及其類型和嚴重級別; 收集數據增強覈對表
    6. 返工 – 主持人分配缺陷給某人修復
    7. 跟進 – 主持人負責監督返工任務,讓評論員詳查整個或部分工作成果
    8. 第三個小時的會議 – 組織感興趣的人,正式詳查結束後討論解決方案
  • 細調詳查 – "丟掉書本"主持,精雕細琢,建立並改進覈對表,讓人對常見錯誤保持警惕
  • 詳查中的自尊心 – 發現缺陷而非探索方案,避免爭論對錯,避免批評設計或代碼,讓作者負責決定如何處理

21.4 其他類型的協同開發實踐

  • 走查 – 焦點在技術事宜(工作會議),參與者已閱讀並準備從中找出錯誤,30~60分鐘;更隨意
  • 代碼閱讀 – 兩到三人,獨立閱讀(1K10K行,1K/天),集中在發現的問題上討論(12h)

第22章 開發者測試

22.1 開發者測試在軟件質量中的角色

  • 避免混淆測試和調試 – 測試是爲了檢查出錯誤; 調試是找到錯誤後修正
  • 構建中測試 – 腦袋裏先過一遍 → 複查代碼 → 單元測試(玻璃盒測試) → 良好測試覆蓋率

22.2 推薦的開發者測試方法

  • 測試每一項相關需求,確保都已實現 – 需求階段就計劃好測試用例(常見如安全、數據存儲、可靠性等)
  • 測試每一個設計關注點,確保設計已實現 – 設計階段就計劃好
  • 用基礎測試擴充測試用例,增加數據流測試
  • 使用檢查表(一個記錄你迄今已犯錯誤的列表)
  • 先行編寫測試用例 – 更早發現缺陷從而更易修正; 迫使思考需求和設計提高質量; 更早暴露需求問題
  • 不減少乾淨測試(檢驗代碼能否工作)的前提下,使骯髒測試(儘可能讓代碼失效)至少爲其5倍量
  • 100%分支覆蓋率,好於100%語句覆蓋率

22.3 測試技巧錦囊

  • 不完整的測試 – 集中精神挑選出最可能找到錯誤的測試用例
  • 結構化的基礎測試(控制流) – 以最小數量的測試用例覆蓋所有路徑(注意那些導致用例數量增加的關鍵字)
  • 數據流測試 – 三種狀態(已初始化、已使用、已銷燬); 搭配不同狀態來測試,大麻煩時列出組合表詳查
  • 等價類劃分 – 若多個用例能揭示的錯誤完全相同,只保留一個
  • 猜測錯誤 – 基於直覺或經驗猜測可能出錯的地方創建測試用例
  • 邊界值分析 – off-by-one剛好差一點; 允許的最大最小值
  • 幾類壞數據 – 數據太多/太少/無; 錯誤/無效數據; 長度錯誤的數據; 未初始化的數據
  • 幾類好數據 – 正常期望值(正中間); 最小/大的正常局面; 與舊數據的兼容性
  • 採用容易手工檢查比對的測試用例

22.4 典型錯誤

  • 哪些類包含最多的錯誤 – 80%錯誤存在於20%的類或子程序中; 避免維護惹人厭的子程序而應修改重寫
  • 錯誤的分類 – 結構(25%) 數據(22%) 已實現功能(16%) 構建(9%) 集成(9%) 功能需求(8%)
    • 85%錯誤易於修正,或可在不修改一個子程序的範圍內得以修正
    • 許多錯誤發生在構建範疇外: 缺乏領域知識、頻繁變動或矛盾的需求、溝通和協調的失效
    • 大多數的構建期錯誤源於編程人員的失誤(或筆誤),或對設計的錯誤理解
  • 測試本身的錯誤

22.5 測試支持工具

  • 爲測試各個類構造腳手架 – 使用JUnit等工具
  • Diff工具 – 自動比對實際輸出與期望輸出的工具
  • 測試數據隨機生成器
  • 覆蓋率監視器
  • 數據/日誌記錄器 – 監控並收集程序狀態信息,如Eurekalog
  • 符號調試器: 單步調試,彙編生成,寄存器和堆棧情況等
  • 系統干擾器: 內存填充,內存抖動,選擇性內存失敗,內存訪問(邊界)檢查
  • 錯誤數據庫

22.6 改善測試過程

  • 有計劃的測試 – 項目開始之初便擬定測試計劃
  • 重新測試(迴歸測試) – 確保修改未引入任何新的錯誤
  • 自動化測試 – 迴歸測試的有力支撐,生成輸入、捕獲輸出、比對預期

22.7 保留測試記錄

  • 收集下列數據,細加思考,判斷項目是向着更健康還是更糟糕的趨勢發展
    • 缺陷的管理方面描述(報告日期、報告人、描述或標題、生成編號、修正日期)
    • 缺陷的分類,完整描述,復現所需步驟,避免/繞開問題的建議,相關的缺陷
    • 問題嚴重程度(致命、嚴重、表面)
    • 缺陷根源:需求、設計、編碼還是測試
    • 修正所改變的類和子程序
    • 查找、修正所花時間(小時)

第23章 調試

23.1 調試概述

  • 程序中的error提供了絕好的學習機會:
    • 理解你正在編寫的程序
    • 明確你犯了哪種類型的錯誤: 問問爲什麼、如何更快的發現、如何預防、是否有類似錯誤、主動修正了麼?
    • 以代碼閱讀者的角度分析代碼質量: 易讀麼? 怎樣才能更好? 用你的結論重構現在的代碼
    • 審視分析改進自己解決問題的方法: 管用麼? 效率高麼? 感到痛苦和挫敗感麼? 是胡亂猜測麼? 需要改進麼?
    • 審視自己修正缺陷的方法: 治標還是治本? 精確分析根本原因了嗎?

23.2 尋找缺陷

  • 科學的調試方法
    1. 讓缺陷可穩定地重現: 若無法重現通常因爲初始化; 簡化測試用例
    2. 確定錯誤的來源 – a.收集產生缺陷的相關數據; b.分析並假設; c.證僞(測試或review) d.做出結論
      • 採用不同方法重現錯誤,頭腦風暴可能的假設,做假設時要考慮所有可用數據,
      • 提煉產生錯誤的測試用例: 不侷限於假設,更多更大範圍的數據
      • 縮小嫌疑代碼範圍: 關注更小的部分–unit-test;增量式集成(部分添加代碼易提取測試)
      • 利用可用的工具: 交互式調試器; 完美主義型編譯器; 內存檢查工具等
      • 逐條列出待嘗試方案,避免死衚衕
      • 優先檢查最近修改過的代碼,檢查常見或同類缺陷,警惕出現過問題的類和子程序
      • 同其他人討論問題: 解釋代碼更易發現自己犯的錯
      • 休息一下,放空大腦
      • 蠻力調試: 列出方法列表,設定時間上限,摒棄調試一堆垃圾而着手重寫
      • 語法錯誤: 不要過分信任編譯器給出的行號/信息,尤其第二條錯誤; 分而治之;
    3. 修補缺陷,並進行測試
    4. 查找是否還有類似錯誤

23.3 修正缺陷

  • 動手前先要理解問題 – 直到每次都能正確地預測出運行結果爲止
  • 理解程序本身,而不僅僅是問題 – 掌控全局,治本而非治標
  • 驗證對錯誤的分析 – 證明假設,證僞假設的逆命題,排除其他因素
  • 放鬆一下 – 匆忙動手最爲低效,要避免壓力下隨機武斷的判定
  • 保留最初的源碼(版本控制),一次只做一個改動
  • 檢查自己的改動,增加能暴露問題的單元測試
  • 搜索類似的缺陷

23.4 調試中的心理因素

  • “心理取向"導致調試時的盲目 – 因此良好的編程習慣將突顯錯誤; 規範的尋找缺陷方法避免"調試盲區”
  • “心理距離”(辨識不同事物的難易程度)在調試中的作用 – 爲變量或子程序使用差異較大的名字(尤其首字母)

23.5 調試工具–明顯的和不那麼明顯的

  • 源代碼比較工具 – 比較差異,喚醒記憶
  • 編譯器警告信息 – 不放過任何警告,並以對待錯誤的態度來處理警告; 項目組範圍統一編譯設置
  • 增強的語法和邏輯檢查 – Lint工具
  • 執行性能剖測器
  • 測試框架/腳手架

第24章 重構

24.1 軟件演化的類型

  • 區分演化類型的關鍵: 1.程序質量提高還是降低了; 2.源於構建還是維護過程中的修改
  • 軟件演化的哲學: 要意識到演化無法避免且意義重要,需細加謀劃;一旦有機會重新審視代碼就用全力改進

24.2 重構簡介

  • 重構的理由:
    • 代碼重複 – 複製粘貼即設計之謬
    • 冗長的子程序 – 提升模塊性,讓子程序各司其職
    • 循環太長或嵌套過深 – 分解代碼,減少複雜性
    • 類的內聚性太差 – 某個類有許多獨立無關任務,或接口未能提供層次一致的抽象,拆分它
    • 參數表中參數太多 – 警示抽象未經斟酌
    • 變化導致要對多個類/繼承體系/case語句並行修改
    • 相關數據項常被同時使用,沒有組織到類中
    • 某個類/成員函數同其他類關係過於親密
    • 過多使用基本數據類型
    • 某個類/中間人對象無所事事
    • 一系列傳遞流浪(僅傳不用)數據/命名不當的子程序
    • 公用的數據成員或全局變量 – 將其隱藏在訪問器子程序背後
    • 某個派生類僅用了基類的很少一部分成員函數
    • 註釋被用於解釋難懂的代碼 – “不要爲拙劣的代碼編寫文檔——應當重寫代碼”
    • 在子程序調用前使用了設置代碼,調用後使用了收尾代碼 – 將其視爲警告
    • 某些代碼似乎將來某刻會用到 – "超前設計"往往是畫蛇添足
  • 拒絕重構的理由: 重構不是修改的同義詞,需要深思熟慮遵循規範恰如其分

24.3 特定的重構

  • 數據級重構:
    • 具名常量代替魔數,清晰明瞭的變量命名
    • 將多用途變量轉換爲多個單一用途的變量
    • 使用局部變量實現局部用途而不是使用參數
    • 將基礎數據類型轉化爲類/類型別名
    • 將一組類型碼轉化爲類/枚舉/繼承體系類
    • 避免一個類返回多個羣集實例(collection)
  • 語句級重構:
    • 複雜的布爾表達式引入中間變量簡化,或轉換爲布爾函數
    • 合併條件語句不同分支中的重複代碼片段
    • 使用breakreturn而不是循環控制變量
    • 在嵌套的if-then-else語句中一旦知道答案就立刻退出,而不是僅僅賦一個返回值
    • 用多態替代條件語句(尤其是重複的case語句)
    • 創建並使用空對象代替空值的檢測
  • 子程序級重構:
    • 提取/內聯子程序
    • 將冗長的子程序轉換爲類
    • 用簡單算法替代複雜算法
    • 需要時增加參數,不使用時刪除參數
    • 將查詢操作從修改操作中區分開來
    • 合併功能相似的子程序,通過參數來區分
    • 分拆根據輸入參數執行不同代碼的子程序
    • 根據情況傳遞整個對象/特定成員
    • 封裝向下轉型的操作 – 尤其適用於迭代器、羣集、羣集元素等
  • 類實現的重構
    • 視情況使用值對象或引用對象
    • 用數據初始化替代虛函數
    • 上/下移成員數據/函數的位置
    • 將特殊代碼提出生成派生類
    • 將相似代碼合併放到基類中
  • 類接口的重構
    • 將單個具備多種截然不同功能的類拆分爲多個
    • 刪除無所事事的類並將代碼移到關係密切的類中
    • 視情況去掉/使用委託或繼承
    • 引入外部的成員函數或擴展類
    • 封裝暴露在外的成員變量,刪除不可修改成員的Set函數
    • 隱藏/封裝不會在類外部被用到的成員函數
    • 合併相似實現的基類和派生類
  • 系統級重構
    • 爲無法控制的數據創建明確的索引源
    • 視情況使用在單向或雙向的類聯繫
    • 用工廠模式而非簡單地構造函數
    • 視情況使用異常或錯誤處理代碼

24.4 安全的重構

  • 使用版本控制比如分支備份初始代碼
  • 放小重構的步伐,且同一時間只做一項重構
  • 一條條列出要做的事情(重構列表),保持思路連貫
  • 設置一個停車場 – 列出需要在未來某個時間進行而現在可以放一邊的修改工作
  • 多使用檢查點,利用好編譯器警告信息
  • 重新測試,需要時增加/刪除測試用例
  • 檢查對代碼的修改 – 像對待複雜修改一樣對待簡單修改
  • 根據重構風險級別來調整 – 類、成員函數接口、數據庫構架等改變極具風險,謹慎處理
  • 避免重構代替重寫 – 幹掉爛代碼,重新設計和開發

24.5 重構策略

  • 在增加子程序時重構 – 檢查相關子程序是否都被合理組織起來了
  • 在添加類的時候重構
  • 在修補缺陷時重構
  • 關注易於出錯/高度複雜的模塊
  • 在維護環境下,改善正在處理的代碼 – 確保更健康
  • 定義清楚乾淨代碼和拙劣代碼之間的邊界,然後嘗試把代碼移過這條邊界

第25章 代碼調整策略

25.1 性能概述

  • 質量特性和性能 – 用戶更在意程序整體表現(完整與易用性),而非速度,除非影響到正常使用
  • 性能和代碼調整 – 對正確代碼調整使其更爲高效,得到累乘的性能提升
    • 程序需求 – 調優前確認是否確實需要高的性能級別
    • 程序的設計 – 根據程序的資源佔用量和速度,仔細設計程序架構去滿足
    • 類和子程序設計 – 選擇合適的數據類型和算法
    • 同操作系統的交互 – 減少I/O操作、操作系統相關調用
    • 用更優秀的編譯器或硬件設施

25.2 代碼調整簡介

  • Pareto(80/20)法則 – 80%的運行時間花費在20%的子程序中
  • 何時調整代碼 – 高質量設計,正確編寫,使之模塊化並易於維護修改,待功能正確完成後檢查性能

25.3 蜜糖和哥斯拉

  • 發現那些如同寒冬罐子裏的蜜糖般粘乎乎,體積如同哥斯拉一樣的代碼,調整優化
  • 常見的低效率之源:
    • 輸入/輸出操作 – 內存優於數據庫優於磁盤
    • 系統調用 – 涉及系統的上下文切換比較耗時
    • 錯誤 – 調試代碼、內存泄露、數據庫表設計失誤、輪詢失效設備直至超時等
  • 常見操作的相對效率:
    • 函數調用: 無參函數=1; 多參函數=2; 多態函數=2.5; 下標訪問數組=1
    • 整數浮點數運算: 賦值/加/減/乘=1; 除法=5; 超越函數(方根/求弦等)=20

25.4 性能測量

  • 測量代碼性能,找出代碼中的熱點 – 如火焰圖
  • 性能測量應當精確 – 避開初始化負擔; 用性能剖測工具

25.5 反覆調整

  • 確定性能瓶頸後,有效結合多種方法,反覆嘗試,單條方法可能收效甚微但累計效果可能驚人

第26章 代碼調整技術

  • 恪守"對每一次的改進進行量化"

26.1 邏輯

  • 在知道答案後停止判斷 – 如"短路求值"
  • 將運行更快、出現頻率更高的判斷順序優先執行
  • 用查詢表替代複雜表達式
  • 使用惰性求值 - 避免做任何事直到迫不得已(即時完成策略)

26.2 循環

  • 將判斷外提 – 將循環時不會改變的判斷提到循環外,注意要同步修改
  • 合併或融合 – 把兩個對相同一組元素進行操作的循環合併在一起,注意下標和先後次序
  • 展開 – 每次遍歷進行兩次或更多的處理而非一次
  • 儘可能減少循環內部所做的工作
  • 哨兵值 – 簡化複合判斷,將哨兵值置於循環範圍的末尾
  • 將最忙的循環放在最內層 – 確保外層循環次數遠少於內層循環,減少下標的初始化
  • 削減強度 – 用多次輕量級運算(如加法)替代一次高昂的運算(如乘法)

26.3 數據變換

  • 使用整形而不是浮點數
  • 儘可能減少數組維度和數組引用
  • 使用輔助索引 – 字符串長度索引; 獨立的平行的索引結構(如索引排序查找避免直接操作大數據塊)
  • 使用緩存機制 – 緩存需頻繁讀取的數據、耗時高的運算結果、創建開銷大的元素(如對象或連接池)

26.4 表達式

  • 利用代數恆等式替代複雜操作: 如 not a and not b 改爲 not (a or b)
  • 削弱運算強度 – 乘改加; 浮點改整數; 雙精度改單精度; 乘除2改移位操作
  • 編譯期初始化 – 提前計算置於常量中
  • 小心繫統函數 – 不一定足夠高效
  • 使用正確的常量類型 – 具名常量應與被賦值的變量類型相同,避免類型轉換
  • 預先算出結果 – 先計算,放入常量/文件中需要時引用
  • 刪除公共子表達式 – 將重複出現的表達式賦給變量需要時直接引用

26.5 子程序

  • 良好的子程序分解 – 分解成短小、定義明確的子程序
  • 將子程序重寫爲內聯(inline)
  • 對性能熱點子程序用低級語言(C或彙編)重寫

第27章 程序規模對構建的影響

27.1 交流和規模

  • 交流路徑的數量大致與項目成員數量的平方成正比
  • 改善交流效率的常用方法是採用正式的文檔 – 方法論的關鍵在於能否促進交流

27.2 項目規模的範圍

  • 評估項目規模的方法之一是考慮項目團隊的規模

27.3 項目規模對錯誤的影響

  • 項目規模既影響錯誤的數量,也會影響錯誤的類型
  • 小項目構建錯誤約佔75%; 大項目構建錯誤降至50%,剩餘由需求和架構錯誤填平

27.4 項目規模對生產率的影響

  • 隨着項目和團隊規模的增大,組織方式對生產率的影響隨之放大(小:大≈3:1; 小:特大≈10:1)
  • 生產率取決於: 軟件類型、人員素質、編程語言、方法論、產品複雜度、編程環境、工具支持等

27.5 項目規模對開發活動的影響

  • 活動比例隨項目規模變大急劇變化: 交流、計劃、管理、需求分析、設計、架構、集成、測試、文檔
  • 程序、產品、系統和系統產品 – 開發成本和難度依次呈3倍遞增,做好區分避免估算偏差
  • 方法論和規模 – 以小的方法論爲起點逐漸擴充適用於大項目,好於從囊括一切的方法論作爲起點

第28章 管理構建

28.1 鼓勵良好的編碼實踐

  • 一份簡單的標準 – 由受尊敬且仍接觸具體編碼事務的"專家層"制定,得到廣泛支持並遵守
  • 爲項目每一部分指派兩個人 – 結對編程、導師帶學生、夥伴複審
  • 逐行復查代碼 – 包括程序員本人和至少兩名評審員
  • 要求代碼簽名 – 確認技術可行並且無錯誤,認定完成前高級技術人員必須在代碼清單上簽字
  • 安排一些好的代碼示例供人蔘考,並獎勵出色代碼
  • 強調代碼是公有財產 – 如開源軟件和極限編程倡導的集體所有權

28.2 配置管理

  • 定義: 系統化地定義項目工件和處理變化,以使項目一直保持其完整性的實踐活動,又稱"變更控制"SCM
  • SCM關注: 全面控制需求變更、設計變更、文檔、源代碼、測試用例和其他項目工件
  • 需求變更和設計變更
    • 遵循某種系統化的變更控制手續 – 放在"對系統整體最爲有利"的環境下進行考慮
    • 成組的處理變更請求 – 記錄所有想法和建議,不管難易,直到有時間處理時,挑選最有益的實施
    • 評估每項變更的成本 – 評估耗時以及導致的連鎖反映(需求、設計、編碼、文檔)的耗時
    • 提防大量的變更請求 – 關鍵警告信號:表明需求、架構或上層設計不夠好,考慮是否返工
    • 成立變更控制委員或類似機構,警惕官僚主義,也不要因害怕它而排斥有效的變更控制
  • 軟件代碼變更
    • 版本控制軟件(Git)及定期備份 – 與缺陷跟蹤和變更管理整合,威力十足
    • 工具版本控制及統一機器配置(容器) – 如需要重新構造出"創建軟件的各個特定版本"的原樣環境

28.3 評估構建進度表

  • 評估的方法 – 評估軟件、算法方法(Cocomo II)、評估專家、評估項目每部分後相加、參考以往經驗
    • 建立目標; 留出評估時間並作出計劃; 清晰軟件需求; 在底層細節層面進行評估; 定期重新評估
  • 評估構建的工作量 – 包括詳細設計、編碼與調試、測試用例等,最佳方法是根據以往類似項目經驗評估
  • 對進度的影響: 文檔量、需求靈活度、平臺、語言和工具經驗、資源限制如數據庫大小、團隊能力與動力、人員流動性、管理質量、客戶關係的質量、產品複雜度、用戶對需求的參與度、
  • 評估的準確度和重要性遠遠比不上"隨後爲了完成進度而成功地控制資源"的重要性
  • 如果落後該怎麼辦: 加速追趕; 擴充團隊; 縮減項目範圍(劃分必須、有則更好、可選)

28.4 度量

  • 任何一種項目特徵都可以用某種方法來度量,而且總會比根本不度量好得多
  • 有用的量度: 規模; 整體質量; 缺陷跟蹤; 可維護性; 生產率

28.5 把程序員當人看

  • 程序員約30%時間花費在"對項目無直接好處"的非技術活動上
  • 性能與質量差異 – 個體差異(最好和最差有着數量級的差異); 團體差異(同類聚集效應如好聚集好)
  • 信仰問題 – 編程語言、代碼風格如縮進註釋大括號等、編碼工具、命名習慣
    • 要清楚知道你是在處理一個敏感的問題 – 全心投入前試探他們有關敏感問題的看法
    • 使用"建議"避免過於僵硬的"規則"; 或讓程序員們指定他們自己的標準
    • 避免流露明顯的意圖 – 比如使用格式化工具; 修改不清晰代碼規整註釋
  • 物理問題 – 更寬敞、安靜、私密的辦公室,且較少受到其他人員和電話的干擾

28.6 管理你的管理者

  • 針對非技術出身或技術落後時代的管理者
    • 隱藏希望做什麼,等待管理者組織一場有關你希望什麼的頭腦風暴/集體討論
    • 把做事情的正確方法傳授給你的管理者
    • 關注管理者興趣,按照他的真正意圖去做,但不要用一些不必要的實現細節分散其注意力(工作封裝)
    • 堅持用正確的方法做自己的事

第29章 集成

  • 將一些獨立的軟件組件組合爲一個完整的系統

29.1 集成方式的重要性

  • 正確的集成尤爲重要 – 避免程序難於編碼、測試、調試

29.2 集成頻率——階段式集成還是增量集成

  • 階段式集成 – 單元開發→系統集成→系統調試,造成問題同時爆發及問題位置不確定性
  • 增量集成: 易於定位錯誤; 及早取得成果提升士氣; 改善進度監控; 更充分的測試各單元
    1. 開發一個小的系統功能部件作爲骨架 – 最小功能、最難、關鍵或以上某種組合部件
    2. 設計、編碼、測試、調試某個類
    3. 將這個新的類集成到系統骨架上
    4. 測試並調試確保結合體正常工作,重複2~4步驟添加新類

29.3 增量集成的策略

  • 自頂向下集成 – 首先編寫並集成繼承體系頂部的基類,必須仔細定義類之間的接口
    • 優點: 更早測試控制邏輯; 及早得到部分可工作系統提升士氣; 能在底層設計細節前編碼
    • 注意: 及早並仔細地演練棘手的系統接口; 避免底層問題上浮影響頂層系統
  • 自底向上集成 – 首先編寫並集成位於繼承體系底部的派生類,並編寫test driver演練底層類
    • 優點: 易於定位錯誤(限制範圍正在集成的類上); 及早演練"可能存在問題的系統接口"
    • 注意: 必須先完成整個系統的設計工作; 避免"讓底層細節驅動高層類的設計"
  • 三明治集成 – 先集成頂部高層業務對象類 → 然後集成接口和工具類 → 集成中間層的類
    • 優點: 先集成通常比較棘手的類; 讓項目所需的腳手架數目最少
  • 風險導向的集成 – 鑑定風險級別,優先實現並集成高風險最棘手的類
  • 功能導向的集成 – 選擇能支撐其他功能的"骨架"部件,優先實現
    • 優點: 基本無需腳手架; 每次集成都增加了系統的功能性; 能與面向對象設計很好的協同工作
  • T型集成 – 選定能從頭到尾演練的特定"豎直塊",且演練過程能找出系統設計所作假設中全部主要問題

29.4 每日構建與冒煙測試

  • 有壓力也堅持每天都將各(源)文件編譯、鏈接並組合爲可執行程序,並執行相對簡單的檢查看是否"冒煙"
  • 檢查失敗的build – 不可用即視爲失敗,修復提升爲最高優先級任務
  • 每天必須進行冒煙測試,並使其與時俱進
  • 將daily build和冒煙測試自動化,並在早上發佈
  • 大型項目成立build小組,全職負責 – 項目越大,增量集成越重要
  • 僅當有意義時,纔將修訂加入build中…但是別等太久才加入進來
  • 要求開發人員在合併代碼進系統前,進行冒煙測試,對破壞build的人進行懲罰
  • 爲即將添加到build的代碼準備一塊暫存區,確認新build可用才合併至主源碼 – 使用版本控制系統

第30章 編程工具

  • 設計工具: 圖形化工具如UML、架構方塊圖、繼承體系圖、實體關係圖、類圖,能檢查設計的一致性
  • 源代碼工具: IDE; 字符搜索如grep; diff; 源碼美化器; 文檔生成如javadoc; 模板; 交叉引用; 類繼承體系生成器
  • 分析代碼質量: 吹毛求疵的語法/語義檢查器; 尺度報告器(分析並報告質量)
  • 重構源代碼: 重構器; 結構改組工具; 代碼翻譯器
  • Vesion Control: 源代碼; 依賴關係; 項目文檔; 項目工件(代碼、測試用例)關聯
  • 數據字典 – 描述項目中所有重要數據的數據庫,包括名稱、描述和注意事項
  • 可執行代碼工具: 代碼生成; 調試; 測試如JUnit; 代碼調整(如反彙編)
  • 打造你自己的編程工具: 項目特有工具; 腳本或批處理文件
  • 測試環境需要: 自動化測試框架、自動測試生成器、覆蓋率監視器、系統擾動器、diff、缺陷跟蹤軟件

第31章 佈局與風格

31.1 基本原則

  • 好的佈局凸現程序的邏輯結構 – 結構能幫助感知、理解和記住程序的重要特性
  • 把佈局作爲一種信仰 – 接受已證實更好的方法,即使調適過程最初會感覺有些不舒服
  • 良好佈局的目標: 始終準確的表現代碼邏輯結構; 改善可讀性; 修改時影響範圍小

31.4 控制結構的佈局

  • begin-end{}對與控制結構對齊(不要縮進),其間的語句縮進,包括單語句塊
  • 段落(邏輯代碼塊)之間使用空行分隔
  • 對於複雜的表達式,根據可讀性將條件分隔放在幾行上
  • 不用goto,如果必須使用,跳轉要顯眼
  • case語句不要有行尾佈局的例外,保持與其他控制結構一致

31.5 單條語句的佈局

  • 語句長度 – 不要武斷的用80字符/行,現代顯示器足以支持更長單行
  • 用空格使語句顯得清楚 – 邏輯表達式; 數組下標; 子程序參數
  • 格式化後續行 – 超長需續行時,符號置於行首; 緊密關聯的元素放一起; 標準量縮進續行
  • 每行僅寫一條語句,避免一行內有多個操作
  • 每行只聲明一個數據(變量),變量聲明儘量靠近首次使用位置,合理組織順序如按類型分組

31.6 註釋的佈局

  • 註釋的縮進要與相應代碼一致
  • 每行註釋間用空行分開

31.7 子程序的佈局

  • 用空行分隔子程序的各部分 – 頭、變量/常量聲明、子程序體之間
  • 將子程序參數按標準縮進,便於看懂、修改、註釋

31.8 類的佈局

  • 類接口的佈局: 1.頭部註釋; 2.構造/析構函數; 3.public方法 4.protect方法 5.private方法和字段
  • 類實現的佈局: 1.頭部註釋如所在文件; 2.類數據; 3.public方法; 4.protect方法; 5.private方法
    • 如果文件包含多個類,要清楚地標出每一個類
  • 文件和程序佈局: 一個文件只有一個類; 命名與類名相關; 清晰分隔各子程序

第32章 自說明代碼

32.1 外部文檔

  • 單元開發文件夾 – 提供未說明的設計決策蹤跡,如需求設計複本、頂層設計的組成部分、開發標準等
  • 詳細設計文檔 – 描述類或子程序層的設計決定,廢棄方案,選用現在方案的理由

32.2 編程風格作文檔

  • 包括良好的程序結構、直率易懂的方法、清晰明瞭的命名和佈局、最低複雜度的控制流及數據結構
  • 類: 能表明中心意圖的類名; 接口抽象一致,使用方法顯而易見; 無需考慮實現過程,可視爲黑盒
  • 子程序: 能準確指示確切幹了些什麼的命名; 各自任務獨立且明確; 接口清晰明瞭
  • 數據名: 有意義的類型名和變量(枚舉)名; 意義明確用途單一; 具名常量替代魔數; 規範的命名首部
  • 數據組織: 根據編程清晰需要額外使用變量; 集中引用某變量; 通過抽象數據類型(子程序)訪問複雜數據
  • 控制: 執行路徑清晰; 相關語句一起; 相對獨立的語句打包爲子程序; 正常情況的處理位於if而非else之後; 每個循環僅且完成一個功能; 最少的嵌套層次; 通過添加布爾變量/布爾函數和功能表簡化邏輯表達式
  • 佈局: 能清晰表現程序的邏輯結構
  • 設計: 代碼直截了當,避免自作聰明和新花樣; 儘可能隱藏實現細節; 採用問題領域而非計算機科學術語

32.4 高效註釋之關鍵

  • 註釋種類: 完工的代碼只允許有註釋類型4,5,6
    1. 重複代碼: 只是用不同文字將代碼的工作又描述了一次 – 除了增加閱讀量沒有提供更多信息
    2. 解釋代碼: 解釋複雜、有巧、敏感的代碼塊 – 應改進代碼使其清晰而後用概述/意圖性註釋
    3. 代碼標記: 提醒工作未完成或便於搜索調試 – 規範並統一風格,如to do標記
    4. 概述代碼: 將若干代碼行的意思以一兩句話說出來 – 提高閱讀速度,易於維護
    5. 代碼意圖說明: 指明一段代碼的意圖(要解決的問題)
    6. 傳達代碼無法表述的信息: 不能通過代碼來表達,又必須包含在源碼中:
      • 版權聲明、保密要求、版本號等雜項
      • 設計相關注意事項; 要求/架構文件索引; 聯機參考鏈接; 優化註記
  • 高效註釋:
    • 採用不會打斷或抑制修改(易於維護)的註釋風格
    • 用僞代碼編程法減少註釋時間
    • 將註釋集成到你的開發風格中,避免事後註釋
    • 性能不是逃避註釋的好藉口
  • 最佳註釋量: 操心數量不如傾力檢查註釋有無效用 – 約每十條語句一個註釋清晰度更高

32.5 註釋技術

  • 註釋單行 – 太複雜需要解釋; 出過錯需要標記
    • 不要隨意添加無關注釋
    • 不要對單行代碼做行尾註釋,避免存放維護註記 – 難以對齊、維護,容易含混不清
    • 行尾註釋用於數據聲明或標記塊尾
  • 註釋代碼段 – 應表達代碼的意圖
    • 代碼本身應盡力做好自說明 – 要清晰、簡潔、明瞭,糟糕代碼儘量重寫
    • 註釋代碼段時應注重"爲何做why"而不是"怎麼做how"
    • 用註釋爲後面的內容做鋪墊 – 瞭解代碼在做什麼,去哪找特定的操作
    • 說明非常規做法、錯誤、語言環境獨特點
    • 避免用縮略語,避免過度註釋(讓每個註釋都有用),將主次註釋區分開
  • 註釋數據聲明 – 應給出變量名未表達出來的各種信息
    • 註釋數值單位、允許範圍、魔數含義(儘量用枚舉替代)、位標識
    • 註釋全局數據 – 指出目的、爲何必須是全局
    • 註釋對輸入數據的限制
    • 將與變量有關的註釋通過變量名關聯起來 – 搜索變量名時可連同找出其註釋
  • 註釋控制結構 – 選擇語句提供爲何判斷的理由及執行結果總結; 循環指出其目的;
    • 應在每個if/case/循環或代碼段前面加上註釋 – 闡明意圖
    • 應在每個控制結構後加上註釋 – 說明結局如何
  • 註釋子程序 – 說明意圖
    • 靠近需要說明的代碼,簡短清晰 – 如子程序上部,避免龐大的註釋頭
    • 在參數聲明處註釋說明它們,分清輸入和輸出數據
    • 利用諸如JavaDoc之類的代碼說明工具
    • 註釋接口假設
    • 對子程序的侷限性作註釋 – 默認行爲,限定,必須避免的修改
    • 說明子程序的全局效果 – 修改全局數據要確切描述意圖和結果
    • 記錄所用算法的來源
  • 註釋類、文件和程序
    • 類: 說明設計方法; 說明侷限性、用法假設等; 註釋類接口但不要說明實現細節;
    • 文件: 說明意圖和內容; 版權聲明等法律通告; 責任者信息(時間,姓名,email); 包含版本控制標記
    • 程序: 將代碼視爲特殊書籍 – 序(單元); 目錄(類和子程序); 節(子程序內部); 交叉引用(參閱…)

第34章 軟件工藝的話題

34.1 征服複雜性

  • 軟件設計和構建的主要目標或動機就是征服複雜性
  • 分解複雜問題; 封裝細節高度抽象; 良好的命名和編程規範; 避免深度繼承或嵌套

34.2 精選開發過程

  • 小項目質量依賴個人能力,中大型項目依賴組織性和開發過程
  • 無誤的需求(靈活則增量式) → 優秀的架構設計 → 一開始有質量的開發 → 避免不成熟的優化 → 增量集成

34.3 首先爲人寫程序,其次纔是爲機器

  • 可讀/可理解性; 容易複查; 錯誤率; 調試; 可修改性; 開發時間; 外在質量

34.4 深入一門語言去編寫,不浮於表面

  • 不要將編程思路侷限於所用語言能自動支持的範圍
  • 傑出的程序員: 考慮技術目標(要幹什麼),然後確定如何用手頭工具(語言)實現這些目標

34.5 藉助規範集中注意力

  • 規範的好處: 精確地傳達重要信息; 規避各種風險; 增加對低層工作的可預見性

34.6 藉助問題域編程

  • 儘可能工作於最高的抽象層次 – 降低複雜性
  • 將程序劃分爲不同層次的抽象: 優秀的設計使更多時間集中在較高層(3、4層):
    1. 操作系統的操作和機器指令
    2. 編程語言結構和工具
    3. 低層實現結構 – 算法和數據結構(鏈表/樹/索引文件/排序算法等)
    4. 低層問題域 – 解決問題的各種基本構件(業務對象層/服務層)
    5. 高層問題域 – 對問題工作的抽象,非編程人員某種程度都可以看懂

34.7 當心落石

  • “這段代碼暗藏玄機”/難以理解 – 危險徵兆,通常意味"差勁代碼",應重寫
  • 類中含有超乎尋常數量的缺陷/錯誤 – 最費精力的部分,考慮重寫
  • 警告信息 – 指出你需要考慮某個問題
  • 多處重複的代碼/相似修改/不愜意/不方便單獨使用 – 考慮控制/耦合是否得當
  • 設計度量相關警告 – 過多的判斷點、邏輯嵌套、變量等

34.8 迭代,反反覆覆,一次又一次

  • 軟件設計是一個逐步精華的過程,需要經過反覆修正和改進 – 反覆設計、測試和開發
  • 一旦軟件能工作,對少部分代碼精雕細琢就能顯著改善整個系統的性能
  • 複審使開發過程少走彎路
  • 早期快速迭代,越靠後,成本越高

34.9 汝當分離軟件與信仰

  • 軟件先知 – 避免偏執,不要盲目跟風,可先實驗,但仍紮根傳統可靠方法
  • 折中主義 – 避免盲目迷信某種方法,折中考慮,權衡各種技術; 你拿的應是"工具箱"而非特定工具
  • 試驗 – 快速試錯,選擇更佳的方法,無論架構亦或詳細設計層
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章