클린코드(CleanCode)를 읽고 간략하게 정리한 글입니다.
8장. 경계
- 시스템에 들어가는 모든 소프트웨어를 직접 개발하는 경우는 드물다.
- 때로는 패키지를 사고, 때로는 오픈 소스를 이용하고, 때로는 사내 다른 팀이 제공하는 컴포넌트를 사용한다.
- 어떤 식으로든 이 외부 코드를 우리 코드에 깔끔하게 통합해야만 한다.
1. 외부 코드 사용하기
- 인터페이스 제공자와 사용자 사이에는 특유의 긴장이 존재한다.
- 패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 한다.
- 반대로, 사용자는 자신의 요구에 집중하는 인터페이스를 요구한다.
- 예를 들어, java.util.Map을 확인 할 수 있다.
- Map 은 굉장히 다양한 인터페이스로 수많은 기능을 제공한다.
- Map 이 제공하는 기능성과 유연성은 확실히 유용하지만 그만큼 위험도(누구나 clear() 메서드를 사용)도 크다.
Map sensors = new HashMap();
Sensor s = (Sensor) sensors.get(sensorId);
- 위와 같은 코드가 한 번이 아니라 여러 차례 사용 된다.
- Map 이 반환하는 Object 를 올바른 유형으로 변환할 책임은 Map을 사용하는 클라이언트에게 있다.
- 깨끗한 코드라 보기 어렵다.
- 의도도 분명하게 드러나지 않는다.
Map<String, Sensor> sensors = new HashMap<Sensor>();
...
Sensor s = sensors.get(sensorId);
- 제네릭을 사용하여 위의 문제를 해결하고 코드 가독성을 높였습니다.
- 그렇지만, 이 방법도 사용자에게 필요하지 않은 기능까지 제공하는 문제가 있습니다.
public class Sensors {
private Map sensors = new HashMap();
public Sensor getById(String id) {
return (Sensor) sensor.get(id);
}
}
- 경계 인터페이스인 Map 을 Sensors 클래스 안으로 숨긴다.
- Map 인터페이스가 변하더라도 나머지 프로그램에는 영향을 미치지 않는다.
- Sensors 클래스 안에서 객체 유형을 관리하고 변환하기 때문에 제네릭을 사용하든 말든 문제가 안된다.
- Map 클래스를 사용할 때마다 위와 같이 캡슐화를 하라는 말은 아니다.
- Map(혹은 유사한 경계 인터페이스) 을 여기저기 넘기지 말라는 것이다.
- Map 과 같은 경계 인터페이스를 사용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다.
- Map 인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다.
2. 경계 살피고 익히기
외부 코드를 익히기는 어렵다. 외부 코드를 통합하기도 어렵다.
두 가지를 동시에 하기는 두 배나 어렵다.
- 외부 패키지 테스트는 우리의 책임이 아니다. 그렇지만 우리 자신을 위해 우리가 사용할 코드를 테스트해야만 한다.
- 곧바로 우리 쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익힌다.
3. log4j 익히기
- 로깅 기능을 직접 구현하는 대신 아파치의 log4j 패키지를 사용한다고 가정한다.
1) 문서를 자세히 읽기 전에 첫 번째 테스트 케이스를 작성한다.
// 화면에 "hello"를 출력하는 테스트 케이스이다.
@Test
public void testLogCreate() {
Logger logger = Logger.getLogger("MyLogger");
logger.info("hello");
}
2) 테스트 케이스를 확인한다. -> Appender 라는 뭔가가 필요하다는 오류가 발생한다.
3) 문서를 읽어보면 ConsoleAppender 라는 클래스를 확인할 수 있다. -> Appender에 출력 스트림이 없다는 사실을 발견한다.
@Test
public void testLogAddAppender() {
Logger logger = Logger.getLogger("MyLogger");
ConsoleAppender appender = new ConsoleAppender();
logger.addAppender(appender);
logger.info("hello");
}
4) 구글을 검색한 후 아래와 같이 시도한다. -> 정상 확인이 된다.
@Test
public void testLogAddAppender() {
Logger logger = Logger.getLogger("MyLogger");
logger.removeAllAppenders();
logger.addAppender(new ConsoleAppender(
new PatternLayout("%p %t %m%n"),
ConsoleAppender.SYSTEM_OUT));
logger.info("hello");
}
5) 테스트 케이스를 작성하는 과정에서 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");
}
}
6) 모든 지식을 활용하여 Logger 클래스로 캡슐화한다.
4. 학습 테스트는 공짜 이상이다.
- 학습 테스트는 이해도를 높여줄 뿐만 아니라 패키지가 예상대로 동작하는지 검증도 한다.
- 통합한 이후라고 하더라도 패키지가 우리 코드와 항상 호환되리라는 보장은 없다.
- 패키지 새 버전이 나올 때마다 새로운 위험이 생긴다. -> 학습 테스트를 통해 위험을 감지할 수 있다.
5. 아직 존재하지 않는 코드를 사용하기
- 경계와 관련해 다른 유형은 아는 코드와 모르는 코드를 분리하는 경계이다. 때로는 우리 지식이 경계를 넘어 미치지 못하는 코드 영역도 존재한다.
- 지금 알지 못하는 코드(다른 팀에서 아직 구현이 안된 코드)를 구현할 때 자체적으로 인터페이스를 정의한다.
- 우리가 바라는 인터페이스를 구현하면 우리가 인터페이스를 전적으로 통제한다는 장점이 생긴다.
- 코드 가독성도 높아지고 코드 의도도 분명해진다.
- 통제가 불가능한 외부 패키지에 의존하는 것보다는 통제가 가능한 우리 코드에 의존하는 편이 좋다.
6. 깨끗한 경계
- 경계에서는 흥미로운 일이 많이 벌어진다. 대표적으로 변경이다.
- 소프트웨어 설계가 우수하다면 변경하는데 많은 투자와 재작업이 필요하지 않고, 엄청난 시간과 노력과 재작업을 요구하지 않는다.
- 통제하지 못하는 코드를 사용할 때는 너무 많은 투자를 하거나 향후 변경 비용이 지나치게 커지지 않도록 주의한다.
- 경계에 위치하는 코드는 깔끔히 분리한다.
- 기대치를 정의하는 테스트 케이스도 작성
- 우리쪽에서 외부 패키지를 세세하게 알아야 할 필요가 없다.
- 통제 불가능한 외부 패키지에 존재하는 대신 통제 가능한 우리 코드에 의존하는 편이 훨씬 좋다.
- 외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리한다.
- 새로운 클래스로 경계를 감싸거나 Adapter 패턴을 사용해 우리가 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환한다.
'Study > CleanCode' 카테고리의 다른 글
[클린코드] 10장. 클래스 (2) | 2023.11.12 |
---|---|
[클린코드] 9장. 단위 테스트 (2) | 2023.11.05 |
[클린코드] 6장. 객체와 자료 구조 (0) | 2023.10.25 |
[클린코드] 5장. 형식 맞추기 (0) | 2023.10.22 |
[클린코드] 4장. 주석 (0) | 2023.10.15 |