본문 바로가기
Clean Code

[클린 코드] 8장 - 경계 (Boundaries)

by 노력남자 2022. 3. 11.
반응형

이번 장에선 클린 코드 8장 - 경계에 대해서 알아보겠습니다.

 

경계(Boundaries)란?

 

 

프로젝트를 진행하다 보면 오픈 소스, 다른 부서가 만들어 놓은 API, 모듈 등 외부 코드를 사용하는 경우가 대부분인데

 

외부 코드를 내 코드에서 호출하는 부분을 경계(boundaries)라고 한다.

 

단순하게 외부 코드를 사용하려는 곳에서 직접 호출할 수 있지만 그렇게 하는 경우는 드물고

 

외부 코드를 호출하는 부분을 따로 만드는 게 보편적인 방법이다.

 

경계 코드도 그냥 짜면 안 된다.

 

어떻게 하면 이쁘게 짤 수 있는지 알아보자!

 

외부 코드 사용하기

 

 외부 라이브러리 SensorsFactory는 Map<String, Sensor> 형태로 값을 리턴해준다고 가정해보자.

 

SensorsFactory.get()으로 가져온 sensors를 sensorId로 가져오는 코드다.

 

Map<String, Sensor> sensors = SensorsFactory().get(); // 외부 라이브러리

Sensor s = sensors.get(sensorId);

 

위 코드에 문제가 있다.

 

문제점

 

1. Map에서 기본으로 제공해주는 메서드인데 clear도 있고 remove도 있고 값을 조작하거나 추가하거나 할 수 있는 문제가 발생한다.

 

 

2. 외부 라이브러리에서 준 Map을 바로 사용하면 여기저기 돌아다니면서 추가되거나 삭제될 수 있는 문제가 있고 만약 스펙이 변경되면 소스를 전반적으로 다 바꿔야 할지도 모른다.

 

해결책

 

public class Sensors {
    private Map<String, Sensor> sensors = SensorsFactory().get();
    
    public Sensor getById(String id) {
        return sensors.get(id);
    }
    
    // 이하 생략
}

 

위 코드처럼 Sensors 경계 클래스를 만들어 외부 코드 SensorsFactory().get()에서 리턴해주는 값을 조작할 수 있도록 만들어 놓으면 여러가지 장점이 있다.

 

1. Sensors라는 경계 클래스를 만들어 호출하는 내부 코드에서 오용하기 어렵게 만들어준다.

 

2. SensorsFactory가 아닌 다른 SensorsFactory2를 사용한다고 하면 단순히 Sensors만 변경해주면 된다.

 

3. 외부 코드에서 가져온 값을 내부에서 사용하는 로직에 맞게 변환할 수 있는 장소가 생긴다.

 

외부 코드를 경계 클래스로 랩핑해서 불필요한 기능을 제한하고 내부 로직에 알맞게 사용하자!

 

외부 코드를 사용할 때 무조건 경계 클래스를 사용하라는 건 아니고 위와 같은 경우에 사용하라는 얘기다.

 

+ 위 예시말고도 옆 부서 api를 가지고 올 때도 위와 비슷하다. restTemplate, webClient로 가지고 오는 부분 그게 바로 경계 클래스다.

 

경계 살피고 익히기 (학습 테스트)

 

경계 클래스를 잘 만들려면 외부 코드를 잘 사용할줄 알아야 한다.

 

아무 내용도 모르는 외부 코드를 어떻게 알아볼 수 있을까?

 

학습 테스트(Learning Test)를 작성하는 방법이 있다.

 

@Test // 1차 시도
public void testLogCreate() {
    Logger logger = Logger.getLogger("MyLogger");
    logger.info("hello");
}

@Test // 2차 시도
public void testLogAddApender() {
    Logger logger = Logger.getLogger("MyLogger");
    ConsoleAppender appender = new ConsoleAppender();
    logger.addAppender(appender);
    logger.info("hello");
}

@Test // 3차 시도 (완료)
public void testLogAddAppender() {
    Logger logger = Logger.getLogger("MyLogger");
    ConsoleAppender appender = new ConsoleAppender();
    logger.addAppender(new ConsoleAppender(
            new PatternLayout("%p %t %m%n"),
            ConsoleAppender.SYSTEM_OUT));
    logger.info("hello");
}

 

위와 같이 1차 시도, 2차 시도, 3차 시도 총 3번만에 Logger를 이용해 로그를 찍는 방법을 알아냈다.

 

이제 그럼 로그 찍는 방법에 대해 쭉 공부를 하고 각 로그 찍는 방법에 대한 테스트를 작성한다.

 

public class LogTest {
    private Logger logger;

    @Before
    public void init() {
        ...
    }

    @Test
    public void basicLogger() {
        ...
    }

    @Test
    public void addAppenderWithStream() {
        ...
    }

    @Test
    public void addAppenderWithoutStream() {
        ...
    }
}

 

위와 같이 각 기능에 대해 테스트를 작성하면 좋은 점이 있다.

 

장점

 

1. 외부 코드에 대해 정확히 알 수 있다.

2. 외부 코드가 버전이 변경됐을 때 사라지거나 변경된 코드가 있는지 알 수 있다. <- 내부 코드에 영향성을 파악할 수 있음

 

 

장점이 있긴 하지만 저렇게 까지 외부 코드에 대해 테스트를 짜놓는 건 아직 본 적이 없다.

 

정말 필요한 외부 코드인 경우엔 저렇게 테스트를 작성하는 방법도 나쁘진 않을 거 같다.

 

 

결론 : 통제가 불가능한 외부 코드에 바로 의존하지 말고 중간에 경계 클래스를 두어 그 클래스에 의존하자.

 

코드 가독성이 높아지며 외부 코드 변경에도 강하다.

반응형

댓글