• 正文
    • 1 保護程序免遭非法輸入的破壞
    • 2 斷言
    • 3 錯誤處理技術(shù)
    • 4 隔離程序錯誤
    • 5 輔助調(diào)試的代碼
    • 6 防御式編程的姿態(tài)
    • 7 小結(jié)
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

防御式編程

02/10 10:20
1134
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

防御式編程,概念來自防御式駕駛思維,永遠(yuǎn)不能確定另一位司機將要做什么,這樣才能確保在他做出危險動作時你不會受到傷害;需要自我承擔(dān)保護責(zé)任,即使是他人犯的錯。防御式編程的主要思想是,不因外部的錯誤數(shù)據(jù)而被破壞。

1 保護程序免遭非法輸入的破壞

如果輸入正確信息就會輸出正確結(jié)果;反之,錯誤的信息輸入會得出錯誤的結(jié)果,除非有設(shè)計的干預(yù)。對已形成產(chǎn)品的軟件而言,僅僅“垃圾進,垃圾出”還不夠;好的程序不會生成垃圾,而是做到“垃圾進,什么都不出”、“進來垃圾,出去是出錯提示”或“不許垃圾進來”。

“垃圾進,垃圾出”是缺乏安全性的程序,通常有三種方法來處理垃圾進來。

1.1 檢查來源于外部的數(shù)據(jù)

當(dāng)從用戶、網(wǎng)絡(luò)或其它外部接口獲取數(shù)據(jù)時,應(yīng)檢查所獲得的數(shù)據(jù),確保在允許的范圍內(nèi)。對于數(shù)值,要確保在可接受的取值范圍內(nèi);對于字符串,要確保其不超長。如果數(shù)據(jù)代表的是特定范圍內(nèi)的數(shù)據(jù) (如交易ID 或性別類型),要確認(rèn)其取值合乎實際,否則應(yīng)拒絕。

1.2 檢查子程序的輸入?yún)?shù)

檢查子程序輸入?yún)?shù)的值,本質(zhì)上和檢查來源于外部的數(shù)值一樣,只不過數(shù)據(jù)是來自于其它子程序而非外部接口。

在輸入數(shù)據(jù)時將其轉(zhuǎn)換為恰當(dāng)?shù)念愋?,輸入的?shù)據(jù)通常都是字符串或數(shù)字的形式。例如數(shù)值有時被映射為“真”或“假”的布爾類型,有時要被映射為驗證紅綠藍顏色的枚舉類型。在程序中長時間傳遞類型不明的數(shù)據(jù)(枚舉、布爾、指針都是個數(shù)字而已),會增加程序的復(fù)雜度和崩潰的可能性,如在需要輸入顏色枚舉值的地方輸入了“假”。因此,微信公眾號【嵌入式系統(tǒng)】提醒,應(yīng)該在輸入數(shù)據(jù)后立即將其轉(zhuǎn)換到恰當(dāng)?shù)念愋?,少用魔法?shù)。后文會闡述實用方法可用于確定程序需要檢查哪些輸入數(shù)據(jù)。

1.3 決定如何處理錯誤的輸入數(shù)據(jù)

一旦檢測到非法的參數(shù),該如何處理它呢?根據(jù)應(yīng)用場景和需求的不同,微信公眾號【嵌入式系統(tǒng)】提醒,后文第3節(jié)“錯誤處理技術(shù)”中會詳細(xì)描述這些。

防御式編程是提高軟件質(zhì)量技術(shù)的有益輔助手段,在設(shè)計過程和調(diào)試中避免防止引入錯誤。防范看似微小的錯誤,收獲可能遠(yuǎn)遠(yuǎn)超出你的想象。微信公眾號【嵌入式系統(tǒng)】提醒可參考《高質(zhì)量嵌入式軟件的開發(fā)技巧》。

2 斷言

斷言(assertion)是指在開發(fā)期間使用的、讓程序在運行時進行自檢的代碼,通常是一個子程序或宏。斷言為真則表明程序運行正常,若斷言為假,則意味著它在代碼中發(fā)現(xiàn)了意料之外的錯誤。斷言對于大型的復(fù)雜程序或可靠性要求高的程序來說尤其有用,通過斷言,程序員能更快速地排查出程序里不匹配的接口假定和錯誤等。

2.1 建立斷言機制

斷言通常含有兩個參數(shù):一個描述假設(shè)為真時的情況的布爾表達式,和一個斷言為假時需要顯示的信息??梢杂脭嘌詸z查如下假定:

■ ?輸入?yún)?shù)或輸出參數(shù)的取值處于預(yù)期的范圍內(nèi);

■ 子程序開始(或結(jié)束)時文件或流是處于打開(或關(guān)閉)的狀態(tài);

■ 僅用于輸入的變量的值沒有被子程序篡改;

■ 指針非空;

■ 傳入子程序的數(shù)組或其他容器至少能容納X個數(shù)據(jù)元素;

這里列出的只是一些基本假定,正常情況下,并不希望用戶看到產(chǎn)品代碼(Release版本)中的斷言信息。斷言通常只是在開發(fā)階段被編譯到目標(biāo)代碼中,而在生成產(chǎn)品代碼時并不編譯進去。在開發(fā)階段,斷言可以幫助查清相互矛盾的假定、預(yù)料之外的情況以及傳給子程序的錯誤數(shù)據(jù)等。在生成產(chǎn)品代碼時,可以不把斷言編譯進目標(biāo)代碼里去,以免降低系統(tǒng)的性能。

包括C++在內(nèi)的很多語言都支持?jǐn)嘌浴H绻恢苯又С謹(jǐn)嘌哉Z句,可自定義實現(xiàn):

/*微信公眾號【嵌入式系統(tǒng)】斷言demo*/
#ifdef?DEBUG
#define?ASSERT(condition) 
?if(!condition) ? 
? ? ?assert_handle(__FILE__ , __LINE__)
#else
?#define?ASSERT(condition) NULL
#endif

2.2 斷言的建議

錯誤處理代碼針對預(yù)期會發(fā)生的狀況,斷言處理絕不應(yīng)該發(fā)生的狀況。錯誤處理通常用來檢查有害的輸入數(shù)據(jù), 而斷言是用于檢查代碼中的bug。

2.2.1 避免把需要執(zhí)行的代碼放到斷言中

如果把邏輯代碼寫在斷言里,那么當(dāng)關(guān)閉斷言功能時,編譯器很可能就把這些代碼除排在外了。所以什么時機使用斷言或者錯誤處理,需要分版本。

2.2.2 用斷言來注解并驗證前條件和后條件

前條件是調(diào)用方對其所調(diào)用的代碼要承擔(dān)的義務(wù),后條件是子程序執(zhí)行結(jié)束后要確保為真的屬性。斷言是用來說明前條件和后條件的有利工具。

微信公眾號【嵌入式系統(tǒng)】舉例,子程序傳入的兩個參數(shù)為浮點,表示經(jīng)度和緯度,(不考慮方向,只是對數(shù)值范圍進行判斷)。

如緯度合理范圍是0到90,若數(shù)據(jù)來源是系統(tǒng)外部,比如GNSS芯片輸出到處理器,對輸入數(shù)值可以使用錯誤處理。

//入口前條件
if(latitude>90)
{
?return?-1;
}

而如果變量的值是源于可信的系統(tǒng)內(nèi)部,并且這段程序是基于該值不會超出合法范圍的假定而設(shè)計,使用斷言則是非常合適的。

//內(nèi)部處理完將退出的后條件
ASSERT(latitude<=90);

2.2.3 高健壯性的代碼應(yīng)先使用斷言再處理錯誤

對于每種可能出錯的條件,通常子程序要么使用斷言,要么使用錯誤處理代碼來進行處理,一般不會同時使用兩者。

然而,現(xiàn)實世界中的程序和項目通常都很混亂。在某些復(fù)雜項目中,可能同時用斷言和錯誤處理代碼來處理同一個錯誤。在代碼中對理論上始終為真的條件都加上斷言,同時也用錯誤處理代碼處理這些錯誤,尤其對復(fù)雜且生命周期很長的應(yīng)用程序而言,斷言是非常有用的。

3 錯誤處理技術(shù)

斷言可以用于處理理論上不應(yīng)發(fā)生的錯誤。那如何處理那些預(yù)料中可能要發(fā)生的錯誤呢?根據(jù)所處情形的不同,可以返回中立值、換用下一個正確數(shù)據(jù)、返回與前次相同的值、換用最接近的有效值、在日志文件中記錄警告信息、返回一個錯誤碼、調(diào)用錯誤處理子程序、顯示出錯信息或者關(guān)閉程序,或這些技術(shù)結(jié)合起來使用。

3.1 錯誤處理技術(shù)

3.1.1 返回中立值

處理錯誤數(shù)據(jù)的最簡單的做法就是繼續(xù)執(zhí)行,并簡單地返回一個沒有危害的數(shù)值。比如數(shù)值計算可以返回0,字符串操作可以返回空字符串,指針操作可以返回空指針等。如視頻游戲中的繪圖子程序接收到了一個錯誤的顏色輸入,那它可以用默認(rèn)的背景色或前景色繼續(xù)繪制。但對于顯示病人X 光片的繪圖子程序而言,最好還是不要顯示某個“中立值”。

3.1.2 換用下一個正確的數(shù)據(jù)

在處理數(shù)據(jù)流的時候,有時只需返回下一個正確的數(shù)據(jù)。假設(shè)以每秒10次的頻率讀取環(huán)境溫度數(shù)據(jù),如果某一次得到的數(shù)據(jù)有誤,只需再等上1/10秒然后繼續(xù)讀取即可,再算法平滑處理。微信公眾號【嵌入式系統(tǒng)】提醒可參考《嵌入式算法3---滑動平均濾波法》。

3.1.3 返回與前次相同的數(shù)據(jù)

如前面提到的環(huán)境溫度讀取軟件,某次讀取中沒有獲得數(shù)據(jù),那么可以簡單地返回前一次的讀取結(jié)果。根據(jù)應(yīng)用領(lǐng)域的情況,溫度在1/10秒的時間內(nèi)不會發(fā)生太大改變。而在視頻游戲里,如果要用一種無效的顏色重繪屏幕的某個區(qū)域,那么可以簡單地使用上一次繪圖時使用的顏色。但如果是處理自動取款機上的交易,則不能使用“和最后一次相同的答案”了,因為那可是前一個用戶的銀行賬號。

3.1.4 換用最接近的合法值

在有些情況下,可以選擇返回最接近的那個合法值,例如溫度計可測量范圍在0到100攝氏度之間,如果檢測結(jié)果大于100,可以把它替換為100。

3.1.5 把警告信息記錄到日志文件中

檢測到錯誤數(shù)據(jù)時,可以選擇在日志文件中記錄一條警告信息,然后繼續(xù)執(zhí)行。這種方法可以同其他的錯誤處理技術(shù)結(jié)合使用。如果用到了日志文件,還要考慮是否能夠安全地公開它,是否需要對其進行加密或施加其他方式的保護,是否影響正常功能。微信公眾號【嵌入式系統(tǒng)】提醒可參考《嵌入式算法6---AES加密/解密算法》。

3.1.6 返回一個錯誤碼

只讓系統(tǒng)的某些部分處理錯誤,其他部分則不在本地(局部)處理錯誤,而只是簡單地報告說有錯誤發(fā)生,并信任調(diào)用上游的子程序會處理該錯誤。(微信公眾號【嵌入式系統(tǒng)】提醒,區(qū)分返回值正常和錯誤,可以使用0和負(fù)數(shù),也有使用正數(shù)和0的風(fēng)格,一般前者比較通用)。返回錯誤碼,更為重要的是要決定哪些只是報告所發(fā)生的錯誤(退出當(dāng)前子程序不管后續(xù)操作),哪些應(yīng)該直接處理錯誤(針對錯誤的后續(xù)補救措施)。

3.1.7 調(diào)用錯誤處理子程序

把錯誤處理都集中在一個全局的錯誤處理子程序中。這種方法的優(yōu)點在于能把錯誤處理的職責(zé)集中到一起,從而讓調(diào)試工作更為簡單、統(tǒng)一,而代價是整個程序都要知道這個集中點并與之緊密耦合。

3.1.8 錯誤發(fā)生時顯示出錯消息

這種方法可以把錯誤處理的開銷減到最少,然而它也可能會讓用戶界面中出現(xiàn)的信息散布到整個應(yīng)用程序中。當(dāng)創(chuàng)建一套統(tǒng)一協(xié)調(diào)的用戶界面時,或讓用戶界面部分與系統(tǒng)的其他部分清晰地分開,或想把軟件本地化到另一種不同的語言時,都會面臨挑戰(zhàn)。還要當(dāng)心不能告訴系統(tǒng)的潛在攻擊者太多東西,攻擊者有時利用錯誤信息來發(fā)現(xiàn)如何攻擊這個系統(tǒng)。

3.1.9 用最妥當(dāng)?shù)姆绞皆诰植刻幚礤e誤

一些設(shè)計方案要求在局部解決所有遇到的錯誤,而具體使用何種錯誤處理方法,則留給設(shè)計和實現(xiàn)會遇到錯誤的這部分系統(tǒng)的程序員來決定。這種方法給予每個程序員很大的靈活度,但也帶來顯著的風(fēng)險,即系統(tǒng)的整體性能將無法滿足對其正確性或可靠性的需求(不同子模塊對同類錯誤的錯誤不一致)。

3.1.10 關(guān)閉程序

有些系統(tǒng)一旦檢測到錯誤就會關(guān)閉。這一方法適用于人身安全攸關(guān)的應(yīng)用程序。如用作控制治療病人的放療設(shè)備的軟件,接收到了錯誤的放射劑量輸入數(shù)據(jù),在這種情況下,關(guān)閉程序是最佳的選擇。

3.2 健壯性與正確性

正如前面視頻游戲和X光機的例子,處理錯誤最恰當(dāng)?shù)姆绞揭鶕?jù)出現(xiàn)錯誤的軟件的類別而定。錯誤處理方式有時更側(cè)重于正確性,而有時則更側(cè)重于健壯性。但嚴(yán)格來說,這兩個術(shù)語在程度上是截然相反的。正確性意味著永不返回不準(zhǔn)確的結(jié)果,哪怕不返回結(jié)果也比返回不準(zhǔn)確的結(jié)果好。然而,健壯性則意味著要不斷嘗試采取某些措施,以保證軟件可以持續(xù)地運轉(zhuǎn)下去,哪怕有時做出一些不夠準(zhǔn)確的結(jié)果。

人身安全攸關(guān)的軟件往往更傾向于正確性而非健壯性,消費類應(yīng)用軟件往往更注重健壯性而非正確性。再戲謔點,無人職守的消費電子產(chǎn)品只要看門狗正常,什么問題都可以暫時忽略;軟件質(zhì)量也要兼顧成本。

3.3 高層次設(shè)計對錯誤處理方式的影響

對錯誤進行處理的方式會直接關(guān)系到軟件能否滿足在正確性、健壯性和其他非功能性指標(biāo)方面的要求。確定一種通用的處理錯誤參數(shù)的方法,是架構(gòu)層次(或稱高層次)的設(shè)計決策。

一旦確定了某種方法,就要確保始終如一地貫徹。如果決定讓高層的代碼來處理錯誤,而低層的代碼只需簡單地報告錯誤,那么就要確保高層的代碼真的處理了錯誤。嵌入式C語言允許忽略“函數(shù)返回的是錯誤碼”,但千萬不要忽略錯誤信息。檢查函數(shù)的返回值,即使認(rèn)定某個函數(shù)絕對不會出錯。防御式編程的重點就在于防御那些未曾預(yù)料到的錯誤。

這些指導(dǎo)建議對于系統(tǒng)函數(shù)和自己寫的函數(shù)都是成立的,在每個系統(tǒng)調(diào)用后檢查錯誤碼。一旦檢測到錯誤,就記下錯誤代號和它的描述信息。

3.4 異常處理

異常是把代碼中的錯誤或異常事件傳遞給調(diào)用方的一種特殊手段。如果在一個子程序中遇到了預(yù)料之外的情況,但不知道該如何處理的話,可以拋出一個異常(嵌入式C不支持),就好比是舉起雙手說“我不知道該怎么處理它”。對出錯的前因后果不甚了解的代碼,可以把對控制權(quán)轉(zhuǎn)交給系統(tǒng)中其他能更好地解釋錯誤并采取措施的部分。

4 隔離程序錯誤

4.1 隔欄

船底包括多個獨立的密封艙,如果船只與冰山相撞導(dǎo)致船體破裂,隔離艙就被封閉起來,從而保證船體的其余部位不會受到影響。隔欄就類似這種容損策略。防御式編程的隔離,是把某些接口選定為“安全” 區(qū)域的邊界。對穿越安全區(qū)域邊界的數(shù)據(jù)進行合法性校驗,并當(dāng)數(shù)據(jù)非法時做出敏銳的反映。

也可以把這種方法看做是手術(shù)室里使用的一種技術(shù)。任何東西在允許進入手術(shù)室之前都要經(jīng)過消毒處理,手術(shù)室內(nèi)的任何東西都可以認(rèn)為是安全的。這其中最核心的設(shè)計決策是規(guī)定什么可以進入手術(shù)室,什么不可以進入。

在編程中規(guī)定,哪些子程序可認(rèn)為是在安全區(qū)域內(nèi)的,哪些是在安全區(qū)域外的,哪些負(fù)責(zé)清理數(shù)據(jù)。完成這一工作最簡單的方法,是在得到外部數(shù)據(jù)時立即進行清理。

4.2 隔欄與斷言的關(guān)系

隔欄的使用,使斷言和錯誤處理有了清晰的區(qū)分。隔欄外部的程序應(yīng)使用錯誤處理技術(shù),在那里對數(shù)據(jù)做的任何假定都是不安全的。而隔欄內(nèi)部的程序里就應(yīng)使用斷言技術(shù),因為傳進來的數(shù)據(jù)應(yīng)該已在通過隔欄時被清理過了。如果隔欄內(nèi)部的子程序檢測到了錯誤的數(shù)據(jù),那應(yīng)該是程序里的錯誤而不是數(shù)據(jù)里的錯誤。

規(guī)定隔欄內(nèi)外的代碼模塊,是架構(gòu)層次上的決策。也可以簡單按硬件通信模塊隔離。A芯片和B芯片的數(shù)據(jù)交換部分就是立隔欄的地方。

5 輔助調(diào)試的代碼

防御式編程的另一重要方面是完善調(diào)試、測試代碼。

5.1 區(qū)分產(chǎn)品版和開發(fā)版

入門程序員常常有個誤區(qū),不在乎debug和release版本的差異。產(chǎn)品級的軟件要求能夠快速地運行,而開發(fā)中的軟件則允許運行緩慢。產(chǎn)品級的軟件要節(jié)約資源,而開發(fā)中的軟件在使用的資源時可能比較奢侈。產(chǎn)品級的軟件不應(yīng)向用戶暴露可能引起危險的操作,而開發(fā)中的軟件則可以提供一些額外的調(diào)試操作。

開發(fā)期間犧牲一些速度和對資源的使用,來換取一些可以讓開發(fā)、測試更順暢的內(nèi)置功能。比如引入輔助調(diào)試的代碼,例如某個功能每天啟動一次,可以模擬加快時鐘頻率用于調(diào)試,或者支持配置為10分鐘循環(huán)一次。

5.2 采用進攻式編程

異常情況應(yīng)在開發(fā)、測試階段讓它顯現(xiàn)出來,而在產(chǎn)品實用時讓它能夠自我恢復(fù),這種方式稱為 “進攻式編程”。

如switch/case 語句處理場景,在最終的產(chǎn)品代碼里,針對默認(rèn)情況default子句(意料之外的)處理則應(yīng)更穩(wěn)妥一些,比如在錯誤日志文件中記錄該消息,而開發(fā)期間進攻式編程的方法,則是使用斷言語句使程序終止運行。

不要讓程序員養(yǎng)成壞習(xí)慣,碰到警告就忽略,而應(yīng)該讓問題引起的麻煩越大越好,這樣它才被重視、被修復(fù)。確保每一個意料之外的分支都能產(chǎn)生嚴(yán)重錯誤(比如讓程序終止運行),或者至少讓這些錯誤不被忽視。最好的防守正是大膽進攻,在開發(fā)階段發(fā)現(xiàn)并解決各種斷言重啟錯誤,才能讓產(chǎn)品發(fā)布后高枕無憂。

5.3 移除調(diào)試代碼

如果程序是個人或內(nèi)部玩玩,那么調(diào)試代碼留在程序里并無大礙。但如果是商用軟件,則會使軟件的體積變大且速度變慢,對性能造成影響。需采取一些措施避免調(diào)試代碼和正式程序代碼糾纏不清。

5.3.1 使用內(nèi)置的預(yù)處理器

嵌入式C/C++ 開發(fā)環(huán)境,可以用預(yù)處理器開關(guān)來包含或排除調(diào)試用的代碼,使用預(yù)處理器的代碼范例:

/*微信公眾號【嵌入式系統(tǒng)】demo*/
#define?DEBUG
#if?defined(DEBUG )?
? ??//debugging ?code
#endif

這一用法可以有幾種變化。除直接定義 DEBUG以外,還可以給它賦一個值,然后判斷其數(shù)值,而不僅是去判斷它是否已經(jīng)定義了(未定義只判斷的為0), 這可以區(qū)分不同級別的調(diào)試代碼。

/*微信公眾號【嵌入式系統(tǒng)】*/
#define?DEBUG ? 100
#if?DEBUG ?> 10
? ??//...
#elif?DEBUG > 20
? ??//...
#else
?//...
#endif

如果不喜歡 #if defined 一類語句散布在代碼里的各處,可以寫一個預(yù)處理宏來完成同樣的任務(wù):

#if?defined(DEBUG)
? ??#define?debug_code(frmt, chengj...) ? ?{?/*debugging ?code*/?}
#else
? ??#define?debug_code(frmt, chengj...)
#endif

這種方法在使用時也可以有多種變化,微信公眾號【嵌入式系統(tǒng)】提醒可參考《高質(zhì)量嵌入式軟件的開發(fā)技巧》、《項目配置與編譯自動化》、《嵌入式軟件分層隔離的典范》相關(guān)章節(jié)。

5.3.2 自定義調(diào)試存根

可以調(diào)用一段子程序進行調(diào)試檢查,在開發(fā)階段,該子程序可執(zhí)行若干操作之后才能把控制權(quán)交還給其調(diào)用方;而在產(chǎn)品代碼里,可用一個簡易或空子程序替換這個復(fù)雜的子程序,但這種方法會有一點性能損耗。

其實現(xiàn)就是編寫一個函數(shù)指針,debug版本為其賦值指向參數(shù)校驗等子函數(shù),在release版本該函數(shù)指針為空不執(zhí)行。這種情況下也可變化為通過指令動態(tài)控制是否執(zhí)行調(diào)試代碼片段。

5.4 產(chǎn)品代碼中保留多少防御式代碼

防御式編程存在一種矛盾的觀念,即在開發(fā)階段希望錯誤能引人注意,寧愿看它的臉色,也不想冒險地去忽視它;但在產(chǎn)品發(fā)布階段,卻想讓錯誤能盡可能地低調(diào),讓程序能穩(wěn)妥地恢復(fù)或停止。

1.保留檢查重要錯誤的代碼 ? ?確定程序的哪些部分可以承擔(dān)未檢測出錯誤而造成的后果,而哪些部分不能承擔(dān)。比如開發(fā)一個電子手表程序,如果在屏幕刷新部分的代碼中存在未檢測出的錯誤,可能可以忍受,因為錯誤造成的主要后果是屏幕顯示錯亂;但如果是數(shù)據(jù)存儲的代碼存在問題,就無法接受了,因為這種錯誤會導(dǎo)致用戶的電子手表出現(xiàn)無法使用的結(jié)果。

2.去掉檢查細(xì)微錯誤的代碼 ? ?如果一個錯誤帶來的影響確實微乎其微的話,可以把檢查它的代碼去掉。在前面的例子中,可以把檢查電子手表屏幕刷新的代碼去掉。這里的“去掉”并不是指永久地刪除代碼,而是指利用版本控制、預(yù)編譯器開關(guān)或其他技術(shù)手段來不編譯這段特定代碼。如果程序所占的空間不是問題,也可以把錯誤檢查代碼全部保留下來,同時不動聲色地把錯誤信息記錄在日志文件。

3.去掉可能導(dǎo)致程序硬性崩潰的代碼 ? ?程序在開發(fā)階段檢測到了錯誤,盡可能地引人注意,以便能修復(fù)它,實現(xiàn)這一目的的最好方法通常就是讓程序在檢測到錯誤后打印出一份調(diào)試信息,然后崩潰退出,這種情況使用斷言對于細(xì)微的錯誤很有用。

然而產(chǎn)品階段,例如軟件用戶需要在程序崩潰之前有機會保存他們的工作成果,為了讓程序給他們留出足夠的保存時間,用戶甚至?xí)淌艹绦虻囊恍┕之愋袨椤O喾?,如果程序中的一些代碼導(dǎo)致了用戶工作成果的丟失,那無論這些代碼對幫助調(diào)試程序并最終改善程序質(zhì)量有多大的貢獻,用戶也不會心存感激。因此,如果程序里存在著可能導(dǎo)致數(shù)據(jù)丟失的調(diào)試代碼,即因為調(diào)試功能導(dǎo)致正常功能崩潰的代碼一定要從最終軟件產(chǎn)品中去掉。

4.保留可以讓程序穩(wěn)妥地崩潰的代碼 ? ?如果程序有能夠檢測出潛在嚴(yán)重錯誤的調(diào)試代碼,那么應(yīng)該保留那些能讓程序穩(wěn)妥地崩潰的代碼。以便開發(fā)人員可利用保留下來的輔助調(diào)試信息,診斷出問題所在并修復(fù)。

5.為技術(shù)支持人員記錄錯誤信息 ? ?可以考慮在產(chǎn)品代碼中保留輔助調(diào)試用的代碼,但要改變它們的工作方式,以便與最終產(chǎn)品軟件相適應(yīng)。如果開發(fā)時在代碼里大量地使用了斷言來中止程序的執(zhí)行,那么在發(fā)布產(chǎn)品時務(wù)必把斷言子程序改為向日志文件中記錄信息,而不是徹底去掉這些代碼。

6 防御式編程的姿態(tài)

6.1 防御要點

過度的防御式編程也會引起問題,如果在每一個能想到的地方,用每一種能想到的方法檢查傳入的數(shù)據(jù),那么程序?qū)兊糜纺[而緩慢。而且引入的額外代碼增加了軟件的復(fù)雜度。因此,要考慮什么地方需要進行防御。

■ 防御式編程技術(shù)是為了讓錯誤更易發(fā)現(xiàn)、更易修改,并減少錯誤對產(chǎn)品代碼的破壞。

■ 斷言可以幫助盡早發(fā)現(xiàn)錯誤。

6.2 軟件過程管理

架構(gòu)層將系統(tǒng)劃分為多個子系統(tǒng),小心定義錯誤處理的方法,傳遞給子程序的參數(shù)數(shù)目應(yīng)盡量少,只傳遞保持子程序接口抽象所必需的參數(shù)。

對于多個程序員參與的項目,組織性的重要程度超過了個人技能。一個龐大的團隊,其合力并不等于每個人能力的簡單相加。

開發(fā)過程之所以重要,主要原因是軟件設(shè)計決定了質(zhì)量。先拼湊,再通過測試剔除缺陷的思路是無法產(chǎn)出高質(zhì)量的軟件的。

6.3 首先為人寫程序,其次才是為機器

計算機不關(guān)心代碼是否好讀,它更善于讀二進制指令;編寫可讀性好的代碼,是為了便于別人以及自己看懂。

可讀的代碼寫起來并不比含糊的代碼多花時間,運行時至少不比后者慢。如果能輕松閱讀理解自己寫的代碼,確保該代碼能工作也會更容易。不僅如此,代碼在復(fù)審過程中也要閱讀它;自己或別人修復(fù)錯誤時也要讀;改動代碼功能時還要讀;當(dāng)別人利用調(diào)用它時也要讀。使代碼可讀性好,并非是開發(fā)過程中的可有可無部分。微信公眾號【嵌入式系統(tǒng)】提醒可參考《代碼審查那些事》《嵌入式C編碼規(guī)范》《代碼的保養(yǎng)》相關(guān)章節(jié)。

實際工作中大部分是修改維護既有代碼,需要先閱讀、理解以前的代碼的含義,如果能在代碼可讀性好,附帶詳細(xì)說明,必能事半功倍。

7 小結(jié)

正經(jīng)的防御式編程不是防人,而是防代碼異常。

 

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

嵌入式系統(tǒng)開發(fā)技術(shù)交流,軟件開發(fā)的思路與方案共享,行業(yè)資訊的分享。