十三、开发大型项目¶
常用代码风格检查工具¶
flake8¶
flake8 作为代码 Linter 工具,可以检验代码是否遵循 PEP8 规范,保持项目风格统一。
- flake8 的 PEP8 检查功能,是由集成在 flake8 内的另一个 Linter 工具 —— pycodestyle 实现的。
- flake8 还集成了另一个重要的 Linter —— pyflakes,它更专注于检查代码的正确性,比如语法错误、变量名未定义等等。
- flake8 为每类错误都定义了不同的错误代码,如 F401、E111 等,首字母代表了不同的错误来源。
- mccabe 模块集成在 flake8 里面,可以扫描代码的圈复杂度。
Tips
$ flake8 --max-complexity 8 flak8_error.py
flake8_error.py:5:1 C901 'complex_func' is too complex (12)
--max-complexity
参数可以修改允许的最大圈复杂度,建议该值不要超过 10。
- flake8 支持用户自定义插件,可参考官网教程,可学习下 wemake-python-styleguide 这个插件,非常严格。
isort¶
isort 工具,可用来整理 import 语句。
Tips
PEP8 关于 import 语句的建议: 1. 导入 Python 标准库的 import 语句; 2. 导入相关联的第三方包的 import 语句; 3. 与当前应用(或当前库)相关的 import 语句
其中,不同的 import 语句组之间,应该用空格分开。
源码文件:isort_demo.py
import os
import requests
import myweb.models
from myweb.views import menu
from urllib import parse
import django
执行 isort isort_demo.py
之后,import 语句会被排列整齐:
- 第一部分:标准库包
- 第二部分:第三方包
- 第三部分:本地包
import os
from urllib import parse
import django
import requests
import myweb.models
from myweb.views import menu
black¶
black 是一款更为激进的代码格式化工具。
black 的最大特点,在于其不可配置。它能让开发者不需要在各种编码风格之间纠结,整体来看,大型项目中引入 black,利大于弊。
pre-commit¶
pre-commit 是一个基于钩子功能开发的工具,专门用于预提交阶段。
如要最大限度地发挥工具地能力,必须让其融入到所有人地开发流程里。
但是,统一 IDE 不现实,不过对版本控制工具—— git 可以进行配置,git 有个特殊的钩子功能,它允许你给每个仓库配置一些钩子程序(hook)。
配置文件 —— pre-commit-config.yaml
fail_fast: true
repos:
- repo: https://github.com/timothycrosley/isort
rev: 5.7.0
hooks:
- id: isort
additional_dependencies: [toml]
- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
- id: black
args: [--config=./pyproject.toml]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: flake8
如此一来,每次 git commit、git push 时,都会需要触发定义的 isort、black、flake8 三个插件,完成代码检查及格式化工作,否则就会提交中断。
mypy¶
mypy 是一款静态类型检查工具。
Python 是一门动态类型语言。它让我们不必声明每个变量的类型,不用关心太多类型问题,只专注于用代码实现功能就好。但现实情况是,我们写的程序里的许多bug和类型系统息息相关。
Tips
在大型项目中,类型注解与mypy的组合能大大提升项目代码的可读性与正确性。
mypy 让动态类型的 Python 拥有了部分静态类型语言才有的能力,值得在大型项目中推广使用。
单元测试¶
根据关注点的不同,自动化测试可分为不同的类型,比如UI测试、集成测试、单元测试等。不同类型的测试,各自关注着不同的领域,覆盖了不一样的场景。
在所有测试中,单元测试数量最多、测试成本最低,是整个自动化测试的基础和重中之重。
unittest¶
pytest¶
有关单元测试的建议¶
写单元测试不是浪费时间¶
缺少单元测试的帮助,你需要耐心找到改动可能会影响到的每个模块,手动验证它们是否正常工作。所有这些事所花费的时间,足够你写好几十遍单元测试。
在项目上线新功能,和对项目做大规模重构时,尤其能体现单元测试的价值。
不要总想着“补”测试¶
很多开发者都是把单元测试,当成一种验证正确性的事后工具,但实际上,单元测试不仅能验证程序的正确性,也能极大帮助改进代码设计。不过前提是必须在编写代码的同时,编写单元测试。
当开发功能与编写测试同步进行时,你会来回切换自己的角色,分别作为代码的设计者和使用者,不断从代码里找出问题,调整设计。
TDD(test-driven development,测试驱动开发)¶
TDD的工作流大致如下:
- (1)写测试用例(哪怕测试用例引用的模块根本不存在);
- (2)执行测试用例,让其失败;
- (3)编写最简单的代码(此时只关心实现功能,不关心代码整洁度);
- (4)执行测试用例,让测试通过;
- (5)重构代码,删除重复内容,让代码变得更整洁;
- (6)执行测试用例,验证重构;
- (7)重复整个过程。
实际开发中,其实并不一定要严格遵循 TDD 标准流程,了解其理念,并找到最适合自己的开发流程即可。
难测试的代码就是烂代码¶
Tips
当模块依赖了一个全局对象时,写单元测试就会变得很难。全局对象的基本特征决定了它在内存中永远只会存在一份。而在编写单元测试时,为了验证代码在不同场景下的行为,我们需要用到多份不同的全局对象。这时,全局对象的唯一性就会成为写测试最大的阻碍。
单元测试是评估代码质量的标尺。每当你发现很难为代码编写测试时,就应该意识到代码设计可能存在问题,需要努力调整设计,让代码变得更容易测试。
像应用代码一样对待测试代码¶
在大部分人看来,测试代码更像是代码世界里的“二等公民”。人们很少关心测试代码的执行效率,也很少会想办法提升它的质量。但这样其实是不对的。
你应该像学习项目 Web 开发框架一样,深入学习测试框架,而不只是每天重复使用测试框架最简单的功能。只有在了解工具后,你才能写出更好的测试代码。
在开发项目时,所有人能更快、更频繁地从测试中获得反馈,写代码的节奏才会变得更好。
避免教条主义¶
单元测试里的单元(unit)其实并不严格地指某个方法、函数,其实指的是软件模块的一个行为单元,或者说功能单元。
所有的自动化测试只要能满足几条基本特征:快、用例间互相隔离、没有副作用,这样就够了。
总结¶
开发大型的 Python 项目,请使用 Linter 工具,编写规范的单元测试。
Tips
要开发一个成功的大型项目(此处仅指工程意义上的成功,非商业意义上),不仅仅需要做好 Linter 工具和单元测试,还需要注重与团队成员间的沟通,积极推行代码审查,营造更好的合作氛围,等等。
建议除了本章内容之外,继续深入学习一些敏捷编程、领域驱动设计、整洁架构等方面的内容。这些知识对于大型项目开发有着很好的启发作用。
Tips
不要掉进完美主义的陷阱。
写代码不是什么纯粹的艺术创作,完美的代码是不存在的。有时,代码只要能满足当前需求,又为未来扩展留了空间就足够了。