[클린코드] 8장 경계

JUN·2024년 8월 19일
0

클린코드

목록 보기
8/14

외부 코드와 시스템 경계 관리

외부 코드 사용하기

  • 긴장: 인터페이스 제공자와 사용자 간의 긴장. 제공자는 유연성을, 사용자는 필요에 맞는 인터페이스를 원함.
  • 문제점: java.util.Map 같은 유연한 인터페이스는 기능이 많지만 오용될 수 있음.

Map 사용 예시

  • 문제: Map<String, Sensor>을 사용하는 코드는 특정 유형으로 변환하는 책임이 클라이언트에 있음.
  • 해결: 제네릭스를 사용해 가독성 향상.
    Map<String, Sensor> sensors = new HashMap<>();
    Sensor sensor = sensors.get(sensorId);
    
  • 문제 지속: 불필요한 기능 노출과 인터페이스 변경 시 수정할 코드 증가.
  • 대안: Map을 캡슐화한 Sensors 클래스를 만들어 변경 영향을 최소화하고 인터페이스를 깔끔하게 유지.
    public class Sensors {
        private Map<String, Sensor> sensors = new HashMap<>();
    
        public Sensor getById(String id) {
            return sensors.get(id);
        }
    }
    

경계 살피고 익히기

  • 외부 코드의 장점: 시간 절약과 기능 추가.
  • 테스트 필요성: 외부 패키지의 사용법을 익히고 예상대로 동작하는지 확인하기 위해 테스트 필요.
  • 학습 테스트: 외부 API를 학습하기 위한 테스트. API의 기능을 이해하고 제대로 사용되는지 확인하는 목적.

Log4j 예시

  • 테스트 과정: 간단한 "hello" 로그를 출력하는 테스트 케이스 작성 후 문제 해결 과정.
    @Test
    public void testLogCreate() {
        Logger logger = Logger.getLogger("MyLogger");
        logger.info("hello");
    }
    
  • 결과: 여러 테스트를 통해 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");
        }
    }
    

학습 테스트의 이점

  • 비용 절감: API 학습에 들이는 노력을 줄이고 정확한 지식을 얻을 수 있음.
  • 새 버전 검증: 패키지의 새 버전이 나올 때 학습 테스트로 호환성을 검증 가능.

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

  • 예시: 송신기 시스템과의 경계 정의 및 Transmitter 클래스 사용.
    public interface Transmitter {
        void transmit(double frequency, byte[] data);
    }
    
    public class FakeTransmitter implements Transmitter {
        @Override
        public void transmit(double frequency, byte[] data) {
            // Fake implementation for testing
        }
    }
    
    public class CommunicationsController {
        private Transmitter transmitter;
    
        public CommunicationsController(Transmitter transmitter) {
            this.transmitter = transmitter;
        }
    
        public void send(double frequency, byte[] data) {
            transmitter.transmit(frequency, data);
        }
    }
    
  • 장점: 우리 코드의 의도를 분명히 하고 코드 가독성 향상.
  • ADAPTER 패턴: 통제 불가능한 API와의 간극을 메워 변경 시 수정할 코드를 최소화.
    public class TransmitterAdapter implements Transmitter {
        private ExternalTransmitterAPI externalTransmitter;
    
        public TransmitterAdapter(ExternalTransmitterAPI externalTransmitter) {
            this.externalTransmitter = externalTransmitter;
        }
    
        @Override
        public void transmit(double frequency, byte[] data) {
            externalTransmitter.sendSignal(frequency, data);
        }
    }
    

깨끗한 경계

  • 변경 관리: 외부 패키지에 의존하는 코드를 최소화하고 내부에서 통제 가능한 코드로 대체.
  • 경계 코드 분리: 새로운 클래스로 경계를 감싸거나 ADAPTER 패턴을 사용해 관리.
  • 테스트 케이스: 외부 패키지의 기대치를 정의하는 테스트 케이스 작성.
profile
순간은 기록하고 반복은 단순화하자 🚀

0개의 댓글