주인장은 요즘 리팩토링에 빠졌습니다.
아침에 일어나서 자기 전까지 하루종일 리팩토링만 하고 있는 것 같아요(거짓말입니다)
제 코드 및 다른 팀원들의 코드를 내맘대로 리뷰하는 중 여러 Handler를 사용하면 코드가 더 깔끔하고 유지보수가 편해질 것 같아 상의도 없이 만들고 PR날린 그것,
ErrorHandler, Validator
기존 코드를 봅시다
@Service
@RequiredArgsConstructor
public class BattleServiceImpl implements BattleService {
//생략
@Override
public void registBattle(BattleInviteRequest battleInviteRequest, User user) {
//생략
if (battleInviteRequest.getOppositeUserId() == user.getId()) {
throw new IllegalArgumentException("Not valid user");
}
if (battleInviteRequest.getTime() <= 0) {
throw new RuntimeException("Wrong time setting");
}
아...
진짜 아... 싶죠? 분명 던지는 에러들이 서버 상 오류가아닌 비지니스로직 적 이루어질 수 있는 에러인데, 저렇게 서비스단에 2~3줄 심지어 if문으로 메서드 안에 넣으면...... 나중에 혹시라도 수정하게되거나 로직을 변경해야 되는 일이 생겼을 시 굉장히 귀찮아지겠죠?ㅎㅎ....
여기서 제가 생각해낸 방법은
1. 체크해야되는 조건들을 하나의 파일로 묶는다.
2. 오류 결과들을 하나의 파일에 정리한다
3. 던진다!
의 과정입니다.
먼저, 저 말도안되는 Not a valid user과 Wrong time setting을 과감히 지우고, "ErrorCode"의 Enum을 만들었습니다.
@Getter
@AllArgsConstructor
public enum ErrorCode {
INVALID_BATTLE_TIME("최소 10분부터 최대 60분까지 설정 가능합니다."),
INVALID_USER("유저 설정이 유효하지 않습니다."),
INVALID_BATTLE_STARTTIME("시작 시간은 최소 60분 후부터 가능합니다."),
INVALID_OPINION_LENGTH("선택지는 최대 16자까지 작성 가능합니다."),
DUPLICATED_BATTLE("이미 해당시간에 예정된 배틀이 존재합니다."),
ENDED_BATTLE("응답 기간이 종료된 배틀입니다.");
private final String message;
}
Wrong time setting보다 저렇게 친절하게 message를 적어준다면 프론트분들도 처리하기 더더욱 쉽겠죠?ㅎㅎ
public class CustomException extends RuntimeException {
private final ErrorCode errorCode;
public CustomException(ErrorCode errorCode) {
super(errorCode.name());
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
그리고, 저 errorcode를 throw할 수 있는 custom exception을 정의해줍니다.
여기서 잠깐! RuntimeException을 extends하는 이유!
왜 하필 많은 오류 중 Runtime exception일까! 저는 굉장히 궁금했습니다. 사실 이름만 보면 그냥 Exception이 더 일반적으로 보이는데 쓰면 계속 throw를 하래잖아요! 그래서 패트병 대신 텀블러로 커피를 1회 마시고 GPT선생님께 여쭈어봤죠.
RuntimeException과 IllegalArgumentException과 같이 평소에 우리가 사용해도 try-catch 등을 쓰지 않아도 되는 Exception들은 Unchecked Exception이라고 합니다! 그에 반해, 일반 Exception은 Checked Exception이라고 try-catch나 throws를 해야된다고 하네요.
그래서 아무튼 Exception이 아닌 것들 중 가장 무난한 RuntimeException을 채택하였습니다.
그 후 원래 제가 만들어놨던 GlobalExceptionHandler 안에 제 작고 소중한 CustomException도 추가해줬어요...><
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ApiResponseDto> handleRuntimeException(RuntimeException error) {
ApiResponseDto res = new ApiResponseDto("fail", error.getMessage(), null);
return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
}
//생략
@ExceptionHandler(CustomException.class)
public ResponseEntity<ApiResponseDto<String>> handleCustomException(CustomException error) {
ApiResponseDto<String> response = new ApiResponseDto<>("fail", error.getErrorCode().getMessage(), null);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
}
첫번째 메서드가 제가 직접 사용하던 RuntimeException 이고요, 밑이 제가 이제 사용하는 CustomException입니다!!
사실 기능상의 차이는 없어요~! RuntimeException도 message를 반환하고, 제 custom handler도 완전 같은 리턴타입이기에..
하지만, 귀엽잖아요.
다꾸, 폰꾸에 이어 에꾸(에러 꾸미기)까지... 즐겁지않나요?><
그리고 이제 Error을 Throw해주는데 사용될 Validator을 만들어줍니다~
@Component
@AllArgsConstructor
public class BattleValidator {
static Date now = new Date();
static Calendar calendar = Calendar.getInstance();
private final BattleRepository battleRepository;
public void validateOppositeUser(BattleInviteRequest battleInviteRequest, User user) {
if (battleInviteRequest.getOppositeUserId() == user.getId()) {
throw new CustomException(ErrorCode.INVALID_USER);
}
}
public void validateTime(int minutes) {
if (minutes < 10) {
throw new CustomException(ErrorCode.INVALID_BATTLE_TIME);
}
}
아까랑 똑같은 코드에 똑같은 결과를 반환하는데, 이렇게 설정하는것이 훨씬 간지작살나지 않나요?
이제 서비스단에 이 Validator 내 메서드를 적용하면...!!
@Transactional
@Override
public void registBattle(BattleInviteRequest battleInviteRequest, User user) {
//생략...
battleValidator.validateOppositeUser(battleInviteRequest, user);
battleValidator.validateTime(battleInviteRequest.getTime());
battleValidator.validateStartTime(battleInviteRequest.getStartDate());
battleValidator.checkOtherBattles(user, battleInviteRequest.getStartDate(), endDate);
}
TADA!!!!!
나만의 귀여운 코꾸(코드꾸미기...), 완성!!!
아까 말도안되는 if문과 비교해보면,, 훨씬 귀엽고 깜찍해졌죠?
이렇게 작성한 코드는 후에 다른사람이 보았을 때도 이해하기 쉽고, 유지보수도 용이해집니다~
사실 저는 1년전 이맘때쯤, 굉장한 홍머병에 걸렸었는데요,
그때는 플젝때도 남들이 내 코드를 이해하기 어렵고, 건드리지 못하는 것이 뭔가 고수같고 개짱짱맨 코드인 줄 알았답니다...
하하 코드는 무조건 간결!!해야하고 모듈화가 잘 되어있어야합니다~
이거는 알림 기능에서도 적용할 수 있는데요!
어떠한 알림을 보낼 지 프론트님님님들과 상의 후
public enum NotificationType {
BATTLE_REQUEST(0, "%s님이 배틀을 신청했어요! 지금 바로 확인해보세요."),
LIVE_NOTICE(1, "[%s] 라이브 시작 5분 전입니다!"),
BATTLE_ACCEPT(2, "[%s] 배틀이 승낙되어 대기중입니다."),
BATTLE_DECLINE(3, "[%s] 배틀이 거절되었습니다."),
BATTLE_UNSATISFIED(4, "[%s] 배틀이 인원수를 충족하지 못해 모닥불로 이동하였습니다."),
BATTLE_SATISFIED(5, "[%s] 배틀이 인원수를 충족하여 불구경 대기중입니다.");
//todo 인원 수 미달 -> 모닥불 (참여자, 개최자)
//todo 인원 수 충족 -> 라이브 개최 (참여자, 개최자)
private final int code;
private final String messageTemplate;
NotificationType(int code, String messageTemplate) {
this.code = code;
this.messageTemplate = messageTemplate;
}
public int getCode() {
return code;
}
public String getMessageTemplate() {
return messageTemplate;
}
}
이렇게 enum으로 정리해놓으며, 추가/수정하면 편하답니다~
그럼 여기서 퀴즈! 알림 메서드들의 문구들을 어떻게하면 더 그럴싸하게 작성할 수 있을까요?(제발도와주세요 저거 넘 구린거같아)
그럼 담에 또봐요 안뇽~~~
'개발일지' 카테고리의 다른 글
[개발일지01] Pagination이란 무엇인가? (0) | 2024.07.28 |
---|