공부거리

Setter를 지양해야하는 이유

cocodingding 2024. 3. 6. 08:14

Setter를 지양해야하는 이유는 뭘까

  1. 불변성 유지: Setter를 사용하면 객체의 상태를 변경할 수 있음. 이는 코드를 이해하기 어렵게 만들고 예상치 못한 부작용을 초래할 수 있음. 객체의 불변성을 유지하면 코드의 예측 가능성이 높아지고 디버깅이 용이함.
  2. 캡슐화 위반: Setter를 남용하면 객체의 내부 상태를 외부에서 직접 변경할 수 있음. 이는 객체 지향 프로그래밍의 중요한 원칙인 캡슐화를 위반하는 것임. 캡슐화는 객체의 내부 상태를 외부로부터 숨기고 외부에서는 객체의 메서드를 통해서만 상호 작용할 수 있도록 하는데, Setter를 남용하면 이 원칙을 해칠 수 있음.
  3. 복잡성 증가: Setter를 사용하면 언제든지 객체의 상태를 변경할 수 있음. 이는 코드의 복잡성을 증가시킬 수 있음. 예측할 수 없는 상태 변화는 코드를 이해하기 어렵게 만듦.
  4. 디버깅과 테스트 어려움: Setter를 남용하면 상태의 변경이 어디서 발생했는지 추적하기 어려울 수 있음. 이는 디버깅을 어렵게 만들고 테스트 케이스를 작성하기도 어려움.

따라서 Setter를 지양하고 불변 객체를 선호하는 것이 좋음. 객체의 상태를 초기화한 후 변경할 수 없도록 설계하여 객체의 불변성을 유지하는 것이 중요함.

 

Setter의 대안

  • 생성자
  • Builder

 

생성자

//유저 생성 예시


//Entity
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "users")
public class User extends Auditable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userId;
    private String userName;
    private String password;
    private String userEmail;
    private String userPhone;
    private String userAddress;
    private String postcode;
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;
    private String streamKey;
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private List<Broadcast> broadcastList;
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private List<Orders> orderList;
    private String refreshToken;

	//엔티티에 생성자를 생성해준다.
    public User(String userName, String userEmail, String password, String streamKey, String userPhone, String userAddress, String postcode) {
        this.userName = userName;
        this.userEmail = userEmail;
        this.password = password;
        this.role = UserRoleEnum.USER;
        this.streamKey = streamKey;
        this.userPhone = userPhone;
        this.userAddress = userAddress;
        this.postcode = postcode;
    }


//service
@Transactional
    public void createUser(UserRequestDto requestDto) {
        User user =  userRepository.save(
        //이런식으로 생성자에 값을 넣어줘서 객체를 생성
            new User(
                    requestDto.getUserName(),
                    requestDto.getUserEmail(),
                    passwordEncoder.encode(requestDto.getPassword()),
                    UUID.randomUUID() + requestDto.getUserEmail().split("@")[0],
                    requestDto.getUserPhone(),
                    requestDto.getUserAddress(),
                    requestDto.getPostcode()
            )
        );

        new UserResponseDto(user);
    }

멤버변수가 많아지면 코드가 길어지고

상황에 따라 엔티티의 변수의 갯수가 다른 생성자가 여러개 생길 수도 있어, 

개인에 따라 가독성이 떨어진다고 느껴질 수도 있다.

 

Builder

//생성자에 Builder 어노테이션을 적용
	@Builder
    public Trade(Commission commission, Member member, String title, String content, String authorEmail, Status status) {
        this.commission = commission;
        this.member = member;
        this.title = title;
        this.content = content;
        this.authorEmail = authorEmail;
        this.status = status;
    }


//service
    public TradeResponseDto createTrade(TradePostDto tradePostDto) {
        Commission commission = commissionService.existCommission(tradePostDto.getCommissionId());//커미션 검증

        String userEmail = getAuthMember();
        Member member = memberService.findVerifyMemberByEmail(userEmail);
		
        //빌더패턴을 이용해 값을 넣어준다.
        Trade trade = Trade.builder()
                .title(tradePostDto.getTitle())
                .content(tradePostDto.getContent())
                .commission(commission)
                .member(member)
                .authorEmail(commission.getMember().getEmail())
                .status(Status.Waiting_Acceptance)
                .build();

        tradeRepository.save(trade);
        return TradeResponseDto.fromEntity(trade);
    }

빌더패턴은 요구사항에 맞게 필요한 데이터만 이용하여 유연한 클래스 생성이 가능하게된다.

때문에 다양한 생성자들이 사라지고 전체 생성자 하나만으로 필요한 변수만을 가진 객체를 생성할 수 있어,

유지보수 및 가독성이 향상됩니다.

또한 객체를 생성할 때 인자 값의 순서가 상관없다는 장점이 있습니다.

다만 생성코드 자체는 길어질 수 있어 이 또한 가독성 별로라는 사람도 있을 수 있다.