エンジニア以外でも簡単にゲームのマスターのバリデーションを作れるツールを作った
概要
ゲーム開発で使うマスターデータのバリデーターの汎用的な仕組みを作りました。
コマンドをパイプのように繋げて書くことでバリデータを誰でも作ることができます。
コマンドはpythonスクリプトで実装することができるので新しいコマンドを作れます。
動機
過去に担当したスマホゲーム案件でマスターデータのバリデータがない案件に遭遇したことがありました。
当然デバッグはしますがそれでもすり抜けて単純なマスターデータ設定ミスによるマスターデータ起因の本番障害になることが度々発生していました。
何度かバリデータの導入を提案しましたがバリデータを作る分実装に時間がかかるなどコストを理由にうやむやになってしまいました。
確かに過去にバリデータを作るのが当たり前の案件では作るのが辛いと感じる経験もしてきましたが、この理由は障害対応コストやプレイヤーからの信用が無くることに比べれば安いものなのではないかと思っています。
バグの修正コストはリリースまでの工程の中で発見が早ければ早いほど小さくなるはずです。
マスターのミスを発見できる一番早いタイミングはマスター入力時です。入力時のタイミングでバリデーターを実行すれば低コストでミスを減らせられるのではないかと思います。
デバッガーに次回リリース分のマスターのデバッグをしてもらう前にバグを見つけられる方がデバッガーにかかる負担も少しは減るでしょう。
このツールの目的
- 案件毎に個別のバリデータシステムを作りたくない
- バリデーションの拡張性
- プランナーでもバリデータを書けること
- 運用していく中でプランナーしか知らないルールができる事もある。そのルールに合ったバリデーションをプランナー自身が作成できるようにするため。
- 学習コストはなるべく低く
- 難しいものは使われない
前提
このツールはcsvファイルを読み込んで処理します。
なのでマスターデータをcsvで管理しているかcsvで出力できることが前提になります。
pythonのバージョンは3.8.2で作っています。
このツールに登場する概念
filterコマンド
バリデーション対象のマスターデータを絞り込むためのコマンドです。
検証処理はマスター内の全ての行に対して実行するとは限らないのでfilterコマンドが存在しています。
マスターによって同じ種類の値でもカラム名が違う(start_dataとopen_dataなど)場合があります。どのマスターでも使えるようにするため、カラム名を引数で渡すように実装します。
validatonコマンド
filterコマンドから渡されるマスターデータを実際に検証するコマンドです。
検証処理の後にエラーになった行やエラーメッセージを出力するためにこれらの情報を戻り値として返します。
filterコマンドと同様にカラム名を引数で渡すように実装します。
バリデータ
上記の2種類のコマンドを組み合わせて作ると以下のようになります。
equal_filter(max, 100) > count_validation(3)
上記のようなバリデータを指定のフォルダにtxtファイルで保存することで main.py
実行時にバリデーションされるようになります。
一つのファイルに複数のバリデータを設定する場合は改行で区切ります。
想定している運用方法
filterコマンドやvalidationコマンド自体はエンジニアが実装します。基本的に個別のマスター用ではなくて汎用的に使えるようなものを実装します。
エンジニアが新しいマスターを実装するタイミングでvalidatorフォルダ内に各マスターのバリデータを作ります。
その後は必要に応じてプランナーなどが追記します。
実行方法
-
master_data
フォルダにマスターデータのcsvファイルを入れる -
master_validator/validator
フォルダにマスターに対応するバリデータを書いたテキストファイルを保存する cd master_validator
-
main.py
を実行する
バリデーションエラーがあれば以下のようなメッセージが出力されます。
マスター名、バリデーション名、エラーメッセージ、バリデーションでエラーになったマスターの行が表示されています。
INFO:root:master=<item_sample> validation=<count_validation> error_message=<3件以上のレコードがありません。2件> error_master_data=<[]>
INFO:root:master=<character_sample> validation=<time_0sec_validation> error_message=<start_dataの秒が0秒になっていません。> error_master_data=<[MasterRow(row={'id': '1', 'name': 'キャラ1', 'attack': '1', 'defence': '11', 'start_data': '2022-05-01 13:59:59'}), MasterRow(row={'id': '6', 'name': 'キャラ6', 'attack': '6', 'defence': '10', 'start_data': '2022-05-05 14:59:59'})]>
バリデータ例
equal_filter(max, 100) > count_validation(3)
equal_filter()
は第1引数にカラム名、第2引数にmaxカラムの値と比較する値を指定してしています。maxカラムの値が100のレコードのみを残します。>
で残ったマスターデータを次のコマンドに渡します。count_validation(3)
はレコード数が3件以上あるかどうか検証します。
対応フォーマット
現在はid
カラムが存在するCSVにのみ対応しています。
CSVを使うことにしたのはpythonだけで実行できるからと、よくスマホゲーム開発で使われているフォーマットだからです。
CSVから読み込むことでDBなどの他の環境を作らなくてもよくなります。
バリデータ詳細
filter、validationコマンドの作り方
詳しくはreadmeファイルに記載しています
構文
例
hoge_filter() > fuga_filter(arg1) > hoge_validation(arg1, arg2)
hoge_filter()は引数なしのfilterコマンドを実行します。次のfuga_filter(arg1)は引数を一つだけ渡すfilterコマンドです。最後は引数が2つのvalidationコマンドを実行しています。
>
でコマンドを次々に繋げていくと各コマンドで処理されたデータが次のコマンドに渡っていきます。
EBNF
Validator ::= (Filter ">")+ Validation
Filter ::= (Character | Number)+ "filter" Args
Args ::= "(" ArgValue? | (ArgValue ("," ArgValue)*) ")"
ArgValue ::= (Character | Number)+
Character ::= a | ... | z | A | ... | Z
Number ::= 0 | ... | 9
Validation ::= (Character | Number)+ "validation" Args
バリデータの構文の実装について
interpreterパターンを使うことで構文を表現しています。最初はシンプルな構文だと思ってif文で書いてましたが次第に理解するのが難しくなっていき辛くなったので止めました。
今回は構文解析処理を書いてみたかったので自前で実装しましたが開発を進めて機能が増えるとライブラリを使うかもしれないです。
今後の開発
まだ基本的なコマンドすら揃っていないので今後作っていきます。
運用するタイプのスマホゲームのマスター入力はgoogleスプレッドシートを使うことが多いです。そのため、スプレッドシートに入力したらすぐにバリデータを実行できるようなプログラムを考えています。
同一マスターを複数人が同時に編集してこのツールでバリデータを実行した場合、他の人の作業途中のデータが混ざってしまって自分のデータのバリデーションの邪魔になることが想定されます。この問題を解決できるようにリリース日などのメタデータを付加して影響範囲を区切れる仕組みの導入を考えています。
現状はバリデータは簡単に書けるがその新しく作ったバリデータの登録(バリデータファイルの配置)はエンジニアがやらなければなりません。登録もプランナー自身でできるようにしたいです。これはjenkinsなどを使っていれば簡単にできるので優先度は低いです。
Discussion