クラス設計の本選び。計算と表示を別の役割として表した立体イラスト。

Java 技術書・書評

Javaのクラス設計は何から学ぶ?最初の1冊と「どこに書くか」の考え方

Javaで動くコードは書ける。でも「なぜ、このクラスに書いたの?」と聞かれると困る。そんな人がクラス設計を学ぶ最初の1冊は、『改訂新版 良いコード/悪いコードで学ぶ設計入門』です。

対象は、Javaの変数・条件分岐・繰り返し・メソッドを使って短い処理を書ける人。classやnewを入門書で見たことがあれば、クラスの分け方をまだ説明できなくても読み進められます。ここでは、料金を計算して表示するコードから「どこに何を書くか」を考えます。

この記事でわかること

  • クラス設計の最初の1冊と、その本を選ぶ理由
  • 正しく動くコードでも、処理を分けたほうがよい場面
  • 送料の変更と表示文の変更で、直す場所を分ける考え方
  • 本で学ぶ範囲と、次のテスト学習へ進む目安

目指すのは、処理を置いた場所と、変更する場所を説明できる人

クラス設計を学ぶ目標は、設計原則の名前や本の内容を丸ごと暗記することではありません。「この要件なら、ここに書く。条件が変わったら、ここを直す」と、コードを指して説明できることです。

私は、開発やレビューで構文の使い方だけでは実装を選べない場面を見て、オブジェクト指向やSOLID原則の勉強会を行ってきました。コードの理解を確認するときに重視しているのは、今回の要件、選べる手段、採用した理由、ほかの手段より今回に合う理由です。

クラスを増やした理由が「きれいになるから」だけでは、次に似た処理が出てきても判断できません。「料金の条件と、表示する文章は別々に変更される。だから置き場所を分けた」なら、どの変更で判断したかが伝わります。この記事では、その説明を短いJavaコードで練習します。

このブログの学習順は、Java基礎 → クラス設計 → テスト → Springです。まずJavaで計算を書き、値と処理の置き場所を決め、期待した結果をテストで確認する。その土台を持ってWebアプリの実装へ進みます。設計をすべて理解してから先へ進む必要はありません。

このコードは正しく動く。見積表示を足すなら、どこへ書く?

商品1種類の請求額を表示する処理です。仕様は「単価×数量が小計」「小計が10,000円以上なら送料無料、それ未満は500円」。単価3,000円・数量2個なら、小計6,000円に送料500円を足し、請求額:6500円という文字列を返します。

public static String format(int unitPrice, int quantity) {
    if (unitPrice < 0 || quantity < 1) {
        throw new IllegalArgumentException("単価は0円以上、数量は1個以上です");
    }
    int subtotal = Math.multiplyExact(unitPrice, quantity);
    int shipping = subtotal >= 10_000 ? 0 : 500;
    int total = Math.addExact(subtotal, shipping);
    return "請求額:" + total + "円";
}

引数は、単価が0円以上、数量が1個以上であることを確認しています。multiplyExactとaddExactは整数を掛け算・足し算し、intに収まらなければ例外にするJavaのメソッドです。ここで考えたいのは、料金を決める処理と「請求額」という文章を作る処理が、一緒になっていることです。

今の仕様には合っています。単発の表示だけなら、この長さのメソッドを急いで分ける必要もありません。では、次の変更が来たらどうしますか。バグを探すつもりで読むのではなく、何を追加し、どこを直すかを考えてください。

  1. 「見積額:6500円」も表示したい。料金の計算をもう一度コピーする?

    見積と請求で料金の決め方は同じです。違うのは前に付ける言葉だけ。どの部分を共通に使い、どの部分を別に書くか説明しづらければ、値と計算をまとめる考え方から学びます。

    この本で、値と処理のまとめ方を学ぶ ↓
  2. 送料無料を8,000円以上に変える。見積と請求の両方で条件を書き換える?

    一つの送料ルールが、使う場所の数だけ増えていないかを考えます。「同じ料金を決める場所は一つにする」と判断できても、どのクラスに置くか迷うなら、役割と名前の付け方を学ぶ段階です。

    この本で、変更する場所の決め方を学ぶ ↓
  3. 表示を「お支払額」に変える。送料を計算するコードも触る必要がある?

    今回は料金そのものは変わりません。金額を決める仕事と、金額をどう伝えるかという仕事を、別の変更として説明できるかが判断の入口です。

    この本で、目的の違う処理の分け方を学ぶ ↓

どれかで迷ったなら、Javaの文法を最初からやり直すより、クラス設計に進むと疑問を整理しやすくなります。逆に、分ける理由と分けなくてもよい条件まで説明できるなら、この本を必ず買い足す必要はありません。後半の変更課題と、テストへ進む目安を確認してください。

最初の1冊は『良いコード/悪いコードで学ぶ設計入門』

書籍「改訂新版 良いコード/悪いコードで学ぶ設計入門」の表紙
商品画像:Amazon.co.jp
PR Java新人・若手向け

改訂新版 良いコード/悪いコードで学ぶ設計入門

仙塲 大也 (著)

レビューで直される理由を、Javaの具体例から理解する。

命名、責務、クラス設計、不変性などを良い例と悪い例で比較できます。現場のコードレビューと結びつけやすい一冊です。

  • 設計の悪さを具体例から学びたい
  • レビュー指摘の判断軸を増やしたい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

『改訂新版 良いコード/悪いコードで学ぶ設計入門』を選ぶ理由は、「動くコード」の次に考えるべきことを、コードの構造と結び付けて学べるからです。名前、データと計算のまとめ方、値の扱い、目的が異なる処理の分離、既存コードの改善を扱っています。

先ほどの例で迷うのは、ifの書き方ではありません。送料の条件をどこに置くか、同じ金額を別の表示でも使うときに何を共有するかという判断です。この1冊を、そうした問いに答えるための本として勧めます。

この本で学ぶこと
関係する値と計算をまとめる考え方、意味が伝わる名前、目的の違う処理の分け方。「クラスを作る」という操作に、判断の基準を持たせます。
ここで得たい力
料金を決める処理をOrderPriceへ、請求の表示文を作る処理をReceiptTextへ置き、送料の変更と表示文の変更で直す場所が違う理由を説明する力。
次に埋めること
分ける前後で結果を変えていないか、送料の条件を変えたときに期待した金額になるかを、テストコードで繰り返し確認する方法。

読むときは「見積にも同じ料金を使うなら、どこを共通にする?」など、自分が答えられなかった問いを一つ持ってください。説明されている手法を、そのまま全部使う必要はありません。今のコードで何が困り、その手法で何が変わるかを結び付けます。

たとえば、不変にする考え方を知ったら「何にでもfinalを付けた」で終えず、計算済みの小計だけが後から変わり、合計と食い違う状態を防ぐためだと説明してみます。処理を分ける考え方を知ったら、ファイル数ではなく「表示文を変えるために料金計算を読み直す必要がなくなったか」を見ます。

これは設計の最初の1冊としての選定です。Javaの構文そのものが読めない段階を補う本としては選びません。また、例にある設計を唯一の正解とせず、自分の要件に必要な部分を取り出して使います。

料金のルールはOrderPriceへ、表示文はReceiptTextへ置く

見積でも請求でも同じ料金を使うなら、料金を決める部分をOrderPriceというクラスにまとめます。持たせる値は小計・送料・合計です。単価と数量を受け取り、一度の計算で三つを決めます。

OrderPriceが小計・送料・合計を決め、その金額を請求表示と見積表示が使う。送料条件の変更はOrderPrice、請求の文言変更はReceiptTextで行う。
料金を決めるルールと、金額を表示する文章を別々に変更できるようにする。

OrderPriceの中心は次の部分です。FREE_SHIPPING_FROMは送料無料になる小計、SHIPPING_FEEはそれ未満の送料を表します。名前を付けると「10,000とは何の金額か」を読み手が探さずに済みます。

public final class OrderPrice {
    private static final int FREE_SHIPPING_FROM = 10_000;
    private static final int SHIPPING_FEE = 500;
    private final int subtotal;
    private final int shipping;
    private final int total;

    public OrderPrice(int unitPrice, int quantity) {
        if (unitPrice < 0 || quantity < 1) {
            throw new IllegalArgumentException("単価は0円以上、数量は1個以上です");
        }
        subtotal = Math.multiplyExact(unitPrice, quantity);
        shipping = subtotal >= FREE_SHIPPING_FROM ? 0 : SHIPPING_FEE;
        total = Math.addExact(subtotal, shipping);
    }

    public int subtotal() { return subtotal; }
    public int shipping() { return shipping; }
    public int total() { return total; }
}

単価と数量のチェックは料金を作る入口に置いています。呼び出す側が毎回チェックし忘れないよう、new OrderPrice(...)を通るときに確認します。値は計算後に変更できず、total()などのメソッドで読み取れます。

この例では小計・送料・合計は、同じ料金を説明する値です。三つを別々の場所で計算して渡し回すより、まとめて作るほうが「この合計にどの送料を足したか」を追いやすくなります。画面を描く処理や文章の組み立ては、ここに入れません。

public static String format(OrderPrice price) {
    return "請求額:" + price.total() + "円";
}

ReceiptTextは、決まった金額を受け取って請求用の文章にするだけです。price.total()の値は使いますが、送料無料の条件をもう一度判断しません。「請求額」を「お支払額」に変えるなら、このメソッドの文字列を変更します。

OrderPrice price = new OrderPrice(3_000, 2);
System.out.println(ReceiptText.format(price));
System.out.println("見積額:" + price.total() + "円");

見積表示にも同じprice.total()を使えます。ReceiptTextから「請求額」という文字を取り除いて金額を取り出したり、見積用に送料計算をコピーしたりする必要がありません。料金を決める機能を、請求の文章を作る機能から独立して使えることが、この分け方の利点です。

整理する前から、送料を計算する場所は一つでした。今回分けた理由は、見積表示を足すときにも同じ計算を使い続けるためです。この先、送料ルールを変更しても、見積と請求で別々に条件を書き換える構造にせずに済みます。

サンプルコード

整理する前と後を、同じ入力で動かす

単価3,000円・数量2個で、どちらも「請求額:6500円」と表示します。整理後は同じ金額を見積表示にも使います。全文と実行手順を用意しています。

  • Java 17以上
  • 外部ライブラリ不要
  • Javaファイル4個

商品1種類、固定の送料ルールを扱う例です。税、割引、過去の請求額の保存は含みません。GitHubのREADMEに実行方法と変更課題を載せています。

クラスを分けるかは、「何が別々に変わるか」で判断する

分け方を考えるとき、まず変更内容を文章にします。この例なら「送料無料の基準を変える」「表示の呼び名を変える」「同じ金額を見積にも使う」の三つです。それぞれで、直したいコードと触らなくてよいコードを指せるか確認します。

送料の変更は、料金を決める場所で直す

送料無料を8,000円以上に変えるなら、OrderPriceのFREE_SHIPPING_FROMを8_000に変更します。見積表示と請求表示には、金額を決める同じ条件を書き足しません。単価7,999円・数量1個は合計8,499円、単価8,000円・数量1個は合計8,000円になります。

同じ送料ルールである限り、この置き方が使えます。もし会員と非会員で同時に異なる送料ルールを使う要件になったら、固定の定数だけで済むかを改めて考えます。今ない要件を先回りして、最初から多数のクラスや仕組みを作ることを目標にはしません。

表示だけの変更で、料金のルールを触らない

「請求額」を「お支払額」に変えるなら、ReceiptTextだけを直します。OrderPriceの計算結果は同じです。後から読む人も、表示文を変えた差分の中に送料条件の変更が混ざっていないかを確認しやすくなります。

逆に、CommonやUtilというクラスに料金と表示の両方を移しただけでは、何を任せたクラスなのかが曖昧なままです。名前を付ける前に、「このクラスは何を決め、何を決めないか」を一文にすると、入れる処理を選びやすくなります。

一度きりの短い処理なら、分けるコストも比べる

クラスを分けると、読むファイルと、クラス間をたどる回数は増えます。請求額を一度表示して終わり、料金を別の場所で使う予定もないなら、最初のメソッドを維持する判断もあります。単純なコードを、設計らしく見せるためだけに複雑にしないことも判断です。

一方、長く改修される業務システムでは、同じ金額を別の用途で使う、表示方法だけを変える、といった変更が積み重なります。担当者も変わるため、「似ているからコピーした」箇所を後任が全部見つけられるとは限りません。すでにある要件や変更を根拠に分け、理由を引き継げる形にすることが大切です。

レビューでは「分けた」だけでなく、要件と採用理由を伝える

レビューに出す前に、次の問いに答えてみてください。すべての設計案を列挙する必要はありません。今回迷った選択肢と、選んだ理由が分かれば十分です。

  • 今回の要件は?見積と請求で同じ料金を使い、それぞれ別の文章で表示する。
  • どんな選択肢があった?計算を見積用にもコピーする。文字列のメソッドを共用する。料金を決める部分を取り出す。
  • なぜ料金を取り出した?同じ料金を表示方法に縛られず使い、送料ルールのコピーを増やさないため。
  • 今回は、ほかの方法より何が合う?見積でも金額そのものが必要なので、請求用の文字列を返す形を使い回すより扱いやすい。

レビューコメントの例なら、「このクラスは計算も文章作成も担当しています。見積でも金額を使うので、金額を決める部分を独立させられますか」です。単に「責務を分けてください」と書くより、どの要件で困るかが伝わります。

返答も「分けました」だけで終えず、「小計・送料・合計をOrderPriceにまとめ、ReceiptTextは表示文だけを作る形にしました。見積は同じ合計を使います」とすると、変更の意図を追えます。AIの提案を採用した場合も、この説明を自分でできるか確認してください。

次のテストへ進む目安は、変更箇所と期待する金額を言えること

設計の本を最後まで暗記するまで、次へ進むのを待つ必要はありません。サンプルを見ながら、次の項目を説明できるか確認してください。まだJUnitの書き方を知らなくても、期待する結果は先に決められます。

  • OrderPriceが料金を決め、ReceiptTextが表示文を作ると説明できる。
  • 送料無料を8,000円以上へ変える箇所を指し、表示側に条件を複製しない。
  • 7,999円・8,000円・8,001円の小計に対し、送料と合計をそれぞれ言える。
  • 「請求額」を「お支払額」に変えても、計算する金額は変わらないと説明できる。
  • 単価がマイナス、数量が0の場合に、料金を作らず例外にする理由を言える。

条件を8,000円以上に変えた場合、上の三つの合計は順に8,499円・8,000円・8,001円です。境目のすぐ下、ちょうど、すぐ上を確認します。変更前の10,000円ルールと結果を混ぜないでください。

次のテスト編へ進む前に、小計7,999円・8,000円・8,001円の順に、変更前と変更後の期待値を書き分けてみてください。変更前の合計は8,499円・8,500円・8,501円です。送料の条件を変えるなら、変わるのは後ろの2件。「請求額」を「お支払額」に変えるだけなら、3件とも金額は変わりません。

次のテスト編でも同じ送料無料の条件を使います。小計7,999円・8,000円・8,001円を渡して、変更後の送料が500円・0円・0円になることをJUnitで確かめます。この設計編で書き出した合計は小計と送料を足した金額です。テスト編では送料だけを取り出して、条件を変えたときにどのケースが変わるかを確認します。

実践|クラス設計 Javaの引数をクラスにまとめる判断|予約の開始・終了時刻を例に 開始と終了を一つの予約枠にまとめます。時刻の前後関係と、部屋によって変わる予約上限をどこに置くか比べられます。 コードを動かす
前の記事はこちら|Java基礎Java初心者におすすめの最初の1冊|Spring Bootに進むまでの学び方 コードの文法やメソッドの読み方から確認したい場合は、Java基礎の本選びへ戻れます。 記事を読む 次の記事はこちら|テストJavaのテスト入門におすすめの本|テストケースを選べる最初の1冊 決めた料金のルールを、どんな入力と期待値で確かめるか。テストに残すケースの選び方へ進みます。 記事を読む

最初は、同じ料金を別の表示でも使える形を説明する

Javaの文法は書けるのに、処理をどのクラスへ置くか迷うなら、『改訂新版 良いコード/悪いコードで学ぶ設計入門』から。コードの見た目だけでなく、データと計算のまとまり、名前、変更される理由を学ぶための1冊です。

最初の題材は、この記事の料金計算で十分です。「送料の条件はOrderPrice、請求の文章はReceiptText。見積も同じ金額を使う」と説明する。変更したい要件が変わったときも、その理由をもとに置き場所を考えられることを目指してください。

最初の1冊と、この本で学ぶことを確認する ↑

-Java, 技術書・書評
-, , , ,