How does Google design APIs?

※ 원문 : How to Design a Good API and Why it Matters(Google Tech Talks, 2007.1.24) ※ 저자 : Joshua Bloch, Principal Software Engineer, Google

01. API 설계가 왜 중요한가?

첫째, API는 회사의 중요한 자산이기 때문이다.

아래 세 가지 이유 때문이다.

  • 고객(API 사용자)들은 무섭게 투자한다 : 구매하고, 사용하고, 배운다.

  • 운영중인 API를 중단시키는 비용은 엄두도 낼 수 없다.

  • 성공적인 Public API는 고객을 불러들인다.

Public API는 한번 오픈하면 함부로 Shutdown 할 수 없다.

그래서 제대로 만들 기회는 오직 딱 한 번 뿐이다.

처음에 만들 때 제대로 만들어야 한다.

02. 나에게 왜 API가 중요한가?

첫째, 당신이 개발자라면, 당신은 이미 API 설계자이기 때문이다.

  • 좋은 코드는 모듈화되어 있고, 좋은 모듈은 API를 가지고 있다.

  • 그래서 당신이 개발자라면, 좋은 API를 어떻게 설계하는지 알아야한다.

둘째, 유용한 모듈들은 반드시 재사용되기 때문이다.

  • 모듈이 일단 사용자를 가지기 시작하면, 임의로 API를 바꿀 수 없다.

  • 좋은 방향으로 재사용되는 모듈들은 회사의 자산이다.

03. 좋은 API의 특징

다음과 같은 7가지 특징이 있다.

  1. 배우기 쉽다.

  2. 문서가 없어도 사용하기 쉽다.

  3. 잘못 사용하기 어렵다.

  4. 읽기 쉽고, API를 사용하는 코드를 유지보수하기 쉽다.

  5. 요구사항을 만족시키기에 충분히 강하다.

  6. 확장하기 쉽다.

  7. 사용하는 사람들의 수준에 맞을 것.

04. 좋은 API의 설계방법

4.1. API 설계 과정

  1. 적당한 수준, 회의적인 태도로 요구사항 수집하기.

종종 솔루션들을 제안 받을 수도 있다. 하지만 진짜 중요한 건 솔루션이 아니라 요구사항을 수집하는 것이다.

가능하면 기능 나열이 아니라, 유즈 케이스 형태로 수집해야한다. 유즈 케이스가 일반적일 수록 설계가 더 쉬워지거나, 보상이 커질 수 있다.

  1. 짧은 스펙으로 시작하기

한 페이지가 이상적이다. Agility가 완벽함을 이긴다. 가능하면 한 페이지 내에 모든 걸 담아봐라.

가능한 많은 사람들로부터 반응을 살펴라. 그들의 이야기를 귀 기울여 듣고, 심각하게 받아들여라.

스펙이 작을수록, 고치기 쉽다. 자신감을 얻을 때까지 살을 붙여라. 이 과정은 필연적으로 코딩을 동반한다.

  1. 미리 API에 글을 적기. 아주 자주 적어두기.

빈 function 내에 필요한 기능들을 주석으로 미리 적어라.

가능한 자주 생각날 때마다 적어 넣어라.

API 개발 시간을 많이 절약해준다.

가능한 스펙을 잡기 이전부터 시작해라. 스펙을 잡는 시간도 절약해준다.

살이 붙을 때까지 API에 글을 적어라. 끔찍한 실패를 예방해준다.

코드는 예제와 Unit Test를 먹고 살기 때문이다.

  1. 서비스 인터페이스에 적는 건 더 중요

서비스 인터페이스란, 남이 쓰라고 열어주는 인터페이스를 뜻한다.

플러그인 방식으로 설계하면, 여러 개의 Implementation을 포함할 수 있다.

Java Cryptographt Extension 같은 것이다.

배포하기 전에 Multiple plugin들을 만들어라.

  1. 만일 플러그인 하나를 만들면, 아마도 API가 다른 것은 지원하지 못하게 될 것이다.

  2. 두 개를 만든다면, 어렵게라도 여러개를 지원하기는 할 것이다.

  3. 세 개를 만든다면, 그것은 아주 잘 작동할 것이다.

  4. 현실적 기대수준을 받아들이기

API 설계를 보면 모든 사람을 만족시키기 위해 지나치게 제약되어 있다.

하지만, 모든 사람을 만족시킬 수 없다. 그냥 모든 사람들을 똑같이 만족스럽지 못하게 하라.

실수를 너무 신경쓰지말고, 그걸 기회로 API를 진화시켜라.


4.2. 일반 원칙

API는 하나의 일만 처리해야 한다.

그리고, 그걸 아주 잘 처리할 수 있어야 한다. 그래서 기능을 설명하기 쉬워야 한다.

만일 이름짓기 힘들다면, 기능을 복잡하게 정의한 것이다. 좋은 이름을 지어보라.

개발이 자연스럽게 될 것이다. 그렇게 될 수 있도록, 여러 모듈들을 쪼개거나 합쳐보라.

API는 가능한 작게 만들어야 한다.

하지만, 너무 작아서는 안된다. API는 요구사항을 만족시켜야 한다.

어떻게 만들어야 할 지 모르겠다면 그냥 그대로 두어라.

기능, class, method, param까지 뭔가를 더하기는 쉽지만, 제거하기는 어렵다.

일단 API가 배포되어 사용자가 생기기 시작하면 회수하는 것은 거의 불가능하다.

개념적인 무게감이 기능의 규모보다 더 중요하다. API 의 힘과 무게 간의 균형비를 찾아라.

Implementation 이 API에 영향을 주어서는 안된다.

구현이 너무 세세해지면, API 사용자들을 혼란스럽게 한다.

Implementation 을 하기 위한 그들의 자유를 방해한다.

세세한 구현이 무엇인지를 정확히 알아라.

Method behavior 스펙을 너무 과하게 잡지 마라.

예를 들면 Hash function 들은 스펙화하지 않는다.

모든 튜닝 파라미터들이 꼭 필요한지 의심해 본다.

즉, 모든 케이스를 지원한다고 좋은 API 가 아니다.

API 설계에 사용자들의 세세한 Implementation 을 모두 담지 마라. 디스크 상이나, 네트워크 상이나, 예외 케이스로라도 !!!

모든 것의 접근을 최소화하라.

가능한 Private 하게 Class 와 Member를 만들어라.

Public class 가 가능한 Public field를 가져서는 안된다.

정보은폐를 최대화하라.

모듈이 독립적으로 Debug 되고, 이해되어지고, 구축되어지고, 테스트 되어지도록 하라.

이름이 중요하다.

API 는 작은 언어다. 이름은 굳이 설명하지 않아도 이해될 수 있어야 한다.

기호나 축약을 사용하지 마라. 일관성을 유지하라.

API 전반에 걸쳐, 똑같은 단어라면 기능도 같은 것이어야 한다.

규칙적이어야 한다. 대칭과 균형을 갈구하라. 코드는 산문처럼 읽힐 수 있어야 한다.

종교처럼 문서화하라.

모든 class, interface, method, constructor, parameter, and exception 을 문서화하라.

  • Class : instance 화 되는 것들

  • Method : method 와 그 client 들간의 계약들이다.

  • Parameter : units, form, ownership 등을 지칭한다.

State-space 를 주의깊게 문서화하라.

API의 성능 결과에 대해서도 고려하라.

나쁜 의사결정은 성능 한계를 만든다.

  • Type 을 상호 교환가능하게 만들어라.

  • Static factory 대신 Constructor 를 제공하라.

  • Interface 대신에 Implementation type 을 사용하라.

성능을 얻기 위해 API를 뒤틀지 마라.

  • 그렇게 한다면 성능 이슈가 고쳐질 수 있다.

  • 하지만 영원한 골치거리를 만드는 일이다.

  • 좋은 설계는 좋은 성능과 일반적으로 일치한다.

성능을 고려한 API 설계는 실질적인 효과로 나타날 뿐 아니라 영원하다.

Component.getSize()는 Dimension을 return 한다. Dimenstion 은 mutual 하다.

모든 getSize call 은 Dimension을 할당해야만 한다.

수많은 불필요한 object allocation 이 발생한다.

대체안이 1.2 버전에 추가된다 : old client code 는 여전히 느리다.

API는 플랫폼과 평화적으로 공존해야만 한다.

관습적인 것을 따라라.

  • 표준 naming rule 을 따라라.

  • 독자적인 파라미터나 Return Type을 쓰지마라.

  • 코어 API나 언어에 있는 패턴을 흉내내어 써라.

API 친화적인 특징을 이용하라.

  • Generics, varargs, enums, default arguments

API 의 위험과 단점들을 잘 알고 회피하라.

  • finalizers, Public static final arrays


4.3. Class 설계

변경을 최소화하라.

만일 다른 일을 해야 할 충분한 이유가 없다면, class 는 불변의 것이어야 한다.

  • 장점 : simple, thread-safe, reusable

  • 단점 : 각 value 별로 분리된 object

만일 변경해야 한다면, State-space를 작게 유지하고, 정의를 잘 유지하라. 언제, 어떤 method를 부르는게 합리적인지를 명확히 하라.

  • Bad: Date, Calendar

  • Good: TimerTask

Subclass는 Substitutability 를 의미한다.

“…은 …이다” 라는 관계가 존재할 때만 subclass 를 써라.

그렇지 않으면, Composition 을 사용하라.

  • Bad: Properties extends Hashtable, Stack extends Vector

  • Good: Set extends Collection

상속을 위해 설계하고 문서를 남겨라.

설계가 그렇게 되어 있지 않고 문서가 없다면, 상속을 못하게 만들어라.

상속이란 캡슐화와 상충한다.

  • subclass 는 superclass 의 세세한 구현에 민감하다. 만일 subclass 를 허락한다면, self-use를 문서화하라.

  • method가 어떻게 다른 method 를 사용하는지 살펴보라. 보수적인 정책으로는 모든 구체적인 class 는 final 이어야 한다.

  • Bad: Many concrete classes in J2SE libraries

  • Good: AbstractSet, AbstractMap


4.4. Method 설계

모듈에 넣을 수 있는 기능을 Client가 하게 하지 마라.

Boilerplate code 에 대한 필요를 줄여라.

  • Boilerplate code 란 일반적으로 cut-and-paste 로 행해지는 코드를 말한다.

  • 모듈을 cut&paste해서 Client 내에 넣어버리면, 아주 번거롭고 오류 발생이 잦게 된다.

“사용자를 놀라게 하지 않기” 원칙을 준수하라

API 사용자가 Behavior 에 의해 놀라서는 안된다.

그렇게 하기 위해 구현에 추가적인 노력을 기울일만한 가치가 있다.

그리고, 조금 성능을 깎여도 될만한 가치가 있다.

빨리 실패하라.

장애가 발생되면 가능한한 빨리 에러를 알려라. 컴파일 시간이 제일 좋다.

static typing, generics

런타임에, 첫 잘못된 method invocation 이 제일 좋다.

method 는 반드시 failure-atomic 해야 한다.

String 포맷으로 있는 모든 데이터를 프로그램 구조로 바꾸어라.

그렇게 하지 않으면 client는 string을 parse해야한다.

  • Client는 괴롭다.

  • 더 나쁜 건, string이 사실 상의 API로 변질되어 버린다.

주의를 가지고 Overloading하라.

모호한 오버로드는 피하라.

  • 여러 개의 오버로딩이 동시에 적용될 수 있다.

  • 보수적이 되어라 : 동일한 argument 숫자가 없도록 하라.

모호한 오버로딩을 제공해야 한다면, 동일한 arguments에 대해서는 동일한 behavior 가 일어나게 하라.

적절한 Parameter와 Return type 을 사용하라.

Input 을 위해 class 전반에 interface type 을 장려하라. 유연성과 성능을 제공하라.

가장 구체적이면서 가능한 input parameter type 들을 사용하라.

런타임 시간으로부터 컴파일 시간까지 에러를 옮겨라.

더 좋은 type 이 있으면, string 을 사용하지 마라.

String 은 무겁고, 에러가 나기 쉽고, 느리기까지 하다.

통화를 표현하는데 floating point 를 사용하지 마라.

binary floating point 는 부정확한 결과를 야기시킨다.

float(32 bits) 보다 double(64 bits)를 사용하라.

정확성 손실은 현실이고, 성능 손실은 무시할만 하다.

Method 전반에 걸쳐 일관적인 Parameter ordering을 사용하라.

특히 parameter type 들이 동일할 때 더욱 중요하다.

#include
char *strcpy (char *dest, char *src);
void bcopy (void *src, void *dst, int n);

java.util.Collections; //first parameter always collection to be modified or queried
java.util.concurrent; //time always specified as long delay, TimeUnit unit

긴 파라미터를 만들지 마라.

파라미터 수는 3 개, 또는 더 적은 수가 이상적이다. 더 많다면, 사용자들은 문서를 들여다보려 한다.

특히 똑같이 타이핑된 긴 파라미터 리스트는 위험하다. 프로그래머들은 Parameter 순서를 실수로 바꾸어버릴 수 있다.

물론 코드는 여전히 컴파일되고 잘 실행된다. 그래서 그게 더 큰 문제다.

파라미터 리스트를 짧게할 수 있는 두가지 기법이 있다.

  • method 를 두개로 나누어라.

  • 파라미터를 유지할 수 있도록 Helper class 를 만들어라.

예외처리를 요구하는 Return Value를 만들지 마라.

zero-length array를 리턴하거나 빈 collection 을 리턴하라. null 을 리턴하지 마라.


4.5. Exception 설계

예외 상황을 표시하기 위해 Exception을 던져라.

Client 가 Control flow 를 위해 exception을 사용하도록 해서는 안된다.

정반대로, 조용하게 fail이 나서는 안된다.

Unchecked Exceptions을 사용하라.

  • Checked : client 가 recovery action 을 해야 한다

  • Unchecked : programming error

  • 물론 Checked exceptions 의 과한 사용은 boilerplate 를 야기시킨다.

예외 안에 에러정보(failure-capture information)를 포함시켜라.

진단을 허용하고 repair 하거나 recovery 하라.

unchecked exception에 대해서는 메시지로 충분하다.

checked exception에 대해서는 accessor 를 제공하라.


4.6. 결론

1. API 디자인은 우아하면서도 돈을 벌 수 있는 행위이다.

많은 프로그래머와 사용자들과 회사들을 이롭게 한다.

2. 이 이야기는 어떤 의미에서 휴리스틱한 기법들을 덮어버린다.

노예같이 휴리스틱한 기술들에 달라붙지 마라. 충분한 이유없이 그들을 침범하지도 마라.

3. API 디자인은 힘들다.

혼자하는 작업이 아니다. 완벽할 수 없다. 그러나 완벽해지려고 시도하라.

4. 뻔뻔하게 스스로 Promotion하라.

Last updated