시스템에 들어가는 모든 소프트웨어를 직접 개발하는 경우는 드물다.
...
어떤 식으로든 이 외부 코드를 우리 코드에 깔끔하게 통합해야만 한다.
경계
인터페이스 제공자 : 적용성을 최대한 넓히려고 함
인터페이스 사용자 : 자신의 요구에 집중하는 인터페이스를 원함
이런 서로의 초점이 다르기 때문에 시스템 경계에서 문제가 생기기도 함
ex) java.utill.Map
//Sensor라는 객체를 담는 Map을 만들 때
Map sensors = new HashMap();
//Sensor 객체가 필요한 코드를 가져올 때
Sensor s = (Sensor)sensors.get(sensorId);
이 코드에서는 Map이 반환하는 Object를 올바른 유형으로 변환할 책임은 Map을 사용하는 클라이언트에 있다.
제네릭스를 사용하면 코드 가독성이 높아진다.
Map<String, Sensor> sensors = new HashMap<Sensor>();
...
Sensor s = sensors.get(sensorId);
이 코드에서는 Map<String, Sensor> 인스턴스를 여기저기로 넘긴다면, Map 인터페이스가 변할 때, 수정할 코드가 상당히 많아진다.
경계 인터페이스인 Map을 Sensors 안으로 숨기는 방법을 사용한다.
public class Sensors {
private Map sensors = new HashMap();
public Sensor getById(String id) {
return (Sensor) sensors.get(id);
}
}
이렇게 할 경우 Map 인터페이스가 변하더라도 나머지 프로그램에는 영향을 미치지 않는다.
경계 인터페이스를 사용할 때
외부에서 가져온 라이브러리를 사용할 때
외부 코드를 익히고 통합하기는 어렵기 때문에 학습 테스트 방법을 이용하면 좋다.
학습 테스트
로깅 기능을 직접 구현하는 대신 아파치의 log4j 패키지를 사용한다고 했을 때 간단한 테스트 케이스들을 통해 log4j가 돌아가는 방식을 이해한다.
@Test
public void testLogCreate() {
Logger logger = Logger.getLogger("MyLogger");
logger.info("hello");
}
//Appender가 필요함
@Test
public void testLogAddAppender() {
Logger logger = Logger.getLoger("MyLogger");
ConsoleAppender appender = new ConsolAppender();
logger.addAppender(appender);
logger.info("hello");
}
//Appender에 출력 스트림이 없음
@Test
public void testLogAddAppender() {
Logger logger = Logger.getLoger("MyLogger");
logger.removeAllAppenders();
logger.addAppender(new ConsoleAppender(new PatternLayout(
"%p %t %m%n"), ConsoleAppender.SYSTEM_OUT));
logger.info("hello");
}
테스트 케이스를 통해 log4j가 어떻게 돌아가는 지를 이해하고 여기서 얻은 것들을 독자적인 로거 클래스로 캡슐화한다. 그러면 나머지 프로그램은 log4j 경계 인터페이스를 몰라도 된다.
학습 테스트
학습 테스트를 이용한 학습이 필요하든 아니든, 실제 코드와 동일한 방식으로 인텊이스를 사용하는 테스트 케이스가 필요하다. 이런 경계 테스트가 있다면 패키지의 새 버전으로 이전하기 쉬워진다.
경계에는 아는 코드와 모르는 코드를 분리하는 경계의 유형이 있다. 때로는 우리 지식이 경계를 너머 미치지 못하는 코드 영역도 있다.
ex) 무선통신 시스템에 들어간느 소프트웨어 개발
소프트웨어의 하위 시스템 중에 송신기가 있는데 여기에 대한 지식이 거의 없음
송신기 API가 설계되지 않음
송신기 모듈에 원하는 기능
지정한 주파수를 이용해 이 스트림에서 들어오는 자료를 아날로그 신호로 전송
만들어지기 바라는 송신기 인터페이스를 자체적으로 정의
송신기 API가 정의된 후에 TransmitterAdapter를 구현
API 사용을 캡슐화 해 API가 바뀔 때 수정할 코드를 한곳으로 모음
경계에 위치하는 코드는 깔끔히 분리한다. 기대치를 정의하는 테스트 케이스도 작성한다.
통제 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 훨씬 좋다.
외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하는 것이 좋다.
새로운 클래스로 경계를 감싸거나, ADAPTER 패턴을 사용해 우리가 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환한다.
외부 패키지를 가져와서 사용을 할 때 외부 코드를 우리 코드에 깔끔하게 통합하는 방법에 대해 이야기 하고 있다. 외부 코드를 가져와서 코드를 통합하고 프로그램을 짜고 해본적이 없어서 이번 내용이 좀 어렵게 느껴졌다. 하지만 동시에 이 부분을 제대로 공부해야겠다는 필요성도 동시에 느꼈다.
이 장의 처음에서 말한 것처럼 시스템에 들어가는 모든 소프트웨어를 직접 개발하지 않고 외부 코드를 가져와서 사용한다. 그렇다면 앞으로 분명히 내가 경험을 하게 될 것이다. 외부 패키지를 이해하고 코드를 짜고 내 코드와 다시 외부 코드를 통합하고... 그리고 버그가 나면 다시 버그를 찾고 수정하고...
깃헙으로 졸작을 작업하고 있는데 각자 작업하고 푸쉬하는 과정에서 처음으로 오류가 났던 적이 있었다. 왠지 모르겠는데 각자 돌릴 때는 잘 됬고 분명 게임 오브젝트에 Rigidbody 컴포넌트를 넣어놨다. 근데 master 브랜치에 merge 하고 실행할 때는 Rigidbody 컴포넌트가 사라져있었다... 이거 하나도 오류나면 이유를 찾고 수정해야 되는 문제로 머리가 아픈데... 외부 패키지는 얼마나 힘들까.. 하는 생각을 잠시 하게 됬다. 그래서 그만큼 더 신경써서 준비를 하고 처음부터 이 책에서 말한 방법을 기억하며 습관을 잘 들여야겠다.