代碼大全2讀書筆記
作者寫這本書的首要目的,就是希望縮小本行業中一般商用實踐與大師級人物及專家們之間的知識差距。許多強大的編程技術在被編程領域的大衆接觸之前,都已在學術論文和期刊裏塵封了多年。
成功學大師拿破崙希爾說過:什麼思想決定什麼樣行爲;什麼樣行爲決定什麼樣的習慣,什麼樣的習慣決定什麼樣性格,而什麼性格決定什麼樣的命運。
本書給我印象較深刻的章節有:前期準備、軟件構建中的設計、防禦式編程、表驅動法。
前期準備中包括了需求、架構方面的知識;軟件構建中的設計中包括了設計模式、設計方法知識;防禦式編程包括了在實際編程中很少關注的處理程序容錯性,讓出錯誤更容易發現、更容易修改,並減少錯誤對產品代碼的破壞;表驅動法介紹了一種編程模式,從表裏面查找信息而不是使用邏輯語句。
一、打好基礎
1、軟件構建世界
軟件開發的主要流程:問題定義-〉需求分析-〉規劃構建-〉架構(概要設計)-〉詳細設計-〉編碼與調試-〉單元測試-〉集成測試-〉系統測試-〉交付或發佈-〉軟件維護
各個過程與軟件構建的關係如下圖:
2、用隱喻來更充分地理解軟件開發
軟件隱喻更像一個探照燈,而不是一個路牌。它並不能告訴你在哪找到答案,但卻能告訴你如何能找到答案。更像一個啓發式的方法而非一個算法。隱喻把軟件開發過程與其他你熟悉的活動聯繫在一起,幫助你更好地理解。
一般的軟件隱喻有以下類型:
1)寫作代碼
這種隱喻認爲開發方式就像寫信,如果寫錯了,拋棄即可。對於大些的項目明顯不適用。
2)培植系統
這種隱喻認爲開發就像種莊稼,耕地、播種、施肥、澆水、收割,一點一點的做,最後豐收。其核心爲“每次做一點”,但缺點爲無法對整個過程進行系統的控制。
3)系統生長
這種隱喻認爲開發就像牡蠣產生珍珠的過程:增量式(增量的,迭代的,自適應的和演進的)。就像珍珠先有一個沙粒,然後再一點一點長成珍珠。軟件開發也是,可以先搭起骨架,然後一點一點附着肌肉和皮膚,一次增加一點代碼,最後形成系統。優點在於不做過多的承諾,缺點在於用它來描繪軟件開發過程還是不恰當。
4)建造軟件
這種隱喻認爲軟件開發就像建築一樣,需要計劃,前期準備和執行。其實軟件開發的很多術語都源於建築,比如:軟件架構,腳手架,構建等。同時你還可以把建築的很多方面引申到軟件開發上來。他們具有很多的相似性。
5)智力工具箱
這種隱喻是指人們在多年的開發過程中積累了大量的技術、技巧和訣竅。在開發過程中學的越多,腦中工具箱可用的工具就越多,以後合適的時候就可以拿出來用。
3、前期準備
不同種類的軟件項目,需要在“準備工作”和“構建活動”之間做出不同的平衡。每一個項目都是獨特的,但是項目可以歸入若干種開發風格。表列出了三種最常見的軟件項目種類,並且列出了各種項目最適合的典型實踐。
三種常見的軟件項目種類,及其典型的良好實踐
軟件種類 |
|||
|
商業系統 |
使命攸關的系統 |
性命攸關的嵌入式系統 |
典型應用 |
Internet站點 Intranet站點 庫存管理 遊戲 管理信息系統(MIS) 工資系統 |
嵌入式軟件 遊戲 Internet站點 盒裝軟件 軟件工具 Web services |
航空軟件 嵌入式軟件 醫療設備 操作系統 盒裝軟件 |
生命週期模型 |
敏捷開發(極限編程、 Scrum、time-box 開發等等) 漸進原型(prototyping) |
分階段交付 漸進交付 螺旋型開發 |
分階段交付 螺旋型開發 漸進交付
|
計劃與管理 |
增量式項目計劃 隨需測試與QA計劃 非正式的變更控制 |
基本的預先計劃 基本的測試計劃 隨需QA計劃 正式的變更控制 |
充分的預先計劃 充分的測試計劃 充分的QA計劃 嚴格的變更控制 |
需求 |
非形式化的需求規格 |
半形式化的需求規格 隨需的需求評審 |
形式化的需求規格 形式化的需求檢查 |
設計 |
設計與編碼是結合的 |
架構設計 非形式化的詳細設計 隨需的設計評審 |
架構設計 形式化的架構檢查 形式化的詳細設計 形式化的詳細設計檢查 |
構建 |
結對編程或獨立編碼 非正式的check-in手續 |
結對編程或獨立編碼 非正式的check-in手續 隨需代碼評審 |
結對編程或獨立編碼 正式的check-in手續 正式的代碼檢查 |
測試與QA |
開發者測試自己的代碼 測試先行開發 很少或沒有測試(由單獨的測試小組來做) |
開發者測試自己的代碼 測試先行開發 單獨的測試小組 |
開發者測試自己的代碼 測試先行開發 單獨的測試小組 單獨的QA小組 |
部署 |
非正式的部署過程 |
正式的部署過程 |
正式的部署過程 |
在真實項目中,你會找到表中所列這三種主調的無數種變奏;無論如何,表中已經列舉了它們的共性。開發商業系統的項目往往受益於高度迭代的開發法,這種方法的“計劃、需求、架構”活動與“構建、系統測試、質量保證”活動交織在一起。
架構師喫掉需求,而設計師喫掉架構,而程序員則消化設計。IBM和其他公司的研究發現,平均水平的項目在開發過程中,需求會有25%的變化。
衡量需求做得如何:
針對功能需求
是否詳細定義了系統的全部輸入,包括其來源、精度、取值範圍、出現頻率等?
是否詳細定義了系統的全部輸出,包括目的地、精度、取值範圍、出現頻率、格式等?
是否詳細定義了所有輸出格式(Web頁面、報表,等等)?
是否詳細定義了所有硬件及軟件的外部接口?
是否詳細定義了全部外部通信接口,包括握手協議、糾錯協議、通信協議等?
是否列出了用戶想要做的全部事情?
是否詳細定義了每個任務所用的數據,以及每個任務得到的數據?
針對非功能需求(質量需求)
是否爲全部必要的操作,從用戶的視角,詳細描述了期望響應時間?
是否詳細描述了其他與計時有關的考慮,例如處理時間、數據傳輸率、系統吞吐量?
是否詳細定義了安全級別?
是否詳細定義了可靠性,包括軟件失靈的後果、發生故障時需要保護的至關重要的信息、錯誤檢測與恢復的策略等?
是否詳細定義了機器內存和剩餘磁盤空間的最小值?
是否詳細定義了系統的可維護性,包括適應特定功能的變更、操作環境的變更、與其他軟件的接口的變更能力?
是否包含對“成功”的定義?“失敗”的定義呢?
需求的質量
需求使用用戶的語言書寫的嗎?用戶也這麼認爲嗎?
每條需求都不與其他需求衝突嗎?
是否詳細定義了相互競爭的特性之間的權衡——例如,健壯性與正確性之間的權衡?
是否避免在需求中規定設計(方案)?
需求是否在詳細程度上保持相當一致的水平?有些需求應該更詳細地描述嗎?有些需求應該更粗略地描述嗎?
需求是否足夠清晰,即使轉交給一個獨立的小組去構建,他們也能理解嗎?開發者也這麼想嗎?
每個條款都與待解決的問題及其解決方案相關嗎?能從每個條款上溯到他在問題與中對應的根源嗎?
是否每條需求都是可測試的?是否可能進行獨立的測試,以檢驗滿不滿足各項需求?
是否詳細描述了所有可能的對需求的改動,包括各項改動的可能性?
需求的完備性
對於在開始開發之前無法獲得的信息,是否詳細描述了信息不完全的區域?
需求的完備度是否能達到這種程度:如果產品滿足所有需求,那麼它就是可接受的?
你對全部需求都感到很舒服嗎?你是否已經去掉了那些不可能實現的需求——那些只是爲了安撫客戶和老闆的東西?
衡量架構做得如何:
針對個架構主題
程序的整體組織結構是否清晰?是否包含一個良好的架構全局觀(及其理由)?
是否明確定義了主要的構造塊(包括每個構造塊的職責範圍及其他構造塊的接口)?
是否明顯涵蓋了“需求”中列出的所有功能(每個功能對應的構造塊不太多也不太少)?
是否描述並論證了那些最關鍵的類?
是否描述並論證了數據設計?
是否詳細定義了數據庫的組織結構和內容?
是否指出了所用的關鍵的業務規則,並描述其對系統的影響?
是否描述了用戶界面設計的策略?
是否將用戶界面模塊化,使界面的變更不會影響程序其餘部分?
是否描述並論證了處理I/O的策略?
是否估算了稀缺資源(如現程、數據庫連接、句柄、網絡帶寬等)的使用量,是否描
述並論證了資源管理的策略?
是否描述了架構的安全需求?
架構是否爲每個類、每個子系統、或每個功能域(functionality area)提出空間與時間預算?
架構是否描述瞭如何達到可伸縮性?
架構是否關注互操作性?
是否描述了國際化/本地化的策略?
是否提供了一套內聚的錯誤處理策略?
是否規定了容錯的辦法(如果需要)?
是否證實了系統各個部分的技術可行性?
是否詳細描述了過度工程的方法?
是否包含了必要的“買 vs. 造”的決策?
架構是否描述瞭如何加工被複用的代碼,使之符合其他架構目標?
是否將架構設計得能夠適應很可能出現的變更?
架構的總體質量
架構是否解決了全部需求?
有沒有哪個部分是“過度設計”或“欠設計”?
整個架構是否在概念上協調一致?
頂層設計是否獨立於用作實現它的機器和語言?
是否說明了所有主要的決策的動機?
序列式開發法與迭代式開發法選擇
序列式開發法適用下列環境:
需求相當穩定。
設計直接了當,而且理解透徹。
開發團隊對於這一應用領域非常熟悉。
項目風險很小。
“長期可預測性”很重要。
後期改變需求、設計和編碼的的代價很可能較昂貴。
迭代式開發法適用下列環境:
需求並沒有被理解透徹,或者出於其他理由你認爲它是不穩定的。
設計很複雜,或者有挑戰性,或者兩者兼具。
開發團隊對於這一應用領域不熟悉。
項目包含許多風險。
“長期可預測性”不重要。
後期改變需求、設計和編碼的代價很可能較低。
4、關鍵的“構建”決策
語言的選擇、編程約定
關鍵點:
輸入一種語言去編程,而不是使用一種語言編程。在一種語言上編程的程序員將受制於程序語言提供的“構件",這樣,如果工具是初級的,那麼程序員的思想也會是初級的。而深入一種語言去編程的程序員會先決定他想表達的思想是什麼,然後再決定如何使用特定的程序語言提供的工具來表達這些思想。
如果你使用的語言缺乏你所希望的構件,或者傾向於出現其他種類的問題,那深入一種語言編程的程序員應該發明自己的編程約定,命名標準,和程序類庫來彌補特定語言自身的缺陷。
二、創建高質量的代碼
5、軟件構建中的設計
對優秀的設計師,他們共有的一項特質就是對變化的預期能力。好的程序設計所面臨的最重要挑戰之一就是適應變化。目標應該是把不穩定的區域隔離出來,從而把變化所帶來的影響限制在一個子程序、類或者包的內部。下面給出你應該採取的應對各種變動的措施:
A、找出看起來容易變化的項目。如果需求做得很好,那麼其中就應該包含一份潛在變化的清單,以及其中每一項變化發生的可能性。在這種情況下,找出潛在的變化就非常容易了。如果需求中沒有包括潛在的變化,請業務規則、對硬件的依賴性、輸入和輸出、被標準的語言特性、困難的設計區域和構建區域、狀態變量等方面考慮變化。
B、把容易變化的項目分離出來。把第一步中找出的容易變化的組件單獨劃分成類,或者和其他容易同時發生變化的組件劃分到同一個類中。
C、把看起來容易變化的項目隔離出來。設法設計好類之間的接口,使其對潛在的變化不敏感。設計好類的接口,把變化限制在類的內部,且不會影響類的外部。任何使用了這個將會發生變化的類的其他類都不會察覺到變化的存在。類的接口應該肩負起保護類的隱私的職責。
設計模式:
創建型模式
Singleton:模式解決實體對象個數問題,除Singleton之外,其他創建型模式解決的都是new所帶來的耦合關係。
Factory mothod(工廠方法) 、abstract factory (抽象工廠)、Builder都需要一個額外的工廠類來負責實例化"
易變對象".而Prototype則是通過原型(一個特殊的工廠類)來克隆“易變對象”。
結構型模式
Adapter模式注重轉換接口,將不吻合的接口適配對接。
Bridge模式注重分離接口與其實現,支持多維度變化。
Composite模式注重統一接口,將“一對多”的關係轉化爲“一對一”的關係。
Decorator模式注重穩定接口,在此前提下爲對象擴展功能。
Facade模式注重簡化接口,簡化組件系統與外部客戶程序的依賴關係。
Flyweight模式注重保留接口,在內部使用共享技術對對象存儲進行優化。
Proxy模式注重假借接口,增加間接層來實現靈活控制。
行爲型模式
Template Method模式封裝算法結構,支持算法子步驟變化。
Strategy模式注重封裝算法,支持算法的變化。
State模式注重封裝與狀態相關的變化,支持狀態的變化。
Memento模式注重封裝對象狀態的變化,支持狀態保存/恢復。
Mediator模式注重封裝對象間的交互,支持對象交互的變化。
Chain Of Responsibility模式注重封裝對象責任,支持責任的變化。
Command模式注重將請求封裝爲對象,支持請求的變化。
Iterator模式注重封裝集合對象內部結構,支持集合的變化。
Interpreter模式注重封裝特定領域變化,支持領域問題的頻繁變化。
Observer模式注重封裝對象通知,支持通信對象的變化。
Visitor模式注重封裝對象操作變化,支持在運行時爲類層次結構動態添加新的操作。
理想的設計特徵
最小的複雜度(Minimal complexity):設計的首要目標是讓複雜度最小。要避免“聰明的”設計,因爲“聰明的”設計常常都是難於理解的。應該做出簡單且易於理解的設計。如果你的設計方案不能讓你在專注於程序的一部分時安心地忽視其他部分的話,這一設計就沒有什麼作用了。
易於維護(Ease of maintenance):意味着在設計時爲做維護工作的程序員着想。請時刻想着維護程序員可能會就你寫的代碼而提出的問題。把這些程序員當成你的聽衆,進而設計出能自解釋的系統來。
鬆散耦合(loose coupling):意味着在設計時讓程序的各個組成部分之間關聯最小。通過應用類接口中的合理抽象、封裝性及信息隱藏等原則,設計出相互關聯儘可能最少的類。減少關聯也就減少了集成、測試與維護工作量。
可擴展性(extensibility):是說你能增強系統的功能而無須破壞其底層結構。你可以改動系統的某一部分而不會影響到其他部分。越是可能發生的改動,越不會給系統造成什麼破壞。
可重用性(reusability):意味着所設計的系統的組成部分能在其他系統中重複使用。
高扇入(high fan-in):是說讓大量的類使用某個給定的類。這意味着設計出的系統很好地利用了在較低層次上的工具類(utility classes)。
低扇出(low fan-out):是說讓一個類裏少量或適中地使用其他的類。高扇出(超過約7個)說明一個類使用大量其他的類,因此可能變得過於複雜。
可移植性(portability):設計出的系統應該很方便地移植到其他環境中。
精簡性(cleanness):設計出的系統沒有多餘的部分。任何多餘的代碼也需要開發、Review和測試,並且修改了其他代碼後還要重新考慮這部分。
層次性(stratification):意味着儘量保持系統各個分解層的層次性,是你能在任意的層面上觀察系統,並得到某種具有一致性的看法。
標準技術(Standard techniques):要儘量用標準化的、常用的方法,讓整個系統給人一種熟悉的感覺。
設計中啓發式方法的總結:
尋找現實世界的對象
形成一致的抽象
封裝實現細節
在可能的情況下繼承
信息隱藏
找出容易改變的區域
保存鬆散耦合
探尋通用的設計模式
設計策略
自上而下策略和自下而上策略的最關鍵的區別在於,前者是一種分解策略,後者是一種合成策略;前者從一般性的問題出發,把該問題分解成可控的部分。後者從可控的部分出發,去構造一個通用的方案。這兩種方法都有各自的強項和弱項。
記錄設計成果
把設計文檔插入到代碼裏:在代碼註釋中寫明關鍵的設計決策,這種註釋通常放在文件或者類的開始位置。如果你同時使用類似於JavaDoc這樣的文檔提取工具,那麼這種方法會確保設計文檔對於開發這部分代碼的程序員來說是立等可取的,同時也有助於程序員保持代碼和設計文檔之間的相當不錯的同步。
用Wiki來記錄設計討論和決策:把你們的設計討論寫到項目的Wiki裏去(Wiki是指一組可以由項目組所有成員用網絡瀏覽器輕鬆編輯的網頁)。儘管文字錄入要比交談麻煩一些,但這樣會自動地記錄下你們的設計討論和設計決策。如果使用Wiki,你可以用圖片來彌補文字討論的不足,並鏈接支持該設計決策的網站、白皮書及其他材料。如果你的開發團隊在地理位置上是分佈式的,那麼這種技術會非常有幫助。
寫總結郵件:每次就設計展開討論過後,請採取這種做法,即指派某人來寫出剛纔討論的綱要——特別是那些決定下來的事項——然後發送給整個項目組。在項目的公共電子郵件文件夾裏保留一份備份。
使用數碼相機:在對設計進行文檔化時有一個很常見的障礙,那就是用流行的畫圖工具畫設計圖表太麻煩。不過文檔化可不僅限於“用漂亮的格式、正規的符號來記錄設計”和“不做任何設計文檔”這兩種選擇。把白板上畫出的圖表照成相片然後嵌入到傳統的文檔裏,這樣做可以帶來事半功倍的效果,因爲它的工作量只是用畫圖工具畫設計圖表的1%,而它的收益卻能達到保存設計圖表的80%。
保留設計掛圖:如果你把設計記錄在大的掛圖上,那麼你只需把這些掛圖保存在方便的地方即可,或者採用更好的做法,把它們張帖在項目工作區域的牆上,讓大家能夠很容易地隨時查閱和修改。
使用CRC(類、職責、合作者)卡片:另外一種技術含量較低的文檔記錄方案是使用索引卡片。在每張卡片上,設計者寫下類的名稱、職責和合作者(與這個類合作的其他類)。一個設計團隊便按照這些卡片的內容展開工作,直到他們認爲已經創建出一個好的設計方案爲止。到那個時候,你只需把這些卡片保留下來,留待日後引用。索引卡片非常便宜,不嚇人,易於攜帶,並且有助於促進團隊合作(Beck 1991)。
在適當的細節層創建UML圖:一種流行的繪製設計圖的方法是由對象管理組織(Object Management Group)定義的統一建模語言(UML)(Fowler 2004)。UML提供了一套豐富的、形式化的表示法,可用於設計實體(entity)及其關係(relationship)。你可以用非正式的UML圖來幫助討論和發現設計思路。從最簡單的草圖開始,直到你最終選定了一套設計方案,才往其中增加細節。由於UML是標準化的,因此在交流設計觀念時大家都能理解它,同時還能加快團隊共同討論各種設計方案的速度。
6、可以工作的類
包含(“有一個……”的關係)
繼承(“是一個……”的關係)
研究表明,人們在做其他事情時,能夠記住的離散項目的個數是7+-2個。如果一個類中包含有超過7個數據成員時,請考慮要不要把他分解成爲幾個更小的類。
創建類的合理原因:對現實世界中的對象建模、對抽象對象建模、降低複雜度、隔離複雜度、隱藏實現細節、限制變化所影響的範圍、隱藏全局數據、讓參數傳遞更順暢、創建中心控制點、讓代碼更易於重用、讓程序族做計劃、把相關操作放到一起、實現特定重構。
要點:
類的接口應提供一致的抽象。很多問題都是由於違背該原則而引起的。
類的接口應該隱藏一些信息——如某個系統接口、某項設計決策、或者一些實現細節。
包含往往比繼承更爲可取——除非你要對“是一個/is a”的關係建模。
繼承是一種有用的工具,但它卻會增加複雜度。
類是管理複雜度的首選工具。
7、高質量子程序
要點:
子程序代碼長度最好控制在200行之內,如果超過200行,會在可讀性方面遇到問題。
把宏表達式整個包含在括號內,比如:#define Cube(a) (a*a*a)
創建子程序最主要的目的是提高程序的可管理性,當然也有其他一些好的理由。其中,節省代碼空間只是一個次要原因:提高可讀性、可靠性和可修改性等原因都更重要一些。
子程序可以按照其內聚性分爲很多類(功能內聚、順序內聚、通訊內聚、臨時內聚過程內聚、邏輯內聚、巧合內聚),而你應該讓大多數子程序具有功能上的內聚性,這是最佳的一種內聚性。
子程序的名字是它的質量的指示器。如果名字槽糕但是恰如其分,那就說明這個子程序設計得很差勁。準確使用對應詞,下面列出一些常見的對應詞組:
add/remove increment/decrement open/close
begin/end insert/delete show/hide
create/destroy lock/unlock source/target
first/last min/max start/stop
get/put next/previous up/down
get/set old/new
只有在某個子程序的主要目的是返回由其名字所描述的特定結果時,才應該使用函數。
細心的程序員會非常謹慎地使用宏,而且只在萬不得已時才用。
8、防禦式編程
防禦式編程的主要思想是:子程序應該不因傳入錯誤數據而被破壞,哪怕是有其他子程序產生的錯誤數據。簡單點講就是容錯性。
要點:
最終產品代碼中對錯誤的處理方式要對“垃圾進,垃圾出”複雜得多。
防禦式編程技術可以讓出錯誤更容易發現、更容易修改,並減少錯誤對產品代碼的破壞。
斷言可以幫助人儘早發現錯誤,尤其是在大型系統和高可靠性的系統中,以及快速變化的代碼中。包含C++
、Java和Microsoft Visual Basic在內的很多語言都支持斷言,比如C++中標準的aasert宏並不支持文本消息。下面實例給出了一個使用C++宏改進的ASSERT實現:
#define ASSERT(condition,message){
if(!(condition)){
LogError("Assertion failed:",!condition,message);
exit(EXIT_FAILURE);
}
}
關於如何處理錯誤輸入的決策是一項關鍵的錯誤處理決策,也是一項關鍵的高層設計決策。常用處理技術:返回中立值、換用一一個正確的數據、返回與前次相同的數據、換用最接近的合法值、把警告信息記錄到日誌文件中、返回一個錯誤碼、調用錯誤處理子程序或對象、當錯誤發生時顯示出錯信息、用最妥當的方式在局部處理錯誤、閉關程序。
異常提供了一種與代碼正常流程角度不同的錯誤處理手段。如果留心使用異常,它可以成爲程序員們知識工具箱中一項有益補充,同時也應該在異常和其他錯誤處理手段之間進行權衡比較。常使用try- catch語句、try- catch-fianlly語句。
針對產品代碼的限制並不適用於開發中的軟件。你可以利用這一優勢在開發中添加有助於更快地排查錯誤的代碼。
9、僞代碼編程過程
僞代碼編程過程有助於減少設計和編寫文檔所需的工作量,同時提高這兩項工作的質量。
要點:
創建類和子程序通常都是一個迭代的過程。在創建子程序的過程中獲得的認識常常會反過來影響類的設計。
編寫好的僞代碼需要使用易懂的英語,要避免使用特定編程語言中才有的特性,同時要在意圖的層面上爲僞代碼(即描述該做什麼,而不是要怎麼去做)。
僞代碼編程過程是一個行之有效的做詳細設計的工具,它同時讓編碼工作更容易。僞代碼會直接轉化爲註釋,從而確保了註釋的準確度和實用性。
三、變量
10、使用變量的一般事項
要點:
數據初始化過程很容易出錯,所以請用本章描述的初始化方法來避免由於非預期的初始值而造成的錯誤。
最小化每個變量的作用域。把同一變量的引用點集中在一起。把變量限定在子程序或者類的範圍之內。避免使用全局數據。
把使用相同變量的語句儘可能集中在一起。
早期綁定會減少靈活性,但有助於減少複雜度。晚期綁定可以增加靈活性,同時增加複雜度。
把每個變量用於唯一的用途。
11、變量名的力量
變量命名規則可以參考著名的匈牙利命名法。比如:
全局變量 g_
常量 c_
c++類成員變量 m_
靜態變量 s_
指針 p
函數 fn
無效 v
句柄 h
長整型 l
布爾 b
浮點型 f
雙字 dw
字符串 sz
短整型 n
雙精度浮點 d
計數 c(通常用cnt)
字符 ch(通常用c)
整型 i(通常用n)
字節 by
字 w
實型 r
無符號 u
最大 Max
最小 Min
初始化 Init
臨時變量 T(或Temp)
源對象 Src
目的對象 Dest
要點:
好的變量名提高程序可讀性的一項關鍵要素。對特殊種類的變量,比如循環下標很狀態變量,需要加以特殊的考慮。
名字要儘可能地具體。那些太模糊或者太通用以致於能夠用於多種目的的名字都是很不好的。
命名規則應該能夠區分局部數據、類數據和全局數據。它們還應當可以區分類型名、具名常量、枚舉類型名字和變量名。
無論做那種類型項目,你都應該採用某種變量命名規則。你所採用的規則的種類取決於你程序的規模,以及項目成員的人數。
現在編程語言很少需要用到縮寫。如果你真的要使用縮寫,請使用項目縮寫詞典或者標準前綴來幫助理解縮寫。
代碼閱讀的次數遠遠多於編寫的次數。確保你所取的名字側重於閱讀方便而不是編寫方便。
12、基本數據類型
數值、整數、浮點數、字符和字符串、布爾變量、枚舉類型、具名常量、數組、創建類型
要點:
使用特定的數據類型就意味着要記住適用於各個類型的很多獨立的原則。
如果你的語言支持,創建自定義類型會使得你的程序更容易修改,並更具有自描述性。
當你用typedef或者其等價方式創建了一個簡單類型的時候,考慮是否更應該創建一個新的類。
13、不常見的數據類型
結構體、指針、全局數據
要點:
結構體可以使得程序更簡單、更容易理解,以及更容易維護。
每當你打算使用結構體的時候,考慮採用類是不是會工作得更好。
指針很容易出錯。用訪問器子程序或者類以及防禦式編程實踐來保護自己的代碼。
避免用全局變量,不只是因爲它們很危險,還是因爲你可以用其他更好的方法來取代它們。
如果你不得不使用全局變量,那麼就通過訪問器子程序來使用它。訪問器子程序爲你帶來全局變量所能帶來的一切優點,還有一些額外好處。
四、語句
14、組織直線型代碼
順序結構
要點:
組織直線型代碼的最主要原則是按照依賴關係進行排列。
可以用好的子程序名、參數列表、註釋。如果代碼足夠重要,內務管理變量來讓依賴關係變得更明顯。
如果代碼之間沒有順序依賴關係,那就設法使相關的語句儘可能地接近。
15、使用條件語句
if-else,switch-case-default
要點:
對於簡單的if-else語句,請注意if子句和else子句的順序。
爲了捕捉錯誤,可以使用case語句中的default子句,或者使用if-then-else語句中的最後那個esle子句。
16、控制循環
while for
要點:
循環很複雜。保持循環簡單將有助於別人閱讀你的代碼。
保持循環簡單的技巧包括:避免使用怪異的循環、減少嵌套層次、讓入口和出口一目瞭然、把內務操作代碼放在一處。
循環下標很容易被濫用。因此命名要準確,並且要把它們各自僅有用於一個用途。
仔細地考慮循環,確認它在每一種情況下都運行正常,並且在所有可能的條件下都能退出。
17、不常見的控制結構
子程序中的多出返回、遞歸、goto語句
要點:
多個return可以增強子程序的可讀性和可維護性,同時可以避免產生很深的嵌套邏輯。
遞歸能夠很優雅地解決一小部分問題。對它的使用要加倍小心。在使用遞歸的時候需要注意:確認遞歸能夠停止、使用安全計數器防止出現無窮遞歸、留心棧空間、不要用遞歸去計算階乘或者斐波那契數列。
除非萬不得已最好不要使用goto,因爲邏輯結構比較混亂。
18、表驅動法
表驅動法是一種編程模式,從表裏面查找信息而不是使用邏輯語句。事實上,凡是能通過邏輯語句來選擇的事物,都可以通過查表來選擇。對於簡單的情況而言,使用邏輯語句更爲容易和直白。但隨着邏輯鏈的越來越複雜,查表法也是愈發顯示地更具有吸引力。
要點:
表提供了一種複雜的邏輯和繼承結構的替換的方案。如果你發現自己對某個應用程序的邏輯和繼承樹關係感到困惑,那麼問問自己它是否可以通過一個查詢表來加以簡化。
使用表的一項關鍵決策是決定如何去訪問表。你可以採取直接訪問、索引訪問、階梯訪問。階梯訪問是通過確定每項命中的階梯層次確定其歸類,它命中的“臺階”確定其類屬。
使用表的另一項關鍵決策是決定應該把什麼內容放入表中。
19、一般控制問題
要點:
使用布爾表達式簡單可讀,將非常有助於提高你的代碼的質量。
深層次的嵌套使得子程序變得難以理解。所幸的是,你可以相對容易地避免這麼做。
結構化編程是一種簡單並且仍然使用的思想,你可以通過順序、選擇、循環三者組合起來而開發出任何程序。
將複雜度降低到最低水平是編寫高質量代碼的關鍵。
五、代碼改善
20、軟件質量
軟件同時擁有內在和外在的質量特性。外在特性指的是該產品的用戶所能夠感受到的部分,包括正確性、可用性、效率、可靠性、完整性、適應性、精確性、健壯性。內在特性包括可維護性、靈活性、可移植性、可重用性、可讀性、可測試性、可理解性。
21、協同構建
協同構建包括結對編程、正式檢查、非正式技術複查、文檔閱讀,以及其他開發人員共同承擔創建代碼及其他工作產品責任的技術。
22、開發者測試
單元測試、組件測試、集成測試
迴歸測試、自動化測試
要點:
開發人員測試是完整測試策略的一個關鍵部分。
同編碼之後編寫測試用例相比較,編碼開始之前編寫測試用例,工作量和花費的時間差不多,但是後者可以縮短缺陷-偵測-調試-修正這一週期。
即使考慮到了各種可用的測試手段,測試仍然只是良好軟件質量計劃的一部分。
你可以根據各種不同的思路產生很多測試用例,這些思路包括基礎測試、數據流分析、邊界分析、錯誤數據類型以及正確數據類型等。你還可以通過猜測錯誤的方式得到更多的測試用例。
錯誤往往集中在少數幾個容易出錯的類和子程序上,找出這部分代碼,重新設計和編寫它們。
23、調試
尋找缺陷的方法:
使用所有可用數據來構造你的假設。
不斷提煉產生錯誤的測試用例。
在自己的單元測試族中測試代碼。
藉助可以獲得的任何工具。
用不同的方式重現錯誤。
通過產生更多的數據來構造更多的假設。
用頭腦風暴的方式找出可能的假設。
在桌上放一個筆記本,把需要嘗試的事情列出來。
縮小被懷疑有問題的代碼區域。
對之前出現過問題的類和子程序保存警惕。
檢查最近修改的代碼。
擴展被懷疑有問題的代碼區域。
採用增量集成。
檢查常見的缺陷。
和其他人一起討論你的問題。
拋開問題休息一下。
列出所有的蠻力調試方法,逐條應用。
解決語法錯誤的方法:
不要太信任編譯器信息中給出的行號。
不要太信任編譯器信息。
分而治之,各個擊破。
使用具有語法分析功能的編譯器來找出位置錯誤的註釋和行號。
修正缺陷的方法:
在動手之前先理解程序。
驗證對錯誤的分析。
放鬆一下。
要保存最初的源代碼。
只有當理由充分的時候纔去修改代碼。
一次只做一個改動。
添加單元測試來暴露代碼中缺陷。
要點:
在動手解決問題之前,要理解問題的根本。
將編譯器警告級別設置爲最嚴格,把警告信息說報告的錯誤都改正。
調試工具對軟件開發而言是強有力的支持手段。
24、重構
數據級別重構:
用具名常量來代替神祕數值。
用更明確或更具信息量的名字來重命名變量。
將表達式內聯化。
用函數來代替表達式。
引入中間變量。
將多用途變量轉換爲多個單一用途變量。
將一組類型碼轉化爲類或枚舉類型或含派生類的類。
將數組轉化爲對象。
封裝羣集。
用數據類替代傳統記錄。
語句級的重構:
分解布爾表達式。
將複雜的布爾表達式轉換爲命名精確的布爾函數。
將條件語句中不同部分中的重複代碼合併。
子程序級的重構:
提取子程序。
將子程序代碼內聯化。
將冗長的子程序轉化爲類。
用簡單的算法替代複雜算法。
類實現的重構:
將值對象改爲引用對象。
用數據初始化來替代虛函數。
改變成員函數或者數據的位置。
將特定的代碼提出生產派生類。
將相似的代碼合併起來放到基類中。
類接口的重構:
將一個類轉化成兩個。
引入外部子程序。
引入擴展類。
封裝暴露在外的成員變量。
系統級的重構:
使用工廠函數而非簡單的構造函數。
要點:
重構成功的另一要素是程序員應當掌握大量特定的重構方法。
重構成功的最後要點在於要有安全重構的策略。
25、代碼調整策略
代碼調整方法:
1)用設計良好的代碼來開發軟件,從而使程序易於理解和修改。
2)如果程序性能很差。
a、保存代碼的可運行版本,這樣你才能回到“最近的已知正常狀態”;
b、對系統進行分析測量,找出熱點;
c、判斷性能差劣是否源於設計、數據類型或算法上的缺陷,確定是否應該做代碼調整,如果不是,請跳回第一步;
d、對步驟c中所確定的瓶頸代碼進行調整;
e、每次調整後都要對性能提升進行測量;
f、如果調整沒有改進代碼的性能,就恢復到步驟a保存的代碼。
3)重複步驟2。
要點:
性能只是軟件整體質量的一個方面,通常不是最重要的。
絕大多數的程序都有那麼一小部分代碼耗費了絕大部分的運行時間。如果沒有測量,你不會知道是哪一部分代碼。
26、代碼調整技術
要點:
優化結果在不同的語言、編譯器和環境下有很大的差異。如果沒有對每一次的優化進行測量,你將無法判斷優化到底是幫助還是損害了這個程序。
第一次優化通常不會是最好的。即使找到了效果很不錯的,也不要停下擴大戰果的步伐。
六、系統考慮
27、程序規模對構建的影響
要點:
隨着項目規模的擴大,交流需要加以支持。
放大輕量級的方法論要好於縮小重量級的方法論。最有效的辦法是使用“適量級”方法論。
28、管理構建
配置管理、備份計劃
要點:
好的軟件評估是一項重大挑戰。成功的關鍵包括採用多種方法、隨着項目的開展而修繕評估結果。
度量是構建管理成功的關鍵。準確的度量是制定準確的進度表、質量控制和改進開發過程的關鍵。
程序員和管理人員都是人,在把他們當人看的時候工作得最好。
29、集成
集成是一種軟件開發行爲:將一些獨立的軟件組件組合爲一個完整系統。
集成頻率——階段式集成與增量集成
增量集成有若干變型,而且任何一種形式的增量集成都比階段式集成好。
針對每個特定的項目,最佳的集成步驟通常是自頂向下、自底向上、風險導向及其他集成方法的某種組合。T型集成和豎直分塊集成通常都能工作得很好。
30、編程工具
設計工具(Rose、visio)、源代碼工具(IDE集成工具、diff比較工具、版本控制工具、生產接口文檔工具)、可執行工具(編譯器與連接器、Build工具、程序庫、代碼生成工具、安裝程序的工具)
測試,下列功能特性和工具有助於你進行有效的測試:
自動測試框架,如JUnit、NUnit、CppUnit等
測試用例的記錄和回放工具
覆蓋率監視器
系統擾動器(內存填充工具、內存“抖動”工具、內存訪問檢查器)
Diff工具(比較數據穩健、圖像等)
缺陷注入工具
缺陷跟蹤工具
要點:
好的工具能讓你的日子過得安逸很多。
下面這些工具可用:編輯、分析代碼質量、重構、版本控制、除錯、測試、代碼調整。
七、軟件工藝
31、佈局與風格
計算機編程美學話題——程序源代碼的佈局。佈局好的代碼看起來不僅感覺漂亮而且結構也清晰。
佈局技巧
用空格可以提高可讀性。空格包括空格、製表符、斷開行及空行。用空格得到羅好的佈局:
分組—— 一個代碼段只能包含有關的、爲完成某一任務而組織在一起的語句。
空格——在程序段的開頭應當加空格表明
對方——把同屬性的元素對齊。例如同一類語句的排成一條直線下來
縮排——當一個句子在邏輯上從屬於上一個句子時,這個名子就比上一行退幾格
要儘量的多用括號,有的時候表達式可能不需要使用括號,但加上括號會使表達的更清晰。
佈局風格
控制語句下的一組語句要區分成一組,再括起來。純塊結構、行尾佈局這些名詞化了,其實都見過的,“行尾佈局”沒用過,感覺這個最不好。文中給出的缺點是:無連續性而且難修改。
控制結構佈局
單條語句佈局
單條語句常規限制不超過80個字符,原因:超過了很難讀、80個字符也防止了深層嵌套、80字符內方便打印。單條語句佈局建議:
邏輯表達式使用空格可增強可讀性
用空格使數組下標更好讀
用空格使子程序參數更好讀
使續行明顯 放在第一行那部分要清楚的表明它是語句的一部分。最好方法是第一行部分獨立出來則有明顯的語法錯誤
把緊密關聯的元素放在一起
子程序調用的續行可後退標準格數
使續行的結尾易於發現
每行僅寫一條語句
數據類型定義佈局
注意數據類型定義的對齊
每行定義一個數據
有意識地安排定義順序
註釋佈局
一個好的註釋能極大提高程序的可讀性,若註釋不成功,則會幫倒忙。
註釋行與相應的代碼行同樣縮排
把註釋行至少用一個空行分開
子程序佈局
如何將子程序佈局好,以下幾點作爲參考:
用空行把子程序各部分分開
對子程序的參數用標準縮排
文件、模塊和程序佈局
把一板塊放在一個文件裏
把一個文件內的子程序區分清楚
如果文件裏有多個模塊,要把這些模塊區分得清清楚楚
各子程序按照字母順序排列
在C中,細心地組織源文件
要點:
可視化佈局的首要任務是指明代碼的邏輯組織。評估該任務是否實現的指標包括準確性、一致性、易讀性和易維護性。
佈局的很多方面涉及信仰問題。
32、自說明代碼
33、個人性格
聰明和謙虛、求知慾、誠實、交流與合作、創造力和紀律、懶惰、堅持、經驗、習慣。
一些培養求知慾和把學習當做第一要務的特定方法:在開發過程中建立自我意識、對編程和開發過程做試驗、閱讀解決問題的有關方法、在行動之前做分析和計劃、學習成功項目的開發經驗、閱讀文檔、閱讀其他書本期刊、同專業人士交往。
最有關係的性格爲:謙虛、求知慾、誠實、創造力和紀律、高明的偷懶。
很多程序員不願意主動吸收新知識和技術,只依靠工作時偶爾接觸新的信息。如果你能抽出少量時間閱讀和學習編程知識,要不了多久就能鶴立雞羣。
好性格與培養正確的習慣關係很大。要成爲傑出的程序員,先要養成良好的習慣,其他自然水到渠成。
什麼思想決定什麼樣行爲;什麼樣行爲決定什麼樣的習慣,什麼樣的習慣決定什麼樣性格,而什麼性格決定什麼樣的命運。
34、軟件工藝的話題
合作開發要求團隊成員之間進行廣泛溝通,其次同計算機交互;而單人開發是自我交流,其次纔是與計算機。
編程應基於問題域而非解決方案,這樣便於複雜度管理。
開發時迭代次數越多,產品的質量越好。
墨守成規的方法有悖於高質量的軟件開發。請將編程工具箱中填滿各種編程工具,不斷提高自己挑選合適工具的能力。
35、何處有更多信息
軟件開發資料庫由幾類信息構成:一類核心數據解釋有效編程的基礎概念;相關的數據解釋編程技術管理及知識背景;還有關於編程語言、操作系統、環境和硬件等面向特定主題的參考書等。