끝을 보는 용기

Day 091 - Spring 심화 프로젝트 2단계 완료, 구현하고 싶은 기능은 참 많은데

writingforever162 2025. 1. 5. 23:52

1. 프로젝트 진행 상황 및 계획[깃허브 링크]

🥇 Spring 심화 프로젝트 도전 과제 6단계 끝내기 (진행 중, 25.01.06 완료 목표)

🥈 Spring 심화 프로젝트 리드미(README) 작성하기 (진행 전, 25.01.06 완료 목표)

🥉 Spring 심화 프로젝트 필수 과제 2단계 끝내기 (완료)

 

2. 한꺼번에 코드 정렬하기 (맥북(MAC OS) 기준)

먼저 패키지를 한 번 클릭하고 command + option + L을 누르면 Reformat Code 창이 뜬다. 팀에서는 우선 불필요한 import를 한꺼번에 제거하고 줄 맞춤 또한 한꺼번에 진행하고자 두 가지를 선택했다. 이렇게 하면 일일이 파일을 눌러서 다듬을 때 드는 시간과 노력을 아낄 수 있다.

 

3. 회고

구현하고 싶은 기능은 참 많은데 시간이 부족하다. 정확히는 시간 안에 구현할 실력이 아직 한참 부족하다. 머릿속 구상과 실제 실력 사이에 생긴 거리가 길수록 괴리감이라고 해야 할까, 실력이 얼마나 부족한지 확 느껴지곤 한다. 집채만 한 크기에 투명한 파도를 철썩 소리 나게 정통으로 맞은 듯이.

 

사실 도전 단계 과제 안 해도 뭐라 할 사람은 아무도 없다. 여태까지 제출한 개인 과제 점수는 팀 배정에 영향을 줄 뿐이라서. 특히 이번에 주어진 과제 6단계 중 필수 과제인 1단계는 기존에 제출한 일정 관리 앱을 보완하기였고, 2단계는 '유효성 검사 추가', '예외 처리 강화', '서비스의 도메인 로직(domain logic)을 엔티티(entity)에 위임하기' 중 2가지 시도하기였다. 필수 과제만 했으면 하루 안에 끝내고 여유로운 한 주를 보낼 수 있었다. 이 점을 너무나도 잘 아는 지금, 해보고 싶은 기능이 머릿속을 떠날 줄 몰라서 도전 과제 6단계를 붙잡는 중이고.

 

정말 쉬엄쉬엄하는 한 주가 고팠지만, 좀처럼 포기할 용기가 나지 않았다.

 

해보고 싶은 게 참 많은지라, 프로젝트를 진행하는 내내 잠을 아낄 듯싶다.

 

지금 많이 헤매야 나중에 헤매지 않을 거라는 욕심에 과제며 머리를 부여잡은 채 한숨 쉬며 뜯어고치는 중이다. 언제쯤 직접 작성한 코드에 만족하는 날이 오려나.

더보기
package com.example.plan.exception;

public class ErrorMessage {

  public static final String MEMBER_NOT_FOUND = "Member does not exist";
  public static final String PLAN_NOT_FOUND = "Plan does not exist";
  public static final String COMMENT_NOT_FOUND = "Comment does not exist";
  public static final String EMAIL_NOT_MATCH = "Email does not Match";
  public static final String PASSWORD_NOT_MATCH = "Password does not match";
  public static final String DATA_ALREADY_DELETED = "Requested data has already been deleted";
  public static final String INVALID_PATH = "Please check input path";

  public static final String ERROR_MEMBER_NOT_FOUND = "ERROR_MEMBER_NOT_FOUND";
  public static final String ERROR_PLAN_NOT_FOUND = "ERROR_PLAN_NOT_FOUND";
  public static final String ERROR_COMMENT_NOT_FOUND = "ERROR_COMMENT_NOT_FOUND";
  public static final String ERROR_EMAIL_NOT_MATCH = "ERROR_EMAIL_NOT_MATCH";
  public static final String ERROR_PASSWORD_NOT_MATCH = "ERROR_PASSWORD_NOT_MATCH";
  public static final String ERROR_DATA_ALREADY_DELETED = "ERROR_DATA_ALREADY_DELETED";
  public static final String ERROR_INVALID_PATH = "ERROR_INVALID_PATH";
  public static final String ERROR_INVALID_INPUT = "ERROR_INVALID_INPUT";
}
더보기
package com.example.plan.exception;

public class AlreadyDeletedException extends RuntimeException {

  public AlreadyDeletedException(String message) {
    super(message);
  }
}
package com.example.plan.exception;

public class CommentNotFoundException extends RuntimeException {

  public CommentNotFoundException(String message) {
    super(message);
  }
}
package com.example.plan.exception;

public class EmailMismatchException extends RuntimeException {

  public EmailMismatchException(String message) {
    super(message);
  }
}
package com.example.plan.exception;

public class MemberNotFoundException extends RuntimeException {

  public MemberNotFoundException(String message) {
    super(message);
  }
}
package com.example.plan.exception;

public class PasswordMismatchException extends RuntimeException {

  public PasswordMismatchException(String message) {
    super(message);
  }
}
package com.example.plan.exception;

public class PlanNotFoundException extends RuntimeException {

  public PlanNotFoundException(String message) {
    super(message);
  }
}
더보기
package com.example.plan.exception;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ResponseEntity<Map<String, Object>> handleValidationException(
      MethodArgumentNotValidException e
  ) {

    List<String> errors = new ArrayList<>();

    errors = e.getFieldErrors()
        .stream()
        .map(DefaultMessageSourceResolvable::getDefaultMessage
        )
        .toList();

    return handleException(
        new Exception(String.join(". ", errors)),
        ErrorMessage.ERROR_INVALID_INPUT,
        HttpStatus.BAD_REQUEST
    );
  }

  @ExceptionHandler(Exception.class)
  public ResponseEntity<Map<String, Object>> handleOtherException() {
    return handleException(
        new Exception(ErrorMessage.INVALID_PATH),
        ErrorMessage.ERROR_INVALID_PATH,
        HttpStatus.NOT_FOUND
    );
  }

  @ExceptionHandler(EmailMismatchException.class)
  public ResponseEntity<Map<String, Object>> handleEmailMismatchException(
      EmailMismatchException ex
  ) {
    return handleException(
        ex,
        ErrorMessage.ERROR_EMAIL_NOT_MATCH,
        HttpStatus.UNAUTHORIZED
    );
  }

  @ExceptionHandler(PasswordMismatchException.class)
  public ResponseEntity<Map<String, Object>> handlePasswordMismatchException(
      PasswordMismatchException ex
  ) {
    return handleException(
        ex,
        ErrorMessage.ERROR_PASSWORD_NOT_MATCH,
        HttpStatus.UNAUTHORIZED
    );
  }

  @ExceptionHandler(MemberNotFoundException.class)
  public ResponseEntity<Map<String, Object>> handleMemberNotFoundException(
      MemberNotFoundException ex
  ) {
    return handleException(
        ex,
        ErrorMessage.ERROR_MEMBER_NOT_FOUND,
        HttpStatus.NOT_FOUND
    );
  }

  @ExceptionHandler(PlanNotFoundException.class)
  public ResponseEntity<Map<String, Object>> handlePlanNotFoundException(
      PlanNotFoundException ex
  ) {
    return handleException(
        ex,
        ErrorMessage.ERROR_PLAN_NOT_FOUND,
        HttpStatus.NOT_FOUND
    );
  }

  @ExceptionHandler(CommentNotFoundException.class)
  public ResponseEntity<Map<String, Object>> handleCommentNotFoundException(
      CommentNotFoundException ex
  ) {
    return handleException(
        ex,
        ErrorMessage.ERROR_COMMENT_NOT_FOUND,
        HttpStatus.NOT_FOUND
    );
  }

  @ExceptionHandler(AlreadyDeletedException.class)
  public ResponseEntity<Map<String, Object>> handleAlreadyDeletedException(
      AlreadyDeletedException ex
  ) {
    return handleException(
        ex,
        ErrorMessage.ERROR_DATA_ALREADY_DELETED,
        HttpStatus.CONFLICT
    );
  }

  private ResponseEntity<Map<String, Object>> handleException(
      Exception ex,
      String errorCode,
      HttpStatus status
  ) {
    String errorMessage = ex.getMessage();

    Map<String, Object> errorResponse = new LinkedHashMap<>();
    errorResponse.put("errorCode", errorCode);
    errorResponse.put("errorMessage", errorMessage);

    return new ResponseEntity<>(errorResponse, status);
  }
}

필수 과제 2단계를 할 때 일일이 메시지를 입력했다가 오탈자가 발생할 가능성이 높아 보여서 ErrorMessage라는 클래스(class)를 별도로 만들었다. 사람마다 다를 테지만 일단 내가 '좋다, 나쁘다'라고 생각할 때 기준이 바로 '오탈자가 있느냐, 없느냐'이었기 때문에. 무엇보다 이렇게 직접 입력할 부분이 여러 번 눈에 띄면 꼭 한 곳에서 관리하고 싶다는 마음이 들었다. 이 방향이 맞는지는 확인해야 하겠지만, 우선 머릿속에 그린 대로 구현하는 데에 성공해서 기분이 좋았다. 그러니 또 구현하고 싶은 기능이 생겼으면 어떻게 해야겠는가. 남은 시간 동안 최대한 집중력을 발휘해서 시도해 볼 수밖에.