코딩 해파리

인스턴스 생성 방식에 대한 고찰 본문

Development/클린 코드

인스턴스 생성 방식에 대한 고찰

haepalea 2025. 7. 7. 22:29

인스턴스 생성 방식에는 크게 4가지가 있을 것 같습니다.

자바 기준으로 설명을 드리겠습니다!

  1. 그냥 new를 사용
  2. 정적팩토리 메서드를 이용
  3. Builder 패턴을 이용
  4. 기본 생성자 + Setter

1. 생성자 호출 (new 키워드)

User user = new User("Haepal", 27);

언제 쓰나?

  • 필수값이 명확하고 객체 상태를 명확히 유지하고 싶을 때
  • 필드 개수가 적고 단순한 객체일 때 가장 심플
  • 불변(Immutable) 객체 생성 시 간편하고 명확함

문제점

  • 생성자에 인자가 많으면 가독성 급감 (예: new User(a,b,c,d,e,f,g,h) ← 보기 어려움) - 근데 사실 요즘 IDE가 매우매우 좋아서 파라미터명 힌트를 줘서… 가독성도 낫배드…
  • 선택적 필드 처리에 취약 (null 남발 가능)

결론: 필수 필드가 소수이고 명확한 경우 추천

  • 생성자 오버로드는 피할 것 (빌더나 팩토리 메서드로 해결)

2. 정적 팩토리 메서드 (Static Factory Method)

User user = User.of("Tom", 30);
User adminUser = User.adminUserOf("Alice");

언제 쓰나?

  • 객체 생성 시 의미 부여 (이름으로 의도를 명확히 전달)
  • 캐싱·싱글턴 등 객체 생성 로직을 커스터마이징하고 싶을 때
  • 인자가 같아도 의도가 다른 여러 방식으로 생성 가능 (오버로딩 대체)
  • 생성자 대신 의미 있는 이름 부여

문제점

  • 클래스 내 메서드 수 증가 가능
  • 너무 많으면 관리가 어려워질 수 있음
  • 객체 상태가 매우 복잡하면 메서드가 너무 많아져 혼란 가능성 존재

결론: 의도를 명확히 전달하거나 생성 방식이 다양할 때 매우 추천

  • 특히 도메인 객체 생성 시 적극 추천

3. Builder 패턴

User user = User.builder()
                .name("Tom")
                .age(30)
                .build();

언제 쓰는지

  • 생성자 인자가 많거나 선택적(Optional) 필드가 다수일 때 가장 좋음
  • 코드 가독성 향상 목적
  • 변경 가능한 필드를 명확히 지정하고 싶을 때
  • 테스트에서 다양한 조합 쉽게 생성

문제점

빌더 패턴은 필수 필드를 컴파일 타임에 강제 X → 예를 들어, DTO에 필드가 새로 추가되었는데 기존 빌더 호출 코드는 새 필드를 잊고 수정하지 않더라도 컴파일 에러가 발생 X…

결과적으로…

  • 런타임 버그 발생 가능 (ex. 누락된 필드로 인해 NPE 또는 잘못된 데이터 생성)
  • 디버깅 난이도 증가 (어디서 빌더 필드를 빠뜨렸는지 찾기 어렵습니다.)

“그럼 Lombok의 @NonNull 넣으면 되잖아!” → 부분적으로만 가능합니다.

  • @NonNull 필드에 대해 빌드 시점에서 런타임 Null 체크를 합니다.
  • 그러나, 런타임 시점 체크라서 컴파일 단계에서 누락을 잡지 못합니다.

즉, 실행 전까지 누락을 모르고 있다가 실제 런타임에서 예외를 만나게 됩니다.. 이건 개발자로서 원하는 상황은 아닐 것이라 생각합니다.

→ 생성자 기반 빌더 패턴, 정적 팩토리 메서드와 조합하여 해결하는 방법들이 존재하지만… 그럴거면, 그냥 생성자나 정적 팩토리 메서드를 쓰는 것이 더 낫지 않을까...하는 생각을 합니다!

“긴 파라미터 리스트도 충분히 깔끔하게 포매팅할 수 있고 훌륭한 IDE들은 파라미터명 힌트도 준다. 자, 이 정도면 컴파일러가 우리를 이끌도록 놔둬도 되지 않을까?”

  • [클린 아키텍처 - 톰 홀버그]

결론: 필드가 많고 선택적인 값이 많다면 추천하지만… 문제점은 명확히 짚고 넘어가야 한다… 


4. 기본 생성자 + Setter

User user = new User();
user.setName("Tom");
user.setAge(30);

언제 쓰나?

  • 프레임워크(JPA, Jackson 등)가 Reflection을 통해 객체를 생성하고, 내부적으로 값 세팅할 때
  • 빠르게 프로토타입 작성할 때

문제점

  • 객체가 불완전한 상태로 생성된 후 채워지므로, 중간에 사용하면 예상치 못한 오류 발생 가능
  • 필수 필드를 누락하기 쉬움 → 런타임 에러 발생 가능성
  • 불변성을 유지할 수 없어서 사이드이펙트 위험 존재
  • 캡슐화가 깨짐

결론: 그냥 사용하지 않는게..

불가피하게 써야 하면 protected 또는 private 접근 제한하고 프레임워크에서만 허용하도록 제한


개인적인 선택과 고려사항

저는 주로 1번(생성자)과 2번(정적 팩토리 메서드)을 선호하는 편입니다..! 그 이유는 다음과 같습니다!

Null 안전성에 대한 우려

DTO, 도메인, 엔티티에서 null 값이 예상치 못한 곳에 들어가는 것을 우려합니다. NPE는 현재는 문제없어 보이더라도 유지보수 과정에서 예상치 못한 곳에서 발생할 수 있기 때문입니다.

Builder나 Setter를 사용하다 보면 필드 변경 시 null이 의도치 않게 들어올 수 있고, 이를 나중에 누군가 접근할 가능성이 있다고 생각합니다.

컴파일 타임 안전성의 가치

필드의 추가나 삭제는 실제 개발에서 자주 발생하는 상황입니다. 이런 변화가 있을 때 파급 효과를 컴파일 타임에 확인할 수 있다는 점에서 생성자나 정적 팩토리 메서드가 주는 안정감이 있습니다.

다양한 관점의 존재

물론 이는 개인적인 선호이며, 각 팀과 프로젝트의 상황에 따라 최적의 선택은 달라질 수 있습니다:

  • Builder 패턴이 더 적합한 경우: 선택적 필드가 많거나, 테스트 코드에서 다양한 조합이 필요한 경우
  • 생성자가 적합한 경우: 불변 객체나 필수 필드 중심의 단순한 객체
  • 팀의 코딩 스타일: 일관성 있는 패턴 사용이 개별 선호보다 중요할 수 있음

결국 각자의 팀 상황, 프로젝트 특성, 그리고 팀원들의 선호도를 종합적으로 고려하여 적절한 인스턴스 생성 방식을 선택하시길 권장드립니다!