Skip to content

十三、开发大型项目

常用代码风格检查工具

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

不要掉进完美主义的陷阱。

写代码不是什么纯粹的艺术创作,完美的代码是不存在的。有时,代码只要能满足当前需求,又为未来扩展留了空间就足够了。