본문 바로가기

STUDY/이펙티브자바

8-3) 메소드 시그니처를 신중히 설계하라

메소드 시그니처

 

private String myFunc(Integer num) {
    ...
}

// 메소드 시그니처가 다르기때문에 컴파일 에러가 발생하지 않는다.
public final Integer myFunc(String str) {
    ...
}

 

private String myFunc(Integer num) {
    ...
}

// 공개범위, final 키워드, 반환 타입이 다르지만 중복된 메소드로 컴파일 에러가 발생한다.
public final Integer myFunc(Integer num) {
    ...
}

메소드 시그니처란 메소드의 고유성을 나타내는 요소들을 말한다. 메소드 시그니쳐가 동일한 메소드는 중복된 메소드를 만드는 것이기 때문에 컴파일 에러가 발생한다. 언어마다 다르지만 자바에서는 메소드 명과 파라미터의 순서, 타입, 개수가 시그니쳐이다. 이런 메소드 시그니처는 다른 개발자가 해당 클래스를 사용할 때 보게되는 것들이므로 쓰기쉽고 실수할 가능성이 적도록 만들어야 한다.

 

 


메소드 명을 신중하게 지어라

 

메소드 명은 클래스를 사용할 때 가장 많이보게되는 것으로 직관적으로 이해할 수 있도록 지어야 한다. 같은 패키지의 메소드들은 일관된 메소드 작명법을 가지는 것이 좋다. 그리고 개발자 커뮤니티에서 일반적으로 쓰이는 명명 규칙을 숙지하고 있는것도 중요하다. 한국의 경우 네이버, 우아한 형제들 등에서 사용하는 Java Coding Conventions를 참고하면 좋을것이다.

 

 

 


편의 메소드를 너무 많이 만들지 말자

 

편의 메소드란 min, max와 같이 편의를 위해 만들어진 메소드를 말한다. 편의 메소드는 다른 메소드를 개발할 때 도움을 주지만, 클래스에 너무 많은 메소드가 있으면 해당 클래스를 사용/테스트/유지보수 하는데 어려움을 준다. 따라서 정말 필요한 경우에만 사용하도록 해야한다.

 

// springframework의 ObjectUtils에서 제공하는 메소드
public static boolean isEmpty(@Nullable Object obj) {
	if (obj == null) {
		return true;
	}

	if (obj instanceof Optional) {
		return !((Optional<?>) obj).isPresent();
	}
	if (obj instanceof CharSequence) {
		return ((CharSequence) obj).length() == 0;
	}
	if (obj.getClass().isArray()) {
		return Array.getLength(obj) == 0;
	}
	if (obj instanceof Collection) {
		return ((Collection<?>) obj).isEmpty();
	}
	if (obj instanceof Map) {
		return ((Map<?, ?>) obj).isEmpty();
	}

	// else
	return false;
}

추가적으로 편의 메소드를 많이 만든다는 건 자바의 범용 패키지들에서 제공하는 메소드들을 잘 모르고있기 때문일 가능성이 크다. 전에 메소드마다 파라미터 유효성을 검사하기위해 객체가 null인지 list나 map이 비어있는지 검사하는 메소드를 직접 개발한 경우를 본 적이 있다. 하지만 해당 메소드는 ObjectUtils에서 isEmpty라는 이름으로 이미 동일한 기능을 제공하고 있다. 이처럼 편의 메소드를 만들려고 할 때는 동일 기능을 제공하는 일반적인 메소드가 있는지 먼저 찾아보는 것이 좋다.

 

 


매개변수 목록은 짧게 유지하자

 

// 리스트의 start부터 end까지의 부분 리스트에서 value를 찾는 동작
nums.subList(start, end).indexOf(value);

너무 많은 매개변수는 코드의 직관성을 저해하고 실수할 가능성을 높힌다. 하지만 필요한 데이터가 많기 때문에 매개변수가 많아지는 것이라면 이를 해결하는 몇가지 방법이 있다. 첫번째는 해당 메소드의 동작을 여러 매소드로 분리하는 것이다. 예를 들어 리스트에서 start부터 end까지의 부분 리스트에서 value를 찾는 기능이 필요하다고 하면, 해당 동작을 모두 하나의 메소드로 만들 수 도 있을 것이다. 그렇게 되면 내부 동작도 복잡해지고 3개의 파라미터를 전달받아야 한다. 하지만 이를 부분리스트 메소드와 value를 찾는 메소드와 같이 일반적인 기능을 하는 메소드로 분리시키면 각각 메소드의 동작은 더욱 간결해지고 파라미터도 줄어든다.

 

public class Deck {
    public void shuffle() {
    	...
    }

    public void addCard(int suid, int value) {
    	...
    }

    public void removeCard(int suid, int value) {
    	...
    }
}

 

public class Deck {
    static public class Card {
        private final int suit; 
        private final int value;
    }

    public void shuffle() {
    	...
    }

    public void addCard(Card c) {
    	...
    }

    public void removeCard(Card c) {
        ...
    }
}

두 번째는 여러 매개변수를 묶어주는 도우미 클래스를 만드는 것이다. 예를 들어 포케게임을 위한 Deck 클래스가 있다고 했을 때, 카드의 모양과 값을 매개변수로 전달 받을 수도 있다. 하지만 이를 Card라는 새로운 클래스로 묶어서 매개변수로 활용할 때 보다 직관적이고 실수할 여지 없이 매개변수를 받을 수 있다.

 

SearchStockVo searchStock = SearchStockVo.builder()
        .marketCode(marketCode)
        .indexName(industry.getIndexName())
        .baseDate(baseDate)
        .build();
List<StockDto> stocks = stockDao.selectStock(searchStock);

마지막으로 빌더 패턴을 매개변수에 활용하는 방법도 있다. 이 방법은 매개변수가 많고 그 매개변수들이 선택적으로 필요할 때 사용하면 좋다. 위 코드를 보면 데이터를 조회하는 조건 파라미터에 빌더 패턴을 활용해서 필요할 때 필요한 조건만을 매개변수로 만들어서 넘길 수 있다.

 

 


매개변수의 타입으로는 클래스보다 인터페이스를 사용하라

 

// Collections 클래스의 sort 메소드
public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

매개변수로 적합한 인터페이스가 있다면 그 인터페이스를 사용하는것이 좋다. List를 정렬하는 sort 메소드를 만든다면 List인터페이스를 매개변수로 사용하면 된다. 구현 클래스인 ArrayList나 LinkedList를 매개변수로 선언할 이유는 없다. 이는 해당 메소드를 사용하는 개발자의 자유를 제한할 뿐이다.

 

 


boolean 보다는 원소 2개짜리 열거타입이 낫다

 

public class Temperature {
    ...

    public Temperature(boolean isCelsius, double value) {
        ...
    }
}

 

public class Temperature {
    static public enum TemperatureScale {
        FAHRENHEIT,
        CELSIUS;
    }

    public Temperature(TemperatureScale temperatureScale, double value) {
        ...
    }
}

기온을 나타내는 Temperature 클래스를 만든다면 기온 값 뿐만 아니라 화씨인지 섭씨인지도 생성자의 파라미터로 받아야할 것이다. 이를 boolean 타입으로 받을 수도 있겠지만 이를 별도의 enum 타입으로 선언해서 true / false 값이 아닌 FAHRENHEIT / CELSIUS로 직접 나타내는것이 코드의 가독성과 안전성을 높혀준다.

728x90