HN 日本語サマリー

← 一覧へ戻る
インフラ・DevOps

ImpactGate: AIが追加する構造的劣化をスコアリングするマージゲート

ImpactGate: A merge gate that scores the structural decay AI adds (github.com)

33 pointsby sagenschneider34 コメント

要約

ImpactGateは、コード変更が導入する構造的劣化(複雑性の増加)を測定・ゲートするツールです。変更の影響度を計算し、しきい値を超えた場合に警告またはビルドをブロックします。CLI、gitフック、GitHub Actions、GitLab CI、Jenkins CIなどのワークフローに統合でき、絶対的なしきい値またはプロジェクトの履歴に基づいたパーセンタイルによるゲート設定が可能です。

全文翻訳

impact-gate

A merge gate that flags changes piling complexity onto code that is already complex, before a class quietly grows into a god class nobody can safely touch. Run it as a standalone CLI, a git pre-commit hook, or a plugin in GitHub, GitLab, and Jenkins CI.

Website: https://impactgate.officefloor.net

そのパターンは徐々に進行します。クラスにメソッドが一つ追加され、さらに一つ、また一つと増えていきます。個々の変更は妥当に見えます。しかし、数十回の変更を経て、クラスは5つの異なるジョブを実行するようになり、あらゆる編集がより危険になります。このゆっくりとした蓄積が、私たちが「構造的劣化」(構造的侵食とも呼ばれる)と呼ぶものです。このゲートは、各変更をデフォルトでmainブランチと比較してスコアリングするため、修正が安価なうちに問題が表面化します。

impact = files_changed * Σ max(WMC_other, 1) * CC * Δlines      (over changed functions)

WMC_otherは、編集中のファイルまたはクラスに既に存在する複雑性で、変更前に測定されたものです。全く新しいファイルを追加するのは安価です。そこには悪化させるものが何もありませんでした。既に重いクラスに複雑なメソッドを追加するのは高価です。このゲートは、差分の生のサイズではなく、その差を測定します。

この計算式の根拠については、OfficeFloorブログのMeasuring the Blast Radius of Changeを参照してください。設計思想、ImpactGateが配置をスコアリングし、意図的に重複をレビューに残す理由については、REASONING.mdを参照してください。

インパクトが高すぎると、ゲートは変更の単純化または触れたコードのリファクタリングを要求します。警告(レポートのみ)またはブロック(ビルド失敗)が可能です。

SonarQube(およびその他の静的解析)との違い

SonarQubeやリンターは、コードの状態をスコアリングします。このファイルは複雑すぎる、このメソッドは長すぎる、このブロックは重複している、などです。

ImpactGateは、変更の限界コストを、それが適用される対象を考慮してスコアリングします。数式中のWMC_other項は、あなたが触れる前のクラスに既に存在する複雑性です。新しいファイル内の複雑なメソッドは安価です。既に5つの責務を担っているクラスに同じメソッドを追加するのは高価です。それは、重いクラスを誰も安全に編集できないゴッドクラスに変える増分です。

重複は、最初のリストにある「状態」の発見事項の一つであり、ImpactGateはそれをスコアリングしません。これは意図的なもので、省略ではありません。与えられたコピーが悪意のある重複なのか、正当な別個の決定なのかは、ノイズなしに決定的なシグナルでは判断できない設計上の判断です。REASONING.mdではその理由と、重複が代わりにどこに属するのかを説明しています。

言語

このゲートは言語に依存しません。以下の言語の変更をスコアリングできます。

Java, C#, C, C++, JavaScript, TypeScript, Python, Go, Kotlin, Swift, Ruby, PHP, Rust, Scala, Objective-C, Lua, and TTCN-3.

複雑性はlizardによって関数ごとに解析されます。混合言語のリポジトリも同様の方法でスコアリングされます。

拡張子を追加またはマッピングできます(lang_by_ext)。

グレーディングカーブは、Python, Java, TypeScript, C#, JavaScript, C, Scala, and Goに対して言語ごとに調整されています。その他の言語も測定・評価されます。それらは、プロジェクトのベースラインが独自の履歴を構築するまで、プールされたクロス言語分布にフォールバックします。

インストール

pip install impact-gate         # `impact-gate` コマンドをインストールします

または、何もインストールせずに、公開されているイメージ経由で実行します。

docker run --rm -v "$PWD":/repo ghcr.io/officefloor/impact-gate \
  score --mode range --base origin/main

ローカルで開発するには、チェックアウトからインストールします。

python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'         # 編集可能インストールとテスト依存関係

使用方法

# コミットしようとしているコミット(pre-commit):ステージング済み vs HEAD。これがデフォルトです。
impact-gate score

# コミットされていないローカル編集:ワーキングツリー vs HEAD。
impact-gate score --mode worktree

# CIまたはPRレビュー:コミットされたブランチ vs main(merge-base..HEAD)。
impact-gate score --mode range --base origin/main --format json

# しきい値と強制設定。これらは .impact-gate.yml にも記述できます。
impact-gate score --warn-at 50000 --block-at 200000 --enforcement block

終了コード。0はOKまたは警告(変更は許可されます)を意味します。2はブロック(--enforcement blockでインパクトが高すぎる)を意味します。1は使用法または環境エラーです。

各レポートには、リファクタリングを検討すべきファイルも、インパクトのシェアでランク付けされてリストされます。変更レベルの数値がゲートを通過させます。ファイルごとのランキングは、劣化が集中している場所を示しており、ゴッドクラスに静かに成長しているファイルが、何かをブロックする前に候補として浮上します。

差分がmax_diff_lines(デフォルトで200,000、測定設定内)より大きいソースファイルは、ほぼ常に生成されたダンプまたはベンダーされたブロブです。ゲートはそれをスキップするため、数値を歪めたりスコアリングを遅くしたりせず、スキップ済みの下にリストされるため、結果がサイレントに間違っていることはありません。

git pre-commitフックとしての使用

CIの前に、ローカルで各コミットをゲートします。

# .git/hooks/pre-commit をインストールします。各コミットでステージングされた変更をスコアリングします。
impact-gate install-hook

.impact-gate.ymlでenforcement: blockを使用すると、インパクトが高すぎるコミットはブロックされます。warn(またはオフ)の場合、レポートが表示され、コミットは続行されます。既存のpre-commitフックを上書きするには--forceで再実行してください。

pre-commitフレームワークを使用していますか?このリポジトリはフック定義を提供しています。これを .pre-commit-config.yaml に追加してください。

repos:
  - repo: https://github.com/officefloor/ImpactGate
    rev: v0.3.0
    hooks:
      - id: impact-gate

分布に対するグレーディング(カーブ)

生のしきい値を設定するのは困難です。典型的な変更のインパクトは、言語やプロジェクトによって桁違いに異なります。数値を推測する代わりに、変更を分布に対するパーセンタイルでグレーディングし、パーセンタイルでゲートします。

# マージされた履歴からプロジェクト固有のインパクト分布を構築(または更新)します。
# .impact-gate-baseline.json を書き込みます。ブランチが移動したら再実行してください。
impact-gate baseline --base-ref main

# 絶対値ではなくグレードでゲートします。
impact-gate score --curve --warn-percentile 90 --block-percentile 98

グレードは2つの分布をブレンドします。

  • シードプライア:ツールに同梱されています。20のオープンソースリポジトリから構築された言語ごとのパーセンタイルテーブルで、テーブルにない言語にはプールされたフォールバックがあります。
  • プロジェクトベースライン:マージされたメインラインからウォークされたリポジトリ固有の変更ごとの分布を使用します。

ブレンドはプロジェクトを w = n / (n + K) で重み付けします。ここで n はベースラインの後ろにある着陸した変更の数、K (curve_prior_weight、デフォルト200) はプロジェクトをシードよりも信頼するために必要な履歴の量です。ベースラインファイルがない新しいリポジトリは、シードのみでグレーディングされます。深い履歴はそれ自体に依存します。グレードはすべてのフォーマットで生の数値の隣に表示されます。

認知複雑性(深くネストされたメソッド)でゲート

変更インパクトとカーブは、変更がどのように移動するかを測定します。読みやすいだけのメソッド(深い決定ネストを持つ動作中のコード)を見ることはできません。これはAIコードジェネレーターが典型的に生成する形状です。

--cognitive-max Nは、絶対的なメソッドごとの認知複雑性(Campbell 2018)のゲートをオンにします。変更されたファイル内のいずれかのメソッドがNを超えると、変更はブロックされます。これはインパクト番号とは独立しています。小さな差分でも、複雑なメソッドを残したためにここでブロックされる可能性があります。

# 認知複雑性が15を超えるメソッドを残す変更をブロックします(SonarSourceの行数)。
impact-gate score --enforcement block --cognitive-max 15

.impact-gate.yml で設定(リポジトリのルート)

warn_at: 50000          # 警告を発するインパクトの上限
block_at: 200000        # ブロックするインパクトの上限
enforcement: warn       # off, warn, or block. warnから始め、準備ができたらblockに切り替えます。
tolerance: 1.0          # 両方のしきい値に対するCI調整可能な乗数。1.0より大きいとより寛容になります。
# measure_config: .impact-measure.yml   # オプション:グロブと言語のオーバーライドを無視します
cognitive_max: 15       # この認知複雑性(null/省略=オフ)を超えるメソッドをブロックします

# グレーディングカーブ(パーセンタイルゲート)。有効にすると、warn_at/block_atは無視され、
# ゲートは以下のパーセンタイルを使用します。
curve_enabled: false           # 絶対値ではなくパーセンタイルグレードでゲートします
warn_percentile: 90            # 警告を発するグレード(以上)
block_percentile: 98           # ブロックするグレード(以上)
curve_prior_weight: 200        # w = n/(n+K)のK:プロジェクトをシードよりも信頼するために必要な履歴
baseline_file: .impact-gate-baseline.json   # `impact-gate baseline`が分布をキャッシュする場所

認知ゲートはカーブとは独立しています。絶対値またはパーセンタイルインパクトゲートと並行して、または単独で実行できます。

CLIフラグはファイルよりも優先されます。CIジョブは--toleranceまたは--warn-atを渡すことができます。これにより、チームはリポジトリを編集せずに許容範囲を調整できます。カーブのダイヤルにもフラグがあります:--curve、--warn-percentile、--block-percentile、--baseline-file。

GitHub Actions での使用

リポジトリにワークフローを追加します。アクションはPRブランチをベースと比較してスコアリングし、サマリーを書き込みます。ベースブランチとマージベースが存在するように、fetch-depth: 0が必要です。

name: Change impact
on: pull_request
permissions:
  contents: read
  pull-requests: write         # アクションがPRコメントとしてスコアを投稿できるようにするため
jobs:
  impact:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
      - uses: officefloor/ImpactGate@v0
        with:
          enforcement: warn        # 準備ができたらblockに切り替えます
          # warn-at: 50000
          # block-at: 200000
          # tolerance: 1.0

スコアはジョブサマリーとPRへの固定コメント(1つのコメント、実行ごとに更新)として表示されます。blockモードでは、インパクトがブロックしきい値を超えるとジョブが失敗します。マージをゲートするには、ブランチ保護でチェックを必須にします。PRコメントの追加にはpull-requests: writeが必要です。それがない場合、実行はパスしますが、コメントはスキップされます。

GitLab CI での使用

準備済みのジョブがci/gitlab-ci.ymlにあります。これを .gitlab-ci.yml にコピーするか、リモートからインクルードしてください。

include:
  - remote: 'https://raw.githubusercontent.com/officefloor/ImpactGate/v0/ci/gitlab-ci.yml'

マージリクエストパイプラインで実行され、MRをベース($CI_MERGE_REQUEST_DIFF_BASE_SHA)と比較してスコアリングし、MRに固定ノート(1つのノート、実行ごとに更新)を投稿します。必要なGITLAB_TOKENがない場合でも、スコアリングとゲートは行われます。ノートはスキップされるだけです。blockエンフォースメントでは、インパクトが高すぎるとジョブが失敗します。マージをゲートするには、マージリクエスト設定で必須にします。

Jenkins での使用

パイプラインスニペットがci/Jenkinsfileにあります。Dockerを備えたエージェントでDockerイメージを実行し、変更をターゲットブランチ(origin/${CHANGE_TARGET:-main})と比較してスコアリングし、レポートをアーカイブします。blockエンフォースメントでは、インパクトが高すぎるとステージが失敗します。PR/MRにスコアを戻すことは、SCM統合に任されています。ツール自体で投稿するには、プロバイダーのトークンと環境を設定してコンテナ内でimpact-gate commentを実行します。