第4章 不変の活用 ─安定動作を構築する─
可変(ミュータブル)と不変(イミュータブル)の設計は、コードの安定性や保守性を大きく左右します。本章では、不変性を活用して意図せぬバグを防ぐための設計指針について解説します。
4.1 再代入
再代入の問題点
破壊的代入(変数への再代入)は、以下の問題を引き起こします。
- 変数の意味が変化し、コードの推測が難しくなる。
- バグを誘発する可能性が高まる。
推奨される方法
変数を再代入せず、新しい変数を用意します。また、ローカル変数や引数を変更不可にできる言語では、その仕組みを活用します。
言語別の例
-
Java:
final
修飾子 -
C++:
const
修飾子 (const T
,const T* const
)
補足
ただし、ループ内の変数など意味が明確に固定される場合は変更を許容する場面もあります。
4.2 可変がもたらす意図せぬ影響
問題点
可変な設計には以下のリスクがあります。
- インスタンスの使い回し: 同じインスタンスを再利用すると、予期せぬ場所で値が変わり、バグを引き起こします。
- 副作用: 同じ結果を得るために、操作手順に依存する設計は予測困難で保守が困難になります。
副作用の分類
- 主作用: 引数を受け取り、結果を返す関数の本来の動作。
- 副作用: 主作用以外に、関数の外部状態を変更する動作(例: メンバ変数の変更、ファイルI/O)。
副作用を持たない設計が推奨されます。
具体的には以下のような方針です。
- 引数で状態を受け取り、関数内で状態を変更しない。
- 結果は新しい値を返す形にする。
- 影響をクラススコープ内に閉じ込める。
実現方法
不変性を保つには、メンバ変数を不変にし、新しいインスタンスを生成する設計を採用します。
4.3 不変と可変の取り扱い方針
不変をデフォルトにする理由
- 変数の意味が変化せず、混乱を防ぐ。
- 挙動が安定し、結果が予測可能。
- 影響範囲が限定され、保守が容易。
可変を許容するケース
以下の場面では、可変を認める設計が実用的です。
- パフォーマンスが重要な場合: 大量データ処理や画像処理など。
- スコープが局所的な場合: ループカウンタや一時的なローカル変数。
注意点
可変にする場合は、状態変更を明確に制御し、誤操作を防ぐ設計(例: フールプルーフ)が重要です。
まとめ
- 不変性を基本とした設計は、コードの安定性と保守性を向上させます。
- 再代入や副作用を極力排除し、状態変更が必要な場合でも範囲を明確に限定することが推奨されます。
- 言語が提供する不変性を活用し、不変な変数やインスタンスを積極的に採用しましょう。
方針
社内メンバと勉強会を行った結果、下記の方針で実装を行うこととなった。
- 再代入は極力避ける: 変数の意味が変わらない設計を心がける。
- 副作用を最小限に: 関数やメンバ変数の影響を局所化する。
- 不変をデフォルトにし、可変は必要な場面に限定する。