重构Citimobil应用程序的历史



一年多以前,我以Android开发人员的身份加入CityMobil团队。 我习惯了自己的新项目,新方法和新技术。 当时,Citimobil已经有相当长的历史了,就像我采用的项目一样,这是一个用于订购出租车的Android应用程序。 但是,在这种情况下通常会发生这种情况,该代码带有旧解决方案的特征痕迹。 现在,在成功重构代码之后,我想分享一些想法,这些想法对那些必须重构现有项目的人很有用。 最重要的是,它对于具有小型开发团队的小型公司可能很有用。

企业通常会测试其构想,将有限的资源用于该构想,并试图获取反馈,并尽快测试其假设。 通常,在这种情况下,考虑到未来的高质量的项目架构思考和实施正在逐渐淡出背景。 逐渐地,项目获得了新的功能,出现了新的业务需求,所有这些都影响了代码库。 “ CityMobil”在这方面也不例外。 该项目由旧办公室中的几个团队顺序开发,然后在迁移过程中得到了支持并与外包部分地对应。 然后他们开始组建新的团队,并把工作交给我。

当时,“发展”移至莫斯科办事处,工作如火如荼-不断出现新的有趣和雄心勃勃的任务。 但是,遗产越来越多地投入使用,一旦我们意识到大变革的时机已到。 不幸的是,那时没有发现太多有用的文献。 这是可以理解的,从经验中知道,几乎不可能想出或找到在100%的情况下都有效的完美配方。

首先要做的是了解您是否真的需要重构? 在以下情况下应考虑使用:

  1. 尽管团队中的专家水平很高,但引入新功能的速度却过低。
  2. 更改程序某一部分中的代码可能会导致另一部分发生意外行为。
  3. 新团队成员的适应被延迟。
  4. 强大的连接性阻碍了代码测试。

意识到问题的存在之后,应该找到以下问题的答案:

  1. 到底是什么错?
  2. 是什么导致了这个?
  3. 需要采取什么措施来防止这种情况再次发生?
  4. 如何解决这种情况?

如果不铺设某种体系结构,几乎不可能构建一个长期有效的项目。 在我们的项目中,我们决定引入一种“分层”架构,该架构已被很好地证明了自己。

最初,该项目主要是在Android SDK本身提供的工具的帮助下编写的。 该方法无疑是有效的,但是它迫使您编写许多样板代码,这极大地阻碍了开发。 考虑到当今许多技术已习惯于某些技术堆栈,因此适应新开发人员的时间更长。 渐渐地,我们发现了许多方便的技术,这些技术很多人都知道和重视,并且已经证明了它们的可靠性和一致性:

  • MVP-用户界面设计模式(模型-视图-演示器)。
  • Dagger 2是用于实现依赖关系的框架。
  • RxJava2是ReactiveX的实现-ReactiveX的一个库,用于使用JVM的Observer模式创建异步和基于事件的程序。
  • Cicerone是一个库,可让您简化应用程序中的导航。
  • 许多用于处理地图和位置的特定库。

为团队采用通用的代码风格,制定一套最佳实践非常重要。 您还应该注意基础架构和流程。 最好立即为新代码编写测试,因为有关此主题的信息很多。

在团队内部,我们开始进行代码审查而没有失败,这并不需要花费很多时间,但是代码的质量已经提高了很多。 即使您是团队中的一员,我也建议您使用Git Flow,创建合并请求,至少自己检查一下。

可以将所有“肮脏”工作委托给CI-在我们的示例中,这是使用Fastlane的TeamCity。 我们对其进行了配置,以构建功能分支,运行测试并布置内部测试。 在我们的位置,我们针对生产/登台环境分别配置了程序集,功能部件-(我们通过任务号和模板TASK#task_number对其进行调用)和发行分支。 这使测试变得更加容易,并且如果发生错误,我们会立即知道需要修复哪些问题以及在哪里进行修复。

采取所有初步措施后,我们开始工作。 通过创建一个包(cleanarchitecture),我们在一个旧项目中开始了新的生活。 将入口点移至应用程序时,请不要忘记活动别名 (a-la ActivitySplash)。 如果您忽略此设置,则在最佳情况下,您将丢失启动器中的图标,在最坏的情况下,将破坏与其他应用程序的兼容性。

<!-- android:name=".SplashActivity" - old launcher activity --> <!-- android:targetActivity=".cleanarchitecture.presentation.SplashActivity" - new launcher activity --> <activity-alias android:name=".SplashActivity" android:targetActivity=".cleanarchitecture.presentation.SplashActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity-alias> 

经验表明,最好从较小的小屏幕和部分应用程序开始重构。 而且,到了处理程序最复杂,最繁琐的部分的时候,相当一部分的代码已经为其他模块编写,并且可以重用。

另外,我们还有一个艰巨的任务来完全重新设计应用程序,有时这会导致屏幕的完全重写。 我们从改进辅助屏幕开始,准备进行主要操作。

重写应用程序的下一部分之后,我们在应用程序的旧部分中搜索了代码段,并用@Deprecated注释及其类似物标记了它们: https : //github.com/VitalyNikonorov/UsefulAnnotation 。 在其中,我们指出了重写程序这一部分时应执行的操作,其功能和实现位置。

 /** * This class deprecated, you have to use * com.project.company.cleanarchitecture.utils.ResourceUtils * for new refactored classes */ @Deprecated public class ResourceHelper {...} 

在一切就绪后,他们决定在6-8周内不发布新功能。 我们在自己的分支中进行了全局重写,然后向其中添加了合并请求。 在重构结束时,他们收到了令人垂涎的请求请求和几乎完全更新的应用程序。

重构后,对应用程序功能的更改变得更加容易。 因此,最近我们再次从事授权屏幕的处理。

最初,它们看起来如下:



经过第一次处理和重构后,它们开始看起来像这样:



现在他们看起来像这样:



结果,第一次迭代花费的时间是第二次的两倍以上。 由于除了处理UI外,我还必须了解位于同一位置的业务逻辑代码,尽管这不是必需的,但已消除了该缺陷,从而减少了在第二次迭代中花费在任务上的时间。

我们现在有什么?

为了使代码便于将来使用和开发,我们坚持“干净架构”的原则。 我不会说我们拥有规范的Clean,但是我们采用了许多方法。 表示层是使用MVP(模型-视图-演示器)模式编写的。

  • 以前,我们不得不无休止地相互讨论每个步骤,以阐明一个模块中的更改是否会影响另一个模块的功能。 现在,通信的开销已大大减少。
  • 由于单个组件和片段的统一,代码库的数量大大减少了。
  • 由于相同的体系结构统一和处理,因此有更多的类,但是现在它们之间有明确的职责划分,从而简化了对项目的理解。
  • 代码库分为多个层,为了使它们分离和交互,使用了依赖注入框架Dagger 2,这减少了代码的一致性并提高了测试速度。

与遗留代码重构相关的还有更多有趣的地方。 如果读者有兴趣,我下次将写更多有关它们的信息。 如果您也分享经验,我也将很高兴。

Source: https://habr.com/ru/post/zh-CN416331/


All Articles