摘要:典型的緩沖區(qū)越界注入等攻擊手段,或者網(wǎng)絡(luò)異常等。這假設(shè)實(shí)際上往往是錯(cuò)誤的,絕大多數(shù)系統(tǒng)實(shí)際對用戶信息的要求不僅僅是滿足基本類型信息即可,而有顯式或隱式的其他條件。當(dāng)然對于這種可能出現(xiàn)的異常應(yīng)該在程序的其他地方捕獲并加以恰當(dāng)?shù)恼故尽?/p>
不管你的水平多高,多么認(rèn)真仔細(xì),程序員總是會(huì)制造出 bug。bug 的根本來源是:
預(yù)期之外的用戶輸入或系統(tǒng)輸入。典型的緩沖區(qū)越界、SQL 注入等攻擊手段,或者網(wǎng)絡(luò)異常等。它的特點(diǎn)是在通常的用戶輸入時(shí),程序表現(xiàn)得非常正常;但對于不普通、或者異常的用戶輸入沒有做任何防御。
程序員的疏忽或理解錯(cuò)誤。例如不正確地調(diào)用了參數(shù),按照不正確地次序調(diào)用函數(shù),錯(cuò)誤的線程數(shù)據(jù)共享。
開發(fā)組件之外的系統(tǒng)其他部分,如中間件、數(shù)據(jù)庫系統(tǒng)或其他組件本身的 bug,也會(huì)給我們本來完美的程序帶來 bug。但這種 bug 我們可以稱為是衍生錯(cuò)誤,本質(zhì)是上述兩種 bug 之一。
所謂防御性編程(Defensive Programming) 一般針對第一類錯(cuò)誤,它的原則是:只要有用戶或系統(tǒng)輸入,就應(yīng)該對它的合規(guī)性進(jìn)行嚴(yán)格的檢查。例如:
javapublic String findUserName(String userId) { return db.userNameFrom(db, userId); }
如果 nameInput 是直接來自于用戶輸入(不論是通過頁面,還是 URL,還是應(yīng)用程序),上述簡單程序意味著:只要 userId 是一個(gè)字符串,它就是合法的!而我們就將它作為合法字符串在系統(tǒng)各處傳遞。這假設(shè)實(shí)際上往往是錯(cuò)誤的,絕大多數(shù)系統(tǒng)實(shí)際對用戶信息的要求不僅僅是滿足基本類型信息即可,而有顯式或隱式的其他條件。例如,userId 很可能有長度限制,例如長于 6 個(gè),短于20個(gè)字符;它很可能有取值限制,例如只能是字符和數(shù)字。而我們的編譯器,即使是 Java 的強(qiáng)類型編譯器,對這種要求一無所知。是程序員的責(zé)任來檢查這些額外要求:
java//一個(gè)特別的類 InputChecker 來檢查輸入 public void checkUserId(String userIdInput) { if (userIdInput.length < 6 || userIdInput.length > 20) throw new InvalidInputException("userId", userIdInput); } //需要和用戶輸入直接打交道的地方調(diào)用 inputChecker public String findUserName(String userId) { inputChecker.checkUserId(userId); return db.userNameFrom(db, userId); }
注意,由于異常輸入是一種異常,用運(yùn)行時(shí)異常來表達(dá)它是合理的。當(dāng)然對于這種可能出現(xiàn)的異常應(yīng)該在程序的其他地方捕獲并加以恰當(dāng)?shù)恼故尽?/p>
這樣的防御性編程代碼是可靠設(shè)計(jì)的一個(gè)良好習(xí)慣,實(shí)際上它也廣泛地被使用。但也有很多程序員將它錯(cuò)誤地過多使用,或者錯(cuò)誤地用它來覆蓋第二類 bug (程序員錯(cuò)誤)。
javapublic ListtransferGameResult(GameRoom room, List gameResult) { if (null == gameResult || gameResult.size() == 0) { return null; ..... }
這段程序的目的是將一種游戲結(jié)果轉(zhuǎn)化為另一種表達(dá)式來方便計(jì)算。其中第一句語句的目的是?
看起來這似乎是防御性編程,想要檢查 gameResult 的輸入錯(cuò)誤:如果 gameResult 不正確輸入,就返回空值。
但這里的 gameResult 是用戶輸入嗎?不可能是,它是一個(gè) int[],只可能是來自于程序其他地方,也就是說來自于這段代碼的調(diào)用者,另一個(gè)程序員。
用正確的參數(shù)來調(diào)用是程序員的責(zé)任,我們不能假裝我們的程序可以正常處理 null 的參數(shù)或者長度為0 的輸入,實(shí)際上對此我們沒有處理能力。因此,這里真正防御性編程的處理是:
javapublic ListtransferGameResult(GameRoom room, List gameResult) { assert(gameResult != null && gameResult.size() > 0); ..... }
表明我們的這段程序?qū)斎雲(yún)?shù)的嚴(yán)格要求。有人可能會(huì)說,assert 不是不應(yīng)該出現(xiàn)在運(yùn)行時(shí)的系統(tǒng)中嗎?至少對于 Java 語言來說,assert 將立即拋出一個(gè)異常(如果你沒有停用 assert 的話),調(diào)用程序員如果對引用這段代碼的程序做了合理的測試,他會(huì)立即發(fā)現(xiàn)這個(gè)錯(cuò)誤,就有了糾正它的機(jī)會(huì)。但按照之前的寫法,調(diào)用程序員則很可能不能看到任何問題,而使用不正確的參數(shù)調(diào)用,本身就是一個(gè)錯(cuò)誤!這樣,我們成功地幫助程序員掩蓋了他的錯(cuò)誤,增加了將 bug 帶到生產(chǎn)系統(tǒng)中的機(jī)會(huì)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/64292.html
摘要:網(wǎng)易跨境電商考拉海購在線筆試現(xiàn)場技術(shù)面面。如何看待校招面試招聘,對公司而言,是尋找勞動(dòng)力對員工而言,是尋找未來的同事。 如何準(zhǔn)備校招技術(shù)面試 標(biāo)簽 : 面試 [TOC] 2017 年互聯(lián)網(wǎng)校招已近尾聲,作為一個(gè)非 CS 專業(yè)的應(yīng)屆生,零 ACM 經(jīng)驗(yàn)、零期刊論文發(fā)表,我通過自己的努力和準(zhǔn)備,從找實(shí)習(xí)到校招一路運(yùn)氣不錯(cuò),面試全部通過,謹(jǐn)以此文記錄我的校招感悟。 寫在前面 寫作動(dòng)機(jī) ...
摘要:單元測試過后,機(jī)器狀態(tài)保持不變。單元測試應(yīng)該產(chǎn)生可重復(fù)一致的結(jié)果。然并卵都說國內(nèi)很多程序員是不寫單元測試的,甚至從來都不寫,筆者當(dāng)年做的時(shí)候也沒寫過幾次捂臉?;貧w測試在單元測試的基礎(chǔ)上,我們就能夠建立關(guān)于這一模塊的回歸測試。 showImg(https://segmentfault.com/img/bVPMPd?w=463&h=312); 送給初級程序員的測試認(rèn)知文 作為開發(fā)同學(xué),一些...
摘要:關(guān)于自己屆畢業(yè)生一本雙非學(xué)校,非科班可能和很多人一樣,因?yàn)樾r(shí)候喜歡打游戲,所以大學(xué)一直想學(xué)編程,但因?yàn)榉N種原因,自己來到了一個(gè)硬件相關(guān)專業(yè),但由于現(xiàn)實(shí)和興趣,自己又從事了軟件相關(guān)的工作。找實(shí)習(xí)實(shí)習(xí)對于之后的秋招來說,是非常非常重要的。 ...
閱讀 1157·2021-10-09 09:43
閱讀 18884·2021-09-22 15:52
閱讀 1124·2019-08-30 15:44
閱讀 3087·2019-08-30 15:44
閱讀 3275·2019-08-26 14:07
閱讀 937·2019-08-26 13:55
閱讀 2598·2019-08-26 13:41
閱讀 3119·2019-08-26 13:29