當你編寫對性能要求高的代碼時,考慮算法複雜度是個好辦法,用Big-O 符號 表示。node
Big-O 用來衡量 投入更多數據時代碼會慢多少。例如,若是有個排序算法的複雜度是 O(n2),排序50倍以上的數據大概要慢 502 = 2,500 時間。Big O 不會給出一個準確的數值,但它能夠幫助你知道算法 效果 如何。react
一些例子:O(n), O(n log n), O(n2), O(n!)。算法
然而,這篇文章與算法或性能無關,與APIs和調試有關。 事實證實,API設計涉及到十分類似的考慮事項。app
咱們大部分時間都用於查找和修復代碼中的錯誤,大部分開發者但願能夠更快的找到bugs。儘管最後的結果可能讓人滿意,但當你已經制定好工做流程時,花費一成天時間來找一個bug是很糟糕的。ide
調試經驗會影響咱們對抽象、類庫和工具的選擇。一些 API 和語言設計能夠杜絕某類錯誤,一些則會引起無數個錯誤,但是咱們怎麼知道要選擇哪一個呢?工具
許多APIs的線上討論主要是關於美學上的,但其中沒有太多提到實際使用後的感覺。組件化
我有一個指標能夠幫助我思考這個問題,我稱它爲 Bug-O符號:post
🐞(n)性能
Big-O 描繪的是算法隨着輸入增加會變慢多少,Bug-O 描繪的是隨着代碼增加會變慢多少。ui
例如,請思考下面代碼,隨着時間流逝,使用 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();
});
})
}
複製代碼
代碼的問題不在於它 「醜」,咱們不討論美學,問題在於若是在代碼中存在一個bug,我不知道要從哪裏開始找。
順序由回調和事件觸發決定,這個程序的代碼路徑數量能夠引起組合爆炸。可能最後我會看到正確的提示,也可能我會看到多個 spinners、失敗和錯誤提示同時出現或者代碼崩潰。
這個方法有4個部分且沒法保證它們的執行順序,我用很是不科學的方法計算,結果告訴我會有 4×3×2×1 = 24 種執行順序。若是我添加更多代碼塊,就多是 8×7×6×5×4×3×2×1 —— 四萬 種組合,祝你調試順利。
就是說,這示例中,Bug-O 爲🐞(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始終從頭開始。這就是咱們解決不可避免的熵 —— 不 要讓錯誤累積起來。這就至關於 「關閉再打開」 的代碼,效果很是好。
若是輸出存在bug,咱們只須要回退 一 步 —— 前一次 setState
調用。這種代碼調試的 Bug-O 複雜度爲 🐞(n),n 是render分支的數量,在這是4(由於 switch
的分支是4)。
在狀態 賦值 中,咱們可能還須要一些條件判斷,但調試起來仍是挺容易的,由於能夠記錄和檢查每一個行進中的狀態值,咱們也能夠避免任何不想要的顯示轉換:
function trySubmit() {
if (currentState.step === 'pending') {
// Don't allow to submit twice
return;
}
複製代碼
固然,老是重置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或state。組件化有助於減少Bug-O。
實際上,若是 React App 中 任何 值看起來有問題,你能夠經過在React樹逐個查看組件上的代碼跟蹤它的來源。無論 app 有多大,跟蹤的值都等於 Bug-O🐞(樹高)。
你下次看見API討論時,請考慮:常見的調試任務的 🐞(n) 是多少?你當前熟悉的APIs和原則怎麼樣?Redux、CSS、繼承 —— 它們都有本身的 Bug-O。
翻譯原文The 「Bug-O」 Notation(2019-01-25)