見出し画像

【Javaお勉強日記】ドメイン駆動設計をどうやってJavaプログラムに落とし込んでいくかをお勉強する-1

ソフトウェアを変更に強くしたい

ソースがごちゃごちゃしていると、ちょっとした変更がとんでもない場所に影響を及ぼしたりして大変である。

私はそれを昨日痛感しました。(VBAですが、二年前の自分が書いたコードの大幅改修が必要になり……まだやっとVBAでマクロが作れるようになったばかりの頃のコード……レイヤードアーキテクチャを意識どころか関数の切り出しすらろくに出来ていない恐ろしいコード……一箇所の変更が際限なく影響を与え……どこに何が書いてあるかわからず……結局作り直しました)

というわけで、如何にコードをスッキリさせて、どこに何が書いてあるかわかりやすくして、変更に強いプログラムを作るかについて、しっかりお勉強していきたいと思います。

テキストはこちら。

現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法 増田 亨

複雑なプログラムとは

・変数の名前が分かりにくい(aとかbとか、cntとか)

・1つのメソッドが長い

・1つのクラスが持っている変数やメソッドが多い

・1つのクラスやメソッドに渡すべき引数が多い

……教科書には以上のように書いてありますが、個人的にはこれに

・パッケージ(フォルダ)が正しく分割されていない

も付け加えておきたいところ。(それで結構散々な目を見ているので……)

なら、わかりやすいプログラムとは

・変数の名前が分かりやすい

・1つのメソッドが短い

・クラスが持つ変数やメソッドは最低限

・引数が少ない

・パッケージが適切に分かれている

ということになりますね!

……で、それをどうやって実現するのか

言うは易し行うは難し。あたまで分はかっていても、実際にその通りにプログラムを組み立てるのは容易ではないもの。

具体的にどのようにわかりやすいプログラムを書いていけば良いのでしょうか。

変数の名前をわかりやすくして、使い回さない

これはもう、今日からすぐ出来るね!

価格をa、数をbと置くのを止めて、price、quantityと名付ける。

taxInPr、ttlQtyみたいな略語も使わない。

なんなら、plainPrice、taxInPrice、みたいにより具体的に命名した変数を、計算ステップごとに用意するようにして、priceの中身をどんどん書き換えて使うのをやめる。

そうすれば、「このaってなんじゃろ……」とか「このpriceは今……税込価格が入ってるんじゃろか、税抜価格じゃろか……」ということにならないので、コードの見通しが良くなります。

できたら業務で使っている用語をそのまま使うと良いです。

(……のは分かってるけど、それを英訳するべきかローマ字表記にするべきかはなかなか判断・意志統一が難しいところ……例えば現場では特定の例外処理をすることを「回す」と呼び習わしていて、「回す」かどうかのフラグを作ったとして、それは「IsMawashi」にするのか「IsTurn」にするのか「回す」の具体的内容を洗い出して英訳するのか……)

メソッドを短くする

例えば、「商品の合計金額と送料を計算して請求総額を求める」というメソッドがあったとして、

public Price calculateTotalPrice(Price basePrice, Quantity quantity){
    Price totalPrice = basePrice * quantity;
    Price shippingCost;
    if(totalPrice < 3000){
        shippingCost = 500;
    } else {
        shippingCost = 0;
    }
    shippingInPrice = totalPrice + shippingCost;
    taxInPrice = shippingInPrice * 1.1;
}

という長いメソッドになっています。

ここから、送料の計算のところだけ切り出して、

public Price calculateTotalPrice(Price basePrice, Quantity quantity){
    totalPrice = basePrice * quantity;
    Price shippingCost = calculateShippingCost(totalPrice)

    shippingInPrice = totalPrice + shippingCost;
    taxInPrice = shippingInPrice * 1.1;
}

// 送料計算部分だけ別のメソッドに切り出す
private Price calculateShippingCost(Price totalPrice){
    if(totalPrice < 3000){
        return new Price(500); // 条件に合う場合はさっさとreturnしてしまう
    }
    return new Price(0); // else節はない方が見やすい
}

と、メソッドを分けることで元のメソッドを小さくできます。

で、切り分けた送料計算メソッドの置き場は、「参照構造の下層の方に置く」「依存関係のないクラスから参照されるなら別クラスとして切り分ける」という二点を意識すると綺麗に収まります。

データとロジックをひとまとめにしたクラスを作る

扱うデータには専用の型を用意してあげて、見当違いの値が入ってこないようにすると良いです。

例えば、電話番号が100桁になることはないし、一般的な売買の注文数ならせいぜい1000個くらいまで取り扱えれば良く、マイナス値は原則入らないはず。これをint型で取り扱ってしまうと、最大でプラス21億からマイナス21億まで入ってきてしまうので、予想外の値が入ってくることによるエラーを回避するためにも、専用の型を用意して、外れ値は入れないようにしてしまうのが良いでしょう。

で、例えば電話番号型であれば、「ハイフン記号を付けたり外したりする」「市外局番から市町村を調べる」とか、そういう操作が付随してくるはずです。

ここを、「電話番号を格納しておくクラス」と「電話番号を操作するクラス」に分けて作ることも出来るけど、電話番号にまつわる修正が発生した時、どっちに影響するのかと考えないといけなくなってしまいます。そこで、それらを一つにまとめて「電話番号クラス」を作ってしまいましょう。

電話番号にまつわる問題は全て電話番号クラスを見れば良い。簡潔です。

データ型はイミュータブルオブジェクトにする

良く言われるやつですね。

私も一回まとめています

priceが税別→送料込み→税込送料込み……と変更されていくと、プログラムを改修するときに「今このpriceにはどんな値が入っているっけ……」という心配をしなければならないので、新しい数値が必要になったら新しいオブジェクトを作り、別の変数に代入するようにします。

イミュータブル(不変)オブジェクトにするには、
・コンストラクタがフィールド変数に値を入れる
・Setterは作らない
・値を変える処理(足し算とか引き算とか)をする場合、自分自身の新たなインスタンスを作成してreturnする
これでイミュータブルに出来ます。やったね。

配列やコレクションをクラスに閉じ込める

配列やそれに類する要素はコードを複雑にします。

そこで、配列にまつわる操作は全部コレクションオブジェクトを作って、そこに押し込んでしまいます。

List<Hoge>型の変数を一つだけ持つHogeListクラスを作って、HogeListにまつわる処理、追加したり削除したり書き換えたり、は全部そこにまとめてしまう。で、HogeList側では、追加したり削除したりする処理をした場合、イミュータブルオブジェクトの時と同じように新しいリストを作って返却するようにすることで、安定に近づきます。

さらに、「リストの中身全部ちょーだい」という処理が必要になった場合、HogeListクラス側に処理を書けないか検討します。

どうしてもリストの中身を全部差し出さないといけない場合は、

return Collections.unmodifiableList(hoge);

という形で、変更不可のリストとして渡すのが良いでしょう。

「場合分け」を簡単にする

「ブロンズ会員なら割引なし」「シルバー会員なら5%オフ」「ゴールド会員なら……」のような、場合に応じて処理を変えないといけない場合、if文をぞろぞろ書いていくとコードが複雑になります。

そこで、以前勉強したポリモーフィズムインターフェイスの出番です。

「割引価格算出」メソッドを持った「会員インターフェイス」を用意して、それを実装した「ブロンズ会員クラス」「シルバー会員クラス」……とそれぞれのクラスを作ります。

で、それを使う側は「会員」クラスを受け取るように設定しておけば、自動的に型変換が行われるのでどの会員クラスでも受け取れて、割引価格を計算するときは各クラスの持つ「割引価格算出」メソッドを使うようにすれば、プログラムからifを消すことができます。やったね!

クラスを一覧にできる列挙型

だいぶ便利になりましたが、でもまだ「どんな会員タイプがあるんだっけ」と解らなくなっちゃう問題があります。そんなとき活躍するのが列挙型(Enum)。

enum MemberType{
    bronze( new BronzeMember() );
    silver( new SilverMember() );
    gold( new GoldMember() );
    
    private Member member;
    
    public MemberType(Member member){
        this.member = member;
    }
}

って型名とそれに対応するクラスを列挙して、コンストラクタの引数にMember型を取るようにしておいて、

MemberType memberType = MemberType.valueOf("gold");

コンストラクタに作りたい型名を指定すれば、内部にその会員型を持つMemberType型のインスタンスを作ることができます。

(ここの内部処理が、わかるようでいまいちよくわからないけど……)

とりあえず、今日はここまで。

続きはこちら。


この記事が気に入ったらサポートをしてみませんか?