⚠️

型の自由度とインターフェースの複雑さのトレードオフ問題について

2024/10/25に公開

なるべく型安全にしたいという考えは、静的型付け言語を扱う人なら誰もが持っているはずです。しかし、自由度の高い型定義を行うことで、コードが複雑化し、利便性が低下することもあります。

例えば、Entityというインターフェースの定義についてです。EntityはIDを持つクラスです。一番簡単な実装例は、IDの型をStringに固定する定義です。

abstract interface class Entity {
  String get id;
}

この利点は、Entityがシンプルなことですが、IDはString型に固定されてしまいます。場合によっては、int型やその他の型でIDを定義したいこともあるかもしれません。

その場合、ジェネリクスを利用することができます。

abstract interface class Entity<TId> {
  TId get id;
}

こうすることで型の自由度が高まり、IDの型は柔軟に指定できるようになりました。一見すると、ジェネリクスを使った型定義の方が有利に見えます。しかし、ジェネリクスを使うことによるデメリットも存在します。

例えば、Entityに対する汎用的なRepositoryを実装する場合、次のようなコードになります。

abstract interface class IRepository<TId, TEntity extends Entity<TId>> {
  Future<TEntity> findById(TId id);
  Future<void> save(TEntity entity);
}

findByIdはIDを引数に取り、Entityを返します。この厳密な型定義を実現するために、IRepositoryクラスはTIdTEntityのジェネリクスを要求することになります。これは、抽象的なEntityクラスを扱うプログラミングを行う際に、頻繁に起こる問題です。

IDの型の自由度を得るために、インターフェースの複雑さを増すべきでしょうか?それとも、IDをString型に固定し、インターフェースをシンプルに保つべきでしょうか?

これが、型の自由度とインターフェースの複雑さのトレードオフ問題です。

Discussion