1차적으로 프로젝트 MVP 개발을 마무리하고,
리팩토링 작업에 들어갔다.
작성했던 코드를 다시한번 살펴보는데
머리가 아파오기 시작했다.
짧은 기간동안 MVP 개발에만 집중했던 탓인지
가독성이 떨어지고, 중복되는 코드가 많고, 네이밍 규칙도 존재하지 않는 등
많은 문제가 보이는 내 코드들을 보니
마음이 아파왔다.
그래서 코드의 효율성과 가독성을 위해서 리팩토링을 하기로 마음먹었다.

문제점 발견
코드를 보다보니 내 눈에 들어온 것은
미래를 생각하지 않은 if문 사용이었다.
//댓글 달기
public void commentService(CommentRequestDTO commentRequestDTO, CustomUserDetails customUserDetails){
String postId = commentRequestDTO.getPostId(); //게시물 trackingId;
Integer commentClass = commentRequestDTO.getCommentClass(); //댓글(0), 대댓글(1) 여부
String parentId = commentRequestDTO.getParentId(); //부모 댓글 trackingId 값
//postId가 실제로 존재하는 게시글인지 검사필요. (feignClient)
//존재하지 않는다면 예외처리
if(!postClient.isValidFeed(postId).result()){
throw new BaseException(POST_NOT_FOUND);
}
//commentClass 가 1(대댓글) 이라면, parentId가 null 이 아닌지 검사,
if(commentClass==1 && parentId.isEmpty()){
throw new BaseException(PARENT_COMMENT_NOT_FOUND);
}
//대댓글이고, parentId가 null이 아니라면, 실제로 해당 댓글(parentId)이 존재 하는지 체크
if(commentClass == 1){
commentRepository.findComment(postId, UUID.fromString(parentId)).orElseThrow(()->new BaseException(PARENT_COMMENT_NOT_FOUND));
}
//만약 0(댓글) 이라면 parentId 값은 null 이어야 함. (여기서 전부 유효성 체크)
if(commentClass==0 && !parentId.isEmpty()){
throw new BaseException(PARENT_COMMENT_CANNOT_HAVE_COMMENT);
}
//모든 유효성 체크가 마무리 된 후, 댓글 등록
Comment comment = Comment.from(commentRequestDTO);
comment.setUserId(customUserDetails.getTrackingId());
commentRepository.save(comment);
}
해당 if문들은 댓글과 대댓글의 유효성 검증을 위해서 추가해둔 것이다.
물론 다 필요한 과정들이다.
하지만 이런식으로 (if문을 남발하듯이) 코드를 작성하게 되면
당장은 아니더라도, 시간이 흐르고 코드를 보게 되면 가독성이 떨어져서 한눈에 파악이 안될 것이다.
해당 코드가 하나의 메서드에만 존재하는 것이 아니라, 여러 메서드에 존재하기 때문에
유지보수가 매우 힘들어질 것이다.
그래서 이러한 유효성 로직들을 상황에 맞게 사용하게 해주고
추후 확장까지 가능하도록 해주는 전략패턴을 사용하기로 했다.
전략패턴
전략패턴은 실행(런타임)중에 알고리즘 전략을 선택하여
객체 동작을 실시간으로 바뀌도록 할 수 있게 해주는 행위 디자인 패턴이다.
'전략'은 어떤 행위를 하기 위한 알고리즘이나 동작 혹은 기능을 수행하기 위한 행동 계획을 말한다.
어떤 일을 수행하는 기능들이 여러가지 일때, 해당 기능들을 전략으로 정의해두고
내가 원할때 손쉽게 전략(기능)을 교체하여 사용할 수 있게 해주는 디자인 패턴이다.
원한다면 새로운 전략을 추가하여 등록할 수 도 있다.
알고리즘/기능/동작 등이 자주 변경되는 환경에서 적합하다고 볼 수 있다.
현재 나의 상황에 맞는 전략 패턴
위의 코드에서 볼 수 있었던 것 처럼
나는 RequestDTO로 들어온 객체가
1. 댓글인 경우
2. 대댓글인 경우
를 구분하여 유효성 검증을 처리 해준다.
그래서 아래와 같이 if문을 사용할 수 밖에 없었다.
//commentClass 가 1(대댓글) 이라면, parentId가 null 이 아닌지 검사,
if(commentClass==1 && parentId.isEmpty()){
throw new BaseException(PARENT_COMMENT_NOT_FOUND);
}
//대댓글이고, parentId가 null이 아니라면, 실제로 해당 댓글(parentId)이 존재 하는지 체크
if(commentClass == 1){
commentRepository.findComment(postId, UUID.fromString(parentId)).orElseThrow(()->new BaseException(PARENT_COMMENT_NOT_FOUND));
}
//만약 0(댓글) 이라면 parentId 값은 null 이어야 함. (여기서 전부 유효성 체크)
if(commentClass==0 && !parentId.isEmpty()){
throw new BaseException(PARENT_COMMENT_CANNOT_HAVE_COMMENT);
}
그래서 나는 댓글인 경우 유효성 검사를 진행하는 전략
대댓글인 경우 유효성 검사를 진행하는 전략
이 2가지를 전략으로 등록하여
전략패턴을 구현하려고 하였다.
그리고 이렇게 된다면
나는 나중에 대대댓글에 대한 유효성 검증 전략도 손쉽게 추가할 수 있을 것이다.
아래 코드를 보면 더욱 잘 이해할 수 있을 것이다.
전략 패턴을 적용한 코드
Comment 유효성 인터페이스
public interface CommentValidationStrategy {
void validate(String postId, CommentRequestDTO commentRequestDTO, CommentRepository commentRepository);
}
댓글 유효성 구현체 (comment 유효성 인터페이스 상속)
@Component
public class CommentValidation implements CommentValidationStrategy{
@Override
public void validate(String postId, CommentRequestDTO commentRequestDTO, CommentRepository commentRepository){
String parentId = commentRequestDTO.getParentId(); //부모 댓글 trackingId 값
//만약 0(댓글) 이라면 parentId 값은 null 이어야 함. (여기서 전부 유효성 체크)
if(!parentId.isEmpty()){
throw new BaseException(PARENT_COMMENT_CANNOT_HAVE_COMMENT);
}
}
}
대댓글 유효성 구현체 (comment 유효성 인터페이스 상속)
@Component
public class ReplyCommentValidation implements CommentValidationStrategy{
@Override
public void validate(String postId, CommentRequestDTO commentRequestDTO, CommentRepository commentRepository){
String parentId = commentRequestDTO.getParentId(); //부모 댓글 trackingId 값
//commentClass 가 1(대댓글) 이라면, parentId가 null 이 아닌지 검사,
if(parentId.isEmpty()){
throw new BaseException(PARENT_COMMENT_NOT_FOUND);
}
//commentClass 가 1(대댓글) 이고, parentId가 null이 아니라면, 실제로 해당 댓글(parentId)이 존재 하는지 체크
commentRepository.findComment(postId, UUID.fromString(parentId)).orElseThrow(()->new BaseException(PARENT_COMMENT_NOT_FOUND));
}
}
전략 컨텍스트
//전략 컨텍스트 (전략 등록/실행)
@RequiredArgsConstructor
public class CommentValidationContext {
private final CommentValidation commentValidation;
private final ReplyCommentValidation replyCommentValidation;
public void commentValidate(String postId, CommentRequestDTO commentRequestDTO, CommentRepository commentRepository){
commentValidation.validate(postId, commentRequestDTO, commentRepository);
}
public void replyCommentValidate(String postId, CommentRequestDTO commentRequestDTO, CommentRepository commentRepository){
replyCommentValidation.validate(postId, commentRequestDTO, commentRepository);
}
}
내가 만약 전략을 추가하고 싶으면
아래와 같이,
전략 컨텍스트에 전략을 추가해서 전략을 사용하고 싶은 곳에서 사용하면 된다.
public class CommentService {
private final CommentValidationContext commentValidationContext;
//댓글 달기
public void createComment(CommentRequestDTO commentRequestDTO, CustomUserDetails customUserDetails){
//댓글인 경우 전략
if(commentClass==0){
commentValidationContext.commentValidate(postId, commentRequestDTO, commentRepository);
}
//대댓글인 경우 전략
commentValidationContext.replyCommentValidate(postId, commentRequestDTO, commentRepository);
}
이걸 클래스 다이어그램으로 그리면 아래와 같다.
맨 위의 러프한 전략 패턴의 구조(사진)와 동일한 모습을 갖는 것을 확인 할 수 있다.
그래서 뭐가 좋아졌냐
각각의 유효성을 구현체로 관리하고, 추후 유효성 확장성을 고려하여 전략패턴을 도입하였는데,
많은 점들이 좋아졌지만, 대표적으로 아래와 같은 점들이 좋아졌다!
추후 확장성이 매우 좋아졌다 (원하는 유효성 전략을 추가만 하면 됨)
전략패턴을 구현하여 가독성이 좋아지고 코드 유효성 체크 로직을 동적으로 체크할 수 있게 되었다.
(특정 조건에 따라 댓글, 대댓글 유효성 구현체를 추가하면 된다.)
하나의 구현체마다 하나의 유효성 검증과정을 가지면서 단일 책임을 갖고,
각 구현체의 책임분리가 명확해졌다.
각 전략 클래스는 독립적이어서 단위 테스트 코드 작성이 쉬워졌다.
하지만, 초기 설계 비용이 크다는 단점이 존재하는 게 조금 아쉽다!!
이건 기존코드인데 이러했던 코드를
if(commentClass==1 && parentId.isEmpty()){
throw new BaseException(PARENT_COMMENT_NOT_FOUND);
}
//대댓글이고, parentId가 null이 아니라면, 실제로 해당 댓글(parentId)이 존재 하는지 체크
if(commentClass == 1){
commentRepository.findComment(postId, UUID.fromString(parentId)).orElseThrow(()->new BaseException(PARENT_COMMENT_NOT_FOUND));
}
//만약 0(댓글) 이라면 parentId 값은 null 이어야 함. (여기서 전부 유효성 체크)
if(commentClass==0 && !parentId.isEmpty()){
throw new BaseException(PARENT_COMMENT_CANNOT_HAVE_COMMENT);
}
이와같이 간편하고, 가독성 좋게 작성할 수 있다.
//댓글인 경우 전략
if(commentClass==0){
commentValidationContext.commentValidate(postId, commentRequestDTO, commentRepository);
}
//대댓글인 경우 전략
commentValidationContext.replyCommentValidate(postId, commentRequestDTO, commentRepository);
'CS > 디자인패턴' 카테고리의 다른 글
싱글톤 패턴 (singleton pattern) (0) | 2025.04.19 |
---|