关于iOS 代码质量把控研究

前提

code review 是我们工作中可以说必不可少的一环,一方面能够提早的发现代码中不合理的部分、或者不稳定、架构不合理的部分,提高代码的质量;另外一方面能,更够让团队的知识共享,互相熟悉业务需求,以及团队写的好的部分。OK,作为code review最早最基础的一环,可以说是代码规范。如果人工进行代码规范的维护,难免会遗漏,而且会花费时间在这部分。

所以,针对这种情况,我们可以做的是,做一个lint帮助我们检测不规范的代码,OK,下面就lint,开始研究!

Lint 时机

我们熟知的Objective-C lint,有OClintInfer; swift 有swiftLint,首先我们先不考虑这个几个lint的问题,我们先考虑一个重要的问题,lint应该放到什么阶段?

  1. 放到build时期?

    这种方案被我们pass掉了,首先放到build时期会增加我们的build时间,第二事实上我们需要嵌入脚本到项目里,可能会影响到打包。

  2. 放到git push 之后gitlab pipeline时期?

    这种方案不出意外也被pass了,首先如果知道pipeline job的人都知道,我们需要配置runner, 本身来说配置加集成,可能就不是一个容易的事情,而且对于我来说我觉得这个事情,太晚了,lint 有问题,还等再次push….

  3. 放到pre commit时期?

    Ok, 对于比较合适的事情,我觉得就是在precommit时期,因为基于git 管理,我们代码每次改动之后都需要commit,那么commit事情去做lint这件事情,是非常合适的,如果存在问题,那么就修改之后,再次commit。时间上,时机上我觉得都很合适

    ok, 对于 lint 放到哪个时机的问题,我们已经确定–pre commit时期

pre-commit

git 其实为我们提供了很多种的hook方式,随便打开一个.git文件夹就可以看到一堆的hooks example文件,如下:

image-20220915152237340

OK,所以其实我们想要hook,就变得非常容易,只要我们修改pre-commit为可执行文件,并在里边按照example嵌入执行代码即可。

这一步,早就有人帮我们做了,那就是pre-commit 他不仅简单了我们的操作,并且起到了脚本lint代码分离的效果,他通过.pre-commit-config.yaml 文件中的配置,在执行git commit时候执行pre-commit 脚本,再而执行到安装的pre-commit代码里,进行clone .pre-commit-config.yaml里配置的repo,以及分析,执行对应的hook代码。

接下来简单的介绍一下pre-commit 相关的东西:

  1. 安装

    Mac 推荐使用:brew install pre-commit

    其他安装:pip install pre-commit

    conda install -c conda-forge pre-commit

  2. 配置

    安装完pre-commit之后,在.git 同级目录,添加.pre-commit-config.yaml 文件,则可支持pre-commit

    .pre-commit-config.yaml的相关配置:

    repos:
    -   repo: https://github.com/pre-commit/pre-commit-hooks
        rev: v2.3.0
        hooks:
        -   id: check-yaml
        -   id: end-of-file-fixer
        -   id: trailing-whitespace
    -   repo: https://github.com/psf/black
        rev: 21.12b0
        hooks:
        -   id: black
    

    注意点:rev: 不再支持commit hash id, 只支持tag

    具体参考:https://pre-commit.com/#plugins

  3. 创建一个自己的hook

    一个hook必须要包含a .pre-commit-hooks.yaml 文件,这个文件是告诉pre-commit,这个hook库里包含的hook id等信息…

    大概张这个样子…

    -   id: trailing-whitespace
        name: Trim Trailing Whitespace
        description: This hook trims trailing whitespace.
        entry: trailing-whitespace-fixer
        language: python
        types: [text]
    

​ hooks 仓库,支持很多种语言,每种语言包含的文件也不太一样,比如script (shell脚本)必须entry参 数给一个相对路径的shell脚本;比如python,必须要执行pip install .(及包括setup.py或者 pyproject.toml)以及entry一般在setup.py 里的console_scripts或者scripts配置的。

了解更多

Objective-C Lint

上面提到现存在两种lint,oc-lint/ infer。

oc-lint

安装: brew tap oclint/formulae

brew install oclint

*// 安装xcpretty* gem install xcpretty

先简单介绍一下OC lint:OC lint 是通过编译之后的产物 compile_commands.json 去分析的,所以必要的是我们执行oc-lint必须要build。然后在完成之后会以自动打开一个html分析结果。

我们先说说他的缺点:

  1. 必须要编译,首先这是一个漫长的过程,而且对于我们一个不支持模拟器的app(害!因为内部直播的framework没把x86打进去)这是比较致命的可以说
  2. 他不支持增量查,这样会带出来很多历史遗留问题,对于集成来说,又成了一个比较窘困的问题(你想想,以前多少可能有问题的代码,这集成都得改,不得改个三天三夜的😁)

接下来我们说说他的优点:

1. oc-lint 不仅查了不规范的代码,而且查了可能会导致问题的坏味道代码
1. oc-lint 对于代码严重程度做了等级分化,并且以html的方式打开,更加的直观
1. oc-lint支持自定义规则,和修改规则

当然,对于1.2的缺点,已经有了一些方案解决,了解更多

后面会尝试集成到pre-commit hook, 敬请期待…

infer

安装: brew install infer

​ 可以选择下载安装包安装,下载地址

Infer 是facebook提供的一个lint 库

缺点:

	1. 貌似infer 也需要编译工程
	1. 不支持自定义规则

优点:

1. 支持多种检测,包括空指针、内存泄漏等等的检测
1. 支持增量检测

具体我也没有太多研究,需要深入研究的,请参考

Objective-CLint 自制hook

这个库是我基于SpaceCommand三方库,做的pre-commit hook,简单的介绍一下:

SpaceCommand是基于clang-format 制作的代码规范工具,而且写了一些自定义规则,用来检测clang-format忽略或者有歧义的部分。它支持了检测不规范代码,并给出终端提示;而且提供了一键格式化所有有问题的代码。

那么Objective-CLint 在它的基础上,1. 我显示了将它配置成一个可以pre-commit集成的hook,2. 增加了可视化diff 到html,自动打开 3. 去掉了一键格式化 5. 修改自定义规则,使得适用更加广泛。

具体内容介绍,请移步到个人博客Objective-CLint 或者移步到github

到此,我们对于OC代码已经有了好几种方案,我选择了容易集成且轻量级的自制hook。

Swift Lint

swiftLint

​ Swift 代码毫无疑问,我们就可以使用SwiftLint去集成。swiftLint 已经为我们提供了pre-commit hook,我 们可以直接使用,当然也可以fork,修改规则使用

​ 具体请查看github:https://github.com/realm/SwiftLint

集成

到此为止,我们所有的lint 工具 以及hook 工具已经齐全,即 pre-commit + Objective-CLint + SwiftLint!

Ok, 当然我们还有一些工作量需要做,为了团队更好的集成,我们添加检测安装脚本位于pod pre_install, 目的在于自动检测是否安装了pre-commit,以及自动安装pre-commit。

下面是检测脚本:

#!/bin/bash

echo "正在检查安装必要工具..."
# 检测是否安装了homebrew
if [ "$(command -v brew -v)" ]; then
    echo "✅homebrew 已经安装"
else 
    echo "正在安装brew..."
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
   echo '✅安装完成homebrew 🍺🍺🍺 /n'
   
fi

# 检测是否安装了pre-commit
if [ "$(command -v pre-commit)" ]; then
    echo "✅pre-commit 已经安装"
else
    echo "正在安装pre-commit..."
    brew install pre-commit
    echo '✅安装完成pre-commit 🍺🍺🍺/n'
fi


if [ ! -f ".pre-commit-config.yaml" ]; then
    echo "\033[31m❎文件 \".pre-commit-config.yaml\" 不存在, 请添加.pre-commit-config.yaml\033[0m"
    exit 1
fi

pre-commit install
echo "\033[36m ...................所有工具都安装成功😁😁😁 \033[0m"

Ok, 我们把它集成到pod中,当然没有使用pod管理的,直接执行这个脚本即可。把下面的脚本嵌入到podfile中

pre_install do |installer|
    flag =  `sh check_tools.sh`
    Pod::UI.puts flag
end

添加.pre-commit-config.yaml

fail_fast: false
repos:
  - repo: https://github.com/HaoXianSen/Objective-CLint.git
    rev: v0.0.4
    hooks:
      - id: objc-lint
        name: objc-format
        entry: format-objc-hook
        language: script
        require_serial: true
        verbose: true

  - repo: https://github.com/realm/SwiftLint
    rev: 0.49.1
    hooks:
    - id: swiftlint

OK,这样我们整个集成就完成了。

当然swiftLint的配置不一定是适用于我们的,比如我们想report为html、或者添加一些自定义的规则等等,就需要我们fork一份swiftLint,做一些自己的适配。上面的swiftLint就使用自己的repo以及自己tag就好了。

总结

到此为止,我们可以说整iOS 混合发开的代码规范lint就集成完事了。

当然我们还有优化的部分,首先自己的lint 只是规范检测,不想OCLint 那样面积大,并且输出也不是很哇塞。后续我们可以继续使用fast-oclint的方案,既能使用OCLint的强大,又能不用编译还支持增量编译。