[译] 停止重写你的工程

865 阅读8分钟

原文:Stop rewriting projects
作者:Michael Spitsin

我曾无数次听人说过:“我希望我可以从头重写我的工程”,“如果我从头再写一次肯定能把所有地方都写好”,“我们是应该维护它,但是我们都知道要重写整个代码”
每次我听到这样的声音时,不解和困惑就会围绕着我。为什么这些人总是在尝试、想要或者梦想从头开始编写所有的代码呢?我认为这是一个错误的希望,会导致产品开发过程中错误的策略和行为

为什么会发生这种情况

对于人类来说从头开始是很正常的,在电影、诗歌甚至生活中,有时你想要放弃一切并从头开始,也就是说,有时候正是因为这个原因,你会搬到新的地方,改变周围的环境,结交新的朋友
工程也是一样,当你查看你的代码库并知道了“看吧,就是这样,写的乱七八糟”,你只想重新开始:

  • 通过使用新的方法,可以完美体现你的理想项目
  • 移除你从别人那里接手的老的遗留代码,如从外包团队转移到内部团队的项目、那些还支持 API 14 和 Java 6 的老到生锈的项目
  • 可能排除你的几个同事然后和几个与你想法一样的新同事一起,以便你不需要花费任何时间在代码争论上,因为这很容易松懈。在这里一切都是你想要的
  • 有适当的管理,这会有助于你进行代码重构,不会给你大量的未详尽说明的 feature,且通过完美的 UI/UX 提供新的 feature
  • 使用正统的 Scrum 或其他你喜欢的开发方法

我知道这种感觉,前段时间我也有过这种想法,但是事实是这永远不会发生
让我们说的更清楚些,就算这发生了,你将拥有一个很棒的项目、一个很棒的团队、一个适当的需求和重构时机以及流程等等,我也有这样的工程,并且我为此深感荣幸
这里的关键点是:当你想要丢弃所有东西改变生活时,你会丢弃它并找个新的。但是你不能在跟妻子同居时说“让我们重新开始吧”,这是行不通的。因为你有一个关联的遗留代码库,所以这是不能擦除的。你需要一起去适配这个关系。工程也是一样,如果你厌倦了并且想要丢弃所有东西,你可以丢弃这个项目和公司等,但是你不能停止整个过程然后说“兄弟们,我们现在就像在造屎,我不喜欢,让我们重新开始吧”

重写 vs 改写

让我们弄清我们的术语并讨论改写(adaptation)本身:因为在任何情况下,你都将删除至少一个符号并用你自己的去更改它,所以你将至少重写(rewrite)工程中的某些内容。嗯,是的,技术上是没问题的,但是你不会重写工程本身
改写的关键在于你将在项目中进行细微的更改。你将重写你的工程,但是只在你的脑海中,只在你团队的脑海中。选择你的路线,并继续它,而且这不仅仅是项目的重构。你无需做一个大步重写或者 10-20 步的重构,而是进行 1K-10K 的微步骤,你将一直进行重构,但是是一小步一小步进行的,这被称为 增量重构 (incremental refactoring)
此外,你将尝试一点一点地改善现有的代码库,假设你收到一个新的请求,要求更改旧页面的 UX,或者你需要修复其中的几个 bug,再花点时间看看,也许某个地方有个大的功能与那个功能相关,可以把它分成小的功能,例如对于 Long method,你可以使用 Extract method 技术。特别是当你拥有良好的测试基础时它将变得更加容易
你还没有?那就开始写测试吧。你需要修复旧页面的 bug 么?可以写个测试复现这个 bug 然后修复它,因此测试就变绿通过了,瞧!你已经有一个测试了。你需要写新的功能,写完它并写一些验收测试,好了,你又有了 10-20 个测试,不错,一点点,一步步的,你的测试将完美覆盖所需的页面,下次你就可以更方便地重构它的方法,因为你有一些备份
当 UI 层和逻辑层(业务逻辑、UI 逻辑、表示逻辑等等)耦合严重时,你没办法写测试,你可以先慢慢解耦,一个函数一个函数的,一个类一个类的。或者你可以在没有良好的测试代码库时进行少量重构,我有过这种经验,当你进行重构时,不仅要分解全部并重新构造,还要进行纳米级增量步骤,然后你会好很多
这里的关键点是:你需要停止重写、全局重构或者类似的东西,相反,要与你的团队一起制定一个计划,包括成千上万的小改进,选择你的路线,然后使用童子军规则以最小的步骤开始重构(可以参考 这篇文章

例子

乍一看并不是每个步骤看起来都那么小,让我们看下一个例子。我们已经有一个已经使用 Dagger 2 的工程,假设你处于某种原因想要迁移到 Koin(或 Kodein),不但是你,就算整个团队都想要这样做都是非常糟糕的,那你该怎么办呢?或者这是一种可以使全局重构暂停一段时间的特殊情况
好吧,首先,我们需要清楚,你的工程是整块的还是多模块的系统,如果是是整块的就要把它拆分成多个模块
不要害怕,你可以先从创建工具模块开始,其中放置基本的,大多数或者全部的工具方法,以帮助你的团队创造出色的产品。然后创建设计模块,其中暴露一些基本的样式、颜色和尺寸等。创建翻译模块,其中包含所有翻译,用户特定字符串和显示给用户的文本。这些模块(至少第一个版本)不需要你费劲,因为你只是将功能从一个地方转移到另一个地方
然后开始在单独的模块中实现新的功能,对于 MVP 版本,请尽量将其放在功能模块中。在某些情况下,由于路由/UI/分页与应用模块紧密相关,因此你无法创建完美的功能模块,但是你可以先从业务逻辑、特定模型、接口或者表示层开始
当你已经有了一些功能模块和工具模块时,让其中的类的对象通过 Koin 而不是 Dagger 注入其他对象,这里唯一的问题就是收集 Dagger 提供的外部依赖并且暴露模块的依赖给 Dagger 对象图,为此你可以添加适配模块,要么 Dagger module 提供所有的依赖关系(将调用委托给对应的 Koin 函数),要么 Koin module 持有 Dagger 相关实例并将实现委托给它们
为了将老的代码库从 Dagger 迁移到 Koin,你可以使用上述的胶水模块然后一个依赖一个依赖地从 Dagger 迁移到 Koin。是的,虽然不是很快,但是足以避免阻塞主要的开发过程,你只需要评估一下你可能或者想要的速度就行了
这里的关键点是:你遇到的几乎每个大问题都可以分解为小步骤以进行逐步重构。要注意的是这种情况下也有例外,如你想把 Xamarin 的工程改成 iOS 和 Android 原生的两个项目,就不得不做出牺牲了

适应你的代码,就像对待孩子一样

最后,重要的不但是保持专业、保持代码清洁、使用童子军规则、进行增量重构等等,更重要的是不重写所有东西。你必须明白,当你增量重构一些东西而不是完全重写全部时你会感觉很正常,因为你打算从头写一个工程,要么是在你知道你需要进行重构的时间点,要么是在有人跟你说你的代码很蠢且需要重写的时间点。所以,打破这个恶性循环,改变你的思想吧
不要再想重写所有东西了,慢慢接纳并适应它们,或者不要参加你不想治疗的工程