Java | 例外と早期returnの組み合わせテンプレート

Java Java
スポンサーリンク

例外と早期 return の組み合わせテンプレート(業務クラス構造つき)」は、
実務で“安全で読みやすくメンテしやすい”コードを書くための基本骨格です。

ここでは、3層構造(Controller/Service/Repository)に沿って、
「早期return」と「例外処理」をどう共存させるかを図とコードで解説します。


1. 全体アーキテクチャ図

┌────────────────────────────┐
│ Controller:入力と結果の橋渡し                     │
│  ├─ バリデーション → 早期return                 │
│  ├─ 想定外エラー → 例外catch                    │
│  └─ レスポンスを返す                             │
├────────────────────────────┤
│ Service:業務ロジック中心                         │
│  ├─ 不正データ → 業務例外throw                  │
│  ├─ DBエラー → システム例外throw                │
│  └─ 正常ならreturn                              │
├────────────────────────────┤
│ Repository:DBアクセス層                          │
│  └─ SQLExceptionを捕捉→変換throw               │
└────────────────────────────┘

2. クラス構造イメージ

com.example.app
├─ controller
│   └─ UserController.java
├─ service
│   └─ UserService.java
├─ repository
│   └─ UserRepository.java
└─ exception
    ├─ ValidationException.java
    ├─ BusinessException.java
    └─ SystemException.java

3. コントローラー層(早期return中心)

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;
    private final Logger log = LoggerFactory.getLogger(getClass());

    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody User user) {
        // --- ① 早期returnで入力チェック ---
        if (user == null || user.getName() == null || user.getName().isEmpty()) {
            return ResponseEntity.badRequest().body("名前を入力してください");
        }

        try {
            // --- ② 正常処理 ---
            userService.register(user);
            return ResponseEntity.ok("登録完了");

        } catch (ValidationException e) {
            // --- ③ 想定内(業務例外) ---
            log.warn("バリデーションエラー: {}", e.getMessage());
            return ResponseEntity.badRequest().body(e.getMessage());

        } catch (SystemException e) {
            // --- ④ 想定外(システム障害) ---
            log.error("システムエラー", e);
            return ResponseEntity.internalServerError().body("内部エラーが発生しました");
        }
    }
}
Java

🟢 ポイント

  • 「入力不備」は例外ではなく 早期return
  • 「業務ロジック上の不正」は 例外でthrow
  • Controllerは「レスポンスに変換」する役割に専念

4. サービス層(業務例外・システム例外)

@Service
public class UserService {

    private final UserRepository userRepository;

    public void register(User user) {
        // --- ① 業務チェック ---
        if (userRepository.existsByName(user.getName())) {
            throw new ValidationException("同じ名前のユーザーが既に存在します");
        }

        try {
            // --- ② データ登録 ---
            userRepository.insert(user);

        } catch (SQLException e) {
            // --- ③ システム例外に変換して上位へ ---
            throw new SystemException("DB登録に失敗しました", e);
        }
    }
}
Java

🟡 ポイント

  • 業務ロジック違反は ValidationException(想定内)
  • システム障害は SystemException(想定外)
  • 呼び出し元(Controller)がキャッチして処理を分ける

5. リポジトリ層(低レベル例外を変換)

@Repository
public class UserRepository {

    private final JdbcTemplate jdbcTemplate;

    public void insert(User user) throws SQLException {
        jdbcTemplate.update(
            "INSERT INTO users (name, email) VALUES (?, ?)",
            user.getName(), user.getEmail()
        );
    }

    public boolean existsByName(String name) {
        String sql = "SELECT COUNT(*) FROM users WHERE name = ?";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class, name);
        return count != null && count > 0;
    }
}
Java

⚙️ ポイント

  • DBアクセスは例外を上位へスロー(Serviceでキャッチして再throw)
  • Repositoryは「例外を包んで投げる」層ではなく「実行担当」

6. 例外クラス設計例

// 入力や業務ルール違反
public class ValidationException extends RuntimeException {
    public ValidationException(String message) { super(message); }
}

// ビジネスロジック全般の例外
public class BusinessException extends RuntimeException {
    public BusinessException(String message) { super(message); }
}

// システム障害(DB, IO等)
public class SystemException extends RuntimeException {
    public SystemException(String message, Throwable cause) { super(message, cause); }
}
Java

💡 ポイント

  • 意味のある例外名で分類する(何が想定内・外か明確化)
  • RuntimeExceptionを継承して煩雑なthrows宣言を省略

7. ベストプラクティスまとめ表

想定内エラー想定外エラーreturn / throw 方針
Controller入力ミス・空値などreturn(早期)
Service業務ロジック違反DB障害などthrow
RepositorySQL例外などthrow(SystemException)

8. 図で理解:return と throw の流れ

Controller
 ├─ 入力NG → return (400)
 └─ 正常入力 → Service呼出
       ├─ 業務NG → throw ValidationException → Controllerで400
       ├─ DB障害 → throw SystemException → Controllerで500
       └─ 正常 → return OK

9. 実務テンプレート(雛形まとめ)

if (入力NG) return 400;

try {
    service.処理();
    return 200;
} catch (ValidationException e) {
    return 400;
} catch (SystemException e) {
    return 500;
}
Java

「早期return」+「例外伝播」の明確分離が安全設計の基本。

Java
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました