- 原文地址:When a rewrite isn’t: rebuilding Slack on the desktop
- 原文做者:Slack Engineering
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:cyz980908
- 校对者:Ultrasteve, githubmnume
新版本的 Slack 将面向桌面用户推出,从头开始构建,更快,更高效,易用性更强。前端
广泛的观点认为你不该该从头重写代码,这是个好建议。花时间重写已经被使用的东西,并不能使咱们的客户的工做生活更简单、更愉快、更富有成效。运行代码也知道一些事情:经过数十亿小时的累积使用和数万个 bug 修复得到的知识是来之不易的。react
不过,软件代码具备生命周期。Slack 的桌面版是咱们最老的客户端,成长于咱们公司早期的快速发展和试验阶段。在那期间,随着客户对产品的使用和指望的增加,咱们一直在优化产品的市场适应性,并在不断的冲刺中跟上客户的步伐。android
现在,通过五年多的快速发展,Slack 被数以百万计的人使用,他们所在的公司规模比五年前更大,处理的数据比咱们刚开始时想象的还要多。多少能够预见,桌面客户端的基础开始出现一些内部问题。此外,技术格局已经偏离了咱们在 2012 年末选择的工具(jQuery,Signals,以及直接操做 DOM),目前的程序趋向于采用可组合的接口和更干净的程序抽象。即便咱们尽最大努力保证敏捷,但很明显,须要进行一些根本性的改变,才能发展桌面应用程序,并为下一波产品开发作好准备。ios
现有的桌面应用程序的体系结构有许多缺点:git
前两个问题是随着时间的推移咱们能够逐步改进的,可是,在一个 Electron 进程中运行多个工做区意味着要改变原始设计的一个基本假设,即一次只运行一个工做区。尽管咱们为拥有大量空闲工做空间的人们作了一些渐进式的改进 额外的改进,可是真正解决多进程问题意味着从零开始重写 Slack 的桌面客户端。github
忒修斯之船是一个思想实验,这个实验考虑的是,当一个物体每一部分随着时间的流逝被逐个替换时,这个物体还会不会是原来的物体。若是一艘船上的每一块木头都被替换了,那么这是同一艘船吗? 若是一个应用程序中的每个 JavaScript 片断都被替换了,它仍是同一个应用程序吗?咱们固然但愿如此,由于这彷佛是最好的作法。redux
咱们的计划是:后端
最后一步,对咱们来讲也是最重要的一步 —— 是建立一个新版的 Slack,它开始时不完整,但随着模块和接口的更新,逐渐向功能完整性发展。架构
去年的大部分时间里,咱们一直在内部使用这个只有新版本的应用程序,如今它正在推广给客户。app
首先要作的是建立新的代码库。虽然这只是咱们代码库中的一个新子目录,但它有三个由约定和工具强制执行的重要规则,每一个规则都旨在解决咱们现有应用程序的一个缺点:
前两条规则,虽然耗费时间,但实现相对简单。然而,转向多工做空间架构是一项艰巨的任务。咱们不能指望每一个函数调用都传递一个工做区 ID ,咱们也不能只设置一个全局变量来讲明当前哪一个工做区是可见的,由于不管用户当前正在查看哪一个工做区,程序内部仍然发生着许多事情。
咱们方法的关键在于 Redux,咱们已经在用来管理咱们的数据模型。在 redux-thunk 库的帮助下咱们进行了一些斟酌,咱们可以在 Redux 存储上模拟几乎全部的动做或查询,容许 Redux 围绕单个工做空间的概念提供一个方便的抽象层。每一个工做空间都有本身的 Redux 存储区,其中包含全部内容 —— 工做空间的数据、关于客户机链接状态的信息、用于实时更新的 WebSocket —— 应有尽有。这个抽象围绕每一个工做区建立了一个概念容器,而没必要将该容器保存在它本身的 Electron 进程中,这正是过去客户端所作的。
有了这些认识后,咱们就有了新的架构:
关于这一点,咱们有一个咱们认为可行的计划和架构,咱们已经准备好经过现有的代码库,对全部内容进行更新改造,直到咱们留下一个全新的 Slack。因此只有最后一个问题要解决。
咱们不能随便用新代码替换旧代码;若是没有某种类型的结构将新旧代码分开,它们将会无可救药地纠缠在一块儿,咱们就永远不会有新代码。为了解决这个问题,咱们在一个名为 legacy-interop 的概念中引入了一些规则和函数:
将新代码导出到旧代码很简单。咱们最初的代码没有使用 JavaScript 模块化或导入导出。相反,咱们将全部内容保存在名为 TS 的顶级全局变量上。导出新代码的过程仅仅意味着调用一个辅助函数,该函数使新代码能够在全局空间的一个特殊的 TS.interop 部分中使用。例如,TS.interop.i18n.t() 将调用咱们新的、多工做区感知的字符串本地化函数。因为 TS.interop 命名空间只在咱们的遗留代码库中使用,它一次只加载一个工做区,因此咱们能够在后台对工做区 ID 做简单的查询,而不须要遗留代码担忧它。
为新代码调整旧代码就没有那么简单了。当咱们运行旧版本的 Slack 时,新代码和旧代码都会被加载,但新版本只会包含新代码。咱们须要找到一种方法,在适当地利用旧代码同时不会在新代码中形成错误,而且咱们但愿这个过程对开发人员是尽量透明的。
咱们的解决方案称为 adaptFunctionWithFallback,它在咱们的旧 TS 对象上运行一个函数路径,并且若是咱们在仅使用新的代码的代码库中运行,可使用这个函数。这个函数默认为空操做,这意味着若是底层旧代码不存在,那么试图调用它的新代码将没有任何效果 —— 而且不会产生任何错误。
有了这两种机制,咱们可以认真地开始咱们的更新新旧代码工做。旧代码能够在更新时访问新代码,新代码能够访问旧代码直到更新时。正如您所想的那样,随着时间的推移,新代码库中旧代码的使用愈来愈少,而且在咱们准备发布时趋向于零。
这个新版本的 Slack 已经推出很长时间了,它包含了过去两年来一直致力于向客户不断推广的数十人的贡献。它成功的关键是咱们在项目早期采用的增量发布策略:随着代码的更新和功能的重建,咱们将它们发布给咱们的客户。Slack 应用程序的第一个“新”部分是咱们的表情符号选择器,咱们在两年多前发布了它 —— 以后是频道侧边栏,消息窗格和许多其余功能。
若是咱们等到 Slack 所有被重写后再发布它,咱们的用户在发布一个“爆炸的”替代品以前,就会对表情符号、消息、频道列表、搜索和无数其余功能有着更糟糕的平常体验。增量发布容许咱们尽快向客户交付真正的价值,帮助咱们专一于持续改进,并经过最大限度地减小客户首次使用的全新代码量,下降了新客户的发布风险。
广泛的观点认为,最好避免重写,但有时好处也很大,不容忽视。咱们的主要指标之一是内存使用状况,新版 Slack 提供:
这些结果验证了咱们在新版 Slack 中所作的全部工做,咱们期待着继续迭代,并随着时间的推移使它变得更好。
在策略规划的指导下,以明智的发布为调和,以及有才华的贡献者的鼓舞,逐渐的重写是纠正过去错误、为本身打造一艘崭新的船的绝佳方式,并使您的用户的工做生活更简单,更愉快,更有成效。
咱们热心于分享更多关于咱们在此过程当中学到的知识。在接下来的几周,咱们将会在 slack.engineering 上撰写更多文章:
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。 译