타입 시스템이 API 설계를 바꾸는 방식
좋은 타입 시스템은 개발자를 통제하기보다 의도를 더 정확하게 표현하게 만듭니다. 그래서 타입 시스템을 이해하는 것은 문법 공부가 아니라 설계 품질을 높이는 일에 가깝습니다.
타입 시스템은 문서보다 강한 계약이 될 수 있다
문서로만 정의된 API는 시간이 지나면 쉽게 어긋납니다. 반면 타입으로 표현된 계약은 코드 변경과 함께 검증됩니다.
null을 허용하는지- 실패 결과가 어떤 형태로 돌아오는지
- 컬렉션이 비어 있을 수 있는지
- 호출자가 반드시 채워야 하는 값이 무엇인지
이런 정보를 타입으로 드러내면 코드 리뷰와 테스트 부담이 줄어듭니다. 문서만 믿고 쓰는 API보다 훨씬 강한 피드백 루프가 생기기 때문입니다.
널 처리 방식은 API 안정성에 직접 연결된다
널 안정성이 약한 언어에서는 방어 로직이 코드 전역으로 퍼집니다. 호출자는 매번 값 유무를 추측하고, 작성자는 “여기서는 아마 null이 아니겠지”라는 기대를 숨겨 둡니다. 반면 널 가능성을 타입에 노출하는 언어는 위험 구간을 앞당겨 드러냅니다.
좋은 API는 선택 값을 애매하게 숨기지 않습니다.
- 반드시 필요한 값은 non-null로 강제
- 선택 값은 optional 또는 nullable로 명시
- 빈 결과와 실패 결과를 구분
이 구분이 약하면 호출자는 null, 빈 배열, 예외, 상태 코드 사이에서 의미를 추측해야 합니다.
제네릭은 재사용보다 제약 표현에 더 유용하다
제네릭은 흔히 중복 제거 도구로 소개되지만, 실무에서는 “어떤 타입이 와야 안전한가”를 표현하는 데 더 유용합니다. 예를 들어 저장소, 이벤트 버스, 직렬화 모듈은 모두 아무 타입이나 받는 것처럼 보여도 실제로는 제약이 많습니다.
잘 설계된 제네릭 API는 다음을 명확하게 만듭니다.
- 입력과 출력 타입의 관계
- 공변성과 반공변성이 필요한 지점
- 호출자가 제공해야 하는 전략 객체의 형태
반대로 제네릭이 과하면 API가 읽기 어려워집니다. 타입 파라미터가 세 개를 넘기 시작하면 “유연성”보다 “설명 불가능성”이 커지는지 의심해 볼 필요가 있습니다.
불변성은 협업 비용을 낮춘다
불변 데이터 구조를 기본으로 두면 함수와 API 경계가 단순해집니다. 호출자는 전달한 객체가 내부에서 예상치 않게 바뀌지 않을 것이라고 믿을 수 있고, 작성자는 상태 변경 시점을 더 명확히 드러낼 수 있습니다.
특히 비동기 코드나 멀티스레드 환경에서는 불변성이 설계 안전성을 크게 높입니다. 변경 가능한 객체를 여러 계층이 공유하면 버그는 재현하기도 어려워집니다.
예외 중심 API와 결과 타입 중심 API
언어마다 실패를 표현하는 관용이 다릅니다. 어떤 언어는 예외를 기본으로 삼고, 어떤 언어는 Result, Either, sealed type 같은 명시적 실패 모델을 선호합니다. 어느 방식이든 중요한 것은 실패가 호출자에게 얼마나 예측 가능하게 전달되느냐입니다.
예외가 나쁜 것은 아니지만, 다음 조건에서는 결과 타입이 더 읽기 좋은 경우가 많습니다.
- 비즈니스 실패가 정상 흐름의 일부일 때
- 재시도, 대체 경로, 사용자 메시지 분기가 필요할 때
- 여러 실패 유형을 호출자가 구분해야 할 때
반대로 시스템 오류처럼 즉시 상위로 전파해야 하는 경우는 예외가 더 자연스러울 수 있습니다.
언어 선택이 설계 문화를 만든다
Kotlin, Rust, TypeScript, Go는 모두 다른 방식으로 타입 안전성을 제공합니다. 중요한 것은 “어느 언어가 더 우월한가”보다 “그 언어가 팀에게 어떤 설계 습관을 강제하거나 유도하는가”입니다.
- Kotlin은 null safety와 sealed class로 상태 모델링을 밀어줍니다.
- Rust는 소유권과 결과 타입으로 실패와 자원 관리를 명시하게 만듭니다.
- TypeScript는 프론트엔드 API 계약을 코드 레벨에서 빨리 검증하게 도와줍니다.
- Go는 단순한 타입 시스템 대신 인터페이스와 명시적 에러 처리 규율을 요구합니다.
언어를 고른다는 것은 결국 실수를 어디에서 잡을지 고르는 일입니다.
마무리
타입 시스템은 개발자 취향 문제가 아니라 설계 비용 구조를 바꾸는 요소입니다. 좋은 API는 문서가 친절해서가 아니라 타입이 의도를 잘 드러내서 이해하기 쉽습니다. 언어의 타입 특성을 제대로 활용하면 테스트, 리뷰, 운영에서 생기는 불확실성을 앞단에서 줄일 수 있습니다. 결국 좋은 설계는 더 많은 자유가 아니라 더 명확한 제약에서 나옵니다.
Continue Reading
다음으로 읽기 좋은 글
TypeScript Generics 실전 가이드
TypeScript 제네릭을 어디서 쓰면 API 계약이 더 강해지고, 어디서부터는 타입 퍼즐이 되는지 실무 기준으로 정리합니다.
💬 Language타입 안전성과 런타임 검증의 경계
타입 시스템이 강해질수록 런타임 검증을 잊기 쉽습니다. 실전 서비스에서 타입과 검증의 경계를 어떻게 나눌지 정리합니다.
📈 최신 동향JDK 25 최신 동향: LTS 채택을 어떻게 읽어야 하는가
JDK 25는 2025년 9월 16일 GA가 되었고 Java 25의 기준 구현입니다. 지금 중요한 것은 JEP 개수보다, 어떤 기능을 실전 채택 대상으로 보고 어떤 것은 관망해야 하는지입니다.
⚙️ Backend백엔드 멱등성과 재시도 설계 원칙
중복 요청, 네트워크 재시도, 비동기 처리 환경에서 멱등성을 어떻게 설계해야 안전한 백엔드 동작을 보장할 수 있는지 정리합니다.
다음 탐색