광의의 ‘API’를 전제로, 문서화와 예제 부재, 오류 처리와 디버깅의 취약점, 추상화 누수, 버전 호환성, 구성 복잡성, 성능·보안·편의의 트레이드오프 등 개발자 생산성을 해치는 대표적 사용성 함정들을 정리한다.
여기서 API는 "API"의 일반화된 개념을 의미합니다:
그런 넓은 의미의 API로 보면, 모든 프로그래밍은 "API"를 사용하는 것(그리고 "API"를 만드는 것)을 중심으로 이루어집니다.
API 사용성은 개발자 생산성에 매우 중요합니다.
입력/출력 데이터의 정확한 형식에 대한 문서화 세부사항이 없거나 예제가 없음. 문서 작성자는 지식의 저주 때문에 사용자가 알고 있다고 가정할 수 있지만, 대부분의 사용자는 모릅니다.
사용 예시를 제공하지 않음. 예시는 작동하는 예시는 세부사항을 생략할 수 없기 때문에 매우 가치가 있습니다. 상세 문서가 없으면, 개발자는 보통 세부사항을 파악하기 위해 API를 수동으로 시험합니다. 작동 예시를 조금씩 바꿔보며(팅커링) 배우는 방식은 학습을 더 능동적이고 효율적으로 만듭니다.
문서에 명확한 설명이 부족함. 많은 용어가 모호합니다. 예를 들어 "immutable"은 1) 참조는 불변, 참조 대상 객체는 가변 2) 참조는 가변, 참조 대상 객체는 불변 3) 참조와 참조 대상 객체 모두 불변 4) 단지 읽기 전용일 뿐, 다른 방식으로는 참조 대상 객체를 변경할 수 있음 … 등으로 해석될 수 있습니다.
수동 테스트가 매우 어려움. 간단한 REPL이 없음. 가상 환경을 쉽게 구성할 수 없음. 스냅샷을 쉽게 찍고 로드할 수 없음. 간단한 명령으로 호출할 수 없음. 테스트 중 실수를 쉽게 되돌릴 수 없음. RESTful API를 curl로 쉽게 테스트할 수 없음.
디버깅 및 시각화 도구가 부족함. 내부 상태나 중간 데이터를 쉽게 확인할 수 없음. 프로파일링이 쉽지 않음. 한 예로 텍스트 형식 대신 효율적인 이진 데이터 형식을 사용하면서, 그 이진 데이터를 검사·편집할 도구가 부족한 경우가 있습니다(텍스트 기반 데이터 형식의 주요 장점 중 하나는 형식 전용의 특수 도구 없이도 쉽게 검사·편집이 가능하다는 점).
동작이 직관적이지 않아 개발자가 쉽게 오해함. 이는 주류 도구의 유사한 API와 다르게 동작할 때도 발생합니다. 예: YAML은 콜론 뒤에 공백이 필요합니다(JSON과 다름). 또 다른 예: CSS 레이아웃.
API 사용의 함정(잘못된 사용법)을 알려주는 문서가 없음.
API를 잘못 사용했을 때 조용히 아무 일도 하지 않음(fail-silent) 혹은 예상치 못한 동작(정의되지 않은 동작)을 함. 예: 메모리 안전하지 않은 언어의 메모리 관리(요즘은 valgrind 같은 도구로 개선). 또 다른 예: JSON 설정 파일에서 필드 이름을 잘못 철자하면, JSON 파서는 보통 알 수 없는 필드를 무시하므로 오류 메시지 없이 설정이 적용되지 않음.
안전망이 없음. 잘못된 API 사용을 방지하지 못함. 흔한 예: 메모리 안전하지 않은 언어(C/C++)의 메모리 관리. 또 다른 예: 데이터 레이스.
추상화 누수. 구현 세부사항을 이해해야만 올바르게 사용할 수 있음. 추상화가 복잡성을 숨기지 못함.
버전 사이에서 하위 호환 없이 API가 변경됨. 온라인 자료가 구식일 수 있으며 LLM도 오래된 자료로 학습되어 있을 수 있음.
어떤 설정이 사용되지 않거나 효과가 없다는 사실을 명시적으로 알리지 않음. (예: 두 개의 설정 집합이 있고 하나가 다른 하나를 오버라이드한다면, 덮어씌워지는 쪽을 변경해도 효과가 없음.)
오류 메시지가 다른 곳에 조용히 기록됨(특정 명령이나 특수 함수 호출로만 확인 가능, 또는 특정 로그 파일에만 존재). 초보자는 오류 메시지를 어디에서 확인해야 하는지 모르는 경우가 많음.
오류 메시지가 모호하여 무엇이 잘못됐는지 알려주지 않음. 예: 다양한 오류에 대응하는 단일 오류 코드만 제공. 이는 종종 런타임 메타데이터를 충분히 유지하지 않아 발생합니다. 런타임에 관련 정보가 없으니 유용한 오류 메시지를 출력할 수 없음.
오류를 일찍 알려주지 않음. 특정 기능을 실제로 사용할 때만 오류를 알림. 이 때문에 특정 조건이 만족될 때까지 설정 버그가 드러나지 않을 수 있음.
연산의 올바른 단계에서 오류를 보고하지 않음. 1단계의 잘못된 설정이 1단계에서는 오류를 내지 않다가, 2단계에서 1단계의 잘못된 데이터를 처리하면서 오류를 발생시켜, 1단계의 맥락이 사라져 메시지가 더 난해해짐.
도구가 내부에서 "마법"을 너무 많이 수행함. API는 겉보기엔 단순하지만 실제로는 복잡함. 이런 "마법"은 때로 편리하지만 원치 않는 동작을 야기하기도 함.
편의 기능이 보안 취약점을 초래함. (예: 일부 JSON 라이브러리는 다형 객체를 지원하려고 JSON에 클래스 이름을 저장하지만, 사용자로부터 들어온 클래스 이름을 신뢰하는 것은 위험함.)
후속 오류가 너무 많아 근본 원인을 가림. 예: 로그 파일의 스팸성 로그—실제로 의미가 있는 것은 첫 번째 오류뿐이고 이후의 스팸 오류는 그 부작용일 뿐. C++에서 STL 컨테이너를 잘못 사용하면, 근본 오류를 가리는 방대한 STL 내부의 컴파일 오류가 쏟아지기도 함.
특수한 커스텀 용례를 수용하려다 API가 복잡해져, 일반적이고 단순한 사용이 오히려 어렵고 더 복잡해짐.
반대로, API가 너무 단순해 특수한 커스텀 용례를 수용하지 못함. 그래서 특수 용례를 하려면 복잡하고 오류가 나기 쉬운 해킹(공개 API가 아니라 내부 구현에 의존)이 필요해짐.
두 개의 API 세트를 제공함(예: 구버전 API와 신버전 API, 또는 단순하지만 느린 API와 복잡하지만 빠른 API). 그러나 내부적으로 두 세트가 복잡하게 상호작용하여, 둘을 함께 쓰면 이상한 동작이 발생함.
격리성과 직교성이 부족함. 한 가지를 바꾸면 겉보기엔 무관해 보이는 다른 것이 영향을 받음. 예: CSS 레이아웃.
프로토타이핑을 어렵게 만드는 엄격한 제약. 예: Rust에서 데이터 구조를 변경하면 모든 사용처에 라이프타임 매개변수를 추가/삭제하거나 참조를 Arc로 바꾸는 등 대규모 리팩터링이 필요할 수 있음(관련 글 참조). 이런 제약은 정확성을 돕고 PR 리뷰를 쉽게 만들지만, 프로토타이핑과 반복을 방해함. 트레이드오프임.
기본 사용법이 비효율적 사용을 부추김. 예: 정규식 문자열을 인자로 직접 넘기면 매 호출마다 정규식을 파싱하게 됨(내부 캐싱으로 완화 가능).
겉보기에 올바름을 위해 사용성을 희생함. 예: Windows 파일 시스템에서는 사용 중인 파일을 이동하거나 삭제할 수 없음. 이는 겉으로는 정확성을 돕지만, 소프트웨어 업그레이드를 어렵게 만듭니다. Windows에서는 다른 소프트웨어가 파일을 읽는 중이면 업그레이드가 오류를 일으키기 쉬워, 재부팅을 통해서만 안전하게 업그레이드할 수 있는 경우가 많습니다. 또한 외래 키는 정확성을 돕지만, 백업 복원과 스키마 마이그레이션을 더 어렵게 만듭니다.
성능을 고려하지 않고 설계된 API여서, 호환성을 깨지 않고는 나중에 최적화하기 어려움.
보안에 지나치게 집중해, 단순한 작업도 어렵게 만듦.
피드백 루프가 길다. 예: 코드를 바꾼 뒤 웹사이트에서 효과를 확인하려면 느린 CI/CD를 기다려야 함. 긴 피드백 루프는 작업을 비효율적으로 만들고, 인내심을 소모하며, 개발자가 단기 기억을 덜 유지하게 만듭니다. 좋은 예는 핫 리로딩으로, 피드백 루프가 매우 짧습니다.
LLM이 중요한 미묘한 전제를 환각으로 만들어내어, 개발자가 API의 미묘한 전제를 오해하고 그 전제를 의심하지 않은 채 디버깅에 많은 시간을 낭비하게 됨.
순서 의존적 설정과 순서 오류에 대한 취약성. 한 단계라도 순서를 틀리면 깨짐. 병렬·분산 시스템에서는 순서가 우연한 요인에 좌우되기 때문에 특히 다루기 어렵고, 재현되지 않는 랜덤 오류를 야기함.
중복된 설정. 한 설정이 세 군데에 중복되어 있으면, 변경 시 세 군데를 모두 바꿔야 함.
다중 출처 설정. 예: 하나의 옵션을 전역에서 변경, 로컬에서 변경, 부모로부터 상속, 타입별 변경 등. CSS가 대표적 예입니다. 편리해 보이지만, 설정값이 잘못됐을 때 그 값이 어디에서 왔는지 추적하기 어렵습니다.
지나치게 유연한 설정 파일. 설정 파일은 보통 변수, 조건, 반복 같은 일반 프로그래밍 언어의 풍부한 기능을 지원하지 않는 평문 텍스트입니다. 설정 파일을 더 유연하고 표현력 있게 만들려다 보면, 결국 사용하기 어려운 DSL로 변질되기 쉽습니다(기존 디버깅·로깅 도구를 적용하기 어렵고, 기존 라이브러리를 쓸 수 없으며, 대개 IDE 지원도 부족함).
라이브러리가 관리하는 데이터와 사용자 코드가 관리하는 데이터 간의 일관성을 유지해야 함. 양쪽이 서로를 갱신할 수 있어 단일 진실 원천(single source of truth)이 없음. 둘이 동기화되지 않으면 이상한 문제가 발생함.
라이브러리가 핵심 기능은 제공하지만 중요한 세부 요소 하나가 빠져 있음. 그 결과 라이브러리를 쓸 수 없고 재구현해야 함. (예: 세밀한 텍스트 레이아웃 제어는 HTML/CSS에서 어렵기 때문에, 많은 웹앱이 모든 텍스트를 캔버스 내부에서 직접 렌더링하도록 강제됨.)