“Bug-O” 符号
栏目: JavaScript · 发布时间: 5年前
内容简介:当你编写对性能要求高的代码时,考虑算法复杂度是个好办法,用Big-O 符号 表示。Big-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! )。
然而, 这篇文章与算法或性能无关,与APIs和调试有关 。 事实证明,API设计涉及到十分相似的考虑事项。
我们大部分时间都用于查找和修复代码中的错误,大部分开发者希望可以更快的找到bugs。尽管最后的结果可能让人满意,但当你已经制定好工作流程时,花费一整天时间来找一个bug是很糟糕的。
调试经验会影响我们对抽象、类库和 工具 的选择。一些 API 和语言设计可以杜绝某类错误,一些则会引发无数个错误, 可是我们怎么知道要选择哪个呢 ?
许多APIs的线上讨论主要是关于美学上的,但其中没有太多提到实际使用后的感受。
我有一个指标可以帮助我思考这个问题,我称它为 Bug-O符号:
:beetle:( n )
Big-O 描绘的是算法随着输入增长会变慢多少, Bug-O 描绘的是随着代码增长会变慢多少。
例如,请思考下面代码,随着时间流逝,使用 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 为:beetle:( n! ) ,这里 n 表示代码中涉及DOM的代码块数量,这是个 阶层 。当然,这不是很科学的计算。在实际中,不可能所有的部分都可以转换,但另一方面,每一段都可以被重复使用,这样 :beetle:( ¯\ (ツ) /¯ ) 也许能更恰当些,但仍然很差劲,我们可以做得更好。
为了改善这代码的 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 复杂度为 :beetle:( 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:beetle:( 树高 )。
你下次看见API讨论时,请考虑:常见的调试任务的 :beetle:( n ) 是多少 ?你当前熟悉的APIs和原则怎么样?Redux、CSS、继承 —— 它们都有自己的 Bug-O。
翻译原文The “Bug-O” Notation(2019-01-25)
以上所述就是小编给大家介绍的《“Bug-O” 符号》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- macos – dyld:惰性符号绑定失败:未找到符号:_PQsetErrorContextVisibility
- 嵌入式C语言自我修养 09:链接过程中的强符号和弱符号
- Scala中的符号
- 简单理解符号执行技术
- GCC 符号表小结
- Golang 格式输出符号
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。