티스토리 뷰

반응형

 

이 챕터에서는 높은 추상화 수준, 즉, 시스템 수준에서도 깨끗함을 유지하는 방법을 살펴봐요.

 

시스템 제작과 시스템 사용을 분리하라

제작(construction)과 사용(use)은 매우 다른 개념이에요. 소프트웨어 시스템은 준비 과정과 (준비 과정 이후 이어지는) 런타임 로직을 분리해야 해요.

 

시작 단계는 모든 앱이 풀어야 할 관심사예요. 대다수 앱은 준비 과정 코드를 주먹구구식으로 구현할 뿐만 아니라 런타임 로직과 마구 뒤섞어요. 

// 책 예시를 그대로 Swift로 표현
func getService() -> Service {
	if service == nil {
    	service = MyServiceImpl(...)
    }
    return service
}


// Swift 식 코드
func service() -> Service {
	if let service = service {
    	return service
    }
    return MyServiceImpl(...)
}

위의 코드 방식을 초기화 지연(Lazy Initialization) 혹은 계산 지연(Lazy Evalueation)이라고 하고, 이 기법에는 여러 장점이 있어요.

우선, 실제로 필요할 때까지 객체를 생성하지 않으므로 불필요한 부하가 걸리지 않아요. 그만큼 앱이 시작하는 시간이 빨라져요. 

둘째, 어떤 경우에도 null 포인터를 반환하지 않아요.

 

하지만 getService 메서드가 MyServiceImpl과 생성자 인수에 명시적으로 의존해요. 런타임 로직에서 MyServiceImpl 객체를 전혀 사용하지 않더라도 의존성을 해결하지 않으면 컴파일이 안돼요.

테스트 역시 문제예요. MyServiceImpl이 무거운 객체라면 단위 테스트에서 getService 메서드를 호출하기 전에 적절한 테스트 전용 객체를 service 필드에 할당해야 해요. 또한 일반 런타임 로직에다 객체 생성 로직을 섞어놓은 탓에 모든 실행 경로도 테스트해야 해요. 작게나마 단일 책임 원칙(Single Responsibility Principle)을 깨고 있어요.

무엇보다 MyServiceImpl이 모든 상황에 적합한 객체인지 모르고 있어요. getService 메서드를 포함한 클래스가 전체 문맥을 알 필요가 있을까요? 

 

한 번 정도의 사용은 별로 심각한 문제가 아니지만 많은 앱들은 이 같은 기법을 수시로 사용해요.

설정 논리는 일반 실행 논리와 분리해야 모듈성이 높아져요.

 

Main 분리

시스템 생성과 시스템 사용을 분리하는 한 가지 방법으로, 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정해요.

main 함수에서 시스템에 필요한 객체를 생성한 후 이를 앱에 넘겨요. 앱은 그저 객체를 사용할 뿐이에요. 즉, 앱은 main이나 객체가 생성되는 과정을 모르고 모든 객체가 적절히 생성되었다고 가정해요.

 

나의 생각: iOS에서는 이게 어떤 구조일지 감이 잘 오진 않아요. main 함수라면 AppDelegate에서 시스템에 필요한 객체를 직접 생성 후 앱에 넘기는 것인가 생각이 드는데 iOS와 잘 어울리는 구조인지는 모르겠어요. 막연하게 "싱글톤처럼 어디선가 생성되어있는 객체를 사용시점에서 불러만 와서 사용하는 구조인가?" 정도의 느낌을 받았어요.

 

Factory

때로는 객체가 생성되는 시점을 앱이 결정할 필요도 생겨요. 이럴 경우, 추상 팩토리(Abstract Factory) 패턴을 사용하면 객체 생성 시점은 앱이 결정하지만 생성하는 코드는 앱이 모르게 돼요.

예를 들어, 주문 관리 시스템에서 앱이 LineItem 인스턴스를 생성해 Order에 추가한다고 하면 main은 LineItem을 직접 생성하는 게 아니라 LineItemFactory를 구현하여 LineItem을 얻게 돼요. 이렇게 되면 main에서 필요한 시점에 LineItem을 생성하지만 구체적인 생성 로직에 의존할 필요는 없어요. 

 

나의 생각: 현업에서 팩토리를 통해 코드를 관리했어요. Assembly에 생성할 객체를 등록하고 특정 객체에서 Factory를 구현하여 Factory를 통해 객체를 사용하는 구조로 작업했어요. 

 

의존성 주입(Dependency Injection)

사용과 제작을 분리하는 강력한 메커니즘 하나가 의존성 주입이에요. 의존성 주입은 제어 역전 기법을 의존성 관리에 적용한 메커니즘이에요. 제어 역전에서는 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 넘겨요. 새로운 객체는 넘겨받은 책임만 맡으므로 단일 책임 원칙을 지키게 돼요. 의존성 관리 맥락에서 객체는 의존성 자체를 인스턴스로 만드는 책임은 지지 않아요. 

 

나의 생각: 현업에서 Pure DI라는 라이브러리를 통해 DI를 관리했어요. 주로 위의 팩토리와 함께 사용했는데 Dependency를 등록할 때 생성할 인스턴스가 필요한 객체는 Factory로 등록했어요. 구현할 때에는 항상 함께 구현하다 보니 구별하기가 쉽지 않았는데 지금 생각해보면 Factory를 통해 시스템 생성과 시스템 사용을 구별하고, 시스템 사용 시 의존성 역전을 위해 Dependency를 등록했다고 판단돼요.

 

 

 

클린코드(Clean Code) by Robert.C.Martin
반응형

'개발 독서' 카테고리의 다른 글

[클린 코드] 완결 - 후기  (0) 2021.12.18
[클린 코드] 12장 - 창발성  (0) 2021.12.15
[클린 코드] 10장 - 클래스  (0) 2021.12.13
[클린 코드] 9장 - 단위 테스트  (0) 2021.12.11
[클린 코드] 8장 - 경계  (0) 2021.12.09
댓글