【Python】Linterとして選ばれたのは「Ruff」でした

# はじめに
2022年頃まで、Pythonの開発でLinterやFormaterを導入する場合、以下のツールを採用することが多く見受けられていました。

| ツール | 用途 |
| --- | --- |
| [Flake8](https://flake8.pycqa.org/en/latest/) | Linter |
| [Pylint](https://pylint.readthedocs.io/en/stable/) | Linter |
| [isort](https://pycqa.github.io/isort/) | Import Sorter |
| [Black](https://black.readthedocs.io/en/stable/) | Formatter |

そんな中、これらのツールに対して一石を投じるようなツールが登場しました。それがRuffです。
本稿は、そんなRuffを導入する際の足がかりとして参考になれば幸いです。

# Ruffについて
Rustで書かれた非常に高速なPythonのLinterです。

https://astral.sh/ruff


## Ruffの特徴
Ruffの特徴として以下のようなものが挙げられます。
<!-- textlint-disable -->
> ⚡️ 既存のリンターよりも 10 ～ 100 倍高速
🐍 経由でインストール可能pip
🛠️ pyproject.tomlサポート
🤝 Python 3.12 との互換性
📦 組み込みのキャッシュにより、変更されていないファイルの再分析を回避します
🔧 自動エラー修正のサポートを修正 (例: 未使用のインポートを自動的に削除)
📏 700 を超える組み込みルール
⚖️ 組み込みの Flake8 ルールセットとほぼ同等
🔌 flake8-bugbear など、数十の Flake8 プラグインのネイティブ再実装
⌨️ VS Codeなどの ファーストパーティエディターの統合
🌎 モノリポに適しており、階層的およびカスケード構成を備えています
https://docs.astral.sh/ruff/ より抜粋
<!-- textlint-enable -->

個人的には、以下の点に魅力を感じました。
- 既存のLinterから置き換えが可能
    - 主要なLinterのルールをサポートしており、一部ツールに関しては移行ツールも提供されています
- 既存のLinterよりも高速
    - 開発中の待ち時間が減り、開発体験の向上に繋がります
- [PEP621](https://peps.python.org/pep-0621/)に準拠（pyproject.tomlサポート）
    - ツールごとで独自の設定ファイルで管理する必要がなくなり、メンテナンスが容易になります

## 静的解析ツールのトレンド
[Ruffの公式ブログ](https://astral.sh/blog/announcing-astral-the-company-behind-ruff)で公開されていた、Pythonの開発で広く利用されているツールのGitHub Star数の推移図が以下になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107176/4004503b-4cc2-1c38-b1d4-d38386ac7708.png)
Ruffは破竹の勢いでGitHub Starが増えており、注目度の高さが見てとれます。
※本稿執筆時点で確認したところ、Star数は18,000を超えていました。

# セットアップ
ローカル環境やエディタ、CI環境への導入方法は以下の通りです。
## ローカル環境
ローカルで実行するにあたり、[pre-commit](https://pre-commit.com/)を使って実行させる場合は以下のように設定します。

```yaml:.pre-commit-config.yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
  rev: v0.0.292
  hooks:
    - id: ruff
      args: [--config, pyproject.toml]
```

## エディタ(VSCode)
Ruff公式からVSCode向けのExtentionが提供されているので、そちらを利用します。
https://github.com/astral-sh/ruff-vscode

保存時に自動で実行させたい場合は、以下のように設定します。
```json:settings.json
{
  "[python]": {
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": true,
      "source.organizeImports.ruff": true
    }
  }
}
```

他にも柔軟に設定ができるので、上記以外の設定は[こちら](https://github.com/astral-sh/ruff-vscode)を参照してください。

## CI環境(GitHub Actions)
GitHub Actionsで実行させる場合は、以下のように設定します。

```yaml:.github/workflows/ruff.yml
name: Ruff
on: [ push, pull_request ]
jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: chartboost/ruff-action@v1
        with:
            src: "./src"
            version: 0.0.292
            args: --config pyproject.toml
```

詳細は[こちら](https://docs.astral.sh/ruff/usage/#github-action)を参照してください。

# パフォーマンス
実行時間を計測するにあたり、以下のような条件で計測しました。
## コードボリューム
<!-- textlint-disable -->
- Pythonファイル数・・・・・・約1800ファイル
- Pythonファイル合計行数・・・約26万行
<!-- textlint-eable -->
## 設定ファイル
```toml:pyproject.toml
[tool.ruff]
target-version="py39"
select = [
    "E",
    "F",
    "W",
    "I",
    "Q",
]
line-length = 119
[tool.ruff.flake8-quotes]
inline-quotes = "single"

[tool.ruff.isort]
force-single-line = true
```

## 実行結果
参考までに、Ruffへの移行前と移行後の実行時間を比較してみましたが、20〜30倍ほど高速化されていることが分かります。
| ツール | 実行時間 | 備考 |
| --- | --- | --- |
| Flake8 | 26.06s user 0.57s system 96％ cpu 27.565 total | Ruff移行前 |
| isort | 4.01s user 2.13s system 70％ cpu 8.768 total | Ruff移行前 |
| Ruff | 1.21s user 0.41s system 106％ cpu 1.519 total | Flake8とisortのルールは同等 |

# Tips
## 設定ファイルの統合
Ruffの導入以前はFlake8やisortなどの設定ファイルをそれぞれ管理していましたが、Ruffではpyproject.tomlに統一することができます。
詳細は[こちら](https://docs.astral.sh/ruff/configuration/#configuring-ruff)を参照してください。

### Ruff導入前
```config:.isort.cfg
[settings]
force_single_line=true
```

```config:setup.cfg
[flake8]
ignore =
    E501,
    F821
max-line-length = 119
per-file-ignores =
    ./tests/*: E101
```

### Ruff導入後
```toml:pyproject.toml
[tool.ruff]
ignore = [
    "E501",
    "F821",
]
select = [
    "E",
    "F",
    "W",
    "I",
]

line-length = 119

[tool.ruff.isort]
force-single-line = true

[tool.ruff.per-file-ignores]
"./tests/*" = ["E101"]
```

## Flake8のルールをRuffに移行する
Ruff公式が提供してくれている[flake8-to-ruff](https://github.com/astral-sh/ruff/blob/main/crates/flake8_to_ruff/README.md)を利用することで、Ruff導入以前に適応していたFlake8の設定をRuffに適応させることができます。

## ルールの適応方法
すでに開発が進んでいるプロジェクトであれば、上述した移行ツールなどを利用して、既存のルールをRuffに適応させることが多いと思います。
しかし、新規でプロジェクトを立ち上げる場合は、Ruffのルールを全適応させ、必要に応じて無効化する運用が良いと思います。
Ruffであればルールを増やしたとしてもパフォーマンスに影響が出にくく、かつpyproject.tomlで管理できるため、ルールの管理も容易となります。

# さいごに
Ruffはリリースから1年ほどしか経過していませんが、既存のLinterから移行するだけの価値がある、非常に魅力的なツールだと感じました。
~~また、現在はPythonのFormatterとして有名なBlackをサポートする動きもあり、v0.0.289からアルファリリースとして公開されました。
https://github.com/astral-sh/ruff/discussions/7310~~

（2023/10/26追記）
[v0.1.2](https://github.com/astral-sh/ruff/releases/tag/v0.1.2)からベータ版としてフォーマッタがサポートされました！
公式ブログで紹介されているベンチーマークによると、「Blackより30倍、YAPFよりも100倍高速である」とされています。

https://astral.sh/blog/the-ruff-formatter

Ruffは今後ますます注目されるツールになると思います。
今後の動向から目が離せません。