静态分析
概述
静态程序分析是一种在不执行代码的情况下,对代码进行分析以发现潜在问题、优化代码质量或检测安全漏洞的方法。
静态分析方法有:
- 语法检查 (Syntax Checking)
- 代码规范检查 (Linting)
- 数据流分析 (Data Flow Analysis)
- 控制流分析 (Control Flow Analysis)
- 安全漏洞检测 (Security Vulnerability Detection)
常用静态分析工具有:
Xcode 静态分析器。Xcode 自带的静态分析器能够分析 Objective-C 和 Swift 代码,自动检测潜在的内存管理问题、逻辑错误以及可能的安全漏洞。开发者可以在 Xcode 中开启静态分析功能,通过菜单路径 Product -> Analyze 来执行分析。
SwiftLint 是一个专为 Swift 语言设计的静态分析工具,用于确保代码符合 Swift 的编码风格和最佳实践。它能够检测代码中的格式问题、不规范的命名、未使用的代码等。
1 | brew install swiftlint |
OCLint 是一款适用于 Objective-C、C 和 C++ 代码的静态分析工具,专注于代码质量的提升。它能够检测代码中的常见问题,如复杂度过高、未使用的代码等,并提供详细的报告。
1 | brew install oclint |
Infer 是由 Facebook 开发的一款开源静态分析工具,支持分析 Objective-C 和 Swift 代码。它特别擅长检测空指针引用、内存泄漏和资源泄漏等常见问题。
1 | brew install infer |
为了获得更全面的静态分析结果,开发者通常会结合多种工具和方法。例如,可以使用 Xcode 的静态分析器进行基本的代码检查,使用 SwiftLint 和 OCLint 确保代码规范,最后通过 Infer 进行深入的逻辑和安全分析。
此外,许多静态分析工具支持集成到持续集成(CI)系统中,使得静态分析成为开发流程的一部分,确保每次代码提交都符合质量标准。
Clang静态分析器
使用
静态分析器是Xcode内置的一个工具,它可以在不运行源代码的情况下进行静态的代码分析。静态分析器不执行代码,而是通过静态分析来检查代码中的逻辑、安全和API使用等问题。主要支持C/C++/Objective-C,对Objective-C和Swift混编的工程也有很好的支持。
在Xcode中,通过点击Product菜单下的Analyze选项来启动静态分析器。分析完成后,问题会以蓝色的感叹号在Xcode上方显示,也可以在Issue Navigator中查看。
Clang静态分析器的模块位于/llvm/tools/clang/lib/StaticAnalyzer。Checkers目录下包含了各种检查器,每个检查器负责一个独立的检查项目。共用逻辑部分下沉到Core模块中,大量依赖LLVM和Clang的库。
- scan-build:负责对目标代码进行分析,并生成HTML样式的分析报告。
- scan-view:负责在本地运行一个简易的web server,方便查看生成的报告。
你也可以直接使用 clang 命令来运行静态分析。假设你有一个 C 文件 example.c,可以这样运行分析:
1 | clang --analyze example.c |
这将对 example.c 进行静态分析,并输出分析结果。
让我们以一个简单的 C 程序为例,它可能包含一些潜在的错误:
1 |
|
在这段代码中,存在以下问题:
- malloc 之后没有检查是否成功分配内存。
- 函数 risky_function 中,如果传入一个空指针 ptr,则可能会解引用空指针。
使用 clang –analyze 来分析这段代码:
1 | clang --analyze example.c |
输出可能会包括如下警告:
1 | example.c:12:5: warning: Dereference of null pointer (loaded from variable 'p') |
如果你在使用 Xcode 进行开发,你可以通过以下方式启用静态分析:
- 在 Xcode 中打开你的项目。
- 选择 Product > Analyze,Xcode 将运行静态分析并在报告导航器中显示发现的所有问题。
根据 Clang 静态分析器的提示,我们可以修改代码来修复这些潜在的问题:
1 |
|
在修复后的代码中:
- 我们在使用 malloc 后检查了是否分配成功。
- 在 risky_function 中添加了一个检查,以确保 ptr 不为 NULL。
Clang 静态分析器可以集成到持续集成(CI)系统中,自动分析代码库中的潜在问题。一个简单的方式是将 clang –analyze 命令添加到你的 CI 脚本中,这样每次提交代码时都会自动运行分析。
自定义
Clang 静态分析器允许你自定义分析器,以便检测特定的代码模式或遵循自定义规则。你可以通过编写 Clang 插件或直接修改 Clang 源代码来实现自定义分析。
自定义检查器是 Clang 静态分析器的一个模块,用于分析特定的代码模式。你可以通过创建一个新的 C++ 类并继承自 Checker 基类来实现它。
1 |
|
将上述代码保存为 NullDerefChecker.cpp,然后编译它:
1 | clang++ -shared -fPIC -o NullDerefChecker.so NullDerefChecker.cpp \ |
生成的 NullDerefChecker.so 是一个共享库,可以在运行 Clang 静态分析时加载这个自定义检查器。
使用 clang 命令运行你的自定义检查器:
1 | clang --analyze -Xclang -load -Xclang ./NullDerefChecker.so -Xclang -analyzer-checker=example.NullDerefChecker example.c |
这里 example.c 是要分析的代码文件。自定义检查器将会在分析时查找空指针解引用的情况,并报告任何发现的问题。
上面的代码实现了一个简单的检查器 NullDerefChecker,它会检测所有的指针解引用操作,并检查该指针是否为空或未定义。如果发现潜在的空指针解引用,它将报告一个错误。
- checkPreStmt 方法:在每个解引用操作之前调用,检查指针是否为空。
- reportBug 方法:当检测到潜在问题时,生成并报告一个错误。
你可以扩展自定义检查器来支持更多类型的检查,或使用其他 Clang API 来分析更复杂的代码结构。例如,可以在 checkPostCall 中检查函数调用后的返回值,或者在 checkEndAnalysis 中对整个代码路径进行总结分析。
代码规范
代码规范指南
一些常见的 iOS 代码规范及其参考资料:
- Swift 官方文档:包含 Swift 语言的详细说明和规范。
- Google Swift Style Guide:Google 提供的 Swift 代码风格指南。
- SwiftLint:一个用于强制执行 Swift 代码风格和规范的工具。
- SwiftFormat:一个用于自动格式化 Swift 代码的工具。
- NSHipster:一个涵盖 iOS 和 macOS 编程的博客,提供了许多有用的技巧和最佳实践。
Code Review 的重要性
- 提高代码质量:通过复查代码,能够及时发现并修复潜在的问题,如逻辑错误、性能瓶颈、安全漏洞等。
- 促进知识共享:高手可以通过审查新手的代码,传授好的编程实践和经验,帮助新手快速成长。同时,审查过程也是互相学习的过程,有助于团队成员之间的知识传递。
- 保证团队规范的执行:每个团队都有自己的代码规范和开发规范,通过代码审查可以确保这些规范得到执行,避免代码中出现不规范的情况。
- 减少技术债务:在代码合并前进行审查,可以及时发现并修复问题,避免问题在后续开发中被放大,从而减少技术债务。
Code Review 的最佳实践
- 作为开发流程的必选项:将Code Review作为开发流程的一个必要环节,确保每次代码合并前都经过审查。这样可以保证审查的代码量适中,减轻审查者的压力,同时也使被审查者更愿意积极修改问题。
- 形成开发文化:让Code Review成为团队的一种文化,而不是仅仅作为一种制度来执行。这需要团队成员从心底接受并认真执行Code Review,同时管理者也需要通过激励和表率作用来推动这一文化的形成。
- 使用合适的工具:现在许多源代码管理工具都自带Code Review工具,如Github、Gitlab、Azure DevOps等。这些工具提供了丰富的功能,如线上讨论、行级别的注释、版本历史等,有助于更高效地进行Code Review。
- 做好设计审查:在开发新功能之前,建议先写一个简单的设计文档,并找资深的团队成员进行设计审查。这样可以确保设计上的合理性,减少在Code Review时出现的问题。
- 提交前自我审查:开发人员在提交代码进行Code Review之前,应该先进行自我审查,确保代码的基本质量。这包括编写必要的自动化测试代码、运行测试用例等。
- 小范围提交:在提交Pull Request(PR)时,尽量保持小范围的修改。如果改动较大,可以分批提交,以减轻审查者的压力。
- 明确审查标准:制定明确的审查标准,如代码风格、注释规范、性能要求等。这有助于审查者更准确地评估代码质量,并给出有针对性的反馈。
- 分级评论:对Review的评论进行分级,如[blocker](必须修改)、[optional](可改可不改)、[question](需要澄清)等。这有助于被审查者更直观地了解Review结果,并提高Review效率。
应重点关注的方面
- 代码的整体结构是否合理?模块化是否清晰?
- 代码是否正确地实现了预期功能?是否存在逻辑错误?
- 代码是否可以进一步简化?是否有重复代码?
- 代码是否有充分的测试覆盖?是否有边界条件的测试?
- 命名是否清晰且符合团队风格?是否有注释?
Code Review 遵循的原则
遵循 KISS 原则(Keep It Simple, Stupid!)
简单是编码的最高境界。确保你的代码简洁明了,易于理解和维护。
示例
1 | // 示例:一个简单的用户模型和用户视图 |
避免重复代码
检查并删除重复的代码段,使用函数、类、结构体或枚举来封装重复逻辑。
1 | // 避免在每个视图中重复写用户信息展示逻辑 |
保持函数职责单一
确保每个函数只负责一项功能,这有助于代码的可读性和可维护性。
1 | // 假设有一个函数处理用户登录 |
优化错误处理和日志记录
确保代码中有适当的错误处理和日志记录,以便在出现问题时能够快速定位和解决。
1 | // 假设的日志记录函数 |
代码审查和反馈
在团队中进行定期的代码审查,分享最佳实践,指出潜在的问题,并促进团队整体代码质量的提升。
实际操作:
- 使用 GitHub Pull Requests, GitLab Merge Requests 或其他代码托管平台的审查功能。
- 在审查过程中,注重代码的可读性、可维护性、性能优化等方面。
- 提供具体的改进建议和反馈,而不是简单的“这个不好”。
度量与改进
项目提测后第一时间进行 Code Review。小模块随时review,项目代码面对面投屏。关注代码设计、总体流程、关键设计、重点功能等。遵循checklist。
改进
- 跟踪Code Review结果的执行,必要时加todo指定时间和人员修改。
- 定期回顾和总结,更新checklist和代码规范。
- 发现好的代码和设计,定期展示并给予奖励。
Code Review的注意事项
- 保持友好沟通:在Code Review过程中,应保持友好和尊重的沟通态度。避免使用负面词汇和攻击性语言,以免对团队成员的士气造成负面影响。
- 及时反馈:审查者应及时给出反馈意见,并尽量在代码提交后的短时间内完成审查。这有助于被审查者及时修改问题,并保持开发进度。
- 重视代码可读性:代码的可读性对于维护性和扩展性至关重要。在Code Review时,应特别关注代码的可读性,如命名规范、注释清晰等。
- 考虑性能和安全:除了关注代码的基本逻辑和可读性外,还应考虑代码的性能和安全性。审查者应评估代码的性能瓶颈和潜在的安全漏洞,并给出相应的改进建议。
Pre-Commit Hooks
实现代码评审(Code Review)与 pre-commit 钩子的结合,可以在代码提交前自动执行代码评审,确保代码质量。
- 安装 Pre-Commit: 首先需要在项目中安装 pre-commit,可以通过 pip 或其他包管理器安装。
1 | pip install pre-commit |
- 配置 Pre-Commit: 在项目根目录创建 .pre-commit-config.yaml 文件,定义需要运行的钩子。例如:
1 | repos: |
这个配置文件定义了几个常用的钩子,比如去除多余空格、确保文件末尾有换行符以及代码格式化。
- 安装 Pre-Commit 钩子: 运行以下命令来安装预定义的钩子到 Git 仓库中。
1 | pre-commit install |
- 自定义 Shell 脚本钩子: 如果需要自定义一些特定的代码检查,可以编写 shell 脚本作为钩子。例如,检查代码中是否存在 TODO 注释:
1 | #!/bin/bash |
然后在 .pre-commit-config.yaml 中添加这个钩子:
1 | - repo: local |
代码评审(Code Review)流程中的集成
- 团队约定: 在团队中约定每个开发者都必须启用 pre-commit 钩子,确保提交的代码符合项目的代码规范,并经过基本检查。
- 持续集成(CI)支持: 在 CI 流程中,也可以使用相同的 pre-commit 配置,确保所有提交和合并请求都经过统一的检查。可以在 CI 管道中运行 pre-commit run –all-files 来检查所有代码。
假设我们希望在 Swift 项目中检查代码格式(使用 SwiftFormat)、静态分析(使用 SwiftLint),以及确保没有 TODO 注释,可以这样配置:
1 | repos: |
扩展阅读
《编写可读代码的艺术》、《重构》、《重构与模式》、《代码精进之路》以及Google Engineering Practices Documentation等。
Language Server Protocol
介绍
Language Server Protocol (LSP) 是一种协议,用于在代码编辑器和语言服务器之间交换信息。LSP 由 Microsoft 提出的,旨在使不同的编辑器能够与各种编程语言的语言服务器进行交互,以提供统一的代码编辑功能。LSP 的核心思想是将语言特定的功能(如语法检查、代码补全、跳转到定义等)封装在语言服务器中,而将编辑器的功能(如用户界面)与语言服务解耦。LSP 定义了一组标准化的协议和消息格式,使得语言服务器和编辑器可以通过这些协议进行通信。
主要组件
- 语言服务器:提供语言特定的服务,如语法检查、代码补全、错误提示等。一个语言服务器可以服务于多个编辑器。
- 客户端(编辑器):实现 LSP 协议,并通过协议与语言服务器进行通信。常见的编辑器如 VS Code、Sublime Text、Atom 等都可以作为 LSP 客户端。
LSP 协议包括以下主要部分:
- 初始化:客户端和服务器建立连接时,进行初始化协商。
- 文本同步:客户端通知服务器文件内容的变化,包括文件的创建、修改和删除。
- 代码补全:客户端请求语言服务器提供代码补全建议。
- 错误检查:服务器返回代码中的错误和警告信息。
- 代码导航:支持跳转到定义、查找引用等功能。
- 文档注释:提供关于符号的详细信息和文档注释。
LSP 的优点
- 统一接口:为不同编辑器和 IDE 提供一致的语言服务接口,简化了语言服务的实现和维护。
- 提升效率:减少了编辑器和语言服务之间的重复工作,提高了开发效率。
- 增强兼容性:支持多种编辑器和 IDE,使得语言服务可以跨平台使用。
sourcekit-lsp
sourcekit-lsp 是一个开源项目,由 Swift 团队开发,用于为 Swift 代码提供语言服务。它基于 Language Server Protocol (LSP),提供了一组功能来支持编辑器和 IDE 中的代码智能化。
sourcekit-lsp 提供了以下主要功能:
- 代码补全:在编辑代码时提供智能提示和自动补全建议。
- 语法高亮:对 Swift 代码进行语法高亮显示。
- 错误检查:实时显示代码中的语法错误和警告。
- 代码导航:支持跳转到定义、查看符号、查找所有引用等。
- 代码重构:支持重命名符号等基本的代码重构操作。
- 文档注释:显示函数、方法、变量等的文档注释信息。
在 Swift 5.1 及更高版本中,sourcekit-lsp 已经包含在 Swift 工具链中,可以通过以下命令安装:
1 | swift package update |
如果你使用的是 macOS,可以通过 Homebrew 安装 sourcekit-lsp:
1 | brew install sourcekit-lsp |
sourcekit-lsp 需要配置到你的代码编辑器中。支持的编辑器包括 Visual Studio Code、Sublime Text、Atom 等。在 Visual Studio Code 中,打开扩展市场(Extensions),搜索并安装 “Swift” 扩展,这个扩展通常会自动安装 sourcekit-lsp。在 VS Code 的设置中,你可以配置 sourcekit-lsp 的路径。如果你通过 Homebrew 安装了 sourcekit-lsp,通常它会自动配置好。
在 VS Code 中打开你的 Swift 项目。编辑器会自动启动 sourcekit-lsp,并在后台提供语言服务。你可以在编辑器的状态栏中看到相关的信息。