SideProject/개발일지

3. 내가 @Setter 어노테이션을 싫어하는 이유

박세류 2024. 7. 15. 17:17

들어가며

사이드 프로젝트 진행 중, 피어코드리뷰를 진행하며 팀원과 @Setter 어노테이션에 있어 이견 차이가 있었다. 나는 평소에 여러 이유로 @Setter를 쓰는것을 싫어한다. (물론 실무에서도 @Setter를 쓰는 경우를 왕왕 보긴 했다.)

그러다 보니 상대방을 설득하려면 논리적으로 나의 생각을 설명해야 하는데, 단편적으로 느끼고 있었던 지식이 파편화 되어있어 말이 잘 안나오더라.. 그래서 그냥 빼애액 우기는 사람의 꼴이 되었다.

싫은데.. 그냥 싫다고!

그래서 좀 반성하며 복습할겸 나름의 이유를 글로 남겨보자 한다.

 

@Setter 어노테이션 이란?

이 글을 보고 있는 사람들에게는 의미없는 단락이겠지만.. '왜?' 를 알기 위해서는 '무엇'인지 부터 알아야 하기 때문에.. 간단히 적어보자면,

public class Person {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

이와 같은 메소드를

@Setter
public class Person {
    private String name;
    private int age;
}

이와 같이 줄여주는 즉, 그냥 보일러 플레이트 코드를 자동으로 작성해주는 lombok 라이브러리의 어노테이션이다.

 

 그래서 왜 싫은건데?

1. 객체지향 원칙중 하나인 캡슐화를 무시한다

캡슐화란, 클래스 안에 서로 연관있는 속성과 기능들을 만들어 데이터를 외부로부터 보호하는 것을 말한다.

 

즉, 필요한 부분만 외부로 노출될 수 있도록 하여, 데이터의 무결성을 유지하고, 안정성을 높이는 개념이다.

@Setter
public class Person {
    private String name;
    private int age;
}

 

하지만 이 코드와 같이, 접근 제어자를 private로 걸고(캡슐화를 위해), setter를 쓰면, 직접참조 -> 간접참조로 바뀔 뿐이지, 기껏 은닉하고 싶어서 private를 걸었지만 접근 할 수 있다는 점은 다를게 없다.

따라서 객체가 언제든지 변할 수 있어, 불변 객체를 만들기 어렵고, 버그가 생기기 쉽다.

 

2. 따라서 설계가 불분명해지고, 유지보수성이 떨어진다.

예를 들어, 

    public Schedule patch(Long id) {
        Schedule schedule = scheduleRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 일정이 존재하지 않습니다."));
        schedule.setTitle("변경된 일정 제목");
        return scheduleRepository.save(schedule);
    }

 

위 코드는 Schedule의 정보를 변경하는 서비스단 메소드인데,

이렇게 접근하여 변경하면 편하긴 하지만, 해당 setTitle의 의미가 드러나지 않는다.

또한, 저 setTitle을 메서드를 쓰는 부분이 100여개 된다고 가정해 보자.

이후 설계가 바뀌어 Schedule에는 title 필드가 없어진단다. 혹은 제목이 이상하게 바뀌었다는 컴플레인이 들어왔다.

틀리지 않고 유지보수를 및 디버깅을 잘 할 자신이 있겠는가? 일단 나는 자신이 없다.

 

그래서 뭐 어떡하라고?

1. 빌더 패턴 사용 

 

내가 가장 애용하는 방법인데,

    @Builder
    public Schedule(String userId, String title, String content, LocalDateTime startDateTime,
                    LocalDateTime endDateTime) {
        this.userId = userId;
        this.title = title;
        this.content = content;
        this.startDateTime = startDateTime;
        this.endDateTime = endDateTime;
    }
  • 다음과 같이 Setter 대신 Builder를 사용한다.
  • 필요한 데이터가 발생하는 시점에 해당 패턴으로 생성 시, 불변 객체를 쉽게 만들 수 있고, 무분별한 setter 함수 사용을 줄일 수 있다.

 

2. 팩토리 메서드 활용

public class UserDTO {
    private final String name;
    private final int age;

    public static UserDTO of(String name, int age) {
        return new UserDTO(name, age);
    }
}
  • 객체를 생성하는 팩토리 메서드를 사용하여 필요할 때만 객체를 생성한다.
  • Builder와 같이, 객체 생성을 통제할 수 있어 더 안전하게 관리 가능한 코드를 작성 할 수 있다.

 

P.S. 그냥 내가 하고 싶었던 말 : 타협하기 쉬워진다.

나는 이 이유가 크다고 생각을 한다.. 개인적인 경험에 비춰보았을 때

사실 몰라서 쓰고 있었다기 보다는, 편해서 쓰는 경향도 있다고 생각하기 때문이다.

위 코드중, patch 메소드를 setter가 아닌 builder 패턴을 사용하면 코드가 길어진다. 그러다 보면 자연스레

그냥 @Setter 하나 붙여주면 될 거를..Entity에 builder 추가해줘야하고, 따로 메소드 추가해서 핸들링 해줘야 하고.. 이렇게 간단한 로직에서도 해야돼? 나중에 하지 뭐..

라는 생각이 들기 마련이다. 그래서 작업속도가 개발 실력의 척도가 되는 회사에서는 종종 사용된다고 생각한다.

하지만, 나중에 고쳐야겠다고 생각한 코드는, 르블랑의 법칙에 의해, 버그가 터지기 전까지 고쳐지지 않을 가능성이 높다. 

타협을 하고 편한길을 걷기로 한 이상, 관성에 의해 계속 하던대로 하게 될 것이고, 나중에 들어온 후임도 이 코드를 보고 눈치껏 알아서 이대로 쓰게 될 가능성이 높다는 것이다. 일종의 Anti-Pattern의 대물림이다..

그렇지 않기 위해 나와 우리는 타협하지 않는 자세가 중요하다(?) 우리 같이 타협하지 말도록 하자.

갑자기 의식의 흐름대로 똥글로 마무리가 되는것 같지만 더 잘 쓸 자신이 없어서 이쯤으로 줄인다..

728x90