# DevJob 코드 리뷰 & 리팩터링 체크리스트 (EP46)

포트폴리오 제출 전 PR 리뷰어 관점에서 점검하는 10가지 체크리스트.
DevJob 코드베이스에서 각 항목이 어디에 적용되어 있는지 함께 표시한다.

| # | 체크리스트 | 적용 현황 |
|---|---|---|
| 1 | Entity를 API 응답으로 직접 반환하지 않는다 (DTO 변환) | 모든 컨트롤러가 `XxxResponse` record를 반환 (EP16) |
| 2 | 비밀번호는 BCrypt로 암호화 | `PasswordEncoder`(BCrypt) — `MemberService.join`, `AuthService.login` (EP19) |
| 3 | `@Transactional`은 쓰기 메서드에만, 클래스 기본은 `readOnly = true` | 모든 Service `@Transactional(readOnly = true)` + 쓰기 메서드만 `@Transactional` |
| 4 | 의미 있는 변수/메서드명 사용 (축약어·매직넘버 지양) | `MAX_FILE_SIZE`, `SLOW_THRESHOLD_MS` 등 상수화 |
| 5 | 핵심 로직에 테스트 존재 | `src/test` — Member/Job/Application/Auth 등 MockMvc + H2 테스트 (EP18) |
| 6 | `Optional.get()` 대신 `orElseThrow()` + 의미 있는 예외 | 전 Service에서 `orElseThrow(() -> new BusinessException(ErrorCode.XXX))` (EP10~) |
| 7 | 상태값은 문자열 하드코딩 대신 Enum 비교 | `JobStatus`, `ApplicationStatus`, `Role` enum + `==` 비교 |
| 8 | Setter 난발 대신 Builder/의미 있는 변경 메서드 | `Job.changeStatus()`, `Application.cancel()`, `Member.changeRole()` 등 |
| 9 | 올바른 HTTP 상태코드 (등록 201, 삭제 204 등) | `JobController.create` → 201+Location, `delete` → 204 (EP46 리팩터링 후) |
| 10 | 횡단 관심사(로깅/실행시간/캐싱)는 AOP·이벤트로 분리 | `ServiceLoggingAspect`(EP43), `ApplicationEventListener`(EP44) |

## Before / After 예시: 지원하기 API

```java
// ❌ Before
@PostMapping("/apply")
public ResponseEntity apply(Long jobId, Long memberId) {
    Job j = jobRepo.findById(jobId).get();        // (6) Optional.get()
    if (j.getStatus().equals("OPEN")) {            // (7) 문자열 하드코딩
        Application a = new Application();
        a.setMemberId(memberId);                   // (8) Setter 난발
        a.setJobId(jobId);
        a.setStatus("PENDING");
        appRepo.save(a);
    }
    return ResponseEntity.ok().build();            // (9) 200 (등록인데 OK)
}
```

```java
// ✅ After (DevJob 실제 구현 — ApplicationController/ApplicationService)
@PostMapping("/{jobId}/apply")
public ResponseEntity<ApiResponse<ApplicationResponse>> apply(
        @PathVariable Long jobId,
        @AuthenticationPrincipal CustomUserDetails user) {
    ApplicationResponse result = applicationService.apply(user.getId(), jobId);
    return ResponseEntity.status(HttpStatus.CREATED)
            .body(ApiResponse.ok(result));         // 201 Created
}
```

## 자주 나오는 실수와 해결

- **Entity 직접 반환 → 무한루프**: 양방향 연관관계를 그대로 직렬화하면 순환 참조가 발생한다. 항상 DTO로 변환한다.
- **`Optional.get()` NPE**: 빈 Optional에서 `get()`을 호출하면 `NoSuchElementException`. 항상 `orElseThrow(() -> new BusinessException(ErrorCode.XXX))`로 의미 있는 예외를 던진다.
- **문자열 상태 비교**: `"OPEN".equals(status)`는 오타에 취약하고 컴파일 시점에 잡히지 않는다. `JobStatus.OPEN == status`처럼 Enum으로 비교한다.
