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工具的优势,同时结合人工审查的经验和判断力,共同推动代码审查流程的优化和升级。

相关推荐
paopaokaka_luck4 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
码农小旋风5 小时前
详解K8S--声明式API
后端
Peter_chq5 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml46 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~6 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616886 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
睡觉谁叫~~~7 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
A ?Charis8 小时前
Gitlab-runner running on Kubernetes - hostAliases
容器·kubernetes·gitlab
2401_865854889 小时前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
AskHarries9 小时前
Spring Boot集成Access DB实现数据导入和解析
java·spring boot·后端