[좋은 코드, 나쁜 코드] 8장. 코드를 모듈화하라

cje·2023년 7월 23일
0
post-thumbnail

모듈화의 주된 목적 중 하나는 코드가 향후에 어떻게 변경되거나 재구성될지 정확히 알지 못한 상태에서 변경과 재구성이 용이한 코드를 작성하는 것이다. 모듈화된 코드는 재사용과 테스트에 더 적합하기 때문에 코드 모듈화는 많은 이점을 가지고 있다.


의존성 주입의 사용을 고려하라

하드 코드화된 의존성은 문제가 될 수 있다

책에서는 클래스로 코드를 설명해놓았지만 함수의 관점으로 보면 다음과 같다.

/* 
	useOnlineMap: 지도의 최신 버전을 가져올지의 여부
    includeSeasonalRoads: 특정 기간에만 개통되는 도로를 지도에 포함할지의 여부
*/
const getSuwonRoadMap = (useOnlineMap: boolean, includeSeasonalRoads: boolean) => {}

const searchRoute = () => {
	const roadMap = getSuwonRoadMap(false, true);
  	// ...
}

위 코드에서 searchRoute는 getSuwonRoadMap에 의존되어 항상 수원 지도를 사용할 때 최신 지도를 사용하지 않을 것이며 계절 도로는 제외되지 않을 것이므로 다용도로 사용할 수 없다는 단점이 있다.

이렇게 하드 코드화된 종속성은 바람직하지 않다.

차라리 의존성을 주입해 하드 코드화된 종속성을 없애고 어떠한 로드맵을 구할 수 있다.

interface RoadMap {
	startPoint: LatLong;
  	endPoint: LatLong;
}

const getSuwonRoadMap = (useOnlineMap: boolean, includeSeasonalRoads: boolean): RoadMap => {}
const searchRoute = (roadMap: RoadMap) => {}

searchRoute(getSuwonRoadMap(false, true));

인터페이스에 의존하라

클래스가 어떤 인터페이스를 구현하고 필요한 기능이 그 인터페이스에 모두 정의되어 있으면, 클래스에 직접 의존하기보다는 인터페이스에 의존하는 것이 더 바람직하다.

구체적인 클래스에 의존하면 인터페이스에 의존할 때보다 적응성이 제한되는 경우가 많다.
클래스는 구현 중심적인 방식으로 하위 문제를 해결하는 반면,
인터페이스에 의존하면 더 간결한 추상화 계층과 더 나은 모듈화를 달성할 수 있다.

의존성 역전 원리
보다 구체적인 구현보다는 추상화에 의존하는 것이 낫다.


클래스 상속을 주의하라

두 가지 사물이 진정한 is-a 관계를 갖는다면 상속이 적절할 수 있다.
(예: a car is a vehicle)

상속을 사용할 수 있는 상황에서 구성(composition)을 상속 대신 사용할 수 있다.
클래스를 확장해 사용하기보다 구성을 이용해 클래스를 다른 클래스로부터 구성할 수 있어 상속의 함정을 피하고 모듈화와 내구성이 향상된 코드를 작성할 수 있다.

클래스의 상속을 통해 클래스를 확장한다면 서브 클래스는 슈퍼 클래스의 모든 기능을 상속한다. 서브 클래스는 슈퍼 클래스의 모든 함수에 액세스가 가능해지고 동시에 슈퍼 클래스의 함수가 노출된다는 문제가 발생한다.

즉, 추상화 계층이 복잡해지고 구현 세부 정보가 드러날 수 있다.

상속을 통해 특정 클래스의 일부 기능을 사용하는 것보다 해당 클래스의 인스턴스를 가져 하나의 클래스를 다른 클래스로부터 구성할 수 있다.

상속과 합성
상속은 is-a,
합성은 전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 재사용하는 has-a의 관계이다. 상속 관계는 클래스 사이의 정적 관계이며 부모 클래스 내부에 구현된 코드 자체를 재사용할 때 사용하며, 합성 관계는 객체 사이의 동적 관계를 가지며 부모 클래스에 포함되는 객체의 퍼블릭 인터페이스를 재사용할 때 사용한다.

예를 들어 Car 컴포넌트와 Aircraft 컴포넌트가 있을 때, FlyingCar를 구현하기 위해서 어떤 컴포넌트를 사용해야 할지 고민될 것이다. 그렇다면 Car 컴포넌트의 drive 특징을 useDriving이라는 훅으로 빼내고, Aircraft 컴포넌트의 flying 특징을 useFlying이라는 훅으로 빼내 두 훅을 재사용해 구현할 수 있을 것이다.


클래스는 자신의 기능에만 집중해야 한다

디미터의 법칙
최소한의 지식 원칙(The Principle of Least Knowledge) 을 강조하며 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙이다. 예를 들어 Chaper.getPreude().wordCount()와 같은 코드는 디미터의 법칙을 위반한다.
(Tell, Don’t Ask 정보전문가 패턴)


관련 있는 데이터는 함께 캡슐화하라

프론트 관점에서 이 부분을 설명하자면
컴포넌트에 특정 데이터의 필드들을 나눠 prop으로 전달하지 않고 데이터 자체를 prop으로 넘기는 방식을 떠올릴 수 있겠다.

<Product name={product.name} price={product.price} stock={product.stock} />
<Product product={product} />
<Product {...product} />

이렇게 객체 자체를 넘기면 스펙이 변경되어도 안정적이며, 나중에 유지보수에도 용이하다.
컴포넌트에 prop이 확장될 경우가 있다면 객체 자체를 넘겨 컴포넌트를 사용하는 관점에서 해당 컴포넌트의 구현 세부 사항을 알지 않아도 되는 이점이 있다.

profile
💭

1개의 댓글

comment-user-thumbnail
2023년 7월 23일

정리가 잘 된 글이네요. 도움이 됐습니다.

답글 달기