本記事は、2026/5/30開催の「JJUG CCC 2026 Spring」の登壇内容に関連した技術記事シリーズです。
AIエージェント(Copilot / Claude Code / Cursor)の普及により、Java開発の前提は大きく変わりました。
本シリーズでは「AI時代における設計の変化」をテーマに、実務視点で整理していきます。
【シリーズ一覧】
- AI時代にJava設計はどう変わったのか
- カッペリーニコードとは何か
- Java17/21は設計をどう変えたか
- AIは設計できるのか
- AI時代の設計ガードレール
はじめに
GitHub CopilotやClaude Codeを使ったことがある開発者なら、こんな経験があるはずです。
AIが生成したコードを見ると、クラスは小さく、構文は整っていて、一見するとお手本のように読みやすい。
なのに、いざ仕様変更に入ろうとすると、処理がどこにあるのか追えない。何かを変えたとき、影響範囲が把握できない。システムの「背骨」がどこにあるのかわからない。
これは、スパゲッティコードではありません。むしろ正反対の見た目をしています。
私はこの状態を 「カッペリーニコード」 と呼んでいます。
1. 定義
カッペリーニコードとは
クラスは小さく、構文は美しい。しかしシステム構造が見えなくなるコード。
カッペリーニはスパゲッティより細いパスタです。細く、軽く、一本一本はきれいです。しかし大量に絡み合うと、スパゲッティより扱いにくい。
コードも同じです。
単体で見れば美しい小クラスが無数に存在し、全体として何をしているのか把握できなくなる。これがカッペリーニコードの本質です。
2. スパゲッティコードとの決定的な違い
スパゲッティコードとは
長年、ソフトウェア開発者が戦ってきた古典的な敵です。
@Service
public class OrderService {
public Order createOrder(CreateOrderRequest request) {
// バリデーション
if (request.items() == null || request.items().isEmpty()) {
throw new IllegalArgumentException("items required");
}
// 合計金額計算
BigDecimal total = request.items().stream()
.map(item -> item.price().multiply(BigDecimal.valueOf(item.quantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 保存
Order order = new Order(total);
orderRepository.save(order);
// メール送信
mailService.send(order.email(), "注文確認", buildMailBody(order));
return order;
}
}
特徴はわかりやすいです。
- クラスが巨大になる
- 責務が一箇所に集中する
- メソッドが長く、可読性が低い
- 依存関係が絡まり合う
問題が 目に見える のが、スパゲッティコードの特徴でもあります。コードを開いた瞬間に「これはまずい」とわかります。
カッペリーニコードとは
AIが生成したコードはこうなります。
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderValidator validator;
private final OrderCalculator calculator;
private final OrderRepository repository;
private final OrderNotifier notifier;
public Order create(CreateOrderRequest request) {
validator.validate(request);
Money total = calculator.calculate(request);
Order order = new Order(total);
repository.save(order);
notifier.notify(order);
return order;
}
}
一見すると、理想的な構造です。
- クラスが小さい
- 責務が分離されている
- メソッドが短い
- 構文もモダン
しかしこれが大量に生成されると、何が起きるか。
OrderService
└─ OrderValidator
└─ OrderCalculator
└─ PriceCalculator
└─ DiscountCalculator
└─ DiscountRuleEvaluator
└─ OrderRepository
└─ OrderNotifier
└─ MailSender
└─ SlackNotifier
処理を追うために、何クラスも渡り歩かなければなりません。しかもそれぞれのクラスは「小さくて正しい」ので、問題があるとは気づきにくい。
対比表
| 観点 | スパゲッティコード | カッペリーニコード |
|---|---|---|
| クラスサイズ | 巨大 | 極小 |
| 構文 | 古い・冗長 | モダン・整っている |
| 可読性(単体) | 悪い | 良い |
| 可読性(全体) | 悪い | 悪い |
| 問題の視認性 | すぐわかる | 気づきにくい |
| 設計意図 | 崩壊している | 存在しない |
スパゲッティコードは「見た目でわかる悪さ」です。カッペリーニコードは「見た目が良いのに動かせない」という、ある意味でより厄介な問題です。
3. 実例:Spring Bootで起きること
Spring Bootプロジェクトでは、この現象が特に発生しやすいです。
DTO の爆発的増加
AIはAPIごとにDTOを丁寧に作ります。
public record UserRequest(String name, String email) {}
public record UserResponse(String id, String name, String email) {}
public record UserUpdateRequest(String name) {}
public record UserSearchRequest(String keyword, int page) {}
public record UserSearchResponse(List<UserSummary> users, int total) {}
public record UserSummary(String id, String name) {}
public record UserDetailResponse(String id, String name, String email, String role) {}
1エンティティに対して7クラス。10エンティティなら70クラス。これが正しいかどうかを判断する軸がないまま、クラス数だけが増えていきます。
Service が薄くなりすぎる
@Service
@RequiredArgsConstructor
public class UserService {
private final UserCreator creator;
public User create(CreateUserRequest request) {
return creator.create(request); // ただ委譲するだけ
}
}
@Component
@RequiredArgsConstructor
public class UserCreator {
private final UserValidator validator;
private final UserMapper mapper;
private final UserRepository repository;
public User create(CreateUserRequest request) {
validator.validate(request);
UserEntity entity = mapper.toEntity(request);
return repository.save(entity);
}
}
UserService は何もしていません。UserCreator に委譲するためだけに存在しています。呼び出しスタックはこうなります。
Controller
→ UserService // 委譲するだけ
→ UserCreator // 本体
→ UserValidator // バリデーション
→ UserMapper // 変換
→ UserRepository // 永続化
このうちどれかを変更したとき、影響範囲を即座に把握できますか。
Mapper が乱立する
UserMapper
UserEntityMapper
UserResponseMapper
UserDtoMapper
UserConversionService
どこで何を変換しているのか、追わないとわかりません。しかもどれも単体では「小さくて正しい」クラスです。
ドメインモデルが存在しない
最も深刻な問題がこれです。
AIはフレームワーク中心の構造を優先します。
Controller → Service → Repository
この3層は常に存在しますが、ドメインモデルが存在しない ことが多いです。
結果として、ビジネスロジックがどこに行くかというと。
@Service
public class OrderService {
// ここにロジックが入りはじめる
}
@Component
public class OrderHelper {
// あふれてきたロジックがここにも入る
}
@Component
public class OrderUtil {
// さらにここにも入る
}
Helper と Util の乱立は、ドメインモデル不在のサインです。
4. なぜ発生するのか
AIは「局所最適」が得意だから
AIは与えられたコンテキストの中で最適なコードを生成します。
- メソッドが長い → 分割する
- クラスが大きい → 分割する
- 重複がある → 共通化する
いずれも正しい判断です。しかしAIはシステム全体のアーキテクチャを維持する主体ではありません。
プロンプト単位、ファイル単位で最適化されたコードの集積が、システム全体の構造を壊すことがあります。リファクタリングは得意でも、設計判断は人間が行う必要があります。
「きれいに見える」から問題に気づかない
スパゲッティコードはコードレビューで検出できます。巨大なクラスを見れば「これはまずい」とわかるからです。
カッペリーニコードは検出できません。小さくて整ったクラスを見ても、問題があるとは気づきにくい。問題はクラス単体ではなく、クラス間の関係と全体構造にある からです。
AIの生成サイクルが問題を加速させる
AIを使った開発では、コード生成のスピードが上がります。これは良いことですが、設計を考える時間が相対的に減るという副作用があります。
生成→動作確認→次の生成、というサイクルが繰り返されると、いつの間にかクラス数だけが膨れ上がります。
5. 問題点
変更影響が追えない
処理フローが多数のクラスに分散しているため、仕様変更の影響範囲を特定するのに時間がかかります。
設計意図が読めない
なぜこのクラスが存在するのか、誰がこれを呼ぶのか、このロジックはどこに属すべきなのか。コードを読んでも答えが見つかりません。
テストが書きにくい
クラスが細かく分割されていると、統合テストと単体テストの境界が曖昧になります。何をテストすべきかが不明確になります。
新規参入者が迷子になる
プロジェクトに新しく入った開発者が、処理を追うために10クラス以上を横断しなければならない状況は、スパゲッティコードと同等かそれ以上の参入コストです。
おわりに
AIはコードを書く。人間は構造を守る。
AI時代の設計で重要なのは、コードの美しさではなくシステムの構造です。
カッペリーニコードという概念は、「きれいなコード ≠ 良い設計」 というシンプルな事実を改めて問い直すためのヒントです。
次回は、カッペリーニコードをどう防ぐか、設計の観点から具体的なアプローチを書きます。
シリーズ一覧
- AI時代にJava設計はどう変わったのか
- カッペリーニコードとは何か
- Java17/21は設計をどう変えたか
- AIは設計できるのか
- AI時代の設計ガードレール