각종 후기/우아한테크코스

[우아한 테크코스 3기] LEVEL 2 회고 (99일차)

제이온 (Jayon) 2021. 5. 11.

안녕하세요? 제이온입니다.

 

오늘은 중간곰과 마지막 페어 프로그래밍을 진행하였고, 드디어 지긋지긋한 지하철 노선도 관리 미션을 제출하였습니다. 물론, 아직 앞으로 심화된 버전의 지하철 노선도 관리 미션 요구 사항이 추가될 예정입니다.

 

 

페어 프로그래밍

어제 구간 등록을 모두 구현하였는데, 아무래도 SectionService와 Sections에 많은 로직이 몰려있다보니까 마땅히 리팩토링을 하기 어려워서 오늘로 미뤘습니다. 그런데, 오늘 중간곰과 만나보니까 중간곰이 상당히 많은 부분을 리팩토링해 와 주셨습니다. 가장 큰 변화점은 SectionController와 SectionService를 제거하고 해당 로직을 Line과 LineService들로 로직을 이동한 것입니다. 이를 통해, 리팩토링이 잘 되지 않을 때는 특정 도메인에 책임을 너무 부여하지 않았나 고민해 봐야겠다는 생각이 들었습니다.

 

놀랍게도 80줄 정도 되었던 코드가 Line과 LineService에 로직을 부여하니까 등록과 삭제 등의 메소드가 깔끔하게 분리가 되었습니다. 오늘의 핵심 주제였던 '리팩토링'이 이렇게 생각보다 간단히 해결되었죠. 물론, 중간곰은 이를 위해서 많은 고민을 하셨을 것으로 예상됩니다.

 

 

그 외에는 누락된 단위 테스트와 E2E 테스트를 추가하였는데, 특히 Mock을 사용한 통합 테스트가 오늘도 저를 힘들게 하였습니다. 그래도 여러 번 고통을 받고 나니까 실력이 좀 늘은 듯합니다.

 

 

    @DisplayName("구간을 등록한다.")
    @Test
    void addSection() {
        final Line line = new Line(1L, "2호선", "black");
        final Section sectionA = new Section(1L, 1L, 2L, 4L, 10);
        final Section sectionB = new Section(2L, 1L, 4L, 6L, 10);
        final List<Section> sectionGroup = Arrays.asList(sectionA, sectionB);

        final Long lineId = 1L;
        final SectionRequest sectionRequest = new SectionRequest(2L, 3L, 7);
        given(lineDao.findById(1L)).willReturn(Optional.of(line));
        given(sectionDao.findByLineId(1L)).willReturn(sectionGroup);
        given(stationService.findById(anyLong())).willAnswer(invocation -> {
            final Long id = invocation.getArgument(0);
            return new Station(id, "역" + id);
        });

        final Section expectedSection = new Section(
            10L, lineId, sectionRequest.getUpStationId(), sectionRequest.getDownStationId(), sectionRequest.getDistance()
        );

        given(sectionDao.save(sectionRequest.toEntity(lineId))).willReturn(expectedSection);

        final SectionResponse sectionResponse = lineService.addSection(lineId, sectionRequest);
        assertThat(sectionResponse.getId()).isEqualTo(expectedSection.getId());

        verify(lineDao, times(1)).findById(1L);
        verify(sectionDao, times(1)).findByLineId(1L);
        verify(stationService, times(3)).findById(anyLong());
        verify(sectionDao, times(1)).update(any(Section.class));
        verify(sectionDao, times(1)).save(any(Section.class));
    }

 

 

이제는 어느 정도 요령이 생겼습니다. void 타입의 메소드는 given ~ willReturn을 통한 작업을 해 줄 필요없이 단순 실행 여부만 확인하면 되고, 하나의 메소드에 대해서 여러 개의 인자를 대입하는 willAnswer() 방식도 알게 되었습니다. 또한, 임의의 파라미터를 넘기는 anyLong() 등, 이제는 대부분의 상황에 대해서 Mocking을 할 수 있게 되었습니다. 물론, 자세한 원리는 아직까지 이해하기 쉽지 않으나, 사용 방법 자체는 익힐 수 있어서 상당히 의미가 있었습니다.

 

 

마지막으로, 예외 처리는 아직도 고민이 많습니다. 각 계층별로 익셉션을 나누는 방법도 잘 모르겠고, 익셉션에 Spring에서 제공하는 상태 코드를 갖지 말라는 피드백을 받아서 어떻게 해야 할지 난감한 상태입니다. (사실은, Spring에서 제공하는 상태 코드를 무조건 사용하지 말라는 것인지는 또 잘 모르겠습니다.)

 

그래서 404와 400 에러코드를 사용하는 에러를 구분하였고, 또한 그 안에서도 에러에 대한 계층을 나누어 주었습니다. 

 

 

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ClientRuntimeException.class)
    public ResponseEntity<String> handleClientException(final ClientRuntimeException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
    }

    @ExceptionHandler(DuplicatedNameException.class)
    public ResponseEntity<String> duplicatedNameExceptionHandler(final DuplicatedNameException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
    }

    @ExceptionHandler(DataNotFoundException.class)
    public ResponseEntity<String> dataNotFoundExceptionHandler(final DataNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
    }

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<String> handleServerException(final RuntimeException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
    }
}

 

 

결과적으로 해당 코드와 같이 Dao에서 사용하는 DuplicatedNameException과 DataNotFoundException을 나누었고, 나머지 도메인과 서비스에서 사용하는 에러는 ClientRuntimeException으로 묶었습니다. 이 부분은 다시 김고래에게 여쭈어 볼 계획입니다.

 

 

정리

오늘 가까스로 PR 및 리뷰 요청을 보냈고, 저녁에는 한강에 가서 술을 마셨습니다. 내일부터 미션 시작인데 다시 힘내서 개발을 해야겠습니다.

댓글

추천 글