本書作者著眼于一系列可能會(huì)在現(xiàn)代軟件系統(tǒng)中出現(xiàn)的問題,特別是分散在地球上的組件和服務(wù)之間復(fù)雜的相互影響造成的問題。無論您是否正在調(diào)試獨(dú)立運(yùn)行的錯(cuò)誤或?yàn)?zāi)難性的企業(yè)系統(tǒng)故障,本指南將幫助您更快更少痛苦地完成任務(wù)。
在開發(fā)軟件或者管理軟件系統(tǒng)時(shí),經(jīng)常會(huì)遇到各種故障。這些故障的類型多種多樣,一些是可以迅速解決的代碼錯(cuò)誤,還有一些可能會(huì)給公司造成每小時(shí)上百萬元的損失——如大型系統(tǒng)的宕機(jī)。對(duì)于以上兩種情況,作為一名高效的專業(yè)人士,要能夠快速識(shí)別并修復(fù)這些故障。這就是調(diào)試的含義,也是本書的主題。
本書并非用于入門,面向的是有經(jīng)驗(yàn)的開發(fā)人員,所以希望你能夠理解用多種不同的編程語言編寫的小型代碼示例,并且可以使用高級(jí)圖形用戶界面和基于命令行的編程工具。但是,本書中仍然包含了詳細(xì)的調(diào)試技術(shù),因?yàn)槲宜姷降囊恍┵Y深開發(fā)人員,即便其熟練地掌握了各種方法,也時(shí)不時(shí)地需要參考資料。此外,如果你曾經(jīng)從事過至少幾個(gè)月的正經(jīng)調(diào)試工作,就能更容易地體會(huì)到書中先進(jìn)的方法。
本書包含的內(nèi)容
如本書主題,調(diào)試工作指的是在開發(fā)和運(yùn)行一個(gè)現(xiàn)代化、精密化的計(jì)算系統(tǒng)時(shí)所用到的策略、工具和方法。在以前,調(diào)試主要指的是檢測(cè)和修正程序錯(cuò)誤。然而,現(xiàn)在的程序很少獨(dú)立運(yùn)行。即使最小的程序也常常與外部庫(kù)動(dòng)態(tài)鏈接。復(fù)雜的程序會(huì)在應(yīng)用服務(wù)器上運(yùn)行、調(diào)用網(wǎng)絡(luò)服務(wù)、使用關(guān)系型數(shù)據(jù)庫(kù)或者NoSQL數(shù)據(jù)庫(kù)、從目錄服務(wù)器上獲取數(shù)據(jù)、運(yùn)行外部程序、利用中間應(yīng)用程序并集成大量的第三方軟件包。完整的系統(tǒng)和服務(wù)的運(yùn)行依賴于散布在全球主機(jī)上的無故障運(yùn)行、內(nèi)部開發(fā)的第三方組件。軟件開發(fā)原則DevOps能夠解決這一現(xiàn)實(shí)問題,它強(qiáng)調(diào)了開發(fā)人員和其他IT專業(yè)人士的角色。本書旨在幫助你全面看待程序錯(cuò)誤,因?yàn)閷?duì)于最具挑戰(zhàn)性的難題,通常很難立即確定軟件中的罪魁禍?zhǔn)住?br />
本書從一般性話題出發(fā),引出具體的問題。第1章的內(nèi)容是“策略”,第2章的內(nèi)容是“方法”,第3章介紹了可以調(diào)試不同軟件和系統(tǒng)故障的工具和技術(shù)。之后,書中還列舉了針對(duì)調(diào)試工作不同階段的技術(shù),如使用調(diào)試器的階段(第4章)、編程階段(第5章)、編譯軟件階段(第6章),以及運(yùn)行系統(tǒng)階段(第7章)。關(guān)于多線程和并發(fā)代碼中那些麻煩的程序錯(cuò)誤,第8章的內(nèi)容單獨(dú)涵蓋了專門的工具和技術(shù)。
如何使用本書
一般情況下,讀書都是從頭看到尾。但實(shí)際上,還有更好的方法。本書中的建議分為以下三類。
第一,在面對(duì)程序錯(cuò)誤時(shí)要用到的策略和方法。這方面的內(nèi)容在第1章和第2章中都有羅列。此外,第5章中的許多技巧也可歸于這一類別。多了解其中的內(nèi)容,在應(yīng)用時(shí)就可手到擒來。在調(diào)試過程中,整體性的思想會(huì)反映到你所使用的方法上。遇到“死胡同”時(shí),回想一下已知路徑,一般能幫助你找到走出迷宮的新路徑。
第二,可以寄予希望的技術(shù)和工具。這些內(nèi)容主要集中在第3章,其中也包含了解決日常問題的各個(gè)要素。具體參見“第36條:調(diào)整你的調(diào)試程序”。你需要找時(shí)間學(xué)習(xí)并逐步把這些條目運(yùn)用到實(shí)踐當(dāng)中。這可能意味著放棄自己熟悉的工具,重新學(xué)習(xí)更先進(jìn)的工具。剛開始時(shí),這種經(jīng)歷可能會(huì)很痛苦,但是從長(zhǎng)遠(yuǎn)來看,它將會(huì)把你塑造成為一名卓越大師。
第三,在面對(duì)困難問題時(shí)的技術(shù)應(yīng)用思考。這些技術(shù)和方法都不會(huì)是你常常使用的,但是在遇到一個(gè)無從下手的難題時(shí),它們能夠節(jié)省你一天(或至少幾個(gè)小時(shí))的時(shí)間。舉個(gè)例子,如果你無法解釋為什么C代碼或C++代碼編譯失敗,你就可以參閱“第50條:檢查生成代碼”。在快速閱讀這些條目時(shí),你可以把它們作為參考,在之后的應(yīng)用過程中,再仔細(xì)甄別。
如何投身編程世界
盡管書中的所有條目都是對(duì)診斷錯(cuò)誤和調(diào)試已有錯(cuò)誤提供建議,但是你仍可以利用從這里學(xué)到的大部分知識(shí)來盡量減少遇到軟件故障的情況,也可以有能力解決突發(fā)狀況,讓生活變得更簡(jiǎn)單。嚴(yán)格的調(diào)試和軟件開發(fā)實(shí)踐相互促進(jìn),這是一個(gè)良性循環(huán)。這條建議對(duì)你現(xiàn)在或?qū)淼能浖_發(fā)、設(shè)計(jì)和管理都大有裨益。
在軟件設(shè)計(jì)中,請(qǐng)遵循以下建議。
?使用最合適的最高級(jí)機(jī)制(第47條:考慮用另一種語言重寫可疑代碼,第66條:考慮用更高層抽象進(jìn)行重寫)。
?提供調(diào)試模式(第6條:使用軟件的調(diào)試功能,第40條:添加調(diào)試功能選項(xiàng))。
?提供監(jiān)控和記錄系統(tǒng)運(yùn)行的機(jī)制(第27條:對(duì)系統(tǒng)各個(gè)獨(dú)立進(jìn)程進(jìn)行監(jiān)視,第41條:添加日志語句,第56條:檢查應(yīng)用程序日志文件)。
?引入U(xiǎn)NIX命令行工具腳本組件選項(xiàng)(第22條:使用UNIX命令行工具分析調(diào)試數(shù)據(jù))。
?確保是由于內(nèi)部錯(cuò)誤引發(fā)的明顯故障,而非系統(tǒng)不穩(wěn)定(第55條:快速故障)。
?提供一種方法來獲取事后問題分析所需的內(nèi)存轉(zhuǎn)儲(chǔ)(第35條:知道如何利用磁心信息轉(zhuǎn)儲(chǔ),第60條:使用后期調(diào)試分析死鎖)。
?縮小軟件執(zhí)行中的問題源頭和非確定性范圍(第63條:隔離與移除非確定性)。
在軟件構(gòu)建中,請(qǐng)遵循以下建議。
?從同事那里獲取反饋(第39條:與同事檢查并推理代碼)。
?給每個(gè)程序創(chuàng)建單元測(cè)試(第42條:使用單元測(cè)試)。
?使用斷言(assertion)來驗(yàn)證你的假設(shè)和代碼是否能夠正確運(yùn)行(第43條:使用斷言)。
?盡量編寫可維護(hù)的代碼,即可讀、穩(wěn)定、易于分析和修改的代碼(第46條:簡(jiǎn)化可疑代碼,第48條:提高可疑代碼的可讀性和結(jié)構(gòu)性)。
?避免構(gòu)建中存在的非確定性源(第52條:配置確定性構(gòu)建和執(zhí)行)。
最后,無論是團(tuán)隊(duì)還是個(gè)人管理軟件的開發(fā)和運(yùn)行,需要遵循以下幾點(diǎn)。
?記錄問題,并使用合適的系統(tǒng)進(jìn)行檢驗(yàn)(第1條:使用問題追蹤系統(tǒng)來處理所有問題)。
?分流并給所有問題劃分優(yōu)先級(jí)(第8條:專注于最重要的問題)。
?在維護(hù)良好的修訂控制系統(tǒng)中記錄軟件的所有更改(第26條:使用修訂控制系統(tǒng)記錄成因和調(diào)試歷史)。
?采用漸進(jìn)方式部署軟件,以便對(duì)新老版本進(jìn)行對(duì)比(第5條:找出已驗(yàn)證系統(tǒng)和失敗系統(tǒng)間的差異)。
?力求所用工具和部署環(huán)境的多樣性(第7條:為軟件構(gòu)建和執(zhí)行環(huán)境拓展多樣性)。
?定期更新工具和庫(kù)(第14條:考慮更新軟件)。
?購(gòu)買所用的第三方庫(kù)的源代碼(第15條:參閱第三方源代碼,深入理解第三方應(yīng)用),同時(shí),購(gòu)買更高級(jí)的工具來診斷難以察覺的錯(cuò)誤(第51條:使用靜態(tài)程序分析,第59條:使用動(dòng)態(tài)程序分析,第62條:運(yùn)用專門工具解開死鎖和競(jìng)態(tài)條件,第64條:調(diào)查源于爭(zhēng)用的可擴(kuò)展性問題,第65條:使用性能計(jì)數(shù)器共享定位錯(cuò)誤)。
?對(duì)任何硬件界面和嵌入式系統(tǒng)的調(diào)試提供專業(yè)工具包(第16條:使用專業(yè)的監(jiān)測(cè)和測(cè)試設(shè)備)。
?使開發(fā)人員能夠遠(yuǎn)程調(diào)試軟件(第18條:從桌面開啟對(duì)難以移動(dòng)的系統(tǒng)的調(diào)試工作)。
?為高要求的排查任務(wù)提供性能充足的CPU和磁盤資源(第19條:自動(dòng)調(diào)試任務(wù))。
?在代碼檢查和指導(dǎo)方面鼓勵(lì)開發(fā)人員的協(xié)作(第39條:與同事檢查并推理代碼)。
?鼓勵(lì)推動(dòng)使用測(cè)試驅(qū)動(dòng)開發(fā)(第42條:使用單元測(cè)試)。
?納入軟件構(gòu)建的性能概況、靜態(tài)分析和動(dòng)態(tài)分析,同時(shí)保持快速、精益、平均的構(gòu)建和測(cè)試周期(第11條:最小化從修改到結(jié)果的換向時(shí)間,第51條:使用靜態(tài)程序分析,第53條:配置使用調(diào)試庫(kù)和檢測(cè),第57條:廓線系統(tǒng)和運(yùn)作流程,第59條:使用動(dòng)態(tài)程序分析工具)。
關(guān)于術(shù)語的一些說明
本書中,在ISO-24765-2010(系統(tǒng)和軟件工程——詞匯)定義范圍內(nèi)使用fault一詞:“計(jì)算機(jī)程序中一個(gè)不正確的步驟、進(jìn)程或者數(shù)據(jù)定義”。這常被稱為defect。在日常語言中,就是通常所說的bug。同樣地,我所使用的術(shù)語failure是根據(jù)同一標(biāo)準(zhǔn):“系統(tǒng)或系統(tǒng)組件不能在規(guī)定限度內(nèi)實(shí)現(xiàn)應(yīng)用功能”。故障的表現(xiàn)有程序崩潰、鎖死,或給出錯(cuò)誤的結(jié)果。因此,在有fault的情況下,就會(huì)出現(xiàn)failure。令人困惑的是,有時(shí)fault和defect也會(huì)用來指代failure,ISO標(biāo)準(zhǔn)已經(jīng)意識(shí)到了這個(gè)問題。本書中,我會(huì)遵守這一區(qū)別。然而,為了避免把內(nèi)容變成法律條文那樣,在上下文很清楚的情況下,我通常用problem一詞來指代fault(即代碼中的問題)或failure(即可復(fù)現(xiàn)的問題)。
如今,UNIX操作系統(tǒng)的shell、庫(kù)和工具可以在許多平臺(tái)上使用。本書使用UNIX來指代任何遵循UNIX原則和API的系統(tǒng),其中包括蘋果的MacOSX、GNU/Linux的各種發(fā)行版(例如,ArchLinux、CentOS、Debian、Fedora、openSUSE、紅帽企業(yè)版Linux、Slackware和Ubuntu)、UNIX操作系統(tǒng)的各個(gè)分支(如AIX、HP-UX、Solaris)、各種BSD衍生(如FreeBSD、OpenBSD、NetBSD),以及在Windows上運(yùn)行的Cygwin。
與之類似,在編寫C++、Java或Python程序時(shí),使用了一種較為現(xiàn)代的語言版本。我在本書中已經(jīng)盡力規(guī)避了那些過于偏頗或過于前沿的例子。
書中所述的“你的代碼(yourcode)”和“你的軟件(yoursoftware)”指代你所調(diào)試的代碼和你正在處理的軟件。這樣說更簡(jiǎn)便,而且也暗含了所有者的含義,這在開發(fā)工作中是非常重要的。
使用routine一詞來指代代碼調(diào)用單元,如成員函數(shù)、方法、函數(shù)、過程和子程序。
使用VisualStudio和Windows來指代相應(yīng)的微軟產(chǎn)品。
使用版本控制系統(tǒng)(revisioncontrolsystem和versioncontrolsystem)來指代用于軟件配置管理的一些工具,如Git。
排版與其他慣例
?代碼是用打字機(jī)字體(typewriterfont)來書寫的,關(guān)鍵點(diǎn)設(shè)置為粗體(bold),條款和工具名稱用斜體設(shè)置(italics)。
?交互會(huì)話清單使用灰度來區(qū)分提示、用戶輸入和產(chǎn)生的輸出結(jié)果。
$echohello,world
Helloworld
?UNIX命令行選項(xiàng)類似--this,或者使用單字符縮寫,如-t。與之對(duì)應(yīng)的Windows工具選項(xiàng)則是/this。
?關(guān)鍵快捷鍵以Shift-F11鍵這種形式進(jìn)行設(shè)定。
?文件路徑
Diomidis Spinellis是雅典經(jīng)濟(jì)與商業(yè)大學(xué)科技管理學(xué)院教授。他的研究領(lǐng)域包括軟件工程、IT安全以及云系統(tǒng)工程。他的著作《代碼閱讀》(Code Reading: The Open Source Perspective)以及《代碼質(zhì)量》(Code Quality: The Open Source Perspective)雙雙榮獲Jolt(軟件開發(fā)生產(chǎn)力)大獎(jiǎng)并被廣泛傳譯。Spinellis博士曾在多個(gè)學(xué)術(shù)期刊以及期刊會(huì)議論文集中發(fā)表了200多篇技術(shù)論文,被引用次數(shù)高達(dá)2500多次。十年來,他作為IEEE Software編委會(huì)的一員,為Tools of the Trade專欄定期撰稿。他為OS X以及BSD UNIX貢獻(xiàn)了很多代碼,并且是UMLGraph、CScout,以及其他軟件開源包、庫(kù)和工具的開發(fā)者。他擁有倫敦帝國(guó)理工學(xué)院軟件工程專業(yè)的碩士學(xué)位及計(jì)算機(jī)科學(xué)專業(yè)的博士學(xué)位。他是ACM以及IEEE的高級(jí)會(huì)員,并且自2015年以來一直擔(dān)任IEEE Software的主編。
目錄
前言 v
致謝 x
作者簡(jiǎn)介 xiv
Figures xix
Listings xx
Item 11: Minimize the Turnaround Time from Your Changes to Their Result 28
Item 12: Automate Complex Testing Scenarios 29
Item 13: Enable a Comprehensive Overview of Your Debugging Data 32
Item 14: Consider Updating Your Software 33
Item 15: Consult Third-Party Source Code for Insights on Its Use 34
Item 16: Use Specialized Monitoring and Test Equipment 36
Item 17: Increase the Prominence of a Failure’s Effects 40
Item 18: Enable the Debugging of Unwieldy Systems from Your Desk 42
Item 19: Automate Debugging Tasks 44
Item 20: Houseclean Before and After Debugging 45
Item 21: Fix All Instances of a Problem Class 46
Chapter 3: General-Purpose Tools and Techniques 49
Item 22: Analyze Debug Data with Unix Command-Line Tools 49
Item 23: Utilize Command-Line Tool Options and Idioms 55
Item 24: Explore Debug Data with Your Editor 57
Item 25: Optimize Your Work Environment 59
Item 26: Hunt the Causes and History of Bugs with the
Revision Control System 64
Item 27: Use Monitoring Tools on Systems Composed
of Independent Processes 67
Chapter 4: Debugger Techniques 71
Item 28: Use Code Compiled for Symbolic Debugging 71
Item 29: Step through the Code 76
Item 30: Use Code and Data Breakpoints 77
Item 31: Familiarize Yourself with Reverse Debugging 80
Item 32: Navigate along the Calls between Routines 82
Item 33: Look for Errors by Examining the Values
of Variables and Expressions 84
Item 34: Know How to Attach a Debugger to a Running Process 87
Item 35: Know How to Work with Core Dumps 89
Item 36: Tune Your Debugging Tools 92
Item 37: Know How to View Assembly Code and Raw Memory 95
Chapter 5: Programming Techniques 101
Item 38: Review and Manually Execute Suspect Code 101
Item 39: Go Over Your Code and Reasoning with a Colleague 103
Item 40: Add Debugging Functionality 104
Item 41: Add Logging Statements 108
Item 42: Use Unit Tests 112
Item 43: Use Assertions 116
Item 44: Verify Your Reasoning by Perturbing the
Debugged Program 119
Item 45: Minimize the Differences between a Working Example
and the Failing Code 120
Item 46: Simplify the Suspect Code 121
Item 47: Consider Rewriting the Suspect Code
in Another Language 124
Item 48: Improve the Suspect Code’s Readability and Structure 126
Item 49: Fix the Bug’s Cause, Rather Than Its Symptom 129
Chapter 6: Compile-Time Techniques 133
Item 50: Examine Generated Code 133
Item 51: Use Static Program Analysis 136
Item 52: Configure Deterministic Builds and Executions 141
Item 53: Configure the Use of Debugging Libraries and Checks 143
Chapter 7: Runtime Techniques 149
Item 54: Find the Fault by Constructing a Test Case 149
Item 55: Fail Fast 153
Item 56: Examine Application Log Files 154
Item 57: Profile the Operation of Systems and Processes 158
Item 58: Trace the Code’s Execution 162
Item 59: Use Dynamic Program Analysis Tools 168
Chapter 8: Debugging Multi-threaded Code 171
Item 60: Analyze Deadlocks with Postmortem Debugging 171
Item 61: Capture and Replicate 178
Item 62: Uncover Deadlocks and Race Conditions with
Specialized Tools 183
Item 63: Isolate and Remove Nondeterminism 188
Item 64: Investigate Scalability Issues by Looking at Contention 190
Item 65: Locate False Sharing by Using Performance Counters 193
Item 66: Consider Rewriting the Code Using Higher-Level
Abstractions 197
Web Resources 207
Index 211