Search
Duplicate

8장 경계

개인적 감상평

과정에서 강조하는 소트웍스 앤솔러지 객체지향 생활체조원칙이라는게 있다.
여기의 규칙중 일급 컬렉션을 사용하라는 말이 있는데, 이 내용이 이번 장과 비슷한 내용을 말한다.
둘 다 결국 원시값 & 외부 코드를 포장(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을 이용하자. 이를 이용하면 외부 패키지의 수정이나 변경에 대응범위가 작아지고 경계를 분명히 해 코드 가독성도 높아진다.