算法复杂性“Bug-O”表示法 — Overreacted

栏目: JavaScript · 发布时间: 5年前

内容简介:在编写对性能敏感的代码时,最好记住它的算法复杂性。它通常用Big-O衡量代码在向其投入更多数据时会变慢多少。例如,如果排序算法具有O(一些例子:O(

在编写对性能敏感的代码时,最好记住它的算法复杂性。它通常用 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() {
  <font><i>// Section 1</i></font><font>
  let spinner = createSpinner();
  formStatus.appendChild(spinner);
  submitForm().then(() => {
      </font><font><i>// Section 2</i></font><font>
    formStatus.removeChild(spinner);
    let successMessage = createSuccessMessage();
    formStatus.appendChild(successMessage);
  }).<b>catch</b>(error => {
      </font><font><i>// Section 3</i></font><font>
    formStatus.removeChild(spinner);
    let errorMessage = createErrorMessage(error);
    let retryButton = createRetryButton();
    formStatus.appendChild(errorMessage);
    formStatus.appendChild(retryButton)
    retryButton.addEventListener('click', function() {
      </font><font><i>// Section 4</i></font><font>
      formStatus.removeChild(errorMessage);
      formStatus.removeChild(retryButton);
      trySubmit();
    });
  })
}
</font>

这段代码的问题并不在于它“丑陋”。我们不是在谈论美学。问题是,如果此代码中存在错误,我不知道从哪里开始查找。

根据回调和事件触发的顺序,该程序可能采用的代码路径数量会出现组合爆炸。在其中一些中,我会看到正确的信息。在其他情况下,我会看到多个微调器,故障和错误消息,并可能崩溃。

此功能有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', <font><i>// 'initial' | 'pending' | 'success' | 'error'</i></font><font>
};

function trySubmit() {
  <b>if</b> (currentState.step === 'pending') {
    </font><font><i>// Don't allow to submit twice</i></font><font>
    <b>return</b>;
  }
  setState({ step: 'pending' });
  submitForm.then(() => {
    setState({ step: 'success' });
  }).<b>catch</b>(error => {
    setState({ step: 'error', error });
  });
}

function setState(nextState) {
  </font><font><i>// Clear all existing children</i></font><font>
  formStatus.innerHTML = '';

  currentState = nextState;
  <b>switch</b> (nextState.step) {
    <b>case</b> 'initial':
      <b>break</b>;
    <b>case</b> 'pending':
      formStatus.appendChild(spinner);
      <b>break</b>;
    <b>case</b> 'success':
      let successMessage = createSuccessMessage();
      formStatus.appendChild(successMessage);
      <b>break</b>;
    <b>case</b> 'error':
      let errorMessage = createErrorMessage(nextState.error);
      let retryButton = createRetryButton();
      formStatus.appendChild(errorMessage);
      formStatus.appendChild(retryButton);
      retryButton.addEventListener('click', trySubmit);
      <b>break</b>;
  }
}
</font>

此代码可能看起来不太相似。它甚至有点冗长。但由于这个思路,调试起来非常简单:

function setState(nextState) {
  <font><i>// Clear all existing children</i></font><font>
  formStatus.innerHTML = '';

  </font><font><i>// ... the code adding stuff to formStatus ...</i></font><font>
</font>

通过在执行任何操作之前清除表单状态,我们确保我们的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();
    <b>if</b> (state.step === 'pending') {
      <font><i>// Don't allow to submit twice</i></font><font>
      <b>return</b>;
    }
    setState({ step: 'pending' });
    submitForm.then(() => {
      setState({ step: 'success' });
    }).<b>catch</b>(error => {
      setState({ step: 'error', error });
    });
  }

  let content;
  <b>switch</b> (state.step) {
    <b>case</b> 'pending':
      content = <Spinner />;
      <b>break</b>;
    <b>case</b> 'success':
      content = <SuccessMessage />;
      <b>break</b>;
    <b>case</b> 'error':
      content = (
        <>
          <ErrorMessage error={state.error} />
          <RetryButton onClick={handleSubmit} />
        </>
      );
      <b>break</b>;
  }

  <b>return</b> (
    <form onSubmit={handleSubmit}>
      {content}
    </form>
  );
}
</font>

代码可能看起来不同,但原理是相同的。组件抽象强制执行边界,以便您知道页面上没有其他代码可以混淆其DOM或状态。组件化有助于减少Bug-O。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

无界

无界

(美)艾米莉·内格尔·格林(Emily Nagle Green) / 卞斌 / 机械工业出版社 / 2011-5 / 39.00元

"数十亿人身在其中、数十万亿美元的新生意,你我此生最大的科技革命,这次转型将如何改变我们的生活? 又如何使我们做生意的方式起革命性的变化? 无界会比你所想更快降临,将创造数兆美元的新价值。你的行动够快吗?这本放眼未来的著作,结合专家的洞见、战术性工具,以及扬基集团独有的无界趋势数据,提供你需要的一切。" 未来的世界和企业,会走向无界的状态,也就是人、构想和产品经由一张全球性的数字......一起来看看 《无界》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具