일관성(Consistency)

일관성(Consistency)

API 설계에서 일관성은 사용 편의성을 보장하는 핵심이에요. 매개변수 순서, 명명 규칙, 오류 처리 방식을 일관되게 유지하면, 사용자에게 더 직관적이고 신뢰할 수 있는 라이브러리가 돼요. 이런 모범 사례를 따르면 혼란과 오용을 피해서 더 나은 개발자 경험과 더 튼튼한 애플리케이션으로 이어져요.

출처: Consistency

본문

매개변수 순서, 명명, 사용법 유지하기

라이브러리를 설계할 때는 인자의 순서, 명명 체계, 오버로딩 사용을 일관되게 유지하세요. 예를 들어 기존 메서드에 offsetlength 파라미터가 있다면, 새 메서드에서는 설득력 있는 이유가 없는 한 startIndexendIndex 같은 대안으로 바꾸지 마세요.

라이브러리가 제공하는 오버로드된 함수는 동일하게 동작해야 해요. 사용자는 라이브러리에 전달하는 값의 타입을 바꿔도 동작이 일관되게 유지되길 기대해요. 예를 들어 다음 호출들은 입력이 의미상 같으므로 모두 동일한 인스턴스를 만들죠:

BigDecimal(200)
BigDecimal(200L)
BigDecimal("200")

startIndexstopIndex 같은 파라미터 이름에 beginIndex, endIndex 같은 동의어를 섞어 쓰지 마세요. 마찬가지로 컬렉션의 값을 나타내는 용어도 element, item, entry, entity 중 하나를 골라 계속 사용하세요.

관련 메서드의 이름은 일관되고 예측 가능하게 지으세요. 예를 들어 Kotlin 표준 라이브러리에는 firstfirstOrNull, singlesingleOrNull 같은 쌍이 있어요. 이런 쌍은 어떤 것이 null을 반환하고 어떤 것이 예외를 던질 수 있는지 분명히 알려줘요. 파라미터는 일반적인 것에서 특수한 순서로 선언해서, 필수 입력이 앞에 오고 선택 입력이 뒤에 오게 하세요. 예를 들어 CharSequence.findAnyOf에서는 strings 컬렉션이 먼저 오고, 그다음 startIndex, 마지막으로 ignoreCase 플래그가 와요.

직원 기록을 관리하는 라이브러리를 생각해 볼게요. 이 라이브러리가 직원을 검색하는 다음과 같은 API를 제공한다고 해요:

fun findStaffBySeniority(
    startIndex: Int,
    minYearsServiceExclusive: Int
): List<Employee>

fun findStaffByAge(
    minAgeInclusive: Int,
    startIndex: Int
): List<Employee>

이 API는 올바르게 사용하기가 매우 어려워요. 같은 타입의 파라미터가 여러 개 있는데 순서도 일관되지 않고, 사용 방식도 일관되지 않죠. 라이브러리 사용자는 기존 함수 경험을 바탕으로 새 함수에 대해 잘못된 추측을 할 가능성이 높아요.

데이터와 상태에는 객체 지향 설계 사용하기

Kotlin은 객체 지향 프로그래밍과 함수형 프로그래밍 스타일을 모두 지원해요. API에서 데이터와 상태를 표현할 때는 클래스를 사용하세요. 데이터와 상태가 계층적이면 상속을 고려해 보세요.

필요한 모든 상태를 파라미터로 전달할 수 있다면 최상위 함수(top-level function)를 쓰는 게 좋아요. 이런 함수의 호출을 체이닝하게 된다면, 가독성을 위해 확장 함수(extension function)로 작성하는 것도 고려해 보세요.

적절한 오류 처리 메커니즘 고르기

Kotlin은 오류 처리를 위한 여러 메커니즘을 제공해요. API가 예외를 던지거나, null 값을 반환하거나, 커스텀 result 타입을 쓰거나, 내장된 Result 타입을 사용할 수 있어요. 라이브러리가 이런 옵션을 일관되고 적절하게 사용하게 하세요.

데이터를 가져오거나 계산할 수 없을 때는 널러블(nullable) 반환 타입을 쓰고 null을 반환해서 데이터가 없다는 걸 알려주세요. 그 외의 경우에는 예외를 던지거나 Result 타입을 반환해요.

하나는 예외를 던지고, 다른 하나는 이를 result 타입으로 감싸는 식의 함수 오버로드를 제공하는 것도 고려해 보세요. 이때 함수에서 예외가 잡힌다는 걸 나타내기 위해 Catching 접미사를 사용해요. 예를 들어 표준 라이브러리에는 runrunCatching 함수가 이 규칙을 쓰고 있고, 코루틴 라이브러리에는 채널을 위한 receivereceiveCatching 메서드가 있어요.

정상적인 제어 흐름에 예외를 사용하는 건 피하세요. 작업을 시도하기 전에 조건을 검사할 수 있게 API를 설계해서, 불필요한 오류 처리를 방지하세요. 명령/질의 분리(Command / Query Separation)가 여기에 적용할 수 있는 유용한 패턴이에요.

규칙과 품질 유지하기

일관성의 마지막 측면은 라이브러리 설계 자체가 아니라, 높은 수준의 품질을 유지하는 것과 관련돼요.

정적 분석용 자동화 도구(린터)를 사용해서 코드가 일반적인 Kotlin 규칙과 프로젝트별 규칙을 모두 따르게 하세요.

Kotlin 라이브러리는 모든 API 진입점의 모든 문서화된 동작을 다루는 단위·통합 테스트 스위트도 제공해야 해요. 테스트에는 특히 잘 알려진 경계(boundary) 사례와 엣지 케이스 등 폭넓은 입력이 포함되어야 해요. 테스트되지 않은 동작은 (기껏해야) 신뢰할 수 없는 것으로 간주해야 해요.

개발 중에는 이 테스트 스위트를 사용해서 변경이 기존 동작을 깨지 않는지 확인하세요. 표준화된 빌드·릴리스 파이프라인의 일부로 모든 릴리스에서 이 테스트를 실행하세요. Kover 같은 도구를 빌드 프로세스에 통합하면 커버리지를 측정하고 보고서를 생성할 수 있어요.

더 알아보기