汽車防御性駕駛是一種安全駕駛的理念和實踐,旨在最大程度地減少交通事故的發生,并保護駕駛員、乘客和其他道路使用者的安全。防御性駕駛核心在于合理懷疑其他交通參與者可能會做出危及安全的行為并提前做出預判,比如碰到路口預判前面可能沖出來小孩。碰到前面緩慢,預料到自己急剎可能導致跟得很近的后車追尾上來。
在編程領域也有防御性編碼(Defensive coding),有著與汽車防御性駕駛相似的理念,它也是保護我們的系統的一種實踐,減少線上事故的發生。
一個大型系統必然是多個團隊的協作成果,團隊之間的接口調用通常靠文檔規范約定,這些接口文檔也便形成了一份份規約??赡苡腥藭f對方團隊不遵守文檔規范是對方的問題,不關我們團隊的事情啊。對于這個問題,我只想說只關心自己不被追責而不關心技術全局是否完善的技術人員注定發展空間是有限的。因為各類規范只能保證下限,在團隊與團隊的協作中僅僅按契約完成規范還不夠,就像僅按交規開車并不能防止事故一樣,我們還需要防御性編碼的意識與實踐。那怎樣做到防御性編碼呢?下面是最常見的一些手段:
作為本系統與外部系統接口交互的記錄,必須要記錄日志。日志無論在事中查錯、事后追責提供證據方面都有著不可替代的作用。如果自己的系統沒有日志,就只能寄希望于上游或下游記錄了日志,這有時不僅受制于人,還可能會為日后互相甩鍋埋下隱患。對于重要的(如涉及資金),或者頻繁需要查日志的系統間交互,不僅需要打印應用日志,還需要考慮將這些日志記錄到數據庫或NoSQL中并提供方便的查詢界面以提高查詢的效率。
比如某第三方支付系統的接口規定,調用支付接口后返回報文的支付狀態字段,1代表支付成功,0代表支付失敗,-1代表不確定。結果線上因為對方升級協議的原因,多了一個狀態2,而這個業務的程序員又沒足夠的經驗預判這種情況,引起了大面積的支付錯誤投訴。他的代碼是這樣寫的:
if(status == -1){ //支付結果不確定處理}else if(status == 1){ //支付結果成功處理}else{ //支付結果失敗處理}
可以看到他把新出現的這個狀態當成支付失敗處理了。對于這種出現了協議之外的值的情況,在業務開始可以拒絕的情況下直接拒絕該請求調用。在不能拒絕的情況下(如本例中用戶已經輸入支付密碼完成支付,不能再拒絕請求調用了)則需要在業務上兼容處理,比如這里可以把這種情況視為支付結果不確定,最后再根據對賬取得最終的支付結果是成功還是失敗。
常見的輸入類型有整數、小數、字符串等,我們必須檢查輸入字段是否符合接口文檔規定的類型,并限制字段的基本長度。同時也從業務角度去檢查字符串是否滿足格式,如常見的身份證、郵箱、手機號碼等是否滿足各自規定的業務格式。
比如你提供了一個搜索接口,但你不確定上游會輸入一句話還是一篇文章,光靠雙方自覺地按接口文檔去約束顯得既蒼白又無力,一刀切停止服務似乎又過于簡單粗暴。一個可行的方法是無論上游輸入多少,只截取前30個字符去ElasticSearch中查詢(接口協議文檔中明確約定)。有時候為了自己系統的安全,對于明顯超出正常業務范圍的字符也會進行過濾,如常見的<>&/r/n等。
一個常見的問題是只檢查了下限沒檢查上限,比如商品數量數據,通常會做大于0檢查,但卻很少會去檢查上限。比如在Java中當我們使用內置運算符做數學運算時,如果結果超出變量類型表達范圍,結果會并不會拋出異常,而計算結果又是不符合預期的。如兩個大的正整數相乘,結果可能溢出后變成了負的。這就要求我們對字段的數值范圍,業務取值范圍進行進行邊界判斷。
我們知道,系統之間交互模式有推送與拉取。拉取雖在及時性上不如推送,但它可以根據系統自身的能力量力而為地處理業務,能較好的保護自身。而推送則有可能出現能力不匹配的情況,如果上游推送很快,自己的系統又處理不過來則需要提前做好限流,并確保上游能兼容限流的情況。
如果指望通過一紙規定就能避免調用方重復發起,就能避免被調用方重復處理問題,那就未免顯得有點天真了。因為重復可能發生在應用層,也可能發生在框架或其它層,未必就是調用方主觀的行為。對重復請求的處理,有些情況可以用冪等處理,除了部分業務本身就是冪等的,其他多數是通過業務惟一索引進行限制實現。需要指出的是有些系統是通過先查詢數據庫或緩存再判斷是否是重復請求的還需要注意原子操作問題。
要特別小心涉及金額的接口處理。對金額的處理,不同的系統有不同的標準,同樣對于1.23元,某互聯網公司的支付系統規定以分為單位的整數(如123);某電信系統則規定以厘為單位的整數(如1230);某金融系統又規定是以元為單位的字符串(如1.23)。尤其需要小心對最后一種情況的數值范圍判斷,涉及到字符串轉數字,要小心精度丟失。在Java中涉及小數的計算,多數情況使用BigDecimal。
防御性編碼要求我們跳出責任歸屬的視角系統性地看待全局問題,對可能發生問題的地方提前預防,對事故高發情況提前預判,保護自己系統的同時也保護上下游系統,取得全局最優解而不僅僅是局部最優解。
本文鏈接:http://www.tebozhan.com/showinfo-26-11827-0.html防御性編碼的意識與實踐
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 為什么Java官方不推薦池化虛擬線程?
下一篇: 您可能會錯過的七個有用的 GIT 命令