[기술독서] Clean Code 8장

떵호·2022년 1월 18일
0

기술독서

목록 보기
8/8
post-thumbnail

8장 경계

이 장에서는 소프트웨어 경계를 깔끔하게 처리하는 기법과 기교를 살펴본다.

외부 코드 사용하기

패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애쓰는 반면, 사용자는 자신의 요구에 집중하는 인터페이스를 바란다. 이런 긴장으로 인해 시스템 경계에서 문제가 생길 소지가 많다.

// Sensor라는 객체를 담는 Map을 만들 때
Map seonsors = new HashMap();
// Sensor 객체가 필요한 코드를 가져올 때
Sensor s = (Sensor)sensors.get(sensorId);

위 코드는 한 번이 아니라 여러 차례 나온다. 즉, Map이 반환하는 Object를 올바른 유형으로 변환할 책임은 Map을 사용하는 클라이언트에 있다.
그리고 깨끗한 코드라 보기도 어렵고 의도도 분명히 드러나지 않는다.

제네릭스Generics을 사용하면 코드 가독성이 크게 높아진다.

Map<String, Sensor> sensors = new HashMap<Sensor>();
...
Sensor s = sensor = sensors.get(sensorId);

위 코드도 "Map<String, Sensor>가 사용자에게 필요하지 않은 기능까지 제공한다"는 문제는 해결하지 못한다.
Map<String, Sensor> 인스턴스를 여기저기로 넘긴다면, Map 인터페이스가 변할 경우, 수정할 코드가 상당히 많아진다.

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

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

위 코드는 경계 인터페이스인 Map을 Sensor 안으로 숨기는 방법이다. Map 인터페이스가 변하더라도 나머지 프로그램에는 영향을 미치지 않는다.

경계 인터페이스를 사용할 때 여기저기 넘기지 말고 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다.
인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다.

경계 살피고 익히기

외부 패키지 테스트가 우리 책임은 아니지만 우리 자신을 위해 우리가 사용할 코드를 테스트하는 편이 바람직하다.
외부 라이브러리를 가져왔을 때

  • 하루나 이틀(아니면 더 오랫동안) 문서를 읽으며 사용법을 결정
  • 우리쪽 코드를 작성해 라이브러리가 예상대로 동작하는지 확인
  • 버그가 발생했을 때 우리 버그인지 라이브러리 버그인지 디버깅

외부 코드를 익히고 통합하기는 어렵기 때문에 학습 테스트를 하는게 좋다.

학습테스트

  • 외부 코드를 호출하는 대신 간단한 테스트 케이스를 작성해 외부 코드를 익히는 것
  • 프로그램에서 사용하려는 방식대로 외부 API를 호출
  • API를 사용하려는 목적에 초점

log4j 익히기

로깅 기능을 직접 구현하는 대신 아파치의 log4j 패키지를 사용한다고 가정할 때 간단한 테스트 케이스를 통해 log4j 작동 방식을 이해한다.

@Test
public void testLogCreate() {
  Logger logger = Logger.getLogger("MyLogger");
  logger.info("hello")
}
// Appender라는 뭔가가 필요하다는 오류가 발생한다.
@Test
public void testLogCreate() {
  Logger logger = Logger.getLogger("MyLogger");
  ConsoleAppender appender = new ConsoleAppender();
  logger.addAppender(appender);
  logger.info("hello")
}
// Appender에 출력 스트림이 없다는 사실을 발견한다.
@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")
}
// 이제 정상적으로 "hello"가 들어간 로그 메시지가 콘솔에 찍힌다.

간단한 콘솔 로거를 초기화하는 방법을 익혔으니, 이제 모든 지식을 독자적인 로거 클래스로 캡슐화한다. 그러면 나머지 프로그램은 log4j 경계 인터페이스를 몰라도 된다.

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

학습 테스트

  • 이해도를 높여주는 정확한 실험
  • 투자하는 노력보다 얻는 성과가 더 큼
  • 패키지가 예상대로 도는지 검증
  • 새 버전이 우리 코드와 호환되지 않으면 이를 밝혀줌

학습 테스트를 이용한 학습이 필요하든 그렇지 않든, 실제 코드와 동일한 방식으로 인터페이스를 사용하는 테스트 케이스가 필요하다. 이런 경계 테스트가 있다면 패키지의 새 버전으로 이전하기 쉬워진다.

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

경계는 아는 코드와 모르는 코드를 분리하는 경계의 유형이 있다. 때로는 우리 지식이 경계를 너머 미치지 못하는 코드 영역도 있다.

예시

무선통신 시스템에 들어갈 소프트웨어 개발에 참여
소프트웨어에는 송신기라는 하위 시스템이 있었는데 팀원 모두 여기에 대한 지식이 없었음
프로젝트 지연을 원하지 않았기에 송신기 하위 시스템과 아주 먼 부분부터 작업을 시작함
점차 우리에게 필요한 경계 인터페이스가 무엇인지 알게됨

'송신기' 모듈에 원하는 기능
지정한 주파수를 이용해 이 스트림에서 들어오는 자료를 아날로그 신호로 전송하라.

이쪽 코드를 진행하고자 자체적으로 인터페이스를 정의
송신기 API를 정의한 후 TrasmitterAdapter를 구현
API 사용을 캡슐화해 API가 바뀔 때 수정할 코드를 한 곳으로 모음

깨끗한 경계

경계에 위치하는 코드는 깔끔히 분리한다. 또한 기대치를 정의하는 테스트 케이스도 작성한다.
통제가 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 훨씬 좋다.
외부 패키지를 호줄하는 코드를 가능한 줄여 경계를 관리하자. 새로운 클래스로 경계를 감싸거나 아니면 ADAPTER 패턴을 사용해 우리가 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환하자.

어느 방법이든 코드 가동석이 높아지며, 경계 인터페이스를 사용하는 일관성도 높아지며, 외부 패키지가 변했을 때 변경할 코드도 줄어든다.

profile
꾸준히 해보자❗️

0개의 댓글