企業級程式碼品質分析方法論教學手冊

版本:v2.0|日期:2026-03-30|適用對象:初階至資深工程師、Tech Lead、架構師
適用技術棧:Java / Spring Boot、Vue / TypeScript、微服務架構
適用產業:銀行、金融業、保險業等高穩定度 / 高安全系統
重要更新:OWASP Top 10: 2025 版、PMD 7.x、Checkstyle 13.x、SpotBugs 4.9.x、ESLint 9.x Flat Config


📋 目錄


第 1 章:程式碼品質概論

1.1 程式碼品質的定義

程式碼品質不只是「能跑就好」,在企業級系統中,品質直接影響系統的穩定性、安全性與長期維護成本。

品質的四個核心維度

維度英文說明
可維護性Maintainability修改程式碼的難易程度,能否快速修 Bug、加新功能
可讀性Readability他人是否能快速理解程式碼意圖
可靠性Reliability系統在各種情況下能否正確運作
安全性Security是否存在可被利用的安全漏洞

架構師觀點:在銀行系統中,一個安全漏洞可能導致數億元損失;一個效能問題可能在月底批次作業時造成全行當機。品質不是可選項,而是必要條件。

好程式碼的特徵

// ✅ Good:意圖清晰、命名有意義、職責單一
public class TransferService {
    
    private final AccountRepository accountRepository;
    private final TransactionLogger transactionLogger;
    
    /**
     * 執行帳戶間轉帳
     * @param fromAccountId 轉出帳號
     * @param toAccountId 轉入帳號
     * @param amount 金額(必須大於零)
     * @throws InsufficientFundsException 餘額不足時拋出
     */
    public TransferResult transfer(String fromAccountId, String toAccountId, BigDecimal amount) {
        validateAmount(amount);
        
        Account fromAccount = accountRepository.findById(fromAccountId)
            .orElseThrow(() -> new AccountNotFoundException(fromAccountId));
        Account toAccount = accountRepository.findById(toAccountId)
            .orElseThrow(() -> new AccountNotFoundException(toAccountId));
        
        fromAccount.debit(amount);
        toAccount.credit(amount);
        
        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
        
        transactionLogger.log(fromAccountId, toAccountId, amount);
        
        return TransferResult.success(fromAccountId, toAccountId, amount);
    }
    
    private void validateAmount(BigDecimal amount) {
        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new InvalidAmountException("轉帳金額必須大於零");
        }
    }
}
// ❌ Bad:命名模糊、邏輯混亂、沒有校驗
public class Svc {
    public void doIt(String a, String b, double amt) {
        // 直接操作 DB,沒有交易控制
        db.execute("UPDATE account SET balance = balance - " + amt + " WHERE id = '" + a + "'");
        db.execute("UPDATE account SET balance = balance + " + amt + " WHERE id = '" + b + "'");
    }
}

上述 Bad 範例存在 SQL Injection 漏洞、無金額校驗、無交易控制、命名不可讀等多項問題。


1.2 技術債(Technical Debt)

技術債是指為了短期快速交付而做出的技術妥協,這些妥協在未來需要花費更多成本來修復。

技術債的類型

mindmap
  root((技術債))
    刻意的
      短期趕工
      跳過測試
      暫時方案
    非刻意的
      知識不足
      設計劣化
      需求演化
    環境變化
      框架過時
      安全漏洞
      平台 EOS

技術債的量化

指標計算方式建議閾值
技術債比率修復成本 ÷ 重新開發成本 × 100%< 5%
技術債天數SonarQube 計算的修復工時< 21 天(每萬行)
新增技術債每次 Sprint 新增的技術債趨勢遞減

技術債的四象限模型

魯莽(Reckless)謹慎(Prudent)
刻意(Deliberate)「我們沒時間寫測試」「我們知道有風險,先上線後排期修復」
非刻意(Inadvertent)「什麼是 SOLID 原則?」「現在我們才知道更好的做法」

金融業案例:某銀行核心系統累積 10 年技術債,平台 EOS(End of Support)後,每年花費超過原始開發 3 倍成本進行維護與安全修補。


1.3 為什麼企業需要品質管理

品質管理的 ROI(投資報酬率)

graph LR
    A[品質投入] --> B[開發階段]
    A --> C[測試階段]
    A --> D[維運階段]
    
    B -->|修復成本 1x| E[Bug 修復成本]
    C -->|修復成本 10x| E
    D -->|修復成本 100x| E
    
    style A fill:#4CAF50,color:#fff
    style E fill:#f44336,color:#fff

越早發現問題,修復成本越低

階段修復成本倍數說明
開發中1xIDE / 靜態分析提示
Code Review3xPR 被打回重寫
QA 測試10x需排查、修復、重測
生產環境100x影響客戶、需緊急修復、調查報告

企業級品質管理的驅動因素

  1. 法規遵循:金融業受金管會、PCIDSS 等法規約束
  2. 安全要求:OWASP Top 10 的防護
  3. 營運穩定:7×24 服務不停機
  4. 成本控制:減少維運花費
  5. 團隊效率:降低新人上手難度

1.4 品質管理全景圖

graph TB
    subgraph "開發階段"
        A[Coding Standard] --> B[IDE 即時檢查]
        B --> C[Pre-commit Hook]
    end
    
    subgraph "Review 階段"
        C --> D[Pull Request]
        D --> E[Code Review]
        E --> F[AI 輔助 Review]
    end
    
    subgraph "CI/CD 階段"
        F --> G[靜態分析<br/>SonarQube]
        G --> H[單元測試]
        H --> I[整合測試]
        I --> J[安全掃描]
        J --> K{Quality Gate}
    end
    
    subgraph "部署階段"
        K -->|Pass| L[部署到 SIT]
        K -->|Fail| M[阻擋部署]
        L --> N[效能測試]
        N --> O[部署到 UAT]
        O --> P[部署到 Production]
    end
    
    style K fill:#FF9800,color:#fff
    style M fill:#f44336,color:#fff
    style P fill:#4CAF50,color:#fff

1.5 實務落地建議

  1. 不要一次導入所有品質工具:先從 SonarQube + 基本 Code Review 開始
  2. 先量化現狀:掃描現有程式碼,建立 baseline
  3. 設定可達成的目標:例如「新程式碼 Bug 率 < A 等級」
  4. 建立品質文化:品質不是 QA 的事,是所有人的事
  5. 銀行業注意:安全性檢查應列為最高優先順序

第 2 章:程式碼品質模型(核心方法論)

2.1 品質模型總覽

本手冊提出一套「企業級六維品質模型(Enterprise Six-Dimension Quality Model, E6DQM)」,適用於大型企業系統。

graph TB
    center((程式碼品質)) --> R[可讀性<br/>Readability]
    center --> M[可維護性<br/>Maintainability]
    center --> T[可測試性<br/>Testability]
    center --> P[效能<br/>Performance]
    center --> S[安全性<br/>Security]
    center --> SC[可擴展性<br/>Scalability]
    
    style center fill:#2196F3,color:#fff
    style R fill:#4CAF50,color:#fff
    style M fill:#8BC34A,color:#fff
    style T fill:#CDDC39,color:#000
    style P fill:#FF9800,color:#fff
    style S fill:#f44336,color:#fff
    style SC fill:#9C27B0,color:#fff

品質維度權重建議(依產業別)

維度銀行/金融電商內部系統
可讀性★★★★★★★★★★
可維護性★★★★★★★★★★★★
可測試性★★★★★★★★★
效能★★★★★★★★★★★★
安全性★★★★★★★★★★★★
可擴展性★★★★★★★★★★★

2.2 可讀性(Readability)

定義

程式碼能被其他開發者在合理時間內理解的程度。一段好的程式碼,應該像散文一樣可以被「閱讀」。

評估方式

指標量化方式建議閾值
方法行數Lines Per Method≤ 30 行
類別行數Lines Per Class≤ 500 行
巢狀深度Nesting Depth≤ 3 層
命名品質命名規範符合率≥ 95%
認知複雜度Cognitive Complexity (SonarQube)≤ 15

常見問題

// ❌ Bad:巢狀太深、命名不清
public void proc(List<Map<String, Object>> d) {
    for (Map<String, Object> m : d) {
        if (m.get("type") != null) {
            if (m.get("type").equals("A")) {
                if (m.get("status") != null) {
                    if (m.get("status").equals("1")) {
                        // 處理邏輯...
                    }
                }
            }
        }
    }
}

改善方法

// ✅ Good:Early Return + 有意義命名 + Guard Clause
public void processTransactions(List<Transaction> transactions) {
    for (Transaction transaction : transactions) {
        if (!transaction.isTypeA()) {
            continue;
        }
        if (!transaction.isActive()) {
            continue;
        }
        processActiveTypeATransaction(transaction);
    }
}

private void processActiveTypeATransaction(Transaction transaction) {
    // 處理邏輯...
}

改善技巧摘要

  1. 使用 Guard Clause(衛語句)取代巢狀 if
  2. 提取方法(Extract Method)
  3. 使用 有意義的命名(不要用 d, m, proc
  4. 善用 Java Stream API 增強表達力

2.3 可維護性(Maintainability)

定義

修改、擴展、修復程式碼的難易程度。可維護性高的系統,能讓團隊在最小風險下進行變更。

評估方式

指標量化方式建議閾值
圈複雜度Cyclomatic Complexity≤ 10(每個方法)
耦合度Coupling Between Objects (CBO)≤ 8
繼承深度Depth of Inheritance Tree (DIT)≤ 4
LCOM4Lack of Cohesion of Methods≤ 1(理想值)
SonarQube 評級Maintainability RatingA

常見問題

  • God Class:一個類別超過 2000 行,包辦所有功能
  • Shotgun Surgery:改一個需求要改 10+ 個檔案
  • Copy-Paste Code:重複程式碼散落各處

改善方法

遵循 SOLID 原則

// ❌ Bad:違反 SRP,一個類別包辦帳務、通知、報表
public class AccountManager {
    public void createAccount(AccountDto dto) { /* ... */ }
    public void sendEmail(String to, String body) { /* ... */ }
    public void generateReport(Date from, Date to) { /* ... */ }
    public void calculateInterest(String accountId) { /* ... */ }
}

// ✅ Good:職責分離
public class AccountService {
    public void createAccount(AccountDto dto) { /* ... */ }
    public void calculateInterest(String accountId) { /* ... */ }
}

public class NotificationService {
    public void sendEmail(String to, String body) { /* ... */ }
}

public class ReportService {
    public void generateReport(Date from, Date to) { /* ... */ }
}

關鍵原則

原則說明違反時的症狀
SRP單一職責類別太大,改一處影響多處
OCP開放封閉加新功能需改現有程式碼
LSPLiskov 替換子類別無法替換父類別
ISP介面隔離實作被迫 override 不需要的方法
DIP依賴反轉高階模組直接依賴低階模組

2.4 可測試性(Testability)

定義

程式碼能被有效測試的程度。可測試性高的程式碼,可以輕鬆寫出快速、穩定、獨立的測試。

評估方式

指標量化方式建議閾值
測試覆蓋率Line / Branch Coverage≥ 80%(新程式碼)
測試執行時間Unit Test Total Time≤ 5 分鐘
可 Mock 性依賴注入比率100%
測試穩定性Flaky Test Rate≤ 1%

常見問題

// ❌ Bad:在方法內部直接 new 依賴,無法 Mock
public class OrderService {
    public void placeOrder(OrderDto dto) {
        // 直接 new,無法替換
        EmailService emailService = new EmailService();
        PaymentGateway gateway = new PaymentGateway();
        
        gateway.charge(dto.getAmount());
        emailService.send(dto.getEmail(), "訂單成功");
    }
}

改善方法

// ✅ Good:依賴注入,可輕鬆 Mock
@Service
public class OrderService {
    
    private final EmailService emailService;
    private final PaymentGateway paymentGateway;
    
    // 建構子注入
    public OrderService(EmailService emailService, PaymentGateway paymentGateway) {
        this.emailService = emailService;
        this.paymentGateway = paymentGateway;
    }
    
    public void placeOrder(OrderDto dto) {
        paymentGateway.charge(dto.getAmount());
        emailService.send(dto.getEmail(), "訂單成功");
    }
}
// ✅ 對應的測試
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    
    @Mock
    private EmailService emailService;
    
    @Mock
    private PaymentGateway paymentGateway;
    
    @InjectMocks
    private OrderService orderService;
    
    @Test
    void placeOrder_shouldChargeAndSendEmail() {
        OrderDto dto = new OrderDto("user@bank.com", BigDecimal.valueOf(1000));
        
        orderService.placeOrder(dto);
        
        verify(paymentGateway).charge(BigDecimal.valueOf(1000));
        verify(emailService).send("user@bank.com", "訂單成功");
    }
}

2.5 效能(Performance)

定義

程式碼在給定資源條件下的執行效率,包含回應時間、吞吐量、資源使用率。

評估方式

指標量化方式建議閾值(銀行等級)
回應時間P95 Response Time≤ 200ms(API)
吞吐量TPS (Transactions Per Second)≥ 1000 TPS
CPU 使用率Peak CPU≤ 70%
記憶體Heap Usage≤ 80%
資料庫查詢Slow Query Rate≤ 0.1%

常見問題

// ❌ Bad:N+1 Query 問題
public List<OrderDetailDto> getOrders(List<String> orderIds) {
    List<OrderDetailDto> results = new ArrayList<>();
    for (String orderId : orderIds) {
        // 每筆訂單都查一次 DB(N+1)
        Order order = orderRepository.findById(orderId).orElse(null);
        if (order != null) {
            results.add(toDto(order));
        }
    }
    return results;
}

改善方法

// ✅ Good:批次查詢
public List<OrderDetailDto> getOrders(List<String> orderIds) {
    return orderRepository.findAllById(orderIds)
        .stream()
        .map(this::toDto)
        .collect(Collectors.toList());
}

效能最佳實踐

  1. 避免 N+1 Query,使用 @EntityGraphJOIN FETCH
  2. 適當使用快取(Redis / Caffeine)
  3. 資料庫索引優化
  4. 避免在迴圈中做 I/O 操作
  5. 使用連線池(HikariCP)

2.6 安全性(Security)

定義

程式碼抵禦攻擊、保護資料的能力。在金融業中,安全性是最高優先順序。

評估方式

指標量化方式建議閾值
OWASP 漏洞數靜態掃描結果0(Critical / High)
相依套件漏洞CVE 數量0(Known Exploited)
Secrets 洩漏Hard-coded 密碼/金鑰0
安全測試覆蓋Security Test Coverage≥ 100%(關鍵流程)

常見問題(OWASP Top 10: 2025)

⚠️ 重要更新:OWASP 於 2025 年發布全新版本 Top 10,取代 2021 版。主要變動包括新增「A03 軟體供應鏈失敗」與「A10 異常條件處理不當」,反映供應鏈攻擊與錯誤處理成為當代主要威脅。

// ❌ Bad:SQL Injection(A05:2025 - Injection)
public User findUser(String username) {
    String sql = "SELECT * FROM users WHERE username = '" + username + "'";
    return jdbcTemplate.queryForObject(sql, User.class);
}

改善方法

// ✅ Good:Parameterized Query
public User findUser(String username) {
    String sql = "SELECT * FROM users WHERE username = ?";
    return jdbcTemplate.queryForObject(sql, new Object[]{username}, userRowMapper);
}

// ✅ Better:使用 Spring Data JPA
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

安全性檢查清單(OWASP Top 10: 2025)

排名OWASP 2025 項目防護方式
A01Broken Access Control(存取控制失效)RBAC / 最小權限原則 / 預設拒絕存取
A02Security Misconfiguration(安全設定錯誤)安全 Header / 最小化安裝 / Production Profile
A03Software Supply Chain Failures(軟體供應鏈失敗) 🆕SCA 掃描 / SBOM 管理 / 相依套件簽章驗證
A04Cryptographic Failures(加密失敗)TLS 1.3 / AES-256 / 禁用弱雜湊(MD5/SHA1)
A05Injection(注入攻擊)Parameterized Query / ORM / 輸入驗證
A06Insecure Design(不安全設計)威脅建模 / 安全設計審查 / Abuse Cases
A07Authentication Failures(認證失敗)MFA / 密碼強度策略 / Session 管理
A08Software or Data Integrity Failures(軟體與資料完整性失敗)數位簽章 / CI/CD Pipeline 安全 / 白名單驗證
A09Security Logging and Alerting Failures(安全日誌與告警失敗)完整稽核日誌 / SIEM 整合 / 即時告警
A10Mishandling of Exceptional Conditions(異常條件處理不當) 🆕統一錯誤處理 / 避免洩漏堆疊資訊 / Fail-safe 設計

📌 2025 vs 2021 主要變動

  • 新增 A03:Software Supply Chain Failures(原 A06 Vulnerable and Outdated Components 擴展而來,強調整個供應鏈安全)
  • 新增 A10:Mishandling of Exceptional Conditions(取代 SSRF,強調例外處理不當的風險)
  • A02 上升:Security Misconfiguration 從 A05 升至 A02,反映雲端環境設定錯誤問題日益嚴重
  • 參考OWASP Top 10: 2025 官方網站

2.7 可擴展性(Scalability)

定義

系統應對增長(使用者、資料量、功能)的能力,包含水平擴展與垂直擴展。

評估方式

指標量化方式建議
模組化程度Package Coupling低耦合
介面抽象度Interface Usage Rate≥ 80%(外部依賴)
設定外部化Hard-coded Config0
無狀態設計Stateless Service Rate100%(Web 層)

常見問題

// ❌ Bad:Hard-coded 設定、緊耦合
public class PaymentService {
    private static final String API_URL = "https://payment.bank.com/api/v1";
    private static final int TIMEOUT = 3000;
    
    public void pay(PaymentRequest request) {
        // 直接使用 HttpClient,無法切換實作
        HttpClient client = HttpClient.newHttpClient();
        // ...
    }
}

改善方法

// ✅ Good:設定外部化 + 介面抽象
@ConfigurationProperties(prefix = "payment.gateway")
public class PaymentGatewayProperties {
    private String apiUrl;
    private int timeoutMs = 3000;
    // getters / setters
}

public interface PaymentGateway {
    PaymentResult pay(PaymentRequest request);
}

@Service
@Profile("production")
public class BankPaymentGateway implements PaymentGateway {
    
    private final PaymentGatewayProperties properties;
    private final RestTemplate restTemplate;
    
    public BankPaymentGateway(PaymentGatewayProperties properties, RestTemplate restTemplate) {
        this.properties = properties;
        this.restTemplate = restTemplate;
    }
    
    @Override
    public PaymentResult pay(PaymentRequest request) {
        return restTemplate.postForObject(
            properties.getApiUrl() + "/pay",
            request,
            PaymentResult.class
        );
    }
}

2.8 實務落地建議

  1. 從最重要的維度開始:銀行系統先顧安全性與可靠性
  2. 每個維度設定可量化指標:透過 SonarQube Dashboard 追蹤
  3. 不要追求完美:先達到「及格線」,再逐步提升
  4. 定期 Review 品質趨勢:每兩週一次品質回顧會議
  5. 工具自動化:人工檢查不可靠,必須仰賴工具

第 3 章:程式碼異味(Code Smells)

3.1 什麼是 Code Smell

Code Smell 不是 Bug,而是程式碼中「暗示潛在問題」的跡象。它不會讓程式當場出錯,但會逐漸侵蝕系統品質。

Martin Fowler:「A code smell is a surface indication that usually corresponds to a deeper problem in the system.」

flowchart LR
    A[Code Smell<br/>程式碼異味] --> B[如果忽略]
    B --> C[技術債累積]
    C --> D[維護成本增加]
    D --> E[系統逐漸腐化]
    E --> F[被迫重寫]
    
    style A fill:#FF9800,color:#fff
    style F fill:#f44336,color:#fff

3.2 常見 15+ 種 Code Smells

Smell #1:Long Method(過長方法)

說明:方法超過 30 行,難以理解和測試。

// ❌ Bad:一個方法做太多事
public TransactionResult processTransaction(TransactionRequest request) {
    // 驗證... (20行)
    // 查詢帳戶... (15行)
    // 計算手續費... (25行)
    // 執行轉帳... (20行)
    // 發送通知... (15行)
    // 產生報表... (10行)
    // 共 105 行
}
// ✅ Good:拆分為小方法
public TransactionResult processTransaction(TransactionRequest request) {
    validate(request);
    Account account = findAccount(request.getAccountId());
    BigDecimal fee = calculateFee(request);
    TransactionResult result = executeTransfer(account, request, fee);
    sendNotification(result);
    return result;
}

改善方式:Extract Method(提取方法)


Smell #2:God Class(上帝類別)

說明:一個類別承擔過多職責,通常超過 500-1000 行。

// ❌ Bad:什麼都做
public class BankingHelper {
    public void createAccount() { }
    public void transferMoney() { }
    public void sendSMS() { }
    public void generatePDF() { }
    public void calculateTax() { }
    public void syncExternalSystem() { }
    // ... 2000+ 行
}
// ✅ Good:按職責拆分
public class AccountService { /* 帳戶相關 */ }
public class TransferService { /* 轉帳相關 */ }
public class NotificationService { /* 通知相關 */ }
public class ReportService { /* 報表相關 */ }
public class TaxCalculationService { /* 稅務計算 */ }

改善方式:Extract Class(提取類別)+ SRP


Smell #3:Primitive Obsession(基本型別偏執)

說明:用 Stringint 等基本型別表達業務概念。

// ❌ Bad
public void transfer(String fromAccount, String toAccount, double amount, String currency) {
    // fromAccount 格式是什麼?amount 可以是負數嗎?currency 有哪些?
}
// ✅ Good:使用 Value Object
public void transfer(AccountNumber from, AccountNumber to, Money amount) {
    // 型別安全,自帶校驗
}

public record AccountNumber(String value) {
    public AccountNumber {
        if (!value.matches("\\d{3}-\\d{2}-\\d{7}")) {
            throw new InvalidAccountNumberException(value);
        }
    }
}

public record Money(BigDecimal amount, Currency currency) {
    public Money {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new NegativeAmountException(amount);
        }
    }
}

改善方式:Replace Primitive with Object / Introduce Value Object


Smell #4:Feature Envy(依戀情結)

說明:一個方法大量使用其他類別的資料,而非自己類別的資料。

// ❌ Bad:大量存取 account 的內部資料
public class TransactionValidator {
    public boolean isValid(Account account, BigDecimal amount) {
        return account.getBalance().subtract(account.getHoldAmount())
            .subtract(account.getPendingAmount())
            .compareTo(amount) >= 0
            && account.getStatus().equals("ACTIVE")
            && !account.isFrozen();
    }
}
// ✅ Good:邏輯放到資料所在的類別
public class Account {
    public boolean canWithdraw(BigDecimal amount) {
        BigDecimal available = balance.subtract(holdAmount).subtract(pendingAmount);
        return available.compareTo(amount) >= 0
            && "ACTIVE".equals(status)
            && !frozen;
    }
}

改善方式:Move Method(搬移方法)


Smell #5:Duplicate Code(重複程式碼)

說明:相同或極相似的程式碼出現在多處。

// ❌ Bad:多個 Controller 都有相同的日期驗證
// AccountController.java
if (startDate.isAfter(endDate)) {
    throw new BadRequestException("起始日不得大於結束日");
}

// TransactionController.java
if (startDate.isAfter(endDate)) {
    throw new BadRequestException("起始日不得大於結束日");
}
// ✅ Good:提取為共用方法或工具類
public class DateValidator {
    public static void validateRange(LocalDate startDate, LocalDate endDate) {
        if (startDate.isAfter(endDate)) {
            throw new BadRequestException("起始日不得大於結束日");
        }
    }
}

改善方式:Extract Method / Pull Up Method


Smell #6:Shotgun Surgery(散彈式修改)

說明:一個業務需求的變更需要修改多個類別。

改善方式:Move Method / Move Field,將相關邏輯集中到同一個模組。


Smell #7:Data Clumps(資料泥團)

說明:一組資料總是一起出現多個方法簽名中。

// ❌ Bad:startDate, endDate 總是一起出現
public List<Transaction> search(LocalDate startDate, LocalDate endDate, String accountId) { }
public BigDecimal summarize(LocalDate startDate, LocalDate endDate, String accountId) { }
public Report generate(LocalDate startDate, LocalDate endDate, String accountId) { }
// ✅ Good:封裝為物件
public record TransactionQuery(DateRange dateRange, AccountNumber accountId) { }
public record DateRange(LocalDate from, LocalDate to) { }

public List<Transaction> search(TransactionQuery query) { }
public BigDecimal summarize(TransactionQuery query) { }
public Report generate(TransactionQuery query) { }

改善方式:Introduce Parameter Object


Smell #8:Dead Code(死碼)

說明:永遠不會被執行的程式碼。

// ❌ Bad
public void process() {
    return;
    System.out.println("永遠不會執行"); // Dead Code
}

// 或是被註解掉的舊程式碼
// public void oldMethod() { ... }  // TODO: 確認後刪除(已存在 3 年)

改善方式:直接刪除。版本控制(Git)會保留歷史。


Smell #9:Comments as Deodorant(用註解掩蓋臭味)

說明:需要大量註解才能理解的程式碼,本身可讀性就有問題。

// ❌ Bad
// 檢查使用者是否有權限且帳戶是有效的且金額大於零
if (u.getR() == 1 && a.getS().equals("A") && amt > 0) { }

// ✅ Good:程式碼自己說話
if (user.hasTransferPermission() && account.isActive() && amount.isPositive()) { }

改善方式:重新命名 + Extract Method,讓程式碼自文件化。


Smell #10:Magic Number(魔術數字)

說明:程式碼中出現無意義的數字常數。

// ❌ Bad
if (account.getBalance().compareTo(new BigDecimal("50000")) > 0) {
    applyDiscount(0.15);
}

// ✅ Good
private static final BigDecimal VIP_THRESHOLD = new BigDecimal("50000");
private static final BigDecimal VIP_DISCOUNT_RATE = new BigDecimal("0.15");

if (account.getBalance().compareTo(VIP_THRESHOLD) > 0) {
    applyDiscount(VIP_DISCOUNT_RATE);
}

改善方式:Replace Magic Number with Named Constant


Smell #11:Large Class(過大類別)

說明:類別包含過多的欄位和方法,超過 500 行。

改善方式:Extract Class / Extract Subclass


Smell #12:Inappropriate Intimacy(不當親密)

說明:兩個類別過度了解對方的內部實作細節。

改善方式:Move Method / Move Field / Extract Class


Smell #13:Speculative Generality(推測性泛化)

說明:為了「未來可能用到」而過度設計的抽象層。

// ❌ Bad:只有一種實作卻建了抽象層
public interface DataProcessor { }
public abstract class AbstractDataProcessor implements DataProcessor { }
public class ConcreteDataProcessor extends AbstractDataProcessor { }
// 四年了仍然只有一種實作

改善方式:刪除不必要的抽象。YAGNI(You Aren’t Gonna Need It)。


Smell #14:Switch Statements(過多 Switch/If-else)

說明:冗長的 switch/if-else 鏈,通常在多處重複。

// ❌ Bad
public BigDecimal calculateFee(String accountType, BigDecimal amount) {
    switch (accountType) {
        case "SAVINGS": return amount.multiply(new BigDecimal("0.001"));
        case "CHECKING": return amount.multiply(new BigDecimal("0.002"));
        case "VIP": return BigDecimal.ZERO;
        default: throw new IllegalArgumentException("Unknown type");
    }
}
// ✅ Good:使用多型
public interface FeeStrategy {
    BigDecimal calculate(BigDecimal amount);
}

public class SavingsFeeStrategy implements FeeStrategy {
    @Override
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.001"));
    }
}

// 使用 Map 或 Spring @Qualifier 注入

改善方式:Replace Conditional with Polymorphism


Smell #15:Lazy Class(冗餘類別)

說明:類別做的事太少,不值得獨立存在。

改善方式:Inline Class(將內容合併到使用者類別)


Smell #16:Middle Man(中間人)

說明:一個類別的大部分方法只是委派給另一個物件。

// ❌ Bad:幾乎所有方法都只是 delegate
public class AccountManager {
    private final AccountService service;
    
    public Account find(String id) { return service.find(id); }
    public void save(Account a) { service.save(a); }
    public void delete(String id) { service.delete(id); }
    // 沒有任何自己的邏輯
}

改善方式:Remove Middle Man,讓呼叫者直接使用被委派的物件。


Smell #17:Refused Bequest(被拒絕的遺產)

說明:子類別繼承了父類別但只用到少部分功能,或覆寫拋出 UnsupportedOperationException

改善方式:使用組合(Composition)取代繼承(Inheritance),或重新設計類別階層。


3.3 實務落地建議

  1. 導入 SonarQube:自動偵測大部分 Code Smells
  2. 在 Code Review 中關注 Top 5:Long Method、God Class、Duplicate Code、Magic Number、Primitive Obsession
  3. 每 Sprint 分配 10-15% 時間修復 Code Smells
  4. 建立團隊共識:哪些異味必須修、哪些可以暫緩
  5. 銀行業優先:與安全性相關的 Smell(如 Dead Code、不當親密)優先處理

第 4 章:靜態分析與工具

4.1 靜態分析概述

靜態分析是指不執行程式碼的情況下,透過分析原始碼或位元碼來發現潛在問題的技術。它是品質管理自動化的基石。

flowchart LR
    A[原始碼] --> B[靜態分析引擎]
    B --> C[規則庫]
    C --> D{分析結果}
    D --> E[Bugs]
    D --> F[Code Smells]
    D --> G[Security Vulnerabilities]
    D --> H[Coverage]
    
    style B fill:#2196F3,color:#fff
    style E fill:#f44336,color:#fff
    style G fill:#f44336,color:#fff

靜態分析的效益

效益說明
早期發現在開發階段即發現問題,成本最低
自動化無需人工逐行檢查
一致性統一標準,不因 Reviewer 不同而有落差
持續追蹤品質趨勢可視化

4.2 SonarQube

功能概述

SonarQube 是目前最廣泛使用的企業級程式碼品質平台,支援 30+ 種程式語言。

核心功能

  • Bugs 偵測:可能導致執行錯誤的程式碼
  • Vulnerabilities:安全漏洞偵測
  • Code Smells:可維護性問題
  • Coverage:測試覆蓋率追蹤
  • Duplications:重複程式碼分析
  • Quality Gate:品質門檻控管

SonarQube 品質評級系統

評級Technical Debt Ratio說明
A≤ 5%優良
B6-10%尚可
C11-20%需改善
D21-50%嚴重
E> 50%危急

企業導入配置範例

# sonar-project.properties(Java Spring Boot 專案)
sonar.projectKey=banking-core-system
sonar.projectName=Banking Core System
sonar.projectVersion=1.0

# 原始碼路徑
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.java.binaries=target/classes

# 排除規則(不掃描自動產生的程式碼)
sonar.exclusions=**/generated/**,**/dto/**/*Mapper.java

# 覆蓋率報告
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml

# 編碼
sonar.sourceEncoding=UTF-8

Maven 整合

<!-- pom.xml -->
<build>
    <plugins>
        <!-- JaCoCo 覆蓋率 -->
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.12</version>
            <executions>
                <execution>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <execution>
                    <id>report</id>
                    <phase>test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
# 執行掃描
mvn clean verify sonar:sonar \
  -Dsonar.host.url=https://sonarqube.company.com \
  -Dsonar.token=${SONAR_TOKEN}

4.3 ESLint

功能概述

ESLint 是 JavaScript / TypeScript 的靜態分析工具,適用於前端(Vue / React)專案。

📌 版本說明:ESLint 9.x 起全面採用 Flat Configeslint.config.js)格式,取代舊版 .eslintrc.*。以下同時提供兩種格式範例。

核心功能

  • 語法錯誤偵測
  • 程式碼風格統一
  • 最佳實踐規則
  • 可自訂規則

企業級 ESLint 配置範例(Flat Config — ESLint 9.x+ 推薦)

// eslint.config.js(Vue 3 + TypeScript 專案)
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginVue from 'eslint-plugin-vue';
import pluginSecurity from 'eslint-plugin-security';

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  ...pluginVue.configs['flat/recommended'],
  {
    files: ['**/*.{ts,vue}'],
    plugins: {
      security: pluginSecurity,
    },
    rules: {
      // 命名規範
      '@typescript-eslint/naming-convention': [
        'error',
        { selector: 'interface', format: ['PascalCase'] },
        { selector: 'class', format: ['PascalCase'] },
        { selector: 'variable', format: ['camelCase', 'UPPER_CASE'] },
      ],
      // 複雜度限制
      'complexity': ['error', { max: 10 }],
      'max-lines-per-function': ['warn', { max: 50 }],
      'max-depth': ['error', { max: 3 }],
      // 安全性
      'no-eval': 'error',
      'no-implied-eval': 'error',
    },
  },
);

舊版格式(.eslintrc.js — ESLint 8.x 及更早)

點擊展開舊版配置
// .eslintrc.js(Vue 3 + TypeScript 專案)
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2022: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:security/recommended',
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2022,
    sourceType: 'module',
  },
  rules: {
    // 命名規範
    '@typescript-eslint/naming-convention': [
      'error',
      { selector: 'interface', format: ['PascalCase'] },
      { selector: 'class', format: ['PascalCase'] },
      { selector: 'variable', format: ['camelCase', 'UPPER_CASE'] },
    ],
    // 複雜度限制
    'complexity': ['error', { max: 10 }],
    'max-lines-per-function': ['warn', { max: 50 }],
    'max-depth': ['error', { max: 3 }],
    // 安全性
    'no-eval': 'error',
    'no-implied-eval': 'error',
  },
};

4.4 PMD / Checkstyle

PMD

PMD 是 Java 靜態分析工具,專注於程式碼問題偵測(如未使用變數、空 catch、不必要的物件建立)。

📌 版本說明:PMD 引擎最新版本為 7.23.0(≥ 7.0 為重大版本升級,規則格式有變動)。以下使用 Apache Maven PMD Plugin 3.24.0,它預設纁結 PMD 7.x 引擎。

<!-- pom.xml:PMD Plugin(使用 PMD 7.x 引擎) -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-pmd-plugin</artifactId>
    <version>3.24.0</version>
    <configuration>
        <rulesets>
            <ruleset>category/java/bestpractices.xml</ruleset>
            <ruleset>category/java/errorprone.xml</ruleset>
            <ruleset>category/java/security.xml</ruleset>
            <ruleset>category/java/performance.xml</ruleset>
        </rulesets>
        <failOnViolation>true</failOnViolation>
        <printFailingErrors>true</printFailingErrors>
    </configuration>
</plugin>

Checkstyle

Checkstyle 專注於程式碼風格與格式(命名、縮排、JavaDoc)。

📌 版本說明:Checkstyle 引擎最新版本為 13.4.0(≥ 13.0.0 要求 JDK 21+)。若專案使用 JDK 17,請使用 Checkstyle 12.x。以下使用 Apache Maven Checkstyle Plugin 3.4.0。

<!-- pom.xml:Checkstyle Plugin -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-checkstyle-plugin</artifactId>
    <version>3.4.0</version>
    <configuration>
        <configLocation>checkstyle.xml</configLocation>
        <failsOnError>true</failsOnError>
        <consoleOutput>true</consoleOutput>
    </configuration>
</plugin>
<!-- checkstyle.xml:企業級規則範例 -->
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
    "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
    "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
    <module name="TreeWalker">
        <!-- 命名規範 -->
        <module name="TypeName">
            <property name="format" value="^[A-Z][a-zA-Z0-9]+$"/>
        </module>
        <module name="MethodName">
            <property name="format" value="^[a-z][a-zA-Z0-9]+$"/>
        </module>
        <!-- 複雜度 -->
        <module name="CyclomaticComplexity">
            <property name="max" value="10"/>
        </module>
        <!-- 方法長度 -->
        <module name="MethodLength">
            <property name="max" value="30"/>
        </module>
        <!-- 類別長度 -->
        <module name="FileLength">
            <property name="max" value="500"/>
        </module>
        <!-- JavaDoc -->
        <module name="MissingJavadocMethod">
            <property name="scope" value="public"/>
        </module>
    </module>
</module>

4.5 SpotBugs

SpotBugs(FindBugs 的繼任者)透過分析 Java Bytecode 來偵測潛在 Bug。

📌 版本說明:SpotBugs 最新版本為 4.9.8(≥ 4.9.0 要求 JRE 11+)。

擅長偵測

  • Null Pointer Dereference
  • Infinite Recursive Loop
  • Resource Leak(未關閉的 Stream / Connection)
  • Concurrency Issues(多執行緒問題)
  • Thread Safety Violations(≥ 4.9.0 新增 SharedVariableAtomicityDetector)
<!-- pom.xml:SpotBugs Plugin -->
<plugin>
    <groupId>com.github.spotbugs</groupId>
    <artifactId>spotbugs-maven-plugin</artifactId>
    <version>4.9.3.0</version>
    <configuration>
        <effort>Max</effort>
        <threshold>Medium</threshold>
        <failOnError>true</failOnError>
        <plugins>
            <!-- 安全性擴充:fb-contrib + find-sec-bugs -->
            <plugin>
                <groupId>com.h3xstream.findsecbugs</groupId>
                <artifactId>findsecbugs-plugin</artifactId>
                <version>1.13.0</version>
            </plugin>
        </plugins>
    </configuration>
</plugin>

4.6 工具功能比較

功能/工具SonarQubeESLintPMDCheckstyleSpotBugs
語言30+JS/TSJavaJavaJava
分析對象原始碼原始碼原始碼原始碼Bytecode
Bug 偵測⚠️✅✅
安全漏洞⚠️⚠️
Code Smell⚠️
程式碼風格⚠️✅✅
重複偵測
覆蓋率
Quality Gate
Dashboard✅✅
成本社群版免費免費免費免費免費

✅✅ = 優秀、✅ = 支援、⚠️ = 部分支援、❌ = 不支援


4.7 企業導入方式

推薦組合策略

flowchart TB
    subgraph "Java 後端"
        A[Checkstyle] -->|風格檢查| D[SonarQube]
        B[SpotBugs] -->|Bug 偵測| D
        C[PMD] -->|最佳實踐| D
    end
    
    subgraph "前端"
        E[ESLint] -->|風格+品質| D
    end
    
    subgraph "CI/CD Pipeline"
        D --> F[Quality Gate]
        F -->|Pass| G[✅ 允許部署]
        F -->|Fail| H[❌ 阻擋部署]
    end
    
    style D fill:#2196F3,color:#fff
    style G fill:#4CAF50,color:#fff
    style H fill:#f44336,color:#fff

分階段導入建議

階段工具目標
Phase 1(第1-2週)Checkstyle統一程式碼風格
Phase 2(第3-4週)SonarQube綜合品質掃描
Phase 3(第5-6週)SpotBugs + FindSecBugs安全性掃描
Phase 4(第7-8週)Quality Gate 整合自動化品質門檻

4.8 實務落地建議

  1. IDE 整合優先:在開發時就能看到問題(SonarLint for VS Code / IntelliJ)
  2. 不要一次開啟所有規則:先從 Critical / Blocker 開始
  3. 新程式碼 vs 舊程式碼:SonarQube 支援「New Code」模式,只檢查新增程式碼
  4. 自訂規則:根據企業需求調整規則集(如銀行禁止 System.out.println
  5. 定期更新規則庫:確保能偵測最新的安全漏洞模式

第 5 章:Code Review 方法論

5.1 Code Review 的價值

Code Review 不只是找 Bug,更是:

  • 知識傳遞:資深帶初階
  • 設計討論:在合併前發現設計問題
  • 品質把關:人工補充自動化工具的不足
  • 團隊共識:建立共同的程式碼標準
flowchart LR
    A[開發者<br/>提交 PR] --> B[自動檢查<br/>CI/CD]
    B --> C{通過?}
    C -->|否| A
    C -->|是| D[同儕 Review]
    D --> E{Approved?}
    E -->|否| A
    E -->|是| F[Tech Lead Review]
    F --> G{Approved?}
    G -->|否| A
    G -->|是| H[合併到主分支]
    
    style H fill:#4CAF50,color:#fff

5.2 Review Checklist(企業版)

功能正確性

  • 是否符合需求規格
  • 邊界條件是否處理(null、空集合、最大值、最小值)
  • 錯誤情況是否有適當處理
  • 併發場景是否安全

設計品質

  • 是否遵循 SOLID 原則
  • 類別職責是否單一
  • 是否有不必要的耦合
  • API 設計是否符合 RESTful 規範

安全性(銀行等級必查)

  • 是否有 SQL Injection 風險
  • 是否有 XSS 風險
  • 敏感資料是否有遮蔽(帳號、密碼)
  • 權限控制是否正確(RBAC)
  • 是否有 Hard-coded 密碼或金鑰
  • 日誌是否洩漏敏感資訊

效能

  • 是否有 N+1 Query
  • 迴圈中是否有不必要的 I/O
  • 是否適當使用快取
  • 大量資料是否有分頁處理

可維護性

  • 命名是否清晰、有意義
  • 方法長度是否合理(≤ 30 行)
  • 是否有重複程式碼
  • 是否有 Magic Number

測試

  • 是否有對應的單元測試
  • 測試是否覆蓋主要路徑與邊界條件
  • 測試命名是否清晰(given-when-then)
  • Mock 使用是否合理

5.3 Pull Request 標準

PR 大小建議

大小行數Review 時間品質
XS< 50 行10 分鐘⭐⭐⭐⭐⭐
S50-200 行30 分鐘⭐⭐⭐⭐
M200-400 行1 小時⭐⭐⭐
L400-800 行2 小時(品質下降)⭐⭐
XL> 800 行不建議

Google 的研究:超過 400 行的 PR,Review 品質顯著下降。建議將大 PR 拆分為多個小 PR。

PR 描述模板

## 變更說明
<!-- 簡述此 PR 的目的 -->

## 變更類型
- [ ] 新功能(Feature)
- [ ] Bug 修復(Bugfix)
- [ ] 重構(Refactoring)
- [ ] 效能優化
- [ ] 安全性修復

## 關聯 Issue
<!-- Fixes #123 -->

## 測試
<!-- 說明測試方式 -->
- [ ] 新增單元測試
- [ ] 通過所有現有測試
- [ ] 手動測試(說明步驟)

## 安全性自檢
- [ ] 無敏感資料洩漏
- [ ] 無 SQL Injection 風險
- [ ] 權限控制正確

## Checklist
- [ ] 程式碼符合團隊規範
- [ ] JavaDoc 已更新
- [ ] SonarQube 掃描通過

5.4 Review 角色分工

角色職責關注點
Author(提交者)提供清晰的 PR 描述、自我檢查確保 CI 通過後再請求 Review
Peer Reviewer程式碼邏輯、可讀性、測試至少 1 位同團隊
Tech Lead設計決策、架構影響涉及核心模組或架構變更時
Security Reviewer安全性檢查涉及認證、授權、資料加密時
Domain Expert業務邏輯正確性涉及複雜業務規則時

5.5 常見錯誤

錯誤說明改善方式
橡皮圖章(Rubber Stamping)不仔細看就 Approve強制至少留 2 則 Comment
過度挑剔糾結空格、換行等小事自動化格式檢查,Review 專注邏輯
PR 太大一次提交 2000+ 行限制 PR ≤ 400 行
Review 太慢PR 放了 3 天沒人看SLA:24 小時內首次 Review
情緒化評論「這寫太爛了」改為建議式:「建議可以考慮…」
只看新增不看刪除忽略刪除的程式碼確認刪除不會影響其他功能

5.6 實務落地建議

  1. 自動化先行:格式、風格交給 Checkstyle / ESLint;人工 Review 專注設計和邏輯
  2. PR 大小 ≤ 400 行:超過就要求拆分
  3. Review SLA:24 小時內首次回覆,48 小時內完成
  4. Review 作為學習機會:鼓勵提問而非只有批評
  5. 使用 AI 輔助:GitHub Copilot Code Review 可自動偵測常見問題

第 6 章:CI/CD 與品質門檻(Quality Gate)

6.1 CI/CD 品質整合概述

在 CI/CD Pipeline 中嵌入品質檢查,是自動化品質管理的關鍵。

flowchart LR
    A[Git Push] --> B[Build]
    B --> C[Unit Test]
    C --> D[靜態分析<br/>SonarQube]
    D --> E[安全掃描<br/>SAST/SCA]
    E --> F{Quality Gate}
    F -->|✅ Pass| G[Deploy to SIT]
    F -->|❌ Fail| H[通知開發者]
    G --> I[Integration Test]
    I --> J[Performance Test]
    J --> K[Deploy to UAT]
    K --> L[Deploy to Production]
    
    style F fill:#FF9800,color:#fff
    style H fill:#f44336,color:#fff
    style L fill:#4CAF50,color:#fff

6.2 Quality Gate 設計

SonarQube Quality Gate 設定建議

New Code(新程式碼)策略——只對新增 / 修改的程式碼設門檻:

條件閾值說明
Coverage on New Code≥ 80%新程式碼測試覆蓋率
Duplicated Lines on New Code≤ 3%新程式碼重複率
Maintainability RatingA可維護性評級
Reliability RatingA可靠性評級
Security RatingA安全性評級
Security Hotspots Reviewed100%安全熱點已審查

Overall Code(全部程式碼)策略——針對整個專案:

條件閾值階段
Overall Coverage≥ 60%Phase 1(初期)
Overall Coverage≥ 70%Phase 2(中期)
Overall Coverage≥ 80%Phase 3(成熟期)
Critical Bugs0立即執行
Critical Vulnerabilities0立即執行

6.3 Pipeline 品質檢查流程

GitHub Actions 範例

# .github/workflows/quality-gate.yml
name: Quality Gate

on:
  pull_request:
    branches: [main, develop]
  push:
    branches: [main, develop]

jobs:
  quality-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # SonarQube 需要完整歷史

      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Cache Maven packages
        uses: actions/cache@v4
        with:
          path: ~/.m2
          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

      # Step 1: 編譯
      - name: Build
        run: mvn clean compile -DskipTests

      # Step 2: 單元測試 + 覆蓋率
      - name: Unit Tests with Coverage
        run: mvn test jacoco:report

      # Step 3: Checkstyle
      - name: Checkstyle
        run: mvn checkstyle:check

      # Step 4: SpotBugs
      - name: SpotBugs
        run: mvn spotbugs:check

      # Step 5: PMD
      - name: PMD
        run: mvn pmd:check

      # Step 6: SonarQube 掃描
      - name: SonarQube Scan
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
        run: |
          mvn sonar:sonar \
            -Dsonar.host.url=${{ secrets.SONAR_URL }} \
            -Dsonar.token=${{ secrets.SONAR_TOKEN }}

      # Step 7: Quality Gate 檢查
      - name: SonarQube Quality Gate
        uses: sonarsource/sonarqube-quality-gate-action@master
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

      # Step 8: OWASP Dependency Check
      - name: OWASP Dependency Check
        run: mvn org.owasp:dependency-check-maven:check

6.4 Fail Pipeline 策略

分級阻擋機制

flowchart TB
    A[品質問題] --> B{嚴重度}
    B -->|Blocker / Critical| C[❌ 阻擋合併<br/>必須立即修復]
    B -->|Major| D[⚠️ 警告<br/>Sprint 內修復]
    B -->|Minor| E[ℹ️ 提醒<br/>下次修復]
    B -->|Info| F[📝 記錄<br/>參考]
    
    style C fill:#f44336,color:#fff
    style D fill:#FF9800,color:#fff
    style E fill:#FFC107,color:#000
    style F fill:#BBDEFB,color:#000

企業建議策略(分階段實施)

Phase阻擋條件適用時機
Phase 1(寬鬆)僅 Blocker Bug + Critical Vulnerability剛導入階段
Phase 2(中等)Phase 1 + Coverage < 60%3 個月後
Phase 3(嚴格)完整 Quality Gate(A 評級)6 個月後
Phase 4(銀行等級)Phase 3 + 安全掃描 + 相依套件掃描1 年後

6.5 實務落地建議

  1. 先監控再阻擋:前 1-2 個月只產出報告,不阻擋 Pipeline
  2. 新程式碼優先:使用 SonarQube New Code Period,不因歷史程式碼而卡關
  3. 快速回饋:Pipeline 應在 15 分鐘內完成,超過要優化
  4. 報告可視化:SonarQube Dashboard 連結到 Slack / Teams 通知
  5. Security Gate 不可妥協:Critical Vulnerability 永遠阻擋,無例外

第 7 章:測試策略與品質關聯

7.1 測試金字塔

測試金字塔(Test Pyramid)是一個指導測試策略的模型,從下到上分為:

graph TB
    subgraph "測試金字塔"
        A[E2E 測試<br/>少量 · 慢 · 昂貴] 
        B[整合測試<br/>適量 · 中速]
        C[單元測試<br/>大量 · 快速 · 便宜]
    end
    
    A --> B --> C
    
    style A fill:#f44336,color:#fff
    style B fill:#FF9800,color:#fff
    style C fill:#4CAF50,color:#fff

各層級建議比例

測試類型比例執行時間穩定性
單元測試70%毫秒級非常高
整合測試20%秒級
E2E 測試10%分鐘級

7.2 單元測試(Unit Test)

什麼是好的單元測試

遵循 F.I.R.S.T. 原則:

原則英文說明
FFast執行快速(毫秒級)
IIndependent測試之間互不影響
RRepeatable任何環境都能重複執行
SSelf-Validating有明確的 Pass/Fail 結果
TTimely與生產程式碼同步撰寫

測試命名規範

推薦使用 Given-When-ThenShould 風格:

@ExtendWith(MockitoExtension.class)
class AccountServiceTest {

    @Mock
    private AccountRepository accountRepository;
    
    @InjectMocks
    private AccountService accountService;

    // ✅ Good:命名清楚表達測試意圖
    @Test
    @DisplayName("當帳戶餘額不足時,轉帳應拋出 InsufficientFundsException")
    void transfer_whenInsufficientBalance_shouldThrowException() {
        // Given
        Account fromAccount = Account.builder()
            .id("ACC-001")
            .balance(new BigDecimal("100"))
            .build();
        when(accountRepository.findById("ACC-001")).thenReturn(Optional.of(fromAccount));
        
        // When & Then
        assertThrows(InsufficientFundsException.class, () -> {
            accountService.transfer("ACC-001", "ACC-002", new BigDecimal("500"));
        });
    }

    @Test
    @DisplayName("當金額為零時,應拋出 InvalidAmountException")
    void transfer_whenAmountIsZero_shouldThrowInvalidAmountException() {
        // Given
        BigDecimal zeroAmount = BigDecimal.ZERO;
        
        // When & Then
        assertThrows(InvalidAmountException.class, () -> {
            accountService.transfer("ACC-001", "ACC-002", zeroAmount);
        });
    }

    @Test
    @DisplayName("正常轉帳應成功扣款並入帳")
    void transfer_withValidRequest_shouldDebitAndCredit() {
        // Given
        Account from = Account.builder().id("ACC-001").balance(new BigDecimal("1000")).build();
        Account to = Account.builder().id("ACC-002").balance(new BigDecimal("500")).build();
        
        when(accountRepository.findById("ACC-001")).thenReturn(Optional.of(from));
        when(accountRepository.findById("ACC-002")).thenReturn(Optional.of(to));
        
        // When
        accountService.transfer("ACC-001", "ACC-002", new BigDecimal("300"));
        
        // Then
        assertThat(from.getBalance()).isEqualByComparingTo(new BigDecimal("700"));
        assertThat(to.getBalance()).isEqualByComparingTo(new BigDecimal("800"));
        verify(accountRepository, times(2)).save(any(Account.class));
    }
}

反面教材:常見的壞測試

// ❌ Bad:測試命名不清
@Test
void test1() { }

// ❌ Bad:沒有 Assert
@Test
void testTransfer() {
    accountService.transfer("A", "B", BigDecimal.TEN);
    // 沒有任何驗證...
}

// ❌ Bad:測試太大,同時驗證多件事
@Test
void testEverything() {
    // 測試建立 + 查詢 + 更新 + 刪除... 200 行
}

7.3 整合測試(Integration Test)

整合測試驗證多個元件之間的協作是否正確。

Spring Boot 整合測試範例

@SpringBootTest
@ActiveProfile("test")
@Transactional // 測試後自動 Rollback
class AccountIntegrationTest {

    @Autowired
    private AccountService accountService;
    
    @Autowired
    private AccountRepository accountRepository;

    @Test
    @DisplayName("完整轉帳流程:從建立帳戶到轉帳成功")
    void fullTransferFlow() {
        // Given:建立兩個帳戶
        Account sender = accountRepository.save(
            Account.builder().balance(new BigDecimal("10000")).build()
        );
        Account receiver = accountRepository.save(
            Account.builder().balance(new BigDecimal("5000")).build()
        );
        
        // When:執行轉帳
        TransferResult result = accountService.transfer(
            sender.getId(), receiver.getId(), new BigDecimal("3000")
        );
        
        // Then:驗證結果
        assertThat(result.isSuccess()).isTrue();
        
        Account updatedSender = accountRepository.findById(sender.getId()).orElseThrow();
        Account updatedReceiver = accountRepository.findById(receiver.getId()).orElseThrow();
        
        assertThat(updatedSender.getBalance()).isEqualByComparingTo(new BigDecimal("7000"));
        assertThat(updatedReceiver.getBalance()).isEqualByComparingTo(new BigDecimal("8000"));
    }
}

API 整合測試

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfile("test")
class AccountApiIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    @DisplayName("POST /api/transfers 應成功回傳 200")
    void createTransfer_withValidRequest_shouldReturn200() {
        // Given
        TransferRequest request = new TransferRequest("ACC-001", "ACC-002", new BigDecimal("100"));
        
        // When
        ResponseEntity<TransferResult> response = restTemplate.postForEntity(
            "/api/transfers", request, TransferResult.class
        );
        
        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().isSuccess()).isTrue();
    }
}

7.4 覆蓋率迷思

覆蓋率不代表品質

// ✅ Coverage = 100%,但測試品質很差
@Test
void testGetBalance() {
    Account account = new Account();
    account.setBalance(new BigDecimal("100"));
    account.getBalance(); // 呼叫了但沒有驗證!
    // 覆蓋率 100%,但什麼都沒測到
}

正確看待覆蓋率

覆蓋率意義建議
< 30%幾乎沒有測試🔴 需要立即加強
30-60%基本覆蓋🟡 持續提升
60-80%良好覆蓋🟢 大多數專案的合理目標
80-90%優秀覆蓋🟢 核心業務邏輯的目標
> 95%可能過度測試⚠️ 小心為衝覆蓋率而寫無效測試

正確的策略

  1. 覆蓋率是地板,不是天花板:80% 是最低要求,不是最終目標
  2. Branch Coverage > Line Coverage:分支覆蓋比行覆蓋更有意義
  3. 關注 Mutation Testing:使用 PIT(pitest)測試你的測試是否真正有效
  4. 核心業務邏輯 > 工具類別:轉帳邏輯需要 95%,DTO getter/setter 不用測

7.5 測試與品質的關係

flowchart LR
    A[良好的測試] --> B[快速回饋]
    B --> C[勇於重構]
    C --> D[程式碼品質提升]
    D --> E[更容易測試]
    E --> A
    
    style A fill:#4CAF50,color:#fff
    style D fill:#2196F3,color:#fff

測試帶來的品質改善

測試活動品質影響
寫單元測試迫使你設計可測試的程式碼(低耦合)
TDD確保每行程式碼都有測試覆蓋
測試先行更清楚了解需求
整合測試確保模組間正確協作
效能測試及早發現效能瓶頸

7.6 實務落地建議

  1. 新程式碼一律寫測試:作為 Code Review 的必要條件
  2. 不要為 Coverage 而測試:專注在業務邏輯和邊界條件
  3. 使用 TDD 處理複雜邏輯:如銀行利息計算、手續費規則
  4. 測試環境隔離:使用 Testcontainers 或 H2 取代真實 DB
  5. Flaky Test 零容忍:不穩定的測試要立即修復或標記

第 8 章:重構(Refactoring)策略

8.1 何時該重構

重構的觸發條件

觸發信號說明優先級
修 Bug 困難找不到問題在哪、改一處壞多處🔴 高
加新功能困難需要改動大量現有程式碼🔴 高
重複程式碼同樣邏輯出現 3+ 處🟡 中
SonarQube 紅燈品質趨勢持續下降🟡 中
Code Review 反覆提出某些區域頻繁被 Review 出問題🟡 中
新人上手困難程式碼無法被理解🟢 低

不該重構的時機

  • ❌ 即將上線前(風險太高)
  • ❌ 沒有測試覆蓋的區域(重構沒有安全網)
  • ❌ 為了重構而重構(沒有明確的業務價值)

8.2 安全重構流程

flowchart TB
    A[識別重構目標] --> B[確認測試覆蓋率]
    B --> C{覆蓋率 ≥ 80%?}
    C -->|否| D[補寫測試<br/>Characterization Tests]
    D --> B
    C -->|是| E[小步重構]
    E --> F[執行測試]
    F --> G{全部通過?}
    G -->|否| H[Revert 變更<br/>分析原因]
    H --> E
    G -->|是| I[Commit]
    I --> J{重構完成?}
    J -->|否| E
    J -->|是| K[Code Review]
    K --> L[合併]
    
    style D fill:#FF9800,color:#fff
    style H fill:#f44336,color:#fff
    style L fill:#4CAF50,color:#fff

安全重構的核心原則

  1. 先有測試再重構:沒有測試覆蓋的程式碼不要碰
  2. 小步前進:每次只做一個小變更,立即測試
  3. 頻繁 Commit:每完成一個小步驟就 Commit,方便 Revert
  4. 不改變外部行為:重構 ≠ 改功能

8.3 常見重構技巧

技巧 1:Extract Method(提取方法)

適用場景:方法太長、邏輯混在一起

// Before(60 行的方法)
public void processPayroll(List<Employee> employees) {
    for (Employee emp : employees) {
        // 計算基本薪資(15行)
        BigDecimal baseSalary = emp.getGrade().getBaseSalary();
        // ... 複雜計算
        
        // 計算加班費(10行)
        BigDecimal overtime = calculateOvertimeHours(emp) 
            .multiply(emp.getHourlyRate())
            .multiply(new BigDecimal("1.5"));
        
        // 計算扣除額(15行)
        BigDecimal deductions = calculateTax(baseSalary.add(overtime))
            .add(calculateInsurance(emp))
            .add(calculatePension(emp));
        
        // 發放薪資(10行)
        BigDecimal netSalary = baseSalary.add(overtime).subtract(deductions);
        payrollRepository.save(new PayrollRecord(emp, netSalary));
        notificationService.sendPayslip(emp, netSalary);
    }
}
// After:清晰的高層抽象
public void processPayroll(List<Employee> employees) {
    for (Employee emp : employees) {
        BigDecimal baseSalary = calculateBaseSalary(emp);
        BigDecimal overtime = calculateOvertime(emp);
        BigDecimal deductions = calculateDeductions(emp, baseSalary, overtime);
        BigDecimal netSalary = baseSalary.add(overtime).subtract(deductions);
        
        saveAndNotify(emp, netSalary);
    }
}

技巧 2:Replace Conditional with Polymorphism(以多型取代條件式)

// Before
public BigDecimal calculateInterest(Account account) {
    switch (account.getType()) {
        case SAVINGS:
            return account.getBalance().multiply(new BigDecimal("0.02"));
        case CHECKING:
            return account.getBalance().multiply(new BigDecimal("0.005"));
        case FIXED_DEPOSIT:
            return account.getBalance()
                .multiply(new BigDecimal("0.04"))
                .multiply(BigDecimal.valueOf(account.getTermInYears()));
        default:
            return BigDecimal.ZERO;
    }
}
// After:使用策略模式
public interface InterestCalculator {
    BigDecimal calculate(Account account);
}

@Component("SAVINGS")
public class SavingsInterestCalculator implements InterestCalculator {
    private static final BigDecimal RATE = new BigDecimal("0.02");
    
    @Override
    public BigDecimal calculate(Account account) {
        return account.getBalance().multiply(RATE);
    }
}

@Service
public class InterestService {
    private final Map<String, InterestCalculator> calculators;
    
    public InterestService(Map<String, InterestCalculator> calculators) {
        this.calculators = calculators;
    }
    
    public BigDecimal calculateInterest(Account account) {
        return calculators.getOrDefault(account.getType().name(), a -> BigDecimal.ZERO)
            .calculate(account);
    }
}

技巧 3:Introduce Parameter Object(引入參數物件)

// Before:過多參數
public List<Transaction> search(String accountId, LocalDate from, LocalDate to,
        String type, BigDecimal minAmount, BigDecimal maxAmount, int page, int size) { }

// After
public record TransactionSearchCriteria(
    String accountId,
    DateRange dateRange,
    String type,
    AmountRange amountRange,
    Pageable pageable
) { }

public List<Transaction> search(TransactionSearchCriteria criteria) { }

技巧 4:Replace Magic Number with Named Constant

// Before
if (retryCount > 3) { Thread.sleep(5000); }

// After
private static final int MAX_RETRY_COUNT = 3;
private static final long RETRY_DELAY_MS = 5000L;

if (retryCount > MAX_RETRY_COUNT) { Thread.sleep(RETRY_DELAY_MS); }

其他常用重構技巧

技巧適用場景
Move Method方法放錯類別
Extract Class類別太大
Inline Method方法體太簡單,不需獨立存在
Replace Temp with Query用方法呼叫取代暫時變數
Decompose Conditional複雜條件式拆解
Consolidate Duplicate Conditional合併重複條件
Replace Inheritance with Delegation以委託取代繼承

8.4 實務落地建議

  1. Boy Scout Rule:離開時比來時更乾淨——每次改動時順手小重構
  2. 每 Sprint 分配 15% 重構預算:不要等到「重構 Sprint」
  3. 重構 PR 和功能 PR 分開:方便 Review 和追蹤
  4. IDE 重構工具:善用 VS Code / IntelliJ 的自動重構功能
  5. 銀行系統:重構核心帳務邏輯前必須有 100% 的關鍵路徑測試覆蓋

第 9 章:企業實務最佳實踐(Best Practices)

9.1 銀行 / 金融系統案例

案例一:核心帳務系統品質問題

背景:某銀行核心帳務系統,10+ 年歷史,200 萬行程式碼。

問題

  • 無單元測試(覆蓋率 0%)
  • SonarQube 掃描出 15,000+ Code Smells
  • 每次改版平均引入 5 個 Bug
  • 新進人員需 6 個月才能獨立開發

改善策略

gantt
    title 品質改善路線圖
    dateFormat YYYY-MM
    
    section Phase 1:基礎建設
    導入 SonarQube          :done, p1a, 2024-01, 2024-02
    建立 CI/CD Pipeline     :done, p1b, 2024-01, 2024-03
    定義 Coding Standard    :done, p1c, 2024-02, 2024-03
    
    section Phase 2:新程式碼品質
    新程式碼 Quality Gate   :active, p2a, 2024-03, 2024-05
    PR Review 制度          :active, p2b, 2024-03, 2024-04
    新程式碼 80% 覆蓋率     :p2c, 2024-04, 2024-06
    
    section Phase 3:歷史程式碼
    Hotspot 重構            :p3a, 2024-06, 2024-09
    補寫核心測試            :p3b, 2024-06, 2024-10
    靜態分析修復            :p3c, 2024-07, 2024-12
    
    section Phase 4:持續改善
    成熟度 Level 4         :p4a, 2025-01, 2025-06
    自動化安全掃描         :p4b, 2025-01, 2025-03

成效

  • 6 個月後,新程式碼 Bug 率下降 60%
  • 12 個月後,整體覆蓋率從 0% 提升到 45%
  • 新人上手時間從 6 個月縮短到 2 個月

案例二:金融交易比對系統

情境:每日處理 500 萬筆交易比對,要求零錯誤。

品質策略

// 核心業務規則使用 Property-Based Testing
@Property
void transferShouldPreserveTotalBalance(
        @ForAll @BigRange(min = "0", max = "999999999") BigDecimal initialFromBalance,
        @ForAll @BigRange(min = "0", max = "999999999") BigDecimal initialToBalance,
        @ForAll @BigRange(min = "1", max = "999999999") BigDecimal transferAmount) {
    
    // 假設:轉出帳戶餘額足夠
    Assume.that(initialFromBalance.compareTo(transferAmount) >= 0);
    
    Account from = Account.withBalance(initialFromBalance);
    Account to = Account.withBalance(initialToBalance);
    BigDecimal totalBefore = from.getBalance().add(to.getBalance());
    
    // 執行轉帳
    from.debit(transferAmount);
    to.credit(transferAmount);
    
    BigDecimal totalAfter = from.getBalance().add(to.getBalance());
    
    // 斷言:總金額不變(守恆定律)
    assertThat(totalAfter).isEqualByComparingTo(totalBefore);
}

9.2 微服務品質管理

微服務品質挑戰

挑戰說明對策
服務邊界職責是否切對Domain-Driven Design
API 契約介面變更的品質控管Consumer-Driven Contract Testing
分散式追蹤Bug 在哪個服務Distributed Tracing(Jaeger / Zipkin)
資料一致性跨服務的資料一致Saga Pattern + 補償交易
整體品質可視性每個服務的品質不一統一 SonarQube Dashboard

微服務品質架構

flowchart TB
    subgraph "品質整合平台"
        SQ[SonarQube<br/>統一品質儀表板]
    end
    
    subgraph "服務 A(帳務)"
        A1[Unit Test] --> A2[Integration Test]
        A2 --> A3[SonarQube Scan]
        A3 --> SQ
    end
    
    subgraph "服務 B(交易)"
        B1[Unit Test] --> B2[Integration Test]
        B2 --> B3[SonarQube Scan]
        B3 --> SQ
    end
    
    subgraph "跨服務測試"
        C1[Contract Test<br/>Pact] --> C2[E2E Test]
    end
    
    SQ --> D{Quality Gate}
    D -->|Pass| E[✅ 部署]
    D -->|Fail| F[❌ 阻擋]
    
    style SQ fill:#2196F3,color:#fff

Contract Testing 範例(Pact)

// Consumer 端(呼叫方)
@Pact(consumer = "transaction-service", provider = "account-service")
public V4Pact getAccountBalance(PactDslWithProvider builder) {
    return builder
        .given("Account ACC-001 exists with balance 10000")
        .uponReceiving("a request to get account balance")
            .path("/api/accounts/ACC-001/balance")
            .method("GET")
        .willRespondWith()
            .status(200)
            .body(newJsonBody(body -> {
                body.stringType("accountId", "ACC-001");
                body.decimalType("balance", 10000.00);
            }).build())
        .toPact(V4Pact.class);
}

@Test
@PactTestFor(pactMethod = "getAccountBalance")
void testGetAccountBalance(MockServer mockServer) {
    AccountClient client = new AccountClient(mockServer.getUrl());
    AccountBalance balance = client.getBalance("ACC-001");
    
    assertThat(balance.getAccountId()).isEqualTo("ACC-001");
    assertThat(balance.getBalance()).isEqualByComparingTo(new BigDecimal("10000"));
}

9.3 DevSecOps 整合

DevSecOps Pipeline 品質整合

flowchart LR
    subgraph "Dev"
        A[IDE<br/>SonarLint] --> B[Pre-commit<br/>Hook]
    end
    
    subgraph "CI"
        B --> C[SAST<br/>靜態安全掃描]
        C --> D[SCA<br/>相依套件掃描]
        D --> E[Unit Test<br/>覆蓋率]
        E --> F[Quality Gate]
    end
    
    subgraph "CD"
        F --> G[DAST<br/>動態安全掃描]
        G --> H[Container<br/>Image Scan]
        H --> I[Deploy]
    end
    
    subgraph "Ops"
        I --> J[Runtime<br/>Monitoring]
        J --> K[Vulnerability<br/>Alert]
    end
    
    style C fill:#f44336,color:#fff
    style D fill:#f44336,color:#fff
    style G fill:#f44336,color:#fff
    style H fill:#f44336,color:#fff

安全工具整合

階段工具檢查內容
開發SonarLint即時安全提示
SASTSonarQube / Fortify原始碼安全漏洞
SCAOWASP Dependency Check / Snyk相依套件 CVE
Secret ScanGitLeaks / TruffleHog密碼 / 金鑰洩漏
DASTOWASP ZAP執行時安全測試
ContainerTrivy / Grype容器映像漏洞

OWASP Dependency Check 配置

<!-- pom.xml -->
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>11.0.1</version>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>
        <suppressionFiles>
            <suppressionFile>owasp-suppressions.xml</suppressionFile>
        </suppressionFiles>
    </configuration>
</plugin>

9.4 實務落地建議

  1. 品質即文化:不是靠一個工具就能搞定,需要持續推動
  2. 從新專案開始:新專案直接用最高標準,舊系統逐步改善
  3. KPI 可視化:把品質指標放在團隊看板上
  4. 安全左移:越早發現安全問題,修復成本越低
  5. 銀行業:必須同時符合內部稽核與外部法規要求

第 10 章:導入策略(企業落地)

10.1 推動步驟(Roadmap)

flowchart LR
    A[Phase 1<br/>基礎建設<br/>1-2個月] --> B[Phase 2<br/>標準建立<br/>2-3個月]
    B --> C[Phase 3<br/>自動化<br/>3-4個月]
    C --> D[Phase 4<br/>持續改善<br/>持續進行]
    
    style A fill:#BBDEFB,color:#000
    style B fill:#90CAF9,color:#000
    style C fill:#42A5F5,color:#fff
    style D fill:#1565C0,color:#fff

Phase 1:基礎建設(第 1-2 個月)

任務負責人產出
安裝 SonarQubeDevOpsSonarQube Server
建立 CI/CD PipelineDevOpsGitHub Actions / Jenkins
定義 Coding StandardArchitectCheckstyle / ESLint 規則
掃描現有程式碼 BaselineArchitect品質基線報告

Phase 2:標準建立(第 2-3 個月)

任務負責人產出
定義 Quality GateArchitect + TLSonarQube 品質門檻
建立 Code Review 制度TLReview Checklist + PR Template
教育訓練Architect培訓簡報 + Workshop
試行新程式碼品質要求TeamNew Code 品質達標

Phase 3:自動化(第 3-4 個月)

任務負責人產出
Quality Gate 整合 CI/CDDevOps自動品質阻擋
安全掃描整合SecuritySAST + SCA Pipeline
品質 DashboardArchitect看板 + 通知
Git Hook(Pre-commit)TL本地品質檢查

Phase 4:持續改善(持續進行)

任務頻率負責人
品質週報 Review每週TL
品質月會每月Architect
規則調整每季Architect + TL
技術債 Sprint每季Team
成熟度評估每半年Architect

10.2 組織角色

graph TB
    A[CTO / 技術長] --> B[Enterprise Architect<br/>企業架構師]
    B --> C[Tech Lead / 技術主管]
    C --> D[Senior Developer<br/>資深開發者]
    D --> E[Developer<br/>開發者]
    
    B --> F[Security Team<br/>安全團隊]
    B --> G[DevOps Team<br/>維運團隊]

    style A fill:#1565C0,color:#fff
    style B fill:#1976D2,color:#fff
    style C fill:#2196F3,color:#fff
角色品質相關職責
Enterprise Architect定義品質模型、選擇工具、設定標準、成熟度評估
Tech Lead執行 Quality Gate、主持 Review、分配重構任務
Senior Developer輔導 Junior、撰寫關鍵測試、識別 Code Smell
Developer遵循標準、寫測試、修復品質問題
Security Team定義安全規則、審查安全掃描結果
DevOps Team維護 CI/CD Pipeline、SonarQube 伺服器

10.3 KPI 指標

品質 KPI 儀表板

KPI 指標計算方式目標值追蹤頻率
新程式碼覆蓋率JaCoCo New Code Coverage≥ 80%每次 PR
新程式碼 Bug 率New Bugs / New Lines × 1000< 1每次 PR
Quality Gate 通過率Passed / Total PRs × 100%≥ 95%每週
Critical 漏洞數SonarQube Critical Vulnerabilities0每日
技術債比率Remediation Cost / Dev Cost × 100%< 5%每月
重複程式碼率Duplicated Lines %< 5%每月
PR Review 時間從建立到首次 Review< 24 小時每週
Bug Escape Rate上線後 Bug / 總 Bug< 5%每月
平均修復時間Bug 發現到修復的時間< 2 天(Critical)每月

10.4 成熟度模型(Level 1~5)

graph LR
    L1[Level 1<br/>初始] --> L2[Level 2<br/>管理]
    L2 --> L3[Level 3<br/>定義]
    L3 --> L4[Level 4<br/>量化]
    L4 --> L5[Level 5<br/>優化]
    
    style L1 fill:#f44336,color:#fff
    style L2 fill:#FF9800,color:#fff
    style L3 fill:#FFC107,color:#000
    style L4 fill:#8BC34A,color:#fff
    style L5 fill:#4CAF50,color:#fff
Level名稱特徵典型指標
Level 1
初始
Ad-hoc無標準、靠個人經驗、無測試Coverage 0%、無 CI/CD
Level 2
管理
Managed有 CI/CD、有基本 ReviewCoverage < 30%、有 Pipeline
Level 3
定義
Defined有 Coding Standard、有 Quality Gate、有測試策略Coverage ≥ 60%、Quality Gate 啟用
Level 4
量化
Quantified品質指標可追蹤、趨勢可視化、主動改善Coverage ≥ 80%、Bug Escape < 5%
Level 5
優化
Optimized持續改善、AI 輔助、自動化全面Coverage ≥ 85%、技術債 < 3%、零 Critical

成熟度評估維度

維度Level 1Level 3Level 5
測試無測試新程式碼有測試TDD + Mutation Testing
Review無 Review有 ChecklistAI 輔助 + 自動標記風險
靜態分析SonarQube 基本掃描全面分析 + 自訂規則
安全基本 SASTSAST + DAST + SCA + Secret Scan
自動化手動建置CI/CD 基本 Pipeline全自動 DevSecOps
指標無追蹤有 Dashboard預測分析 + 自動告警

10.5 實務落地建議

  1. 由上而下推動:需要管理層支持(CTO/VP level)
  2. 先贏得小勝利:選一個小專案試行,展示成效後再擴展
  3. 不要一口氣全做:分階段導入,每階段 2-3 個月
  4. 培養品質 Champion:每個團隊指定 1-2 位品質推動者
  5. 獎勵機制:將品質 KPI 納入績效考核(但比重不要太高,避免作弊)
  6. 真實數據說話:用 SonarQube 報表向管理層報告成效

附錄 A:新進成員檢查清單(Checklist)

🟢 開發前(每日)

  • IDE 已安裝 SonarLint(VS Code / IntelliJ)
  • 已拉取最新程式碼(git pull --rebase
  • 了解今天要開發的需求和驗收標準
  • 確認相關 API 規格文件

🟢 開發中(每次 Commit)

  • 程式碼符合命名規範(PascalCase / camelCase / UPPER_SNAKE_CASE)
  • 方法長度 ≤ 30 行
  • 巢狀深度 ≤ 3 層
  • 無 Magic Number(使用常數)
  • 無 Hard-coded 密碼或設定值
  • 使用 Parameterized Query(防 SQL Injection)
  • 敏感資料已遮蔽(帳號、密碼不出現在 Log 中)
  • 已處理 Null 和邊界條件
  • 新增的 public 方法有 JavaDoc
  • 已撰寫對應的單元測試(覆蓋主要路徑 + 邊界條件)

🟢 提交 PR 前

  • 本地 SonarLint 無 Blocker / Critical 問題
  • 所有單元測試通過(mvn test
  • PR 大小 ≤ 400 行(如超過請拆分)
  • PR 描述清楚(變更說明 + 測試方式 + 關聯 Issue)
  • 已自我 Review 一遍
  • 無不必要的 System.out.println 或 Debug 程式碼

🟢 Code Review 時

  • 邏輯是否正確(特別是邊界條件)
  • 安全性檢查(注入、權限、資料洩漏)
  • 效能檢查(N+1 Query、不必要的迴圈 I/O)
  • 命名是否清晰易懂
  • 是否有重複程式碼
  • 測試是否充分

🟢 上線前

  • CI/CD Pipeline 全部通過
  • SonarQube Quality Gate 通過
  • 安全掃描無 Critical / High 漏洞
  • 相依套件無已知 CVE
  • 效能測試通過(如有必要)

附錄 B:品質指標速查表

程式碼指標

指標工具建議閾值
Cyclomatic ComplexitySonarQube / PMD≤ 10 per method
Cognitive ComplexitySonarQube≤ 15 per method
Lines per MethodCheckstyle≤ 30
Lines per ClassCheckstyle≤ 500
Nesting DepthSonarQube≤ 3
Coupling (CBO)PMD≤ 8
Duplicated LinesSonarQube≤ 3%(新程式碼)

測試指標

指標工具建議閾值
Line Coverage(新程式碼)JaCoCo≥ 80%
Branch Coverage(新程式碼)JaCoCo≥ 70%
Mutation ScorePIT≥ 60%
Unit Test TimeMaven Surefire≤ 5 min
Flaky Test RateCI Analytics≤ 1%

安全指標

指標工具建議閾值
Critical VulnerabilitySonarQube0
High VulnerabilitySonarQube0
Known CVE (High+)OWASP Dependency Check0
Hard-coded SecretsGitLeaks0
Security Hotspot ReviewSonarQube100% reviewed

流程指標

指標工具建議閾值
PR SizeGitHub≤ 400 行
PR Review TimeGitHub≤ 24 小時(首次)
Quality Gate Pass RateSonarQube≥ 95%
Bug Escape RateJira / SonarQube≤ 5%
Technical Debt RatioSonarQube≤ 5%

附錄 C:推薦學習資源

書籍

書名作者主題
Clean CodeRobert C. Martin程式碼品質基礎
RefactoringMartin Fowler重構技巧
Clean ArchitectureRobert C. Martin架構設計
Working Effectively with Legacy CodeMichael Feathers處理遺留系統
Test Driven DevelopmentKent BeckTDD 方法論
Effective Java (3rd Edition)Joshua BlochJava 最佳實踐
Domain-Driven DesignEric Evans領域驅動設計

線上資源

資源網址說明
SonarQube 文件docs.sonarsource.com/sonarqube-server官方使用指南
OWASP Top 10: 2025owasp.org/Top10/2025Web 安全 Top 10(2025 最新版)
PMD 文件docs.pmd-code.orgPMD 7.x 官方文件
Checkstyle 文件checkstyle.orgCheckstyle 13.x 官方文件
SpotBugs 文件spotbugs.github.ioSpotBugs 官方文件
Refactoring Gururefactoring.guru重構與設計模式
Martin Fowler Blogmartinfowler.com架構與品質文章

工具

工具用途安裝方式
SonarQube Community綜合品質平台Docker / 獨立安裝
SonarLintIDE 即時品質檢查VS Code / IntelliJ Plugin
JaCoCoJava 覆蓋率Maven Plugin
SpotBugsBug 偵測Maven Plugin
Checkstyle程式碼風格Maven Plugin
PMD最佳實踐Maven Plugin
OWASP Dependency Check相依套件安全Maven Plugin
PIT (pitest)Mutation TestingMaven Plugin
Testcontainers整合測試容器Maven Dependency

文件維護:本手冊建議每季 Review 一次,根據工具版本更新與團隊實踐經驗持續改善。
問題回饋:如有任何問題或建議,請透過內部 Wiki 或 Slack #code-quality 頻道提出。