使用 Xcode 编译规则提升代码质量

随着项目的迭代,工程代码量越来越大,如果使用 Xcode 默认的编译警告级别,越来越多的隐患将被隐藏在代码中,这类隐患代码大部分来自旧代码,也有部分来自日常开发的代码,特别是在模块重构时,这些代码大概率会成为 bug 的发源地,也将会是后期优化的绊脚石。

由于 Objective-C 语言的动态性,部分不安全的代码写法在编译时并不会报错(error),只是会产生警告 (warning),而后者总会有意无意地总被忽略掉。通过新增加编译检查规则,提升编译器的校验级别,强制对可能存在隐患的代码拒绝编译通过,能尽可能减少人为出错,提升代码质量。

如何提升校验级别

Xcode 中允许开发者自行添加编译校验规则,如下图:

这些规则可以通过编译日志查询得到:

上图中紧接 -W 的字符串都是编译规则,具体含义可以去 Clang 文档上查找。找到需要的校验规则后,比如-Wunreachable-code,将其转换成 error 格式 -Werror=unreachable-code,然后填写到 Targets -> Build Settings ->Apple Clang-Custom Compiler Flags 中即可,再次编译时,原先 unreachable-code 地方的代码就会报错,提醒我们删掉不相关的代码,精简结构。

实用的校验规则

我们目前用到的规则有下面几条:

  1. 未用到的变量
    • 规则:-Werror=unused-variable
    • 例外:在变量前添加 __unused 修饰
  2. switchcase 没有完全列举
    • 规则:-Werror=switch
    • 例外:使用 default 关键字。其实这里不建议使用 default,如果后期因为业务代码变动 case 有增删改,它不会显示地报错误或者警告,只会默默地“吃掉”这些增删改,非常不便于错误排查。
  3. 方法有申明,但是没有实现
    • 规则:-Werror=incomplete-implementation
    • 例外:无例外,不允许出现。曾经碰到到在 .h 里面有申明但是 .m 中忘记写实现的情况,险些酿成大错。
  4. 调用未申明的 selector
    • 规则:-Werror=undeclared-selector
    • 例外:
      1
      2
      3
      4
      #pragma clang diagnostic push
      #pragma clang diagnostic ignored "-Wundeclared-selector"
      [self performSelector:@selector(testFun)];
      #pragma clang diagnostic pop
  5. 未实现标记为 @required 的协议方法
    • 规则:-Werror=protocol
    • 例外:如果不需要实现,申明改为 @optional;如果是 @required 必须要实现
  6. 不同指针类型间赋值(比如 NSNumber * 赋值给 NSString *NSArray<NSNumber *> * 赋值给 NSArray<NSString *> *)
    • 规则:-Werror=incompatible-pointer-types
    • 例外:尽量避免这种情况出现。如果很清楚后果,显示地类型转换
  7. 指针隐式赋值给基本类型(如 self 指针赋值给 int 变量)
    • 规则:-Werror=int-conversion
    • 例外:尽量避免这种情况出现。如果很清楚后果,显示地类型转换
  8. 不同枚举类型之间赋值
    • 规则:-Werror=enum-conversion
    • 例外:尽量避免这种情况出现。如果很清楚后果,显示地类型转换
  9. 不允许判断语句里面写成 “=”,如 if (a = b) // error
    • 规则:-Werror=parentheses
    • 例外:多加一层括号,明确表示使用返回值进行判断

目前这些规则,已经帮我们避免了很多隐藏问题,后续会根据实际情况再增加或删除。