开放式代码审查实践指南:告别走过场,让Code Review真正生效
2026/9/25 5:49:08
大四下学期,白天实习、晚上论文,老师还催着“系统要演示、报告要胶装”。典型的一天是这样循环的:
编码和文档双重压力,本质上是“上下文切换”太频繁:刚进入代码区,又被格式、字号、引用文献拉回写作区,效率自然腰斩。
| 场景 | GitHub Copilot | 通义灵码 | Claude 3.5 |
|---|---|---|---|
| 需求→用户故事 | 补全快、中文提示弱 | 中文需求理解好 | 长文本梳理清晰 |
| 数据库建模 | 靠注释猜字段 | ER 图一键生成 | 可输出 PlantUML |
| API 开发 | 实时补全,Clean Code 风格好 | 注释→代码同步 | 适合写复杂业务注释 |
| 报告生成 | 不适合长文 | 模板多,可直接出章节 | 逻辑段落最自然 |
我的组合打法:
功能边界:注册登录、商品列表、下单、支付回调、后台改状态。只保留毕业设计必考的核心链路,方便写报告时“有图有真相”。
shopping-mini/ ├── back/ (Spring Boot 33.1) └── front/ (Vue3 + Vite)@Component @RequiredArgsConstructor public class JwtFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserService userService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token = resolveToken(request); if (token != null && jwtUtil.validate(token)) { String username = jwtUtil.getUsername(token); UserDetails user = userService.loadUserByUsername(username); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( user, null, user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); } chain.doFilter(request, response); } private String resolveToken(HttpServletRequest req) { String bearer = req.getHeader("Authorization"); if (bearer != null && bearer.startsWith("Bearer ")) { return bearer.substring(7); } return null; } }要点:
OncePerRequestFilter保证一次请求只过滤一次;@PreAuthorize兜底,逻辑更干净。public enum OrderState { PENDING_PAY, PAID, SHIPPED, COMPLETED, CANCELLED; public boolean canPay() { return this == PENDING_PAY; } public boolean canShip() { return this == PAID; } public boolean canComplete() { return this == SHIPPED; } public boolean canCancel() { return this == PENDING_PAY || this == PAID; } } @Service @RequiredArgsConstructor public class OrderService { private final OrderRepo repo; @Transactional public void pay(Long orderId) { Order o = repo.findById(orderId).orElseThrow(); if (!o.getState().canPay()) throw new BizException("非法支付"); o.setState(OrderState.PAID); } @Transactional public void ship(Long orderId) { Order o = repo.findById(orderId).orElseThrow(); if (!o.getState().canShip()) throw new BizException("非法发货"); o.setState(OrderState.SHIPPED); } }状态机把“能不能做”封装进枚举,Service 只看业务,不写if-else堆成山。
// api/order.js import axios from '@/utils/request' export function payOrder(id) { return axios.post(`/order/${id}/pay`) } // views/Order.vue const handlePay = async () => { await payOrder(props.orderId) ElMessage.success('支付成功') emit('refresh') }步骤拆解:
这样,报告 80% 框架 AI 搞定,留下 20% 人工润色,既保证学术规范,也避免“查重”飘红。
Example或@Query写法,禁止拼接字符串;OrderState加锁版本号,AI 容易忽略,需要手动补上;spring.jpa.properties.hibernate.show_sql=true,看到多余语句就加@EntityGraph;OrderService.ship()里手动加@Log("订单发货")。一句话:AI 是高速打字机,安全与性能仍是程序员的责任。
axios.put,后端却生成@PostMapping,跑不通还找不到原因;答辩时老师最爱问:“这段订单状态为什么要加版本号?”
如果你一句“AI 写的”甩锅,现场气氛直接零下。
正确姿势是:先让 AI 给出方案,自己再查《Java 并发实践》补理论,最后把“乐观锁防止并发修改”写在报告里——既展示工程能力,也体现你理解原理。
if-else;动手改一行代码,比让 AI 写十页报告更能证明你是合格工程师。
祝各位毕业顺利,把 AI 当加速器,别当驾驶座。代码跑通、报告胶装那一刻,你会感谢那个“又写又改”的自己。