dto に対するリフレクションの是非
1時に寝て7時に起きた。
ドラクエタクトのリアルタイム対戦⌗
もう2年間もずっとゲームし続けている。最近 リアルタイム対戦 モードがリリースされた。全然やる気なかったんやけど、リアルタイム対戦で得たコインでもらえるアイテムが魅力的なのでやることにした。そして、実際にアルタイム対戦をやってみるとはまる。運営の手の平でゲームさせられている。
- 人間が相手で狡猾な戦略で負けると悔しい
- 人間が相手の戦略の方が創意工夫があって学びになる
- 実際にやり始めるとおもしろくなってきてずっとやってしまう
できる時間を制限しているというのもうまいやり方だなと思っている。朝・昼・晩の7-9時、12-14時、19-22時に制限している。そこまでしてリアルタイムに人間同士をマッチングしてゲームさせる必要があるのか?という素朴な疑問に私は辿りつくが、おそらくゲーム開発者からみたらそうじゃない大事なユーザー体験があるのだろうと推測する。あと不思議なことが1つ。マッチングしていると、たまにわざと負けてくれる人がマッチングされる。チームのメンバーが1人で向こうが先行なら戦いを辞退 (こちらの勝ちになる) するし、こちらが先行でもすぐにやっつけられる。ちゃんと統計をとってないけど、20回に1回ぐらいの頻度でわざと負けてくれる人とマッチングする。あの人たちは一体どういう理由でわざと負けているんだろう?
リフレクションのユーティリティを作った⌗
いまお手伝いで開発している api サーバーは外界と内部のデータの境界を明確にわけていて、外向けのオブジェクト定義と内部向けのオブジェクト定義が異なる。ほとんど同じデータであっても dto を介して値を受け渡ししないといけない。そうすると、次のような dto と他のオブジェクトとの値渡しのための処理が型ごとにあちこちに実装されている。
private MyRecord toMyRecord(MyDataInput in) {
var record = new MyRecord();
record.id = in.id
record.name = in.name;
record.someId1 = in.someId1;
record.someId2 = in.someId2;
record.someId3 = in.someId3;
record.sortOrder = in.sortOrder;
record.createUser = in.createUser;
record.updateUser = in.updateUser;
...
}
メンバー数が20-30ぐらいあると、たまに値のセット忘れがあったり、あとから追加したメンバーの保守ができてないとか、たまにトラブルが起きる。これ自体は間違っているわけじゃなくて境界を明確にわけるメリットもあるのでプログラミングの煩雑さとトレードオフと言える。
最近、私が管理系の web api のエンドポイントを作る機会が多いせいか、dto と外部向けのオブジェクトを明確にわける必要のない要件もあったりする。試しにリフレクションを使って同名のフィールド間の値の受け渡しは自動でやってみたらどんな感じかな?と思って作ってみた。
public class ReflectionUtil {
private ReflectionUtil() {
throw new AssertionError("ReflectionUtil is a utility class");
}
private static <T> Field getField(Class<T> klass, String fieldName) {
try {
return klass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
return null;
}
}
public static <T1, T2> T2 mapFieldValues(T1 fromInstance, T2 toInstance) {
var fromClass = fromInstance.getClass();
for (var toField : toInstance.getClass().getDeclaredFields()) {
toField.setAccessible(true);
var fromField = getField(fromClass, toField.getName());
if (fromField != null) {
fromField.setAccessible(true);
try {
toField.set(toInstance, fromField.get(fromInstance));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
return toInstance;
}
}
これを使うと、先のコードがこれだけで済む。煩わしい値の受け渡しだけのコードを削減できる。
var record = ReflectionUtil.mapFieldValues(in, new MyRecord());
こんなことやると、セキュリティ的によくないとか反論されるかな?と思いながら pr を出してみたら思いの外、好評だったのでちょっと使ってみようと思う。