티스토리 뷰

이번 아이템에는 개별 아이템으로 두기 애매한 API 설계 요령들을 모아 놓았습니다.

이 요령들을 잘 활용하면 배우기 쉽고, 쓰기 쉬우며, 오류 가능성이 적은 API를 만들 수 있습니다.

[1]. 메서드 이름을 신중히 짓자.

항상 표준 명명 규칙(아이템 68)을 따라야 합니다.

이해할 수 있고, 같은 패키지에 속한 다른 이름들과 일관되게 짓는 게 최우선 목표입니다.

🐻
클린 코드에서도 이와 같은 이야기가 언급됩니다.

 

그 다음 목표는 개발자 커뮤니티에서 널리 받아들여지는 이름을 사용하는 것입니다.

긴 이름은 지양합시다!

🐻
클린 코드에서는 긴 이름이어도 명확하게 의미를 전달하는 게 중요하다고 말합니다.
상황에 따라서 개발자가 선택을 하면 될 것 같습니다.

 

애매하면 자바 라이브러리의 API 가이드를 참조합시다.

자바 라이브러리가 워낙 방대하다 보니 일관되지 않은 이름도 제법 많지만 대부분은 납득할 만한 수준입니다.

[2]. 편의 메서드를 너무 많이 만들지 말자.

모든 메서드는 각각 자신의 소임을 다해야 합니다.

메서드가 너무 많은 클래스는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 힘듭니다.

인터페이스도 마찬가지입니다.

메서드가 너무 많으면 이를 구현하는 사람과 사용하는 사람 모두를 고통스럽게 합니다.

클래스나 인터페이스는 자신의 각 기능을 완벽히 수행하는 메서드로 제공해야 합니다.

아주 자주 쓰일 경우에만 별도의 약칭 메서드를 두기 바랍니다.

확신이 서지 않으면 만들지 맙시다.

[3]. 매개변수 목록은 짧게 유지하자.

4개 이하가 좋습니다.

일단 4개가 넘어가면 매개변수를 전부 기억하기가 쉽지 않습니다.

여러분이 만든 API에 이 제한을 넘는 메서드가 많다면 프로그래머들은 API 문서를 옆에 끼고 개발해야 할 것입니다.

IDE를 사용하면 수고를 많이 덜 수 있지만, 여전히 매개변수 수는 적은 쪽이 훨씬 낫습니다.

같은 타입의 매개변수 여러 개가 연달아 나오는 경우가 특히 해롭습니다.

사용자가 매개변수 순서를 기억하기 어려울뿐더러, 실수로 순서를 바꿔 입력해도 그대로 컴파일되고 실행됩니다.

단지 의도와 다르게 동작할 뿐입니다.

과하게 긴 매개변수 목록을 짧게 줄여주는 기술 세 가지를 아래에서 소개하겠습니다.

[4]. 매개변수 타입으로는 클래스보다는 인터페이스가 더 낫다.

아이템 64에서 설명합니다.

매개변수로 적합한 인터페이스가 있다면 (이를 구현할 클래스가 아닌) 그 인터페이스를 직접 사용합시다.

예를 들어 메서드에 HashMap을 넘길 일은 전혀 없습니다.

대신 Map을 사용합시다.

그러면 HashMap뿐 아니라 TreeMap, ConcurrentHashMap, TreeMap의 부분맵 등 어떤 Map 구현체도 인수로 건넬 수 있습니다.

심지어 아직 존재하지 않는 Map도 가능합니다.

인터페이스 대신 클래스를 사용하면 클라이언트에게 특정 구현체만 사용하도록 제한하는 꼴이며, 혹시라도 입력 데이터가 다른 형태로 존재한다면 명시한 특정 구현체의 객체로 옮겨 담느라 비싼 복사 비용을 치러야 합니다.

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

메서드 이름상 boolean을 받아야 의미가 더 명확할 때에는 예외입니다.

열거 타입을 사용하면 코드를 읽고 쓰기가 더 쉬워집니다.

나중에 선택지를 추가하기도 쉽습니다.

예를 들어 다음은 화씨온도와 섭씨를 원소로 정의한 열거 타입입니다.

public enum TemperatureScale { FAHRENHEIT, CELSIUS }

온도계 클래스의 정적 팩터리 메서드가 이 열거 타입을 입력받아 적합한 온도계 인스턴스를 생성해준다고 해봅시다.

확실히 TemperatureScale.newInstance(true)보다는 TemperatureScale.newInstance(TemperatureScale.CELSIUS)가 하는 일을 훨씬 명확히 알려줍니다.

나중에 캘빈온도도 지원해야 한다면, TemperatureScale 열거 타입에 캘빈온도를 추가하면 됩니다.

또한 온도 단위에 대한 의존성을 개별 열거 타입 상수의 메서드 안으로 리팩터링해 넣을 수도 있습니다. (아이템 34)

예컨대 double 값을 받아 섭씨온도로 변환해주는 메서드를 열거 타입 상수 각각에 정의해둘 수도 있습니다.

과하게 긴 매개변수 목록을 짧게 줄여주는 기술 세 가지

[1]. 여러 메서드로 쪼갠다.

쪼개진 메서드 각각은 원래 매개변수 목록의 부분집합을 받습니다.

잘못하면 메서드가 너무 많아질 수 있지만, 직교성을 높여 오히려 메서드 수를 줄여주는 효과도 있습니다.

java.util.List 인터페이스가 좋은 예시입니다.

리스트에서 주어진 원소의 인덱스를 찾아야 하는데, 전체 리스트가 아니라 지정된 범위의 부분리스트에서의 인덱스를 찾는다고 해봅시다.

이 기능을 하나의 메서드로 구현하려면 '부분리스트의 시작', '부분리스트의 끝', '찾을 원소' 까지 총 3개의 매개변수가 필요합니다.

그런데 List는 그 대신 부분리스트를 반환하는 subList 메서드와 주어진 원소의 인덱스를 알려주는 indexOf 메서드를 별개로 제공합니다.

subList가 반환한 부분리스트 역시 완벽한 List이므로 두 메서드를 조합하면 원하는 목적을 이룰 수 있습니다.

결과적으로 강함과 유연함이 절묘하게 균형을 이룬 API가 만들어진 것입니다.

[2]. 매개변수 여러 개를 묶어주는 도우미 클래스를 만들자.

일반적으로 이런 도우미 클래스는 정적 멤버 클래스(아이템 24)로 둡니다.

특히 잇따른 매개변수 몇 개를 독립된 하나의 개념으로 볼 수 있을 때 추천하는 기법입니다.

예를 들어 카드게임을 클래스로 만든다고 해봅시다.

그러면 메서드를 호출할 때 카드의 숫자(rank)와 무늬(suit)를 뜻하는 두 매개변수를 항상 같은 순서로 전달할 것입니다.

따라서 이 둘을 묶는 도우미 클래스를 만들어 하나의 매개변수로 주고받으면 API는 물론 클래스 내부구현도 깔끔해질 것입니다.

[3]. 앞서의 두 기법을 혼합한 방법

객체 생성에 사용한 빌더 패턴(아이템 2)을 메서드 호출에 응용한다고 보면 됩니다.

이 기법은 매개변수가 많을 때, 특히 그 중 일부는 생략해도 괜찮을 때 도움이 됩니다.

먼저 모든 매개변수를 하나로 추상화한 객체를 정의하고, 클라이언트에서 이 객체의 세터 메서드를 호출해 필요한 값을 설정하게 하는 것입니다.

이때 각 세터 메서드는 매개변수 하나 혹은 서로 연관된 몇 개만 설정하게 합니다.

클라이언트는 먼저 필요한 매개변수를 다 설정한 다음, execute 메서드를 호출해 앞서 설정한 매개변수들의 유효성을 검사합니다.

마지막으로 설정이 완료된 객체를 넘겨 원하는 계산을 수행합니다.

직교성

"직교성이 높다"라고 하면 "공통점이 없는 기능들이 잘 분리되어 있다" 혹은 "기능을 원자적으로 쪼개 제공한다" 정도로 해석할 수 있습니다.

예를 들어 본문의 List 설명에서 '부분리스트 얻기'와 '주어진 원소의 인덱스 구하기'는 서로 관련이 없습니다.

따라서 두 기능을 개별 메서드로 제공해야 직교성이 높다고 할 수 있습니다.

그렇다면 "직교성을 높여 오히려 메서드 수를 줄여드는 효과가 있다"는 말은 무슨 뜻일까요?

세상 만물은 결국 100개가 조금 넘는 원소가 결합되어 만들어집니다.

마찬가지로 API도 기본 기능만 잘 갖춰놓으면 아무리 복잡한 기능도 조합해낼 수 있습니다.

예컨대 기본 기능 3개로 조합할 수 있는 기능은 총 일곱 가지나 됩니다.

그래서 편의성을 높인다는 생각에 고수준의 복잡한 기능을 하나씩 추가하다 보면 부지불식간에 눈덩이처럼 커진 API가 만들어질 수도 있습니다.

기능을 원자적으로 쪼개다 보면, 자연스럽게 중복이 줄고 결합성이 낮아져 코드를 수정하기 수월해집니다.

테스트하기 쉬워짐은 물론입니다.

일반적으로 직교성이 높은 설계는 가볍고 구현하기 쉽고 유연하고 강력합니다.

그렇다고 무한정 작게 나누는 게 능사는 아닙니다.

API 사용자의 눈높이에 맞게, 즉 API가 다루는 개념의 추상화 수준에 맞게 조절해야 합니다.

또한 특정 조합의 패턴이 상당히 자주 사용되거나 최적화하여 성능을 크게 개선할 수 있다면 직교성이 낮아지더라도 편의 기능으로 제공하는 편이 나을수도 있습니다.

이처럼 직교성은 절대적인 가치라기보다는, 철학과 원칙을 가지고 일관되게 적용해야 하는 설계 특성입니다.

이와 같은 직교성 개념은 소프트웨어 설계 전 분야로 확대할 수 있습니다.

저수준에서는 CPU 명령어 설계 철학의 두 기둥인 RISC와 CISC가 좋은 예입니다.

RISC는 직교성이 높고, CISC는 직교성이 낮습니다.

고수준으로 올라오면 클래스, 패키지, 모듈 설계에도 똑같이 적용됩니다.

더 위로 올라와 마이크로서비스 아키텍처는 직교성이 높고, 이와 대비되는 모놀리식 아키텍처는 직교성이 낮다고 할 수 있습니다.

이들을 직교성 하나만으로 해석하기에는 무리가 있지만, 대체적인 경향이 그렇다는 뜻입니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함