- 시스템에 들어가는 모든 소프트웨어를 직접 개발하는 경우는 드물다.
- 때로는 패키지를 사고, 오픈 소스를 이용한다.
- 때로는 사내 다른 팀이 제공하는 컴포넌트를 사용한다.
- 어떤 식으로든 이 외부 코드를 우리 코드에 깔끔하게 통합해야만 한다.
- 이 장에선 소프트웨어 경계를 깔끔하게 처리하는 기법과 기교를 살펴본다.
외부 코드 사용하기
- 패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애쓴다.
- 이를 사용하는 클라이언트 사용자는 자신의 요구에 집중하는 인터페이스를 바란다.
- 이러한 긴장으로 인해 시스템 경계에서 문제가 생길 소지가 많다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//나쁜 코드 - 형변환이 매번 있으며 sensors의 권한이 사용하는 모두에게 존재함.
// 누군가는 Map 이 제공하는 clear 를 호출해서 Map 의 담겨져있는 데이터를 전부 날릴 수 있음
Map sensors = new HashMap();
Sensor s = (Sensor)sensors.get(sensorId);
//Generics 사용하여 개선 - 코드 가독성은 높아지지만 권한 문제는 해결x
// Map 인터페이스가 변경되면 수정해야할 부분이 생긴다.
Map<String, Sensor> sensors = new HashMap<Sensor>();
Sensor s = sensors.get(sensorId);
//Map을 캡슐화하는 Sensors 클래스 정의하여 개선 - 캡슐화를 통해 권한 문제도 해결할 수 있음.
public class Sensors {
private Map sensors = new HashMap();
public Sensor getById(String id) {
return (Sensor)sensors.get(id);
}
}
- 외부 API를 캡슐화하면 나머지 프로그램이 설계 규칙과 비즈니스 규칙을 따르도록 강제할 수 있다.
- Map 클래스를 사용할때마다 캡슐화를 하라는게 핵심이 아니다. Map을 여기저기 넘기지 말라는것이 핵심이다.
- Map과 같은 경계 인터페이스를 이용할때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다.
- Map 인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다.
경계 살피고 익히기
- 외부 패키지 테스트가 우리 책임은 아니지만 우리 자신을 위해 우리가 사용할 코드를 테스트하는 편이 바람직하다.
- 외부 코드를 익히긴 어렵다. 외부 코드를 통합하기 또한 어렵다. 이 두 가지를 동시에 하기는 두 배나 어렵다.
- 다르게 접근하면 어떨까? 곧바로 우리쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익히면 어떨까? 짐 뉴커크는 이를
학습테스트
라 부른다.
학습테스트
- 학습 테스트는 프로그램에서 사용하려는 방식대로 외부 API를 호출한다.
- 학습 테스트는 다음의 순서대로 진행한다.
- 1)외부 패키지의 문서를 자세히 읽기 전에 첫 번째 테스트 케이스를 작성한다.
- 2)오류를 해결하기 위해 문서를 읽고 테스트를 해결한다.
- 3)얻은 지식을 토대로 간단한 단위 테스트 케이스들이 담긴 테스트 클래스로 표현한다.
- 테스트 클래스로부터 얻은 지식을 바탕으로 독자적인 클래스로 외부 API를 캡슐화한다.
- 학습테스트는 필요한 지식만 확보하는 손쉬운 방법이다.
- 패키지 새 버전이 나오면 학습 테스트를 돌려 차이가 있는지 확인할 수 있다.
- 학습테스트만 돌려도 우리 코드와 호환되는지 확인 가능하다.
- 이러한 경계 테스트가 있다면 패키지의 새 버전으로 이전하기 쉬워진다.
- 만약 반대로 없다면 낡은 버전을 필요 이상으로 오랫동안 사용하려는 유혹에 빠지기 쉽다.
아직 존재하지 않는 코드를 사용하기
- 때로는 우리 지식이 경계를 너머 미치지 못하는 코드 영역도 있다.
- 코드를 작성하는 우리는 경계가 어딘쯤인지 대략적으로 알 수 있다.(필요한 경계 인터페이스를 찾을 수 있음)
- 자체적으로 우리가 바라는 인터페이스를 정의하자.
어댑터 패턴 (ADAPTER PATTERN)
- 어댑터 패턴은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴이다.
- 우리가 바라는 인터페이스를 구현하면 우리가 인터페이스를 전적으로 통제한다는 장점이 생긴다.
- 코드 가독성도 높아지고 코드 의도도 분명해진다.
- API 사용을 캡슐화해 API가 바뀔 때 수정할 코드를 한곳으로 모아 응집도를 높일 수 있다.
- 테스트도 아주 편하다. 실제 Transmitter API 가 나온 다음 경계 테스트 케이스를 생성해 API를 올바로 사용하는지 테스트할 수도 있다.
출처: https://haeng-on.tistory.com/69
- 어댑터 패턴과 관련된 내용은 아래 링크를 참고하면 더 자세한 내용을 알 수 있다.
깨끗한 경계
- 소프트웨어 설계가 우수하다면 변경하는데 많은 투자와 재작업이 필요치 않다.
- 통제하지 못하는 코드를 사용할 땐 너무 많은 투자를 하거나 향후 변경 비용이 지나치게 커지지 않도록 각별히 주의해야 한다.
- 경계에 위치하는 코드는 깔끔히 분리한다.
- 통제가 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 훨씬 좋다. 자칫하면 외부 코드에 휘말린다..
- 외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자.
- Map 에서 봤듯이, 새로운 클래스로 경계를 감싸거나 아니면 ADAPTER 패턴을 사용해 우리가 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환하자.
- 어느 방법이든 코드 가독성이 높아지며, 경계 인터페이스를 사용하는 일관성도 높아지며, 외부 패키지가 변했을 때 변경할 코드도 줄어든다.
Reference
- 예제 코드 및 이미지 출처