개인적 감상평
여기의 규칙중 일급 컬렉션을 사용하라는 말이 있는데, 이 내용이 이번 장과 비슷한 내용을 말한다.
둘 다 결국 원시값 & 외부 코드를 포장(boxing)하여 사용하라는 것인데, 어째서 포장을 해야하고 힘들게 포장을하면 뭐가 좋은지에 대해 말한다.
그리고 이 장에서는 소프트웨어 경계를 깔끔하게 처리하는 기법과 기교를 살펴본다.
1. 외부 코드 사용하기
•
인터페이스(라이브러리) 제공자는 적용성을 최대한 넓히려 한다.
•
인터페이스(라이브러리) 사용자는 자신의 요구에 집중하는 인터페이스를 바란다.
이런 양방향의 긴장으로 인해 시스템 경계에서 문제가 생길 수 있습니다.
Map
외부라이브러리 중 컬렉션이 있고 이중 자주쓰이는 것 중 java.util.Map이 있다.
이 맵에서 제공하는 api만해도 clear, containsKey, entrySet, equals, .isEmpty, keySet, notify, notifyAll, put, putAll, remove, size, toString, values, wait 등 엄청 다양하고 많은 함수를 제공한다.
근데 만일 이 Map에 내가 필요한 객체들을 담아서 쓴다고 할 경우 이 값의 불변성이 보장될까?
private final Map<String, Car> cars = new HashMap<>();
cars.put("firstCar", new Car());
Java
복사
위와같이 car를 추가했다고 할 때 이 인스턴스를 여기저기로 넘긴다면 Map 인터페이스가 변할 경우, 수정할 코드가 상당히 많아진다.
이를 일급 콜렉션을 사용한다면 Map을 콜렉션으로 숨기면서 불변성도 보장할 수 있고, Map 인터페이스가 변경되더라도 해당 일급 콜렉션만 수정하면되기에 변경의 범위가 축소된다.
public class Cars{
private final Map<String, Car> cars = new HashMap();
public Car getById(String id){
return cars.get(id);
}
}
Java
복사
정리하자면, Map인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다.
2. 경계 살피고 익히기
•
학습 테스트: 간단한 테스트 케이스를 작성해 외부 코드를 익혀보자
학습테스트는 프로그램에서 사용하려는 방식대로 외부 API를 호출한다.
통제된 환경에서 API를 제대로 이해하는지를 확인하는 셈이며, API를 사용하려는 목적에 초점을 맞춘다.
log4j 익히기 예제
1.
첫 번째 테스트 케이스 작성
@Test
public void testLogCreate(){
Logger logger = Logger.getLogger("MyLogger");
logger.info("hello");
}
Java
복사
⇒ Appender가 필요하다는 에러 발생
2. 두 번째 테스트 케이스 작성
문서내에서 ConsoleAppender 라는 클래스가 있다는 사실을 확인
@Test
public void testLogCreate(){
Logger logger = Logger.getLogger("MyLogger");
ConsoleAppender appender = new ConsoleAppender();
logger.addAppender(appender);
logger.info("hello");
}
Java
복사
⇒ Appender에 출력 스트림이 없다는 에러 발생
3. 세 번째 테스트 케이스 작성
: 구글링을 한 뒤 다시 시도
@Test
public void testLogCreate(){
Logger logger = Logger.getLogger("MyLogger");
logger.removeAllAppenders();
logger.addAppender(new ConsoleAppender(
new PatternLayout("%p %t %m%n"),
ConsoleAppender.SYSTEM_OUT));
logger.info("hello");
}
Java
복사
⇒ 이제 정상작동을 한다.
근데 여기서 문서를 더 살펴보고 구글링을 해보니 더 생략할 것을 알게 된다.
4. 네 번쨰 테스트 케이스 작성
public class LogTest {
private Logger logger;
@Before
public void setup() {
logger = Logger.getLogger("logger");
logger.removeAllAppenders();
Logger.getRootLogger().removeAllAppenders();
}
@Test
public void basicLogger(){
BasicConfigurator.configure();
logger.info("basicLogger");
}
@Test
public void addAppenderWithStream() {
logger.addAppender(new ConsoleAppender(
new PatternLayout("%p %t %m%n"),
ConsoleAppender.SYSTEM_OUT));
logger.info("addAppenderWithStream");
}
@Test
public void addAppenderWithoutStream() {
logger.addAppender(new ConsoleAppender(
new PatternLayout("%p %t %m%n")));
logger.info("addAppenderWithoutStream");
}
}
Java
복사
학습 테스트는 공짜 이상이다.
이렇게 번거롭게 학습 테스트를 하면 시간낭비가 아닐까 생각할 수도 있지만, 이렇게 학습테스트를 함으로써 API에 대한 이해도도 높힐 수 있을 뿐 아니라 새로운 버전으로 업데이트되더라도 기존에 작성한 학습테스트를 통해 호환성 체크도 할 수 있고 그에따라 적용해줄 수도 있습니다.
3. 아직 존재하지 않는 코드를 사용하기
•
경계의 또 다른 유형은 아는 코드와 모르는 코드를 분리하는 경계다.
•
협업을 하다보면 아직 다른팀에서 완성하지 못한 API를 사용해야 하는 경우가 있다.
Fake구현체 생성
다른 팀에서 아직 API를 제작하지 못했다면 해당 API에서 인터페이스를 추출해 Controller에서 분리한 뒤 Fake 구현체를 사용하며, API가 제작이 완료되면 Adapter를 이용해 간극을 메워준다.
이와같이 Fake 구현체 클래스를 사용하면 Controller도 테스트할 수 있다.
깨끗한 경계
•
외부 패키지를 호출하는 코드를 줄여 경계를 관리하며 새로운 클래스(ex: 일급 콜렉션)나 ADAPTER PATTERN을 이용하자. 이를 이용하면 외부 패키지의 수정이나 변경에 대응범위가 작아지고 경계를 분명히 해 코드 가독성도 높아진다.