code-quality-gates

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Code Quality Gates — Expert Decisions

代码质量门——专业决策

Expert decision frameworks for quality gate choices. Claude knows linting tools — this skill provides judgment calls for threshold calibration and rule selection.

针对质量门选择的专业决策框架。Claude熟悉代码检查工具——本技能为阈值校准和规则选择提供专业判断建议。

Decision Trees

决策树

Which Quality Gates for Your Project

为你的项目选择合适的质量门

Project stage?
├─ Greenfield (new project)
│  └─ Enable ALL gates from day 1
│     • SwiftLint (strict)
│     • SwiftFormat
│     • SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
│     • Coverage > 80%
├─ Brownfield (legacy, no gates)
│  └─ Adopt incrementally:
│     1. SwiftLint with --baseline (ignore existing)
│     2. Format new files only
│     3. Gradually increase thresholds
└─ Existing project with some gates
   └─ Team size?
      ├─ Solo/Small (1-3) → Lint + Format sufficient
      └─ Medium+ (4+) → Full pipeline
         • Add coverage gates
         • Add static analysis
         • Add security scanning
Project stage?
├─ Greenfield (new project)
│  └─ Enable ALL gates from day 1
│     • SwiftLint (strict)
│     • SwiftFormat
│     • SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
│     • Coverage > 80%
├─ Brownfield (legacy, no gates)
│  └─ Adopt incrementally:
│     1. SwiftLint with --baseline (ignore existing)
│     2. Format new files only
│     3. Gradually increase thresholds
└─ Existing project with some gates
   └─ Team size?
      ├─ Solo/Small (1-3) → Lint + Format sufficient
      └─ Medium+ (4+) → Full pipeline
         • Add coverage gates
         • Add static analysis
         • Add security scanning

Coverage Threshold Selection

覆盖率阈值选择

What type of code?
├─ Domain/Business Logic
│  └─ 90%+ coverage required
│     • Business rules must be tested
│     • Silent bugs are expensive
├─ Data Layer (Repositories, Mappers)
│  └─ 85% coverage
│     • Test mapping edge cases
│     • Test error handling
├─ Presentation (ViewModels)
│  └─ 70-80% coverage
│     • Test state transitions
│     • Skip trivial bindings
└─ Views (SwiftUI)
   └─ Don't measure coverage
      • Snapshot tests or manual QA
      • Unit testing views has poor ROI
What type of code?
├─ Domain/Business Logic
│  └─ 90%+ coverage required
│     • Business rules must be tested
│     • Silent bugs are expensive
├─ Data Layer (Repositories, Mappers)
│  └─ 85% coverage
│     • Test mapping edge cases
│     • Test error handling
├─ Presentation (ViewModels)
│  └─ 70-80% coverage
│     • Test state transitions
│     • Skip trivial bindings
└─ Views (SwiftUI)
   └─ Don't measure coverage
      • Snapshot tests or manual QA
      • Unit testing views has poor ROI

SwiftLint Rule Strategy

SwiftLint规则策略

Starting fresh?
├─ YES → Enable opt_in_rules aggressively
│  └─ Easier to disable than enable later
└─ NO → Adopting on existing codebase
   └─ Use baseline approach:
      1. Run: swiftlint lint --reporter json > baseline.json
      2. Configure: baseline_path: baseline.json
      3. New violations fail, existing ignored
      4. Chip away at baseline over time

Starting fresh?
├─ YES → Enable opt_in_rules aggressively
│  └─ Easier to disable than enable later
└─ NO → Adopting on existing codebase
   └─ Use baseline approach:
      1. Run: swiftlint lint --reporter json > baseline.json
      2. Configure: baseline_path: baseline.json
      3. New violations fail, existing ignored
      4. Chip away at baseline over time

NEVER Do

绝对不要做的事

Threshold Anti-Patterns

阈值反模式

NEVER set coverage to 100%:
yaml
undefined
绝对不要将覆盖率设置为100%:
yaml
undefined

❌ Blocks legitimate PRs, encourages gaming

❌ Blocks legitimate PRs, encourages gaming

MIN_COVERAGE: 100
MIN_COVERAGE: 100

✅ Realistic threshold with room for edge cases

✅ Realistic threshold with room for edge cases

MIN_COVERAGE: 80

**NEVER** use zero-tolerance for warnings initially on legacy code:
```bash
MIN_COVERAGE: 80

**绝对不要**在遗留项目初期对警告采取零容忍策略:
```bash

❌ 500 warnings = blocked pipeline forever

❌ 500 warnings = blocked pipeline forever

SWIFT_TREAT_WARNINGS_AS_ERRORS = YES # On day 1 of legacy project
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES # On day 1 of legacy project

✅ Incremental adoption

✅ Incremental adoption

1. First: just report warnings

1. First: just report warnings

SWIFT_TREAT_WARNINGS_AS_ERRORS = NO
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO

2. Then: require no NEW warnings

2. Then: require no NEW warnings

swiftlint lint --baseline existing-violations.json
swiftlint lint --baseline existing-violations.json

3. Finally: zero tolerance (after cleanup)

3. Finally: zero tolerance (after cleanup)

SWIFT_TREAT_WARNINGS_AS_ERRORS = YES

**NEVER** skip gates on "urgent" PRs:
```yaml
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES

**绝对不要**在“紧急”PR中跳过质量门:
```yaml

❌ Creates precedent, gates become meaningless

❌ Creates precedent, gates become meaningless

if: github.event.pull_request.labels.contains('urgent') run: echo "Skipping quality gates"
if: github.event.pull_request.labels.contains('urgent') run: echo "Skipping quality gates"

✅ Gates are non-negotiable

✅ Gates are non-negotiable

If code can't pass gates, it shouldn't ship

If code can't pass gates, it shouldn't ship

undefined
undefined

SwiftLint Anti-Patterns

SwiftLint反模式

NEVER disable rules project-wide without documenting why:
yaml
undefined
绝对不要在不记录原因的情况下全局禁用规则:
yaml
undefined

❌ Mystery disabled rules

❌ Mystery disabled rules

disabled_rules:
  • force_unwrapping
  • force_cast
  • line_length
disabled_rules:
  • force_unwrapping
  • force_cast
  • line_length

✅ Document the reasoning

✅ Document the reasoning

disabled_rules:

line_length: Configured separately with custom thresholds

force_unwrapping: Using force_unwrapping opt-in rule instead (stricter)


**NEVER** use inline disables without explanation:
```swift
// ❌ No context
// swiftlint:disable force_unwrapping
let value = optionalValue!
// swiftlint:enable force_unwrapping

// ✅ Explain why exception is valid
// swiftlint:disable force_unwrapping
// Reason: fatalError path for corrupted bundle resources that should crash
let value = optionalValue!
// swiftlint:enable force_unwrapping
NEVER silence all warnings in a file:
swift
// ❌ Nuclear option hides real issues
// swiftlint:disable all

// ✅ Disable specific rules for specific lines
// swiftlint:disable:next identifier_name
let x = calculateX()  // Math convention
disabled_rules:

line_length: Configured separately with custom thresholds

force_unwrapping: Using force_unwrapping opt-in rule instead (stricter)


**绝对不要**在不解释原因的情况下内联禁用规则:
```swift
// ❌ No context
// swiftlint:disable force_unwrapping
let value = optionalValue!
// swiftlint:enable force_unwrapping

// ✅ Explain why exception is valid
// swiftlint:disable force_unwrapping
// Reason: fatalError path for corrupted bundle resources that should crash
let value = optionalValue!
// swiftlint:enable force_unwrapping
绝对不要静默文件中的所有警告:
swift
// ❌ Nuclear option hides real issues
// swiftlint:disable all

// ✅ Disable specific rules for specific lines
// swiftlint:disable:next identifier_name
let x = calculateX()  // Math convention

CI Anti-Patterns

CI反模式

NEVER run expensive gates first:
yaml
undefined
绝对不要先运行耗时的质量门:
yaml
undefined

❌ Slow feedback — tests run even if lint fails

❌ Slow feedback — tests run even if lint fails

jobs: test: # 10 minutes ... lint: # 30 seconds ...
jobs: test: # 10 minutes ... lint: # 30 seconds ...

✅ Fast feedback — fail fast on cheap checks

✅ Fast feedback — fail fast on cheap checks

jobs: lint: runs-on: macos-14 steps: [swiftlint, swiftformat]
build: needs: lint # Only if lint passes ...
test: needs: build # Only if build passes ...

**NEVER** run quality gates only on PR:
```yaml
jobs: lint: runs-on: macos-14 steps: [swiftlint, swiftformat]
build: needs: lint # Only if lint passes ...
test: needs: build # Only if build passes ...

**绝对不要**仅在PR中运行质量门:
```yaml

❌ Main branch can have violations

❌ Main branch can have violations

on: pull_request: branches: [main]
on: pull_request: branches: [main]

✅ Protect main branch too

✅ Protect main branch too

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

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

---

Essential Configurations

必要配置

Minimal SwiftLint (Start Here)

基础SwiftLint配置(从这里开始)

yaml
undefined
yaml
undefined

.swiftlint.yml — Essential rules only

.swiftlint.yml — Essential rules only

excluded:
  • Pods
  • .build
  • DerivedData
excluded:
  • Pods
  • .build
  • DerivedData

Most impactful opt-in rules

Most impactful opt-in rules

opt_in_rules:
  • force_unwrapping # Catches crashes
  • implicitly_unwrapped_optional
  • empty_count # Performance
  • first_where # Performance
  • contains_over_first_not_nil
  • fatal_error_message # Debugging
opt_in_rules:
  • force_unwrapping # Catches crashes
  • implicitly_unwrapped_optional
  • empty_count # Performance
  • first_where # Performance
  • contains_over_first_not_nil
  • fatal_error_message # Debugging

Sensible limits

Sensible limits

line_length: warning: 120 error: 200 ignores_urls: true ignores_function_declarations: true
function_body_length: warning: 50 error: 80
cyclomatic_complexity: warning: 10 error: 15
type_body_length: warning: 300 error: 400
file_length: warning: 500 error: 800
undefined
line_length: warning: 120 error: 200 ignores_urls: true ignores_function_declarations: true
function_body_length: warning: 50 error: 80
cyclomatic_complexity: warning: 10 error: 15
type_body_length: warning: 300 error: 400
file_length: warning: 500 error: 800
undefined

Minimal SwiftFormat

基础SwiftFormat配置

ini
undefined
ini
undefined

.swiftformat — Essentials only

.swiftformat — Essentials only

--swiftversion 5.9 --exclude Pods,.build,DerivedData
--indent 4 --maxwidth 120 --wraparguments before-first --wrapparameters before-first
--swiftversion 5.9 --exclude Pods,.build,DerivedData
--indent 4 --maxwidth 120 --wraparguments before-first --wrapparameters before-first

Non-controversial rules

Non-controversial rules

--enable sortedImports --enable trailingCommas --enable redundantSelf --enable redundantReturn --enable blankLinesAtEndOfScope
--enable sortedImports --enable trailingCommas --enable redundantSelf --enable redundantReturn --enable blankLinesAtEndOfScope

Disable controversial rules

Disable controversial rules

--disable acronyms --disable wrapMultilineStatementBraces
undefined
--disable acronyms --disable wrapMultilineStatementBraces
undefined

Xcode Build Settings (Quality Enforced)

Xcode构建设置(强制执行质量要求)

ini
undefined
ini
undefined

QualityGates.xcconfig

QualityGates.xcconfig

// Fail on warnings SWIFT_TREAT_WARNINGS_AS_ERRORS = YES GCC_TREAT_WARNINGS_AS_ERRORS = YES
// Strict concurrency (Swift 6 prep) SWIFT_STRICT_CONCURRENCY = complete
// Static analyzer RUN_CLANG_STATIC_ANALYZER = YES CLANG_STATIC_ANALYZER_MODE = deep

---
// Fail on warnings SWIFT_TREAT_WARNINGS_AS_ERRORS = YES GCC_TREAT_WARNINGS_AS_ERRORS = YES
// Strict concurrency (Swift 6 prep) SWIFT_STRICT_CONCURRENCY = complete
// Static analyzer RUN_CLANG_STATIC_ANALYZER = YES CLANG_STATIC_ANALYZER_MODE = deep

---

CI Patterns

CI模式

GitHub Actions (Minimal Effective)

GitHub Actions(最简有效配置)

yaml
name: Quality Gates

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

jobs:
  # Fast gates first
  lint:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - run: brew install swiftlint swiftformat
      - run: swiftlint lint --strict --reporter github-actions-logging
      - run: swiftformat . --lint

  # Build only if lint passes
  build:
    needs: lint
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - name: Build with strict warnings
        run: |
          xcodebuild build \
            -scheme App \
            -destination 'platform=iOS Simulator,name=iPhone 15' \
            SWIFT_TREAT_WARNINGS_AS_ERRORS=YES

  # Test only if build passes
  test:
    needs: build
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - name: Test with coverage
        run: |
          xcodebuild test \
            -scheme App \
            -destination 'platform=iOS Simulator,name=iPhone 15' \
            -enableCodeCoverage YES \
            -resultBundlePath TestResults.xcresult

      - name: Check coverage threshold
        run: |
          COVERAGE=$(xcrun xccov view --report --json TestResults.xcresult | \
            jq '.targets[] | select(.name | contains("App")) | .lineCoverage * 100')
          echo "Coverage: ${COVERAGE}%"
          if (( $(echo "$COVERAGE < 80" | bc -l) )); then
            echo "❌ Coverage below 80%"
            exit 1
          fi
yaml
name: Quality Gates

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

jobs:
  # Fast gates first
  lint:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - run: brew install swiftlint swiftformat
      - run: swiftlint lint --strict --reporter github-actions-logging
      - run: swiftformat . --lint

  # Build only if lint passes
  build:
    needs: lint
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - name: Build with strict warnings
        run: |
          xcodebuild build \
            -scheme App \
            -destination 'platform=iOS Simulator,name=iPhone 15' \
            SWIFT_TREAT_WARNINGS_AS_ERRORS=YES

  # Test only if build passes
  test:
    needs: build
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - name: Test with coverage
        run: |
          xcodebuild test \
            -scheme App \
            -destination 'platform=iOS Simulator,name=iPhone 15' \
            -enableCodeCoverage YES \
            -resultBundlePath TestResults.xcresult

      - name: Check coverage threshold
        run: |
          COVERAGE=$(xcrun xccov view --report --json TestResults.xcresult | \
            jq '.targets[] | select(.name | contains("App")) | .lineCoverage * 100')
          echo "Coverage: ${COVERAGE}%"
          if (( $(echo "$COVERAGE < 80" | bc -l) )); then
            echo "❌ Coverage below 80%"
            exit 1
          fi

Pre-commit Hook (Local Enforcement)

预提交钩子(本地强制执行)

bash
#!/bin/bash
bash
#!/bin/bash

.git/hooks/pre-commit

.git/hooks/pre-commit

echo "Running pre-commit quality gates..."
echo "Running pre-commit quality gates..."

SwiftLint (fast)

SwiftLint (fast)

if ! swiftlint lint --strict --quiet 2>/dev/null; then echo "❌ SwiftLint failed" exit 1 fi
if ! swiftlint lint --strict --quiet 2>/dev/null; then echo "❌ SwiftLint failed" exit 1 fi

SwiftFormat check (fast)

SwiftFormat check (fast)

if ! swiftformat . --lint 2>/dev/null; then echo "❌ Code formatting issues. Run: swiftformat ." exit 1 fi
echo "✅ Pre-commit checks passed"

---
if ! swiftformat . --lint 2>/dev/null; then echo "❌ Code formatting issues. Run: swiftformat ." exit 1 fi
echo "✅ Pre-commit checks passed"

---

Threshold Calibration

阈值校准

Finding the Right Coverage Threshold

找到合适的覆盖率阈值

Step 1: Measure current coverage
$ xcodebuild test -enableCodeCoverage YES ...
$ xcrun xccov view --report TestResults.xcresult

Step 2: Set threshold slightly below current
Current: 73% → Set threshold: 70%
Prevents regression without blocking

Step 3: Ratchet up over time
Week 1: 70%
Week 4: 75%
Week 8: 80%
Stop at: 80-85% (diminishing returns above)
Step 1: Measure current coverage
$ xcodebuild test -enableCodeCoverage YES ...
$ xcrun xccov view --report TestResults.xcresult

Step 2: Set threshold slightly below current
Current: 73% → Set threshold: 70%
Prevents regression without blocking

Step 3: Ratchet up over time
Week 1: 70%
Week 4: 75%
Week 8: 80%
Stop at: 80-85% (diminishing returns above)

SwiftLint Warning Budget

SwiftLint警告预算

yaml
undefined
yaml
undefined

Start permissive, tighten over time

Start permissive, tighten over time

Week 1

Week 1

MAX_WARNINGS: 100
MAX_WARNINGS: 100

Week 4

Week 4

MAX_WARNINGS: 50
MAX_WARNINGS: 50

Week 8

Week 8

MAX_WARNINGS: 20
MAX_WARNINGS: 20

Target (after cleanup sprint)

Target (after cleanup sprint)

MAX_WARNINGS: 0

---
MAX_WARNINGS: 0

---

Quick Reference

快速参考

Gate Priority Order

质量门优先级排序

PriorityGateTimeROI
1SwiftLint~30sHigh — catches bugs
2SwiftFormat~15sMedium — consistency
3Build (0 warnings)~2-5mHigh — compiler catches issues
4Unit Tests~5-15mHigh — catches regressions
5Coverage Check~1mMedium — enforces testing
6Static Analysis~5-10mMedium — deep issues
PriorityGateTimeROI
1SwiftLint~30sHigh — catches bugs
2SwiftFormat~15sMedium — consistency
3Build (0 warnings)~2-5mHigh — compiler catches issues
4Unit Tests~5-15mHigh — catches regressions
5Coverage Check~1mMedium — enforces testing
6Static Analysis~5-10mMedium — deep issues

Red Flags

危险信号

SmellProblemFix
Gates disabled for "urgent" PRCulture problemGates are non-negotiable
100% coverage requirementGaming metrics80-85% is optimal
All SwiftLint rules enabledToo noisyCurate impactful rules
Pre-commit takes > 30sDevs skip itOnly fast checks locally
Different rules in CI vs localSurprisesSame config everywhere
SmellProblemFix
Gates disabled for "urgent" PRCulture problemGates are non-negotiable
100% coverage requirementGaming metrics80-85% is optimal
All SwiftLint rules enabledToo noisyCurate impactful rules
Pre-commit takes > 30sDevs skip itOnly fast checks locally
Different rules in CI vs localSurprisesSame config everywhere

SwiftLint Rules Worth Enabling

值得启用的SwiftLint规则

RuleWhy
force_unwrapping
Prevents crashes
implicitly_unwrapped_optional
Prevents crashes
empty_count
Performance (O(1) vs O(n))
first_where
Performance
fatal_error_message
Better crash logs
unowned_variable_capture
Prevents crashes
unused_closure_parameter
Code hygiene
RuleWhy
force_unwrapping
Prevents crashes
implicitly_unwrapped_optional
Prevents crashes
empty_count
Performance (O(1) vs O(n))
first_where
Performance
fatal_error_message
Better crash logs
unowned_variable_capture
Prevents crashes
unused_closure_parameter
Code hygiene

SwiftLint Rules to Avoid

应避免的SwiftLint规则

RuleWhy Avoid
explicit_type_interface
Swift has inference for a reason
file_types_order
Team preference varies
prefixed_toplevel_constant
Outdated convention
extension_access_modifier
Rarely adds value
RuleWhy Avoid
explicit_type_interface
Swift has inference for a reason
file_types_order
Team preference varies
prefixed_toplevel_constant
Outdated convention
extension_access_modifier
Rarely adds value