[개발자 원칙] 제어할 수 없는 것에 의존하지 않기_❶ 코드 설계에 적용하기

앞선 글에서는 현실 세계의 속성과 소프트웨어 간 의존 관계를 이야기해보았습니다. 현실 세계에 적용한 첫 번째 사례를 들어보겠습니다.

앞의 글 보기 : 제어할 수 없는 것에 의존하지 않기

  1. 사례) 코드 설계에 적용하기 <- 이번 글
  2. 사례) 이직에 적용하기 <- 앞으로 공개할 글
  3. 사례) 조직과 매니징에 적용하기 <- 앞으로 공개할 글

코드 설계에 적용하기

‘제어할 수 없는 것에 의존하지 않기 원칙’을 코드에 적용해보겠습니 다. 테스트 코드의 중요성에 모두 공감할 겁니다. 테스트 코드를 작성하 다 보면 개인의 테스트 코드 작성 역량과 무관하게 테스트 코드 작성이 어려울 때가 있습니다. 코드 자체가 테스트하기에 적당하지 않은 코드일 때입니다. 예를 들어 다음과 같이 도메인 로직을 품고 있는 클래스가 있다고 가정하겠습니다.

export default class Order {
    ...
    discount() {
        const now = LocalDateTime.now();    // 현재 시간을 반환하는 메서드
        if (now.dayOfWeek() == DayOfWeek.SUNDAY) {
            this._amount = this._amount * 0.9
        }
    }
}

이 코드는 결과를 예측하기 어려운, 즉 테스트하기 어려운 코드 입니다. 이유는 실행할 때마다 달라지는 값, 현재 시간 생성 함수 LocalDateTime.now()를 사용하고 있기 때문입니다. 이 메서드의 테스트 코드를 예를 들어 작성해보겠습니다.

it('일요일에는 주문 금액이 10% 할인된다', () => {
    const sut = Order.of(10_000, OrderStatus.APPROVAL);
    
    sut.discount();
    
    expect(sut.amount).toBe(9_000);
});

이 테스트는 매주 일요일에 수행할 때만 통과할 수 있습니다. 같은 일 요일에 수행하더라도 일요일 중 언제 수행하냐에 따라 테스트 대상인 discount의 결과가 달라집니다. 테스트 대상인 discount가 언제나 같은 결과를 보장하지 않기 때문에 테스트 코드 작성이 어렵습니다. 이 코드를 테스트하려면 LocalDateTime.now()를 목킹(Mocking)해야만 수행이 가능한 데, 이 역시 쉽지 않습니다. 그렇다면 이 코드를 어떻게 테스트하기 쉬운 코드로 개선할 수 있을까요? 제어하기 어려운 코드인 현재 시간 메서드를 외부에서 주입받도록 수정하면 됩니다.

export default class Order {
    ...
    // 현재 시간 메서드(now)를 밖에서 주입받음
    discountWith(now: LocalDateTimw) { 
        if (now.dayOfWeek() == DayOfWeek.SUNDAY) {
            this._amount = this._amount * 0.9
        }
    }
}

그러면 다음과 같이 제어할 수 없는 시간이라는 값을 내가 원하는 값으로 지정해서 테스트를 작성할 수 있게 됩니다.

it('일요일에는 주문 금액이 10% 할인된다', () => {
  const sut = Order.of(10_000, OrderStatus.APPROVAL);
  const now = LocalDateTime.of(2022,8,14,10,15,0); // 2022-08-14 10:15:00 시로 고정
  sut.discountWith(now);

  expect(sut.amount).toBe(9_000);
});

제어할 수 없는 값인 now()를 메서드 인수로 빼도록 변경하는 순간 Order.discountWith() 메서드는 항상 같은 결과를 뱉어내고, 테스트 역시 항상 일관된 결과를 출력하게 됩니다. 물론 타입스크립트(TypeScript)처럼 함수 인수에 기본값을 선언하는 방법을 지원하는 언어라면 기존의 사용성을 유지하면서 제어할 수 없는 코드에 대한 의존성을 줄일 수 있습니다.

export default class Order {
    ...
    // 인수가 없으면 기본값인 LocalDate.now() 사용
    discountWith(now = LocalDateTime.now()) { 
        if (now.dayOfWeek() == DayOfWeek.SUNDAY) {
            this._amount = this._amount * 0.9
        }
    }
}

정리하면 다음과 같습니다.

  • 제어할 수 없는 값에 의존하는 코드들을 최대한 멀리한다.
  • 주요 비즈니스 로직은 모두 제어할 수 있는 값만 의존하게 해 테스트 코드 작성이 쉬 운 형태로 구성한다.

메서드/함수가 제어할 수 없는 값에 의존하지 않을수록 항상 같은 결과 를 반환하는 부수효과가 적은 메서드/함수가 됩니다. 아직까지 코드를 작 성할 때 제어할 수 없는 값에 의존하는 코드를 작성한다면 한 번 적용해 보시길 바랍니다. 그간 답답했던 문제를 해결하게 될 겁니다.

다음 글에서 다시 만나뵙겠습니다. 길 글을 읽어주셔서 감사드립니다.

 

 

 

 


 

다음 글에서 다시 만나뵙겠습니다. 긴 글을 읽어주셔서 감사드립니다.

이동욱(향로)

인프랩(인프런) CTO. 10여 년 동안 SI, 인터넷 포털, O2O 스타트업, 에듀테크 등 분야에서 개발자, 리드 엔지니어로 활동했습니다. 누적 조회수 800만 기술 블로그 ‘기억보단 기록을’에 기술을 공유하고 있으며, 개발 유튜브 채널 ‘개발바닥’에 개발에 대한 여러 생각을 공유합니다.

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내