개발노트

Clean Code | 경계 본문

Computer Science/Software Enginerring

Clean Code | 경계

개발자? 2023. 5. 31. 00:36

목차

8장 경계

  • 외부 코드 사용하기
  • 경계 살피고 익히기
  • 학습 테스트는 공짜 이상이다
  • 아직 존재하지 않는 코드를 사용하기
  • 깨끗한 경계

Intro

시스템에 들어가는 모든 소프트웨어를 "직접" 개발하는 경우는 드물다. 패키지를 사거나, 오픈소스를 이용하거나, 다른 팀이 제공하는 컴포넌트를 사용한다. 어떤 식으로든 이러한 외부 코드를 우리 코드에 깔끔하게 통합해야만 한다.

이 장에서는 소프트웨어 경계를 깔끔하게 처리하는 기법과 기교에 대해 알아보자.

 

소프트웨어 경계에 대해 이해가 되었는가???

이 장에서 말하는 경계는 "외부코드" 또는 "외부 라이브러리", "외부 API" 를 의미하는 듯 하다.

 

외부 코드 사용하기

경계 인터페이스를 이용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의하자. 

 

프레임워크/패키지 제공자는 적용성을 최대한 넓히려 한다. 더 많은 환경에서 돌아가야 더 많은 고객이 구매하니까!

반면, 사용자는 자신의 요구에 집중하는 인터페이스를 원한다. 이런 긴장으로 인해 시스템 경계(boundary)에서 문제가 생길 소지가 많다.

 

java.util.Map 이 대표적인 예시이다. Map 이 제공하는 기능성과 유연성은 확실히 유용하지만 그만큼 위험도 크다.

Map 클래스는 clear() 메서드가 있다. 이로 인해 Map 사용자라면 누구나 Map 내용을 지울 수 있는 권한이 있다. 또한 Map은 저장하는 객체의 유형을 제한하지 않아 설계시 지정한 객체 외에도 저장이 가능하다.

Map sensors = new HashMap();
...
Sensor s = (Sensor) sensors.get(id);

위의 예시는 Map 이 반환하는 Object 를 변환할 책임을 클라이언트에게 넘긴 것으로 깨끗한 코드라 보기 어렵고, 의도도 분명하게 드러나지 않는다.

아래와 같이 제네릭스를 사용하여 가독성을 높일 수 있다. 

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

하지만, 위의 예시도 사용자에게 불필요한 기능까지 제공한다는 문제를 해결하진 못한다.

아래와 같이 변경하자. Sensors 사용자는 제네릭스가 사용되었는지 여부에 신경쓸 필요가 없고, Map 에 직접 접근하지 않기 때문에 Map 이 제공하는 기본기능들을 사용자에게 제한하여 제공 가능하다.

public class Sensors {
	// 경계 인터페이스인 Map 을 Sensors 클래스 안으로 숨김 
    // -> Sensors 클래스 안에서 객체 유형을 관리하고 변환하기 때문에 Map 인터페이스가 변하더라도 나머지 프로그램에는 영향을 주지 않는다.
	private Map sensors = new HashMap();
	
    // 필요한 인터페이스만 제공 가능하다.
	public Sensor getById(String id) {
		return (Sensor) sensors.get(id);
	}
}

 

경계 살피고 익히기

외부 코드를 사용하면 적은 시간에 더 많은 기능을 만들기가 쉬워진다. 

그렇다면 외부 코드를 사용할 때는 어떻게 시작해야 좋을까? 

외부 코드(패키지)에 대한 테스트는 우리 책임일까? 

 

외부코드를 익힐 때 우리 코드를 사용해서 테스트를 할 수 있도록 학습 테스트를 만들자.

학습 테스트란 우리 코드를 작성해서 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성하고 외부 코드를 익히는 방법이다. 학습 테스트는 API를 사용하려는 목적에 초점을 두고, 프로그램에서 사용하려는 방식대로 외부 API 를 호출한다. 통제된 환경에서 API 를 제대로 이해하는지를 확인하는 셈이다.

 

위에 2가지 질문에 대한 답을 달아보면,

 

a. 그렇다면 외부 코드를 사용할 때는 어떻게 시작해야 좋을까? 

→ 학습 테스트를 이용하여 익히고 적용하자

 

b. 외부 코드(패키지)에 대한 테스트는 우리 책임일까? 

→ 우리 책임이 아니다. 그러나 우리 자신을 위해 우리가 사용할 코드는 테스트하는 편이 바람직하다.

 

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

학습 테스트에 드는 비용은 없다. 어쨌든 API를 배워야 하기 때문이다. 오히려 필요한 지식만 확보하는 손쉬운 방법이다.

학습 테스트는 이해도를 높여주는 정확한 실험이다.

 

학습 테스트는 패키지가 예상대로 도는지 확인할 때 사용한다. 새 버전이 우리 코드오 호환되지 않으면 학습 테스트가 이 사실을 곧바로 밝혀낸다. 이를 통해 패키지의 새 버전으로의 업데이트가 쉬워진다. 이런 환경이 구축되어 있지 않으면 낡은 버전을 필요 이상으로 오랫동안 사용하려는 유혹에 빠지기 쉽다.

 

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

경계와 관련해 또 다른 유형은 아는 코드와 모르는 코드를 분리하는 경계다. 

우리가 바라는 인터페이스를 구현하면 우리가 인터페이스를 전적으로 통제한다는 장점이 생긴다. 또한 코드 가독성도 높아지고 코드 의도도 분명해진다.

 

예시) 외부에서 개발할 "예정인(아무것도 정의되지 않은 상태)" 송신기 모듈의 요구 기능 : 지정한 주파수를 이용해 이 스트림에서 들어오는 자료를 아날로그 신호로 전송하라.

출처) https://hirlawldo.tistory.com/142

송신기 개발팀이 아직 API를 설계하지 않았으므로 위와 같이 자체적으로 인터페이스를 정의했다. 

Transmitter 라는 간단한 클래스를 만든 후 transmit 이라는 메서드를 추가했다. Transmit 인터페이스는 주파수와 자료 스트림을 입력으로 받았다. 즉, 우리가 바라는 인터페이스였다.

따라서 우리가 통제하지 못하고 정의되지도 않은 송신기 API 에서 CommunicationsController 를 분리할 수 있었다.

 

송신기 개발팀이 API를 정의한 후에는 TransmitterAdapter 를 구현해서 간극을 매꿨다. Adapter 패턴으로 API 사용을 캡슐화하여 API가 바뀔 때 수정할 코드를 한 곳으로 모았다.

 

이와 같은 설계는 테스트도 쉽다.

적절한 FakeTransmitter 클래스를 사용하면 CommunicationsController 클래스를 테스트할 수 있다.

Transmitter API 인터페이스가 나온 다음 경계 테스트 케이스를 생성해 우리가 API를 올바로 사용하는지도 테스트 할 수 있다.

 

깨끗한 경계

  • 경계는 변경이 많다. 변경에는 비용이 따른다. 경계는 통제하지 못하는 코드이기 때문에 변경 비용을 예측하기 힘들다. 그러니 경계의 변경에 대한 비용이 커지지 않도록 각별히 주의해야 한다.
  • 경계에 위치하는 코드는 깔끔히 분리한다. 경계의 기대값에 대한 테스트 케이스도 작성한다. 통제가 불가능한 외부 패키지에 의존하는 대신 통제가 가능한 우리 코드에 의존하는 편이 훨씬 좋다.
  • 외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자. 새로운 클래스로 경계를 감싸거나, Adapter 패턴을 이용해서 우리가 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환하자. 이는 코드 가독성을 높이고, 경게 인터페이스 사용에 대한 일관성을 높일뿐 아니라 외부 패키지가 변경됐을 때 변경해야 할 코드도 줄여준다.

 

이 내용은 로버트 C.마틴 의 『Clean Code』 를 보고 정리하였습니다. 문제시 삭제하겠습니다.
반응형

'Computer Science > Software Enginerring' 카테고리의 다른 글

Clean Code | 클래스  (2) 2023.05.24
Clean Code | 오류 처리  (0) 2023.05.24
Clean Code | 객체와 자료구조  (0) 2023.05.24
Clean Code | 주석  (0) 2023.05.24
Clean Code | 함수  (2) 2023.05.21
Comments