esm アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

RuboCop オリジナル Cop 活用のオススメ

こんにちは、夜な夜なフォーチュンタワーに登っている nsgc です。

複数人でプロダクトやサービスを作る際に可読性や保守性を向上させるため、 コーディング規約を用意してフォーマットを統一したり、あきらかに不要な記述を静的解析ツールでチェックすることはよくあるかと思います。

Ruby でプログラミングしている場合、そういった Formatter/Linter としては RuboCop が有名ですが、 RuboCop 標準で用意されているルールだけではもの足りず、プロジェクトのコンテキストに特化した独自ルールが欲しい時がありませんか?

そんな時には、オリジナルの Cop の作成をオススメしたい!

tapp をチェックする Cop を作ってみよう

突然ですが tapp というツールをご存知でしょうか? tapp はメソッドチェーンの途中でもオブジェクトを表示できる優れものなのですが、プロダクトコードには入れたくありません。

コードレビューの時に「デバッグ用途の tapp が残ってますよ」 という指摘をしますが、本来はその前に CI で検知してほしいものです。そこで、今回 "tapp が呼ばれているか検知する" 独自 Cop を用意してみましょう。

まずは、Cop クラスの作成です。 RuboCop::Cop::Base を継承したクラスにコールバックを定義し、その中で問題になる条件がないかチェックし、最終的には警告メッセージを出すためのメソッドを呼びだします。

下記例では、メソッド呼び出し時に呼ばれる on_send コールバックを定義し、警告表示のための add_offense を呼んでいます。 RESTRICT_ON_SEND 定数に指定することで on_send で呼ばれる対象を tapp のみに限定し、add_offense で表示するメッセージを MSG 定数で設定しています。

# frozen_string_literal: true

module RuboCop
  module Cop
    module InternalAffairs
      #
      # # bad (ok during development)
      # # using tapp
      # def some_method
      #  do_something.tapp
      # end
      #
      # # good
      # def some_method
      #  do_something
      # end
      #
      class Tapp < Base
        RESTRICT_ON_SEND = %i(tapp).freeze
        MSG = 'Remove debugger'.freeze

        def on_send(node)
          add_offense(node)
        end
      end
    end
  end
end

Cop クラスが用意できたら、次にそのクラスを .rubocop.yml で読み込み、他の Cop 同様に有効にすると使えます。

require:
  - ./lib/rubocop/cop/internal_affairs/tapp

InternalAffairs/Tapp:
  Enabled: true

ここまで読んでどうでしょう?単純なメソッドコールの有り無し位ならとても簡単に作れそうですね。

もう少しリッチな構文チェックをしたい場合やオートコレクトを用意したい場合は公式で用意されている記事 が丁寧に書かれていますし、既存の Cop と近い Cop を作りたい場合は既にある実装 がリファレンスとして参考になります。

さいごに

私が参加しているプロジェクトでは、 特定のロールの利用者が操作時の証跡をデータベース上に残す必要があり、 controller でその証跡レコーディング用のメソッドが呼ばれているかを Cop でチェックしています。

また、Time::DATE_FORMATS で独自のフォーマットを用意しているのですが、それを用いないで書かれた場合にアラートをあげる Cop もいます。

チームのコードレビューで機械的に検出できるものを何度も指摘をしているなら、適応する Cop がないか探してみたり、なければ、自分たちで Cop を用意してチェックを任せ、人間にしかできないレビューに注力していきましょう!