GitLab集成GPT进行自动化CodeReview实战

背景

GitLab基于Merge Request的Code Review流程是一个团队协作中至关重要的环节,它确保了代码质量并促进了团队成员之间的有效沟通。CodeReview准备工作如下

  • 为了确保Code Review的有效性,需要设置分支的合并权限。通常,只有项目维护者(maintainers)才拥有合并权限,而开发者只能提交Merge Request并等待审核。
  • 在GitLab的项目设置中,找到"Repository"下的"Protected Branches",将需要保护的分支(如master、develop等)设置为只允许维护者合并,不允许其他人推送。

创建Merge Request并发起Code Review

创建Merge Request

在GitLab的项目页面上,找到"Merge Requests"选项,并点击"New merge request"按钮。在弹出的页面中,选择源分支(即你刚刚推送的feature-branch)和目标分支(如developmaster),并填写必要的描述信息。点击"Submit merge request"按钮,提交Merge Request。

指派审核者与发起Code Review

在Merge Request的页面中,可以指派一个或多个审核者(reviewer)来审查代码。通常,审核者应该是除了开发者自己之外的其他开发者或项目维护者。审核者通过Merge Request页面可以看到代码修改记录,并可以在"Changes"标签页中逐行审查代码。审查过程中,审核者可以添加评论、提出修改意见或直接批准Merge Request。

讨论与修改:如果审核者提出修改意见,开发者需要根据意见进行相应的修改,并将修改后的代码再次推送到远端仓库。审核者和开发者可以在Merge Request的评论区域中进行讨论,直到所有问题都得到解决。

如您计划在本地部署,请前文我们有介绍Docker Compose部署GitLab

目标

我们的目标是在 提交Merge Request后,由AI大模型(大型语言模型(Large Language Models)的介绍)自动对Code diff进行代码审查,生成改进建议。之前文章也写过轻松连接 ChatGPT实现代码审查。今天我们再来实战基于Gitlab.com的自动化CodeReview。

流程如下

image

集成流程

实战开始

.gitlab-ci.yml配置

.gitlab-ci.yml文件是GitLab CI/CD流程的核心配置文件,它在软件开发过程中起着至关重要的作用。以下是.gitlab-ci.yml文件的主要作用:

定义CI/CD任务

.gitlab-ci.yml文件用于定义项目中各个阶段的CI/CD任务,包括构建、测试、部署等,以及它们之间的依赖关系和执行顺序。这使得开发者能够清晰地规划和管理项目的自动化流程。

版本控制

将CI/CD配置与代码存储在同一个版本控制系统中,使得配置变更能够与代码变更保持一致,更易于管理和维护。这确保了CI/CD流程的稳定性和可追溯性。

自动化流程

通过配置CI/CD流程,可以实现自动化构建、测试和部署,从而提高开发团队的效率和产品质量。自动化流程减少了人为错误,并加速了软件的交付速度。

规范化流程

.gitlab-ci.yml文件定义了统一的CI/CD配置文件结构和语法规则,有助于规范团队的开发流程。这降低了错误发生的可能性,并提高了流程的可维护性。

分阶段定义任务

将CI/CD流程划分为多个阶段(stages),每个阶段包含一个或多个任务(jobs)。这有助于组织和管理复杂的CI/CD流程,使得任务的执行顺序清晰可控。

我们只定义一个阶段用测试,实际CI过程应该是多阶段的,如下

makefile 复制代码
stages:
  - review

review:
stage: review
rules:
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
image: registry.gitlab.com/gitlab-ci-templates3/gitlab-ci-chatgpt:latest

script:
  - python /app/main.py

核心代码

安装依赖requirements.txt

ini 复制代码
python-gitlab==3.15.0
openai==0.27.8

程序逻辑main.py

ini 复制代码
from typing import List, Any  # 导入必要的类型提示模块
import gitlab  # 导入GitLab SDK
import os  # 导入操作系统相关模块
from itertools import dropwhile  # 导入用于迭代器处理的函数
import openai  # 导入OpenAI SDK
from dataclasses import dataclass  # 导入用于创建数据类的装饰器
import logging  # 导入日志记录模块
# 设置日志记录的基本配置,包括编码格式和日志级别
logging.basicConfig(encoding="utf-8", level=logging.INFO)
# 使用数据类定义一个存储文件路径和差异信息的对象
@dataclass
class Diff:
     path: str
     diff: str

# 初始化GitLab客户端,使用环境变量中存储的个人访问令牌(PAT)
gl = gitlab.Gitlab(private_token=os.environ["PAT"])
# 设置OpenAI API的密钥,从环境变量中读取
openai.api_key = os.environ["OPENAI_API_KEY"]
# 主函数定义
def main():
     # 获取合并请求中的差异
     diffs, mr = get_diffs_from_mr()
    # 获取代码审查反馈
     response = get_review(diffs)
    # 记录审查反馈的日志
     logging.info(response)
    # 在合并请求中创建一条讨论,包含审查反馈
     mr.discussions.create({"body": response})

# 函数用于从提供的差异中获取审查反馈
def get_review(diffs):
     # 初始化用户消息,告知模型将进行代码审查
     user_message_line = ["Review the following code:"]
    # 遍历差异列表,构建完整的审查信息
     for d in diffs:
         user_message_line.append(f"PATH: {d.path}; DIFF: {d.diff}")
    # 将差异信息转换为字符串
     user_message = "\n".join(user_message_line)
    # 创建OpenAI ChatCompletion请求
     message = openai.ChatCompletion.create(
         model="gpt-3.5-turbo",  # 指定使用的模型
         messages=[  # 构建对话消息
             {
                 "role": "system",  # 系统角色信息,定义模型的行为
                 "content": "You are a code reviewer on a Merge Request on Gitlab. Your responsibility is to review "
                 "the provided code and offer"
                 "recommendations for enhancement. Identify any problematic code snippets, "
                 "highlight potential issues, and evaluate the overall quality of the code you review. "
                 "You will be given input in the format PATH: <path of the file changed>; DIFF: <diff>. "
                 "In diffs, plus signs (+) will mean the line has been added and minus signs (-) will "
                 "mean that the line has been removed. Lines will be separated by \n.",
             },
             {"role": "user", "content": user_message},  # 用户角色信息,提供审查的具体内容
         ],
     )
    # 从模型响应中提取审查反馈
     response = message["choices"][0]["message"]["content"]
    # 返回审查反馈
     return response

# 函数用于从合并请求中获取差异信息
def get_diffs_from_mr() -> (List[Diff], Any):
     # 获取项目信息
     project = gl.projects.get(os.environ["CI_PROJECT_PATH"])
    # 获取合并请求信息
     mr = project.mergerequests.get(id=os.environ["CI_MERGE_REQUEST_IID"])
    # 获取合并请求中的更改信息
     changes = mr.changes()
    # 清洗差异内容,并创建差异对象列表
     diffs = [
         Diff(c["new_path"], sanitize_diff_content(c["diff"]))
         for c in changes["changes"]
     ]
    # 返回差异对象列表和合并请求对象
     return diffs, mr

# 函数用于清洗差异内容,去除不必要的头部信息
def sanitize_diff_content(diff: str):
     # 使用dropwhile去除差异字符串前缀,直到遇到'@'字符
     return "".join(list(dropwhile(lambda x: x != "@", diff[2:]))[2:])

# 如果该脚本作为主程序运行,则执行main函数
if __name__ == "__main__":
     main()

以上可以看到其中包含提示词,可以进一步完善与修改。

自己部署GitLab集成

如果为支持自己部署GitLab, 支持通过环境变量配置 GitLab Server 的基础 URL,您可以在初始化 gitlab.Gitlab 对象时传入 base_url 参数。此外,您还需要确保 base_url 能够正确地从环境变量中读取。代码可以修改为

ini 复制代码
from typing import List, Any
import gitlab
import os
from itertools import dropwhile
import openai
from dataclasses import dataclass
import logging
logging.basicConfig(encoding='utf-8', level=logging.INFO)
@dataclass
class Diff:
     path: str
     diff: str
# 从环境变量读取 GitLab 基础 URL 和私有 token
gitlab_base_url = os.getenv("GITLAB_BASE_URL", "https://gitlab.example.com")
private_token = os.getenv("PAT")
gl = gitlab.Gitlab(url=gitlab_base_url, private_token=private_token)
openai.api_key = os.environ["OPENAI_API_KEY"]
def main():
     diffs, mr = get_diffs_from_mr()
     response = get_review(diffs)
     logging.info(response)
     mr.discussions.create({'body': response})
def get_review(diffs):
     user_message_line = ["Review the following code:"]
     for d in diffs:
         user_message_line.append(f"PATH: {d.path}; DIFF: {d.diff}")
     user_message = "\n".join(user_message_line)
     message = openai.ChatCompletion.create(
         model="gpt-3.5-turbo",
         messages=[
             {
                 "role": "system",
                 "content": "You are a code reviewer on a Merge Request on Gitlab. Your responsibility is to review "
                            "the provided code and offer recommendations for enhancement. Identify any problematic "
                            "code snippets, highlight potential issues, and evaluate the overall quality of the code "
                            "you review. You will be given input in the format PATH: <path of the file changed>; "
                            "DIFF: <diff>. In diffs, plus signs (+) will mean the line has been added and minus "
                            "signs (-) will mean that the line has been removed. Lines will be separated by \n."
             },
             {
                 "role": "user",
                 "content": user_message
             }
         ],
     )
     response = message['choices'][0]['message']['content']
     return response
def get_diffs_from_mr() -> (List[Diff], Any):
     project = gl.projects.get(os.environ["CI_PROJECT_PATH"])
     mr = project.mergerequests.get(id=os.environ["CI_MERGE_REQUEST_IID"])
     changes = mr.changes()
     diffs = [Diff(c['new_path'], sanitize_diff_content(c['diff'])) for c in changes['changes']]
     return diffs, mr
def sanitize_diff_content(diff: str):
     return "".join(list(dropwhile(lambda x: x != "@", diff[2:]))[2:])
if __name__ == "__main__":
     main()

请确保在运行此脚本之前设置了正确的环境变量,例如通过命令行设置:

arduino 复制代码
export GITLAB_BASE_URL=https://your-gitlab-server.example.com

Dockerfile

sql 复制代码
FROM python:3.9
WORKDIR /app
COPY main.py .
COPY requirements.txt .
RUN pip install -r requirements.txt
CMD ["/bin/bash"]

构建容器

bash 复制代码
docker login -u <gitlab-email> -p $PAT registry.gitlab.com
docker build -t registry.gitlab.com/gitlab-ci-templates3/gitlab-ci-code-reviewer .
docker push registry.gitlab.com/gitlab-ci-templates3/gitlab-ci-chatgpt

CICD环境变量配置

clipboard

PAT 是 Gitlab 个人token

OPENAI_API_BASE 是为了替换 AI请求基础地址, 我们在这儿使用的是vip.apiyi.com/v1/,你也可以使用其他厂商代理。

OPENAI_API_KEY 是对应申请的Key

Job 详情

clipboard

Running logs

clipboard

ChangeSet

我们用于测试代码,刻意加了一条不太规范的代码,如下

MergeRequest自动插入调用GPT审查代码评价

结果如下

MergeRequest自动插入调用GPT审查代码评价

GPT的CodeReview评价中文翻译后参考


代码审查对于HelloWorld.java

整体代码质量

该代码提供了一个简单的实现来打印问候语以及使用DateTimeFormatter和LocalDateTime获取的当前时间。

尽管实现达到了目的,但在可读性、可维护性和功能性方面还有几个可以改进的地方。

有问题的代码片段及建议

静态上下文问题:

直接在类体中放置了System.out.println("Begin line");这一行代码,这可能会导致编译错误,因为它不在任何方法内。

建议:将System.out.println("Begin line");语句移到某个方法(如main)内。

java 复制代码
public class HelloWorld {
    final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("h:mm:ss a 'on' MMMM d, yyyy'");
    final LocalDateTime now = LocalDateTime.now();
    public static void main(String[] args) {
        HelloWorld hello = new HelloWorld();
        hello.greet();
    }
    public void greet() {
        System.out.println("Begin line");
        System.out.println("Hello, World! The current time is " + dtf.format(now));
    }
}

实例变量使用final:

虽然通常使用final是一个好习惯,特别是对于常量来说,但是对now使用final意味着它会在对象创建时初始化,并且之后不会改变。如果目的是每次调用问候方法时显示当前时间,则应考虑在方法内部进行初始化。

建议:将now的初始化移到greet()方法内以在每次调用时显示当前时间。

csharp 复制代码
public void greet() {
    LocalDateTime now = LocalDateTime.now();
    System.out.println("Hello, World! The current time is " + dtf.format(now));
}

代码可读性:

时间格式字符串可以提取成常量或可配置设置,以便提高可读性和可维护性。

建议:定义日期时间模式为常量。

ini 复制代码
private static final String DATE_TIME_PATTERN = "h:mm:ss a 'on' MMMM d, yyyy";
final DateTimeFormatter dtf = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);

错误处理:

尽管这个例子在正常情况下可以工作,但在生产代码中考虑实现错误处理或检查,特别是在处理日期和时间格式时。

建议:可以在greet()方法中围绕日期格式化添加try-catch块。

csharp 复制代码
public void greet() {
    LocalDateTime now = LocalDateTime.now();
    try {
        System.out.println("Hello, World! The current time is " + dtf.format(now));
    } catch (DateTimeException e) {
        System.out.println("Error in formatting date and time: " + e.getMessage());
    }
}

总结

你在HelloWorld.java中的当前方法是可行的,但需要一些修改以确保遵循Java中的最佳实践。确保将代码封装在方法内,考虑变量初始化的上下文,提高代码可读性,并优雅地处理潜在的异常或错误。总体上,这些改动将增强代码的健壮性和可维护性。


API调用Token消耗

clipboard

API调用Token消耗统计

模型倍率 0.25,分组倍率 1,补全倍率 3,充值转换率 1,用时 16秒

总结

本文基于GitLab实战配置ChatGPT自动化代码审查(CodeReview)过程。 使用AI工具进行代码审查(CodeReview)在软件工程领域具有重要意义,它代表着软件开发流程的一次革新。

一、AI工具进行代码审查的意义

提高代码质量和安全性

AI工具通过机器学习、自然语言处理等技术,能够基于大量的代码数据和规则集对代码进行智能分析,识别潜在问题并提出改进建议。这些工具能够检测到人工审查可能遗漏的细微问题,如安全漏洞、代码异味(Code Smells)等,从而显著提升代码的整体质量和安全性。

提升开发效率

AI代码审查工具通常能够在几秒钟内完成对大量代码的分析,显著缩短了代码审查的周期。通过自动化代码审查,开发团队能够减少人工审查的时间和精力,使开发者能够更专注于创新和功能实现。

保持代码风格的一致性

AI工具能够基于团队的编码规范和最佳实践对代码进行审查,确保代码风格的一致性。这有助于维护代码的可读性和可维护性,降低因代码风格不一致而导致的沟通成本。

辅助新手开发者成长

AI工具能够提供实时的代码审查建议,帮助新手开发者快速适应团队的编码规范和最佳实践。通过不断学习和更新,AI工具还能够适应团队的特定需求,为开发者提供更个性化的支持。

二、AI工具与人工代码审查的关系

互补而非替代

尽管AI工具在代码审查中表现出色,但它们并不能完全替代人工审查。AI工具在处理复杂的业务逻辑、理解代码上下文和意图方面仍有局限。因此,开发者需要对AI工具的审查结果进行人工复核,以确保准确性。

协同工作

AI工具可以处理重复性、机械性的任务,如语法检查、代码风格检查等。人类审查者则专注于复杂的逻辑和业务需求审查,以及对AI工具审查结果的复核和确认。这种协作模式能够大大提升代码审查的效率和准确性。

使用AI工具进行代码审查对于软件工程具有重要意义。它不仅能够提高代码质量和安全性、提升开发效率、保持代码风格的一致性,还能够辅助新手开发者成长。然而,AI工具并不能完全替代人工审查,而是与人工审查形成互补和协同工作的关系。 因此,在软件工程实践中,应充分利用AI工具的优势,同时结合人工审查的经验和判断力,共同推动代码审查流程的优化和升级。

相关推荐
摇滚侠3 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯5 小时前
Spring Boot测试框架详解
java·spring boot·后端
你的人类朋友6 小时前
什么是断言?
前端·后端·安全
程序员小凯7 小时前
Spring Boot缓存机制详解
spring boot·后端·缓存
i学长的猫8 小时前
Ruby on Rails 从0 开始入门到进阶到高级 - 10分钟速通版
后端·ruby on rails·ruby
用户21411832636028 小时前
别再为 Claude 付费!Codex + 免费模型 + cc-switch,多场景 AI 编程全搞定
后端
茯苓gao8 小时前
Django网站开发记录(一)配置Mniconda,Python虚拟环境,配置Django
后端·python·django
Cherry Zack8 小时前
Django视图进阶:快捷函数、装饰器与请求响应
后端·python·django
爱读源码的大都督9 小时前
为什么有了HTTP,还需要gPRC?
java·后端·架构
码事漫谈9 小时前
致软件新手的第一个项目指南:阶段、文档与破局之道
后端