Djangoでコミット前に yapf / isort を実行する

# はじめに
ソフトウェア開発において、ソースコードの可読性と一貫性は重要です。特に、複数の開発者が携わる場合、あるいはプロジェクトが長期間に渡って継続する場合、各自のコーディングスタイルが異なると、コードの理解や保守が難しくなります。そこで、プロジェクト全体に統一されたコーディングルールを導入するために、フォーマッターが使用されています。フォーマッターとは、コードのインデントや空白・改行などを自動的に調整し、統一されたコーディングスタイルを適用するツールです。

例えば、[ロバストPython―クリーンで保守しやすいコードを書く（2023）](https://www.oreilly.co.jp/books/9784814400171/)では、「ロバストネス」という概念を用いて、クリーンコードの重要性が述べられています。クリーンコードを実現するためには、次のような態度が重要であると、本書には記載されています。

> 本書は、コラボレータやメンテナについて考える出発点を提供する。持続可能な技術の実践について考え、長持ちするコードを書く必要がある。そのための第一歩は、コードを通じてコミュニケーションを取ることだ。将来の開発者があなたの意図を理解できるようにする必要がある（p.5）

ソースコードを同僚あるいは未来の開発者とのコミュニケーションツールだと捉えることで、コーディングスタイルを統一することの重要性を私は改めて認識しました。本記事では、Djangoのプロジェクトにおいて、フォーマッターを導入する方法、特にフォーマッターによる検証を自動実行する方法について記述します。

# フォーマッターの導入
今回は yapf と isort というフォーマッターを導入します。black や ruff など、Pythonには他のフォーマッターも存在しているので、好みのフォーマッターを選択してください。

### yapf
yapf は、Googleが開発したオープンソースのPythonフォーマッターです。PEP8に基づいたスタイルガイドに沿ってコードを整形することで、可読性と一貫性を向上させます。Black などの他のフォーマッターと比べて、フォーマットのルールをカスタマイズできる範囲が広いのが特徴です。詳しくは公式GitHubリポジトリを参照してください。

https://github.com/google/yapf

導入は使用しているパッケージ管理ツールに従ってください。

```
$ pipenv install yapf
```

### isort
isort は、Pythonのインポート文を自動的に整理・分類するためのユーティリティです。アルファベット順、セクション別、そして各セクション内でのカスタムの順序付けなど、さまざまな方法でインポート文をソートします。これにより、コードの可読性と一貫性を向上させます。詳細については、公式ドキュメントを参照してください。

https://pycqa.github.io/isort/

導入は使用しているパッケージ管理ツールに従ってください。

```
$ pipenv install isort
```


### 設定
両者の設定が競合しないように、pyproject.toml に yapf / isort の設定を記載します。下記の設定は一例です。好みの設定に変更して使用してください。

```toml
[tool.yapf]
# 複数の論理演算子が1つの式内に存在し、その式が一行に収まらない場合に、論理演算子の前で改行する
split_before_logical_operator = true

# インラインコメントの前に2文字のスペースを開ける
spaces_before_comment = 2

# 閉じ括弧やブラケットが複数行にわたる式の最初の行と同じインデントレベルで始まるようにする
dedent_closing_brackets = true

# 行の長さの上限を119文字に設定
column_limit = 119


[tool.isort]
# Googleスタイルガイドに基づいた設定を使用
profile = "google"

# yapfのデフォルトの行長と一致させる
line_length = 119

# Googleスタイルに合わせてインポートを複数行に分割
multi_line_output = 3

# yapfと一致させるため、末尾のカンマを含める
include_trailing_comma = true

# インポートの複数行分割時に括弧を使用
use_parentheses = true

# インポートの分割を強制
force_single_line = false
```

### 実行
次のコマンドをターミナルで実行することで、プロジェクト全体に yapf / isort によるフォーマットを適用することができます。

```
$ isort .
$ yapf -ir .
```

一括で適用できるように、`.bashrc` に下記のエイリアスを追加しています。

```
alias django-format="isort . && yapf -ir ."
```

# 自動検証の実現
ここまでの設定により、一括でフォーマットを実行できるようになりました。しかし、フォーマットを実行するか否かは、あくまで開発者に委ねられている状態です。仮にフォーマットをかけないままで、devブランチにマージされてしまうと、他の開発者がそこから切ったブランチでフォーマットを実行した時に、予期せぬ部分にまでフォーマットがかかってしまいます。手動運用で乗り切ることもできますが、得策ではないでしょう。そこで、自動検証の導入方法を検討しました。

導入したのは `pre-commit` というツールです。pre-commit は、Git フックを利用してコミット前に自動的に各種チェックやフォーマットを実行するためのフレームワークです。設定ファイルを使用して、実行するツールやそのオプションを指定します。これにより、コミット時に自動的に yapf や isort などのフォーマッターを実行することができます。詳しくは公式のWebサイトを参照してください。

https://pre-commit.com/

導入は使用しているパッケージ管理ツールに従ってください。

```
$ pipenv install pre-commit
```

### 設定
pre-commit の設定は `.pre-commit-config.yml` に記載します。yapf 及び isort を実行したい場合は、下記の設定ファイルを作成します。Versionは作成時点で最新のものです。

```yml
repos:
  - repo: https://github.com/pycqa/isort
    rev: 5.13.2
    hooks:
      - id: isort
        name: isort (python)
  - repo: https://github.com/google/yapf
    rev: v0.40.2
    hooks:
      - id: yapf
        name: yapf (python)
```

その上で、下記のコマンドを実行します。

```
$ pre-commit install
```

### 実行
上述の設定をすると、commit の度に yapf / isort による検証が走るようになります。

例えば、isort のみ適合しない状態になっていると、下記のように、isort は Failed で。yapf は Passed と表示されます。このとき、commit は行われません。

```
isort (python)...........................................................Failed
- hook id: isort
- files were modified by this hook

Fixing <ここにファイル名が入る>

yapf (python)............................................................Passed
```

フォーマットを適用して、yapf / isort ともに適合する状態になると、ともに Passed と表示され、commit を実行完了することができます。

# まとめ
備忘録を兼ねて、Pythonにおけるフォーマッターと、その自動実行ツールである pre-commit の紹介をしました。ファイルを保存する度にフォーマッターが実行されるのも、実行が人間に委ねられるのも微妙な開発体験だと感じていたので、commit ごとに自動検証されるのはちょうど良い頻度なのではないかと思っています。より良い方法があれば、ぜひ教えてください。

後ほど mypy などの型チェッカについても記事にまとめようと考えています。