この記事は一休.comアドベントカレンダー2017の17日目です。 残すところ一週間とすこしですね
一休.com スパ を運用・開発しているid:kichion0526です。
テクニカルな話や一休の苦労話etcは諸先輩方がたくさん書いてくれているので
最近、何を意識して実装しているかを書き残したいと…
ギークなテッキーになりたかった故に小難しいことをしていた
前職からC#で書くことも多く、リフレクションなどを使い倒しメタプログラミングがすらすらできるようになるのが当時の目標でした
一例として
~ゲームにおける様々なアイテムを生成するファクトリ~
public abstract class ItemBase { protected ItemBase(int id) { Id = id } /// アイテムカテゴリを取得します。 public abstract ItemCategory Category { get; } /// アイテムIDを取得します。 public int Id { get; } /// アイテム名を取得します。 public abstract string Name { get; } } public enum ItemCategory { Weapon = 0, // みんな大好き武器 Armor = 1, // みんな大好き防具 GachaTicket = 2, // みんな大好(ry ...(などなどいっぱい)... } public class Weapon : ItemBase { public override ItemCategory Category => ItemCategory.Weapon; ....(コンストラクタ等でid指定etc)... } public class Armor : ItemBase { public override ItemCategory Category => ItemCategory.Armor ; ....(コンストラクタ等でid指定etc)... } public class GachaTicket : ItemBase { public override ItemCategory Category => ItemCategory.GachaTicket; ....(コンストラクタ等でid指定etc)... }
アイテムクラスを定義したら端的にswitch文でファクトリ作ると下記のイメージ
public static class ItemFactory { public static T Create<T>(int id, ItemCategory category) where T : ItemBase { switch (category) { case ItemCategory.Weapon: return new Weapon(id); case ItemCategory.Armor: return new Armor(id); case ItemCategory.GachaTicket: return new GachaTicket(id); ...(その他もろもろ)... } } }
「新しいアイテムが追加されるたびにここをいじるのはめんどくさいなぁ」
と思うとリフレクションの出番です
public static class ItemFactory { private static readonly Dictionary<ItemCategory, Func<int, ItemBase>> Items; static ItemFactory() { var constructors = typeof (ItemBase).Assembly.GetTypes() .Where(x => x.IsSubclassOf(typeof (ItemBase))) .Where(x => !x.IsAbstract) .Select(x => { // コンストラクタの引数の型 var argumentType = typeof (int); // コンストラクタ var constructor = x.GetConstructor( BindingFlags.Instance | BindingFlags.Public, null, CallingConventions.HasThis, new[] {argumentType}, new ParameterModifier[0] ); if (constructor == null) return null; // コンストラクタの引数 var id = Expression.Parameter(argumentType, "id"); // コンストラクタをデリゲート化 return Expression.Lambda<Func<int, ItemBase>>( Expression.New(constructor, id), id ).Compile(); }) .Where(x => x != null) .ToArray(); Items = constructors.ToDictionary(x => x(0).Category); // Keyを生成するためなので引数は何でもいい } public static ItemBase Create(ItemCategory category, int id) { return Items[category](id); } }
これでItemBase
を継承するクラスのコンストラクタを辞書化しておけるのでCreate
メソッドがこんなシンプルに!
しかも、辞書はstatic変数で保持しているのでアイテム生成のコストも気にしなくていい!
(こんな書き方できるのかっこいい!)
何が得られたの?
メリット
- 新しいカテゴリのアイテムが追加されるたびにファクトリを修正しなくて良くなった
- 生成メソッドの行数の少なさ
- (かっこいいという満足感)
デメリット
- switch文の実装より初期実装に時間がかかる
- リフレクション知っているとしても初見は黒魔術
Dictionary
なので2つ以上のクラスで同じカテゴリを使うと一律エラーで死ぬ- アイテム種類数が少ないと単純に行数が増えてる
- 後任の人が困る材料になりかねない
もともとは
「新しいアイテムが追加されるたびにここをいじるのはめんどくさいなぁ」
というモチベーションから始まった改修でした
今でこそ振り返ると複雑な概念を導入した割には解決したことが薄すぎると思っています
こうなるとswitch文を書き換えないという選択肢の方が良さそうです
結局何が言いたいの?
ホントの目的がないとテクニカルなコーディングはただの自己満足になると感じています
私自身、本当にテクニカルな手法が必要なのかを意識するきっかけになったのは一休に転職するようになってからです
極端な例では「この改修でどれくらい儲かるか」「今のチームの人が全員入れ替わったら扱えるか」なんてことを考えたりします
一休はエンジニアでもビジネス視点が求められているのでより「事業目標」に向かってコーディングできていると思います
より目的を意識してプログラミングと付き合うと
どうコーディングしたらいいか?の問いに答えが出しやすいのかなと実感してます
まとめ
目的を見失ったプログラミングは自己満足になりがち という話でした
この考えに至ったのも一休にいる優秀な方々のおかげだと思っています これからも社内のエンジニアの方から学びを得て全能感を高めていけたらと…
つらつらと書きましたが先人たちがいい言葉を残してくれていますので、それで締めたいと思います
プログラマが学ぶべき最も大切な技能というのは、コードを書かないときを知ることなのかもしれない。
最も読みやすいコードは、何も書かれていないコードだ。
(「リーダブルコード」和訳版 第13章「短いコードを書く」P.168 より)
明日は sagisakat さんの
「アプリのローンチと2度のメジャーアップデート、何を考えてデザインしたか」 です