面倒な定形コードは自動生成!バッチスクリプト用ジェネレータを作ってみた

キッズリーの開発を担当している倉成です。

先日参加したRubykaigiの『Scaling Teams using Tests for Productivity and Education』というセッションの中でチームのルールは文書よりも動くコードで周知するという内容に強く感銘を受けました。参加レポートは以下で公開しています。
チームのルールは自動テストで周知しよう -- Rubykaigi参加レポート

今回はセッションの内容を体現すべく、バッチスクリプトを書く際の考慮すべき事項をチーム内で共有するためのジェネレータを作ってみたのでその紹介をします。

バッチスクリプトを開発する際には実行時間を出力する、Dry Runを行うオプションを提供するなど考慮事項が多くあります。これらの考慮事項を文書化しそのルールに従ってもらうのも一つの手ではありますが、バッチスクリプトを開発する頻度が少ないことや、緊急対応的にスクリプトを書かなければならないことも多いため、必ずしもドキュメントの考慮事項に沿った実装がされるとは限りません。
今回は文章よりも動くコードをという方針のもとバッチスクリプト開発のベストプラクティスに自然に従った形でコードが生成するGemを作成してみました。

データメンテナンス用のスクリプトの課題

サービスを運用していく中で利用者がアプリケーションから直接操作をできないデータを変更する必要が生じた際、エンジニアがバッチスクリプトを書いて修正するDBに接続しクエリを流すなどの方法でデータメンテナンスを行うことはよく発生すると思います。

データメンテナンスではデータベースの値を修正するような操作を行うため、慎重を期して実装・実行されるべきものではあります。
しかし実際には本来書かれるべきであるユニットテストが書かれていない、実行時間の概算が予めされていないなど、必ずしも慎重を期しているとは言えない方法でメンテナンスが実施されてしまうケースを目にします。1)直接DBに接続して作業した場合には、作業内容は作業者にしか分からないという状況になってしまうこともあります
またバッチスクリプトには、実行時間を出力する、Dry Run可能にする、実行記録を保存するなど、オンライン処理とは異なる考慮する事項もあります。

これらの問題に対して、データメンテナンスのボイラープレートのコードを作成し開発者にバッチスクリプト作成時の考慮事項を意識してもらうためのRailsジェネレータを作ってみました。

今回作成したジェネレータはGem化しており下記から確認できます。
https://github.com/kuranari/data_maintenance

使い方

Gemfileに

gem 'data_maintenance'

を追加すればインストールは完了です。

この状態で

rails g data_maintenance [NAME]

コマンドを実行することで、db/maintenances以下にスクリプトの雛形が作成されます。

例えばset_user_statusという名前で生成すると下記のような出力となります。

$ bin/rails g data_maintenance set_user_status
      create  db/maintenances/set_user_status.rb
      invoke  test_unit
      create    test/db/maintenances/set_user_status_test.rb

スクリプトの本体であるdb/maintenances/set_user_status.rbの中身は

class SetUserStatusMaintenance
  def self.execute
    # do something
  end
end
realtime = Benchmark.realtime do
  SetUserStatusMaintenance.execute
end
puts "realtime: #{realtime}"

となっており、実行時間のベンチマークが出力されるようなテンプレートとなっています。

またテストコードであるtest/db/maintenances/set_user_status_test.rb

require 'test_helper'
require './db/maintenances/set_user_status'
class SetUserStatusMaintenanceTest < ActiveSupport::TestCase
  test "execute" do
    flunk "add some examples to (or delete) #{__FILE__}"
  end
end

と、何もしない状態ではFailする状態になっており、必ずテストケースの編集をする必要があります。

このようにジェネレータを使うことで

  • (ベンチマーク集計のコードを消さない限り)必ず実行時間を出力できる
  • テストコードの追加忘れを防止できる

というメリットがあります。

まとめ

データメンテナンス用のジェネレータを作成することで、チームのメンバーにメンテナンススクリプトの設計指針を示すGemの紹介をしました。
このジェネレータを使うことで、レビュー時にテストを書いてください実行時間の出力をしてくださいのようなコメントをする機会を減らすことができるでしょう。
またボイラープレートは生成しつつも、テストが不要であればテストファイル自体を削除してしまうなど、柔軟に実装が行えるのもジェネレータを使うことのメリットの一つです。

今回は動作説明のためにGemを作成しましたが、各リポジトリのlib/generators配下などにジェネレータ用コードを置いておくことで同様のことができます。(詳細はRailsGuidesのRails ジェネレータとテンプレート入門を一通り読んでみると理解できるでしょう。)

今回紹介したGemを参考にしてもらい、Dry-runを必須にしたり、コマンドライン引数のルールを統一したり、チーム独自のテンプレートを用意してみてはいかがでしょうか。

脚注

脚注
1 直接DBに接続して作業した場合には、作業内容は作業者にしか分からないという状況になってしまうこともあります