
날이 풀리고 어느덧 6월 말이 되었을 때쯤 연구실에 남아있던 DY오빠와 JW오빠가 코드리뷰를 해주셨다. 아직 CRUD 만 해놓은 상태라 보여주기 민망했지만 혼자 구현하는 나에게 직접 손을 뻗어준 것에 너무 감사했다. 덕분에 리팩터링에 많은 도움이 되었고 클린코드가 무엇인지도 생각하는 계기가 되었다.
📝 카테고리(Category) 요구사항은 다음과 같다.
- 카테고리는 '여성 의류, 남성 의류, 신발, 가방, 악세사리, 폰케이스' 로 구성된다.
위와 같이 카테고리는 총 6개의 고정값을 갖는다. @PostConstruct 를 사용해 유연하게 데이터를 초기화하는 것도 방법이지만 카테고리값이 이미 정해져있는, 더 이상 늘어나지 않는 값이라면 data.sql 을 사용해 추가하는 방법이 더 적합할 수 있다. 따라서 아래와 같이 코드를 수정했다.
📍 기존 코드
@PostConstruct
public void initCategory() {
final List<Category> categories = categoryRepository.findAll();
if(!categories.isEmpty())
return;
final Category female = Category.builder().title("여성 의류").build();
final Category male = Category.builder().title("남성 의류").build();
final Category shoes = Category.builder().title("신발").build();
final Category bag = Category.builder().title("가방").build();
final Category acc = Category.builder().title("악세사리").build();
final Category tech = Category.builder().title("폰케이스").build();
categoryRepository.save(female);
categoryRepository.save(male);
categoryRepository.save(shoes);
categoryRepository.save(bag);
categoryRepository.save(acc);
categoryRepository.save(tech);
}
✅ data.sql 로 데이터 초기화
INSERT INTO category(title)
VALUES ('여성 의류'),
('남성 의류'),
('신발'),
('가방'),
('폰 케이스'),
('악세사리');
/)는 포함하지 않는다.http://localhost:8080/postshttp://localhost:8080/posts/ _) 대신 하이픈(-)를 사용한다. ※ 단, 많은 하이픈은 자제할 것을 권장한다.GET /posts/post-commentsGET /posts/post_commentsGET /users/profileGET /users/ProFilEGET /posts/1GET /posts/savePost/1GET /posts/duplicateGET /posts/duplicatingGET /users/profileGET /users/profile.jpgGET /postsGET /post※ 더 자세한 사항은 여기서 확인하자.
📝 좋아요(Interest) 의 요구사항은 다음과 같다.
- 회원은 게시글에 좋아요를 누를 수 있다.
위 요구사항을 만족하려면 좋아요 등록될 때 회원과 게시글에 좋아요를 저장해 줘야한다. 이때 setter 를 사용해 저장해야 하는데 스프링에서 제공하는 setter 어노테이션을 사용하지 않은 이유는 다음과 같다.
⭐️ 객체의 일관성을 유지하기 어렵다 !
특히 엔티티 클래스에서는 절대 사용하면 안된다. 무작정 setter 를 생성할 경우, 해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드 상으로 명확하게 구분할 수 없어 차후 기능 변경 시 복잡해진다.
그럼에도 불구하고 setter 가 필요한 경우가 있다. 이럴 경우 아래와 같이 private 타입으로 정의하는 것이 좋다. 관계 연결이 필요한 객체를 인자로 받아 해당 인자 안에 있는 메소드를 호출해 좋아요를 추가하는 방법이다.
✅ private 타입의 setter 로 변경된 코드
@Entity
@Getter
@NoArgsConstructor(access = PROTECTED)
public class Interest {
...
private void setMember(final Member member) {
this.member = member;
member.getInterests().add(this);
}
private void setPost(final Post post) {
this.post = post;
post.getInterests().add(this);
}
public Interest(final Member member, final Post post) {
setMember(member);
setPost(post);
}
}
언젠가 사용할 목적으로 작성했다면 다시한번 생각해봐라. 오히려 메모리 차지만 할 수 있다.
📍 기존 코드
@Transactional
public Long updatePost(final Long postId, final PostUpdateRequestDto requestDto) {
final Post post = postRepository.findById(postId)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id = " + postId));
...
}
모든 예외처리를 위와 같이 처리했는데 이렇게 되면 코드의 가독성이 떨어지고 자세하지 않은 에러 메시지로 인해 클라이언트에게 혼동을 줄 수 있다. 반면, GlobalExceptionHandler 를 사용해 전역으로 에러 처리하면 다양한 예외별로 상태코드나 메시지를 반환할 수 있고, 일관성있는 응답으로 클라이언트에게 전달할 수 있다.
✅ GlobalExceptionHandler 로 처리한 코드
@Transactional
public Long updatePost(final Long postId, final PostUpdateRequestDto requestDto) {
final Post post = postRepository.findById(postId)
.orElseThrow(PostNotFoundException::new);
...
}
※ ExceptionHandler 적용은 여기서 확인하자.