「位置引数とキーワード引数の分離」の要修正箇所収集するマン

## 0. はじめに

前置きとなる記事は以下 :rolling_eyes: 

##### [Ruby 3.0 に含まれる非互換「位置引数とキーワード引数の分離」の乗り越え方](https://qiita.com/m-kubo/private/0cff8efb04390eb32e75)

## 1. 「位置引数とキーワード引数の分離」の要修正箇所を収集するコード

さっそくですが、以下のようなコードを Rails に追加します。

```ruby:config/initializers/separation_of_positional_and_keyword_arguments.rb
# 「位置引数とキーワード引数の分離」の対応を進めるための補助モジュール
# Warning.warn をオーバーライドし、warning を出力する条件、出力先を制御します。
#   参考：https://docs.ruby-lang.org/ja/2.7.0/class/Warning.html
#        https://docs.ruby-lang.org/en/2.7.0/Warning.html
#   参考：https://www.ruby-lang.org/ja/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
#        https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/

$VERBOSE = false # 重要な警告のみ出力
Warning[:deprecated] = true # 非推奨な機能を使用した際に警告を出力する

module Warning
  module SeparationOfPositionalAndKeywordArgument
    # Gem 内のコードのため、Gem をバージョンアップさせて解消予定のもの
    GEM_USING = %w[
    ]
    GEM_PASSING = %w[
    ]
    GEM_SPLITTING = %w[
    ]

    # 直接コード修正をして解消予定のもの
    TODO_USING = %w[
    ]
    TODO_PASSING = %w[
    ]
    TODO_SPLITTING = %w[
    ]

    def using?
      include?('Using the last argument as keyword parameters is deprecated')
    end

    def passing?
      include?('Passing the keyword argument as the last hash parameter is deprecated')
    end

    def splitting?
      include?('Splitting the last argument into positional and keyword parameters is deprecated')
    end

    def using_listed?
      (GEM_USING | TODO_USING).any? { |item| include?(item) }
    end

    def passing_listed?
      (GEM_PASSING | TODO_PASSING).any? { |item| include?(item) }
    end

    def splitting_listed?
      (GEM_SPLITTING | TODO_SPLITTING).any? { |item| include?(item) }
    end

    def should_output?
      (using? && !using_listed?) || (passing? && !passing_listed?) || (splitting? && !splitting_listed?)
    end

    def output_for_test
      File.open(Rails.root.join('log/separation_of_positional_and_keyword_arguments.log'), 'a') do |file|
        file.write(self)
      end
    end
  end

  def self.warn(message)
    return unless %w[development test].include?(Rails.env) # 左記以外の環境では return
    return unless message.extend(SeparationOfPositionalAndKeywordArgument).should_output? # 出力条件に合致しなければ return

    # 環境別の出力メソッドが用意されていればそちらへ、無ければデフォルトの $stderr に出力
    message.respond_to?("output_for_#{Rails.env.downcase}") ? message.__send__("output_for_#{Rails.env.downcase}") : super
  end
end
```

## 2. 使い方

### 1. 全テストを動かしてみよう

コードを配置したら、全テストを動かしてみましょう。`$ bundle exec rspec` とか好きにやってください :rolling_eyes: `RAILS_ENV=test` でお願いしますね

### 2. 全テスト実行時に通ったコード中に潜む要修正箇所の確認

全テストの実行が終わると、`separation_of_positional_and_keyword_arguments.log` というファイルが出力されているはずです。このファイルには、テスト実行時に通るコードで出力される、「位置引数とキーワード引数の分離」に対する全ての warning が出力された状態になっています。サンプルとしては、以下のような感じです。

```text:log/separation_of_positional_and_keyword_arguments.log
/Users/m-kubo/.anyenv/envs/rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/active_hash-3.1.0/lib/associations/associations.rb:22: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/m-kubo/.anyenv/envs/rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/capybara-3.36.0/lib/capybara/session.rb:772: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/m-kubo/repository/project/app/controllers/application_controller.rb:33: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/m-kubo/repository/project/app/controllers/api/v1/users_controller.rb:31: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/m-kubo/repository/project/spec/models/user_spec.rb:4: warning: Passing the keyword argument as the last hash parameter is deprecated
/Users/m-kubo/repository/project/app/helpers/customer_helper.rb:12: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
```

もし、あなたのプロジェクトのテストカバレッジが `100%` なら、これだけで全ての要修正箇所を収集できたことになるはずです。もちろん、あなたのプロジェクトはテストカバレッジ `100%` ですよね？ :rolling_eyes: 

### 3. 無視リストの設定

さて、`separation_of_positional_and_keyword_arguments.log` の中身ですが、重複もたくさんあると思います。それらを除去して、ソートして、いい感じのリストを作ってください。ファイルパスも、相対パスに加工してしまいましょう。

要修正箇所を示すファイルパスのうち、Gem 内のファイルパスを示しているものがあると思います。これらは、`separation_of_positional_and_keyword_arguments.rb` 内の定数の、`GEM_USING` `GEM_PASSING` `GEM_SPLITTING` のいずれかに追加してください。どの定数に追加すべきかは、warning メッセージの最初の単語で区別します。設定例は、例えば以下のような感じになります。

```ruby
GEM_USING = %w[
  gems/active_hash-3.1.0/lib/associations/associations.rb:22
  gems/capybara-3.36.0/lib/capybara/session.rb:772
]
```

残ったファイルパスのリストは、同様にメッセージの最初の単語で区別して、`TODO_USING` `TODO_PASSING` `TODO_SPLITTING` にそれぞれ追加してしまいましょう。設定例は、以下のような感じです。

```ruby
TODO_USING = %w[
  app/controllers/application_controller.rb:33
  app/controllers/api/v1/users_controller.rb:31
  app/helpers/customer_helper.rb:12
]
TODO_PASSING = %w[
  spec/models/user_spec.rb:4
]
```

これらの定数（無視リスト）を設定した状態で、再びテストを実行すると、今度は `separation_of_positional_and_keyword_arguments.log` が出力されなくなるはずです。そうしたら、この状態で `separation_of_positional_and_keyword_arguments.rb` をプロジェクトにマージしてしまいましょう。

### 4. CI ツールで要修正箇所の混入を監視できる？

私のプロジェクトでは、CI ツールでテストの自動実行を行っていたので、テスト実行の後に `separation_of_positional_and_keyword_arguments.log` が出力されていないか、チェックする CI 設定を追加しました。これにより、新たな要修正箇所が混入してくるのを防げるようになりました。

CI ツールを使っていないプロジェクトでは、定期的に手動で全テストを実行し、新しい要修正箇所が増えていないか、監視していく運用が必要そうです 😣 

### 5. 要修正箇所を修正していく

`GEM_USING` `GEM_PASSING` `GEM_SPLITTING` に含まれている要修正箇所であれば、該当 Gem を適切にバージョンアップすることによって、`TODO_USING` `TODO_PASSING` `TODO_SPLITTING` に含まれている要修正箇所であれば、該当コードを修正することによって、無視リストの中身を減らしていくことができます。全ての無視リストが空になるまで、「位置引数とキーワード引数の分離」の対応を進めていきましょう。

### 6. 全テストでカバーできていないコードの確認はどうするの？

カバーできていないコードが少量であれば、手元でそれらのコードを通るような動作確認を行い、warning を収集すれば良いでしょう。しかし、そもそもカバレッジが低いプロジェクトの場合は、本番環境やステージング環境で実際にサービスを稼働させながら、要修正箇所を収集する必要があるでしょう :thinking: 

本番環境やステージング環境で、`separation_of_positional_and_keyword_arguments.log` が出力されるよう、`separation_of_positional_and_keyword_arguments.rb` のコードを調整し、warning の出力負荷（たぶん大丈夫だと思いますが）に気をつけながら、要修正箇所の収集と、無視リストへの追加をマネジメントしてみてください。

## 3. 最後に

実は、私のプロジェクトでは、まだ Ruby バージョンアップの対応の真っ最中なので、上記の手順で対処しきれない事態に、これから遭遇する可能性はあります :sweat_smile: その場合、この記事に加筆、修正を行う予定ですが、今のところこの手順で問題は発生していません。

この記事が誰かのお役に立てれば幸いです。今、もしくはこれから、この試練に立ち向かう必要があるみなさん、ぜひ一緒にがんばっていきましょー :smile: 

:heart: おわり :heart: