第一章: 導(dǎo)論
++++++++++++
要養(yǎng)成一個習(xí)慣, 經(jīng)常花時間閱讀別人編寫的高品質(zhì)代碼.
要有選擇地閱讀代碼, 同時, 還要有自己的目標. 您是想學(xué)習(xí)新的模式|編碼風(fēng)格|還是滿足某些需求的方法.
要注意并重視代碼中特殊的非功能性需求, 這些需求也許會導(dǎo)致特殊的實現(xiàn)風(fēng)格.
在現(xiàn)有的代碼上工作時, 請與作者和維護人員進行必要的協(xié)調(diào), 以避免重復(fù)勞動或產(chǎn)生厭惡情緒.
請將從開放源碼軟件中得到的益處看作是一項貸款, 盡可能地尋找各種方式來回報開放源碼社團.
多數(shù)情況下, 如果您想要了解"別人會如何完成這個功能呢?", 除了閱讀代碼以外, 沒有更好的方法.
在尋找bug時, 請從問題的表現(xiàn)形式到問題的根源來分析代碼. 不要沿著不相關(guān)的路徑(誤入歧途).
我們要充分利用調(diào)試器|編譯器給出的警告或輸出的符號代碼|系統(tǒng)調(diào)用跟蹤器|數(shù)據(jù)庫結(jié)構(gòu)化查詢語言的日志機制|包轉(zhuǎn)儲工具和Windows的消息偵查程序, 定出的bug的位置.
對于那些大型且組織良好的系統(tǒng), 您只需要最低限度地了解它的全部功能, 就能夠?qū)λ龀鲂薷?
當向系統(tǒng)中增加新功能時, 首先的任務(wù)就是找到實現(xiàn)類似特性的代碼, 將它作為待實現(xiàn)功能的模板.
從特性的功能描述到代碼的實現(xiàn), 可以按照字符串消息, 或使用關(guān)鍵詞來搜索代碼.
在移植代碼或修改接口時, 您可以通過編譯器直接定位出問題涉及的范圍, 從而減少代碼閱讀的工作量.
進行重構(gòu)時, 您從一個能夠正常工作的系統(tǒng)開始做起, 希望確保結(jié)束時系統(tǒng)能夠正常工作. 一套恰當?shù)臏y試用例(test case)可以幫助您滿足此項約束.
閱讀代碼尋找重構(gòu)機會時, 先從系統(tǒng)的構(gòu)架開始, 然后逐步細化, 能夠獲得最大的效益.
代碼的可重用性是一個很誘人, 但難以理解與分離, 可以試著尋找粒度更大一些的包, 甚至其他代碼.
在復(fù)查軟件系統(tǒng)時, 要注意, 系統(tǒng)是由很多部分組成的, 不僅僅只是執(zhí)行語句. 還要注意分析以下內(nèi)容: 文件和目錄結(jié)構(gòu)|生成和配置過程 | 用戶界面和系統(tǒng)的文檔.
可以將軟件復(fù)查作為一個學(xué)習(xí)|講授|援之以手和接受幫助的機會.
++++++++++++++++++++
第二章: 基本編程元素
++++++++++++++++++++
第一次分析一個程序時, main是一個好的起始點.
層疊if-else if-else序列可以看作是由互斥選擇項組成的選擇結(jié)構(gòu).
有時, 要想了解程序在某一方面的功能, 運行它可能比閱讀 源代碼 更為恰當.
在分析重要的程序時, 最好首先識別出重要的組成部分.
了解局部的命名約定, 利用它們來猜測變量和函數(shù)的功能用途.
當基于猜測修改代碼時, 您應(yīng)該設(shè)計能夠驗證最初假設(shè)的過程. 這個過程可能包括用編譯器進行檢查|引入斷言|或者執(zhí)行適當?shù)臏y試用例.
理解了代碼的某一部分, 可能幫助你理解余下的代碼.
解決困難的代碼要從容易的部分入手.
要養(yǎng)成遇到庫元素就去閱讀相關(guān)文檔的習(xí)慣; 這將會增強您閱讀和編寫代碼的能力.
代碼閱讀有許多可選擇的策略: 自底向上和自頂向下的分析|應(yīng)用試探法和檢查注釋和外部文檔, 應(yīng)該依據(jù)問題的需要嘗試所有這些方法.
for (i=0; i<n; i++)形式的循環(huán)執(zhí)行n次; 其他任何形式都要小心.
涉及兩項不等測試(其中一項包括相等條件)的比較表達式可以看作是區(qū)間成員測試.
我們經(jīng)常可以將表達式應(yīng)用在樣本數(shù)據(jù)上, 借以了解它的含義.
使用De Morgan法則簡化復(fù)雜的邏輯表達式.
在閱讀邏輯乘表達式時, 問題可以認為正在分析的表達式以左的表達式均為true; 在閱讀邏輯和表達式時, 類似地, 可以認為正在分析的表達式以左的表達式均為false.
重新組織您控制的代碼, 使之更為易讀.
將使用條件運行符? :的表達式理解為if代碼.
不需要為了效率, 犧牲代碼的易讀性.
高效的算法和特殊的優(yōu)化確實有可能使得代碼更為復(fù)雜, 從而更難理解, 但這并不意味著使代碼更為緊湊和不易讀會提高它的效率.
創(chuàng)造性的代碼布局可以用來提高代碼的易讀性.
我們可以使用空格|臨時變量和括號提高表達式的易讀性.
在閱讀您所控制的代碼時, 要養(yǎng)成添加注釋的習(xí)慣.
我們可以用好的縮進以及對變量名稱的明智選擇, 提高編寫欠佳的程序的易讀性.
用diff程序分析程序的修訂歷史時, 如果這段歷史跨越了整體重新縮排, 常炒蓿可以通過指定-w選項, 讓diff忽略空白差異, 避免由于更改了縮進層次而引入的噪音.
do循環(huán)的循環(huán)體至少執(zhí)行一次.
執(zhí)行算術(shù)運算時, 當b=2n-1時, 可以將a&b理解為a%(b+1).
*將a<<n理解為ak, k=2n. **
**將a>>n理解為a/k, k=2n. **
每次只分析一個控制結(jié)構(gòu), 將它的內(nèi)容看作是一個黑盒.
將每個控制結(jié)構(gòu)的控制表達式看作是它所包含代碼的斷言.
return, goto, break和continue語句, 還有異常, 都會影響結(jié)構(gòu)化的執(zhí)行流程. 由于這些語句一般都會終止或重新開始正在進行的循環(huán), 因此要單獨推理它們的行為.
用復(fù)雜循環(huán)的變式和不變式, 對循環(huán)進行推理.
使用保持含義不變的變換重新安排代碼, 簡化代碼的推理工作.
+++++++++++++++++++
第三章: 高級C數(shù)據(jù)類型
+++++++++++++++++++
了解特定語言構(gòu)造所服務(wù)的功能之后, 就能夠更好地理解使用它們的代碼.
識別并歸類使用指針的理由.
54.在C程序中, 指針一般用來構(gòu)造鏈式數(shù)據(jù)結(jié)構(gòu)|動態(tài)分配的數(shù)據(jù)結(jié)構(gòu)|實現(xiàn)引用調(diào)用|訪問和迭代數(shù)據(jù)元素|傳遞數(shù)組參數(shù)|引用函數(shù)|作為其他值的別名|代表字符串|以及直接訪問系統(tǒng)內(nèi)存.
以引用傳遞的參數(shù)可以用來返回函數(shù)的結(jié)果, 或者避免參數(shù)復(fù)制帶來的開銷.
指向數(shù)組元素地址的指針, 可以訪問位于特定索引位置的元素.
指向數(shù)組元素的指針和相應(yīng)的數(shù)組索引, 作用在二者上的運算具有相同的語義.
使用全局或static局部變量的函數(shù)大多數(shù)情況都不可重入(reentrant).
字符指針不同于字符數(shù)組.
識別和歸類應(yīng)用結(jié)構(gòu)或共用體的每種理由.
C語言中的結(jié)構(gòu)將多個數(shù)據(jù)元素集合在一起, 使得它們可以作為一個整體來使用, 用來從函數(shù)中返回多個數(shù)據(jù)元素|構(gòu)造鏈式數(shù)據(jù)結(jié)構(gòu)|映射 數(shù)據(jù)在硬件設(shè)備|網(wǎng)絡(luò)鏈接和存儲介質(zhì)上的組織方式|實現(xiàn)抽象數(shù)據(jù)類型|以及以面向?qū)ο蟮姆绞骄幊?
共用體在C程序中主要用于優(yōu)化存儲空間的利用|實現(xiàn)多態(tài)|以及訪問數(shù)據(jù)不同的內(nèi)部表達方式.
一個指針, 在初始化為指向N個元素的存儲空間之后, 就可以作為N個元素的數(shù)組來使用.
動態(tài)分配的內(nèi)在塊可以電焊工地釋放, 或在程序結(jié)束時釋放, 或由垃圾回收器來完成回收; 在棧上分配的內(nèi)存塊當分配它的函數(shù)退出后釋放 .
C程序使用typedef聲明促進抽象, 并增強代碼的易讀性, 從而防范可移植性問題, 并模擬C++和Java的類聲明行為.
可以將typedef聲明理解成變量定義: 變量的名稱就是類型的名稱; 變量的類型就是與該名稱對應(yīng)的類型.
+++++++++++++++
第四章: C數(shù)據(jù)結(jié)構(gòu)
+++++++++++++++
根據(jù)底層的抽象數(shù)據(jù)類型理解顯式的數(shù)據(jù)結(jié)構(gòu)操作.
C語言中, 一般使用內(nèi)建的數(shù)組類型實現(xiàn)向量, 不再對底層實現(xiàn)進行抽象.
N個元素的數(shù)組可以被序列for (i=0; i<N; i++)完全處理; 所有其他變體都應(yīng)該引起警惕.
表達式sizeof(x)總會得到用memset或memcpy處理數(shù)組x(不是指針)所需的正確字節(jié)數(shù).
區(qū)間一般用區(qū)間內(nèi)的第一個元素和區(qū)間后的第一個元素來表示.
不對稱區(qū)間中元素的數(shù)目等于高位邊界與低位邊界的差.
當不對稱區(qū)間的高位邊界等于低位邊界時, 區(qū)間為空.
不對稱區(qū)間中的低位邊界代表區(qū)間的第一個元素; 高位邊界代表區(qū)間外的第一個元素.
結(jié)構(gòu)的數(shù)組常常表示由記錄和字段組成的表.
指向結(jié)構(gòu)的指針常常表示訪問底層記錄和字段的游標.
動態(tài)分配的矩陣一般存儲為指向數(shù)組列的指針或指向元素指針的指針; 這兩種類型都可以按照二維數(shù)組進行訪問.
以數(shù)組形式存儲的動態(tài)分配矩陣, 用自定義訪問函數(shù)定位它們的元素.
抽象數(shù)據(jù)類型為底層實現(xiàn)元素的使用(或誤用)方式提供一種信心的量度.
數(shù)組用從0開始的順序整數(shù)為鍵, 組織查找表.
數(shù)組經(jīng)常用來對控制結(jié)構(gòu)進行高效編碼, 簡化程序的邏輯.
通過在數(shù)組中每個位置存儲一個數(shù)據(jù)元素和一個函數(shù)指針(指向處理數(shù)據(jù)元素的函數(shù)), 可以將代碼與數(shù)據(jù)關(guān)聯(lián)起來.
數(shù)組可以通過存儲供程序內(nèi)的抽象機(abstract machine)或虛擬機(virtual machine)使用的數(shù)據(jù)或代碼, 控制程序的運作.
可以將表達式sizeof(x) / sizeof(x[0])理解為數(shù)組x中元素的個數(shù).
如果結(jié)構(gòu)中含有指向結(jié)構(gòu)自身|名為next的元素, 一般說來, 該結(jié)構(gòu)定義的是單向鏈表的結(jié)點.
指向鏈表結(jié)點的持久性(如全局|靜態(tài)或在堆上分配)指針常常表示鏈表的頭部.
包含指向自身的next和prev指針的結(jié)構(gòu)可能是雙向鏈表的結(jié)點.
理解復(fù)雜數(shù)據(jù)結(jié)構(gòu)的指針操作可以將數(shù)據(jù)元素畫為方框|指針畫為箭頭.
遞歸數(shù)據(jù)結(jié)構(gòu)經(jīng)常用遞歸算法來處理.
重要的數(shù)據(jù)結(jié)構(gòu)操作算法一般用函數(shù)參數(shù)或模板參數(shù)來參數(shù)化.
圖的結(jié)點常常順序地存儲在數(shù)組中, 鏈接到鏈表中, 或通過圖的邊鏈接起來.
圖中的邊一般不是隱式地通過指針, 就是顯式地作為獨立的結(jié)構(gòu)來表示.
圖的邊經(jīng)常存儲為動態(tài)分配的數(shù)組或鏈表, 在這兩種情況下, 邊都錨定在圖的結(jié)點上.
在無向圖中, 表達數(shù)據(jù)時應(yīng)該將所有的結(jié)點看作是等同的, 類似地, 進行處理任務(wù)的代碼也不應(yīng)該基于它們的方向來區(qū)分邊.
在非連通圖中, 執(zhí)行遍歷代碼應(yīng)該能夠接通孤立的子圖.
處理包含回路的圖時, 遍歷代碼應(yīng)該避免在處理圖的回路進入循環(huán).
復(fù)雜的圖結(jié)構(gòu)中, 可能隱藏著其他類型的獨立結(jié)構(gòu).
+++++++++++++++++
第五章: 高級控制流程
+++++++++++++++++
采用遞歸定義的算法和數(shù)據(jù)結(jié)構(gòu)經(jīng)常用遞歸的函數(shù)定義來實現(xiàn).
推理遞歸函數(shù)時, 要從基準落伍測試開始, 并認證每次遞歸調(diào)用如何逐漸接近非遞歸基準范例代碼.
簡單的語言常常使用一系列遵循該語言語法結(jié)構(gòu)的函數(shù)進行語法分析.
推理互遞歸函數(shù)時, 要基于底層概念的遞歸定義.
尾遞歸調(diào)用等同于一個回到函數(shù)開始處的循環(huán).
將throws子句從方法的定義中移除, 然后運行Java編譯器對類的源代碼進行編譯, 就可以容易地找到那些可能隱式地生成異常的方法.
在多處理器計算機上運行的代碼常常圍繞進程或線程進行組織.
工作群并行模型用于在多個處理器間分配工作, 或者創(chuàng)建一個任務(wù)池, 然后將大量需要處理標準化的工作進行分配.
基于線程的管理者/工人并行模型一般將耗時的或阻塞的操作分配給工人子任務(wù), 從而維護中心任務(wù)的響應(yīng)性.
基于進程的管理者/工人并行模型一般用來重用現(xiàn)有的程序, 或用定義良好的接口組織和分離粗粒度的系統(tǒng)模塊.
基于流水線的并行處理中, 每個任務(wù)都接收到一些輸入, 對它們進行一些處理, 并將生成的輸出傳遞給下一個任務(wù), 進行不同的處理.
競爭條件很難捉摸, 相關(guān)的代碼常常會將競爭條件擴散到多個函數(shù)或模塊; 因而, 很難隔離由于競爭條件導(dǎo)致的問題.
對于出現(xiàn)在信號處理器中的數(shù)據(jù)結(jié)構(gòu)操作代碼和庫調(diào)用要保持高度警惕.
在閱讀包含宏的代碼時, 要注意, 宏既非函數(shù), 也非語句.
do…while(0)塊中的宏等同于控制塊中的語句.
宏可以訪問在它的使用點可見的所有局部變量.
宏調(diào)用可改變參數(shù)的值
基于宏的標記拼接能夠創(chuàng)建新的標記符.
+++++++++++++++++
第六章: 應(yīng)對大型項目
+++++++++++++++++
我們可以通過瀏覽項目的源代碼樹—包含項目源代碼的層次目錄結(jié)構(gòu), 來分析一個項目的組織方式. 源碼樹常常能夠反映出項目在構(gòu)架和軟件過程上的結(jié)構(gòu).
應(yīng)用程序的源代碼樹經(jīng)常是該應(yīng)用程序的部署結(jié)構(gòu)的鏡像.
不要被龐大的源代碼集合嚇倒; 它們一般比小型的專門項目組織得更出色.
當您首次接觸一個大型項目時, 要花一些時間來熟悉項目的目錄樹結(jié)構(gòu).
項目的源代碼遠不只是編譯后可以獲得可執(zhí)行程序的計算機語言指令; 一個項目的源碼樹一般還包括規(guī)格說明|最終用戶和開發(fā)人員文檔|測試腳本|多媒體資源|編譯工具|例子|本地化文件|修訂歷史|安裝過程和許可信息.
大型項目的編譯過程一般聲明性地借助依賴關(guān)系來說明. 依賴關(guān)系由工具程序, 如make及其派生程序, 轉(zhuǎn)換成具體的編譯行動.
大型項目中, 制作文件常常由配置步驟動態(tài)地生成; 在分析制作文件之前, 需要先執(zhí)行項目特定的配置.
檢查大型編譯過程的各個步驟時, 可以使用make程序的-n開關(guān)進行預(yù)演.
修訂控制系統(tǒng)提供從儲存庫中獲取源代碼最新版本的方式.
可以使用相關(guān)的命令, 顯示可執(zhí)行文件中的修訂標識關(guān)鍵字, 從而將可執(zhí)行文件與它的源代碼匹配起來.
使用修訂日志中出現(xiàn)的bug跟蹤系統(tǒng)內(nèi)的編號, 可以在bug跟蹤系統(tǒng)的數(shù)據(jù)庫中找到有關(guān)的問題的說明.
可以使用修訂控制系統(tǒng)的版本儲存庫, 找出特定的變更是如何實現(xiàn)的.
定制編譯工具用在軟件開發(fā)過程的許多方面, 包括配置|編譯過程管理|代碼的生成|測試和文檔編制.
程序的調(diào)試輸出可以幫助我們理解程序控制流程和數(shù)據(jù)元素的關(guān)鍵部分.
跟蹤語句所在的地點一般也是算法運行的重要部分.
可以用斷言來檢驗算法運作的步驟|函數(shù)接收的參數(shù)|程序的控制流程|底層硬件的屬性和測試用例的結(jié)果.
可以使用對算法進行檢驗的斷言來證實您對算法運作的理解, 或?qū)⑺鳛橥评淼钠瘘c.
對函數(shù)參數(shù)和結(jié)果的斷言經(jīng)常記錄了函數(shù)的前置條件和后置條件.
我們可以將測試整個函數(shù)的斷言作為每個給定函數(shù)的規(guī)格說明.
測試用例可以部分地代替函數(shù)規(guī)格說明.
可以使用測試用例的輸入數(shù)據(jù)對源代碼序列進行預(yù)演.
+++++++++++++++++++
第七章: 編碼規(guī)范和約定
+++++++++++++++++++
了解了給定代碼庫所遵循的文件組織方式后, 就能更有效率地瀏覽它的源代碼.
閱讀代碼時, 首先要確保您的編輯器或優(yōu)美打印程序的tab設(shè)置, 與代碼遵循的風(fēng)格規(guī)范一致.
可以使用代碼塊的縮進, 快速地掌握代碼的總體結(jié)構(gòu).
對編排不一致的代碼, 應(yīng)該立即給予足夠的警惕.
分析代碼時, 對標記為XXX, FIXME和TODO的代碼序列要格外注意: 錯誤可能就潛伏在其中.
常量使用大寫字母命名, 單詞用下劃線分隔.
在遵循Java編碼規(guī)范的程序中, 包名(package name)總是從一個頂級的域名開始(例如, org, com), 類名和接口名由大寫字母開始, 方法和變量名由小寫字母開始.
用戶界面控件名稱之前的匈牙利記法的前綴類型標記可以幫助我們確定它的作用.
不同的編程規(guī)范對可移植構(gòu)造的構(gòu)成有不同的主張.
在審查代碼的可移植性, 或以某種給定的編碼規(guī)范作為指南時, 要注意了解規(guī)范對可移植性需求的界定與限制.
如果GUI功能都使用相應(yīng)的編程結(jié)構(gòu)來實現(xiàn), 則通過代碼審查可以輕易地驗證給定用戶界面的規(guī)格說明是否被正確地采用.
了解項目編譯過程的組織方式與自動化方式之后, 我們就能夠快速地閱讀與理解對應(yīng)的編譯規(guī)則.
當檢查系統(tǒng)的發(fā)布過程時, 常称ㄉ#可以將相應(yīng)發(fā)行格式的需求作為基準.
++++++++++++
第八章: 文檔
++++++++++++
閱讀代碼時, 應(yīng)該盡可能地利用任何能夠得到的文檔.
閱讀一小時代碼所得到的信息只不過相當于閱讀一分鐘文檔.
使用系統(tǒng)的規(guī)格說明文檔, 了解所閱讀代碼的運行環(huán)境.
軟件需求規(guī)格說明是閱讀和評估代碼的基準.
可以將系統(tǒng)的設(shè)計規(guī)格說明作為認知代碼結(jié)構(gòu)的路線圖, 閱讀具體代碼的指引.
測試規(guī)格說明文檔為我們提供可以用來對代碼進行預(yù)演的數(shù)據(jù).
在接觸一個未知系統(tǒng)時, 功能性的描述和用戶指南可以提供重要的背景信息,從而更好地理解閱讀的代碼所處的上下文.
從用戶參考手冊中, 我們可以快速地獲取, 應(yīng)用程序在外觀與邏輯上的背景知識, 從管理員手冊中可以得知代碼的接口|文件格式和錯誤消息的詳細信息.
利用文檔可以快捷地獲取系統(tǒng)的概況, 了解提供特定特性的代碼.
文檔經(jīng)常能夠反映和提示出系統(tǒng)的底層結(jié)構(gòu).
文檔有助于理解復(fù)雜的算法和數(shù)據(jù)結(jié)構(gòu).
算法的文字描述能夠使不透明(晦澀, 難以理解)的代碼變得可以理解.
文檔常常能夠闡明源代碼中標識符的含義.
文檔能夠提供非功能性需求背后的理論基礎(chǔ).
文檔還會說明內(nèi)部編程接口.
由于文檔很少像實際的程序代碼那樣進行測試, 并受人關(guān)注, 所以它常澄婊撸可能存在錯誤|不完整或過時.
文檔也提供測試用例, 以及實際應(yīng)用的例子.
文檔常常還會包括已知的實現(xiàn)問題或bug.
環(huán)境中已知的缺點一般都會記錄在源代碼中.
文檔的變更能夠標出那些故障點.
對同一段源代碼重復(fù)或互相沖突的更改, 常常表示存在根本性的設(shè)計缺陷, 從而使得維護人員需要用一系列的修補程序來修復(fù).
相似的修復(fù)應(yīng)用到源代碼的不同部分, 常常表示一種易犯的錯誤或疏忽, 它們同樣可能會在其他地方存在.
文檔常常會提供不恰當?shù)男畔? 誤導(dǎo)我們對源代碼的理解.
要警惕那些未歸檔的特性: 將每個實例歸類為合理|疏忽或有害, 相應(yīng)地決定是否應(yīng)該修復(fù)代碼或文檔.
有時, 文檔在描述系統(tǒng)時, 并非按照已完成的實現(xiàn), 而是系統(tǒng)應(yīng)該的樣子或?qū)淼膶崿F(xiàn).
在源代碼文檔中, 單詞gork的意思一般是指”理解”.
如果未知的或特殊用法的單詞阻礙了對代碼的理解, 可以試著在文檔的術(shù)語表(如果存在的話)|New Hacker’s Dictionary[Ray96]|或在 Web搜索引擎中查找它們.
總是要以批判的態(tài)度來看待文檔, 注意非傳統(tǒng)的來源, 比如注釋|標準|出版物|測試用例|郵件列表|新聞組|修訂日志|問題跟蹤數(shù)據(jù)庫|營銷材料|源代碼本身.
總是要以批判的態(tài)度來看待文檔; 由于文檔永遠不會執(zhí)行, 對文檔的測試和正式復(fù)查也很少達到對代碼的同樣水平, 所以文檔常常會誤導(dǎo)讀者, 或者完全錯誤.
對于那些有缺陷的代碼, 我們可以從中推斷出它的真實意圖.
在閱讀大型系統(tǒng)的文檔時, 首先要熟悉文檔的總體結(jié)構(gòu)和約定.
在對付體積龐大的文檔時, 可以使用工具, 或?qū)⑽谋据敵龅礁咂焚|(zhì)輸出設(shè)備上, 比如激光打印機, 來提高閱讀的效率.
++++++++++++++
第九章: 系統(tǒng)構(gòu)架
++++++++++++++
一個系統(tǒng)可以(在重大的系統(tǒng)中也確實如此)同時出多種不同的構(gòu)架類型. 以不同的方式檢查同一系統(tǒng)|分析系統(tǒng)的不同部分 | 或使用不同級別的分解, 都有可能發(fā)現(xiàn)不同的構(gòu)架類型.
協(xié)同式的應(yīng)用程序, 或者需要協(xié)同訪問共享信息或資源的半自治進程, 一般會采用集中式儲存庫構(gòu)架.
黑板系統(tǒng)使用集中式的儲存庫, 存儲非結(jié)構(gòu)化的鍵/值對, 作為大量不同代碼元件之間的通信集線器.
當處理過程可以建模|設(shè)計和實現(xiàn)成一系列的數(shù)據(jù)變換時, 常常會使用數(shù)據(jù)流(或管道—過濾器)構(gòu)架.
在批量進行自動數(shù)據(jù)處理的環(huán)境中, 經(jīng)常會采用數(shù)據(jù)流構(gòu)架, 在對數(shù)據(jù)工具提供大量支持的平臺上尤其如此.
**數(shù)據(jù)流構(gòu)架的一個明顯征兆是: 程序中使用臨時文件或流水線(pipeline)在不同進程間進行通信. **
使用圖示來建模面向?qū)ο髽?gòu)架中類的關(guān)系.
可以將源代碼輸入到建模工具中, 逆向推導(dǎo)出系統(tǒng)的構(gòu)架.
擁有大量同級子系統(tǒng)的系統(tǒng), 常常按照分層構(gòu)架進行組織.
分層構(gòu)架一般通過堆疊擁有標準化接口的軟件組件來實現(xiàn).
系統(tǒng)中每個層可以將下面的層看作抽象實體, 并且(只要該層滿足它的需求說明)不關(guān)心上面的層如何使用它.
層的接口既可以是支持特定概念的互補函數(shù)族, 也可以是一系列支持同一抽象接口不同底層實現(xiàn)的可互換函數(shù).
用C語言實現(xiàn)的系統(tǒng), 常常用函數(shù)指針的數(shù)組, 表達層接口的多路復(fù)用操作.
用面向?qū)ο蟮恼Z言實現(xiàn)的系統(tǒng), 使用虛方法調(diào)用直接表達對層接口的多嘴復(fù)用操作.
系統(tǒng)可以使用不同的|獨特的層次分解模型跨各種坐標軸進行組織.
使用程序切片技術(shù), 可以將程序中的數(shù)據(jù)和控制之間依賴關(guān)系集中到一起.
在并發(fā)系統(tǒng)中, 一個單獨的系統(tǒng)組件起到集中式管理器的作用, 負責(zé)啟動|停止和協(xié)調(diào)其他系統(tǒng)進程和任務(wù)的執(zhí)行.
許多現(xiàn)實的系統(tǒng)都會博采眾家之長. 當處理此類系統(tǒng)時, 不要徒勞地尋找無所不包的構(gòu)架圖; 應(yīng)該將不同構(gòu)架風(fēng)格作為獨立但相關(guān)的實體來進行定位|識別并了解.
狀態(tài)變遷圖常常有助于理清狀態(tài)機的動作.
在處理大量的代碼時, 了解將代碼分解成單獨單元的機制極為重要.
大多數(shù)情況下, 模塊的物理邊界是單個文件|組織到一個目錄中的多個文件或擁有統(tǒng)一前綴的文件的集合.
C中的模塊, 由提供模塊公開接口的頭文件和提供對應(yīng)實現(xiàn)的源文件組成.
對象的構(gòu)造函數(shù)經(jīng)常用來分配與對象相關(guān)的資源, 并初始化對象的狀態(tài). 函數(shù)一般用來釋放對象在生命期中占用的資源.
對象方法經(jīng)常使用類字段來存儲控制所有方法運作的數(shù)據(jù)(比如查找表或字典)或維護類運作的狀態(tài)信息(例如, 賦給每個對象一個標識符的計數(shù)器).
在設(shè)計良好的類中, 所有的字段都應(yīng)在聲明為private, 并用公開的訪問方法提供對它們的訪問.
在遇到friend聲明時, 要停下來分析一下, 看看繞過類封裝在設(shè)計上的理由.
可以有節(jié)制地用運算符增強特定類的可用性, 但用運算符重載, 將類實現(xiàn)為擁有內(nèi)建算術(shù)類型相關(guān)的全部功能的類實體, 是不恰當?shù)?
泛型實現(xiàn)不是在編譯期間通過宏替換或語言所支持的功能(比如C++模板和Ada的泛型包)來實現(xiàn), 就是在運行期間通過使用數(shù)據(jù)元素的指針和函數(shù)的指針|或?qū)ο蟮亩鄳B(tài)性實現(xiàn).
抽象數(shù)據(jù)類型經(jīng)常用來封裝常用的數(shù)據(jù)組織方案(比如樹|列表或棧), 或者對用戶隱藏數(shù)據(jù)類型的實現(xiàn)細節(jié).
使用庫的目的多種多樣: 重用源代碼或目標代碼, 組織模塊集合, 組織和優(yōu)化編譯過程, 或是用來實現(xiàn)應(yīng)用程序各種特性的按需載入.
大型的|分布式的系統(tǒng)經(jīng)常實現(xiàn)為許多互相協(xié)作的進程.
對于基于文本的數(shù)據(jù)儲存庫, 可以通過瀏覽存儲在其中的數(shù)據(jù), 破譯出它的結(jié)構(gòu).
可以通過查詢數(shù)據(jù)字典中的表, 或使用數(shù)據(jù)庫專有的SQL命令, 比如show table, 來分析關(guān)系型數(shù)據(jù)庫的模式.
識別出重用的構(gòu)架元素后, 可以查找其最初的描述, 了解正確地使用這種構(gòu)架的方式, 以及可能出現(xiàn)的誤用.
要詳細分析建立在某種框架之上的應(yīng)用程序, 行動的最佳路線就是從研究框架自身開始.
在閱讀向?qū)傻拇a時, 不要期望太高, 否則您會感到失望.
學(xué)習(xí)幾個基本的設(shè)計模式之后, 您會發(fā)現(xiàn), 您查看代碼構(gòu)架的方式會發(fā)生改變: 您的視野和詞匯將會擴展到能夠識別和描述許多通用的形式.
頻繁使用的一些模式, 但并不顯式地指出它們的名稱, 這是由于構(gòu)架性設(shè)計的重用經(jīng)常先于模式的形成.
請試著按照底層模式來理解構(gòu)架, 即使代碼中并沒有明確地提及模式.
大多數(shù)解釋器都遵循類似的處理構(gòu)架, 圍繞一個狀態(tài)機進行構(gòu)建, 狀態(tài)機的操作依賴于解釋器的當前狀態(tài)|程序指令和程序狀態(tài).
多數(shù)情況下, 參考構(gòu)架只是為應(yīng)用程序域指定一種概念性的結(jié)構(gòu), 具體的實現(xiàn)并非必須遵照這種結(jié)構(gòu).
+++++++++++++++++
第十章: 代碼閱讀工具
+++++++++++++++++
詞匯工具可以高效地在一個大代碼文件中或者跨多個文件查找某種模式.
使用程序編輯器和正則表達式查找命令, 瀏覽龐大的源代碼文件.
以只讀方式瀏覽源代碼文件.
使用正則表達式 ^function name 可以找出函數(shù)的定義.
使用正則表達式的字符類, 可以查找名稱遵循特定模式的變量.
使用正則表達式的否定字符類, 可以避免非積極匹配.
使用正則表達式 symbol-1. *symbol-2, 可以查找出現(xiàn)在同一行的符號.
使用編輯器的 tags 功能, 可以快速地找出實體的定義.
可以用特定的 tag 創(chuàng)建工具, 增加編輯器的瀏覽功能.
使用編輯器的大綱視圖, 可以獲得源代碼結(jié)構(gòu)的鳥瞰圖.
使用您的編輯器來檢測源代碼中圓括號|方括號和花括號的匹配.
使用 grep 跨多個文件查找代碼模式.
使用 grep 定位符號的聲明|定義和應(yīng)用.
當您不能精確地表述要查找的內(nèi)容時, 請使用關(guān)鍵單詞的詞干對程序的源代碼進行查找.
用 grep 過濾其他工具生成的輸出, 分離出您要查找的項.
238.將 grep 的輸出輸送到其他工具, 使復(fù)雜處理任務(wù)自動化.
通過對 grep 的輸出進行流編輯, 重用代碼查找的結(jié)果.
通過選取與噪音模式不匹配的輸出行(grep-v), 過濾虛假的 grep 輸出.
使用 fgrep 在源代碼中查找字符串列表.
查找注釋, 或標識符大小寫不敏感的語言編寫的代碼時, 要使用大小寫不敏感的模式匹配(grep -i).
使用 grep –n 命令行開關(guān), 可以創(chuàng)建與給定正則表達式匹配的文件和行號的檢查表.
可以使用 diff 比較文件或程序不同版本之間的差別.
在運行 diff 命令時, 可以使用 diff –b, 使文件比較算法忽略結(jié)尾的空格, 用 –w 忽略所有空白區(qū)域的差異, 用 –i 使文件比較對大小寫不敏感.
不要對創(chuàng)建自己的代碼閱讀工具心存畏懼.
在構(gòu)建自己的代碼閱讀工具時: 要充分利用現(xiàn)代快速原型語言所提供的能力; 從簡單開始, 根據(jù)需要逐漸改進; 使用利用代碼詞匯結(jié)構(gòu)的各種試探法; 要允許一些輸出噪音或寂靜(無關(guān)輸出或缺失輸出); 使用其他工具對輸入進行預(yù)處理, 或者對輸出進行后期處理.
要使編譯器成為您的: 指定恰當級別的編譯器警告, 并小心地評估生成的結(jié)果.
使用C預(yù)處理器理清那些濫用預(yù)處理器特性的程序.
要徹底地了解編譯器如何處理特定的代碼塊, 需要查看生成的符號(匯編)代碼.
通過分析相應(yīng)目標文件中的符號, 可以清晰地了解源文件的輸入和輸出.
使用源代碼瀏覽器瀏覽大型的代碼集合以及對象類型.
要抵制住按照您的編碼規(guī)范對外部代碼進行美化的誘惑; 不必要的編排更改會創(chuàng)建不同的代碼, 并妨礙工作的組織.
優(yōu)美打印程序和編輯器語法著色可以使得程序的源代碼為易讀.
cdecl 程序可以將難以理解的C和C++類型聲明轉(zhuǎn)換成純英語(反之亦然).
實際運行程序, 往往可以更深刻地理解程序的動作.
系統(tǒng)調(diào)用|事件和數(shù)據(jù)包跟蹤程序可以增進對程序動作的理解.
執(zhí)行剖析器可以找出需要著重優(yōu)化的代碼, 驗證輸入數(shù)據(jù)的覆蓋性, 以及分析算法的動作.
通過檢查從未執(zhí)行的代碼行, 可以找出測試覆蓋的弱點, 并據(jù)此修正測試數(shù)據(jù).
要探究程序動態(tài)動作時的每個細節(jié), 需要在調(diào)試器中運作它.
將您覺得難以理解的代碼打印到紙上.
可以繪制圖示來描繪代碼的動作.
可以試著向別人介紹您在閱讀的代碼, 這樣做一般會增進您對代碼的理解.
理解復(fù)雜的算法或巧妙的數(shù)據(jù)結(jié)構(gòu), 要選擇一個安靜的環(huán)境, 然后聚精會神地考慮, 不要借助于任何計算機化或自動化的幫助.
+++++++++++++++++++++
第十一章: 一個完整的例子
+++++++++++++++++++++
模仿軟件的功能時, 要依照相似實體的線路(類|函數(shù)|模塊). 在相似的現(xiàn)有實體中, 為簡化對源代碼庫的文本查找, 應(yīng)選取比較罕見的名稱.
自動生成的文件常常會在文件的開關(guān)有一段注釋, 說明這種情況.
如果試圖精確地分析代碼, 一般會陷入數(shù)量眾多的類|文件和模塊中, 這些內(nèi)容會很快將我們淹沒; 因此, 我們必須將需要理解的代碼限定在絕對必需的范圍之內(nèi).
采用一種廣度優(yōu)先查找策略, 從多方攻克代碼閱讀中存在的問題, 進到找出克服它們的方法為止.
..
..
..