Java ☕

[Effective Java] 프로젝트에 적용하기

z.zzz 2024. 3. 5. 21:49

아이템 1 생성자 대신 정적 팩터리 메서드를 고려하라

클래스는 생성자와 별도로 그 클래스의 인스턴스를 반환하는 정적 메서드를 제공할 수 있다. 

 

정적 팩터리 메서드가 생성자보다 좋은 점

1. 이름을 가질 수 있다. 

2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다. 

3. 반환 타입의 하위 타입 객체를 리턴할 수 있다. 

4. 입력 매개변수에 따라 다른 클래스의 객체를 반환할 수 있다. 

5.정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다. 

 

⬇️ 장점 추가 설명

더보기

1. 생성자 자체는 생성되는 객체의 특징을 직관적으로 설명하진 않는다. 반면 정적 팩터리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.

2. 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.

3. 리턴 타입을 인터페이스로 지정하여 구현체는 노출시키지 않을 수 있다. (유연성)

4. 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다. (유연성)

5. 인터페이스나 클래스가 만들어지는 시점에서 하위 타입의 클래스가 존재하지 않아도 나중에 만들 클래스가 기존의 인터페이스나 클래스를 상속 받으면 언제든지 의존성을 주입 받아서 사용가능하다.

 

정적 팩터리 메서드의 단점

1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다. 생성자는 Javadoc이 자동으로 상단에 모아서 보여주지만 정적 팩토리 메서드는 그렇지 않다

 

→ 정적 팩터리 메서드인 ofInprogressOrderExist(), ofRemoteLocation()로 객체를 생성, 반환한다.

(💡 호출된 메서드에 대해선 매번 같은 객체를 반환하므로 static final로 객체를 생성해두고 이걸 반환하게 하면 불필요한 객체 생성을 막을 수 있을 것 같다.)

 

public static class TransportStatusChangeResponseDtoV2 {

    private boolean hasInProgressOrder;
    private boolean isDriverNearBy;
    
    public static TransportStatusChangeResponseDtoV2 ofInProgressOrderExist() {
        return TransportStatusChangeResponseDtoV2.builder()
            .hasInProgressOrder(true)
            .isDriverNearBy(false)
            .build();
    }
    
    public static TransportStatusChangeResponseDtoV2 ofRemoteLocation() {
        return TransportStatusChangeResponseDtoV2.builder()
            .hasInProgressOrder(false)
            .isDriverNearBy(false)
            .build();
    }

 

 

아이템 4 인스턴스화를 막으려거든 private 생성자를 사용하라

정적 멤버만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 게 아니다. 하지만 생성자를 명시하지 않으며 컴파일러가 자동으로 기본 생성자를 만들어준다. 사용자는 이 생성자가 자동으로 생성된 것인지 구분할 수 없으며 의도치 않게 인스턴스로 만들어 사용할 수도 있다. 이때 private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.

 

→ AddressUtil 클래스 생성자의 접근제한자를 private으로 설정해 인스턴스화를 막았다.

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AddressUtil {
    ...
}

 

 

아이템 49 매개변수가 유효한지 검사해라

"오류는 가능한 한 빨리 (발생한 곳에서) 잡아야 한다"

오류를 발생한 즉시 잡지 못하면 해당 오류를 감지하기 어려워지고, 감지하더라도 오류의 발생 지점을 찾기 어려워진다.

매개변수 검사를 제대로 하지 않았을 때 발생할 수 있는 문제들은 다음과 같다.

1. 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있다.

2. 메서드가 잘 수행되지만 잘못된 결과를 반환할 수 있다. 

3. 메서드는 문제없이 수행됐지만, 어떤 객체를 이상한 상태로 만들어놓아, 미래의 알 수 없는 시점에 메서드와 관련 없는 오류를 발생시킬 수 있다.

 

 Bean Validation의 @Notnull로 dto 필드의 유효성을 검사했다.

public class OrderRequest {

    public static class TransportStatusChangeRequestDto {
    
        @NotNull(message = "오더 id는 null일 수 없다.")
        private Long id;

        @NotNull(message = "위도는 null이 될 수 없다.")
        double latitude;

        @NotNull(message = "경도는 null이 될 수 없다.")
        double longitude;

 

 

아이템 61 박싱된 기본 타입보다는 기본 타입을 사용하라

기본 타입과 박싱된 기본 타입 중 하나를 선택해야 한다면 가능하면 기본 타입을 사용하라. 기본 타입은 간단하고 빠르다. 

 

 엔티티의 컬럼 타입을 Integer가 아닌 int로 선언했다.

@Entity
public class Cargo extends BaseTime {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cargoId;
    
    private int width;
    
    private int length;
    
    private int height;
    
    private int weight;