はじめに
私の所属する会社では今リファクタリングを積極的に実施しています。
私自身も取り組んでいますが、「こういう時どうやって変えたら読みやすいんだろう」というのはよく発生する悩みです。
そんなお悩みに応えるべく、今回は「引数が多くなっちゃった時ってどうするの?」をまとめます。
参考:
【初心者向け】引数が多すぎるメソッドを改善する-パラメータオブジェクト
javaで、引数が多すぎるときの対処法
引数が多いことの問題点
例えば以下のようなコンストラクタを考えます。
public User(int userId, String name, String email, boolean isAdmin, boolean sendMail, String postCode, String address) { ... }
使う側はこうなります。
User u = new User(1, "田中", "tanaka@example.com", false, true, "000-0000", "東京都〇〇区〇〇00-00-00");
読みにくい
ぱっと見、ながーいですよね。
Effective Javaによると、引数の個数は4個以下を目指すべきだそうです。
同じ型が並ぶ場合、渡し間違える
上の例では、isAdmin=false、 sendMail=falseのようですが、この順番を勘違いして逆に渡してもコンパイルエラーにはなりません。
アプリケーションを動かしてみたら、なぜか田中さんに管理者権限がついていてる。なぜか田中さんにメールが来ない。といった不具合が起こってからはじめて、値を渡し間違えていることが発覚するわけです。ツラい。
対策
機能追加に伴い、一部のメソッドの引数の個数がどんどん増えてしまう、ということはよくあることです。
メソッドの引数を増やすとき、あるいは新たに作成しようとしたメソッドの引数が多くなりそうな時、以下のような対策を考えてみてください。
引数を減らす
まずは、引数を減らすためにできることを考えてみましょう。
メソッドを分割する
引数が多い場合、しばしば、そのメソッドの責務が膨れ上がっていることが考えられます。
より小さな責務、メソッドに分割できないかを検討してみてください。
例えば、上で登場したUserの場合、
- ユーザーの基本情報
- 管理者としての振る舞い
- メール送信の振る舞い
といった3つの責務を持っていることがわかりました。
それぞれを分割した後のコンストラクタは以下のようになります。
public User(int userId, String name, String email, String postCode, String address) { ... }
public Admin(int userId) { ... }
public MailTo(int userId, int email, boolean sendMail) { ... }
パラメータオブジェクト
メソッドを分割しても、どうしても引数が減らない、という場合があります。
責務が一つでも、利用する情報が多い場合は普通にあります。
そんな時は、パラメータを意味のあるまとまりにしてみましょう。これをパラメータオブジェクトと言います。
例えば、上記でメソッドを分割したものの、Userはまだ引数が多いですね。
パラメータオブジェクト化を検討します。
最後の二つの引数postCodeとaddressはどちらも住所に関連するものです。よって、これらをオブジェクトとして定義します。
class Address {
private String postCode;
private String address;
}
Userのコンストラクタは以下のようにできます。
public User(int userId, String name, String email, Address address) { ... }
また、ID、名前、メアド、住所を一つのまとまり=ユーザー情報とすることも考えられます。
その場合、Userのコンストラクタは以下のようにできます。
public User(UserInfo info) { ... }
Builderパターン
詳細は他記事にお任せします
当初のUserクラスのコンストラクタをBuilderパターンで実装すると、使う側は以下のように変わります。
// 変更前
User u = new User(1, "田中", "tanaka@example.com", false, true, "000-0000", "東京都〇〇区〇〇00-00-00");
// 変更後
User u = User.builder()
.userId(1)
.name("田中")
.email("tanaka@example.com")
.isAdmin(false)
.sendMail(true)
.postCode("000-0000")
.address("東京都〇〇区〇〇00-00-00")
.build();
それぞれの値が何を意味しているのか、一目瞭然になりましたね。
同じ型の値を渡し間違える心配も減ります。
引数をわかりやすくする方法
引数が多くても読みやすいメソッドは、どの引数が何を示しているのか明確なものです。
booleanはEnum化する
例えば、以下のようなメソッドを考えましょう。
// メソッド定義
void notify(String name, String email, boolean isAdmin) { ... }
// 使う側
notify("田中", "tanaka@example.com", false);
使う側だけ見ると、第3引数のbooleanが何を示しているのかわかりません。
このような場合にしばしば見るやり方が、変数として名前をつけることで意味を明示するものです。
boolean isAdmin = false;
notify("田中", "tanaka@example.com", isAdmin);
これで読みやすさは改善されますが、一度しか使わない変数を宣言するのはセンスに欠けるような気がしませんか?
そもそも、booleanで表現したいものは何でしょうか?YES/NOではなく、本来は「それぞれ意味を持つ2値」ではないでしょうか?
そのような場合はbooleanではなくEnumを使うことを検討しましょう。
Enum Role { ADMIN, NORMAL }
notify("田中", "tanaka@example.com", Role.ADMIN);
何を指すのかが明らかになりますし、このようにEnum化することで「isAdmin」を利用する箇所はすべてこのEnumを使って読みやすく書くことができます。
また、 isAdminに関するコードはこれまで探しにくかったはずです(それぞれの箇所で単に「true/false」と書かれるか、ローカル変数として宣言される)が、Enumの呼び出し箇所を調べることで、すぐに見つけることができるようになります。