소프트웨어는 혼자 완성되지 않는다. 표준 라이브러리, 오픈소스, 사내 공통 모듈처럼 외부 코드와 계속 맞닿는다. 문제는 외부 코드가 우리의 의도와 같은 방향으로 설계되어 있지 않을 수 있다는 점이다. 그래서 경계를 잘 세우는 일이 중요하다.

서드파티 코드를 사용할 때

인터페이스를 제공하는 쪽은 많은 사용자를 만족시켜야 하므로 범용성을 중시한다. 반대로 사용하는 쪽은 자기 문제에 필요한 기능만 안정적으로 쓰고 싶어 한다. 이 차이 때문에 외부 API를 애플리케이션 곳곳에서 직접 사용하면 변경의 충격이 넓게 퍼진다.

필요하다면 외부 API를 얇게 감싸는 클래스를 두는 편이 낫다. 래퍼는 우리가 실제로 사용하는 기능만 노출하고, 외부 라이브러리의 세부사항을 내부로 숨긴다. 이렇게 하면 라이브러리 변경, 교체, 테스트 대역 사용이 훨씬 쉬워진다.

학습 테스트

서드파티 코드를 바로 본 코드에 붙이기보다 작은 테스트로 먼저 다뤄보면 좋다. 이를 학습 테스트라고 부를 수 있다. 목적은 라이브러리를 검증하는 것이 아니라, 우리가 이해한 사용법이 실제로 맞는지 확인하는 데 있다.

예를 들어 log4j를 처음 붙인다면 설정 방식과 appender 동작을 작은 테스트로 확인할 수 있다.

public class LogTest {
    private Logger logger;
 
    @Before
    public void initialize() {
        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");
    }
}

이런 테스트는 처음 학습할 때만 유용한 것이 아니다. 라이브러리를 업그레이드할 때도 우리가 기대한 동작이 여전히 유지되는지 확인해 준다. 작은 테스트가 경계의 안전망이 되는 셈이다.

아직 존재하지 않는 코드와의 경계

때로는 필요한 모듈이 아직 만들어지지 않았는데, 그 모듈을 사용하는 코드를 먼저 작성해야 한다. 이때는 우리가 원하는 인터페이스를 먼저 정의하면 된다. 실제 구현이 늦어져도 사용하는 쪽의 설계를 진행할 수 있고, 테스트에서는 가짜 구현으로 흐름을 검증할 수 있다.

중요한 점은 외부 팀이나 미래의 구현에 맞춰 현재 코드를 흐릿하게 만들지 않는 것이다. 우리가 필요한 동작을 인터페이스로 표현하고, 나중에 실제 구현과 연결되는 지점에서 어댑터를 두면 된다.

깨끗한 경계

경계는 변경을 흡수하는 완충지대다. 외부 코드를 직접 신뢰하기보다, 우리가 이해하고 통제할 수 있는 형태로 좁혀서 사용하는 편이 안정적이다. 학습 테스트는 경계 바깥의 동작을 확인해 주고, 래퍼와 어댑터는 경계 안쪽의 코드를 보호해 준다.

결국 좋은 경계는 외부 코드를 멀리하자는 뜻이 아니다. 외부 코드를 적극적으로 사용하되, 그 영향이 시스템 전체로 번지지 않게 만드는 설계다.

다음장으로 9장