代码写多了,难免有些"代码债"。变量命名不规范、代码格式乱七八糟、潜在bug没发现……这些问题日积月累会让代码越来越难维护。代码质量检查工具能帮我们自动化地发现问题、保持代码整洁。

一、为什么要做代码质量检查

  1. 发现问题:静态检查能发现潜在bug
  2. 统一风格:团队代码风格一致
  3. 提高可读性:规范的代码更容易理解
  4. 减少review负担:工具能发现的不用人肉找

二、基础工具:Flake8

Flake8是最常用的Python代码检查工具,集合了pyflakes、pycodestyle、mccabe。

# 安装
pip install flake8

# 基本使用
flake8 your_script.py

# 检查整个项目
flake8 .

# 忽略特定规则
flake8 --ignore=E501,W503 your_script.py

# 输出详细格式
flake8 --show-source --statistics your_script.py

配置.flake8文件:

[flake8]
max-line-length = 120
exclude = 
    .git,
    __pycache__,
    build,
    dist,
    *.egg-info
ignore = 
    E203,  # whitespace before ':'
    W503,  # line break before binary operator
per-file-ignores =
    __init__.py:F401,F403
max-complexity = 10

三、格式化工具:Black

Black是"固执己见"的格式化工具,它决定了代码风格,你不用操心。

# 安装
pip install black

# 格式化单个文件
black your_script.py

# 格式化整个项目
black .

# 检查是否需要格式化(不实际修改)
black --check your_script.py

# 查看会做什么改动
black --diff your_script.py

配合pre-commit使用:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.1.0
    hooks:
      - id: black
        language_version: python3

四、导入排序:isort

统一import语句的顺序。

# 安装
pip install isort

# 检查import顺序
isort --check-only your_script.py

# 自动排序
isort your_script.py

# 与black配合
isort --profile black your_script.py

配置pyproject.toml:

[tool.isort]
profile = "black"
line_length = 120
skip_gitignore = true

五、类型检查:mypy

Python动态类型虽灵活,但也容易出错。mypy帮你做静态类型检查。

# 示例:有类型注解的代码
def process_data(data: list[dict]) -> dict:
    result = {}
    for item in data:
        key = item.get('id')
        value = item.get('value', 0)
        if key:
            result[key] = value
    return result
# 安装
pip install mypy

# 运行检查
mypy your_script.py

# 严格模式
mypy --strict your_script.py

# 忽略缺失的类型
mypy --ignore-missing-imports your_script.py

六、综合工具:pylint

pylint是最全面的代码检查工具,检查项非常多。

# 安装
pip install pylint

# 检查
pylint your_script.py

# 输出评估分数
pylint --output-format=text your_script.py | tail -5

# 生成报告
pylint --reports=y your_script.py

配置.pylintrc:

[MESSAGES CONTROL]
disable=C0111,  # missing-docstring
        C0103   # invalid-name

[FORMAT]
max-line-length=120

[DESIGN]
max-args=8
max-locals=20

七、自动化检查脚本

写一个一键检查脚本:

#!/usr/bin/env python3
"""
代码质量检查脚本
"""
import subprocess
import sys
from pathlib import Path
from dataclasses import dataclass

@dataclass
class CheckResult:
    tool: str
    passed: bool
    message: str = ""

class CodeQualityChecker:
    def __init__(self, target='.'):
        self.target = Path(target)
        self.results = []
    
    def run_command(self, cmd, description):
        """运行命令并返回结果"""
        try:
            result = subprocess.run(
                cmd, shell=True, capture_output=True, text=True
            )
            passed = result.returncode == 0
            message = result.stdout.strip() if result.stdout else result.stderr.strip()
            return CheckResult(description, passed, message)
        except Exception as e:
            return CheckResult(description, False, str(e))
    
    def check_flake8(self):
        """Flake8检查"""
        result = self.run_command(
            f'flake8 {self.target} --max-line-length=120 --extend-ignore=E203,W503',
            "Flake8"
        )
        if result.passed:
            result.message = "✓ 检查通过"
        else:
            # 只显示错误摘要
            lines = result.message.split('\n')[:10]  # 只显示前10条
            result.message = '\n'.join(lines)
        return result
    
    def check_black(self):
        """Black格式检查"""
        result = self.run_command(
            f'black --check {self.target}',
            "Black"
        )
        if result.passed:
            result.message = "✓ 格式正确"
        else:
            result.message = "需要格式化"
        return result
    
    def check_isort(self):
        """isort导入排序检查"""
        result = self.run_command(
            f'isort --check-only --diff {self.target}',
            "isort"
        )
        if result.passed:
            result.message = "✓ 导入顺序正确"
        else:
            result.message = "导入需要排序"
        return result
    
    def check_mypy(self):
        """mypy类型检查"""
        py_files = list(self.target.rglob('*.py'))
        if not py_files:
            return CheckResult("mypy", True, "无Python文件")
        
        py_files_str = ' '.join(str(f) for f in py_files)
        result = self.run_command(
            f'mypy {py_files_str} --ignore-missing-imports',
            "mypy"
        )
        if result.passed:
            result.message = "✓ 类型检查通过"
        return result
    
    def check_pylint(self):
        """pylint检查(只检查主要模块)"""
        main_files = list(self.target.glob('*.py'))
        if not main_files:
            return CheckResult("pylint", True, "无主文件")
        
        for f in main_files:
            if f.name not in ['setup.py', '__main__.py']:
                result = self.run_command(
                    f'pylint --disable=C,R {f}',
                    "pylint"
                )
                # pylint分数10分满分,太严格了,改成警告
                if not result.passed and 'error' in result.message.lower():
                    return result
        
        return CheckResult("pylint", True, "✓ 检查通过")
    
    def auto_fix(self):
        """自动修复可修复的问题"""
        print("执行自动修复...\n")
        
        # isort
        print("1. 排序导入...")
        subprocess.run(f'isort {self.target}', shell=True)
        
        # black
        print("2. 格式化代码...")
        subprocess.run(f'black {self.target}', shell=True)
        
        print("\n自动修复完成!")
    
    def run_all(self):
        """运行所有检查"""
        checks = [
            self.check_flake8,
            self.check_black,
            self.check_isort,
            self.check_mypy,
        ]
        
        print("=" * 60)
        print("开始代码质量检查")
        print("=" * 60 + "\n")
        
        all_passed = True
        for check in checks:
            result = check()
            self.results.append(result)
            status = "✓ PASS" if result.passed else "✗ FAIL"
            print(f"{status} | {result.tool}")
            if result.message:
                print(f"    {result.message}")
            if not result.passed:
                all_passed = False
            print()
        
        print("=" * 60)
        if all_passed:
            print("✓ 所有检查通过!")
        else:
            failed = [r.tool for r in self.results if not r.passed]
            print(f"✗ 以下检查未通过: {', '.join(failed)}")
            print("\n可以尝试自动修复: python quality_check.py --fix")
        print("=" * 60)
        
        return all_passed

if __name__ == '__main__':
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('--target', default='.', help='检查目标目录')
    parser.add_argument('--fix', action='store_true', help='自动修复')
    args = parser.parse_args()
    
    checker = CodeQualityChecker(args.target)
    
    if args.fix:
        checker.auto_fix()
    else:
        success = checker.run_all()
        sys.exit(0 if success else 1)

八、Git钩子:pre-commit

配置Git钩子,提交前自动检查:

# 安装pre-commit
pip install pre-commit

# 创建配置
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

  - repo: https://github.com/psf/black
    rev: 23.1.0
    hooks:
      - id: black

  - repo: https://github.com/pycqa/isort
    rev: 5.12.0
    hooks:
      - id: isort
        args: ['--profile', 'black']

  - repo: https://github.com/PyCQA/flake8
    rev: 6.0.0
    hooks:
      - id: flake8
        args: ['--max-line-length=120', '--extend-ignore=E203,W503']
# 安装钩子
pre-commit install

# 手动运行
pre-commit run --all-files

九、CI/CD集成

GitHub Actions中集成检查:

# .github/workflows/quality.yml
name: Code Quality

on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      
      - name: Install tools
        run: |
          pip install flake8 black isort mypy
          pip install -e .
      
      - name: Run quality checks
        run: |
          flake8 . --max-line-length=120 --extend-ignore=E203,W503
          black --check .
          isort --check-only --profile black .
          mypy . --ignore-missing-imports

总结

代码质量工具推荐组合:

工具 用途 必要性
Black 代码格式化 必装
isort 导入排序 必装
Flake8 基础检查 必装
mypy 类型检查 推荐
pre-commit Git钩子 推荐
pylint 深度检查 可选

建议:

  1. 新项目:从一开始就配置好这些工具
  2. 老项目:逐步添加检查,先忽略已有问题
  3. 团队:统一配置,提交前必须通过检查
  4. CI/CD:把检查集成到流水线中

好的工具配合好的习惯,代码质量想不好都难。

更多推荐