코드 경계

Hant·2021년 11월 8일
0

Clean Code

목록 보기
7/13
post-thumbnail

1. 외부 코드 사용하기

패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애씁니다. 더 많은 환경에서 돌아가야 더 많은 고객이 구매하기 떄문입니다. 반면, 사용자는 자신의 요구에 집중하는 인터페이스를 바랍니다. 이런 갈등으로 인해 시스템 경계에서 문제가 생길 소지가 많습니다.

const sensors = new Map();
const s: Sensor = sensors.get(sensorId) as Sensor;

위와 같은 코드가 한 번이 아니라 여러 차례 나옵니다. 즉, Map이 반환하는 Object를 올바른 유형으로 변환할 책임은 Map을 사용하는 클라이언트에게 있습니다. 그래도 코드는 동작합니다. 하지만 깨끗한 코드라 보기는 어렵습니다. 게다가 위와 같은 코드는 의도도 분명히 드러나지 않습니다. 대신 다음과 같은 제네릭스(Generics)을 사용하면 코드 가독성이 크게 높아집니다.

const sensors = new Map<string, Sensor>();
const s: Sensor = sensors.get(sensorId);

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

다음은 Map을 좀 더 깔끔하게 사용한 코드입니다. Sensors 사용자는 제네릭스가 사용되었는지 여부에 신경 쓸 필요가 없습니다. 아래에서 보듯, 제네릭스의 사용 여부는 Sensors 안에서 결정합니다.

class Sensors {
  private sensors = new Map();

  getByid(id: string) {
    return this.sensors.get(id) as Sensor;
  }
}

경계 인터페이스인 Map을 Sensors 안으로 숨깁니다. 따라서 Map 인터페이스가 변하더라도 나머지 프로그램에는 영향을 미치지 않습니다. 제네릭스를 이용하든 하지 않든 더 이상 문제가 안 됩니다. Sensors 클래스 안에서 객체 유형을 관리하고 변환하기 때문입니다.

또한 Sensor 클래스는 프로그램에 필요한 인터페이스만 제공합니다. 그래서 코드는 이해하기 쉽지만 오용하기는 어렵습니다. Sensors 클래스는 (나머지 프로그램이) 설계 규칙과 비즈니스 규칙을 따르도록 강제할 수 있습니다.

Map 클래스를 사용할 때마다 위와 같이 캡슐화하라는 소리가 아닙니다. Map을 (혹은 유사한 경계 인터페이스를) 여기저기 남기지 말라는 말입니다. Map과 같은 경계 인터페이스를 이용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의합니다. Map 인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않습니다.

2. 경계 살피고 익히기

외부 코드를 사용하면 적은 시간에 더 많은 기능을 출시하기 쉬워집니다. 만약 외부에서 가져온 패키지를 사용하고 싶다면 어디서 어떻게 시작해야 좋을까요? 외부 패키지 테스트가 우리 책임은 아닙니다. 하지만 우리 자신을 위해 우리가 사용할 코드를 테스트하는 편이 바람직합니다.

타사 라이브러리를 가져왔으나 사용법이 분명치 않다고 가정합니다. 대개는 하루나 이틀 (아니면 더 오랫동안) 문서를 읽으며 사용법을 결정합니다. 그런 다음 우리쪽 코드를 작성해 라이브러리가 예상대로 동작하는지 확인합니다. 때로는 우리 버그인지 라이브러리 버그인지 찾아내느라 오랜 디버깅으로 골치를 앓습니다.

외부 코드를 익히기는 어렵습니다. 외부 코드를 통합하기도 어렵습니다. 두 가지를 동시에 하기는 두 배나 어렵습니다. 다르게 접근하면 어떨까요? 곧바로 우리쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익히면 어떨까요? 짐 뉴커크(Jim Newkirk)는 이를 학습 테스트라고 부릅니다.

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

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

학스 테스트에 드는 비용은 없습니다. 어쩄든 API를 배워야 하기 떄문입니다. 오히려 필요한 지식만 확보하는 손쉬운 방법입니다. 학습 테스트는 이해도를 높여주는 정확한 실험입니다.

학습 테스트는 공짜 이상입니다. 투자하는 노력보다 얻는 성과가 더 큽니다. 패키지 새 버전이 나온다면 학습 테스트를 돌려 차이가 있는지 확인할 수 있고, 패키지의 새 버전으로 이전하기 쉬워집니다. 그렇지 않다면 낡은 버전을 필요 이상으로 오랫동안 사용하려는 유혹에 빠지기 쉽습니다.

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

경계와 관련해 또 다른 유형은 아는 코드와 모르는 코드를 분리하는 경계입니다. 때로는 우리 지식이 경계를 너머 미치지 못하는 코드 영역도 있습니다. 때로는 아렬고 해도 알 수 없습니다. 떄로는 더 이상 대다보지 않기로 결정합니다. 이 때 우리가 통제하지 못하는 곳에서 우리가 바라는 인터페이스를 분리하여 구현하면, 우리가 인터페이스를 전적으로 통제한다는 장점이 생깁니다. 또한 코드 가독성도 높아지고 코드 의도도 분명해집니다.

5. 깨끗한 경계

경계에서는 흥미로운 일이 많이 벌어집니다. 변경이 대표적인 예입니다. 소프트웨어 설계가 우수하다면 변경하는데 많은 투자와 재작업이 필요하지 않습니다. 엄청난 시간과 노력과 재작업을 요구하지 않습니다. 통제하지 못하는 코드를 사용할 때는 너무 많은 투자를 하거나 향후 변경 비용이 지나치게 커지지 않도록 각별히 주의해야 합니다.

경계에 위치하는 코드는 깔끔히 분리합니다. 또한 기대치를 정의하는 테스트 케이스도 작성합니다. 이쪽 코드에서 외부 패키지를 세세하게 알아야 할 필요가 없습니다. 통제가 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 좋습니다. 자칙하면 오히려 외부 코드에 휘둘리고 맙니다.

외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리합니다. 어느 방법이든 코드 가독성이 높아지며, 경계 인터페이스를 사용하는 일관성도 높아지며, 외부 패키지가 변했을 때 변경할 코드도 줄어듭니다.

6. 출처

  • 제목: 클린 코드 - 애자일 소프트웨어 장인 정신
  • 저자: 로버트 C.마틴
  • 옮긴이: 박재호, 이해영
  • 출판사: 인사이트
profile
끊임없이 도전하는 프론트 개발자가 되고자 노력합니다.

0개의 댓글