何行で分ける? 意味で、境目を作る。 短さより、読みやすさと同じ動作。

広告 設計・テスト・レビュー

クラスやメソッドは何行で分ける?CSV取込を例に、切る場所を決める

レビューで「このメソッド、長いので分けて」と言われた。とりあえず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)

※Amazonのリンクにはアフィリエイトを含みます。

次に長い処理を直すときは、行を切る前に「読み込み・変換・保存」などの仕事を書き出してください。境目を決め、同じ失敗条件でも同じ動作になるか確かめる。それが分割の最初の一歩です。

-設計・テスト・レビュー
-,