OSSにコントリビュートするアイデアとRubyGemsでの実践例

# OSSにコントリビュートするアイデア

これまでいくつかの OSS にコントリビュートしてきた中で、**非推奨・廃止コードの対応**は比較的取り組みやすいと感じています。

そこで、今回は RubyGems での非推奨・廃止コードに対応する例を紹介します。RubyGems は1つの例であり、同じような対応をする事で他の言語やパッケージなどに応用できます。

主に「OSS にコントリビュートしたいけれど、具体的なアイデアが思いつかない、手段が分からない」といった課題を感じている方が参考になればうれしいです。

# 非推奨・廃止コードとは？

Ruby は一定の後方互換性を保ちつつ、時には非互換な変更を加えながら進化を続けているプログラミング言語です。非互換な変更として、例えばクラスやメソッドの廃止が該当します[^ruby]。

[^ruby]: 非互換な変更により、Ruby ユーザは既存コードの改修を余儀なくされます。しかし、ソフトウェア開発において、最初からすべてを予測する事は不可能であり、時代の変化に合った必要な対応だと考えています。また、Ruby の人気の高さは後方互換性の維持に固執しなかった事も影響していると思います。

ただし、実際に廃止されるまでは警告を出して、いくつかのバージョンを経た後に廃止されます。これは、各 gem や既存システムで十分な対策が取られるよう移行期間をとる事で、バージョンアップによって突然エラーが発生するリスクを回避するためです。

次は「移行期間がない場合」と「移行期間がある場合」を比較したイメージです[^ruby_migration]。

[^ruby_migration]: 説明のためのイメージであり、実際は複数のバージョンを経由して廃止される事が多いです。

![ruby_migration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36122/46d5ee60-4eb7-fa4e-26d1-6687c010518c.png)

非推奨のコードとは、将来廃止されるため利用が推奨されないコードであり、将来的に廃止コードになります。

# 非推奨・廃止コードの調べ方

まずは Ruby での非推奨・廃止コードを調べます。

この記事を書いているのは2022年の12月上旬で、12月25日にはおそらく Ruby 3.2 がリリースされます。新しいバージョンであればまだ移行が出来ていない gem が多いと想像して、最新の 3.2 のリリースノートを参照します。

- [Ruby 3\.2\.0 RC 1 Released](https://www.ruby-lang.org/en/news/2022/12/06/ruby-3-2-0-rc1-released/)

リリースノートからは、いくつかの定数やメソッドが廃止になっている事が分かります。

その中でも目を引くのが `Dir.exist?` と `File.exists?` です。私自身、過去にこれらのメソッドを使った事が多く、感覚的に「移行に対応できていない gem は多いのではないか？」と仮説を立てます。それぞれ、`Dir.exist?` と `File.exist?` への**置き換えが明確に推奨されていて、プロジェクト (gem) によって対応のブレが発生しづらい**のも取り組みやすいポイントです。

別のアプローチとして、静的な解析と修正を行うツールの [Rubocop](https://docs.rubocop.org/) に非推奨の警告を出すルールがあるので、非推奨に関してはそちらを参照する方法もあります。

- [Lint/DeprecatedClassMethods](https://docs.rubocop.org/rubocop/cops_lint.html#lintdeprecatedclassmethods)
   - 非推奨のクラスメソッドが使われていた場合の警告
- [Lint/DeprecatedConstants](https://docs.rubocop.org/rubocop/cops_lint.html#lintdeprecatedconstants)
   - 非推奨の定数が使われていた場合の警告

Rubocop の非推奨に関するルールは特定の Ruby バージョンに限定していないので、よりたくさんのコードを知ることが出来ます。一方で Ruby 側で非推奨が決定してからルールへの反映がすぐに行われる訳ではない点に注意が必要です[^rubocop_rule]。

[^rubocop_rule]: と言っても、Rubocop はアクティブなリポジトリなので実際はかなり速いです。

# 非推奨・廃止コードを使っている gem の調べ方

次に、非推奨・廃止コードを使っている gem を調べます。

方法として、例えば次の2つがあります。それぞれメリット・デメリットがあるので、両方試すことをおすすめします。

## その1. ローカルで検索

1つ目はローカルに存在するコードを検索する方法です。Ruby はスクリプト言語なので、利用している gem の Ruby で書かれたコードの検索が簡単に行なえます。

例えば [ripgrep (rg)](https://github.com/BurntSushi/ripgrep) コマンドや [RubyMine](https://www.jetbrains.com/ruby/) の検索機能などでローカルに存在する該当の非推奨コードを検索します。

次は RubyMine の「ファイル内検索」でキーワードに `File.exists?` を指定して検索した結果の例です。RubyMine では検索のために内部的にインデックスを作っていて、すぐに検索結果が表示されます。

![RubyMineでの検索結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36122/aa3a5264-758d-cd5c-6a53-a3f52386a417.png)

検索のコツとして、検索条件に「ファイル末尾が `.rb`」「大文字小文字を区別」「コメントは除外」を加えると精度が上がります。

![RubyMineでの検索のコツ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36122/4e01d52c-0e0f-1320-d3b0-03b00b333e4f.png)

この方法のメリットは、検索条件を正しく入力すれば**検索漏れがほとんどない**点です。

デメリットは、**対象のソースコードが手元に存在する必要がある**点です。普段関わっているプロジェクトなど、すでに手元にある場合でもあくまでそれらに限定されるため、めぼしいリポジトリを能動的に clone してくる必要があります。

clone 対象のリポジトリを見つけるには、例えば [BestGems](https://bestgems.org/) が参考になります。こちらでは、gems がダウンロード数や人気順でランキング形式になって表示されるので、ここから確認することが出来ます。

また、必須ではないですがソースコードのディレクトリもまとまって存在していると検索しやすいです。

自分は git リポジトリの clone に [ghq](https://github.com/x-motemen/ghq) を使っていて、clone 先は `~/go/src/github.com/` にしているので、対象にはこちらのディレクトリ以下を指定しました。もし、プロジェクトのディレクトリがバラバラな場所にあると、それぞれを対象に指定する工夫が必要になります。

## その2. GitHub code search で検索

2つ目は [GitHub code search](https://github.com/features/code-search) で検索する方法です。

次は検索ボックスで `/File\.exists\?/ language:Ruby` を指定して検索した結果です。

![GitHub code searchでの検索結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36122/e3dc9327-7f81-bdc7-18c7-2f28a32bb08e.png)

この方法のメリットは、**手元にないリポジトリも横断して検索できる**点で、先程のローカルでの検索のデメリットを補うものです。コードが GitHub に登録・修正されると自動的に登録されます。

デメリットは次の3点です。ただし、いずれのケースも対象のコードやリポジトリによっては該当しない事があります。

1. **すべて表示されない**
   - 先程の検索結果には「About 103k files」とあるが、1ページあたり20件で、5ページ目までしか確認出来ない
   - fork されたリポジトリが含まれるので、これを除外した上で50ページぐらいは閲覧したい
   - 対象が GitHub に存在して、かつ、自身のアカウントで閲覧できるものに限定される
1. **結果が古い**
   - ヒットしても該当のリポジトリでメインブランチを確認するとすでに対応されている事がある
   - コードが変更されてから検索結果に反映されるまでラグがある
1. **大文字小文字の区別が出来ない**
   - 正規表現に対応しているはずだが、区別できていないケースがある

# 対応

修正対象のリポジトリが分かったので、その対応を行います。

1. アクティブなリポジトリであるかの確認
   - アーカイブされていればすでにメンテナンスされていないので対応できない
      - もしどうしても必要であれば fork して対応する
   - アーカイブされていなくても、長期間活動のないリポジトリは PR のマージが期待できない
      - 感覚的には、3年以上コミットが無いとかなり厳しい
1. PR の確認
   - すでに同じ目的の PR がないか確認する
      - もしあれば重複を避けるため、基本的にそちらで対応するのが良いはず
         - 対象のコードに関する対応は、基本的にはここで終わる
      - 修正方法に疑問や改善点があれば、既存の PR にその旨をコメントして反応を確認する
1. 環境の準備
   - リポジトリを clone する
   - 対象バージョンの Ruby をインストールする
      - ここは一度だけ実施すればよい
   - `bundle install` を実行して gem をインストールする
1. 自動テストの実行
   - テストファーストで、最初に自動テストを実行する
      - ここではテストが失敗する事を期待する
   - テストの実行方法は Rakefile や `.github/workflows/*`、`.circleci/config.yml` などを確認する
   - もし、修正箇所をカバーする自動テストが無かった場合は、テストを作成して失敗することを確認する
1. 修正
   - 自動テストが失敗する部分のコードを修正する
1. 動作確認
   - 修正箇所について、動作に問題がないか最終的に確認する
1. PR 作成
   - CI が失敗したり、レビューで修正依頼があれば対応する

次は [active_decorator](https://github.com/amatsuda/active_decorator) に送った PR の例です。CI で一部のテストが失敗しているのですが、今回の修正とは関係なさそう、という事でマージしてもらえました。

- [Replace \`File\.exists?\` with \`File\.exist?\` by kyoshidajp · Pull Request \#117 · amatsuda/active\_decorator](https://github.com/amatsuda/active_decorator/pull/117)

# 応用

冒頭で紹介した通り、この対応は Ruby gems だけでなく、他の言語やパッケージなどに応用する事が出来ます。

次は [faraday](https://github.com/lostisland/faraday) の GitHub Actions で使われていた [actions/setup-ruby](https://github.com/actions/setup-ruby) を [ruby/setup-ruby](https://github.com/ruby/setup-ruby) へ移行する PR です。

- [Replace actions/setup\-ruby with ruby/setup\-ruby by kyoshidajp · Pull Request \#1466 · lostisland/faraday](https://github.com/lostisland/faraday/pull/1466)

actions/setup-ruby の README.md の冒頭には次のように移行を促すメッセージがあり、すでにリポジトリは archive されています。

> Please note: This action is deprecated and should no longer be used. The team at GitHub has ceased making and accepting code contributions or maintaining issues tracker. Please, migrate your workflows to the ruby/setup-ruby, which is being actively maintained by the official Ruby organization.

「非推奨・廃止コード」であり、「移行先が明確に推奨されている」のです。

# 追記（2023/01/07）

同僚がこのエントリに興味を持ってくれて、さっそくコントリビュートしていました。

https://github.com/googleapis/google-api-ruby-client/pull/13161

[google-api-ruby-client](https://github.com/googleapis/google-api-ruby-client) gem は Google API にアクセスする Google 公式の Ruby クライアントです。この gem を利用しているプロジェクトやユーザも多いので、エントリを書いて良かったと思います。