この記事は、保守性の高いコード設計シリーズの一部です。これまでの記事では、クラス設計の基本として「完全コンストラクタ」や「値オブジェクト」を解説しました。今回は視点をメソッド内部に向け、コードの安定性を損なう「再代入」の問題とその解決策を探ります。
こんにちは。皆さんは、一つの変数を計算の過程で何度も上書き(再代入)して使っていないでしょうか? 例えば、tmp
(temporary: 一時的)のような名前の変数に、次々と計算結果を代入していくスタイルです。
この「再代入」は、コードを追跡しにくくし、デバッグを困難にする大きな要因です。今回は、なぜ再代入が良くないのか、そしてJavaのfinal
キーワードを使って、いかにしてコードを明確で追跡しやすくできるかを見ていきましょう。
なぜ「再代入」はコードを読みづらくするのか?
ECサイトの最終的な支払い金額を計算するロジックを例に考えてみましょう。
【Before】一つの変数が何度も上書きされ、途中経過が失われるコード
// 'tempPrice' が小計、ポイント適用後、送料込み、と役割を変え続ける
double tempPrice = itemA.price() + itemB.price();
tempPrice = tempPrice - member.getPoints() * 0.5; // ポイント割引を適用
tempPrice = tempPrice * 1.10; // 消費税を加算
tempPrice = tempPrice + shipping.getFee(); // 送料を加算
tempPrice = Math.max(0, tempPrice); // 最低0円に補正
return tempPrice;
このコードの問題点は、ある特定の行におけるtempPrice
の値を理解するために、それ以前のすべての行を頭の中で実行しなければならないことです。最終結果がバグっていた場合、どの計算ステップで値がおかしくなったのかを特定するのも一苦労です。計算の途中経過が、上書きによって失われてしまっているからです。
解決策:final
で不変にし、計算ステップを明確にする
この問題を解決するシンプルかつ強力な方法が、ローカル変数を**不変(イミュータブル)**にすることです。Javaでは、変数の宣言時にfinal
をつけることで、その変数への再代入をコンパイルレベルで禁止できます。
final
を使って先ほどのコードを書き直すと、計算の各ステップが独立した変数として表現され、物語のように読み進められるようになります。
【After】final
を使い、各計算ステップが独立した変数になったコード
// ステップ1: 商品の小計
final double subtotal = itemA.price() + itemB.price();
// ステップ2: ポイント割引を適用
final double priceAfterPoints = subtotal - member.getPoints() * 0.5;
// ステップ3: 消費税を加算
final double priceWithTax = priceAfterPoints * 1.10;
// ステップ4: 送料を加算
final double priceWithShipping = priceWithTax + shipping.getFee();
// ステップ5: 最終的な支払い金額(最低0円)
final double finalPaymentAmount = Math.max(0, priceWithShipping);
return finalPaymentAmount;
いかがでしょうか。このコードでは、subtotal
やpriceWithTax
といった各変数が、計算プロセスにおける一つの明確な役割を担っています。どの変数も一度値が決まったら二度と変わりません。
これにより、以下のようなメリットが生まれます。
- 可読性の向上: コードが上から下への単純なデータの変換プロセスとして読める。
- デバッグの容易さ: 各ステップの値が独立した変数として残っているため、デバッガで途中経過を簡単に確認できる。
- 安全性の向上: 誤って変数を上書きしてしまうという単純なミスを防げる。
引数もfinal
で不変にする
この「不変にする」という考え方は、メソッドの引数にも適用できます。引数もメソッド内ではローカル変数の一種です。
【Before】引数をメソッド内で変更してしまっている
void applyDiscount(int price) {
if (price > 10000) {
price = price - 1000; // 引数を直接変更している
}
// ...
}
このように引数を変更するコードは、そのメソッドが「受け取った値を参照しているだけ」なのか「途中で値を変更しているのか」が分かりにくく、混乱を招きます。
【After】引数をfinal
にして、変更しないことを明示する
void applyDiscount(final int price) {
// price = price - 1000; // これはコンパイルエラーになる!
final int discountedPrice = (price > 10000) ? price - 1000 : price;
// ... discountedPrice を使って処理を続ける
}
引数にfinal
をつけることで、「このメソッドは、受け取った引数の値を途中で変更しません」という設計意図を明確に示し、それをコンパイラに保証させることができます。
まとめ
変数の再代入は、一見便利なようで、コードの追跡を困難にし、バグの温床となります。
**「ローカル変数と引数は、原則としてfinal
をつける」**という習慣を身につけるだけで、メソッドの内部構造は劇的にクリーンで安定したものになります。これは、安定したソフトウェアを構築するための、小さくても非常に効果的な一歩です。
▼次の記事 次回は、この「不変性」の考え方をローカル変数からオブジェクト全体へとスケールアップさせます。変更可能なオブジェクトが引き起こす「副作用」の恐ろしさと、それを不変なオブジェクト設計でいかにして防ぐかを詳しく解説します。