book/Clean Code

[Clean Code] 8장, 경계

항성 2022. 9. 1. 14:11
반응형

외부 코드 사용하기

패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애쓴다.

사용자는 자신의 요구에 집중하는 인터페이스를 바란다.

  • java.util.Map
    • Map은 다양한 인터페이스로 수많은 기능 제공. Map이 제공하는 기능성과 유연성은 유용하지만 그만큼 위험이 크다.
    • 문제 1 : Map 사용자라면 누구나 Map 내용을 지울 권한이 있다 - clear()
    • 문제 2 : Map은 객체 유형을 제한하지 않기에 사용자는 어떤 객체 유형도 추가할 수 있다.

 

  • 개선이 필요한 예시 (문제 1, 문제 2 존재)
    • Map이 반환하는 Objet를 올바른 유형으로 변환할 책임은 Map을 사용하는 클라이언트에 있다.
    • clear() 함수를 이용해서 누구나 Map의 내용을 지울 수 있다.
Map sensors = new HashMap();

Sensor s = (Sensor)sensor.get(sensorId);

 

  • 개선이 필요한 예시 - Generics 도입 (문제 1 존재, 문제 2 해결)
    • Generics를 도입하여 필요한 타입을 지정한다. → 문제 2 해결
    • Map<String, Sensor>인스턴스를 여기저기 넘긴다면 Map 인터페이스가 변할 경우 수정할 코드가 상당히 많아진다.
Map<String, Sensor> sensor = new HashMap<Sensor>();

Sensor s = sensor.get(sensorId);

 

  • 개선한 예시 - Map을 깔끔하게 사용 (문제 1, 문제 2 해결)
    • Map을 Sensors 안으로 숨긴다 → 문제 1 해결
      • Sensors 클래스 안에서 객체 유형을 관리하고 변환. Map 인터페이스가 변하더라도 나머지 프로그램에는 영향을 미치치 않는다.
    • 프로그램에 필요한 인터페이스만 제공한다. → 코드를 이해하기는 쉽지만 오용하기 어려움.
    • Map과 같은 경계 인터페이스를 이용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다.
    • Map 인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다.

 

public class Sensors {
    private Map sensors = new HashMap();

    public Sensor getById(String id) {
        return (Sensor) sensors.get(id);
    }
}

 

경계 살피고 익히기

  • 학습 테스트
    • 외부 API 혹은 패키지 사용시 곧바로 우리쪽 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해서 외부 코드를 익히는 방식.
    • 학습 테스트는 프로그램에서 사용하려는 방식대로 외부 API를 호출한다. API를 사용하려는 목적에 초점을 맞춘다.
  • 외부 패키지 테스트가 우리 책임은 아니다. 하지만 우리 자신을 위해 우리가 사용할 코드를 테스트하는 편이 바람직하다.

 

log4j 익히기

  • Log4j 테스트코드 예시
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");
    }

    @Test
    public void addAppenderWithoutStream() {
        logger.addAppender(new ConsoleAppender(
            new PatternLayout("%p %t %m%n")));
        logger.info("addAppenderWithoutStream");
    }
}

 

학습 테스트는 공짜 이상이다

  • 학습 테스트 작성을 통해 필요한 지식만 손쉽게 학습할 수 있다.
  • 학습 테스트는 패키지가 예상대로 도는지 검증한다.
    • 패키지의 새 버전이 우리 코드와 호환되지 않으면 학습 테스트가 이 사실을 곧바로 밝혀낸다.
    • 패키지의 새 버전으로 이전하기 쉬워진다.

 

아직 존재하지 않는 코드를 사용하기

  • 경계와 관련해 또 다른 유형은 아는 코드와 모르는 코드를 분리하는 경계다.

Untitled

  • 예시 상황
    무선통신 시스템을 개발해야 하는데, 송신기라는 반드시 필요한 하위 시스템이 인터페이스도 정해지지 않았다고 생각해보자. 그렇다고 송신기가 구현될 때까지 언제까지고 개발을 미루며 기다릴 수는 없을 것이다.
    • 나에게 필요한 송신기의 기능을 분석해서 임시 인터페이스를 정의하고 메서드를 작성한다.
    • 송신기 API가 정의되지 않았지만 임시 인터페이스의 Fake 구현체 클래스를 사용하여 Communication Controller 정의가 가능하다.
    • 송신기 API가 정의된 후에는 TransmitterAdapter를 구현해 간극을 메운다.
    • (ADAPTER 패턴으로 API 사용을 캡슐화해 API가 바뀔 때 수정할 코드를 한 곳으로 모은다.)

 

깨끗한 경계

  • 소프트웨어 설계가 우수하다면 변경하는데 많은 투자와 재작업이 필요하지 않음.
    • 경계에 위치하는 코드는 깔끔하게 분리한다.
    • 기대치를 정의하는 테스트 케이스를 작성한다.
    • 외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리한다.
      • Map에서 봤듯이 새로운 클래스로 경계를 감싸거나, ADAPTER 패턴을 사용하자.
반응형

'book > Clean Code' 카테고리의 다른 글

[Clean Code] 7장, 오류 코드  (0) 2022.08.31
[Clean Code] 6장, 객체와 자료구조  (0) 2022.08.30
[Clean Code] 5장, 형식 맞추기  (0) 2022.08.29
[Clean Code] 4장, 주석  (0) 2022.08.28
[Clean Code] 3장, 함수  (0) 2022.08.27