Clean Code 8장
8장 경계
소프트웨어 경계를 깔끔하게 처리하는 기법과 기교를 살펴보자
외부 코드 사용하기
- 패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애쓴다.
- 더 많은 환경에서 돌아가야 더 많은 고객이 구매하기 때문이다.
- 사용자는 자신의 요구에 집중하는 인터페이스를 바란다.
예를 들어, java.util.Map을 살펴보자.
Map sensors = new HashMap();
Sensor s = (Sensor)sensors.get(sensorId);
[문제점 1]
해당 코드에서 Map이 반환하는 object의 올바른 유형으로 변환할 책임은 클라이언트에 있다. 또한, 코드의 의도가 분명히 드러나지 않는다.
- 제네릭스(Generics)를 사용하면 코드 가독성이 크게 높아진다.
Map<String, Sensor> sensors = new HashMap();
Sensor s = sensors.get(sensorId);
[문제점 2]
Map이 사용자에게 필요하지 않은 기능까지 제공한다.
ex) sensors.clear()
를 통해 사용자가 내용을 지울 권한을 가진다.
[문제점 3]
Map 인스턴스를 여기저기 넘긴다면, Map 인터페이스가 변할 경우, 수정할 코드가 많아진다.
- 경계 인터페이스인 Map을 새로운 클래스 Sensors 안으로 숨긴다.
- Map 인터페이스가 변경되어도 나머지 프로그램에는 영향을 미치지 않는다.
- 프로그램에 필요한 인터페이스만 제공한다.
public class Sensors {
private Map sensors = new HashMap();
public Sensor getById(String id) {
return (Sensor) sensors.get(id);
}
}
주의할 점
- 경계 인터페이스를 이용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다.
- Map 인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다.
경계 살피고 익히기
- 외부 패키지 테스트는 우리 책임은 아니다. 하지만, 우리를 위해 사용할 코드를 테스트하는 편이 바람직하다.
- 학습 테스트
- 외부 코드를 사용할 때는 간단한 테스트 케이스를 작성해 익힌다.
- 프로그램에서 사용하려는 방식대로 외부 API를 호출한다.
- 통제된 환경에서 API를 제대로 이해하는지를 확인하는 셈이다.
- API를 사용하려는 목적에 초점을 맞춘다.
- 사용하는 방법을 익히면, 독자적인 클래스로 캡슐화한다.
- 경계 인터페이스를 몰라도 사용할 수 있다.
학습 테스트는 공짜 이상이다
- 투자하는 노력보다 얻는 성과가 더 크다.
- 패키지 새 버전이 나온다면 학습 테스트를 돌려 차이가 있는지 확인한다.
- 패키지가 예상대로 도는지 검증한다.
- 우리 코드와 호환되리라는 보장이 없다.
- 새 버전이 호환되지 않으면 학습 테스트가 이 사실을 곧바로 밝혀낸다.
- 경계 테스트가 있다면 패키지의 새 버전으로 이전하기 쉬워진다.
아직 존재하지 않는 코드를 사용하기
- 아는 코드와 모르는 코드를 분리하는 경계
- 아직 정의되지 않은 경계 API가 존재한다.
- 구체적인 방법을 모른다.
- 자체적으로 인터페이스를 정의한다.
- 우리가 바라는 인터페이스를 구현하면, 우리가 전적으로 통제한다는 장점이 생긴다. 또한, 코드 가독성도 높아지고 의도도 분명해진다.
- 경계 API 정의된 후, Adapter 패턴으로 API 사용을 캡슐화한다.
- API가 바뀔 때 수정할 코드를 한곳으로 모았다.
장점
- 테스트가 아주 편하다.
- 경계 API 인터페이스가 나온 다음 경계 테스트 케이스를 생성해 올바르게 API를 사용하는지 테스트할 수 있다.
깨끗한 경계
- 소프트웨어 설계가 우수하다면 변경하는데 많은 투자와 재작업이 필요하지 않다.
지켜야할 것
- 경계에 위치하는 코드는 깔끔히 분리한다.
- 기대치를 정의하는 테스트 케이스도 작성한다.
- 코드에서 외부 패키지를 세세하게 알아야 할 필요가 없다.
- 통제 가능한 우리 코드에 의존하는 편이 훨씬 좋다.
- 외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자
- 새로운 클래스로 경계를 감싼다.
- Adapter 패턴을 사용해 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환한다.
장점
- 코드 가독성이 높아진다.
- 경계 인터페이스를 사용하는 일관성도 높아진다.
- 외부 패키지가 변했을 때 변경할 코드가 줄어든다.