Techioz Blog

Rubocop が Metrics/AbcSize について不満を言う

概要

それで、私はここで正しい道を進み、この警察を解決する方法を理解しようとしています、これはIMOの小さなコードのように見えますが、なぜ文句を言うのでしょうか?

ネストされた if-else を移動しても何も変わりません。この問題を解決する方法について何か提案はありますか?

 class WebPush::Register

  include Interactor

  # rubocop:disable Metrics/AbcSize
  def call
    user = Learner.find_by(id: context.user_id)

    # return if existing
    if user.web_push_subscription
      context.subscription = user.web_push_subscription
    else
      subscription = WebPushSubscription.new(
        endpoint:   context.push_params[:endpoint],
        auth_key:   context.push_params[:keys][:auth],
        p256dh_key: context.push_params[:keys][:p256dh],
        learner:    user
      )

      if subscription.save
        context.subscription = subscription
      else
        context.error = subscription.errors.full_messages
        context.fail!
      end
    end
  end
  # rubocop:enable Metrics/AbcSize

end

解決策

まず、ABC がどのように計算されるかを理解する必要があります。条件文のネストは ABC には影響しません。 RuboCop の警告出力には、計算結果が表示されます。

Assignment Branch Condition size for call is too high. [<5, 28, 4> 28.72/17]

この記事で説明したように、<5, 28, 4> は <代入、分岐、条件> です。

全体のスコアは次のように計算されます: sqrt(5^2 + 28^2 + 4^2) = 28.72

警官のデフォルトの最大スコアは 17 です。

以下のコードに各行の ABC スコアの注釈を付けました。

def call
  user = Learner.find_by(id: context.user_id) # <1, 3, 0>

  if user.web_push_subscription # <0, 1, 1>
    context.subscription = user.web_push_subscription # <1, 3, 0>
  else # <0, 0, 1>
    # this constructor call is the most expensive part
    subscription = WebPushSubscription.new( # <1, 1, 0>
      endpoint:   context.push_params[:endpoint], # <0, 3, 0>
      auth_key:   context.push_params[:keys][:auth], # <0, 4, 0>
      p256dh_key: context.push_params[:keys][:p256dh], # <0, 4, 0>
      learner:    user # <0, 0, 0>
    )

    if subscription.save # <0, 1, 1>
      context.subscription = subscription # <1, 2, 0>
    else # <0, 0, 1>
      context.error = subscription.errors.full_messages # <1, 4, 0>
      context.fail! # <0, 2, 0>
    end
  end
end

コンテキストが参照されるたびに、メトリックによって B ポイントが追加されます。これは、コンテキストが呼び出し関数のローカル変数ではないため、メトリクスでは毎回新しいメソッド呼び出しであると想定されるためです。

cop オプション CountRepeatedAttributes: false (これをお勧めします) を設定すると、結合されたコンテキストへのすべての参照に対して B ポイントが 1 つだけ追加されます。これにより、ABC スコアは 19.1 に下がります。

その代わりに、次のように WebPushSubscription の作成を独自のメソッドに抽出することでスコアを下げることができます。

  def call
    user = Learner.find_by(id: context.user_id)

    if user.web_push_subscription
      context.subscription = user.web_push_subscription
    else
      create_subscription(user)
    end
  end

  private

  def create_subscription(user)
    push_params = context.push_params
    subscription = WebPushSubscription.new(
      endpoint:   push_params[:endpoint],
      auth_key:   push_params.dig(:key, :auth),
      p256dh_key: push_params.dig(:key, :p256dh),
      learner:    user
    )

    if subscription.save
      context.subscription = subscription
    else
      context.error = subscription.errors.full_messages
      context.fail!
    end
  end

これにより、スコアが 2 つの方法に分割されます。 create_subscription には、push_params を変数に割り当てたり、ネストされたハッシュ アクセサーに dig を使用したりするなど、追加の ABC 節約戦略がいくつかあることに注意してください。 create_subscription の最終スコアは、使用するcopオプションに応じて12から16の間であり、callは6から8の間です。

一般に、ABC スコアを下げるために必要なのは、より小さなメソッドにリファクタリングすることだけです。