코드리뷰로 발전하는 MUSE:

Seunghee Lee·2024년 3월 17일

캡스톤

목록 보기
6/10
post-thumbnail

날이 풀리고 어느덧 6월 말이 되었을 때쯤 연구실에 남아있던 DY오빠와 JW오빠가 코드리뷰를 해주셨다. 아직 CRUD 만 해놓은 상태라 보여주기 민망했지만 혼자 구현하는 나에게 직접 손을 뻗어준 것에 너무 감사했다. 덕분에 리팩터링에 많은 도움이 되었고 클린코드가 무엇인지도 생각하는 계기가 되었다.

1️⃣ 고정된 더미데이터 값이면 data.sql 로 대체하자

📝 카테고리(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 ('여성 의류'),
       ('남성 의류'),
       ('신발'),
       ('가방'),
       ('폰 케이스'),
       ('악세사리');

2️⃣ REST API URL 규칙을 준수하자

1. 마지막에 슬라이스(/)는 포함하지 않는다.

  • ⭕️ http://localhost:8080/posts
  • ❌ http://localhost:8080/posts/

2. 언더바(_) 대신 하이픈(-)를 사용한다. ※ 단, 많은 하이픈은 자제할 것을 권장한다.

  • ⭕️ GET /posts/post-comments
  • ❌ GET /posts/post_comments

3. 소문자를 사용한다.

  • ⭕️ GET /users/profile
  • ❌ GET /users/ProFilE

4. 행위는 URL 에 포함하지 않는다.

  • ⭕️ GET /posts/1
  • ❌ GET /posts/savePost/1

5. 명사를 사용하되, 함수나 컨트롤 리소스를 나타내는 URL 은 동사를 허용한다.

  • ⭕️ GET /posts/duplicate
  • ❌ GET /posts/duplicating

6. 파일 확장자는 URL 에 포함하지 않는다.

  • ⭕️ GET /users/profile
  • ❌ GET /users/profile.jpg

7. URL 에서 사용되는 단어는 복수형으로 작성한다.

  • ⭕️ GET /posts
  • ❌ GET /post

※ 더 자세한 사항은 여기서 확인하자.

3️⃣ Setter 는 지양해라, 단 쓰고 싶다면 private 으로 정의하자

📝 좋아요(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);
    }
}

4️⃣ 사용하지 않는 메소드나 필요없는 주석은 제거하자

언젠가 사용할 목적으로 작성했다면 다시한번 생각해봐라. 오히려 메모리 차지만 할 수 있다.

5️⃣ 예외처리는 GlobalExceptionHandler 로 처리하자

📍 기존 코드

@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 적용은 여기서 확인하자.

profile
자라나라 개발개발 ~..₩

0개의 댓글