|
最近我問一個同事為什么需要做某項檢查,他聳聳肩說,“為了保險起見。”在工作中,我見過很多代碼都是為了保險起見而寫的。我自己也寫過這樣的代碼!如果不確定是否存在依賴性,那么我肯定會加上一個檢查,以防拋異常。 我在這里所說無效代碼指的是在代碼中加入了非必要的默認值,比如下面的代碼。 axios.get(url).then(({ data }) => 或者一層層深入檢查數(shù)據(jù)中每個key的存在性。 render() { 還有其他無效代碼,比如防止拋異常。我們常常無意中抑制異常的發(fā)生,就像在墻上掛幅畫掩蓋后面的洞。 乍一看,這并沒有什么問題。但是這種做法并沒有把洞補好,同樣也沒有改好 bug。而且寫入程序的不再是易于追蹤的異常,而是非法的數(shù)據(jù)。如果后端部署錯誤造成返回了一個空的響應(yīng)呢?代碼就會使用那些默認值,而一連串的 && 檢查都會返回 undefined,那么最后頁面上就會顯示 undefined。在 React 代碼中,頁面根本得不到渲染。 計算中有一句格言:“大度地接受,保守地發(fā)送。”有些人可能認為這些原則都有實際應(yīng)用的例子,但是我不太同意。我認為,如果過度使用這些模式,會對代碼庫以及服務(wù)提供的保障缺乏正確的理解。 來自第三方的數(shù)據(jù)或參數(shù) 你的代碼對他人代碼的期望可以看成是一種合約。通常,這個合約只是隱含的,但是應(yīng)該注意數(shù)據(jù)的格式并記錄在文檔中。沒有充分理解并清晰地記錄下來的 API 返回數(shù)據(jù)格式,一旦出錯,如何判斷是誰的代碼出了問題呢?明確的定義可以建立彼此的信任。 當(dāng)向一個外部的 HTTP API 發(fā)送請求的時候,并不需要檢查 response 對象是否包含了 data。data 必然存在,因為這是你和請求庫之間的合約。舉例來說,axios 的文檔定義了返回的響應(yīng)數(shù)據(jù)的格式。而且 response 返回的數(shù)據(jù)結(jié)構(gòu)應(yīng)該也是已知的。除非請求是有狀態(tài)的,或遇到了錯誤,否則響應(yīng)的內(nèi)容永遠是一樣的。這就是你和后臺建立的合約。 在應(yīng)用程序內(nèi)傳遞數(shù)據(jù) 你編寫的函數(shù)和創(chuàng)建的類也是一種合約,但是這個合約由你這個開發(fā)者來保證。你需要對自己的數(shù)據(jù)有信心,代碼會變得更加可預(yù)測,而且故障原因會更加明顯。如果拋出的異常位于錯誤數(shù)據(jù)的附近,那么調(diào)試數(shù)據(jù)錯誤就會變得更簡單。 非必要性的安全意味著,函數(shù)可以繼續(xù)悄悄地傳遞錯誤數(shù)據(jù),直到遇到一個沒有過度防御的函數(shù)。這會導(dǎo)致故障在應(yīng)用程序中某個地方演變成奇怪的行為,而自動化工具很難檢測到這類的問題。調(diào)試工作的難度也會加大,因為你必須一路追溯,直到找到錯誤數(shù)據(jù)的發(fā)生源頭。 我在代碼沙盒中建立了一個過度防御與不安全訪問的例子。 const initialStuff = { 下面的“安全”代碼可以防止拋出異常: const { title, description } = 而如下的不安全代碼可以不經(jīng)檢查賦值: const { title, description } = this.state.stuff.things.meta; 性能與開發(fā)速度 除此之外,條件邏輯的使用是有代價的。分開來看,這些代碼對性能的影響很小,但是如果這種重復(fù)非必要檢查的編程習(xí)慣廣泛蔓延,那么就會產(chǎn)生顯著的耗時。這種影響是巨大的:React 的生產(chǎn)模式會刪除 prop types 檢查,從而獲得性能的顯著提高。一些測試數(shù)據(jù)顯示 React 15 的生產(chǎn)模式的速度比開發(fā)模式提高可以 2-4 倍。 條件邏輯還會增加開發(fā)人員思想上的負擔(dān),這會影響所有依賴于此模塊的代碼。謹慎對待外部數(shù)據(jù)意味著下一個使用數(shù)據(jù)的人不知道它是否值得信賴。如果沒有深入研究數(shù)據(jù)的來源,檢查數(shù)據(jù)的可信度,那么最安全的選擇是將其視為不安全。因此,此代碼的行為將迫使其他開發(fā)人員將其視為不可信,甚至?xí)腥舅行戮帉懙拇a。 如何解決這個問題 當(dāng)我們寫代碼的時候,應(yīng)該花些時間考慮一下極端的例子。 可能發(fā)生什么樣的錯誤?什么樣的數(shù)據(jù)會引發(fā)這些錯誤? 是否可以處理預(yù)見的錯誤? 生產(chǎn)環(huán)境中可能發(fā)生這樣的錯誤嗎?或者只是在開發(fā)中會遇到這樣的錯誤? 如果提供默認值,是否可以在后續(xù)代碼中正確地使用? 很多糾正這個問題的方式都是處理可以解決的錯誤,并拋出無法解決的錯誤。驗證外部 API 返回的數(shù)據(jù)是否符合期望的結(jié)構(gòu)固然沒錯,但是如果不驗證,你的應(yīng)用還能不能正常運行?應(yīng)該依靠錯誤處理,并向用戶做出恰當(dāng)?shù)捻憫?yīng),同時將錯誤信息寫入日志,通知開發(fā)人員錯誤的發(fā)生。 掌握外部工具可以提供什么輸出是編寫值得信賴的代碼中很重要的一部分。在大多數(shù)情況下文檔會清晰地交代,但是有的時候僅僅是隱含。后臺 API 的數(shù)據(jù)格式由后臺代碼的作者決定。如果你是全棧開發(fā),那么很好,你可以同時控制前端和后臺,而且你可以相信自己(對吧?)。如果后臺的 API 由不同的團隊負責(zé),那么你需要定義正確的行為,并且互相支持。第三方的 API 更加難以讓人信賴,但是你也很少能夠影響到它的返回值。 在寫 React 組件的時候,你有更加強大的工具:PropTypes。你可以省略一系列的檢查,比如 a && a.b && a.b.c && typeof a.b.c === ''function'' && a.b.c(),只需加入一個靜態(tài)屬性的類型定義,如下所示: Thing.propTypes = { 這段代碼看上去不太好看,但是這個組件可以在開發(fā)期間記錄錯誤的數(shù)據(jù)。遺漏的數(shù)據(jù)可能往后拋它自己的錯誤,那么你覺得下列哪個信息更加有幫助? Warning: Failed prop type: The prop ''a'' is marked as required in ''Thing'', but its value is ''undefined''. 還是, Uncaught TypeError: Cannot read property ''b'' of undefined 外部數(shù)據(jù)可能變化 當(dāng)然,有時你并不能確認手中掌握的數(shù)據(jù)。數(shù)據(jù)中可能包含 a, b, c 或 x, y, z 等鍵,又或者 data 可能為 null。這種情況下最好還是檢查一下,但是可以考慮把檢查寫成函數(shù): const hasDataLoaded = data => typeof data !== 'undefined'; 命名良好的函數(shù)可以向后來的同事展示為什么加入了這些檢查。個別非常好的命名可以幫助他們在將來進行更精準(zhǔn)地檢查。 過分注重安全的無效代碼,甚至是考慮周全的檢查,也只是在防范數(shù)據(jù)類型的錯誤而已。PropTypes 可以很容易地添加到已有的 React 代碼中,但這并不是唯一的可選方案??梢杂?TypeScript 和 Flow 等更高級的工具來驗證數(shù)據(jù)的類型。PropTypes 可以在運行時拯救你的代碼,但 TypeScript 或 Flow 則可以在開發(fā)的時候驗證代碼。類型可以在代碼中硬性規(guī)定:如果你不正確使用數(shù)據(jù),那么甚至無法通過編譯! 也許有些人并不喜歡類型,但是對我來說,類型的應(yīng)用很廣泛、又非常復(fù)雜、是代碼中很難改變的部分。而對于剩下的代碼,至少在 React 中,PropTypes 可以幫助你迅速定位錯誤,并讓你對自己的代碼更自信。 當(dāng)一個開發(fā)人員編寫“為了保險起見”的代碼的時候,這就意味著他們可能對某些東西不夠了解。如果無視這種情況,那么小問題就會日益積累,最終發(fā)展成大問題。在做改變的時候,必須掌握你想要什么樣的錯誤信息,如何防御那些不需要的錯誤,并對自己的代碼建立信心。 原文:https:///@cvitullo/overly-defensive-programming-e7a1b3d234c2 |
|
|
來自: flyk0tcfb46p9f > 《AI》