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

image

背景

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

  • 为了确保Code Review的有效性,需要设置分支的合并权限。通常,只有项目维护者(maintainers)才拥有合并权限,而开发者只能提交Merge Request并等待审核。
  • 在GitLab的项目设置中,找到“Repository”下的“Protected Branches”,将需要保护的分支(如master、develop等)设置为只允许维护者合并,不允许其他人推送。
创建Merge Request并发起Code Review
  1. 创建Merge Request
    • 在GitLab的项目页面上,找到“Merge Requests”选项,并点击“New merge request”按钮。
    • 在弹出的页面中,选择源分支(即你刚刚推送的feature-branch)和目标分支(如developmaster),并填写必要的描述信息。
    • 点击“Submit merge request”按钮,提交Merge Request。
  2. 指派审核者与发起Code Review
    • 在Merge Request的页面中,可以指派一个或多个审核者(reviewer)来审查代码。通常,审核者应该是除了开发者自己之外的其他开发者或项目维护者。
    • 审核者通过Merge Request页面可以看到代码修改记录,并可以在“Changes”标签页中逐行审查代码。审查过程中,审核者可以添加评论、提出修改意见或直接批准Merge Request。
  3. 讨论与修改
    • 如果审核者提出修改意见,开发者需要根据意见进行相应的修改,并将修改后的代码再次推送到远端仓库。
    • 审核者和开发者可以在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文件的主要作用:

  1. 定义CI/CD任务
    • .gitlab-ci.yml文件用于定义项目中各个阶段的CI/CD任务,包括构建、测试、部署等,以及它们之间的依赖关系和执行顺序。这使得开发者能够清晰地规划和管理项目的自动化流程。
  2. 版本控制
    • 将CI/CD配置与代码存储在同一个版本控制系统中,使得配置变更能够与代码变更保持一致,更易于管理和维护。这确保了CI/CD流程的稳定性和可追溯性。
  3. 自动化流程
    • 通过配置CI/CD流程,可以实现自动化构建、测试和部署,从而提高开发团队的效率和产品质量。自动化流程减少了人为错误,并加速了软件的交付速度。
  4. 规范化流程
    • .gitlab-ci.yml文件定义了统一的CI/CD配置文件结构和语法规则,有助于规范团队的开发流程。这降低了错误发生的可能性,并提高了流程的可维护性。
  5. 分阶段定义任务
    • 将CI/CD流程划分为多个阶段(stages),每个阶段包含一个或多个任务(jobs)。这有助于组织和管理复杂的CI/CD流程,使得任务的执行顺序清晰可控。


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

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


核心代码

安装依赖

python-gitlab==3.15.0

openai==0.27.8


程序逻辑main.py

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 Server 的基础 URL,您可以在初始化 gitlab.Gitlab 对象时传入 base_url 参数。此外,您还需要确保 base_url 能够正确地从环境变量中读取。代码可以修改为

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()

请确保在运行此脚本之前设置了正确的环境变量,例如通过命令行设置:
export GITLAB_BASE_URL=https://your-gitlab-server.example.com

Dockerfile

FROM python:3.9

WORKDIR /app

COPY main.py .

COPY requirements.txt .

RUN pip install -r requirements.txt

CMD ["/bin/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请求基础地址,  我们在这儿使用的是https://vip.apiyi.com/v1/,你也可以使用其他厂商代理。

OPENAI_API_KEY 是对应申请的Key


Job 详情

clipboard

Running logs

clipboard

ChangeSet

    我们用于测试代码,刻意加了一条代码

clipboard


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

结果如下

Code Review for HelloWorld.java

Overall Code Quality

  • The code provides a simple implementation to print a greeting and the current time using DateTimeFormatter and LocalDateTime.
  • While the implementation achieves its goal, there are several aspects that can be improved for better readability, maintainability, and functionality.

Problematic Code Snippets & Recommendations

  1. Static Context Issue:
    • The line System.out.println("Begin line"); is placed directly in the class body which could cause a compilation error since it is not inside a method.

Recommendation: Move the System.out.println("Begin line"); statement into a method, such as main.

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)); } }

  1. Use of final for Instance Variables:
    • While it is often good practice to use final for constants, using final for now makes it initialized with the value at the creation time of an object. It would not change over time. If the intent is to always show the current time whenever the greeting method is called, consider initializing it inside the method.

Recommendation: Move the initialization of now into the greet() method to show the current time at each invocation.

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

  1. Code Readability:
    • The time format string could be extracted into a constant or configurable setting for better readability and maintainability.

Recommendation: Define the date-time pattern as a constant.

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

  1. Error Handling:
    • Although this example works under normal circumstances, consider implementing error handling or checks in production code, especially when dealing with date and time formats.

Recommendation: You could add a try-catch block around the date formatting within the greet() method.

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()); } }

Summary

Your current approach in HelloWorld.java works but requires some modifications to ensure better practices in Java. Make sure to encapsulate code in methods, consider the context of variable initialization, promote code readability, and handle potential exceptions or errors gracefully. Overall, these changes will enhance the robustness and maintainability of your code.

中文翻译后参考

代码审查对于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()方法内以在每次调用时显示当前时间。

```java

public void greet() {

LocalDateTime now = LocalDateTime.now();

System.out.println("Hello, World! The current time is " + dtf.format(now));

}

```

代码可读性:

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

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

```java

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块。

```java

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

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


总结

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

一、AI工具进行代码审查的意义
  1. 提高代码质量和安全性

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

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

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

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

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

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

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



今天先到这儿,希望对云原生,技术领导力, 企业管理,系统架构设计与评估,团队管理, 项目管理, 产品管理,信息安全,团队建设 有参考作用 , 您可能感兴趣的文章:
构建创业公司突击小团队
国际化环境下系统架构演化
微服务架构设计
视频直播平台的系统架构演化
微服务与Docker介绍
Docker与CI持续集成/CD
互联网电商购物车架构演变案例
互联网业务场景下消息队列架构
互联网高效研发团队管理演进之一
消息系统架构设计演进
互联网电商搜索架构演化之一
企业信息化与软件工程的迷思
企业项目化管理介绍
软件项目成功之要素
人际沟通风格介绍一
精益IT组织与分享式领导
学习型组织与企业
企业创新文化与等级观念
组织目标与个人目标
初创公司人才招聘与管理
人才公司环境与企业文化
企业文化、团队文化与知识共享
高效能的团队建设
项目管理沟通计划
构建高效的研发与自动化运维
某大型电商云平台实践
互联网数据库架构设计思路
IT基础架构规划方案一(网络系统规划)
餐饮行业解决方案之客户分析流程
餐饮行业解决方案之采购战略制定与实施流程
餐饮行业解决方案之业务设计流程
供应链需求调研CheckList
企业应用之性能实时度量系统演变

如有想了解更多软件设计与架构, 系统IT,企业信息化, 团队管理 资讯,请关注我的微信订阅号:

image_thumb2_thumb_thumb_thumb_thumb[1]

作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 该文章也同时发布在我的独立博客中-Petter Liu Blog。

posted on 2024-10-17 12:11  PetterLiu  阅读(179)  评论(0编辑  收藏  举报