2012年12月22日 星期六

《編程創藝:編寫出卓越的程式碼》筆記

一、善於防守

1.不要做任何設想。沒有記錄下來的設想會不斷地造成缺陷。


2.一定要在完成與一個程式碼段相關的所有任務之後,再進入下一個環節。


3.對於在執行期間所獲取的任何資源,必須徹底釋放。


4.在宣告位置要記得初始化所有的變數


5.盡可能推遲一些變數的宣告,可以使變數的宣告位置與使用它的位置盡量接近,以防止它干擾到程式碼的其他部分。


6.不要在多個地方重複使用同一個臨時變數。


7.在主要的函數中放置前置條件和後置條件,並且在關鍵的迴圈中放置不變條件。


8.只有在「限制測試」時才可以使用斷言(「程式在指定位置」狀態的描述)。


9.當你發現並修正了一個錯誤時,要在修正錯誤的地方加入一條斷言。




二、精心佈局

1.編寫程式時,永遠記得要為程式碼的讀者著想。




三、名正言順

1.在進行命名時,要將重點放在清晰而非簡潔上。


2.變數通常是以名詞名詞化的動詞來命名。


3.型別名稱通常都有一個大寫的首字母,而變數則具有小寫的首字母。


4.函數的命名通常是動詞。名稱要能描述邏輯上的操作,而不是具體的實作過程。


5.當類別是用來描述一些具有狀態的資料物件時,其名稱應為名詞;若是實作某種虛擬回調介面的函數物件或類別,則應為動詞


6.巨集的名稱必須全部使用大寫表示。




四、不言自明

1.如果你要定義一個永遠都不改變的值,記得強制將它定義為一個常數型別。


2.如果一個變數不應當包含負值,就應使用無符號型別。


3.使用列舉來描述一組相關的值。


4.在類別中要按一定順序進行宣告。公共資訊應該放在首位;將私有的實做細節放在最後。


5.每行只寫一條敘述,並保持每條敘述都很簡單。


6.務必要限制嵌套的條件敘述數量。


7.確保所有重要的程式碼都非常突出。將目標讀者不關心的任何內容都隱藏起來。


8.將所有相關的資訊放在同一個地方。一個元件的 API 應該放在同一個檔案內。


9.在檔案的頂部放置一個註解區塊,用來描述檔案的內容以及該檔案所屬的專案。


10.只有在當你無法以其他方式提高程式碼清晰度的情況下,才添加註解。


11.對於每個公共可見的物件,都編寫一到兩句的描述。


12.如果變數或參數的用途不明確時,要將它們文件化。若它們的名稱已使其用途很明顯時則不必。


13.如果函數的一些參數用於輸入,另一些用於輸出,則在其描述中要把這種情況寫清楚,並文件化。


14.文件化任何一個函數的前置或後置條件,可能會拋出什麼例外,以及對函數造成的副作用等。




五、隨篇註解

1.註解不應該描述程式是怎樣執行的,而是描述這段程式碼的目的,或是下一個敘述區塊最終要發生什麼作用。


2.不要用註解來描述變數的用法,而是要重新命名那個變數。


3.塊狀註解的寫法:
/*
 * A
 * B
 */


4.註解不應該截斷程式碼,或者打亂邏輯流程。要讓註解的內縮位置與周圍的程式碼保持一致。


5.所有的原始檔案都應該以描述其內容的註解區塊作為開頭。這個檔頭註解是概述、前言。


6.檔頭註解不應該包含容易過時的資訊。


7.利用註解幫助編寫例行程式的做法:以註解構造程式碼的結構,然後在各行註解的下面填寫程式碼。每當任務結束時,檢查剩下的註解是否仍有用。


8.註解不要描述已經改變的事情,也不要講述某個事物過去是做什麼的。註解是要活在現在


9.當你修改程式碼時,記得一併維護程式碼周圍的所有註解。




六、人非聖賢

1.絕對不要忽視任何一種狀況。如果你不知道該怎麼處理,就向調用程式碼發送一個故障信號。


2.將函數設定一個共用的全域錯誤變數,而不要返回一個原因的程式碼。在調用函數之後,你必須查看這個狀態變數,以確定函數是否已經成功地完成。


3.在每個函數中都必須執行的錯誤檢查清單:

(1)檢查所有的函數參數。確保你得到了正確和前後一致的輸入,並考慮使用斷言。

(2)檢查執行過程中關鍵點處的不變條件是否都滿足。

(3)在使用任何來自外部的值之前都要檢查其有效性。

(4)檢查所有系統調用和其它下級函數調用的返回狀態。




七、測試時代

1.測試只能證明存在缺陷,而不能證明不存在缺陷。


2.當你開始考慮編寫某段程式碼時,就必須同時考慮如何對它進行測試。


3.不要輕易信任你所讀到的程式碼,要以批評的眼光來閱讀所有的程式碼。


4.測試的種類:

(1)單元測試:針對類別或函數的測試。要以嚴密隔離的方式執行。與某個單元相連的任何非信賴的外部程式碼,都應替換為「虛位程式」或「模擬程式」,確保你只是搜尋位於這個單元中的 bug,而不是那些外部造成的 bug。

(2)元件測試:為單元測試的下一步,用於驗證由一個或多個單元組成的完整元件的行為。

(3)整合測試:對放入一個系統中的元件的組合進行測試,確保它們之間正確地相連。

(4)回歸測試:針對軟體或其環境進行修改之後重新進行的測試。

(5)負載測試:確保程式可以處理拋給它的預期資料量。可發掘出與系統相關的效率問題。

(6)壓力測試:在短時間內向程式碼拋出大量的資料,以觀察程式碼會出現什麼情況。

*負載測試的目的是驗證程式碼可以滿足它的預期要求;壓力測試的目的則是確保程式碼在受到真正的衝擊時不會亂作一團。

(7)疲勞測試:使程式碼在較高的負載下執行很長一段時間,以確定任何會在執行大量的操作之後出現的性能問題。

(8)可用性測試:把軟體放在現實世界的環境中,看用戶覺得怎麼樣。

(9)黑盒功能測試:將實際的功能與期望的功能進行比較。黑盒測試並不關心程式碼的每一行是否都得到了測試,只關心程式碼是否是否滿足軟體的規格。

(10)白盒結構測試:程式碼的每一行都要被系統仔細地檢查,以確保正確。白盒測試只關心對程式碼的各行進行測試,而不保證它們是否符合規格。

黑盒測試關心程式碼缺少什麼行為白盒測試關心程式碼功能上的缺陷


5.進行黑盒測試時可使用的測試案例:

(1)一些良好的輸入:覆蓋有效輸入值的整個範圍,包括一些處於中間位置的值、一些可取輸入下邊界附近的值和一些上邊界附近的值。

(2)一些不好的輸入:包括數值上太大或太小的值、負值、過長或過短的輸入、空字串、不同大小的陣列和列表、在內部不一致的資料值。

(3)邊界值:測試邊界值本身、剛好在邊界上方的值、剛好在邊界下方的值。

(4)隨機數據:編寫一個自動的測試用具,以重複生成並應用隨機的資料。

(5)


6.測試的設計原則:

(1)使各個程式碼部分都自給自足,而不要與外部世界建立未經說明的或缺乏實質的依賴關係。不要寫死與系統其他部分的連結,盡量依賴那些可以由系統元件或測試類比程式實作的抽象介面。

(2)不要依賴全域變數,將這種狀態集中到一個作為引數傳遞的共用結構中。

(3)限制你的程式碼複雜度,把程式碼分為較小的、可理解的、並可以單獨進行測試的程式碼區塊。

(4)保持程式碼的可觀察性。




八、尋找缺陷

1.確定缺陷位置的第一步,是查明如何可靠地使它再次出現




九、程式碼構建

1.對於每個構建規則,都要設定一個對應的撤銷所有操作的清理規則。


2.發行構建應總是從原始的原始碼樹進行,而不是從某人構建了一半的開發樹進行。你要確保將來一定可以從原始碼控制或備份存檔中獲得這些原始的原始碼。




十、追求速度

1.使程式的執行速度加快的六個步驟:

(1)確定程式執行得太慢,並證明你的確需要進行最佳化。

(2)找出執行得最慢的程式碼。以這段程式碼為目標。

(3)測試這段作為最佳化目標的程式碼的性能。

(4)對這段程式碼進行最佳化。

(5)測試最佳化後的程式碼是否仍然可以正常執行。

(6)測試速度增加了多少,並決定下一步要做什麼。


2.單獨最佳化你的程式碼,與其他所有工作都分開,確保結果不會影響其他的任務。


3.最佳化你的程式的發行構建,而不是開發構建。


4.執行計時測試時要考慮的事項:

(1)前後兩測試要使用完全相同的輸入資料集。

(2)在主要條件相同的情況下執行所有的測試。

(3)確保你的測試不依賴於用戶的輸入。


5.最佳化的技術:

(1)添加快取或緩衝層,以加快較慢的資料存取,或防止漫長的重複計算。預先計算那些你知道以後將會用到的值,並把它們儲存起來,以便可以快速存取。

(2)建立一個資源庫,以減少分配物件的執行成本。

(3)減少浮點數據的精度。

(4)更改資料的儲存模式或它在磁片上的表示法,以便適應高速運算。

(5)利用平行和執行緒來防止操作串列地執行。

(6)有效地運用執行緒,避免或移除過多的鎖定。使用靜態的檢查以證明哪些鎖是必須的,哪些不是。

(7)避免對例外過度的使用,它們會限制編譯器的最佳化。

(8)如果能節省程式碼空間,可以放棄某些特定的語言功能。

(9)對於複雜的計算,使用查表法,以時間換取空間。

(10)善用短路運算。將比較可能失敗的計算放在前面。

(11)使用標準的常式。

(12)將公用程式碼提取到共用函數中以避免重複。

(13)將極少使用的函數移開。把它們放入一個動態載入的程式庫或一個單獨的程式中。




十一、不安全感綜合症

1.緩衝溢出:當緩衝區位於堆疊上時。只要重寫堆疊中儲存的某個函數調用的返回位址,就可以控制 CPU 的行為。


2.嵌入的查詢字串:與緩衝溢出一樣,依賴於沒有對輸入進行分析,但是它不會突破緩衝區的邊界,而是利用隨後程式使用未被過濾的輸入所執行的操作。


3.競爭狀況:利用依賴於事件的執行順序,實作非預期行為或使程式碼當機。


4.整數溢出:當一種整數型別對於表示算術運算的結果來說太小時。




十二、崇尚設計

1.為了建立良好的介面必須執行的步驟:

(1)確定用戶,瞭解需求。

(2)確定供應端,瞭解能力。

(3)推斷所需的介面型別。

(4)確定操作的性質。


2.結構化設計的焦點在於系統必須執行的操作物件導向設計的焦點在於系統中的資料




十三、軟體架構

1.分層的架構:每個元件都由層次結構中的一個區塊來表示。層次結構中的位置表明了各個元件的位置,元件之間的相互關係。方塊可以並排地放在相同的層次上,甚至可以跨兩個層次。


2.管道和過濾器架構:需良好地定義各個過濾器之間的資料結構,每個過濾器只進行一些簡單的處理。

這種架構必須重複地對輸出資料進行編碼,然後沿著管道向下傳輸,並在隨後的每個過濾器中對這些料進行解析。好處是能使功能的添加變得簡單,壞處是難以處理錯誤訊息。


3.用戶端 / 伺服器兩層式架構:將功能分割成用戶端與伺服器兩大塊。


4.基於元件的架構:分散了控制權,將其分配到許多相互協作的狹立元件中,而不再是一個單一的結構。每個元件的公共介面通常都是用介面定義語言(IDL)定義的,並且獨立於任何實現之外。


5.框架:為一種可擴展的程式庫(通常是一組相互協作的類別),它為特定的問題域提供了可重用的設計解決方案。




十四、改良與革命

1.在修改一個檔案或程式碼模組之前,首先要明白:

(1)它在整個系統中處於什麼位置。

(2)它擁有什麼樣的依賴關係。

(3)程式碼建立之初曾作了哪些設想。

(4)已做了修改的歷史紀錄。


2.盡量不要在添加新程式碼時的同時引入額外的依賴關係。


3.在維護任何程式碼時,要保留你正在處理的原始檔案的程式設計風格。


4.使用程式碼的測試套件來檢查是否造成了什麼破壞,並透過全面的回歸測試再次檢視。




十五、團結就是力量

1.任何程式設計師都不應獨佔程式庫的任何一部份。團隊中的每個人都有權限存取所有程式碼,並可以在適當的時候做出修改。




十六、安全措施

1.關鍵不在於你編寫了多少的程式碼,而是在於你編寫程式碼的方式




十七、注意細節

1.需求規格包含:

(1)功能需求:詳細描述程式必須實作哪些功能。

(2)性能需求:說明軟體執行的速度應當有多快,以及是否有某些操作必須限時完成。

(3)互動操作性需求:描述軟體必須與之互動的其他軟體、硬體和外部系統。

(4)未來的操作需求:確定當前必須容納的功能,即使這些功能還不能馬上實作。


2.功能規格:描述軟體可以觀察到的行為。功能規格必須完整並清晰地描述它的公共介面,以及對它們的功能和使用方法的描述。

功能規格也描述了所有外部資料的結構和格式,以及與其他元件、工作包裹或規格之間的相互依賴關係。


3.系統架構規格:描述軟體解決方案的整體情況和結構,包括:

(1)物理的電腦佈局。

(2)軟體的元件構成。

(3)併發性(有多少個執行緒會同時執行)。

(4)資料儲存。

(5)系統架構的所有其他介面。


4.用戶介面規格:包含用戶介面的資訊:它看起來是什麼樣的,以及它會如何反應。


5.設計技術規格:描述元件的內部設計,描述功能規格將會怎樣或已經怎樣實作。設計規格描述了所有的內部 API、資料結構和格式。

它應該詳細描述所有重要的演算法、執行路徑和執行緒互動。它還描述了程式語言的選擇,以及用於構建程式碼的工具。


6.測試規格:描述對軟體的特定部分進行測試的規範。它說明如何根據軟體的功能規格來驗證它的實作,以便瞭解軟體何時可以發佈。

測試規格包含了一張所有必須執行的測試列表。每個測試都在測試腳本中詳細的描述:一組執行測試簡單的步驟,以及驗收指標和在其中執行測試的環境。


7.規格的編寫過程:

(1)選擇適當的文件樣板作為起步。如果沒有樣板,就以一個現有的規格作為基礎。

(2)編寫文件,內容取決於規格的型別。

(3)安排對文件進行審查。讓所有相關的人員都參與進來。

(4)一旦審查通過,就可以將帶有版本資訊的規格放入文件庫中,並將它發佈給相對應的讀者。

(5)如果以後出現任何問題,就對規格提出變更需求,並確保你瞭解修改會怎樣影響你的開發工作。




十八、程式碼審查

1.評估哪些程式碼需要審查:

(1)中心元件中的核心程式碼。

(2)執行分析器,看一看大部份 CPU 時間被用在哪裡,然後對那部分程式碼進行審查。

(3)執行複雜性分析工具,然後對那段最糟糕的程式碼進行審查。

(4)將目標鎖定在那些已呈現出高錯誤率的程式碼上。

(5)挑出那些你不信任的程式設計師所編寫的程式碼。


2.為了組織程式碼審查會議:

(1)作者通知大家程式碼已經準備就緒,可以進行審查。

(2)主持人安排會議。

(3)準備好所有必需的資源。

(4)提前發出會議通知。

(5)在發出會議通知之後,作者就不能再毫無理由地隨便更改程式碼。


3.程式碼審查會議的召開過程:

(1)主持人事先安排佈置好會議室。

(2)作者花幾分鐘的時間來解釋程式碼的目的,並介紹一下它的結構(會議召開前大家仍必須先閱讀過)。

(3)先邀請大家對結構上的設計發表意見。這些意見程式碼與實作的結構相關,而不是與程式碼的具體敘述相關。

(4)邀請大家對程式碼給出總體的意見。

(5)程式碼經歷逐步的排查(每次一行或一個程式碼區塊),以便尋找缺陷。

(6)考慮許多程式碼使用的場景範例,並對控制流進行研究。若有一套完整的測試單元,則這些測試將詳細描述所有需要分析的場景。

(7)秘書記錄下所有必須進行的更改(記錄檔案名稱和程式碼行號)。

(8)任何可能會影響程式庫其他部分的問題都將被記錄下來,留待進一步研究。




十九、程式祕方

1.瀑布模型:需求 > 設計 > 實現 > 整合與測試 > 維護。


2.結構化系統分析和設計方法SSADM):包括分析和設計,而不包括實現和測試。由以下五個主要步驟組成:

  • 可行性研究
  • 需求分析
  • 需求規格
  • 邏輯系統規格
  • 物理設計


3.V模型:需求 > 高級設計 > 低級設計 > 編寫程式 > 整合測試 > 系統測試 > 驗收測試。


4.原型設計:建立許多用後可扔掉的軟體系統原型。每個原型都要被評估,向客戶展示,並且客戶的回饋將用來製作下一個原型。此流程將一直進行,直到已經有足夠的資訊來開發和部署真正的產品。


5.迭代和增量開發:圍繞較小開發生命週期的許多往返(迭代)背靠背地運行(增量),每個週期向系統逐漸增加越來越多的功能,直到系統完成。每個小的生命週期的運行都遵循瀑布模型。


6.螺旋模型:建模成一個像螺旋的形狀。從中心開始,向外展開,進行流程後面的階段。功能將按照優先順序逐漸降低的順序進行定義和實現。

螺旋分為四個象限:

(1)目標設置:為該階段確定特定的目標。

(2)風險評估減少:確定和分析關鍵風險,並尋找減少這些風險的資訊。

(3)開發和驗證:為下一個開發階段選擇適當的模型。

(4)規劃:對項目進行複審,為下一回合的螺旋制定計劃。


7.敏捷開發:敏捷流程都有以下的中心原則:

(1)透過執行許多較小的迭代開發週期,使風險最小化。

(2)通過強調持續執行自動地回歸測試套件,而不是在開發結束時進行冗長的測試週期,從而使風險最小化。

(3)減少折磨中量級流程的文件。敏捷開發將程式碼本身看作設計和實現文件。

(4)強調人並致力於使溝通便利,寧可面對面的溝通,而不是透過文件。

(5)把「編寫中軟體」當作進度和性能的度量,而不是規格的編寫。開發人員通過在開發流程中修改程式碼來解決問題和回變化。


8.分階段交付:遵循一個連續的流程,直到架構設計,然後實施各個單獨的元件,並在每個完成時展示給客戶,如果有需要還會返回前一個開發步驟。


9.快速應用程式開發RAD):強調用戶的參與和較小的開發團隊,並且大量運用原型和自動工具。


10.RUP:依賴於 UML 圖,並使用「案例驅動」設計(案例描述了單個用戶的活動或與軟體系統的交互)。

沒有留言:

張貼留言