모르지 않다는 것은 아는것과 다르다.

Books

클린코드 8장

채마스 2022. 4. 23. 21:26

개요

  • 개발을 하면서 모든 기능을 직접 개발하는 경우는 거의 불가능이다.
  • 때때로는 패키지를 사기도하고, 오픈 소스를 사용하는 경우도 많다.
  • 이번 장에서는 소프트웨어 경계를 깔끔하게 처리하는 기법과 기교를 살펴보려고 한다.

 

외부 코드 사용하기

  • 인터페이스 제공자와 인터페이스 사용자 사이의 경계를 정확히 파악하는 것은 중요하다.
  • 패키지 제공자는 다양한 환경에서 고객의 요구사항을 충족시키기 위해서 적용성을 최대한 넓히려고 노력한다.
  • 반면에 사용자는 자신의 요구에 집중할 수 있는 인터페이스를 원한다.
  • 정리 하자면, 제공자는 폭넓게 제공하고 사용자는 자신의 요구사항에 맞는(폭 좁게) 기능을 제공받기를 원하기 때문에 이 둘간의 간극으로 인해서 문제가 생길 소지가 있다.

 

외부 코드의 대표적인 예시 -> java.util.Map

  • Map 은 대표적인 외부 코드이다.
  • Map 의 기능은 아래와 같다.
    • clear() void - Map
    • containsKey(Object key) boolean - Map
    • containsValue(Object value) boolean - Map
    • entrySet() Set - Map
    • equals(Object o) boolean - Map
    • get(Object key) Object - Map
    • getClass() Class <? extends Object> - Object
    • hasCode() int - Map
    • isEmpty() boolean - Map
    • keySet() Set - Map
    • notify() void - Object
    • notifyAll() void - Object
    • put(Object key, Object value) Object - Map
    • putAll(Map t) void - Map
    • remove(Object key) Object - Map
    • size() int - Map
    • toString() String - Object
    • values() Collection - Map
    • wait() void - Object
    • wait(long timeout) void - Object
    • wait(long timeout, int nanos) void - Object
  • Map이 제공하는 기능성과 유연성은 확실히 유용하지만 그만큼 위험도 크다.
  • 예를 들어, Map의 clear() 메서드는 Map의 사용자라면 누구나 Map 내용을 지울 권한이 있다.
  • 뿐만아니라, Map에는 객체 유형을 제한하지 않기 때문에 어떤 타입이든 다 넣을 수 있다.
Map sensors = new HashMap();
Sensor sensor = (Sensor) sensors.get(sensorId);
  • 위의 코드에서 올바른 타입으로 객체를 반환하는 책임은 Map 을 사용하는 클라이언트에게 있다.
  • 그렇기 때문에 제네릭을 사용해서 아래와 같이 수정하는 것이 좋다.
Map<String, Sensor> sensors = new HashMap<Sensor>();
Sensor sensor = sensors.get(sensorId);
  • 하지만 더 좋은 방법은 아래와 같이 캡슐화를 하는 것이다.
public class Sensors {
    private Map sensors = new HashMap();

    public Sensor getById(String id) {
        return (Sensor) sensors.get(id);
    }
    // 이하 생략
}
  • 캡슐화를 통해서 경계 인터페이스인 Map을 Sensors안으로 숨긴다.
  • 이렇게 된다면, Map인터페이스가 변하더라도 나머지 프로그램에는 영향을 미치지 않는다.
  • 이제 Sensors 클래스 안에서 객체 유형을 관리하고 변환하기 때문에 타입을 신경쓰지 않아도 된다.
  • 또한 Sensors 클래스는 프로그램에 필요한 인터페이스만 제공하기 때문에, 코드를 이해하기 쉽지만 오용하기는 어렵다.
  • 그렇다면, 모든 Map 클래스를 사용할 때마다 캡슐화 해야할까?
  • 물론아니다 -> Map을 여기저기 넘기지 말아야 한다.
  • 다시 말해서 Map과 같은 경계 인터페이스를 이용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다.

 

경계 살피고 익히기

  • 외부 코드를 사용하면 적은 시간에 더 많은 기능을 출시할 수 있다.
  • 외부 패키지의 기능이 우리의 책임은 아니지만 우리 자신을 위해 우리가 사용할 코드를 테스트하는 편이 바람직하다.
  • 타사로 부터 가져온 라이브러리는 문서를 충분히 읽어 사용법을 숙지하고 충분히 테스트하는 것이 좋은 습관이다.
  • 그렇지 않으면 추후에 버그가 발생했을때, 이것이 우리의 버그인지 라이브러리의 버그인지 찾는데 오랜 시간을 투자해야 하기 때문이다.
  • 짐 뉴커크 간단한 테스트 케이스를 작성해 외부 코드를 익히것을 학습 테스트라고 부른다.
  • 추후 새로운 버전이 나왔을때, 학습 테스트는 코드가 호환되는지를 빠르게 파악할 수 있는 도구가 된다.

 

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

  • 만약 아직 API 도 정의 되어있지않고, 내가 잘 아는 분야도 아닌 영역을 개발해야한다면 어떻게 해야할까?
  • 모든 API 가 정의되고 내가 그 분야를 완벽히 이해할 때까지 개발을 멈춰야할까? -> 당연히 아니다.
  • 예를들어, 내가 잘알지 못하는 송신기 기능에 대해서 개발을 해야한다고 가정하자.
  • 먼저, 만들어지기 바라는 송신기 인터페이스를 자체적으로 정의한다.
  • 그 이후에 송신기 기능에 대한 API 가 정의된 후에 Adapter를 구현를 구현해서 내가 구현한 인터페이스와 정의된 API 사이의 간극을 매운다.
  • 그리고 API 사용을 캡슐화 해 API가 바뀔 때 수정할 코드를 한곳으로 모은다.
  • 이와 같은 설계는 테스트에도 아주 용이하다.

 

깨끗한 경계

  • 경계에서는 흥미로운 일이 많이 벌어진다. -> 변경할 일이 많기 때문이다.
  • 소프트웨어 설계가 우수하다면 변경하는데 많은 투자와 재작업이 필요하지 않다.
  • 그 이유는 이미 충분한 테스트를 거쳤고, 변경에 열려있는 설계를 했기때문이다. -> 변경이 무섭지 않을 것이다.
  • 통제하지 못하는 코드를 사용할 때는 너무 많은 투자를 하거나 향후 변경 비용이 지나치게 커지지 않도록 각별히 주의해야 한다.
  • 새로운 클래스로 경계를 감싸거나 아니면 ADAPTER 패턴을 이용해 우리가 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환하자.
  • 두 방법 모두 코드 가독성이 높아지며, 경계 인터페이스를 사용하는 일관성도 높아지며, 외부 패키지가 변했을 때 변경할 코드도 줄어든다.
  • 다시한번 정리하면, 통제 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 좋다는 것을 기억하는 것이 중요하다.




REFERENCES

  • 클린코드 8장

'Books' 카테고리의 다른 글

클린코드 10장  (0) 2022.04.30
클린코드 9장 (단위 테스트)  (0) 2022.04.30
클린코드 6장 (객체와 자료구조)  (0) 2022.04.23
클린코드 4장 (주석)  (0) 2022.04.23
클린코드 3장 (함수)  (0) 2022.04.17