1、一场关于"大括号放哪"的战争

团队里两个人吵了半天:大括号是 if (x) { 还是 if (x)\n{。争论消耗的时间、产生的情绪,远超这件事本身的价值。

风格问题不该由人吵,该由工具定。 这就是编码规范(Coding Standard)与静态检查(Static Analysis)要解决的问题。

本课解决三件事:

  1. 编码规范到底规范什么?(不只是风格)
  2. 怎么用自动化工具强制执行,而不是靠自觉?
  3. 静态检查能帮我们在运行前抓出哪些 Bug?

2、编码规范规范什么:四个层次

层次内容例子
格式(Formatting)空格、缩进、换行、行宽2 空格还是 4 空格;行宽 100
命名(Naming)变量/函数/类的命名风格snake_case vs camelCase
结构(Structure)文件组织、函数长度、模块边界一个函数不超过 50 行
实践(Practice)禁止的做法、必须的做法禁止裸 except;必须处理错误

前两层(格式/命名)应该 100% 交给工具自动处理——人对这些没有讨论价值。
后两层(结构/实践)才需要人做决策,但也能用 Linter 兜底提醒。

一个反常识的建议:与其争论"哪种风格更好",不如随便选一种(或直接用社区标准,如 PEP 8、Google Style),然后用工具锁定,全团队统一。统一的普通标准,胜过不统一的最优标准。

3、工具链:格式化器 + Linter + 类型检查

三类工具各司其职:

① 格式化器(Formatter):只管排版,不管对错

特点:确定性的、无争议的——同一个输入永远产出同一个输出。团队不再需要为格式评审。

语言常用格式化器
Pythonblack、ruff format
JavaScript/TSprettier
Gogofmt(Go 自带,这是 Go 社区极少有格式争论的原因)
Rustrustfmt
Javagoogle-java-format

用法:配置一次,保存时自动格式化,CI 里检查"是否已格式化",未格式化直接失败。

② Linter:找"可疑的代码"

Linter 找的是"可能有问题"的模式,比格式化器聪明,但有些规则有主观性。

语言常用 Linter
Pythonruff、flake8、pylint
JavaScript/TSeslint
Gogolangci-lint
Rustclippy

Linter 能抓的典型问题:

# 未使用的变量
def calc(a, b):
    unused = a * 2      # ⚠️ unused 从未被使用
    return a + b

# 裸 except,会吞掉所有异常(包括 Ctrl+C!)
try:
    do_something()
except:                 # ⚠️ 太宽泛,应该指定异常类型
    pass

# 可变默认参数(Python 经典陷阱)
def add_item(item, bucket=[]):   # ⚠️ bucket 在所有调用间共享!
    bucket.append(item)
    return bucket

# 比较用 == 而不是 is None
if x == None:           # ⚠️ 应写 x is None
    ...

③ 类型检查(Type Checker):在运行前发现类型错误

给动态语言加上"编译期"保护,第 21 课详解。这里先知道有这类工具:

语言工具
Pythonmypy、pyright
TypeScripttsc(自带)
Java/C#编译器自带静态类型
def greet(name: str) -> str:
    return "Hello, " + name

greet(42)   # mypy 会在运行前就报错:参数类型不符

4、静态检查 vs 动态检查

静态检查动态检查(运行时)
何时写代码时 / 提交前 / CI程序运行时
看什么代码本身(不运行)实际执行行为
优点快、能在提交前拦住问题能发现真实运行才能暴露的问题
缺点可能误报、抓不到运行时逻辑错事后才发现,可能已在生产
例子Linter、类型检查单元测试、断言

两者互补,都要有:静态检查拦住"低级错误",测试(第 19 课起)验证"业务正确"。

静态检查的核心价值:把问题左移(Shift Left)。

成本
  ▲
  │                                     ● 生产环境发现
  │                          ● 测试阶段
  │              ● 代码评审
  │      ● 提交时 / IDE 实时
  │  ●
  └──────────────────────────────────────────▶ 开发进程

同一个 bug,越早发现,修复成本越低——在生产环境发现一个 bug,成本可能是写代码时发现的 100 倍。静态检查就是在最左边设卡。

5、怎么落地:三道防线

规范不能只写在 Wiki 里,要靠三道防线强制执行:

第一道:IDE 实时         保存时自动格式化 + 实时高亮
        ↓
第二道:提交前钩子       git pre-commit hook,不合格不让提交
        ↓
第三道:CI 守门           PR 上跑检查,不通过不能合并(第 16 课)

第一道:编辑器/IDE 集成

配置 VS Code / JetBrains 的格式化与提示,保存即格式化。这让开发者几乎不用手动管格式。

第二道:pre-commit 钩子

用 pre-commit 这类工具,在 git commit 前自动跑检查和格式化:

# .pre-commit-config.yaml(Python 示例)
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.0
    hooks:
      - id: ruff          # Linter
      - id: ruff-format   # 格式化
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.11.0
    hooks:
      - id: mypy          # 类型检查

效果:不合格的代码根本提交不上去——问题在离开你电脑之前就被拦住了。

第三道:CI 守门(下一阶段铺垫)

CI(第 16 课)在 PR 上再跑一遍全部检查,防止有人绕过本地钩子(git commit --no-verify)。

配置文件要进版本库(.pre-commit-config.yaml、ruff.toml、.eslintrc……),这样所有人共享同一套规则,新人克隆下来就有。

6、代码风格指南与"团队约定"

除了工具能管的,还有些团队层面的约定要写下来(放 CONTRIBUTING.md):

  • 分支命名(第 13 课)
  • 提交信息规范(第 12 课)
  • 目录结构:src/、tests/、docs/ 怎么放
  • 错误处理策略:业务错误用异常还是返回值?日志在哪一层打?
  • 依赖引入规则:加新依赖要经过谁同意?(防止依赖失控)
  • 文档要求:公开函数要写 docstring 吗?README 维护到哪一级?

写约定的原则:

  1. 能自动化的就别写进文档(写在文档里的规则会被遗忘,写在工具里才生效)。
  2. 约定要短:一份读 5 分钟能读完、2 页以内的 CONTRIBUTING.md,比 20 页没人看的手册强。
  3. 定期复查:不合时宜的规则要及时删。

7、动手实验 🎯

任务:给你自己的项目装上三道防线。

  1. 选一个你熟悉的语言,配好格式化器(如 Python 用 ruff format,JS 用 prettier),跑一遍项目,观察会改动哪些文件。
  2. 配好 Linter,修复它报的至少 3 个问题(区分哪些是真 bug、哪些只是风格)。
  3. 写一段故意含有 3 个典型陷阱的代码(如可变默认参数、裸 except、未使用变量),用 Linter 跑出来,记录每条告警。
  4. 配置 pre-commit 钩子,让"未格式化/有告警"的代码提交失败,然后验证它确实拦住了你。
  5. 写一份不超过 2 页的 CONTRIBUTING.md,包含:分支命名、提交规范、如何跑格式化和检查、目录结构、错误处理约定。
  6. 画一遍你项目的"问题左移"流程,标出你的三道防线分别在哪一步拦截问题。

做完这步,"风格之争"就彻底交给机器了——下一课进入开发方法论的核心:测试驱动开发(TDD)。

8、今天你带走的三句话

  1. 格式和命名交给工具,人只管结构和实践;统一的中等标准 > 不统一的最优标准。
  2. 静态检查的价值是把问题左移:IDE → 提交钩子 → CI 三道防线,越早拦截越便宜。
  3. 能自动化的规则就别写进文档,写进工具的规则才会真正生效。

下一课进入测试驱动开发(TDD)——一种"先写测试、再写代码"的开发节奏。

标签: 软件工程, 项目管理, 开发流程

添加新评论