Javaのrecord型がもたらす設計ミス
Javaのrecord型がもたらす設計ミス
Java 14で導入された record は、いわゆる“データ専用クラス”の記述を簡素化する機能として歓迎されました。
一方で、その構文的な簡潔さが設計判断を曖昧にし、責務の誤配置を誘発するケースも少なくありません。
本記事では record を巡る設計的な誤解を解き、レビューアーとしてどのような指摘が妥当かを整理していきます。
recordとは何か:構文と目的の整理
Javaのrecordは、値の集まり(Value Object)を安全かつ簡潔に表現するための言語構文です。
public record Point(int x, int y) {}これは以下のようなコードを暗黙的に生成しています:
finalなフィールド定義(x,y)- コンストラクタ
 - ゲッター(
x(),y()) toString(),equals(),hashCode()の実装
recordは「不変なデータの容れ物」としての責務に特化した構造です。副作用を持たず、状態を変更しない構造であることが設計前提となります。
OK設計:recordが本来持つべき責務とは
recordがうまく機能するのは以下のような用途です:
- 外部APIとのデータ交換用DTO
 - enum的に使う識別子の集合(例:
Coordinates) - ビジネスロジックに依存しない不変データの転送・集約
 
public record UserDto(String name, int age) {}このように、recordは「構造が意味を持ち、振る舞いを持たない」場合に真価を発揮します。
誤った使い方と設計ミスの例
recordは簡潔な構文のため、過剰なロジックや設計上の責務を押し込まれがちです。以下に典型的なNG例を挙げます。
1. ビジネスロジックをrecordに押し込む
public record Order(int amount, String currency) {
    public boolean isFreeShipping() {
        return amount > 10000 && "JPY".equals(currency);
    }
}「データ構造」に「条件ロジック」が混ざっており、record の責務逸脱と判断される設計です。
このような振る舞いはService層やユーティリティに移譲すべきです。
2. DI対象やドメインモデルへの流用
@Component
public record AccountService(AccountRepository repo) {
    public void execute() {
        // 処理
    }
}recordの構文でクラスを簡単に定義できるため、安易にコンポーネント化してしまう例です。
recordを使うと「コンストラクタ・フィールド・getter」が自動で生成されるため、つい責務を誤認してロジックを混在させる傾向があります。
UMLでみる責務の混在構造
このように、record内部に判定ロジックが存在するだけで、責務の視点からは密結合に近づいてしまいます。
なぜrecordが“設計ミス”につながるのか
構文上の「簡潔さ」が責務設計を曖昧にする
recordの構文はあまりにも書きやすいため、「データを集めるためのクラス」以外の目的でも乱用されがちです。これはクラス設計の粒度を見誤らせ、以下のような問題につながります:
- 本来レイヤ分離されるべきロジックの集約
 - 機能追加時の責務の集中(God Object化)
 - テストが難しい(副作用が想定しづらい)
 
recordがService・Entity・DTOの役割を横断し始めると、アーキテクチャ全体に混乱を招きます。
レビューでの指摘ポイント
recordの使用に対して、レビューアーとして注目すべき観点を以下に示します。
チェックリスト:record設計レビュー観点
recordにビジネスロジックが含まれていないかをまず疑ってください。
設計改善パターン
record + ユーティリティ
public record Order(int amount, String currency) {}
public class ShippingUtils {
    public static boolean isFreeShipping(Order order) {
        return order.amount() > 10000 && "JPY".equals(order.currency());
    }
}責務を明示的に分離することで、recordの役割は「データ構造」に限定され、ユーティリティクラスがロジックを受け持ちます。
ロジックはServiceやUtilに、recordはDTOとして。それぞれの責務が混ざらない構造が理想です。
設計的補足:recordとイミュータブル設計
recordはイミュータブル(不変)であるがゆえに、変更操作が発生する箇所では適さないケースもあります。
例:
- バリデーション後の状態変化(例:
isVerified→true) - 一部フィールドだけを変更したいケース
 
このような場合には、クラスによる柔軟な構造やビルダー・パターンの方が設計的に適していることもあります。
まとめ:recordは“安易な万能型”ではない
recordは構文的に優れているがゆえに、責務の混乱を引き起こしやすい構造でもあります。
レビューアーは以下の3点に特に注意すべきです。
- recordにロジックが入っていないか(構造と振る舞いの混同)
 - アーキテクチャ層を越えて使用されていないか
 - 変更に弱い構造になっていないか(不変であることの落とし穴)
 
recordを“構造のみ”に限定して活用することで、Java設計の明快さは保たれます。
