扩展前端应用开发的原则

代码共享和单一代码库

如果你想获得一剂肾上腺素的刺激,那就进行单一代码库(monorepo)迁移。这绝对是值得的。它鼓励协作并降低了协作的门槛。GitHub 的代码所有者(code owners)解决了可扩展性问题。作为我们 Spaces 产品的一部分,我们正在推出一个名为 "owners" 的东西,它基本上就像代码所有者(code owners),但更加优秀。

因此,人们会在他们的公司通过将模块发布到 NPM 来共享代码。所以我加入了 Vercel,我看到这种情况发生,比如某个团队会说,嘿,你有一些很酷的代码,你能发布到 NPM 上,这样我就可以使用它吗?然后我必须更新我的 package JSON 到你的新版本。也许有人在帮助我,也许我正在自动执行这个过程。这是一个非常糟糕的过程,如果你有一个较大的团队,规模扩大后,这真的会造成困扰。所以我有一个有点奇怪的建议,如果你还没有尝试过,但想要获得一剂肾上腺素注射,那就迁移到一个单一代码库(monorepo)吧。这真的很值得。当然,显然它也有权衡之处。在软件工程中的一切都伴随着权衡。几个月后,你会注意到,哦,每个人都可以更改任何东西,也许这不是地球上最好的想法。但拥有一个鼓励协作并将协作的门槛降低的基础是有好处的,你可以处理在路上遇到的问题。例如,GitHub 的代码所有者(code owners)解决了某些可扩展性问题。不过,奇怪的是,在 GitHub 的其他很棒的产品中,代码所有者似乎不是特别出色。因此,作为我们 Spaces 产品的一部分,我们实际上正在推出一种称为 owners 的东西,它基本上就像代码所有者,但更好。所以去看一下吧。

简化代码删除和数据获取

使删除代码变得容易是在管理大型代码库中至关重要的。通过鼓励工程师尽量减少代码,包括删除不必要的代码,我们可以维护一个更高效和可扩展的应用程序。使用像 Tailwind 或 CSS-in-JS 库这样的工具可以轻松删除代码,因为 CSS 与代码共存。同样,组件内部的数据获取,例如在 Next.js 13 中,减少了不必要的代码,并提高了性能。尽管存在一些怀疑,但这种方法已经成功地应用于较大的 Google 应用程序,证明了其可扩展性和有效性。

好的,对于第二个原则,我称之为使删除代码变得容易。因为关于任何大型代码库的一个真实情况是,它会变得越来越大。在某种程度上,这是好事,对吧,就像写代码是我们的工作,我们会写代码,你无法阻止它。但我们可以再次作为平台领导者,专业地管理和鼓励我们的工程师尽可能少地添加代码,包括删除代码。

现在,如果我们都考虑一下我们的代码库,里面可能有一些东西,对吧?有一些明显不应该在那里的代码,你知道,2017 年之类的。也许最初它是一个 React 组件,我们摆脱了那个 React 组件,对吧?我们有一个 CSS 文件,也为我们的 2017 新年消息做出了贡献。谁知道那个选择器是否可以删除,对吧?这真的几乎是一个停机问题类型的问题,所以它一直存在。另一方面,如果你使用像 Tailwind 或 CSS in JS 库这样的东西,因为你的 CSS 与代码共存,你可以有很高的信心直接删除整个东西,摆脱它。再次强调,作为领导者,你应该有这种思考方式,说这就是为什么我选择使用像 Tailwind 这样的东西,因为它实际上使删除代码变得更容易。作为这种思考方式的另一个很好的例子,我认为是 Next.js 13 中组件内的数据获取。在这个例子中,我们有一个 Tweet 组件,对吧?我们只传递了 ID,Tweet 组件自己获取自己的数据。现在,如果我们删除了 Tweet 组件,一切都消失了。还记得这在类似 Next.js 的旧版本或者 remix loaders 中是如何工作的吗?数据获取被提升到路由的顶部。因此,如果我删除了 React 渲染树中很远的某些内容,我必须记住也删除数据获取代码。如果你有一个庞大的团队,有些人会有时候忘记,这不仅会使你的应用程序变得更大,并且有不必要的东西,还会使它变得相当慢。因此,对于数据获取,你可以引入成本,因为你调用了某个后端系统,实际上你不必调用。所以,再次强调,这与 CSS in JS 的思想相同,只不过这是针对 JS 中的数据获取。我知道有些人会说,哇,这不可行,等等。我认为在谷歌的一个我最自豪的成就之一就是引入了这种方法。因此,在过去的八年左右中,你使用过的任何较大的谷歌应用程序都在使用这种技术,其中数据获取是在渲染树中进行的,而不是以某种提升的伪可扩展方式进行的。

渐进式迁移和应用标准

始终进行渐进式迁移。从一开始就计划逐步迁移。以 Next.js 13 的应用路由器为例。一次迁移一个路由。向 Next.js 团队致以嘉奖。第四个原则:始终进步,永不倒退。引入 Lint 规则以确保应用程序标准。注释可以作为技术债务的记录。

好的,第三个原则。始终采用渐进迁移。前几天我在 Twitter 上开玩笑说,基本上只有两种迁移方式。有渐进迁移,还有失败的迁移。有时你开始有一个大计划,你会说,我们一次全部搞定。但我认为更常见的情况是,也许你已经进行了三个月,你的老板走过来说,这一切都很好,但我们不能再等待六个月来证明这将有效,我下周或下个月需要发布一些东西,对吧?因此,可能最初是一个潜在的大爆炸式迁移最终通常会变成渐进迁移,所以最好从一开始就计划好。这只是一个示例,说明了用于 API 设计的类型思维,以使渐进迁移成为可能。我认为另一个很好的例子再次是 Next.js 13 的应用路由器,你可以逐个迁移单个路由,将其移入新的世界,对吧?没有人强迫你选择旧 API 或新 API,你可以将该页面放在这个 API 上,将该页面放在那个 API 上,并通过代码库自行解决。甚至在这个项目的更深层次上,你知道,这里有一些 get service at props,传统的。我应该说传统吗?我不知道。不管怎样。旧 API,对吧?你完全可以继续使用。但是,这就是它的样子,然后你可以执行第一个迁移步骤。请注意,这是一个微不足道的迁移。对吧?几乎什么都没有改变。我们只是将 get service at props 中的代码放入了顶级组件中。现在,比以前好吗?不,不是。对吧?但它是一个渐进步骤,你不必重写整个渲染树来利用一些新的功能。再次强调,这只是一个示例,说明了如何创建可以按照逐步方式迁移的 API,而不是一次完成所有工作。当然,再次向 Next.js 团队致敬,我认为他们做得很好。酷。接下来是第四个原则。始终进步,永不倒退。从某种程度上来说,这实际上与渐进迁移的主题有点相关,但更一般一些。所以一个确保你的应用程序具有一定标准的好方法是引入 lint 规则,对吧?这些规则告诉你,在我的应用程序中,你必须按照这种方式做,而不是其他方式。但有时会发生一些不太好的事情,比如这样的情况,顺便说一下,我只是随机从 Vercel 的内部超级机密代码库中复制粘贴的,如果有什么有趣的东西在里面,我很抱歉。实际上有趣的部分是注释,这些注释基本上在各个地方禁用了 lint 规则,对吧?实际上我认为这是正确的方式,我会谈一谈为什么这是正确的方式,但显然在你的代码库中添加这些没有价值的注释很糟糕,对吧?但从某种程度上说,这些注释现在就是技术债务的分类账,对吧?技术债务通常是一个超级抽象的东西。

允许列表和接纳不足的知识

对于我们的 Spaces 产品,我们使用外部允许列表以避免在代码库中乱七八糟。规则在新代码上的价值更高。接纳不足的知识,并以机器可读的方式编码代码库信息。例如:Next.js 中间件和使用允许列表来确保上下文特定更改的批准。

好的,现在来谈谈第四个原则,始终进步,永不倒退。从某种程度上来说,这实际上与渐进迁移的主题有点相关,但更一般一些。所以一个确保你的应用程序具有一定标准的好方法是引入 lint 规则,对吧?这些规则告诉你,在我的应用程序中,你必须按照这种方式做,而不是其他方式。但有时会发生一些不太好的事情,比如这样的情况,顺便说一下,我只是随机从 Vercel 的内部超级机密代码库中复制粘贴的,如果有什么有趣的东西在里面,我很抱歉。实际上有趣的部分是注释,这些注释基本上在各个地方禁用了 lint 规则,对吧?实际上我认为这是正确的方式,我会谈一谈为什么这是正确的方式,但显然在你的代码库中添加这些没有价值的注释很糟糕,对吧?但从某种程度上说,这些注释现在就是技术债务的分类账,对吧?技术债务通常是一个超级抽象的东西。

但在这里你有一个列表。你想要修复的任务清单。再次强调,这对你的代码库来说有点不好,这就是为什么对于我们的 Spaces 产品,我们使用的是这些外部允许列表,不会在你的代码库中添加垃圾。你基本上有一个 JSON 文件,上面有一些规则,而在这个文件中,违反规则是可以的。

回到变得更好而不是变得更糟的主题,你真正想看到的是你团队的代码库是这样的图表。这些是随时间推移的违规情况。你希望这个图逐渐下降,然后你会看到这些上升的步骤。这实际上并不坏,这是当你引入新规则时发生的事情。你允许所有现有的违规情况,然后随着这段代码实际被触碰,它会随着时间的推移变得更好。我想鼓励每个人都允许这些东西。现在迁移一切并将其放入新状态并不重要。原因是,这个在你的应用程序上的规则的边际价值在新代码上比在旧代码上高得多,因为你的旧代码经过了战斗测试。它经历了 QA 测试,经历了生产故障,经历了用户对 "名称" 有创造性想法。你已经解决了所有这些问题,对吧?因此,你可能实际上不会找到任何有趣的东西。这些规则存在的原因是,当你编写新代码时,它会立刻告诉你,嘿,你犯了一个错误,去立即修复它,对吧?而且这样做也很便宜。这就是为什么将这些观点迅速纳入你的代码库以供未来代码使用非常重要,现在没有必要把生活中的一个月时间用于迁移你已经有的一切到新状态。

所以我在抽象层面上稍微谈到了这样的规则,给出了一个 lint 规则的示例,但实际上我想谈谈比 lint 规则稍微高级一点的东西,与代码风格等相关。回到原则,你知道,拥抱不了解。再次强调,你是这个更大团队的领导,团队中会有一些新成员,会有一些更年轻的人,他们可能会缺乏有关他们不了解的代码库的上下文,他们可能不知道或错过了,或者他们没有看备忘录,对吧?这都会发生。作为团队的领导者,我们必须接受他们可能没有那个上下文。所以这就是为什么将你对代码库的许多了解以某种机器可读的方式编码是很重要的原因。我这里有一个示例,说明了这是什么意思。而且这远远不止是关于 linting,还涉及到应用程序设计观点等更多内容。这个规则,再次强调,是从我们自己的内部代码库中提取的。所以我们使用 Next.js 中间件,这是一个非常强大的工具,对吧?它几乎在我们提供的所有请求上运行。这意味着如果我们的中间件很慢,我们的站点将会非常慢,对吧?你可以通过从某个其他服务获取一些内容来使你的中间件变得非常慢。另一方面,从某个其他服务获取一些内容可能正是你想要做的,对吧?因为这是你做有趣事情的方式,比如调用服务。现在,这意味着有时候你必须调用服务,但有时候这不是一个好主意,对吧?因此,通过这种允许列表机制,你可以说,默认情况下,进行这种更改需要得到具有在这个上下文中做出此决策的上下文的人的批准。这将允许的典型外部于代码之外的机制与我们的所有者机制相结合,其中允许列表的所有者不是编写代码的同一个人,因此你可以轻松强制执行,你的架构师或者你的平台团队的领导去看看并验证在这种情况下,是的,这是可以接受的用法。然后再次强调,这不是像 lint 规则那样最终可能需要有统一的代码库。在这里,你只需确保我们已经看过了,这确实是我们想要做的。

消除系统复杂性

消除系统复杂性在构建可扩展的应用程序中至关重要。分布式系统中常见的一个问题是版本不一致,即客户端和服务器不在同一个版本上。这在引入 API 中的新字段时可能会导致问题。为解决这个问题,像 Zot 库和 TRPC 层这样的工具提供了解决方案,但可能会很复杂。在 Vercel,我们引入了一个无服务器产品,可以提供站点的不同版本,确保客户端和服务器始终保持同步。这完全消除了问题,使大规模构建应用程序变得更容易。

非常好,来到最后一个原则,消除系统复杂性。再次强调,你是组织中领导平台团队的人,你看到人们一直在努力解决的问题。我认为不时地退后一步,列出人们一直在努力解决的问题的目录,并尝试通过引入某种抽象来一劳永逸地解决这个问题,让人们不再担心它,是很重要的。

我认为这种问题的一个很好的例子是,至少在大型标签中被称为版本不一致。版本不一致是在分布式系统中发生的一种情况,当你有一个客户端和一个服务器,它们不在同一个版本上,对吧?现在,你可能会说,你知道,我是一个前端工程师,我不写分布式系统,但你错了。实际上,你在做困难模式,对吧?因为在一个数据中心中,你可以控制部署方式,对吧?你知道它是如何工作的。你可以制定规则。另一方面,当你有一个 Web 客户端和一个服务器时,你知道,当 Web 客户端重新部署时,你无法控制它,对吧?所以,它访问你的站点,它可能会停留在那里,对吧?至少在一段时间内。与此同时,你重新部署了服务器。所以现在,你有了这个旧客户端、新服务器,你在 API 中引入了一个新字段,客户端一无所知,对吧?所以,这是一个你现在必须管理的问题。

现在,几乎有一个完整的生态系统围绕着这个问题。比如,TypeScript 的 Zot 库或者其上层的 TRPC 层,它允许你表达关于你的 API 的期望,并在不满足这些期望的情况下处理问题。但这是很复杂的,对吧?有很多事情你必须要做。如果你没有得到这个字段,你应该怎么做呢?你真的知道该怎么做吗?这是一个难题。所以,在 Vercel,我们看到了这个问题,我们在想,我们是否可以再次消除整个系统性的问题?所以我们做的是,我们说,嘿,我们有这个无服务器产品,对吧?我们不只有一个具有最新版本的服务器。我们实际上可以为你的站点提供比最新部署更旧的版本。所以,我们现在有一个非常实验性的版本,但在接下来的几周内我们将推出它,你可以选择在 Next.js 中启用一个模式,其中响应你的客户端的服务器将始终与客户端所在的版本完全相同,这意味着你可以完全忘记这个问题。你永远不必再担心它。特别是对于服务器操作来说,因为它们已经像函数调用一样了,现在它们真的像函数调用一样,因为你实际上知道你正在调用哪些函数,对吧?它不是某个抽象版本的某个名为这样的东西,但你不知道是哪一个。它将完全是那样的。所以,这只是一个示例,说明了你可以做的一些事情,使构建大规模应用程序更容易。

太棒了,这就是我今天要说的所有内容。这就是整个列表。我不必再次浏览它。我认为我们有问答环节,也许对问题和其他方面有所帮助。非常感谢。我们可能有时间回答一两个问题,所以让我们开始吧。

自动化代码迁移和消除障碍

使用 AST(抽象语法树)自动化代码迁移可以是有益的,即使是进行大规模的更改。Google 的机器人 Rosy 通过将一次性的 15,000 行更改转换为多个一行的更改,逐步应用以避免潜在的错误。这种方法消除了障碍,使代码迁移更加顺畅。

这是一个非常有趣的问题,我想进一步展开一下。问题是,你提到了始终进行渐进式迁移,但关于使用 AST(抽象语法树)自动迁移大量代码,你有什么看法?并谈一谈何时值得自动化?它是一个好处还是一个分散注意力的东西?不,自动化肯定是值得的。即使你自动化了一个更改,我仍然会应用这个原则。在 Google,有一个名叫 Rosy 的机器人,它可以将你的 15000 行更改变成 15000 个一行的更改,并管理以渐进的方式将其应用到你的代码库中,这样你就不必最终说,哎呀,我犯了一个错误,不得不回滚 15000 行的更改。它以递归方式应用更改,这真的很酷。消除障碍,Google。

相关推荐
10年前端老司机2 小时前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~2 小时前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客3 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2453 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇8 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖8 小时前
http的缓存问题
前端·javascript·http
小小小小宇8 小时前
请求竞态问题统一封装
前端
loriloy8 小时前
前端资源帖
前端
源码超级联盟8 小时前
display的block和inline-block有什么区别
前端
GISer_Jing9 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js