Introduce Parameter Object: 확장 가능한 리팩터링 패턴

발행: (2025년 12월 29일 오후 11:00 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

위 링크에 있는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL, 마크다운 형식 및 기술 용어는 그대로 유지됩니다.)

Introduction

제가 정기적으로 적용하는 리팩터링 패턴이 하나 있는데, 그 가치는 거의 주목받지 못합니다. 눈에 띄게 인상적이지는 않지만, 코드베이스가 스스로의 무게에 의해 서서히 무너지는 것을 방지합니다.

문제: 긴 매개변수 목록

다음과 같은 메서드 시그니처를 확실히 본 적이 있을 것입니다:

public function createInvoice(
    int $customerId,
    string $currency,
    float $netAmount,
    float $taxRate,
    string $country,
    bool $isReverseCharge,
    ?string $discountCode,
    DateTime $issueDate
): Invoice

처음에는 괜찮아 보이지만, 비즈니스 요구사항이 늘어나면서 호출 지점이 취약해집니다. 긴 매개변수 목록은 단순히 미관상의 문제가 아니라 실제로 다음과 같은 문제를 일으킵니다:

  • 읽기 어려운 메서드 호출
  • 코드 리뷰 시 높은 인지 부하
  • 매개변수 순서 오류가 빈번하게 발생
  • 수십 개의 호출 지점에 파급되는 변경
  • “건드리지 마라” 증후군이라 불리는 두려움 기반 리팩터링

무엇보다도, 매개변수들 사이의 암묵적인 관계를 숨깁니다. 여러 매개변수가 항상 함께 전달되고, 단일 도메인 개념을 나타내며, 동일한 비즈니스 이유로 변경될 때, 시그니처는 도메인 언어를 명확히 표현하지 못합니다.

파라미터 객체 소개

이 패턴은 단순히 인수 개수를 줄이는 것이 아니라, 관련된 데이터를 의미 있는 객체로 묶는 것입니다.

예시 그룹화

// Tax‑related data
float $netAmount,
float $taxRate,
bool  $isReverseCharge,
string $country

// Pricing context
string $currency,
?string $discountCode

작고 집중된 객체

class TaxContext
{
    public function __construct(
        public float $netAmount,
        public float $taxRate,
        public bool $isReverseCharge,
        public string $country
    ) {}
}

class PricingContext
{
    public function __construct(
        public string $currency,
        public ?string $discountCode
    ) {}
}

이전 → 이후

이전

public function createInvoice(
    int $customerId,
    string $currency,
    float $netAmount,
    float $taxRate,
    string $country,
    bool $isReverseCharge,
    ?string $discountCode,
    DateTime $issueDate
): Invoice

이후

public function createInvoice(
    int $customerId,
    PricingContext $pricing,
    TaxContext $tax,
    DateTime $issueDate
): Invoice

동작은 변경되지 않으며; 인터페이스가 더 명확해집니다.

혜택

1. 가독성 있는 호출

Before

$service->createInvoice(
    $customerId,
    'EUR',
    1000,
    0.21,
    'DE',
    false,
    null,
    new DateTime()
);

After

$pricing = new PricingContext('EUR', null);
$tax = new TaxContext(1000, 0.21, false, 'DE');

$service->createInvoice(
    $customerId,
    $pricing,
    $tax,
    new DateTime()
);

매개변수 위치를 해석할 필요가 없습니다.

2. 지역화된 향후 변경

새로운 세금‑관련 요구사항이 나타날 때는 TaxContext만 확장하면 됩니다:

class TaxContext
{
    public function __construct(
        public float $netAmount,
        public float $taxRate,
        public bool $isReverseCharge,
        public string $country,
        public ?string $vatId = null
    ) {}
}

메서드 시그니처는 변경되지 않습니다.

3. 데이터와 가까운 검증 및 동작

class TaxContext
{
    public function isTaxApplicable(): bool
    {
        return !$this->isReverseCharge && $this->taxRate > 0;
    }
}

데이터와 로직을 캡슐화하면 패턴이 시간이 지남에 따라 복리 효과를 발휘합니다.

적용 시점

  • 세 개 이상의 매개변수가 이미 의미 있는 도메인 개념을 형성하고 있을 때.
  • 동일한 인수 그룹이 여러 호출 지점에서 나타날 때.
  • 하나의 비즈니스 변경이 여러 매개변수에 동시에 영향을 미칠 때.

정확한 개수보다 중요한 것은 코드가 인터페이스를 통해 의도를 표현하는 데 어려움을 겪고 있다는 신호입니다.

함정 및 팁

  1. “멍청한 데이터 가방”을 피하세요. 객체가 행동을 전혀 얻지 못한다면, 가치의 일부를 놓친 것입니다.
  2. 점진적으로 리팩터링하세요. 하나의 메서드부터 시작하세요; 이것은 대대적인 변화가 아닙니다.
  3. 객체가 도메인 질문에 답할 수 있도록 하세요. 행동을 전혀 얻지 못하는 파라미터 객체는 종종 리팩터링이 불완전함을 나타냅니다.

결론

파라미터 객체를 도입하는 것은 눈에 띄는 변화는 아니지만, 조용히 개선합니다:

  • 가독성
  • 유지보수성
  • 변경에 대한 탄력성
  • 개발자 신뢰

성숙한 코드베이스에서는 이러한 리팩토링이 가장 중요합니다.

What’s Next?

다음 기사에서는 다음과 같은 “조용한” 리팩터링 패턴을 탐구할 예정입니다:

  • 시간적 결합 리팩터링
  • 도메인 특화 쿼리 객체 추출
  • 검증 로직을 도메인 개념으로 이동

작은 변화, 장기적인 영향.

Back to Blog

관련 글

더 보기 »