☰
代码审查、重构与提交前检查:小项目开发流程实战指南
2026/10/11 8:08:52 网站建设 项目流程

1. 为什么代码审查、重构和提交前检查要放在一起做

很多人把代码审查、重构和提交前检查当成三件独立的事:审查是审查,重构是重构,提交前检查就是跑一下测试。我一开始也这么想,直到在一个小项目上连续踩了几次坑,才发现这三件事本质上是一条流水线上的三个工位,拆开做就会出问题。

先说一个我亲身经历的教训。当时我在做一个数据同步的小工具,功能不复杂,大概两千行代码。写完第一版之后,我觉得代码有点乱,就花了一个下午做重构,把几个大类拆成了小模块,方法名也改得更清晰了。重构完之后我跑了一下主流程,没问题,就提交了。结果第二天同事拉下代码一跑,发现配置文件读取的路径变了,因为我在重构的时候顺手把配置加载的逻辑挪到了另一个模块里,但忘了同步更新默认路径的拼接方式。主流程能跑通是因为我本地有一个旧的缓存文件,而同事那边是全新环境,直接就报错了。

这件事让我意识到一个问题:重构本身没有错,错的是我在重构之后没有做完整的提交前检查。而如果当时有代码审查环节,同事在看我改动的时候大概率会发现路径拼接的变化,因为审查的时候人会关注“改了什么”而不是“能不能跑”。

所以这三件事的关系是这样的:代码审查是让别人帮你发现你看不到的问题,重构是让代码变得更容易被审查和维护,提交前检查是最后一道防线,确保你的改动不会破坏别人的环境。三者缺一不可,而且顺序很重要。

还有一个常见的误区是觉得小项目不需要这些流程。恰恰相反,小项目往往是一个人或者两三个人在维护,没有专门的测试团队,没有CI/CD流水线,一旦出问题就是直接影响到实际使用。大项目有完善的工具链兜底,小项目只能靠自己的习惯和流程来兜底。

这篇文章我会围绕一个模拟的小项目场景,把代码审查、重构和提交前检查这三个环节拆开来讲,每个环节都会说清楚“为什么这么做”“具体怎么做”“我踩过哪些坑”。适合有一定编程基础、正在独立开发或者在小团队里协作的开发者参考。不管你是写Python、JavaScript还是Java,思路是通用的,具体工具可以根据自己的技术栈替换。

2. 代码审查:小项目里怎么做才不流于形式

2.1 审查的时机比审查的内容更重要

很多人觉得代码审查就是写完代码之后找人看一眼,但其实审查的时机决定了审查的效果。我试过两种方式:一种是写完整个功能之后再审查,另一种是每完成一个小模块就审查一次。实测下来,后者的效果要好得多。

原因很简单:当你写完整个功能再审查的时候,改动可能涉及十几个文件、上千行代码,审查的人看到这么大的diff,心理上就已经疲惫了,很容易只扫一眼就点通过。而且这个时候如果发现设计上的问题,改动的成本已经很高了,因为很多代码已经基于错误的设计写完了。

小模块审查的好处是每次改动控制在两三百行以内,审查的人有精力逐行看,而且发现问题的时候改动成本低。我一般的做法是:一个功能拆成三到四个小模块,每个模块完成后就提交一次审查请求,审查通过后再继续下一个模块。

具体操作上,如果你用的是Git,可以这样做:

# 创建一个功能分支 git checkout -b feature/data-sync # 完成第一个小模块后提交 git add module_a.py git commit -m "feat: 实现数据读取模块" # 推送并创建审查请求 git push origin feature/data-sync

然后在代码托管平台上创建一个审查请求,指定审查人。审查人看完之后如果有意见就评论,你修改后再推送,直到审查通过。

注意:不要在一个审查请求里混合多个不相关的改动。比如你既改了数据读取逻辑,又改了日志格式,还顺手升级了一个依赖,这三件事应该分成三个审查请求。混在一起会让审查人很难判断哪些改动是相关的,哪些是顺带的。

2.2 审查清单:小项目里最值得关注的五个点

大公司通常有很详细的代码审查清单,几十条规则,但对于小项目来说,逐条对照根本不现实。我根据自己的经验总结了一个精简版清单,只有五个点,但覆盖了大部分常见问题。

第一,看接口设计是否合理。这是最重要的一点。接口包括函数签名、类的方法、模块之间的调用方式。审查的时候先看接口,因为接口一旦定下来,后面所有代码都要围绕它来写,改起来成本最高。具体看什么呢?看参数是不是太多了(超过四个就要考虑封装成对象),看返回值是不是清晰(不要返回一个含义不明的字典),看方法名是不是能准确表达意图。

第二,看边界条件有没有处理。这是小项目最容易出问题的地方。比如读取文件的时候文件不存在怎么办,网络请求超时怎么办,输入参数为空怎么办。审查的时候重点看这些地方有没有做判断,判断的逻辑对不对。

第三,看有没有硬编码。小项目里硬编码特别常见,因为方便。但硬编码的路径、端口号、密钥一旦需要修改,就会很麻烦。审查的时候看到硬编码就要提出来,至少改成配置项。

第四,看错误处理是否合理。不是说要每个地方都try-catch,而是说错误发生的时候,程序的行为是不是可预期的。比如一个函数出错的时候是返回None、抛异常还是返回错误码,整个项目应该统一。

第五,看有没有明显的性能问题。小项目一般性能要求不高,但有些写法会埋下隐患。比如在循环里反复读文件、反复建立数据库连接、反复做重复计算。这些在数据量小的时候看不出来,数据量一大就出问题。

这五个点看起来简单,但实际审查的时候能全部覆盖到就已经很不错了。我自己的习惯是在审查请求的描述里把这五个点列出来,审查人逐条确认,这样不容易遗漏。

2.3 审查意见怎么提才不伤和气又有效

代码审查有一个很微妙的点:怎么提意见。提得太直接,对方可能觉得你在挑刺;提得太委婉,对方可能get不到你的意思。我试过几种方式,最后总结出一个原则:对事不对人,说清楚为什么。

举个例子,你看到一段代码是这样的:

def get_user_data(user_id): conn = sqlite3.connect('data.db') cursor = conn.cursor() cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") return cursor.fetchone()

如果你只说“这里有问题”,对方可能不知道你说的是什么问题。如果你说“这里应该用参数化查询”,对方可能知道要改但不知道为什么要改。比较好的提法是:

这里用f-string拼接SQL会有注入风险,如果user_id是外部传入的,攻击者可以构造恶意输入。建议改成参数化查询:cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))。另外连接用完记得关闭,或者用with语句管理。

这样说既指出了问题,又解释了原因,还给出了修改建议。对方看到之后不仅知道要改,还知道为什么要改,下次写代码的时候就会注意。

还有一个技巧是:把“你”换成“这里”或者“这段代码”。不要说“你没有处理异常”,而要说“这段代码没有处理异常”。虽然意思一样,但后者听起来更像是在讨论代码本身,而不是在指责写代码的人。

2.4 审查通过之后别急着合并

审查通过之后,很多人就直接合并到主分支了。我建议再等一步:让审查人确认一下你的修改。因为审查意见提出来之后,你修改了代码,但审查人可能没有再看一遍。如果修改的时候引入了新的问题,直接合并就会把问题带到主分支。

正确的做法是:修改完之后再推送一次,在审查请求里回复“已修改,请再看一下”。审查人确认没问题之后再合并。这一步多花几分钟,但能避免很多返工。

3. 重构:什么时候该动手,什么时候该忍住

3.1 重构的信号:这五种情况说明代码该整理了

重构不是什么时候都能做的,也不是什么时候都需要做的。我总结了一下,出现下面这五种情况的时候,重构的收益比较大。

第一种,同一个逻辑在三个以上的地方出现。比如你发现有三个函数都在做类似的数据校验,那就可以考虑抽成一个公共函数。注意是三个以上,两个地方重复可能只是巧合,三个以上就说明确实有共性的需求。

第二种,一个函数超过五十行。五十行不是绝对标准,但超过这个数通常意味着这个函数做了太多事情。我一般的做法是看这个函数能不能拆成几个步骤,每个步骤一个子函数。

第三种,修改一个功能需要改五个以上的文件。这说明模块之间的耦合太紧了,改一个地方会牵连很多其他地方。这时候需要考虑重新划分模块边界。

第四种,加一个新功能需要改很多旧代码。这说明原来的设计没有预留扩展点。比如你原来只支持一种数据格式,现在要加一种新格式,如果发现要改十几个地方,那就说明需要抽象出一个接口。

第五种,你自己看代码的时候觉得费劲。这是一个很主观但很准的信号。如果你自己写的代码过了一个月再看,需要花很长时间才能理解,那说明代码的可读性有问题,值得重构。

反过来,下面这些情况我建议先忍住不重构:代码能正常工作且最近不会改动、项目马上要交付没有时间测试、你还不完全理解这段代码的逻辑。重构的前提是你对代码的行为有充分的了解,否则很容易改出问题。

3.2 小步重构的具体手法:从改名开始

重构最怕的就是一次改太多,改完之后出了问题不知道是哪里改坏的。我的经验是:每次只做一种类型的重构,做完之后跑测试,通过之后再继续下一种。

最安全的重构是改名。把含义不清的变量名、函数名改成能表达意图的名字。比如把d改成user_data,把process改成validate_and_save。改名不会改变代码的行为,但能大幅提升可读性。

# 改名前 def proc(d): if d['t'] == 1: return d['v'] * 2 return d['v'] # 改名后 def calculate_discounted_price(product): if product['type'] == 'premium': return product['value'] * 2 return product['value']

改名的技巧是:名字要说明“是什么”而不是“怎么做”。比如calculate_discounted_price说的是这个函数做什么,而multiply_by_two说的是怎么做。前者更好,因为如果以后折扣逻辑变了,函数名不需要改。

第二种安全的重构是提取函数。把一段代码从大函数里抽出来,变成一个独立的小函数。这样做的好处是原来的大函数变短了,抽出来的小函数可以单独测试。

# 重构前 def process_order(order): # 校验 if not order.get('items'): raise ValueError('订单不能为空') if order.get('total', 0) <= 0: raise ValueError('金额必须大于零') # 保存 db.save(order) # 通知 send_email(order['user_email'], '订单已创建') # 重构后 def validate_order(order): if not order.get('items'): raise ValueError('订单不能为空') if order.get('total', 0) <= 0: raise ValueError('金额必须大于零') def process_order(order): validate_order(order) db.save(order) send_email(order['user_email'], '订单已创建')

提取函数的时候有一个判断标准:如果一段代码可以用一句话描述它在做什么,那它就可以被提取成一个函数。比如上面那段校验代码,一句话就是“校验订单”,所以提取成validate_order。

第三种重构是消除重复。当你发现同样的代码出现在多个地方,就把它抽成一个公共函数或者基类。但要注意,消除重复的前提是这些代码真的是同一个逻辑,而不是碰巧长得像。如果两个地方的代码只是看起来一样但实际含义不同,强行合并反而会导致以后改一个地方影响另一个地方。

3.3 重构之后必须做的验证:别只跑主流程

重构之后要验证,这个大家都知道。但很多人只跑一下主流程,觉得没问题就完了。我踩过的坑告诉我,重构之后的验证要覆盖边界条件和异常路径。

具体怎么做呢?我一般分三步:

第一步,跑一遍现有的单元测试。如果项目没有单元测试,那至少写几个针对被重构代码的测试。不用追求覆盖率,但要把主要的输入输出组合覆盖到。

第二步,手动测试边界条件。比如空输入、超大输入、特殊字符、并发调用。这些情况在主流程里可能不会出现,但实际使用中一定会遇到。

第三步,对比重构前后的行为。如果条件允许,可以在重构前记录一些典型输入对应的输出,重构后再跑一遍,看结果是否一致。这个方法在重构复杂逻辑的时候特别有用。

注意:重构之后如果发现行为变了,先不要急着改代码,先确认一下原来的行为是不是正确的。有时候重构会暴露出原来的bug,这时候应该先修复bug,而不是让重构后的代码去兼容错误的行为。

3.4 重构和审查的配合:先审查再重构还是反过来

这个问题我纠结过很久。后来我的做法是:小重构直接做,做完之后一起审查;大重构先审查方案,通过之后再动手。

小重构比如改名、提取函数、消除明显的重复,这些改动不会影响外部行为,直接做完之后在审查请求里说明“这是重构,没有改变行为”,审查人重点看改动是否真的没有改变行为就行。

大重构比如重新划分模块、更换设计模式、调整接口,这些改动会影响多个文件甚至整个项目的结构。这种重构应该先写一个简单的方案说明,包括为什么要重构、打算怎么改、影响范围有多大,让审查人先看方案。方案通过之后再动手,避免改到一半发现方向不对。

我见过一个反面的例子:有个开发者觉得项目的数据库访问层设计得不好,花了一周时间重写了一遍,提交审查的时候审查人问“为什么要重写”,他说“原来的不好”。审查人又问“哪里不好”,他说“不够优雅”。这种重构就是没有明确目标的,改完之后除了代码风格变了,其他方面没有任何提升,反而引入了新的bug。

4. 提交前检查:最后一道防线怎么设

4.1 自动化检查:让工具做它能做的事

提交前检查的第一层是自动化检查。这部分工作应该交给工具,不要靠人。我一般会配置三个检查:代码格式检查、静态类型检查、单元测试。

代码格式检查用来自动统一代码风格。Python用black,JavaScript用prettier,Java用checkstyle。这些工具能自动格式化代码,避免因为空格、换行、引号风格这些小事在审查的时候浪费时间。

# Python项目配置pre-commit # .pre-commit-config.yaml repos: - repo: https://github.com/psf/black rev: 23.1.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: - id: flake8

配置好之后,每次git commit的时候会自动运行这些检查,不通过就不让提交。这样能保证进入仓库的代码风格是一致的。

静态类型检查用mypy(Python)或者TypeScript(JavaScript)。类型检查能在编译阶段发现很多潜在的错误,比如传错了参数类型、调用了不存在的方法。小项目里很多人觉得类型检查麻烦,但实际用下来,它能帮你省下很多调试的时间。

单元测试是最后一道自动化防线。我建议至少覆盖核心逻辑和边界条件。不需要追求100%的覆盖率,但关键路径一定要有测试。测试写完不是就完了,每次提交前都要跑一遍,确保没有破坏已有的功能。

# 提交前手动跑一遍测试 pytest tests/ -v # 或者配置成pre-push钩子 # .git/hooks/pre-push #!/bin/sh pytest tests/ || exit 1

4.2 手动检查清单:那些工具查不到的东西

自动化工具能查语法错误、格式问题、类型不匹配,但有些东西工具查不到,必须手动检查。我总结了一个提交前的手动检查清单,每次提交前花两分钟过一遍。

第一,检查有没有调试代码残留。比如print语句、console.log、断点、注释掉的代码块。这些东西在开发的时候有用,但提交上去就是垃圾。

第二,检查配置文件有没有改错。比如数据库地址是不是从测试环境改回了生产环境,日志级别是不是从DEBUG改回了INFO,超时时间是不是调回了正常值。

第三,检查依赖有没有更新。如果你在开发过程中安装了新的依赖,确认一下依赖文件(requirements.txt、package.json)有没有同步更新。反过来,如果你删除了某个依赖的使用,确认一下依赖文件里有没有把它移除。

第四,检查提交信息是否清晰。提交信息要能说明这次提交做了什么。我一般的格式是:类型: 简短描述,比如feat: 添加数据导出功能、fix: 修复空指针异常、refactor: 重构用户模块。类型包括feat、fix、refactor、docs、test、chore等。

第五,检查有没有遗漏的文件。有时候新建了文件但忘了git add,提交上去之后别人拉下来发现少文件。用git status确认一下所有该提交的文件都提交了。

4.3 提交信息的写法:三个月后的你能看懂吗

提交信息看起来是小事,但其实很重要。我给自己定了一个标准:三个月后回头看这条提交信息,能不能想起来当时改了什么、为什么改。

不好的提交信息长这样:

update fix bug 修改

好的提交信息长这样:

fix: 修复用户名为空时登录崩溃的问题 当用户名为空字符串时,登录接口会抛出KeyError。 原因是查询用户时没有做空值判断,直接取了字典的key。 现在在查询前增加了空值校验,返回明确的错误提示。

好的提交信息包含三个部分:做了什么(fix)、改了什么(空值校验)、为什么改(避免崩溃)。这样即使过了很久,回头看也能快速理解这次提交的意图。

如果一次提交涉及多个不相关的改动,建议拆成多次提交。比如你既修复了一个bug又添加了一个新功能,应该分成两次提交。这样以后如果要回滚其中一个改动,不会影响到另一个。

4.4 提交前的最后一步:在干净环境里验证

这是我最想强调的一点。很多问题在开发环境里发现不了,因为开发环境里有很多缓存、临时文件、本地配置。只有在干净环境里才能暴露出来。

我一般的做法是:提交前在一个全新的目录里克隆一份代码,按照README的说明从头配置一遍,跑一遍主流程。这个过程能发现很多问题,比如:

  • 依赖没有写全,别人拉下来装不上
  • 配置文件没有提供模板,别人不知道要配哪些项
  • 数据库初始化脚本没有更新,新环境跑不起来
  • 文档里的步骤过时了,和实际代码不一致

这个过程大概花五到十分钟,但能避免很多“在我机器上能跑”的尴尬。

提示:如果项目有CI/CD流水线,这一步可以交给流水线自动做。每次推送代码后,流水线在一个干净的环境里自动构建和测试,结果会通知你。小项目如果没有流水线,手动做这一步也值得。

5. 三个环节串起来:一个完整的实操流程

5.1 从写代码到合并的完整时间线

把上面说的三个环节串起来,一个完整的流程大概是这样的:

第一步,写代码之前先拉一个功能分支。不要在主干上直接开发,这样即使出了问题也不会影响其他人。

第二步,每完成一个小模块就提交一次。提交信息写清楚这个模块做了什么。提交之后推送分支。

第三步,创建审查请求。在审查请求的描述里说明这次改动的内容、影响范围、需要重点看的地方。指定审查人。

第四步,根据审查意见修改。修改完之后再推送,回复审查人请对方确认。

第五步,审查通过后做重构。如果审查过程中发现了设计上的问题,先重构再合并。重构之后重新跑测试。

第六步,提交前检查。跑自动化检查、过手动清单、在干净环境里验证。

第七步,合并到主干。合并之后删除功能分支。

这个流程看起来步骤很多,但实际做起来每个步骤花的时间并不多。小模块的审查可能就十分钟,重构可能就半小时,提交前检查可能就五分钟。加起来可能比出问题之后调试的时间还短。

5.2 工具链推荐:小项目够用就好

工具不需要多,够用就行。我推荐一套最小化的工具组合:

用途推荐工具说明
版本控制Git基础工具,必须
代码托管任意支持审查请求的平台小项目用免费的就行
代码格式化black / prettier自动统一风格
静态检查flake8 / eslint发现潜在问题
类型检查mypy / TypeScript可选,但推荐
单元测试pytest / jest覆盖核心逻辑
提交钩子pre-commit自动运行检查

这套工具组合的学习成本不高,配置一次之后就能长期使用。我建议先从代码格式化和单元测试开始,这两个的收益最明显。静态检查和类型检查可以后面再加。

5.3 常见问题与应对:流程执行不下去怎么办

问题一:一个人开发,没人审查怎么办?可以自己审查自己。具体做法是:写完代码之后隔一天再看,或者换一个环境(比如换个编辑器)再看。隔一段时间之后,你看自己的代码会像看别人的代码一样,更容易发现问题。

问题二:项目太急,没时间走完整流程怎么办?可以裁剪流程,但不能完全跳过。最精简的版本是:提交前跑一遍测试,提交信息写清楚。审查和重构可以等有时间的时候补做。

问题三:重构之后测试跑不过怎么办?先确认测试本身是不是正确的。如果测试是正确的,那说明重构改变了行为,需要修复。如果测试本身有问题,先修测试再继续重构。

问题四:审查意见太多,改不过来怎么办?把审查意见分类:必须改的(bug、安全问题)、建议改的(代码风格、命名)、可以不改的(个人偏好)。先改必须改的,建议改的看时间,可以不改的说明理由。

问题五:提交前检查发现的问题太多怎么办?说明平时的开发习惯需要调整。把检查中发现的问题记下来,下次写代码的时候注意避免。坚持一段时间之后,提交前检查发现的问题会越来越少。

6. 我踩过的坑和总结的经验

6.1 那些年我在提交前检查上偷过的懒

我印象最深的一次是提交前没有在干净环境里验证。当时我本地开发环境里有一个环境变量文件,里面配置了数据库连接信息。我写完代码之后直接提交了,忘了把这个文件加到.gitignore里。结果同事拉下代码之后,他的环境变量文件被我的覆盖了,连不上数据库,排查了半天才发现是这个问题。

还有一次是提交前没有跑测试。我觉得改动很小,就改了一个函数的返回值类型,从int改成了float。本地跑了一下主流程没问题,就提交了。结果另一个模块里有个地方对这个返回值做了整除运算,float不支持整除,直接报错。如果当时跑一遍测试,这个问题就能提前发现。

这些坑让我养成了一个习惯:不管改动多小,提交前必须跑测试和在干净环境里验证。这两步花的时间不多,但能避免很多低级错误。

6.2 重构时最容易犯的三个错误

第一个错误是重构和功能修改混在一起。比如你在重构一个函数的同时,顺手加了一个新功能。这样如果出了问题,你分不清是重构导致的还是新功能导致的。正确的做法是分开提交,先重构,验证通过后再加新功能。

第二个错误是重构没有测试覆盖。如果你要重构的代码没有测试,那重构的风险就很高。我的做法是:先给要重构的代码补测试,确保测试能覆盖主要行为,然后再重构。重构之后跑测试,通过就说明行为没有改变。

第三个错误是重构范围太大。一次重构改了几十个文件,改完之后自己都记不清改了哪些地方。正确的做法是小步走,每次只改一个类型的问题,改完验证,再改下一个。

6.3 代码审查中最容易忽略的细节

代码审查的时候,大家通常关注逻辑对不对、有没有bug,但有些细节容易被忽略。

一个是命名的一致性。比如项目里有的地方用get_user,有的地方用fetch_user,有的地方用query_user。虽然功能一样,但看起来不统一。审查的时候应该提出来,统一成一种命名风格。

一个是注释的准确性。代码改了但注释没改,这种情况很常见。审查的时候看到注释和代码不一致,一定要提出来。错误的注释比没有注释更糟糕,因为它会误导人。

一个是异常处理的完整性。比如一个函数声明了会抛出某种异常,但调用方没有处理。或者一个地方捕获了异常但没有做任何处理,只是pass了。这些在审查的时候都要关注。

6.4 让这套流程真正跑起来的建议

最后分享几个让这套流程真正落地的建议。

第一,从最小的流程开始。不要一上来就搞一套完整的CI/CD,先从提交前跑测试开始。等这个习惯养成了,再加代码格式化,再加静态检查,一步一步来。

第二,把检查自动化。能自动做的就不要手动做。配置pre-commit钩子,让工具在提交的时候自动运行检查。这样你不需要记住每次都要跑什么命令,工具会帮你记住。

第三,定期回顾。每隔一段时间回顾一下最近提交的代码,看看有没有反复出现的问题。如果有,就针对性地调整流程或者补充检查项。

第四,不要追求完美。这套流程的目的是减少问题,不是杜绝问题。总会有漏网之鱼,这很正常。重要的是持续改进,让问题越来越少。

我在实际使用这套流程之后,最明显的感受是:调试的时间变少了,写代码的时间变多了。以前经常花半天时间排查一个提交后才发现的问题,现在这些问题在提交前就被拦住了。虽然提交前多花了几分钟,但省下的调试时间远远超过这几分钟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询