“Bug-O” 符号

当你编写对性能要求高的代码时,考虑算法复杂度是个好办法,用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)

相关文章
相关标签/搜索