この記事は、保守性の高いコード設計シリーズの一部です。前回の記事「【第1部】 その
static
メソッド本当に必要?凝集度を高める設計の基本」では、データとロジックを一つにまとめることの重要性を見ました。
こんにちは。メソッドを設計する際、その「顔」となるのがシグネチャ(メソッド名と引数のリスト)です。分かりにくい、あるいは長すぎる引数リストは、メソッドの使い勝手を著しく低下させ、バグの温床となります。
今回は、低凝集の兆候でもある「悪いメソッドシグネチャ」の典型的なパターンと、それをクラス設計によって改善する方法を解説します。
パターン1:結果を返すために引数を使わない
メソッドの基本的な役割は、「引数を受け取り、処理を行い、結果を返す」ことです。しかし、戻り値を使わずに、引数として受け取ったオブジェクトの状態を直接変更してしまうメソッドがあります。
【Before】引数で受け取ったposition
の状態を直接変更している
class CharacterMover {
// positionオブジェクトのx, yを直接変更(副作用)
void move(Position position, int deltaX, int deltaY) {
position.x += deltaX;
position.y += deltaY;
}
}
この設計の最大の問題点は、メソッドを呼び出す側から見て、引数に渡したposition
オブジェクトが変更されることが分かりにくいことです。これは予期せぬ副作用を生み、特にそのオブジェクトが他の場所でも使われている場合、追跡困難なバグの原因となります。
【After】引数は変更せず、新しい結果を戻り値として返す
// Positionクラスを不変(Immutable)にする
class Position {
final int x;
final int y;
Position(final int x, final int y) { /* ... */ }
// 移動後の「新しい位置」を計算し、新しいインスタンスとして返す
Position move(final int deltaX, final int deltaY) {
return new Position(this.x + deltaX, this.y + deltaY);
}
}
// 利用側のコード
Position current = new Position(10, 20);
// moveメソッドはcurrentの状態を変えず、新しいmovedインスタンスを返す
Position moved = current.move(5, 5);
ロジックをCharacterMover
からPosition
クラス自身に移し(高凝集!)、move
メソッドが新しいPosition
インスタンスを返すように変更しました。これにより、状態変化が戻り値として明確に表現され、副作用のない安全なメソッドになります。
パターン2:多すぎる引数と「プリミティブ型執着」
メソッドの引数が4つ、5つと増えてきたら、それは危険信号です。多くの場合、その背景にはプリミティブ型執着という問題が潜んでいます。これは、int
やString
といった言語標準の基本型(プリミティブ型)だけで、あらゆる概念を表現しようとすることです。
【Before】プリミティブな引数が多く、間違いやすいメソッド
// 5つのintと2つのString... 引数の順番を間違えそう!
void shipPackage(int width, int height, int depth, int weight, int postalCode, String address, String recipientName) {
// widthやweightがマイナスでないか、ここでチェック…
// postalCodeの形式は正しいか、ここでチェック…
}
このメソッドには、以下のような問題があります。
- 間違いやすい:
width
とheight
のように、同じint
型の引数の順番を間違えても、コンパイラは検知してくれません。 - 低凝集: 荷物の寸法や重さ、住所といった、本来まとまりであるべき概念がバラバラになっています。
- ロジックの重複:
width
がマイナスでないかのチェックなど、同じバリデーションロジックが様々な場所に散らばりがちです。
【After】意味のある単位でクラス化し、引数を減らす この問題は、関連するプリミティブ値を**意味のあるクラス(値オブジェクト)**にまとめることで解決できます。
// 「寸法」という概念をクラス化
class Dimensions {
final int width, height, depth;
Dimensions(int w, int h, int d) { /* マイナス値でないか等のバリデーションをここに集約 */ }
}
// 「住所」という概念をクラス化
class ShippingAddress {
final int postalCode;
final String address;
final String recipientName;
ShippingAddress(int pc, String addr, String name) { /* バリデーションをここに集約 */ }
}
// 引数が劇的に減り、意図が明確になったメソッド
void shipPackage(final Dimensions dimensions, final ShippingAddress address) {
// ...
}
「寸法」や「住所」といった具体的な型を設計することで、引数の数は減り、それぞれの役割が明確になりました。バリデーションロジックも各クラスのコンストラクタに集約され、コードの重複もなくなります。これがプリミティブ型執着からの脱却です。
まとめ
クリーンなメソッドシグネチャは、保守性の高いコードの要です。
- 結果は戻り値で返す: 引数を変更する副作用は避けましょう。
- プリミティブ型執着をやめる: 関連するデータは独自のクラスにまとめ、メソッドの引数を豊かで安全なものにしましょう。
▼次の記事 次回は、一見すると便利に見える「メソッドチェーン」が、なぜ危険な設計の兆候となりうるのか、そしてオブジェクトに適切に「仕事を任せる」ための「デメテルの法則」について解説します。