Clean Code · Ch.11

Clean Systems

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

애플리케이션 객체를 제작하고 의존성을 연결하는 과정은 별도로 분리되어야 한다.

❌ Lazy Init 안티패턴
public Service getService() {
    if (service == null)
        service = new MyServiceImpl(...);
    return service;
}
🔗

구현에 직접 의존: getService()가 MyServiceImpl의 생성자 인수에 의존한다.

🧪

테스트 어려움: service가 null인 경우와 아닌 경우를 모두 테스트해야 한다.

🌀

책임 혼재: 설정 로직과 실행 로직이 한 메서드에 섞여 있다.

원칙 — 설정 논리는 일반 실행 논리와 분리해야 한다. 시작 단계라는 관심사를 분리하라.
방법 1: Main 분리
🏗️

생성 코드는 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었다고 가정한다. 시스템의 나머지는 객체 생성 과정을 전혀 모른다.

main()
→ 객체 생성 →
Application
→ 사용
방법 2: Factory 분리
🏭

객체가 생성되는 시점을 어플리케이션이 결정하는 기법. 어플리케이션은 언제 만들지는 알지만 구체적인 방법은 모른다.

App
→ Factory.Create() →
Object
방법 3: 의존성 주입 DI
// ❌ 직접 생성
public class OrderService {
    private readonly IPaymentGateway _gateway = new StripeGateway();
}

// ✅ DI — 생성은 컨테이너가
public class OrderService {
    private readonly IPaymentGateway _gateway;
    public OrderService(IPaymentGateway gateway) {
        _gateway = gateway;
    }
}
DI 컨테이너가 요청이 들어올 때 필요한 객체를 만들고 의존성을 설정하여 반환한다.

관심사 분리와 Scaling

컨테이너에 종속된 코드는 테스트도 확장도 어렵다.

❌ EJB-style — 컨테이너 종속
public class Bank : IBank
{
    public void AddAccount(AccountDTO accountDTO)
    {
        // ⚠️ JNDI lookup 같은 역할 (직접 서비스 탐색)
        var accountService = new AccountServiceFactory().Create();
        var account = accountService.Create(accountDTO);
        _accounts.Add(account);
    }

    // ⚠️ 이상한 생명주기 메서드들
    public void SetEntityContext(object ctx) { }
    public void Activate() { }
    public void Passivate() { }
    public void Load() { }
    public void Store() { }
    public void Remove() { }
}
🔗

컨테이너 종속: 비즈니스 로직이 컨테이너에 타이트하게 연결되어 있다. 컨테이너가 제공해야 하는 인터페이스를 반드시 구현해야 한다.

🧪

테스트/상속 어려움: 개발자가 비즈니스 로직과 관계없는 코드까지 억지로 작성해야 한다.

이상적인 방향 — POJO(Plain Old Java/C# Object)처럼 순수한 비즈니스 로직만 담고, 컨테이너 의존성은 프레임워크가 처리하게 위임하라.

횡단 관심사와 AOP

persistence, logging, security 등은 코드 곳곳에 흩어진다. AOP로 모듈화하라.

Cross-cutting 관심사란 — persistence, logging, transaction 등의 동작은 객체 경계를 넘나들며 시스템 전반에 산재하는 경향이 있다. 원칙적으로는 모듈화 가능하지만, 현실은 코드 곳곳에 중복된다.
❌ AOP 없이 — logging 중복
public class OrderService {
    public void PlaceOrder(Order order) {
        _logger.Log("PlaceOrder called");  // 중복 관심사
        _transaction.Begin();              // 중복 관심사
        // 실제 비즈니스 로직
        _orderRepo.Save(order);
        _transaction.Commit();
        _logger.Log("PlaceOrder completed");
    }
}
✅ AOP — Attribute 기반
[Log]
[Transactional]
public void PlaceOrder(Order order) {
    // 순수 비즈니스 로직만
    _orderRepo.Save(order);
}
🎯

Persistency를 예시로 들면, persistency를 적용할 객체와 속성을 선언한 후 영속성 책임을 프레임워크로 위임한다.

🔧

AOP 프레임워크는 해당 코드에 영향을 미치지 않는 상태로 동작 방식을 변경한다.

✂️

관심사의 분리 → 각 모듈은 자신의 책임만 집중하게 된다.

Ch.11 핵심 요약

시스템을 깨끗하게 유지하는 네 가지 핵심 전략

🏗️

생성/사용 분리

시스템 시작과 실행은 다른 관심사. main에서 구성, 앱에서 사용.

💉

의존성 주입

객체 생성 책임을 DI 컨테이너에 위임하라. 코드는 추상에만 의존.

🔌

POJO 유지

비즈니스 로직을 컨테이너와 분리하라. 테스트와 확장이 쉬워진다.

🔀

AOP로 횡단 관심사

logging, transaction은 비즈니스 코드가 아니다. AOP로 분리하라.