🔤

keep-sortedでコード内リストを自動ソート管理する

に公開

なぜソートが必要なのか

ソートには以下のような利点があります:

1. 重複の発見

設定ファイルで同じキーを複数回定義してしまい、意図しない動作になったことはありませんか?
(私はないです)

allow_warping: false
enable_two_players: false
show_end_credits: true
enable_frost_band: false
enable_two_players: true  # 重複に気づきにくい

ソートすると重複が明確になりますね:

# keep-sorted start
allow_warping: false
enable_frost_band: false
enable_two_players: false
enable_two_players: true  # 重複が隣接して発見しやすい
show_end_credits: true
# keep-sorted end

参考: Sort Lines in Source Code - Google Testing Blog

2. 可読性とメンテナンス性の向上

  • アルファベット順になっていると、特定の項目を探しやすい
  • 追加・削除の差分が明確になる
  • マージコンフリクトが発生しにくくなる

3. レビューの負担軽減

「ソート順を揃えてください」というコメントが不要になります。

keep-sortedとは

keep-sortedは、Googleが開発したファイル内の特定セクションを自動的にソートするツールです。

https://github.com/google/keep-sorted

言語非依存で、マーカーで囲んだ部分を自動的にアルファベット順(または指定した順序)にソートしてくれます。

従来のツールとの違い

ソートツールには以下のようなものがあります:

言語専用ツール(import文に特化)

  • Python: isort, black
  • Go: goimports
  • JavaScript: eslint-plugin-import

汎用テキストソートツール

  • UNIXのsortコマンド
  • エディタのソート機能

言語専用ツールはimport文に特化しており、その他のリストには使えません。汎用ツールは任意のテキストをソートできますが、言語の構文やコンテキストを理解しません。

keep-sortedはその中間的な存在で:

  • 言語非依存 - あらゆるファイル形式で使える
  • 柔軟なソート対象 - 明示的にマーカーで囲んだ部分をソート
  • 細かい制御 - オプションで順序をカスタマイズ(numeric、prefix_orderなど)

インストール

Go 1.23以上が必要です。

go install github.com/google/keep-sorted@v0.7.1

基本的な使い方

ソートしたい部分を keep-sorted startkeep-sorted end で囲むだけです。

@Component(
    modules = {
      // keep-sorted start
      UtilsModule.class,
      GetRequestModule.class,
      PostRequestModule.class,
      AuthModule.class,
      // keep-sorted end
    })

keep-sortedを実行すると:

keep-sorted example.java

自動的にソートされます:

@Component(
    modules = {
      // keep-sorted start
      AuthModule.class,
      GetRequestModule.class,
      PostRequestModule.class,
      UtilsModule.class,
      // keep-sorted end
    })

実用的なユースケース

Expressのルーティング定義

HTTPメソッドを無視して、エンドポイント名でソートする:

// keep-sorted start ignore_prefixes=app.get,app.post,app.put,app.delete
app.post('/auth/login', loginHandler);
app.get('/users', getUsersHandler);
app.delete('/users/:id', deleteUserHandler);
app.post('/users', createUserHandler);
app.get('/posts', getPostsHandler);
app.put('/posts/:id', updatePostHandler);
app.get('/comments', getCommentsHandler);
// keep-sorted end

実行後、エンドポイント別にグループ化されます:

// keep-sorted start ignore_prefixes=app.get,app.post,app.put,app.delete
app.post('/auth/login', loginHandler);
app.get('/comments', getCommentsHandler);
app.get('/posts', getPostsHandler);
app.put('/posts/:id', updatePostHandler);
app.post('/users', createUserHandler);
app.get('/users', getUsersHandler);
app.delete('/users/:id', deleteUserHandler);
// keep-sorted end

ルーティング定義が多い場合に、この整理が役立ちます。

Markdownテーブル

ヘッダー行をスキップして内容だけソート:

<!-- keep-sorted start skip_lines=2 -->
| Name | Age | City |
|------|-----|------|
| Charlie | 35 | Tokyo |
| Alice | 28 | Osaka |
| Bob | 42 | Kyoto |
<!-- keep-sorted end -->

実行後:

<!-- keep-sorted start skip_lines=2 -->
| Name | Age | City |
|------|-----|------|
| Alice | 28 | Osaka |
| Bob | 42 | Kyoto |
| Charlie | 35 | Tokyo |
<!-- keep-sorted end -->

オプション

keep-sortedには豊富なオプションがあります。上の例で使った:

  • ignore_prefixes - 特定のプレフィックスを無視してソート
  • prefix_order - カスタム順序を指定
  • skip_lines=N - 先頭N行をスキップ

その他にも numeric=yes(数値ソート)、case=no(大文字小文字を区別しない)など様々なオプションがあります。

詳細は公式READMEを参照してください。

pre-commitとの統合

.pre-commit-config.yamlに以下を追加:

repos:
  - repo: https://github.com/google/keep-sorted
    rev: v0.7.1
    hooks:
      - id: keep-sorted

コミット時に自動的にソートされます。

$ git commit -m "Add unsorted list"
keep-sorted..............................................................Failed
- files were modified by this hook

ファイルが自動修正されるので、再度コミットすれば成功します:

$ git add .
$ git commit -m "Add sorted list"
keep-sorted..............................................................Passed

GitHub Actionsとの統合

pre-commitをスキップした変更を検知するため、CIでもチェックを行うことができます。

.github/workflows/keep-sorted.ymlを作成:

name: keep-sorted

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  check-sorted:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.23'

      - name: Install keep-sorted
        run: go install github.com/google/keep-sorted@v0.7.1

      - name: Check if files are sorted
        run: keep-sorted --mode=lint .

--mode=lint はチェックのみ行い、ファイルを変更しません。ソートされていなければCIが失敗します。

lint modeの出力例

$ keep-sorted --mode=lint unsorted.txt
[
  {
    "path": "unsorted.txt",
    "lines": { "start": 2, "end": 5 },
    "message": "These lines are out of order.",
    "fixes": [{
      "replacements": [{
        "lines": { "start": 2, "end": 5 },
        "new_content": "apple\nbanana\ncherry\nzebra\n"
      }]
    }]
  }
]

ベストプラクティス

ローカルとCIの組み合わせ

  1. ローカル(pre-commit) - 自動修正で開発者の利便性を向上
  2. CI(GitHub Actions) - 品質ゲートとして機能

この組み合わせで、ソート順の問題を削減できます。

適切な粒度で使う

すべてをソートする必要はありません。マージコンフリクトが起きやすい部分や、順序を統一したい部分に限定して使いましょう。

まとめ

keep-sortedは:

  • マージコンフリクトを削減 - 自動ソートで順序を統一
  • 言語非依存 - あらゆるファイル形式で使える
  • 柔軟 - 豊富なオプションで細かく制御
  • 自動化 - pre-commit/GitHub Actionsで運用可能

特に以下のような用途で活用できます:

  • APIルーティング定義
  • 設定ファイル
  • enumや定数リスト
  • Markdownテーブル

チーム開発でのコンフリクトを減らし、レビューをスムーズにするための選択肢として検討してみてください。

Discussion