前段时间和一同事一起重构了两个 APP,正好想写一些重构心得,前天又在知乎上看到一前辈推荐《重构》这本书,据说是程序员的必读书籍,于是就粗略的读了一遍,对重构有了更深层次的认识了。这里结合 iOS 项目的重构,谈谈与重构相关的问题,做一下记录及分享。

一、《重构》读书笔记

1.1 重构的定义

  • “重构”这个词有两种不同的定义:
    • 第一个定义是名词形式: 重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
    • 第二个定义是动词形式: 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

重构的定义说明了两点,第一,重构的目的是使软件更容易被理解和修改;第二,重构不会改变软件可观察的行为——重构之后软件功能一如既往。

1.2 为何重构?

  • 重构可以帮你始终良好的控制自己的代码,它可以用于以下几个目的:

    • 重构改进软件设计 如果没有重构,程序的设计会逐渐腐败变质。当人们只为短期目的,或是在完全理解整体设计之前,就贸然修改代码,程序将逐渐失去自己的结构,程序员越来越难通过阅读源码而理解原来的设计。 完成同一件事情,设计不良的程序往往需要更多代码,这常常是因为代码在不同的地方使用完全相同的语句做同样的事情。因此改进设计的一个重要方向就是消除重复代码。

    • 重构使软件更容易理解 书的前面有这么一句话:“任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。”而重构可以使代码结构更清晰,使代码更容易被理解。

    • 重构帮助找到 bug 对代码进行重构,可以深入理解代码的作为,并恰到好处地把新的理解反馈回去,在重构的同时,我们可以发现某些代码逻辑写的不严谨或有问题。

    • 重构提高编程速度 良好设计师维持软件开发速度的根本,重构可以帮你更快速地开发软件,因为它阻止系统腐败变质,它甚至还可以提高设计质量。

1.3 何时重构?

  • 怎样安排重构时间表?是不是应该每两个月就专门安排两个星期来进行重构呢?这里需要说明,重构不是一件应该特别拨出时间做的事情,重构应该随时随地进行。不应该为重构而重构,我们之所以重构,是因为想做别的事情,而重构可以帮助我们把那些事做好。

    • 三次法则(事不过三,三则重构) 第一次做某件事时只管去做;第二次做类似的事情会产生反感,但还是可以去做;第三次再做类似的事情,就应该重构了。

    • 添加功能时重构 最常见的重构时机就是我们想给软件添加新特性的时候,此时,重构的直接原因往往是为了帮助我们理解需要修改的代码——这些代码可能是别人写的,也可能是自己写的。

    • 修补错误时重构 调试过程中重构,多半是为了让代码更具有可读性。

    • 复审代码时重构 代码复审对于编写清晰代码很重要,比如我的代码也许对我自己来说很清晰,但对他人则不然,这是无法避免的,代码复审会让更多人有机会提出有用的建议,然后考虑是否可以通过重构来轻松的实现它们。

1.4 重构的基本技巧:

  • 小步前进、频繁测试

二、结合 iOS 项目重构心得

2.1 项目目录结构

项目的目录结构是开发中最基础的,但也是很重要的,清晰的目录结构能够让人一眼就看懂该项目的业务及功能,目录结构也能反应一个开发者的经验及架构水平。项目目录结构比较常规的有两种,第一种是按照业务分类,第二种是按照模块分类。当然具体还得根据具体的业务需求来做,适合自己的才是最好的。

这里有一篇关于项目目录结构的文章,有兴趣的童鞋可以读下:iOS 项目的目录结构能看出你的开发经验

2.2 业务与 UI

这里不讨论 MVC 架构与 MVVM 架构,关于架构模式之间的争论有很多,个人比较赞同一个观点:不要局限于 MVC、MVVM、MVP 等等一些架构模式,万变不离其宗,真正适用于项目的架构才是最好的架构。刚接手的旧项目在设计初期以及开发过程中,没有进行合理的规划,以至于一些控制器过于臃肿,代码量很多都是超过了 1000 行,有的甚至超过了 1500 行,而且写的很乱。重构的目的,就是提高代码的可读性以及便于以后的维护,我这里按照 MVC 的架构模式,将 UI 部分进行抽离,将工具代码(比如计算球面两点之间的距离)进行封装,并放到了相关的工具类中,又对控制器中的冗余代码进行了整理,使得控制器中的代码减少至之前的三分之一以下。分享一张 cocoa 上的 MVC 架构图:

MVC 架构

2.3 代码还是 xib、 storyboard?

写 UI 界面用代码还是用 xib 一直是 iOS 界的争论,有的人倾向于使用代码,有的人倾向于使用 xib,巧神之前在博客中也讨论过这个问题,并给出了一些建议(个人比较赞同👍):

  • 对于复杂的、动态生成的界面,建议使用手工编写界面。
  • 对于需要统一风格的按钮或 UI 控件,建议使用手工用代码来构造。方便之后的修改和复用。
  • 对于需要有继承或组合关系的 UIView 类或 UIViewController 类,建议用代码手工编写界面。
  • 对于那些简单的、静态的、非核心功能界面,可以考虑使用 xib 或 storyboard 来完成。

这里是巧神关于写 UI 用代码还是用 xib 的相关讨论: iOS 开发中的争议(二)

2.4 模块化设计

什么是模块化?比如我们刚开始码代码的时候,有一个经常用的方法(比如还是计算球面两点之间的距离),由于这个方法经常用,我们会把这段代码拿出来放到一个公共类里,以便实现代码的复用,这就是简单的模块化。关于模块化设计的原则,一位阿里大神的建议如下:

  • 越底层的模块,应该越稳定,越抽象,越具有高复用度。
  • 不要让稳定的模块依赖不稳定的模块, 减少依赖。
  • 每个模块只做好一件事情,不要让 Common 出现(避免一大堆不相干的代码放进一个模块)。
  • 按照架构的层数从上到下依赖,不要出现下层模块依赖上层模块的现象 业务模块之间也尽量不要耦合。

对模块化设计感兴趣的童鞋可以看下这篇文章,绝对干货!模块化与解耦

2.5 代码规范

关于代码规范,每个程序员遵守的代码规范多多少少都会有些不同(比如什么时候该空格,常量变量的命名方式等等),之前听一前辈说过,尽量遵守那些“约定俗成”的代码规范,另外在编码时,要保证自己的代码规范始终一致,别给人一种你写的代码是几个人共写的错觉。

  • 命名规范 iOS 命名主要注意两个方面,第一是可读性高,别人一看这个名字就知道它的含义及作用;第二是防止命名冲突,命名时应遵循驼峰式命名法则,另外要加前缀,比如常量命名一般会在前面加上字母 k。

  • 编码规范 关于编码规范有很多细节需要注意,比如函数方法一般不能过长;比如实例变量的修饰符要注意;再比如尽可能保证 .h 文件简洁,API 尽量写在实现文件里……编码时还有其它一些应该注意的,比如写 delegate 的时候类型应该为 weak,以避免循环引用;再比如经典的圆角问题,过多的使用 layer.masksToBounds 对系统的开销非常大,会使页面变的卡顿等等……这些编码细节有很多需要注意,就不一一列举了。

  • 写注释 写注释写注释写注释,重要的事情说三遍😂。注释可以帮助其他同事更好的理解你写的代码,还方便自己以后的阅读。

代码规范方面,这里也推荐一篇不错的文章:iOS开发-代码细节优化(长期更新)

再安利一本书,《编写高质量 iOS 与 OS X 代码的 52 个有效方法》,这本书对编码时应注意的细节写的很全面,之前读过一遍,过几天会再读一遍,并记录。