背景
为什么要尝试做GPT Code Review机器人?
大致是受2个点的启发:
- 在组内的代码质量讨论时,尝试将一些代码甩给GPT进行优化,GPT还是能够提供不错的优化意见
- 社区中相关的实践也有一些,因此特别想引入并看一下效果
自己尝试开发前,也过问公司其他团队是否有可以直接用的能力或者社区中是否有能用的能力(比如coderabbit),但是始终没有找到比较容易接入的,因此决定自己搞一个试试看
GPT Code Review机器人和ESLint的边界在哪里?
我们都知道,ESLint本质上是代码Lint工具, Lint 代码的本意就是找出程序中的错误,这些错误包括潜在的语法错误,编译错误,拼写错误等。事实上,ESLint+Prettier目前已经是前端工程的标配了,二者结合起来,为基础的代码质量保证发挥了很大作用,也是前端工程不可或缺的一环
然而,Chat GPT作为大模型,带来了一种全新的视角,我们可以把GPT想象成一位资深程序员,如果告诉它我们希望它对代码进行Review并提出相应的改进意见,也许能够从它那里获取到Lint之外的有效信息,例如:
- 一段代码有更优雅或者更简洁的实现思路
- 一段代码有潜在的逻辑漏洞
- 重复代码太多了,可以做一些代码复用
- 有一些代码是多余的或者没有意义的
- 有一些变量名字的含义难以理解,需要更换命名或者添加注释
以上这几点,都是代码Lint工具不太擅长或者压根就做不到的,而且可能也是日常代码Review中经常被提出的问题
做Code Review机器人的最终目标是什么?
最终的目标是期望机器人能够能成代码初审员,将Lint之外一些常见的代码问题前置发现并反馈,从而降低Reviewer的负担或者让Reviewer将精力放在更核心的业务逻辑代码审查方面
但是初步实践下来,目前暂时还是难以达到代码初审员这个要求,暂时只能先定位成给Reviewer或者Commiter提供意见参考
具体实现
核心依赖
-
本质上,Review的过程还是一个GPT Chat的场景,我们给GPT输入代码,GPT给出审查意见,因此最核心的依赖就是GPT的chat接口,本文的方案是使用了公司内部封装的chat接口
-
Gitlab REST API
-
目前前端仓库均托管在Gitlab中,因此对Gitlab的接口有直接依赖,具体包括:
- Webhook能力,用于感知Merge Request的创建和更新等hook事件
- 获取Merge Request Diff接口,用于获取某次MR的代码diff
- 评论接口,使用了评论创建和更新接口,用于将GPT的Review意见显示在页面中
-
Prompt
Prompt如下:
# Role
你扮演一个中立、严格、专业的 Code Review 辅助 AI,用户发给你 Json 编码的字符串,字符串中每一个元素是一个对象,对象中new_path代表了文件的路径,diff字符串是unidiff格式的代码diff描述,你用简练的语言来给出代码Review的意见
# 注意事项
下面几条准则你必要用遵守:
1、 你不关注代码中中import或者require来导入模块相关的代码
2、你不关注console.log等打日志的代码,也无需对代码中日志打印相关的代码进行评价
3、你不关注代码缩进等格式问题
4、由于你获取到的只是代码段中的一部分代码,因此你无需关注代码中变量或者函数是否存在这类问题,因为你可能做出较多的误判
5、你不需要对代码进行夸奖,也不需要输出类似”这是一个好的实践“, "这是一个改进", "...是好的"这类结果
6、你不需要提醒开发者添加单测
7、如果某个文件无改进建议,则不需要输出“无改进建议”这类结果
8、不需要提示开发者代码的修改可能导致的相关的功能问题,例如不需要输出”请确保这一变更不会影响现有功能“, ”请确保整个系统的一致性,并且相关文档和用户界面也应该同步更新“等等类似的提醒
# Review重点
下面这几条是你Review代码时需要重点关注的:
- 对于代码风格来说:
1、 代码有冗余,没有使用更加简练的写法
- 对于代码逻辑来说:
1、 逻辑不清晰,不容易被看懂,
2、 逻辑不完善,或者没有考虑各种异常情况及边界 case
3、 逻辑不必要地复杂,难以维护,容易出现 bug
4、 逻辑不易测试,难以写出 test case
5、代码应该具备良好的可维护性,可扩展性、可读性
- 代码是否遵循了以下几个原则
1、SOLID原则,即SRP(Single Responsibility Principle) 单一职责,OCP(Open Closed Principle) 开闭原则,LSP(Liskov Substitution Principle) 里氏替换原则,ISO(Interface Segregation Principle) 接口隔离原则
2、DIP(Dependency Inversion Principle) 依赖倒置/依赖反转原则
3、DRY (Don't Repeat Yourself)原则
4、KISS (Keep It Simple And Stupid)原则
5、YAGNI(You Aint't Gonna Need It)原则
6、 LOD(Law of Demeter)原则/迪米特法则
- 作为一个专业的 Code Review AI,你也应该尽可能按照优秀的软件工程实践,补充其他改进点。
# 输出要求:
对于输出的内容,你必须要遵守下面几条
- 如果代码行数小于5行,只输出1条,否则最多输出{{maxLength}}条
- 请逐条列出改进建议,不需要分类,按照无序列表的格式输出
- 每一条都需要有明确可以改进的地方,如果没有,或者信息不足无法评定,就不要写这条
- 基于上述review 的要求,给出建议改进或优化后的代码示例;
# 输出形式:
- Markdown
- 中文
实现流程
具体的实现中,有一个小小的工程问题需要解决,即每次调用chat接口是一个非常耗时的操作,而Gitlab Webhook要求hook url必须尽快地给出响应,否则过了超时时间就会认为hook失效了,多次失效后可能就不给这个hook发送数据了。因此,收到Gitlab的MR hook数据后,我们需要将数据存储在一个队列中,然后再通过一个定时任务去队列中取出数据并请求chat接口,然后再创建或者更新MR的评论。因此,整体流程分两块:
接收Gitlab MR数据:
定时任务取出MR数据并获取GPT Review意见:
其中,有几个细节需要注意:
-
为了降低对chat接口的请求负荷,目前实际上做了一个限制,即同一时间只会有一个MR在请求chat接口,不过这里对及时性的要求暂时没有那么高,还可以接受,后续优化也比较简单
-
有一些Diff实际上不需要GPT Review,比如删除文件的diff,或者单纯重命名的diff,同时为了将重点放在核心代码上,对于非代码文件的类型(ts,tsx,vue,js,jsx,mjs之外的后缀文件)也先过滤掉了
-
由于GPT有最大Token的限制,因此使用gpt-tokens对diff代码需要的token数量进行了粗略的估算并且对diff文件进行了一定的拆分,而不是无脑一把吐给GPT
- 实践中还会有单个文件diff就超出了最大token的情况,此时会直接返回,并进行“文件过大,建议拆分代码”的提示
-
由于一个MR可能多次更新,为了避免MR页面的Comment过长(现在基架的sonar扫描每次都是创建新的comment,MR页面有时候要滚动很长的距离),目前每次MR更新后,都是将原来的GPT Review Comment给刷新掉
效果和问题
效果展示
存在的问题
如开头中提到的,目前GPT Code Review的定位还难以达到“代码初审员”这个角色的要求,原因有以下几个:
- 由于token的限制,目前只能给到GPT的是代码Diff,但是这其实会遗失一些上下文,导致GPT还是会出现错误的理解,比如提示某个变量不存在,虽然在Prompt进行了抑制,但偶尔还是会提
- 有时候会讲废话,有时候提出的意见不痛不痒或者过于理想化
- 偶尔还是会有说错的情况,尤其是一些逻辑判断比较多的地方,它给出的修改意见可能是错的
未来规划
短期内还是聚焦于“给Reviewer提供参考意见,给Committer进行自检提供建议,降低代码Review的负担”这个方向去演进,具体的事情可能包括:
- 通过优化Prompt继续抑制废话和可能出错的情况
- 让GPT对代码改动进行总结,给Reviewer提供一个整体的参考(具体的Action,应该可以做)
- 参考社区中的其他方案和做法,看看有没有更好的优化方向
- 最近OpenAI o1出来了,理论上可能对于识别代码问题的效果会好一些,后续等公司接入了也可以尝试一下