談談Bug引起的複雜性“Bug-O” — Overreacted
在編寫對效能敏感的程式碼時,最好記住它的演算法複雜性。它通常用Big-O表示法表示。
Big-O衡量程式碼在向其投入更多資料時會變慢多少。例如,如果排序演算法具有O(n 2)複雜度,則排序×50倍以上的專案將大約為50 2 =慢2,500倍。Big O不會給你一個確切的數字,但它可以幫助你理解演算法如何擴充套件。
一些例子:O(n),O(n log n),O(n 2),O(n!)。
但是,這篇文章與演算法或效能無關。它是關於API和除錯的。事實證明,API設計涉及非常類似的考慮因素。
我們的大部分時間都用於查詢和修復程式碼中的錯誤。大多數開發人員希望更快地發現錯誤。儘管最終可能令人滿意,但是如果您已經實施了路線圖中的某些內容,那麼花費一整天時間來追逐單個錯誤是很糟糕的。
除錯經驗會影響我們對抽象,庫和工具的選擇。一些API和語言設計使得錯誤變得不可能。有些則會產生無窮無盡錯誤,如何分辨?
我有一個指標可以幫助我思考這個問題。我把它稱為Bug-O表示法。
Big-O描述了隨著輸入增長,演算法減慢了多少。Bug-O描述了隨著你的程式碼的增長,API讓你減緩多少。
例如,考慮一下這個程式碼,它會隨著時間的推移使用node.appendChild(),node.removeChild()手動更新DOM,且這兩個函式沒有明確的結構:
function trySubmit() { // Section 1 let spinner = createSpinner(); formStatus.appendChild(spinner); submitForm().then(() => { // Section 2 formStatus.removeChild(spinner); let successMessage = createSuccessMessage(); formStatus.appendChild(successMessage); }).catch(error => { // Section 3 formStatus.removeChild(spinner); let errorMessage = createErrorMessage(error); let retryButton = createRetryButton(); formStatus.appendChild(errorMessage); formStatus.appendChild(retryButton) retryButton.addEventListener('click', function() { // Section 4 formStatus.removeChild(errorMessage); formStatus.removeChild(retryButton); trySubmit(); }); }) } |
這段程式碼的問題並不在於它“醜陋”。我們不是在談論美學。問題是,如果此程式碼中存在錯誤,我不知道從哪裡開始查詢。
根據回撥和事件觸發的順序,該程式可能採用的程式碼路徑數量會出現組合爆炸。在其中一些中,我會看到正確的資訊。在其他情況下,我會看到多個微調器,故障和錯誤訊息,並可能崩潰。
此功能有4個不同的部分,不保證其順序。我的非科學計算告訴我,他們可以執行4×3×2×1 = 24個不同的順序。如果我再新增四個程式碼段,它將是8×7×6×5×4×3×2×1 - 四萬組合。祝你好運除錯。
換句話說,這種方法的Bug-O是BUg(n!),其中n是觸及DOM的程式碼段的數量。是的,這是一個因素。當然,我在這裡並不是很科學。在實踐中並非所有轉換都是可能的。但另一方面,這些細分中的每一個都可以執行多次。
為了改進此程式碼的Bug-O,我們可以限制可能的狀態和結果的數量。我們不需要任何庫來執行此操作。這只是在我們的程式碼上強制執行某些結構的問題。這是我們可以做到的一種方式:
let currentState = { step: 'initial', // 'initial' | 'pending' | 'success' | 'error' }; function trySubmit() { if (currentState.step === 'pending') { // Don't allow to submit twice return; } setState({ step: 'pending' }); submitForm.then(() => { setState({ step: 'success' }); }).catch(error => { setState({ step: 'error', error }); }); } function setState(nextState) { // Clear all existing children formStatus.innerHTML = ''; currentState = nextState; switch (nextState.step) { case 'initial': break; case 'pending': formStatus.appendChild(spinner); break; case 'success': let successMessage = createSuccessMessage(); formStatus.appendChild(successMessage); break; case 'error': let errorMessage = createErrorMessage(nextState.error); let retryButton = createRetryButton(); formStatus.appendChild(errorMessage); formStatus.appendChild(retryButton); retryButton.addEventListener('click', trySubmit); break; } } |
此程式碼可能看起來不太相似。它甚至有點冗長。但由於這個思路,除錯起來非常簡單:
function setState(nextState) { // Clear all existing children formStatus.innerHTML = ''; // ... the code adding stuff to formStatus ... |
透過在執行任何操作之前清除表單狀態,我們確保我們的DOM操作始終從頭開始。這就是我們如何對抗不可避免的熵 - 不要讓錯誤累積起來。這是相當於“關閉再開啟”的編碼,它的效果非常好。
如果在輸出中出現錯誤,我們只需要考慮一個退一步-以前的setState電話。除錯渲染結果的Bug-O是Bug(n),其中n是渲染程式碼路徑的數量。這裡只有四個(因為我們在a中有四個案例switch)。
我們在設定狀態時可能仍然存在競爭條件,但除錯它們更容易,因為可以記錄和檢查每個中間狀態。我們還可以明確禁止任何不需要的轉換:
當然,總是重置DOM需要權衡。每次都過分刪除和重新建立DOM會破壞其內部狀態,失去焦點,並在較大的應用程式中導致可怕的效能問題。
這就是像React這樣的庫包可以提供幫助的原因。它們讓您在總是從頭開始重新建立UI的範例中思考,而不必這樣做:
function FormStatus() { let [state, setState] = useState({ step: 'initial' }); function handleSubmit(e) { e.preventDefault(); if (state.step === 'pending') { // Don't allow to submit twice return; } setState({ step: 'pending' }); submitForm.then(() => { setState({ step: 'success' }); }).catch(error => { setState({ step: 'error', error }); }); } let content; switch (state.step) { case 'pending': content = <Spinner />; break; case 'success': content = <SuccessMessage />; break; case 'error': content = ( <> <ErrorMessage error={state.error} /> <RetryButton onClick={handleSubmit} /> </> ); break; } return ( <form onSubmit={handleSubmit}> {content} </form> ); } |
程式碼可能看起來不同,但原理是相同的。元件抽象強制執行邊界,以便您知道頁面上沒有其他程式碼可以混淆其DOM或狀態。元件化有助於減少Bug-O。
相關文章
- 淺談時間複雜度時間複雜度
- 複雜性系統設計:福特CEO談特斯拉的三個特點
- 從物理讀突增的複雜分析路徑談起
- node雜談
- 雜談20190505
- 2024.9.19雜談
- 退役雜談
- CodeReview雜談View
- token 的生成雜談
- Redux複雜應用(一):淺談狀態管理Redux
- 萬智牌設計雜談:重複利用(上)
- 萬智牌設計雜談:重複利用(下)
- kubernetes雜談之(二)Pod初談
- 談談BUG嚴重級別(severity)管理
- 談談Python中物件複製Python物件
- 【雜談】策略模式模式
- 【雜談】Starter Template
- 數學雜談 #??
- 雜談其一
- 免殺雜談
- 正則雜談
- 監控雜談
- 一些雜感雜想(一)談談加班、團隊
- 談談ThreadStatic屬性用法thread
- 【譯】談談“typeof null為object”這一bug的由來NullObject
- 【雜談】對CopyOnWriteArrayList的認識
- 雜談WebApiClient的效能優化WebAPIclient優化
- 近期的爬蟲工作雜談爬蟲
- 前端雜談:DOMevent原理前端
- 專案交接雜談
- 前端隨筆(雜談)前端
- 架構雜談《九》架構
- 架構雜談《八》架構
- 架構雜談《七》架構
- 架構雜談《六》架構
- 架構雜談《五》架構
- iOS APP安全雜談iOSAPP
- 資料分析雜談