Chapter 8. 경계

Posted by yunki kim on January 19, 2022

  시스템을 만들기 위해 때로는 외부 패키지, 오픈 소스를 활용한다. 따라서 코드를 작성할 때 외부 코드와 우리의 코드를 깔끔하게 통합해야 한다. 코드의 깔끔한 통합은 다음과 같은 방법을 통해 달성할 수 있다.

 

외부 코드 사용하기

  패키지나 프레임워크 제공자는 이들의 적용성을 최대한 넓히려 한다. 반면 사용자는 자신의 요구사항에 집중하는 인터페이스를 원한다. 예를 들어 java.util.Map의 경우 아주 많은 기능들을 제공하고 있기 때문에 위험도가 높다. Map의 경우 clear()메서드를 통해 누구나 상태를 지울 권한이 있다. 또 한 Map을 다음과 같이 사용할 경우 객체 유형을 제한하지 않는다.

1
2
3
4
Map sensors = new HashMap(); // 객체 유형을 제한하지 않는다.
// Map이 반환하는 Object를 캐스팅할 책임이 클라이언트에 있다.
// 따라서 이 코드는 깔끔하지 않다.
Sensor s = (Sensor)sensors.get(sensorId);
cs

  물론 generic을 사용하면 객체 유형 제한 문제를 해결되지만 사용자에게 필요하지 않은 기능까지 제공한다는 문제는 해결되지 않는다.

1
2
3
Map <String, sensors> sensors = new HashMap(); // 객체 유형을 제한하지 않는다.
...
Sensor s = sensors.get(sensorId);
cs

  만약 Map을 다음과 같이 사용한다면 Sensors 사용자는 제네릭스가 사용되었는지를 신경쓰지 않아도 된다. 따라서 나중에 Map 인터페이스가 변해도 나머지 프로그램에는 영향을 미치지 않는다. 또 한 Sensors 클래스는 오직 인터페이스만 제공하기 때문에 설계 규칙과 비즈니스 규칙을 따르도록 강제할 수 있다.

1
2
3
4
5
6
7
8
9
public class Sensors {
    private Map sensors = new HashMap();
 
    public Sensor getById(String id) {
        return (Sensor).sensors.get(id);
    }
 
    ...
}
cs

  여기서 학고 싶은 말은 이런 경계를 사용할 때마다 캡슐화를 하라는 것이 아니다. 이런 경계를 여기저기 넘기지 말라는 의미다. 경계 인터페이스를 잉요할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않게 해야 한다. 이런 인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다.

 

경계 살피고 익히기

  외부 코드를 익히기는 어렵다. 외부 코드를 통합하는 것 역시 어렵다. 따라서 곧바로 우리쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익히는 것이 좋다. 짐 뉴커크(Jim Newkirk)는 이를 학습 테스트라 했다.

  학습 테스트는 프로그램에서 사용하려는 방식대로 외부 API를 호출한다. 이를 통해 통제된 환경에서 API를 제대로 이해하는지를 확인한다. 학습 테스트는 API를 사용하려는 목적에 초점을 맞춘다.

 

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

  학습 테스느는 투가하는 노력보다 얻는 성과가 더 크다. 패키지 새 버전이 나온다면 학습 테스트를 돌려 차이가 있는지 확인한다.

  학습 테스트는 패키지가 예상대로 도는지 검증한다. 일단 통합한 이후라고 해도 패키지가 우리 코드와 호환된다는 보장이 없다. 패키지 작성자는 기능을 추가하고 버그를 수정한다. 따라서 패키지의 새 버전이 나올때 마다 위험이 생긴다. 이때 학습 테스트는 이 위험을 알려준다.

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

 

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

  때로는 기능 구현을 위해 API가 필요하지만 API의 구현이 미완성일 때가 존재한다. 이럴때는 우리에게 필요한 인터페이스를 정의하고 사용한 뒤 API가 정의되면 Adapter pattern을 사용해 API 사용을 캡슐화하면 된다. 그러면 API가 바뀔때마다 수정해야 하는 코드가 한 곳에 모여있게 된다.

 

깨끗한 경게

  설계가 우수하면 변경에 많은 리소스가 투입되지 않는다. 통제하지 못하는 코드를 사용할 때는 많은 투자를 하거나 향후 변경 비용이 지나치게 커지지 않게 주의해야 한다.

  경계에 위치하는 코드는 깔끔하게 분리해야 한다. 또 한 기대치를 정의하는 테스트 케이스도 작성해야 한다.

  외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자. 래퍼 클래스를 사용하던, Adapter pattern을 상요하던 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환하자. 그래야 코드 가독성이 높아지고, 경계 인터페이스를 사용하는 일관성도 높아지고, 외부 패키지가 변했을 때 변경할 코드도 줄어든다.

 

출처 - 클린 코드