dan_abrarmov的react内核开发五年回顾

196 阅读10分钟

dan_abramov是react的核心开发者。看到ricky的一年回顾,他觉得这个主题很不错。于是他决定也写一个帖子来分享近五年开发react中遇到的有趣事情。

五年故事回顾

刚开始的时候,我们编写了自己的Scheduler。当然最终这样的Scheduler,浏览器应该会原生支持的。2021年我们已经开始用浏览器原生提供的Scheduler!
chromestatus.com/feature/603…

2019年当我们对facebook网站进行重构的时候,我们慢慢认识到最初关于Suspend的想法是毫无意义的。一个简单的改动能让UI卡顿5秒钟,工程师还找不到原因,并且我们也一筹莫展。我们被自己开发的React API搞的晕头转向。

\

为了搞明白这些问题,我与大约10位工程师进行了沟通,并记录了他们所知道的每一个问题。这些是我做的笔记。我对这些问题进行了分类,并解释了为什么用我们api(有缺陷的)修复一个问题又会导致另一个问题的出现。(“Blue”是旧网站,“Comet”是新网站)

\

根据我们收集到的信息,sebmarkbag走到白板前。通过测试所有的产品用例和与团队外同事的交谈(特别感谢@_jstejada,@catchingash,@frankyan,@en_JS,@jingc,@steveluscher)他重新设计了API。

2020年,我们遇到了一系列不同的问题。重新设计的Suspend相关的api组合使用得很好,但实现有很多bug。为了给你一个概念,看下图。当一个工程师试图添加数据重取,闪屏bug就出现了。我们慢慢地修复了每个bug。

这些bug很难浮现,更别提找出原因了。我最喜欢的策略是删除99%的组件树,直到有bug的部分被隔离确认出来。然后我可以看到导致问题的确切原因,并尝试在沙盒中重新浮现它。有些bug花了一周的时间。

当我隔离出一个bug时,我会尝试编写一个失败测试或制作一个沙盒。@acdlite或@sebmarkbage会来修复它们。我们为每一个bug添加了一个回归测试——这样它就不会再出现了。这是一条规则:除非我们知道问题在哪里,否则我们不会去修复它。

随着时间的推移,我们发现很多bug都有共同的原因。我们代码的某些部分过于“聪明”和脆弱。在某些地方,整个模式是有缺陷的,修复一处代码会破坏另一处代码。认识到这种情况后,@acdlite,@lunaruan,@brian_d_vaugh做了两次大规模的重构。

重构后,这些错误就……消失了。我们最近已经几个月没有遇到什么大bug了。因为进行大规模重构影响范围太大了,甚至可能导致整个代码不可用,所以我们保留了大多数文件的两个版本(.new,.old文件),并以AB测试的形式发布。

其中一个重构是有风险的,因为我们用一个更朴素(naïve)的机制取代了一个“聪明”快速机制。这修正了bug,但AB测试暴露了显著的perf回归(性能下降了)。我们不能发布这个代码,因为我们不知道原因。我们不能因为某些重构而降低所有React应用的速度。

我们尝试了许多策略、做了许多优化、也做了很多实验,但是依然缓慢。我们找不到原因,我们希望这是一个bug,而不是一个致命的性能缺陷。因此,我们撤销了整个重构,然后将其拆分为小的独立提交。

回想起来,我们一开始就该这么做。重新做小的原子更改和AB分别测试每个更改(至少一周)似乎很辛苦,但最终我们发现了错误提交。在一个小小的更改中,团队找到了这个问题,修复工作成功了。

通过在产品代码中大量使用这个重构,我们已经了解并解决了Suspend API 相关的最后一些重要问题。在2021年初之后,我们开始在项目里面增量使用这个API了。

我知道人们对facebook把某些“抢鲜体验”发布为新特性情绪复杂。但我100%相信,把我们在2018年、2019年或2020年的一些功能和设计发布出去将是一场灾难。开源版本的门槛很高。为了稳定性,我们仍然支持从2014年开始的设计错误。

我曾很多次感觉到茫茫的黑暗。我觉得我们过早地分享了我们的愿景,让社区失望了。现在不得不专注于为之前的错误去救火,但人们认为我们忘记或不在乎。@sebmarkbage多年前告诉我的一句话,一直鼓舞着我,让我能坚持下去。

我们的工作是基于理论的。理论上,有些方法更好,所以我们追求它。许多人不相信这个理论。但是我们不能放弃。尽管这可能需要许多实践妥协和务实牺牲才能实现。如果我们继续,最终理论会证明他的胜利。

我想澄清一件事。在FB提前部署新功能是我们证明一项功能有效和可扩展的一种手段。但我们拒绝了许多facebook工程师想要的功能。我们被我们的愿景所驱动(基于我们共同的UI编程痛点的领悟)。facebook发布的代码都经过了压力测试。

我知道这很难相信。但举个例子,我们在facebook开始创建新网站之前就开始了Concurrency模式的工作。事实上,我们被告知很多次,React重写是不可能发生的。Concurrency的工作源于一个2014年的理论原型——因为它有意义。

早些时候,许多facebook工程师对以我们的研究为基础建立新网站持极其怀疑的态度。(在某种程度上,他们是对的——还远没有准备好!但也有很多人不相信这种模式会奏效或带来好处)。如果我们没有远见,我们早就放弃了。

我们的团队很幸运,能够从我们的同事那里得到足够的信任,我们可以把我们的疯狂的想法在充满压力的网站重写中实践。这很难,但我们都得到了我们想要的。FB获得了良好的性能,而我们也获得了被证明有效的API和模型。

现在,我们已经将工作的重点完全转移到社区推广上。这就是React 18 Alpha和Working Group的意义所在。从研究到生产再到开源。我们认为这些特性将会使许多React应用受益,我们很兴奋地回复:我们可以在上面构建什么。

我想在未来,我们可以让吃自己的狗粮过程不那么以fb为中心。这很难,因为修复早期问题需要完全访问调试环境。但我可以想象,团队将会扩大,可以让非facebook工程师全职工作,测试他们的应用。

我们已经与Next和Gatsby等大型框架建立了非正式的合作关系,在早期阶段添加了非facebook内容。React 18工作组是第一次尝试将其正式化。如果这一努力是成功的,我们可以考虑进一步扩大它。

当“可怕的”bug出现时,这表明模型存在缺陷。解决方案不是一个一个地修正它们(尽管这很有帮助),而是修正整个模型。一旦我们修复了模型,一系列类似的bug就消失了。这给了我们准备Alpha版本的信心。

我分享这些是因为我认为诚实对待这个过程很重要。有些人会认为React有问题。但这就是工程的样子。我们犯错误,我们改正错误,我们从错误中学习,我们重新设计,我们验证,然后我们发布。

关闭的想法。是的,React内部很复杂。你知道还有什么是复杂的吗?node . js是复杂的。Postgres是复杂的。我不在乎,因为它们让我的代码更简单。你知道什么内部不复杂吗?Redux简单!尽管redux做的很好,它的内部简单性并不能帮助应用程序代码。

我听起来像是在挖苦Redux。对不起,我应该说得更清楚些。我所比较的是如何解决复杂度。Redux不能吸收复杂性。这并不能决定它是好是坏——它从来没有想过要吸收复杂性。

我拒绝的格言是:“为了让我们的应用简单,我们需要拒绝复杂的工具。”这对我来说没有意义。即使是在Redux中,我们的目的也是让它之上的元分层(metaLayer)能够吸收复杂性。

这类似于Promise不吸收复杂性的原因。Promise是一个有用的模式吗?绝对的!他们会为你编写异步逻辑吗?不,他们只是让你方便的写你的异步逻辑,异步逻辑的复杂性依然存在。

回顾感悟

在dan_abramov分享的最近五年react迭代中遇到的重要问题中,我发现有如下问题。

  1. react这样世界级的框架也充满了设计错误,并且不得不反复迭代,老的错误还需要一直支持
  2. 发布开源框架,必须要稳妥的进行大量AB测试,并且自己提前吃狗粮
  3. 设计一个框架的时候,一定要坚守自己的理论,最后会证明理论的正确性
  4. 最小化系统是排查bug的关键手段,失败测试和用例保证了这些bug不再出现
  5. 当遇到bug的时候,很可能说明系统设计存在问题,进行重新设计,这些bug就自动消失了。

针对问题1,react项目的复杂度会越来越高、代码越来越臃肿,看看new.js,old.js并存就知道了。

针对问题2,自己写的框架,让别人吃狗粮,很容易被人骂成狗。

针对问题3,坚持理论的时候,一定要机灵,否则别人可能会说”这个死脑筋“

针对问题5,想法很好,可不要轻易尝试,你这样重构来重构去,325很可能就和你结缘了。曾经有个哥们嫌弃代码bug多,重构了一番,发布上线后,被通报批评了。所以重构一定要分清场合。

在dan_abramov的分享中,我们确实可以身临其境地感受到react的开发过程,明白react的过去和现状,相信和顶级程序员有这样的神交对我们日常工作还是很有裨益的。

看了dan_abramov的分享,你有什么看法呢?欢迎评论!