Java代码质量门禁案例

wen java案例 2

本文目录导读:

Java代码质量门禁案例

  1. 项目结构
  2. 核心实现代码
  3. 配置示例 (quality-gate.properties)
  4. CI 集成 (GitLab CI)
  5. 使用说明
  6. 最佳实践

我将为您提供一个完整的Java代码质量门禁案例,包括配置、实现和示例。

项目结构

quality-gate/
├── pom.xml
├── src/
│   ├── main/
│   │   └── java/
│   │       └── com/example/qualitygate/
│   │           ├── QualityGateConfig.java
│   │           ├── QualityGateRunner.java
│   │           └── QualityGateResult.java
│   └── test/
│       └── java/
│           └── com/example/qualitygate/
│               └── QualityGateRunnerTest.java
└── quality-gate-config.yml

核心实现代码

1 质量门禁配置类

package com.example.qualitygate;
import java.util.HashMap;
import java.util.Map;
public class QualityGateConfig {
    private Map<String, Object> thresholds = new HashMap<>();
    private boolean failOnError = true;
    // 质量门禁阈值配置
    public static QualityGateConfig defaultConfig() {
        QualityGateConfig config = new QualityGateConfig();
        config.thresholds.put("complexity.max", 10);
        config.thresholds.put("coverage.min", 80.0);
        config.thresholds.put("duplication.max", 5.0);
        config.thresholds.put("bugs.max", 0);
        config.thresholds.put("vulnerabilities.max", 0);
        config.thresholds.put("codeSmells.max", 100);
        config.thresholds.put("testRatio.min", 0.8);
        config.thresholds.put("linesOfCode.max", 2000);
        config.thresholds.put("methods.max", 50);
        config.thresholds.put("classes.max", 30);
        return config;
    }
    public Object getThreshold(String key) {
        return thresholds.get(key);
    }
    public void setThreshold(String key, Object value) {
        thresholds.put(key, value);
    }
    public boolean isFailOnError() {
        return failOnError;
    }
    public void setFailOnError(boolean failOnError) {
        this.failOnError = failOnError;
    }
}

2 质量门禁结果类

package com.example.qualitygate;
import java.util.ArrayList;
import java.util.List;
public class QualityGateResult {
    private boolean passed = true;
    private List<String> violations = new ArrayList<>();
    private String gateName;
    private long timestamp;
    private Map<String, Object> metrics = new HashMap<>();
    public void addViolation(String violation) {
        violations.add(violation);
        passed = false;
    }
    public boolean isPassed() {
        return passed;
    }
    public List<String> getViolations() {
        return new ArrayList<>(violations);
    }
    public String getSummary() {
        StringBuilder sb = new StringBuilder();
        sb.append("Quality Gate: ").append(gateName).append("\n");
        sb.append("Timestamp: ").append(timestamp).append("\n");
        sb.append("Status: ").append(passed ? "PASSED" : "FAILED").append("\n");
        if (!violations.isEmpty()) {
            sb.append("Violations:\n");
            for (String violation : violations) {
                sb.append("  - ").append(violation).append("\n");
            }
        }
        return sb.toString();
    }
    public void addMetric(String name, Object value) {
        metrics.put(name, value);
    }
}

3 质量门禁执行器

package com.example.qualitygate;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;
import java.io.*;
public class QualityGateRunner {
    private QualityGateConfig config;
    private List<CodeMetric> metrics = new ArrayList<>();
    public QualityGateRunner(QualityGateConfig config) {
        this.config = config;
        initializeMetrics();
    }
    private void initializeMetrics() {
        metrics.add(new CodeSizeMetric());
        metrics.add(new ComplexityMetric());
        metrics.add(new CoverageMetric());
        metrics.add(new DuplicationMetric());
        metrics.add(new SecurityMetric());
        metrics.add(new TestQualityMetric());
    }
    public QualityGateResult execute(String sourceDirectory, String testDirectory) {
        QualityGateResult result = new QualityGateResult();
        result.gateName = "Java Code Quality Gate";
        result.timestamp = System.currentTimeMillis();
        Map<String, Object> analysisResults = analyzeCode(sourceDirectory, testDirectory);
        // 执行各项检查
        checkCodeSize(analysisResults, result);
        checkComplexity(analysisResults, result);
        checkCoverage(analysisResults, result);
        checkDuplication(analysisResults, result);
        checkSecurity(analysisResults, result);
        checkTestQuality(analysisResults, result);
        return result;
    }
    private Map<String, Object> analyzeCode(String sourceDir, String testDir) {
        Map<String, Object> results = new HashMap<>();
        List<Path> javaFiles = collectJavaFiles(sourceDir);
        List<Path> testFiles = collectJavaFiles(testDir);
        // 收集基本指标
        results.put("linesOfCode", calculateLines(javaFiles));
        results.put("methods", countMethods(javaFiles));
        results.put("classes", countClasses(javaFiles));
        results.put("complexity", calculateAverageComplexity(javaFiles));
        results.put("testRatio", calculateTestRatio(javaFiles, testFiles));
        return results;
    }
    private List<Path> collectJavaFiles(String directory) {
        List<Path> javaFiles = new ArrayList<>();
        if (directory == null || directory.isEmpty()) {
            return javaFiles;
        }
        Path root = Paths.get(directory);
        if (!Files.exists(root)) {
            System.err.println("Directory not found: " + directory);
            return javaFiles;
        }
        try {
            Files.walk(root)
                .filter(Files::isRegularFile)
                .filter(p -> p.toString().endsWith(".java"))
                .forEach(javaFiles::add);
        } catch (Exception e) {
            System.err.println("Error scanning directory: " + directory);
        }
        return javaFiles;
    }
    private int calculateLines(List<Path> files) {
        int totalLines = 0;
        for (Path file : files) {
            try {
                long count = Files.lines(file).count();
                totalLines += count;
            } catch (IOException e) {
                System.err.println("Error reading file: " + file);
            }
        }
        return totalLines;
    }
    private int countMethods(List<Path> files) {
        int methodCount = 0;
        Pattern methodPattern = Pattern.compile(
            "(public|private|protected|static|final|synchronized|abstract)\\s+" +
            "(<[^>]+>\\s*)?" +
            "[\\w\\[\\]\\<\\>\\?]+\\s+" +
            "\\w+\\s*\\("
        );
        for (Path file : files) {
            try {
                String content = new String(Files.readAllBytes(file));
                Matcher matcher = methodPattern.matcher(content);
                while (matcher.find()) {
                    methodCount++;
                }
            } catch (IOException e) {
                // ignore
            }
        }
        return methodCount;
    }
    private int countClasses(List<Path> files) {
        int classCount = 0;
        Pattern classPattern = Pattern.compile(
            "(public|abstract|final)?\\s*class\\s+\\w+"
        );
        for (Path file : files) {
            try {
                String content = new String(Files.readAllBytes(file));
                Matcher matcher = classPattern.matcher(content);
                while (matcher.find()) {
                    classCount++;
                }
            } catch (IOException e) {
                // ignore
            }
        }
        return classCount;
    }
    private double calculateAverageComplexity(List<Path> files) {
        int totalComplexity = 0;
        int methodCount = 0;
        for (Path file : files) {
            try {
                String content = new String(Files.readAllBytes(file));
                methodCount += countMethods(Collections.singletonList(file));
                totalComplexity += calculateCyclomaticComplexity(content);
            } catch (IOException e) {
                // ignore
            }
        }
        return methodCount > 0 ? (double) totalComplexity / methodCount : 0;
    }
    private int calculateCyclomaticComplexity(String code) {
        int complexity = 1;
        String[] keywords = {"if", "else if", "for", "while", "case", "catch", "&&", "||", "?"};
        for (String keyword : keywords) {
            int count = countOccurrences(code, keyword);
            complexity += count;
        }
        return complexity;
    }
    private int countOccurrences(String text, String word) {
        int count = 0;
        int index = 0;
        while ((index = text.indexOf(word, index)) != -1) {
            count++;
            index += word.length();
        }
        return count;
    }
    private double calculateTestRatio(List<Path> sourceFiles, List<Path> testFiles) {
        if (sourceFiles.isEmpty()) {
            return 0;
        }
        return (double) testFiles.size() / sourceFiles.size();
    }
    // 各检查方法
    private void checkCodeSize(Map<String, Object> results, QualityGateResult result) {
        int lines = (int) results.get("linesOfCode");
        int maxLines = (int) config.getThreshold("linesOfCode.max");
        result.addMetric("linesOfCode", lines);
        if (lines > maxLines) {
            result.addViolation("Lines of code exceed limit: " + lines + " > " + maxLines);
        }
    }
    private void checkComplexity(Map<String, Object> results, QualityGateResult result) {
        double complexity = (double) results.get("complexity");
        int maxComplexity = (int) config.getThreshold("complexity.max");
        result.addMetric("averageComplexity", complexity);
        if (complexity > maxComplexity) {
            result.addViolation("Average cyclomatic complexity too high: " + 
                String.format("%.2f", complexity) + " > " + maxComplexity);
        }
    }
    private void checkCoverage(Map<String, Object> results, QualityGateResult result) {
        // 简化版本,实际应集成 JaCoCo 等工具
        double coverage = calculateMockCoverage();
        double minCoverage = (double) config.getThreshold("coverage.min");
        result.addMetric("coverage", coverage);
        if (coverage < minCoverage) {
            result.addViolation("Test coverage too low: " + 
                String.format("%.2f%%", coverage) + " < " + minCoverage + "%");
        }
    }
    private void checkDuplication(Map<String, Object> results, QualityGateResult result) {
        // 简化版本,实际应集成 PMD 或 SonarQube
        double duplication = calculateMockDuplication();
        double maxDuplication = (double) config.getThreshold("duplication.max");
        result.addMetric("duplication", duplication);
        if (duplication > maxDuplication) {
            result.addViolation("Code duplication too high: " + 
                String.format("%.2f%%", duplication) + " > " + maxDuplication + "%");
        }
    }
    private void checkSecurity(Map<String, Object> results, QualityGateResult result) {
        // 简化版本,实际应集成 FindBugs 或 SpotBugs
        int bugs = 0;
        int vulnerabilities = 0;
        try {
            // 模拟安全检查
            Pattern dangerous = Pattern.compile("ProcessBuilder|Runtime\\.exec|System\\.getProperty");
            // 实际文件扫描逻辑
        } catch (Exception e) {
            // ignore
        }
        result.addMetric("bugs", bugs);
        result.addMetric("vulnerabilities", vulnerabilities);
        if (bugs > (int) config.getThreshold("bugs.max")) {
            result.addViolation("Too many bugs found: " + bugs);
        }
        if (vulnerabilities > (int) config.getThreshold("vulnerabilities.max")) {
            result.addViolation("Too many vulnerabilities found: " + vulnerabilities);
        }
    }
    private void checkTestQuality(Map<String, Object> results, QualityGateResult result) {
        double testRatio = (double) results.get("testRatio");
        double minTestRatio = (double) config.getThreshold("testRatio.min");
        result.addMetric("testRatio", testRatio);
        if (testRatio < minTestRatio) {
            result.addViolation("Test to source ratio too low: " + 
                String.format("%.2f", testRatio) + " < " + minTestRatio);
        }
    }
    private double calculateMockCoverage() {
        // 模拟覆盖率,实际应集成 JaCoCo
        return 85.0; // 示例值
    }
    private double calculateMockDuplication() {
        // 模拟重复率,实际应集成 PMD
        return 3.5; // 示例值
    }
}

4 CI/CD 集成示例

<!-- pom.xml 中的配置 -->
<project>
    <properties>
        <!-- 质量门禁配置 -->
        <quality.gate.max.coverage>80</quality.gate.max.coverage>
        <quality.gate.min.complexity>10</quality.gate.min.complexity>
        <quality.gate.max.duplication>5</quality.gate.max.duplication>
    </properties>
    <build>
        <plugins>
            <!-- Checkstyle 插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>3.1.2</version>
                <configuration>
                    <configLocation>checkstyle.xml</configLocation>
                    <failOnViolation>true</failOnViolation>
                    <violationSeverity>warning</violationSeverity>
                </configuration>
            </plugin>
            <!-- PMD 插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-pmd-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <failOnViolation>true</failOnViolation>
                    <rulesets>
                        <ruleset>rulesets/java/braces.xml</ruleset>
                        <ruleset>rulesets/java/naming.xml</ruleset>
                    </rulesets>
                </configuration>
            </plugin>
            <!-- JaCoCo 覆盖率插件 -->
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.7</version>
                <configuration>
                    <excludes>
                        <exclude>**/qualitygate/**</exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <id>prepare-agent</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>check</id>
                        <goals>
                            <goal>check</goal>
                        </goals>
                        <configuration>
                            <rules>
                                <rule>
                                    <element>BUNDLE</element>
                                    <limits>
                                        <limit>
                                            <counter>LINE</counter>
                                            <value>COVEREDRATIO</value>
                                            <minimum>${quality.gate.max.coverage}</minimum>
                                        </limit>
                                    </limits>
                                </rule>
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- SpotBugs 插件 -->
            <plugin>
                <groupId>com.github.spotbugs</groupId>
                <artifactId>spotbugs-maven-plugin</artifactId>
                <version>4.2.0</version>
                <configuration>
                    <effort>Max</effort>
                    <threshold>Low</threshold>
                    <failOnError>true</failOnError>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

5 门禁执行工具类

package com.example.qualitygate;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
public class QualityGateExecutor {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.err.println("Usage: QualityGateExecutor <sourceDir> <testDir>");
            System.exit(1);
        }
        String sourceDir = args[0];
        String testDir = args[1];
        // 1. 加载配置
        QualityGateConfig config = QualityGateConfig.defaultConfig();
        loadCustomConfig(config);
        // 2. 创建执行器
        QualityGateRunner runner = new QualityGateRunner(config);
        // 3. 执行质量检查
        QualityGateResult result = runner.execute(sourceDir, testDir);
        // 4. 输出结果
        System.out.println(result.getSummary());
        // 5. 退出码
        if (!result.isPassed() && config.isFailOnError()) {
            System.exit(1);
        }
    }
    private static void loadCustomConfig(QualityGateConfig config) {
        // 从配置文件或环境变量加载自定义配置
        Properties props = new Properties();
        try (InputStream in = QualityGateExecutor.class
                .getResourceAsStream("/quality-gate.properties")) {
            if (in != null) {
                props.load(in);
                // 解析并覆盖默认配置
                props.forEach((key, value) -> {
                    String metricKey = key.toString().replace("quality.","");
                    String[] keyParts = metricKey.split("\\.");
                    if (keyParts.length == 2) {
                        config.setThreshold(keyParts[0] + "." + keyParts[1], 
                            parseValue(value.toString()));
                    }
                });
            }
        } catch (IOException e) {
            System.err.println("Error loading quality gate configuration: " + e.getMessage());
        }
    }
    private static Object parseValue(String value) {
        if (value.contains(".")) {
            try {
                return Double.parseDouble(value);
            } catch (NumberFormatException e) {
                return value;
            }
        }
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
                return Boolean.parseBoolean(value);
            }
            return value;
        }
    }
}

6 测试类

package com.example.qualitygate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class QualityGateRunnerTest {
    private QualityGateConfig config;
    private QualityGateRunner runner;
    @BeforeEach
    void setUp() {
        config = QualityGateConfig.defaultConfig();
        runner = new QualityGateRunner(config);
    }
    @Test
    void testPassingQualityGate() {
        // 使用高质量代码目录
        QualityGateResult result = runner.execute(
            "src/test/resources/good-code",
            "src/test/resources/good-code-test"
        );
        assertTrue(result.isPassed(), "Quality gate should pass for good code");
        assertTrue(result.getViolations().isEmpty());
    }
    @Test
    void testFailingQualityGate() {
        // 使用低质量代码目录
        QualityGateResult result = runner.execute(
            "src/test/resources/bad-code",
            "src/test/resources/bad-code-test"
        );
        assertFalse(result.isPassed(), "Quality gate should fail for bad code");
        assertFalse(result.getViolations().isEmpty());
    }
    @Test
    void testCustomThreshold() {
        config.setThreshold("complexity.max", 5); // 更严格的复杂度限制
        runner = new QualityGateRunner(config);
        QualityGateResult result = runner.execute(
            "src/test/resources/complex-code",
            "src/test/resources/complex-code-test"
        );
        assertFalse(result.isPassed());
        assertTrue(result.getViolations().stream()
            .anyMatch(v -> v.contains("complexity")));
    }
    @Test
    void testEmptySource() {
        QualityGateResult result = runner.execute("", "");
        assertFalse(result.isPassed());
    }
}

配置示例 (quality-gate.properties)

# 质量门禁配置
# 代码规模
quality.linesOfCode.max=2000
quality.classes.max=30
quality.methods.max=50
# 复杂度
quality.complexity.max=10
# 覆盖率
quality.coverage.min=80.0
# 重复率
quality.duplication.max=5.0
# 质量指标
quality.bugs.max=0
quality.vulnerabilities.max=0
quality.codeSmells.max=100
# 测试指标
quality.testRatio.min=0.8
# 其他设置
quality.failOnError=true

CI 集成 (GitLab CI)

# .gitlab-ci.yml
stages:
  - build
  - test
  - quality
  - deploy
quality-gate:
  stage: quality
  script:
    - mvn verify
    - mvn checkstyle:check
    - mvn pmd:check
    - mvn spotbugs:check
    - java -jar quality-gate.jar ./src ./test
  artifacts:
    when: always
    paths:
      - target/surefire-reports/
    reports:
      junit:
        - target/surefire-reports/TEST-*.xml

使用说明

构建和运行

# 编译项目
mvn clean package
# 运行质量门禁
java -jar target/quality-gate.jar ./src/main/java ./src/test/java
# 执行完整质量检查
mvn verify
mvn checkstyle:check
mvn pmd:check
mvn spotbugs:check

集成到 CI/CD

  1. Jenkins Pipeline

    pipeline {
     stages {
         stage('Quality Gate') {
             steps {
                 sh 'mvn verify'
                 sh 'mvn checkstyle:check pmd:check spotbugs:check'
             }
         }
     }
    }
  2. GitHub Actions

    name: Quality Gate
    on: [push, pull_request]
    jobs:
    quality:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
       - name: Set up JDK
         uses: actions/setup-java@v2
         with:
           java-version: '11'
       - name: Run quality gate
         run: |
           mvn verify
           mvn checkstyle:check
           mvn pmd:check

最佳实践

  1. 分级门禁: 根据项目阶段设置不同严格度的门禁
  2. 渐进式改进: 逐步提高质量标准
  3. 持续集成: 将门禁集成到CI流程
  4. 定制规则: 根据团队情况自定义规则
  5. 自动修复: 集成自动格式化工具
  6. 定期审查: 定期审查和更新质量规则

这个案例提供了一个完整的Java代码质量门禁实现,可以根据实际需求进行调整和扩展。

上一篇Java Sonar集成案例

下一篇当前分类已是最新一篇

抱歉,评论功能暂时关闭!