前言

最近公司的项目要更新所有界面的 UI 风格,趁此机会正好把项目重构一遍,本文主要记录重构时的一些选择和解决的问题。

背景

首先说说背景,也就是为什么要重构,因为重构是需要成本的,一不小心修改错了,就会让原来完好的产品出各种问题,所以先总结下为什么,主要是以下四个问题:

  • 1.没有统一的代码风格
    这个就比较痛苦了,因为历史的原因,或者说这个项目初期就没有代码规范,在接手项目前,代码风格就非常随意、天马行空。在此期间我重构了一部分,但仍有一大半不规范的代码。
  • 2.臃肿的 ViewController
    这个好理解,一个类几千行代码,应该的不应该的都挤在一起。
  • 3.难以理解的逻辑代码
    业务逻辑代码混乱不堪,看的头疼,还不敢乱删。
  • 4.混乱的类调用
    没有明确一个类该做什么,全都堆在一起。

追求代码质量是一个优秀程序员对自己的要求,所以趁这个机会,决定把整个项目重构一遍。

架构的选择

当前在 iOS 开发上有很多热门的架构模式,如 MVC、MVVM、MVCS、VIPER 等等。对此我的观点是:架构的选择要结合具体的情况,比如业务的复杂度、开发人员的接受程度,以及重构的时间周期等等,选择一个对的架构比选择一个复杂的架构更为重要。
在老项目里选择的是 MVC,对于项目中的绝大部分场景来说,MVC 没有成为制约业务发展的瓶颈,同时考虑到新项目的学习成本以及重构时间周期的问题,所以在新项目上还是选择了 MVC。

统一的代码风格

无规矩不成方圆,在老项目里由于流程的缺失和开发人员的更迭,一直没有一个统一的编码规范。而在多人开发时,保持统一的编码规范是很有必要的,这样易于保持代码一致性和 Code Review。所以确定了架构之后,接着就是确定编码规范。
下面是编码规范里一些需要注意的点:

命名

使用可读的驼峰命名法去给类、方法、变量命名。
常量应该使用驼峰式命名规则,所有的单词首字母大写和加上与类名有关的前缀。

代码分块

在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#pragma mark - Lifecycle

- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Custom Accessors

- (void)setCustomProperty:(id)value {}
- (id)customProperty {}

#pragma mark - IBActions

- (IBAction)submitData:(id)sender {}

#pragma mark - Public

- (void)publicMethod {}

#pragma mark - Private

- (void)privateMethod {}

#pragma mark - Protocol conformance

留空白

  • 建议使用tabs 而不是使用空格,tabs缩进使用4个空格,确保在Xcode偏好设置来设置。
  • 文件结束时留一行空白
  • 用足够的空行把代码分割为合理的逻辑块,而不是非常紧凑
  • 不要在一行代码结尾处留空格
  • 更不要在空行(\n)中使用缩进(\t)

代码块缩进

(if/else/switch/while etc.)或者method function 的大括号留在当前行,并前保留一个空格 ,能省略的不要添加

1
2
3
4
5
if user.isHappy {
  // Do something
} else {
  // Do something else
}

不推荐

1
2
3
4
5
6
7
if (user.isHappy )          多余空格
{                  换行位置不对
  // Do something
}
else {
  // Do something else
}

项目层级划分

物理层级

上文确认了项目架构是以 MVC 为主,所以这里推荐使用 MVC + 按业务划分项目层级的方式。具体的如下图:

整个项目主要分为4个文件夹,分别是:

  • AppDelegate: 程序入口。
  • Classes: 存放主要代码文件。
  • Resources: 存放资源文件,如图片、音频、视频、 HTML 文件等。
  • Supporting Files: 存放配置文件,如 Info.plist、main.m、pch 文件等。

而其中 Classes 目录下,又细分如下图:

  • General:存放一些通用的类,如基类、Category、Model 等。
  • Sections:按业务模块细分 MVC。
  • Helpers:存放各种工具类。
  • Venders:存放需要手动引入的第三方库。
  • Macro:存放全局头文件,各种宏定义,常量等。

代码逻辑层级

从代码逻辑上,大致分为 5 层

  • 网络层:负责和服务器通讯获取数据。
  • 数据层:存储用户的数据,包括内存cache。
  • 业务层:包含各种业务逻辑。
  • UI数据层:负责提供UI层所需要的数据,UI只和这层打交道。
  • UI层:包括ViewController和View,处理用户的输入。

分层使项目更清晰,开发时做到各个层独立,高内聚、低耦合。

布局规范

AutoLayout

老项目是纯 Frame 布局,代码里有一大堆 Magic Number,一大堆屏幕尺寸判断,而这些造成了项目里有过多的布局代码,对于刚上手项目的人来说也不太容易看懂。所以这次重构整体采用 AutoLayout 布局,摒弃老项目里的纯 Frame 布局方式,精简代码。

Xib/Storyboard

手写 UI 和 Xib/Storyboard 谁优谁劣这里不做讨论,但有一点我认为 Xib/Storyboard 是比手写 UI 要快的。而这次重构工作量大工期紧,所以摒弃老项目里的手写 UI ,改用 Xib/Storyboard 。

精简 ViewController

这一部分我认为是本次重构的重头戏,也是本次重构的主要目的所在,精简 ViewController 里的代码,解决老项目中 Massive ViewController 的问题。
主要工作分为以下几步:

  • 1.保留最重要的任务,拆分其它不重要的任务。
  • 2.拆分后的模块要尽可能提高可复用性,尽量做到DRY
  • 3.提高拆分模块后的抽象度

胖 Model 的问题

上文精简 ViewController 把代码都加入到 Model 里去,所以会造成胖 Model 的问题。对此我的理解是,除了优化外,代码的总量是确定的,一方的精简必然会造成一方的增加,而 Model 在这里是用来帮 ViewController 处理业务逻辑的,也就是说胖 Model 包含了部分弱业务逻辑。胖 Model 要达到的目的是,Controller从胖 Model 这里拿到数据之后,不用额外做操作或者只要做非常少的操作,就能够将数据直接应用在 View 上,从这点上看胖 Model 也是可以接受的。

单元测试

老项目是不写测试代码的,也没有写单元测试的条件。想想一个类堆砌几千行代码,想写也不好下手,但既然重构了,单元测试也得补上。

单元测试对于目前来说,就是为了方便测试一些功能是否正常运行,还有调试接口是否能正常使用。

有时候你可能是为了测试某一个网络接口,然后每次都重新启动并且经过很多操作之后才测试到了那个网络接口,如果使用了单元测试,就可以直接测试那个方法,相对方便很多。
比如由于修改较多,想测试一下分享功能是否正常,这时候就有用了。或者直接看一些接口返回的数据也会非常直观,不用启动整个工程。

TODO

在这也列举两个接下来可以做的事,先添加到 ToDoList 里。

  • URLRoute

  • 模块化

最后

其实总结起来就三点,尽量保证:

  • Controller 里的代码尽可能的少
  • Model 的功能尽可能的完整
  • View 尽可能独立

就能构建一个容易维护,便于协同的项目。

本文只是在项目重构中总结的一些比较重要的点,并不意味着都是最优解。而我在项目的架构和代码的组织上经验尚浅,若本文有什么错误或是有更好的方法请直接指出,欢迎交流讨论。

Reference

iOS应用架构谈 view层的组织和调用方案