編程須謹(jǐn)記:大象不咬人,蚊子卻致命
本文轉(zhuǎn)載自公眾號(hào)“讀芯術(shù)”(ID:AI_Discovery)。
1996年6月24日,歐洲航天局的阿麗亞娜5號(hào)無(wú)人火箭在發(fā)射僅37秒后爆炸。三億七千萬(wàn)的投資和十幾年的努力在一瞬間付諸東流。
原因?yàn)楹?源自一個(gè)簡(jiǎn)單的軟件漏洞。它試圖將可代表數(shù)十億個(gè)潛在值的64位浮點(diǎn)型變量存儲(chǔ)至只能代表65535個(gè)潛在值的16位整數(shù)中。也就是說,為了讓火箭進(jìn)入太空,需要分配更多的儲(chǔ)存空間。
這告訴我們,小的失誤會(huì)造成嚴(yán)重的后果,潛在危險(xiǎn)最大,為此付出的代價(jià)最高。
大象不咬人,它們只想與你成為朋友
程序出錯(cuò)時(shí),幾乎每一個(gè)明顯的錯(cuò)誤都會(huì)在代碼中顯現(xiàn)。這些錯(cuò)誤會(huì)引發(fā)顯而易見的編譯器錯(cuò)誤或運(yùn)行錯(cuò)誤,而編譯器錯(cuò)誤或運(yùn)行錯(cuò)誤會(huì)在用戶界面或編譯器中體現(xiàn)出來(lái)。開發(fā)人員很清楚,這些問題需要立即解決,所以這些錯(cuò)誤幾乎不會(huì)引發(fā)擔(dān)憂。
最有可能的情況是,發(fā)現(xiàn)錯(cuò)誤后,開發(fā)人員立刻進(jìn)行修復(fù),絕不會(huì)提交一份板上釘釘?shù)陌氤善坊蚓帉懥瞬环项A(yù)期目標(biāo)的代碼。
大象不咬人,因?yàn)榭梢砸灾庇^的方式將它們輕易馴服。從長(zhǎng)遠(yuǎn)角度來(lái)看,它們不會(huì)惹任何麻煩,也不會(huì)造成任何傷害(除非命令它們那么做)。它們?nèi)菀鬃R(shí)別,通常會(huì)說,“看看我!關(guān)注我,我會(huì)向你表達(dá)愛意,你不會(huì)后悔的。”
蚊子會(huì)成群結(jié)隊(duì)地叮咬你,讓你患上萊姆病,甚至殺死你
與大象相對(duì)的是蚊子——代碼中看似無(wú)關(guān)緊要且并不明顯的部分,作為隱藏?cái)?shù)據(jù)藏匿于看似有效的代碼的backburner中。他們隨時(shí)準(zhǔn)備發(fā)動(dòng)攻擊,讓代碼毀于一旦。由此導(dǎo)致的錯(cuò)誤包括:邏輯錯(cuò)誤、設(shè)計(jì)問題、雜亂的代碼以及有缺陷的非優(yōu)化算法。
阿麗亞娜5號(hào)火箭發(fā)射的問題在于,研究人員復(fù)制了此前成功發(fā)射的阿麗亞娜4號(hào)火箭的工作代碼。研究人員顯然認(rèn)為這些代碼同樣適用于阿麗亞娜5號(hào),但這些代碼并不能滿足阿麗亞娜5號(hào)火箭的環(huán)境需求,無(wú)法應(yīng)對(duì)新環(huán)境的要求。
圖源:unsplash
最小的錯(cuò)誤卻造成了最大的問題
- 邏輯錯(cuò)誤會(huì)引發(fā)產(chǎn)品處理問題和產(chǎn)品信息顯示問題,減損預(yù)期功能,影響用戶體驗(yàn)。即便開發(fā)人員沒有在應(yīng)用程序的運(yùn)行方式上發(fā)現(xiàn)任何相關(guān)的或可立即識(shí)別的問題,也會(huì)導(dǎo)致用戶流失。
- 設(shè)計(jì)不當(dāng)會(huì)導(dǎo)致類似阿麗亞娜5號(hào)火箭爆炸等問題。這種設(shè)計(jì)下的代碼可以在低計(jì)算量的環(huán)境中工作,但不能在高計(jì)算量的環(huán)境中工作。忽略相關(guān)事宜會(huì)導(dǎo)致系統(tǒng)故障,因?yàn)橄到y(tǒng)不是為處理大規(guī)模操作而設(shè)計(jì)。
此類問題通常在編寫良好的測(cè)試代碼中出現(xiàn),這些測(cè)試代碼旨在給系統(tǒng)施加盡可能多的壓力,但如果不編寫測(cè)試代碼處理這些情況,可能會(huì)發(fā)生令人震驚的黑天鵝事件,從而造成嚴(yán)重的后果。
- 雜亂的代碼會(huì)增加代碼中的錯(cuò)誤和問題識(shí)別難度。除此之外,隨著代碼的擴(kuò)展和修改越發(fā)困難,開發(fā)成本也會(huì)急劇增加。這會(huì)使代碼更容易出現(xiàn)常見錯(cuò)誤。雜亂的代碼出現(xiàn)時(shí),應(yīng)即刻提高警惕,進(jìn)行代碼重構(gòu)。
- 在計(jì)算量大的情況下,非優(yōu)化算法會(huì)影響性能。尤其對(duì)于適用于不適應(yīng)重構(gòu)代碼以更好地執(zhí)行算法的程序員來(lái)說,這個(gè)細(xì)節(jié)很容易被忽視。
當(dāng)遇到加載時(shí)間長(zhǎng)、超時(shí)或限制時(shí)(特別是使用云后端時(shí)),通常會(huì)注意到這個(gè)細(xì)節(jié)。有些代碼單獨(dú)運(yùn)行時(shí)效果會(huì)很好,但在編程時(shí)需意識(shí)到,單獨(dú)運(yùn)行順利并不意味著它能夠與大型數(shù)據(jù)集和其他組件協(xié)同工作。
如果不顧這些小問題,其結(jié)果將會(huì)令人大吃一驚。好消息是,有很多方法可以降低并最小化這些錯(cuò)誤的影響。
使用驅(qū)蟲劑!
不注意小的問題,最終會(huì)面臨最大的問題。在某種程度上,阿麗亞娜5號(hào)事件的程序員值得同情。但是此類事件說明,在大量的壓力測(cè)試和測(cè)試驅(qū)動(dòng)開發(fā)的支持下,仔細(xì)編寫代碼尤為重要。
編程不只是寫出能運(yùn)行的代碼,它還需要仔細(xì)周到的考慮:代碼不是胡亂編寫,許多新老程序員只是把代碼片段拼湊在一起,就像試圖把圓柱體塞進(jìn)方孔一樣。雖然圓柱也能塞進(jìn)去,但并不合理,而且無(wú)論如何也不穩(wěn)固。
圖源:unsplash
因此,在編寫代碼時(shí)最好把這些問題放在心中:
- 代碼是否過于復(fù)雜?應(yīng)如何將其簡(jiǎn)化?
- 是否為代碼編寫了嚴(yán)格的測(cè)試類,這些測(cè)試類具有強(qiáng)大的斷言和測(cè)試功能,以應(yīng)對(duì)多種不同的數(shù)據(jù)飽和和高計(jì)算量情境?是否了解代碼的局限性?
- 函數(shù)是否太小?可以將大型函數(shù)的方法應(yīng)用于小型函數(shù)嗎?
- 變量,類和函數(shù)是否擁有清晰明確的名稱?僅通過閱讀名稱能否明確了解代碼的功能?
- 是否復(fù)制了過多的方法,而這些方法可在多種不同的過程中重用并具有通用功能?重復(fù)方法是絕對(duì)必要的嗎?它們值得應(yīng)用于不同的功能情境嗎?
- 如何處理錯(cuò)誤?是否使用try-catch塊拋出錯(cuò)誤,并對(duì)變量運(yùn)行null檢查?錯(cuò)誤被發(fā)現(xiàn)時(shí),是否有特定的過程來(lái)確保功能平穩(wěn)?
- 代碼是否易于擴(kuò)展和延伸?如果進(jìn)行了修改,需要擔(dān)心任何相依性嗎?
- 代碼是否有能力優(yōu)化來(lái)處理大量數(shù)據(jù)?代碼在運(yùn)行壓力過大時(shí)是否會(huì)引發(fā)錯(cuò)誤或超時(shí)?
當(dāng)然,還可以問自己更多的問題,這些問題足夠匯編成一個(gè)詳盡的清單上述問題可能是最重要的,但卻未得到足夠的重視。通過限制潛在的未知錯(cuò)誤,使不確定的事情成為已知,來(lái)降低代碼中任何錯(cuò)誤的風(fēng)險(xiǎn),而非觀察后才成為已知。
這些大錯(cuò)誤不應(yīng)成為長(zhǎng)期關(guān)注點(diǎn),因?yàn)榇箦e(cuò)誤很容易更正和解決。日常的微小錯(cuò)誤和不一致性才是真正需要關(guān)注的問題。