レビューで「このメソッド、長いので分けて」と言われた。とりあえず30行ずつ切ったら、途中の変数を大量に引数へ渡すことになり、前より追いづらくなった。
この記事は、長い処理を任され、分割する境目に迷っているJava開発者向けです。行数のルールを増やすのではなく、入力・出力・失敗が説明できる仕事のまとまりを見つけます。
行数は見直すきっかけ。切る場所は処理の意味で決める。
分割後の呼び出し元を読んで、何をしているか分かること。途中の状態を追うために、あちこち往復しなくて済むことを確認します。
CSV取込には、別の理由で変わる処理が入っている
例えばアカウント一覧を取り込む処理には、ファイルの読み込み、CSV解析、入力確認、保存、結果表示が入ります。
| まとまり | 変わる理由の例 |
|---|---|
| CSV解析 | 引用符や文字コードなど、入力形式の変更 |
| アカウントの入力確認 | 必須項目や契約数の業務ルール変更 |
| 保存 | 保存先や一括保存方式の変更 |
| 結果の表示 | 画面・通知の表現変更 |
この段階で四つのクラスを作る必要はありません。まず違う判断が混ざっている場所を見つけます。今回なら、CSV解析後の行をアカウントへ変換する部分と、保存する部分を区別できます。
行ごとの変換を名前で表し、保存の順序を残す
次の例はCSVを解析済みの行を受け取ります。列はメールアドレスと契約数の二つ。CSVパーサーや実際のDB処理は、分割の論点から外しています。
record Account(String email, int seats) {}
static Account toAccount(String[] row) {
if (row.length != 2 || row[0].isBlank()) {
throw new IllegalArgumentException("列数またはメールが不正");
}
int seats = Integer.parseInt(row[1]);
if (seats < 1) {
throw new IllegalArgumentException("契約数は1以上");
}
return new Account(row[0], seats);
}
static void importAccounts(
java.util.List<String[]> rows,
java.util.function.Consumer<java.util.List<Account>> save) {
var accounts = new java.util.ArrayList<Account>();
for (String[] row : rows) {
accounts.add(toAccount(row));
}
save.accept(accounts);
}呼び出し元には「全行をアカウントへ変換してから保存する」という順序が残りました。toAccountの入力は一行、出力はAccount、不正な場合は例外です。ループの途中変数をいくつも受け渡していません。
この例では、配列や各要素はnullでない前提です。メール書式の完全な検証もしていません。実務の入力仕様へ広げる際は、その条件も追加します。
分割のついでに、動作を変えない
上の処理は、すべての行を変換してから保存を呼びます。もし短くするため次のように順序を変えると、意味も変わります。
変更前:全行を確認 → まとめて保存
別の動作:一行を確認 → その行を保存 → 次の行へ後者では、二行目で入力エラーになったとき、一行目の保存を呼び出した後かもしれません。「メソッドを分けただけ」のつもりで、部分的に進むタイミングを変えてはいけません。
なお、まとめて保存を一回呼ぶことと、DBへの書き込みが原子的であることは別です。保存の内部やトランザクションの保証は、その実装で確認します。
分割前後は、正常系だけで比べない
今回の例なら、次の条件を同じデータで確かめます。
| 条件 | 変えてはいけない観察結果 |
|---|---|
| 二行とも正常 | 同じ順序・値で、一回の保存へ渡す |
| 二行目のメールが空 | 保存を呼ぶ前に失敗する |
| 契約数が数値でない | 数値変換の失敗を返し、保存しない |
| 契約数が0 | 入力の失敗を返し、保存しない |
| 列数が足りない | 入力の失敗を返し、保存しない |
| 空の一覧 | この例では空の一覧で保存を一回呼ぶ |
この六条件は、分割前の一つのメソッドと分割後のコードをJava 17.0.8で比較しています。実DBやCSVファイルの解析を試した検証ではありません。
例外を出す場所や保存の呼び出し回数も確認対象です。戻り値が同じというだけでは、整理前と同じ動作とは言い切れません。
メソッドからクラスへ分けるのは、独立した役割が見えてから
toAccountを別クラスへ移すかは、行数より役割で考えます。変換規則が大きくなり、複数の入口で同じ規則を使うなら、アカウント変換の担当として分ける候補です。
反対に、短い変換一つをHelperへ移しても、何の責任を持つのか曖昧な置き場が増えるだけです。「汎用化したい」より、名前で担当を説明できるかを先に見ます。
次の兆候があれば、切り方を見直してください。
- 途中のフラグやカウンターを大量に引数へ渡す。
- 呼ぶ順番を間違えると壊れる小さなメソッドが並ぶ。
- 名前を読んでも内容が分からず、全部開き直す。
チームに行数の規約があるなら守りつつ、こうした具体的な不都合を材料に相談します。30〜40行はJavaの制限でも、読みやすさを保証する数値でもありません。
名前と責務の判断を広げる一冊
『改訂新版 良いコード/悪いコードで学ぶ設計入門』は、名前、関心の分離、依存関係など、分割する理由を具体例で考えたい人向けです。出版社の目次から、今悩んでいる章を選べます。
※Amazonのリンクにはアフィリエイトを含みます。
次に長い処理を直すときは、行を切る前に「読み込み・変換・保存」などの仕事を書き出してください。境目を決め、同じ失敗条件でも同じ動作になるか確かめる。それが分割の最初の一歩です。
