第一章:導論
1. 要養成一個習慣,經常花時間閱讀別人編寫的高品質代碼。
2. 要有選擇地閱讀代碼,同時,還要有自己的目標。您是想學習新的模式、編碼風格、還是滿足某些需求的方法?
3. 要注意並重視代碼中特殊的非功能性需求,這些需求也許會導致特定的實現風格。
4. 在現有的代碼上工作時,請與作者或維護人員進行必須的協調,以避免重複勞動或因此而產生厭惡情緒。
5. 請將從開放源碼軟體中得到的益處看作是一項貸款,儘可能地尋找各種方式來回報開放源碼社團。
6. 多數情況下,如果您想要瞭解“別人會如何完成這個功能呢?”,除了閱讀代碼以外,沒有更好的方法。
7. 在尋找 BUG時,請從問題的表現形式到問題的根源來分析代碼。不要沿著不相關的路徑(誤入岐途)
8. 我們要充分利用調度器,編譯器給出的警告或輸出的符號代碼,系統調用跟蹤器,資料庫結構化查詢語言的日誌機制、包轉儲工具和Windows的訊息偵查程式,定出BUG的位置。
9. 對於那些大型且組織良好的系統,您只需要最低限度地瞭解它的全部功能,就能夠對它做出修改。
10. 當向系統中增加新功能時,首先的任務就是找到實作類別似特性的代碼,將它作為待實現功能的模板。
11. 從特性的功能描述到代碼的實現,可以按照字串訊息,或使用關鍵詞來搜尋代碼。
12. 在移植代碼或修改介面時, 您可以通過編譯器直接定位出問題涉及的範圍,從而減少代碼閱讀的工作量。
13. 進行重構時,您從一個能夠正常工作的系統開始做起,希望確保結束時系統能夠正常工作。一套恰當的測試案例可以協助您滿足此項約束。
14. 閱讀代碼尋找重構機會時,先從系統的構架開始,然後逐步細化,能夠獲得最大的效益。
15. 代碼的可重用性是一個誘人的,但難以掌握的思想;降低期望就不會感到失望。
16. 如果您希望重要的代碼十分棘手,難以理解與分離,可以試著尋找粒度更大一些的包,甚至其他代碼。
17. 在複查軟體系統時,要注意,系統是由很多部分組成的,不僅僅只是執行語句。還要注意分析以下內容:檔案和目錄結構、產生和配置過程、使用者介面和系統的文檔。
18. 可以將軟體複查作為一個學習、講授、援之以手和接受協助的機會。
第二章:基本編程元素
19.第一次分析一個程式時, main是一個好的起始點.
20.層疊if-else if-...-else序列可以看作是由互斥選擇項組成的選擇結構.
21.有時, 要想瞭解程式在某一方面的功能, 運行它可能比閱讀原始碼更為恰當.
22.在分析重要的程式時, 最好首先識別出重要的組成部分.
23.瞭解局部的命名規範, 利用它們來猜測變數和函數的功能用途.
24.當基於猜測修改代碼時, 您應該設計能夠驗證最初假設的過程. 這個過程可能包括用編譯器進行檢查|引入斷言|或者執行適當的測試案例.
25.理解了代碼的某一部分, 可能協助你理解餘下的代碼.
26.解決困難的代碼要從容易的部分入手.
27.要養成遇到庫元素就去閱讀相關文檔的習慣; 這將會增強您閱讀和編寫代碼的能力.
28.代碼閱讀有許多可選擇的策略: 自底向上和自頂向下的分析|應用試探法和檢查注釋和外部文檔, 應該依據問題的需要嘗試所有這些方法.
29.for (i=0; i<n; i++)形式的迴圈執行n次; 其他任何形式都要小心.
30.涉及兩項不等測試(其中一項包括相等條件)的比較運算式可以看作是區間成員測試.
31.我們經常可以將運算式應用在樣本資料上, 藉以瞭解它的含義.
32.使用De Morgan法則簡化複雜的邏輯運算式.
33.在閱讀邏輯乘運算式時, 問題可以認為正在分析的運算式以左的運算式均為true; 在閱讀邏輯和運算式時, 類似地, 可以認為正在分析的運算式以左的運算式均為false.
34.重新組織您控制的代碼, 使之更為易讀.
35.將使用條件運行符? :的運算式理解為if代碼.
36.不需要為了效率, 犧牲代碼的易讀性.
37.高效的演算法和特殊的最佳化確實有可能使得代碼更為複雜, 從而更難理解, 但這並不意味著使代碼更為緊湊和不易讀會提高它的效率.
38.創造性的代碼布局可以用來提高代碼的易讀性.
39.我們可以使用空格|臨時變數和括弧提高運算式的易讀性.
40.在閱讀您所控制的代碼時, 要養成添加註釋的習慣.
41.我們可以用好的縮排以及對變數名稱的明智選擇, 提高編寫欠佳的程式的易讀性.
42.用diff程式剖析器的修訂曆史時, 如果這段曆史跨越了整體重新縮排, 常常可以通過指定-w選項, 讓diff忽略空白差異, 避免由於更改了進層次而引入的噪音.
43.do迴圈的迴圈體至少執行一次.
44.執行算術運算時, 當b=2n-1時, 可以將a&b理解為a%(b+1).
45.將a<<n理解為a*k, k=2n.
46.將a>>n理解為a/k, k=2n.
47.每次只分析一個控制結構, 將它的內容看作是一個黑盒.
48.將每個控制結構的控製表達式看作是它所包含代碼的斷言.
49.return, goto, break和continue語句, 還有異常, 都會影響結構化的執行流程. 由於這些語句一般都會終止或重新開始進行中的迴圈,因此要單獨推理它們的行為.
50.用複雜迴圈的變式和不變式, 對迴圈進行推理.
51.使用保持含義不變的變換重新安排代碼, 簡化代碼的推理工作.
第三章: 進階C資料類型
52.瞭解特定語言構造所服務的功能之後, 就能夠更好地理解使用它們的代碼.
53.識別並歸類使用指標的理由.
54.在C程式中, 指標一般用來構造鏈式資料結構|動態分配的資料結構|實現引用調用|訪問和迭代資料元素|傳遞數組參數|引用函數|作為其他值的別名|代表字串|以及直接存取系統記憶體.
55.以引用傳遞的參數可以用來返回函數的結果, 或者避免參數複製帶來的開銷.
56.指向數組元素地址的指標, 可以訪問位於特定索引位置的元素.
57.指向數組元素的指標和相應的數組索引, 作用在二者上的運算具有相同的語義.
58.使用全域或static局部變數的函數大多數情況都不可重新進入(reentrant).
59.字元指標不同於字元數組.
60.識別和歸類應用結構或共用體的每種理由.
61.C語言中的結構將多個資料元素集合在一起, 使得它們可以作為一個整體來使用, 用來從函數中返回多個資料元素|構造鏈式資料結構|映射資料在硬體裝置|網路連結和儲存介質上的組織方式|實現抽象資料類型|以及以物件導向的方式編程.
62.共用體在C程式中主要用於最佳化儲存空間的利用|實現多態|以及訪問資料不同的內部表達方式.
63.一個指標, 在初始化為指向N個元素的儲存空間之後, 就可以作為N個元素的數組來使用.
64.動態分配的內在塊可以電焊工地釋放, 或在程式結束時釋放, 或由記憶體回收行程來完成回收; 在棧上分配的記憶體塊當分配它的函數退出後釋放.
65.C程式使用typedef聲明促進抽象, 並增強代碼的易讀性, 從而防範可移植性問題, 並類比C++和Java的類聲明行為.
66.可以將typedef聲明理解成變數定義: 變數的名稱就是類型的名稱; 變數的類型就是與該名稱對應的類型.
第四章: C資料結構
67.根據底層的抽象資料類型理解顯式的資料結構操作.
68.C語言中, 一般使用內建的數群組類型實現向量, 不再對底層實現進行抽象.
69.N個元素的數組可以被序列for (i=0; i<N; i++)完全處理; 所有其他變體都應該引起警惕.
70.運算式sizeof(x)總會得到用memset或memcpy處理數組x(不是指標)所需的正確位元組數.
71.區間一般用區間內的第一個元素和區間後的第一個元素來表示.
72.不對稱區間中元素的數目等於高位邊界與低位邊界的差.
73.當不對稱區間的高位邊界等於低位邊界時, 區間為空白.
74.不對稱區間中的低位邊界代表區間的第一個元素; 高位邊界代表區間外的第一個元素.
75.結構的數組常常表示由記錄和欄位組成的表.
76.指向結構的指標常常表示訪問底層記錄和欄位的遊標.
77.動態分配的矩陣一般儲存為指向數組列的指標或指向元素指標的指標; 這兩種類型都可以按照二維數組進行訪問.
78.以數組形式儲存的動態分配矩陣, 用自訂訪問函數定位它們的元素.
79.抽象資料類型為底層實現元素的使用(或誤用)方式提供一種信心的量度.
80.數組用從0開始的順序整數為鍵, 組織尋找表.
81.數組經常用來對控制結構進行高效編碼, 簡化程式的邏輯.
82.通過在數組中每個位置儲存一個資料元素和一個函數指標(指向處理資料元素的函數), 可以將代碼與資料關聯起來.
83.數組可以通過儲存供程式內的抽象機(abstract machine)或虛擬機器(virtual machine)使用的資料或代碼, 控製程序的運作.
84.可以將運算式sizeof(x) / sizeof(x[0])理解為數組x中元素的個數.
85.如果結構中含有指向結構自身|名為next的元素, 一般說來, 該結構定義的是單向鏈表的結點.
86.指向鏈表結點的持久性(如全域|靜態或在堆上分配)指標常常表示鏈表的頭部.
87.包含指向自身的next和prev指標的結構可能是雙向鏈表的結點.
88.理解複雜資料結構的指標操作可以將資料元素畫為方框|指標畫為箭頭.
89.遞迴資料結構經常用遞迴演算法來處理.
90.重要的資料結構操作演算法一般用函數參數或模板參數來參數化.
91.圖的結點常常順序地儲存在數組中, 連結到鏈表中, 或通過圖的邊連結起來.
92.圖中的邊一般不是隱式地通過指標, 就是顯式地作為獨立的結構來表示.
93.圖的邊經常儲存為動態分配的數組或鏈表, 在這兩種情況下, 邊都錨定在圖的結點上.
94.在無向圖中, 表達資料時應該將所有的結點看作是等同的, 類似地, 進行處理任務的代碼也不應該基於它們的方向來區分邊.
95.在非連通圖中, 執行遍曆代碼應該能夠接通孤立的子圖.
96.處理包含迴路的圖時, 遍曆代碼應該避免在處理圖的迴路進入迴圈.
97.複雜的圖結構中, 可能隱藏著其他類型的獨立結構.
第五章: 進階控制流程程
98.採用遞迴定義的演算法和資料結構經常用遞迴的函數定義來實現.
99.推理遞迴函式時, 要從基準落伍測試開始, 並認證每次遞迴調用如何逐漸接近非遞迴基準範例代碼.
100.簡單的語言常常使用一系列遵循該語言文法結構的函數進行文法分析.
101.推理互遞迴函式時, 要基於底層概念的遞迴定義.
102.尾遞迴調用等同於一個回到函數開始處的迴圈.
103.將throws子句從方法的定義中移除, 然後運行Java編譯器對類的原始碼進行編譯, 就可以容易地找到那些可能隱式地產生異常的方法.
104.在多處理器電腦上啟動並執行代碼常常圍繞進程或線程進行組織.
105.工作群並行模型用於在多個處理器間分配工作, 或者建立一個任務池, 然後將大量需要處理標準化的工作進行分配.
106.基於線程的管理者/工人並行模型一般將耗時的或阻塞的操作分配給工人子任務, 從而維護中心任務的響應性.
107.基於進程的管理者/工人並行模型一般用來重用現有的程式, 或用定義良好的介面組織和分離粗粒度的系統模組.
108.基於流水線的平行處理中, 每個任務都接收到一些輸入, 對它們進行一些處理, 並將產生的輸出傳遞給下一個任務, 進行不同的處理.
109.競爭條件很難捉摸, 相關的代碼常常會將競爭條件擴散到多個函數或模組; 因而, 很難隔離由於競爭條件導致的問題.
110.對於出現在訊號處理器中的資料結構作業碼和庫調用要保持高度警惕.
111.在閱讀包含宏的代碼時, 要注意, 宏既非函數, 也非語句.
112.do…while(0)塊中的宏等同於控制塊中的語句.
113.宏可以訪問在它的使用點可見的所有局部變數.
114.宏調用可改變參數的值
115.基於宏的標記拼接能夠建立新的標記符.
第六章: 應對大型項目
116.我們可以通過瀏覽項目的原始碼樹—包含項目原始碼的層次目錄結構, 來分析一個項目的組織方式. 源碼樹常常能夠反映出項目在構架和
軟體過程上的結構.
117.應用程式的原始碼樹經常是該應用程式的部署結構的鏡像.
118.不要被龐大的原始碼集合嚇倒; 它們一般比小型的專門項目組織得更出色.
119.當您首次接觸一個大型項目時, 要花一些時間來熟悉項目的分類樹結構.
120.項目的原始碼遠不只是編譯後可以獲得可執行程式的電腦語言指令; 一個項目的源碼樹一般還包括規格說明|終端使用者和開發人員文檔|
測試指令碼|多媒體資源|編譯工具|例子|本地化檔案|修訂曆史|安裝過程和許可資訊.
121.大型項目的編譯過程一般聲明性地藉助依賴關係來說明. 依賴關係由工具程式, 如make及其派生程式, 轉換成具體的編譯行動.
122.大型項目中, 製作檔案常常由配置步驟動態地產生; 在分析製作檔案之前, 需要先執行項目特定的配置.
123.檢查大型編譯過程的各個步驟時, 可以使用make程式的-n開關進行預演.
124.修訂控制系統提供從儲存庫中擷取原始碼最新版本的方式.
125.可以使用相關的命令, 顯示可執行檔中的修訂標識關鍵字, 從而將可執行檔與它的原始碼匹配起來.
126.使用修訂日誌中出現的bug跟蹤系統內的編號, 可以在bug跟蹤系統的資料庫中找到有關的問題的說明.
127.可以使用修訂控制系統的版本儲存庫, 找出特定的變更是如何?的.
128.定製編譯工具用在軟體開發過程的許多方面, 包括配置|編譯過程管理|代碼的產生|測試和文檔編製.
129.程式的調試輸出可以協助我們理解程式控制流程程和資料元素的關鍵區段.
130.跟蹤語句所在的地點一般也是演算法啟動並執行重要部分.
131.可以用斷言來檢驗演算法運作的步驟|函數接收的參數|程式的控制流程程|底層硬體的屬性和測試案例的結果.
132.可以使用對演算法進行檢驗的斷言來證實您對演算法運作的理解, 或將它作為推理的起點.
133.對函數參數和結果的斷言經常記錄了函數的前置條件和後置條件.
134.我們可以將測試整個函數的斷言作為每個給定函數的規格說明.
135.測試案例可以部分地代替函數規格說明.
136.可以使用測試案例的輸入資料對原始碼序列進行預演.
第七章: 編碼規範和約定
137.瞭解了給定程式碼程式庫所遵循的檔案組織方式後, 就能更有效率地瀏覽它的原始碼.
138.閱讀代碼時, 首先要確保您的編輯器或優美列印程式的tab設定, 與代碼遵循的風格規範一致.
139.可以使用代碼塊的縮排, 快速地掌握代碼的總體結構.
140.對編排不一致的代碼, 應該立即給予足夠的警惕.
141.分析代碼時, 對標記為XXX, FIXME和TODO的代碼序列要格外注意: 錯誤可能就潛伏在其中.
142.常量使用大寫字母命名, 單詞用底線分隔.
143.在遵循Java編碼規範的程式中, 包名(package name)總是從一個頂級的網域名稱開始(例如, org, com), 類名和介面名由大寫字母開始, 方法
和變數名由小寫字母開始.
144.使用者介面控制項名稱之前的匈牙利記法的首碼類型標記可以協助我們確定它的作用.
145.不同的編程規範對可移植構造的構成有不同的主張.
146.在審查代碼的可移植性, 或以某種給定的編碼規範作為指南時, 要注意瞭解規範對可移植性需求的界定與限制.
147.如果GUI功能都使用相應的編程結構來實現, 則通過代碼審查可以輕易地驗證給定使用者介面的規格說明是否被正確地採用.
148.瞭解項目編譯過程的組織方式與自動化方式之後, 我們就能夠快速地閱讀與理解對應的編譯規則.
149.當檢查系統的發布過程時, 常常可以將相應發行格式的需求作為基準.
第八章: 文檔
150.閱讀代碼時, 應該儘可能地利用任何能夠得到的文檔.
151.閱讀一小時代碼所得到的資訊只不過相當於閱讀一分鐘文檔.
152.使用系統的規格說明文檔, 瞭解所閱讀代碼的運行環境.
153.軟體需求規格說明是閱讀和評估代碼的基準.
154.可以將系統的設計規格說明作為認知代碼結構的路線圖, 閱讀具體代碼的指引.
155.測試規格說明文檔為我們提供可以用來對代碼進行預演的資料.
156.在接觸一個未知系統時, 功能性的描述和使用者指南可以提供重要的背景資訊,從而更好地理解閱讀的代碼所處的上下文.
157.從使用者參考手冊中, 我們可以快速地擷取, 應用程式在外觀與邏輯上的背景知識, 從管理員手冊中可以得知代碼的介面|檔案格式和錯誤消
息的詳細資料.
158.利用文檔可以快捷地擷取系統的概況, 瞭解提供特定特性的代碼.
159.文檔經常能夠反映和提示出系統的底層結構.
160.文檔有助於理解複雜的演算法和資料結構.
161.演算法的文字描述能夠使不透明(晦澀, 難以理解)的代碼變得可以理解.
162.文檔常常能夠闡明原始碼中標識符的含義.
163.文檔能夠提供非功能性需求背後的理論基礎.
164.文檔還會說明內部編程介面.
165.由於文檔很少像實際的程式碼那樣進行測試, 並受人關注, 所以它常常可能存在錯誤|不完整或過時.
166.文檔也提供測試案例, 以及實際應用的例子.
167.文檔常常還會包括已知的實現問題或bug.
168.環境中已知的缺點一般都會記錄在原始碼中.
169.文檔的變更能夠標出那些故障點.
170.對同一段原始碼重複或互相衝突的更改, 常常表示存在根本性的設計缺陷, 從而使得維護人員需要用一系列的Hotfix來修複.
171.相似的修複應用到原始碼的不同部分, 常常表示一種易犯的錯誤或疏忽, 它們同樣可能會在其他地方存在.
172.文檔常常會提供不恰當的資訊, 誤導我們對原始碼的理解.
173.要警惕那些未歸檔的特性: 將每個執行個體歸類為合理|疏忽或有害, 相應地決定是否應該修複代碼或文檔.
174.有時, 文檔在描述系統時, 並非按照已完成的實現, 而是系統應該的樣子或將來的實現.
175.在原始碼文檔中, 單詞gork的意思一般是指”理解”.
176.如果未知的或特殊用法的單詞阻礙了對代碼的理解, 可以試著在文檔的術語表(如果存在的話)|New Hacker’s Dictionary[Ray96]|或在Web搜尋引擎中尋找它們.
177.總是要以批判的態度來看待文檔, 注意非傳統的來源, 比如注釋|標準|出版物|測試案例|郵件清單|新聞群組|修訂日誌|問題追蹤資料庫|營銷材料|原始碼本身.
178.總是要以批判的態度來看待文檔; 由於文檔永遠不會執行, 對文檔的測試和正式複查也很少達到對代碼的同樣水平, 所以文檔常常會誤導讀者, 或者完全錯誤.
179.對於那些有缺陷的代碼, 我們可以從中推斷出它的真實意圖.
180.在閱讀大型系統的文檔時, 首先要熟悉文檔的總體結構和約定.
181.在對付體積龐大的文檔時, 可以使用工具, 或將文本輸出到高品質輸出裝置上, 比如雷射印表機, 來提高閱讀的效率.
第九章: 系統構架
182.一個系統可以(在重大的系統中也確實如此)同時出多種不同的構架類型. 以不同的方式檢查同一系統|分析系統的不同部分|或使用不同層級的分解, 都有可能發現不同的構架類型.
183.協同式的應用程式, 或者需要協同訪問共用資訊或資源的半自治進程, 一般會採用集中式儲存庫構架.
184.黑板系統使用集中式的儲存庫, 儲存非結構化的鍵/值對, 作為大量不同代碼元件之間的通訊集線器.
185.當處理過程可以建模|設計和實現成一系列的資料變換時, 常常會使用資料流(或管道—過濾器)構架.
186.在批量進行自動資料處理的環境中, 經常會採用資料流構架, 在對資料工具提供大量支援的平台上尤其如此.
187.資料流構架的一個明顯徵兆是: 程式中使用臨時檔案或流水線(pipeline)在不同進程間進行通訊.
188.使用圖示來建模物件導向構架中類的關係.
189.可以將原始碼輸入到建模工具中, 逆向推匯出系統的構架.
190.擁有大量同級子系統的系統, 常常按照分層構架進行組織.
191.分層構架一般通過堆疊擁有標準化介面的軟體組件來實現.
192.系統中每個層可以將下面的層看作抽象實體, 並且(只要該層滿足它的需求說明)不關心上面的層如何使用它.
193.層的介面既可以是支援特定概念的互補函數族, 也可以是一系列支援同一抽象介面不同底層實現的可互換函數.
194.用C語言實現的系統, 常常用函數指標的數組, 表達層介面的多工操作.
195.用物件導向的語言實現的系統, 使用虛方法調用直接表達對層介面的多嘴複用操作.
196.系統可以使用不同的|獨特的層次分解模型跨各種座標軸進行組織.
197.使用程式切片技術, 可以將程式中的資料和控制之間依賴關係集中到一起.
198.在並發系統中, 一個單獨的系統組件起到集中式管理器的作用, 負責啟動|停止和協調其他系統進程和任務的執行.
199.許多現實的系統都會博採眾家之長. 當處理此類系統時, 不要徒勞地尋找無所不包的構架圖; 應該將不同構架風格作為獨立但相關的實體來進行定位|識別並瞭解.
200.狀態變遷圖常常有助於理清狀態機器的動作.
201.在處理大量的代碼時, 瞭解將代碼分解成單獨單元的機制極為重要.
202.大多數情況下, 模組的物理邊界是單個檔案|組織到一個目錄中的多個檔案或擁有統一首碼的檔案的集合.
203.C中的模組, 由提供模組公開介面的標頭檔和提供對應實現的源檔案組成.
204.對象的建構函式經常用來分配與對象相關的資源, 並初始化對象的狀態. 函數一般用來釋放對象在生命期中佔用的資源.
205.對象方法經常使用類欄位來儲存控制所有方法運作的資料(比如尋找表或字典)或維護類運作的狀態資訊(例如, 賦給每個對象一個標識符的計數器).
206.在設計良好的類中, 所有的欄位都應在聲明為private, 並用公開的存取方法提供對它們的訪問.
207.在遇到friend聲明時, 要停下來分析一下, 看看繞過類封裝在設計上的理由.
208.可以有節制地用運算子增強特定類的可用性, 但用運算子多載, 將類實現為擁有內建算術類型相關的全部功能的類實體, 是不恰當的.
209.泛型實現不是在編譯期間通過宏替換或語言所支援的功能(比如C++模板和Ada的泛型包)來實現, 就是在運行期間通過使用資料元素的指標和函數的指標|或對象的多態性實現.
210.抽象資料類型經常用來封裝常用的資料群組織方案(比如樹|列表或棧), 或者對使用者隱藏資料類型的實現細節.
211.使用庫的目的多種多樣: 重用原始碼或目標代碼, 組織模組集合, 組織和最佳化編譯過程, 或是用來實現應用程式各種特性的按需載入.
212.大型的|分布式的系統經常實現為許多互相協作的進程.
213.對於基於文本的資料儲存庫, 可以通過瀏覽儲存在其中的資料, 破譯出它的結構.
214.可以通過查詢資料字典中的表, 或使用資料庫專有的SQL命令, 比如show table, 來分析關係型資料庫的模式.
215.識別出重用的構架元素後, 可以尋找其最初的描述, 瞭解正確地使用這種構架的方式, 以及可能出現的誤用.
216.要詳細分析建立在某種架構之上的應用程式, 行動的最佳路線就是從研究架構自身開始.
217.在閱讀嚮導產生的程式碼時, 不要期望太高, 否則您會感到失望.
218.學習幾個基本的設計模式之後, 您會發現, 您查看代碼構架的方式會發生改變: 您的視野和詞彙將會擴充到能夠識別和描述許多通用的形式.
219.頻繁使用的一些模式, 但並不顯式地指出它們的名稱, 這是由於構架性設計的重用經常先於模式的形成.
220.請試著按照底層模式來理解構架, 即使代碼中並沒有明確地提及模式.
221.大多數解譯器都遵循類似的處理構架, 圍繞一個狀態機器進行構建, 狀態機器的操作依賴於解譯器的目前狀態|程式指令和程式狀態.
222.多數情況下, 參考構架只是為應用程式定義域指定一種概念性的結構, 具體的實現並非必須遵照這種結構.
第十章: 代碼閱讀工具
223.詞彙工具可以高效地在一個大代碼檔案中或者跨多個檔案尋找某種模式.
224.使用程式編輯器和Regex尋找命令, 瀏覽龐大的原始碼檔案.
225.以唯讀方式瀏覽原始碼檔案.
226.使用Regex ^function name 可以找出函數的定義.
227.使用Regex的字元類, 可以尋找名稱遵循特定模式的變數.
228.使用Regex的否定字元類, 可以避免非積極匹配.
229.使用Regex symbol-1. *symbol-2, 可以尋找出現在同一行的符號.
230.使用編輯器的 tags 功能, 可以快速地找出實體的定義.
231.可以用特定的 tag 建立工具, 增加編輯器的瀏覽功能.
232.使用編輯器的大綱視圖, 可以獲得原始碼結構的鳥瞰圖.
233.使用您的編輯器來檢測原始碼中圓括弧|方括弧和花括弧的匹配.
234.使用 grep 跨多個檔案尋找代碼模式.
235.使用 grep 定位器號的聲明|定義和應用.
236.當您不能精確地表述要尋找的內容時, 請使用關鍵單詞的詞幹對程式的原始碼進行尋找.
237.用 grep 過濾其他工具產生的輸出, 分離出您要尋找的項.
238.將 grep 的輸出輸送到其他工具, 使複雜處理任務自動化.
239.通過對 grep 的輸出進行流編輯, 重用代碼尋找的結果.
240.通過選取與噪音模式不匹配的輸出行(grep-v), 過濾虛假的 grep 輸出.
241.使用 fgrep 在原始碼中尋找字串列表.
242.尋找注釋, 或標識符大小寫不敏感的語言編寫的代碼時, 要使用大小寫不敏感的模式比對(grep -i).
243.使用 grep –n 命令列開關, 可以建立與給定Regex匹配的檔案和行號的檢查表.
244.可以使用 diff 比較檔案或程式不同版本之間的差別.
245.在運行 diff 命令時, 可以使用 diff –b, 使檔案比較演算法忽略結尾的空格, 用 –w 忽略所有空白地區的差異, 用 –i 使檔案比較對大小寫不敏感.
246.不要對建立自己的代碼閱讀工具心存畏懼.
247.在構建自己的代碼閱讀工具時: 要充分利用現代快速原型語言所提供的能力; 從簡單開始, 根據需要逐漸改進; 使用利用代碼詞彙結構的各種試探法; 要允許一些輸出噪音或寂靜(無關輸出或缺失輸出); 使用其他工具對輸入進行預先處理, 或者對輸出進行後期處理.
248.要使編譯器成為您的: 指定恰當層級的編譯器警告, 並小心地評估產生的結果.
249.使用C前置處理器理清那些濫用前置處理器特性的程式.
250.要徹底地瞭解編譯器如何處理特定的代碼塊, 需要查看產生的符號(彙編)代碼.
251.通過分析相應目標檔案中的符號, 可以清晰地瞭解源檔案的輸入和輸出.
252.使用原始碼瀏覽器瀏覽大型的代碼集合以及物件類型.
253.要抵制住按照您的編碼規範對外部代碼進行美化的誘惑; 不必要的編排更改會建立不同的代碼, 並妨礙工作的組織.
254.優美列印程式和編輯器文法著色可以使得程式的原始碼為易讀.
255.cdecl 程式可以將難以理解的C和C++型別宣告轉換成純英語(反之亦然).
256.實際運行程式, 往往可以更深刻地理解程式的動作.
257.系統調用|事件和資料包跟蹤程式可以增進對程式動作的理解.
258.執行剖析器可以找出需要著重最佳化的代碼, 驗證輸入資料的覆蓋性, 以及分析演算法的動作.
259.通過檢查從未執行的程式碼, 可以找出測試覆蓋的弱點, 並據此修正測試資料.
260.要探究程式動態動作時的每個細節, 需要在調試器中運作它.
261.將您覺得難以理解的代碼列印到紙上.
262.可以繪製圖示來描繪代碼的動作.
263.可以試著向別人介紹您在閱讀的代碼, 這樣做一般會增進您對代碼的理解.
264.理解複雜的演算法或巧妙的資料結構, 要選擇一個安靜的環境, 然後聚精會神地考慮, 不要藉助於任何電腦化或自動化的協助.
第十一章: 一個完整的例子
265.模仿軟體的功能時, 要依照相似實體的線路(類|函數|模組). 在相似的現有實體中, 為簡化對原始碼庫的文本尋找, 應選取比較罕見的名稱.
266.自動產生的檔案常常會在檔案的開關有一段注釋, 說明這種情況.
267.如果試圖精確地分析代碼, 一般會陷入數量眾多的類|檔案和模組中, 這些內容會很快將我們淹沒; 因此, 我們必須將需要理解的代碼限定在絕對必需的範圍之內.
268.採用一種廣度優先尋找策略, 從多方攻克代碼閱讀中存在的問題, 進到找出克服它們的方法為止.