본문으로 건너뛰기

파라미터 검증

Address.Create(city, street, zipCode) — 3개의 파라미터를 받는 팩토리 메서드입니다. 누군가 리팩토링하면서 zipCode 파라미터를 빼면 어떻게 될까요? 컴파일은 호출부를 고치면 성공하고, 기존 테스트도 수정하면 통과합니다. 하지만 주소의 필수 정보가 빠진 채로 객체가 생성되는, 설계 의도와 다른 코드가 되어버립니다. 이 장에서는 메서드의 파라미터 개수와 타입을 아키텍처 테스트로 검증하여, 팩토리 메서드의 시그니처를 일관되게 유지하는 방법을 학습합니다.

“파라미터 시그니처는 API 계약입니다. 계약 변경을 테스트로 감지하면, 의도하지 않은 시그니처 변경이 코드 리뷰를 통과하는 것을 막을 수 있습니다.”

  1. 정확한 파라미터 개수 검증

    • RequireParameterCount(n)으로 팩토리 메서드의 시그니처 고정
    • 파라미터가 추가되거나 제거되면 즉시 테스트 실패
  2. 최소 파라미터 개수 검증

    • RequireParameterCountAtLeast(n)으로 하한선 보장
    • 여러 클래스에 공통 적용할 때 유용
  3. 파라미터 타입 검증

    • RequireFirstParameterTypeContaining으로 첫 번째 파라미터의 타입 확인
    • RequireAnyParameterTypeContaining으로 특정 타입의 파라미터 존재 여부 확인
  • Address.Create: 정확히 3개의 string 파라미터 강제
  • Coordinate.Create: double 타입 파라미터 존재 검증
  • 모든 팩토리 메서드에 최소 1개 이상 파라미터 보장

3개의 문자열 파라미터를 받는 팩토리 메서드를 가집니다.

public sealed class Address
{
public string City { get; }
public string Street { get; }
public string ZipCode { get; }
private Address(string city, string street, string zipCode)
{
City = city;
Street = street;
ZipCode = zipCode;
}
public static Address Create(string city, string street, string zipCode)
=> new(city, street, zipCode);
}

2개의 double 파라미터를 받는 팩토리 메서드를 가집니다.

public sealed class Coordinate
{
public double Latitude { get; }
public double Longitude { get; }
private Coordinate(double latitude, double longitude)
{
Latitude = latitude;
Longitude = longitude;
}
public static Coordinate Create(double latitude, double longitude)
=> new(latitude, longitude);
}
[Fact]
public void AddressCreate_ShouldHave_ThreeParameters()
{
ArchRuleDefinition
.Classes()
.That()
.ResideInNamespace("ParameterValidation.Domains")
.And()
.HaveNameEndingWith("Address")
.ValidateAllClasses(Architecture, @class => @class
.RequireMethod("Create", m => m
.RequireParameterCount(3)),
verbose: true)
.ThrowIfAnyFailures("Address Parameter Count Rule");
}
[Fact]
public void FactoryMethods_ShouldHave_AtLeastOneParameter()
{
ArchRuleDefinition
.Classes()
.That()
.ResideInNamespace("ParameterValidation.Domains")
.ValidateAllClasses(Architecture, @class => @class
.RequireMethod("Create", m => m
.RequireParameterCountAtLeast(1)),
verbose: true)
.ThrowIfAnyFailures("Factory Method Minimum Parameter Rule");
}
[Fact]
public void AddressCreate_ShouldHave_StringFirstParameter()
{
ArchRuleDefinition
.Classes()
.That()
.ResideInNamespace("ParameterValidation.Domains")
.And()
.HaveNameEndingWith("Address")
.ValidateAllClasses(Architecture, @class => @class
.RequireMethod("Create", m => m
.RequireFirstParameterTypeContaining("String")),
verbose: true)
.ThrowIfAnyFailures("Address First Parameter Type Rule");
}
[Fact]
public void CoordinateCreate_ShouldHave_DoubleParameter()
{
ArchRuleDefinition
.Classes()
.That()
.ResideInNamespace("ParameterValidation.Domains")
.And()
.HaveNameEndingWith("Coordinate")
.ValidateAllClasses(Architecture, @class => @class
.RequireMethod("Create", m => m
.RequireAnyParameterTypeContaining("Double")),
verbose: true)
.ThrowIfAnyFailures("Coordinate Double Parameter Rule");
}

다음 표는 파라미터 검증 API와 각각의 검증 방식을 요약합니다.

API검증 대상사용 시나리오
RequireParameterCount(n)정확히 n개시그니처가 고정된 팩토리 메서드
RequireParameterCountAtLeast(n)최소 n개 이상여러 클래스에 공통 하한선 적용
RequireFirstParameterTypeContaining(fragment)첫 번째 파라미터의 타입 이름파라미터 순서까지 강제할 때
RequireAnyParameterTypeContaining(fragment)하나 이상의 파라미터 타입 이름특정 타입의 파라미터가 있는지만 확인할 때
구분개수 검증타입 검증
강도파라미터 추가/제거 감지타입 변경 감지
유연성정확한 수 또는 최솟값문자열 기반 매칭
조합단독 사용 가능개수 검증과 함께 사용 권장

Q1: RequireParameterCount와 RequireParameterCountAtLeast는 어떻게 구분해서 사용하나요?

섹션 제목: “Q1: RequireParameterCount와 RequireParameterCountAtLeast는 어떻게 구분해서 사용하나요?”

A: RequireParameterCount(3)은 정확히 3개여야 통과합니다. Address.Create처럼 시그니처가 확정된 메서드에 적합합니다. RequireParameterCountAtLeast(1)은 1개 이상이면 통과하므로, “파라미터 없는 팩토리 메서드는 허용하지 않는다”는 공통 규칙을 여러 클래스에 일괄 적용할 때 유용합니다.

Q2: RequireFirstParameterTypeContaining과 RequireAnyParameterTypeContaining의 차이는?

섹션 제목: “Q2: RequireFirstParameterTypeContaining과 RequireAnyParameterTypeContaining의 차이는?”

A: RequireFirstParameterTypeContaining은 첫 번째 파라미터만 검사하여 파라미터 순서까지 강제합니다. RequireAnyParameterTypeContaining은 순서와 관계없이 해당 타입의 파라미터가 하나라도 존재하면 통과합니다. 예를 들어 Coordinate.Create(double, double)에서 RequireAnyParameterTypeContaining("Double")은 어느 위치든 double 파라미터가 있으면 성공합니다.

Q3: 타입 이름 매칭에서 “String”과 “string”은 다른가요?

섹션 제목: “Q3: 타입 이름 매칭에서 “String”과 “string”은 다른가요?”

A: 매칭은 CLR 타입의 전체 이름(System.String, System.Double 등)에 대해 수행됩니다. C# 키워드(string, double)가 아니라 CLR 타입 이름의 일부를 사용해야 합니다. 대소문자가 구분되므로 "String"은 매칭되지만 "string"은 매칭되지 않습니다.

Q4: 파라미터 개수 검증과 타입 검증을 함께 사용할 수 있나요?

섹션 제목: “Q4: 파라미터 개수 검증과 타입 검증을 함께 사용할 수 있나요?”

A: 네, 체이닝으로 조합할 수 있습니다. m.RequireParameterCount(3).RequireFirstParameterTypeContaining("String")처럼 작성하면 “정확히 3개의 파라미터가 있고, 첫 번째는 String 타입”이라는 복합 규칙을 적용할 수 있습니다.


파라미터 시그니처까지 검증할 수 있게 되었습니다. 다음 장에서는 클래스의 프로퍼티와 필드를 검증하여, 도메인 클래스의 불변성이 깨지지 않도록 public setter 금지와 인스턴스 필드 금지 규칙을 강제하는 방법을 학습합니다.

4장: 프로퍼티와 필드 검증