はじめに
この記事では
- 基本型と業務で扱う値の範囲の違いについて
- 扱う値の範囲を表現することで値を扱う側をシンプルに
これらについて、
9年間ほど現場で設計やリファクタリングをしてきた自分なりの体験や観点、
現場で役立つシステム設計の原則第1章 小さくまとめてわかりやすくする
を参考に図などを使ってまとめました。
新卒や社内でもこの内容を共有していく予定で、
特に駆け出しエンジニアやプログラマーの方達には
早い段階で理解して頂きたい内容です。
現場で役立つシステム設計の原則を読んでの気づきなどのまとめ記事を作ったので
こちらも合わせてご覧ください。

基本型の値の範囲と業務で扱う値の範囲の違い
例えば、
システムで金額を取り扱う場合、
取り扱える金額としてマイナスが許容されず、
100万円までの取引が可能だった場合
どのように業務で取り扱う金額を表現するのが良いでしょうか。
基本型であるintはマイナス21億〜プラス21億までの値の範囲を扱えます。
つまりintで表現すると
マイナスを許容して
プラス100万円より大きな値を許容することになります。
val transactionAmount: Int = 1_000_001
業務で扱う値をシステム内で存在させてしまうと、
その値を使う側は、
業務で取り扱っていい値なのかを常に気にしなければなりません。
if(transactionAmount < 0 || transactionAmount > 1_000_000) {
throw IllegalArgumentException("取引金額が不正です")
}
さらに、
大抵の場合使う側は複数箇所あるので、
複数箇所で気にしなければなりません。
では、
もしシステムリリース後しばらくして、
業務で取り扱う値の範囲が変更になったらどうでしょう。
業務で取り扱う値の範囲を気にしていた箇所
(if文でチェックしていた箇所)
を全て変更して回ることになります。
これが、
業務で取り扱う値の範囲は狭いのに
基本型の取り扱う広い値の範囲で
丸っと表現してしまった場合に発生する
悪い例です。。。
扱う値の範囲を表現することで値をシンプルに扱う
ここからは、
先ほどの悪い例に陥らない対策について見ていきます。
やり方としては、
業務で扱う値の範囲しか取り扱えないクラスを作って、
それを値のように利用します。
data class TransactionAmount(
val amount: Int,
) {
init {
if(amount < 0 || amount > 1_000_000) {
throw IllegalArgumentException("取引金額が不正です")
}
}
}
class 使う側クラス() {
val transactionAmount = TransactionAmount(100) // 取り扱える
val errorTransactionAmount = TransactionAmount(101) // IllegalArgumentException
}
TransactionAmountという業務で扱う金額を表現するクラスは、
インスタンス生成の時に、
0~1,000,000の範囲外では生成できなくしています。
これによって使う側は値が作れた(インスタンス化成功)場合は
必ず業務で取り扱える0~1,000,000の範囲内なので、
余計な疑いを持たずに値が利用できます。
(至る所での値の範囲チェック不要)
また、もし取り扱う値の範囲が変わっても、
変更する箇所(影響箇所)は、
TransactionAmountクラスだけになり、
仕様変更による変更も楽になります。
このように、業務で扱う値の範囲を正確に表現できると、
値を扱う側がシンプルになります。
おまけ(不変による副作用対策)
実はこのTransactionAmountの持っているamountは値をvalで不変にしていて、
例えば1という値の意味が途中で2に変わらないのと同じように、
インスタンス化した値は途中で変更できないようにしています。
これによって副作用が起きにくく、
システムが安定します。
が、この辺りはまた別記事でまとめようと思います。
業務で取り扱う値の範囲と
intなどの基本型が取り扱う範囲は異なる。
無理やり基本型で業務で扱う値を表現するのではなく、
業務で扱う値の範囲に特化したクラスで表現すると、
使う側がシンプルで、仕様変更にも強くなる。
参考文献