원시 강박 관념이 언제 코드 냄새가 아닌가?


22

나는 최근 원시 강박 관념 을 코드 냄새로 묘사하는 많은 기사를 읽었습니다 .

원시적 인 집착을 피하는 데에는 두 가지 이점이 있습니다.

  1. 도메인 모델을보다 명확하게 만듭니다. 예를 들어, 우편 번호가 포함 된 문자열 대신 우편 번호에 대해 비즈니스 분석가에게 문의 할 수 있습니다.

  2. 모든 유효성 검사는 응용 프로그램 전체가 아닌 한 곳에 있습니다.

코드 냄새가 나는 경우를 설명하는 기사가 많이 있습니다. 예를 들어, 다음과 같은 포스트 코드에 대한 원시 강박 관념을 제거하면 이점이 있습니다.

public class Address
{
    public ZipCode ZipCode { get; set; }
}

ZipCode의 생성자는 다음과 같습니다.

public ZipCode(string value)
    {
        // Perform regex matching to verify XXXXX or XXXXX-XXXX format
        _value = value;
    }

우편 번호가 사용되는 모든 곳에서 유효성 검사 논리를 적용 하여 DRY 원칙을 위반하게 됩니다.

그러나 다음 개체는 어떻습니까?

  1. 생년월일 : 생각보다 크고 오늘 날짜보다 작은 지 확인하십시오.

  2. 급여 : 0보다 크거나 같은지 확인하십시오.

DateOfBirth 오브젝트와 Salary 오브젝트를 작성 하시겠습니까? 장점은 도메인 모델을 설명 할 때 이에 대해 이야기 할 수 있다는 것입니다. 그러나 유효성 검사가 많지 않기 때문에 과도한 엔지니어링의 경우입니다. 원시적 집착을 제거 할시기와시기를 설명하는 규칙이 있습니까? 아니면 가능하면 항상 그렇게해야합니까?

클래스 대신 유형 별칭을 만들 수 있다고 생각합니다. 위의 점을 이해하는 데 도움이됩니다.


8
"당신은 우편 번호가 사용되는 모든 곳에서 유효성 검사 논리를 적용하는 DRY 원칙을 깨뜨릴 것입니다." 사실이 아닙니다. 데이터가 모듈에 입력 되 자마자 유효성 검사를 수행해야합니다 . 하나 이상의 "진입 지점"이있는 경우 유효성 검사는 재사용 가능한 단위 여야하며 DTO 일 필요는 없습니다.
Timothy Truckle

1
"최근 날짜"와 "오늘 날짜"를 DateOfBirth의 생성자에게 어떻게 확인할 수 있습니까?
Caleth

11
사용자 정의 형식을 만들면 얻을 수있는 또 다른 이점은 형식 안전성입니다. 당신이 가지고 Salary있고 Distance반대 한다면, 당신은 그것들을 서로 바꾸어 사용할 수 없습니다. 둘 다 유형 인 경우 가능합니다 double.
Scroog1

3
@ w0051977 당신은 (내가 이해 한 바와 같이) DTO 생성자에서 유효성을 검사하는 것 이외의 다른 것이 DRY를 위반한다는 것을 암시했다. 실제로 유효성 검사 DTO 외부에 있어야 합니다.
Timothy Truckle

2
나에게 그것은 범위의 문제입니다. 프리미티브에 넓은 범위를 제공하면 오용 및 잘못 처리 할 수있는 여러 가지 방법이 있습니다. 따라서 일반적으로 범위를 좁히고 자하는 한 가지 방법은 내부적으로 개인용으로 저장된 기본을 사용하여 개념을 나타내는 클래스를 구현하여 구현하는 것입니다. 이제 프리미티브의 범위가 좁아 잘못 사용되거나 오용 될 가능성이 거의 없으므로 불변량을 효과적으로 유지할 수 있습니다. 그러나 프리미티브의 범위가 처음에 좁아 졌다면, 이것은 과잉 일 수 있으며 유지해야 할 많은 추가 커플 링과 코드를 소개합니다.

답변:


17

원시 강박 관념은 기본 아이디어를 표현하기 위해 기본 데이터 형식을 사용하고 있습니다.

그 반대의 경우는 "도메인 모델링"또는 "과잉 엔지니어링"입니다.

DateOfBirth 오브젝트와 Salary 오브젝트를 작성 하시겠습니까?

Salary 객체를 도입하는 것은 다음과 같은 이유로 좋은 아이디어가 될 수 있습니다. 도메인 모델에서 숫자는 거의 독립적이지 않으며 거의 ​​항상 차원과 단위를 갖습니다. 우리는 일반적으로 시간이나 질량에 길이를 더하면 유용한 것을 모델링하지 않으며 미터와 피트를 혼합 할 때 좋은 결과를 얻지 못합니다 .

DateOfBirth와 관련하여 아마 고려해야 할 두 가지 문제가 있습니다. 우선, 기본이 아닌 날짜를 만들면 날짜 수학과 관련된 모든 이상한 문제를 집중시킬 수 있습니다. 많은 언어가 즉시 사용 가능한 언어를 제공합니다. 날짜 시간 , java.util.Date . 이것들은 날짜에 대해 도메인에 구애받지 않는 구현이지만 기본 이 아닙니다 .

둘째, DateOfBirth실제로 날짜 시간이 아닙니다. 여기 미국에서, "생년월일"은 문화적 구성 / 법적 소설입니다. 우리는에서 생년월일을 측정하는 경향이 현지 된 명 생년월일; 밥, 캘리포니아에서 태어난 그는, 비록 뉴욕에서 태어난 앨리스보다 "이전"생년월일을해야 할 수도 있습니다 젊은 두의가.

원시적 집착을 제거하지 않을시기와시기를 설명하는 규칙이 있습니까? 가능하면 항상 그렇게해야합니다.

항상 그런 것은 아닙니다. 경계에서 응용 프로그램은 객체 지향적이지 않습니다 . 테스트 에서 동작을 설명하는 데 사용되는 기본 요소를 보는 것이 일반적 입니다.


1
맨 위에 인용 된 후 첫 번째 주석은 비평 행인 것으로 보입니다. 또한 그것은 단지 질문의 주제를 회복시킵니다. 그렇지 않으면 좋은 대답이지만 이것이 정말 산만합니다.
JimmyJames

C # DateTime 또는 java.util.Date는 DateOfBirth에 적합한 기본 유형이 아닙니다.
케빈 클라인

아마 교체 java.util.Datejava.time.LocalDate
Koray 투 케이

7

솔직히 말하면 : 그것은 달려 있습니다.

코드를 과도하게 엔지니어링 할 위험이 항상 있습니다. DateOfBirth와 Salary가 얼마나 널리 사용됩니까? 세 개의 밀접하게 결합 된 클래스에서만 사용합니까, 아니면 응용 프로그램 전체에서 모두 사용됩니까? 하나의 제약 조건을 적용하기 위해 자체 유형 / 클래스로 "단순히"캡슐화하겠습니까, 아니면 실제로 거기에 속하는 더 많은 제약 / 기능을 생각할 수 있습니까?

예를 들어, Salary를 보자 : "Salary"(예 : 다른 통화 처리 또는 toString () 함수)에 대한 작업이 있습니까? 간단한 기본으로 보지 않을 때 급여가 무엇인지, 급여가 자신의 수업이 될 가능성이 높습니다.


타입 별칭이 좋은 대안입니까?
w0051977

@ w0051977 나는 charonx에 동의하고 타입 별칭은 대안이 될 수 있습니다
techagrammer

@ w0051977 유형 별명은 엄격한 타이핑을 시행하고 실수로 "float dollar"(시간당? 주? 월?)가 실수로 할당되는 것을 피하기 위해 특정 값이 무엇인지 명시 적으로 명시하는 것입니다. "부동 급여"(월? 연도?). 그것은 실제로 당신의 필요에 달려 있습니다.
CharonX

@CharonX, 나는 부동 소수점이 아닌 급여에 소수점을 사용해야한다고 생각합니다. 동의하십니까?
w0051977

@ w0051977 만약 당신이 좋은 10 진수 타입이라면, 그 타입이 바람직 할 것입니다. (현재 C ++ 프로젝트를 진행 중이므로 부울, 정수 및 부동 소수점이 내 마음의 최전선에 있습니다)
CharonX

5

가능한 경험 법칙은 프로그램 계층에 따라 달라질 수 있습니다. 를 들어 도메인 일명 (DDD) 엔티티 레이어 잘 수로 (마틴, 2018),이 힘은 "도메인 / 비즈니스 개념을 나타내는 아무것도 프리미티브를 방지하는 방법"을 참조하십시오. 정당화는 OP에 의해 표현 된 바와 같이보다 표현적인 도메인 모델, 비즈니스 규칙 검증, 암시 적 개념을 명시 적으로 만든다 (Evans, 2004).

유형 별칭은 간단한 대안이 될 수 있으며 (Ghosh, 2017) 필요할 때 엔터티 클래스로 리팩토링됩니다. 예를 들어, 우리는 먼저 요구할 수있다 SalaryBE를 >=0하고, 나중에 허용하기로 결정 $100.33333이상 아무것도 $10,000,000(클라이언트를 파산 것이다). Nonnegative대표 Salary및 다른 개념 을위한 기본 요소 의 사용은 이러한 리팩토링을 복잡하게 할 것이다.

프리미티브를 피하면 과도한 엔지니어링을 피할 수 있습니다. 급여생년월일 을 데이터 구조로 결합해야한다고 가정합니다 ( 예 : 더 적은 분석법 파라미터를 갖거나 모듈간에 데이터를 전달). 그런 다음 type 튜플을 사용할 수 있습니다 (Salary, DateOfBirth). 실제로, 프리미티브가있는 튜플 (Nonnegative, Nonnegative)은 정보가없는 반면, 일부 부풀어 오른 부분 class EmployeeData은 필요한 필드를 숨길 수 있습니다. 의 서명은에서 calcPension(d: (Salary, DateOfBirth))보다 더 집중 calcPension(d: EmployeeData)되어 인터페이스 분리 원칙을 위반합니다. 마찬가지로, 전문화가 class SalaryAndDateOfBirth어색해 보이며 아마도 과잉 일 것입니다. 나중에 데이터 클래스를 정의하도록 선택할 수 있습니다. 튜플과 원소 도메인 유형을 사용하면 그러한 결정을 연기 할 수 있습니다.

외부 층 (예를 들어, GUI)에서, 엔티티를 그들의 구성 프리미티브 (예를 들어, DAO에 넣는 것)로 "스트립 (strip)"하는 것이 합리적 일 수있다. 이것은 Martin (2018)에서 주장한 것처럼 도메인 추상화가 외부 레이어로 누출되는 것을 방지합니다.

참고 문헌
E. Evans, "도메인 기반 디자인", 2004
D. Ghosh, "기능 및 반응성 도메인 모델링", 2017
RC Martin, "청결한 아키텍처", 2018


모든 참조에 대해 +1
w0051977

4

원시 강박 관념 이나 건축 우주 비행사 로 고통받는 것이 더 낫 습니까?

두 경우 모두 병리학 적이며, 어떤 경우에는 추상화가 너무 적어서 반복을 일으키고 사과를 오렌지색으로 잘못 착각하는 경우가 있으며 다른 경우에는 이미 그만두고 일을 끝내는 것을 잊어 버렸습니다. .

거의 항상 그렇듯이, 여러분은 잘 고려 된 중간 방법 인 검토를 원합니다.

속성에는 유형 외에도 이름이 있습니다. 또한 주소를 구성 부분으로 분해하면 항상 같은 방식으로 수행하면 너무 제한적일 수 있습니다. 모든 세계가 NY 시내에있는 것은 아닙니다.


3

Salary 클래스가 있다면 ApplyRaise와 같은 메소드를 가질 수 있습니다.

반면에 ZipCode 클래스는 유효성 검사가 중복되는 것을 피하기 위해 내부 유효성 검사가 필요하지 않습니다. ZipCodeValidator 클래스가 삽입 될 수 있으므로 시스템이 미국과 영국 주소에서 모두 실행되는 경우 올바른 유효성 검사기 및 AUS 주소를 처리해야 할 때 새 유효성 검사기를 추가하면됩니다.

또 다른 관심사는 EntityFramework를 통해 데이터베이스에 데이터를 써야하는 경우 Salary 또는 ZipCode를 처리하는 방법을 알아야합니다.

지능적 클래스가 어떻게되어야하는지에 대한 명확한 답은 없지만, 검증과 같은 비즈니스 로직을 데이터 클래스가 순수한 데이터 인 비즈니스 로직 클래스로 옮기는 경향이 있다고 말할 것입니다. EntityFramework로 더 잘 작동합니다.

유형 별명을 사용하는 경우 구성원 / 속성 이름은 내용에 필요한 모든 정보를 제공해야하므로 유형 별명을 사용하지 않습니다.


타입 별칭이 좋은 대안입니까?
w0051977

2

(질문이 실제로 무엇인가)

원시 유형의 사용이 코드 냄새가 아닌 경우는 언제입니까?

(대답)

매개 변수에 규칙이없는 경우 기본 유형을 사용하십시오.

다음과 같은 기본 유형을 사용하십시오.

htmlEntityEncode(string value)

다음과 같은 목적으로 객체를 사용하십시오.

numberOfDaysSinceUnixEpoch(SimpleDate value)

후자의 예는 그 안에 규칙, 즉, 객체가 SimpleDate구성되어 Year, Month하고 Day. 이 경우 Object를 사용하면 SimpleDate유효한 개념을 객체 내에 캡슐화 할 수 있습니다.


1

이 질문의 다른 곳에서 제공되는 전자 메일 주소 또는 우편 번호의 정식 예제 외에도 원시 강박 관념을 벗어난 리팩토링이 엔티티 ID에 특히 도움이 될 수 있습니다 ( https://andrewlock.net/using-strongly-typed-entity 참조) -ids-to-avoid-primitive-obsession-part-1 / .NET에서 수행하는 방법에 대한 예제).

메소드에 다음과 같은 서명이 있었기 때문에 버그가 발생하는 횟수를 잃었습니다.

int leaveId = 12345;
int submitterId = 23456;
int approverId = 34567;

SubmitLeaveApplication(leaveId, approverId, submitterId);

public void SubmitLeaveApplication(int leaveId, int submitterId, int approverId) {
  // implementation here
}

잘 컴파일하고 단위 테스트에 엄격하지 않으면 통과 할 수 있습니다. 그러나 이러한 엔티티 ID를 도메인 특정 클래스로 리팩터링하면 컴파일 타임 오류가 발생합니다.

LeaveId leaveId = 12345;
SubmitterId submitterId = 23456;
ApproverId approverId = 34567;

SubmitLeaveApplication(leaveId, approverId, submitterId);

public void SubmitLeaveApplication(LeaveId leaveId, SubmitterId submitterId, ApproverId approverId) {
  // implementation here
}

메소드가 최대 10 개 이상의 매개 변수, 모든 int데이터 유형 ( Long Parameter List 코드 냄새를 염두에 두지 않음)으로 확장되었다고 상상해보십시오 . AutoMapper와 같은 것을 사용하여 도메인 오브젝트와 DTO간에 교환하고 리팩토링을하면 더 악화됩니다. 자동 매핑에 의해 선택되지 않습니다.


0

우편 번호가 사용되는 모든 곳에서 유효성 검사 논리를 적용하여 DRY 원칙을 위반하게됩니다.

반면, 여러 국가 및 다른 우편 번호 시스템을 다룰 때, 해당 국가를 모르면 우편 번호를 확인할 수 없습니다. 따라서 ZipCode수업은 국가를 저장해야합니다.

그러나 국가를 Address우편 번호의 일부와 우편 번호의 일부 (확인 용) 로 별도로 저장 합니까?

  • 그렇게하면 DRY도 위반하는 것입니다. DRY 위반이라고 부르지 않더라도 (각 인스턴스가 다른 목적을 제공하기 때문에) 두 국가 값이 다를 때 (논리적으로 절대로해서는 안되는 버그에 대한 문을 여는 것 외에도) 추가 메모리를 불필요하게 차지합니다. 있다).
    • 또는 두 데이터 포인트를 항상 동일하게 유지하기 위해 두 데이터 포인트를 동기화해야하므로,이 데이터를 실제로 단일 포인트에 저장해야하므로 목적을 달성 할 수 없습니다.
  • 그렇지 않으면 ZipCode수업이 아니라 Address수업이 다시 포함됩니다 string ZipCode.

예를 들어, 우편 번호가 포함 된 문자열 대신 우편 번호에 대해 비즈니스 분석가에게 문의 할 수 있습니다.

장점은 도메인 모델을 설명 할 때 이에 대해 이야기 할 수 있다는 것입니다.

정보에 특정 변수 유형이있는 경우 비즈니스 분석가와 대화 할 때마다 해당 유형을 언급해야한다는 기본 주장을 이해하지 못합니다.

왜? 왜 단순히 "우편 번호"에 대해 이야기하고 특정 유형을 완전히 생략 할 수 없습니까? 부동산 유형이 대화와 본질적인 관계가있는 비즈니스 분석가 (기술이 아님)와 어떤 종류의 토론을하고 있습니까?

내가 온 곳의 우편 번호는 항상 숫자입니다. 선택의 여지가 있으므로 int또는로 저장할 수 있습니다 string. 우리는 데이터에 대한 수학 연산에 대한 기대가 없기 때문에 문자열을 사용하는 경향이 있지만 비즈니스 분석가가 문자열이 필요하다고 말한 적이 없습니다 . 그 결정은 개발자에게 맡겨져 있습니다 (또는 기술 분석가는 아니지만 내 경험으로는 별다른 문제를 직접 다루지는 않지만).

비즈니스 분석가는 응용 프로그램이 예상 한 작업을 수행하는 한 데이터 유형에 신경 쓰지 않습니다.


검증은 인간이 기대하는 것에 의존하기 때문에 다루기 까다로운 짐승입니다.

우선, 나는 원시적 강박 관념을 피해야하는 이유를 보여주기위한 방법으로 검증 논증에 동의하지 않습니다. 왜냐하면 보편적 인 진실로 데이터가 항상 항상 검증되어야한다는 데 동의하지 않기 때문입니다.

예를 들어, 이것이 좀 더 복잡한 조회라면? 간단한 형식 검사가 아닌 검증에서 외부 API에 연결하여 응답을 기다리는 경우 어떻게해야합니까? ZipCode인스턴스화 하는 모든 객체에 대해 애플리케이션이이 외부 API를 강제로 호출하도록 하시겠습니까?
어쩌면 그것은 엄격한 비즈니스 요구 사항 일 수도 있고 당연히 정당 할 수도 있습니다. 그러나 이것은 보편적 인 진실이 아닙니다. 이것이 솔루션보다 더 많은 부담이되는 사용 사례가 많이있을 것입니다.

두 번째 예로 양식에 주소를 입력 할 때 국가 앞에 우편 번호를 입력하는 것이 일반적입니다. UI에서 즉각적인 유효성 검사 피드백을받는 것이 좋지만 문제의 실제 원인이 (예를 들어) 내 국가는 기본적으로 선택된 국가가 아니므로 잘못된 국가에 대한 유효성 검사가 수행되었습니다.
잘못된 오류 메시지이므로 사용자의주의를 산만하게하고 불필요한 혼란을 초래합니다.

영원한 검증이 보편적 인 진실이 아닌 것과 마찬가지로 나의 예도 아닙니다. 그것은의 문맥 . 일부 응용 프로그램 도메인은 무엇보다도 데이터 유효성 검사가 필요합니다. 다른 도메인은 실제 우선 순위와 충돌하는 번거 로움 (예 : 사용자 경험 또는 결함이있는 데이터를 초기에 저장하여 수정하지 않도록 수정하는 대신 우선 순위 목록)에 우선 순위를 두지 않습니다. 저장)

생년월일 : 생각보다 크고 오늘 날짜보다 작은 지 확인하십시오.
급여 : 0보다 크거나 같은지 확인하십시오.

이러한 유효성 검사의 문제점은 불완전하거나 중복되거나 훨씬 더 큰 문제를 나타냅니다 .

날짜가 생각보다 큰지 확인하는 것은 중복입니다. 마인드는 말 그대로 그것이 가능한 가장 작은 날짜임을 의미합니다. 게다가, 당신은 관련 선을 어디에서 그리나요? 예방 DateTime.MinDate하지만 허용 하는 요점은 무엇입니까 DateTime.MinDate.AddSeconds(1)? 다른 많은 값과 비교할 때 특히 잘못되지 않은 특정 값을 선택합니다.

내 생일은 1978 년 1 월 2 일입니다 (그렇지는 않지만 가정합니다). 그러나 응용 프로그램의 데이터가 잘못되었다고 가정하면 내 생일은 다음과 같습니다.

  • 1978 년 1 월 1 일
  • 1722 년 1 월 1 일
  • 2355 년 1 월 1 일

이 모든 날짜가 잘못되었습니다. 그들 중 어느 것도 다른 것보다 "더 옳은"것은 없습니다. 그러나 유효성 검사 규칙은 이 세 가지 예 하나만 잡습니다 .

또한이 데이터를 사용하는 방법의 컨텍스트를 완전히 생략했습니다. 예를 들어 생일 알림 봇에서 사용하는 경우 잘못된 날짜를 입력하면 특별한 나쁜 결과가 없으므로 유효성 검사가 의미가 없다고 말하고 싶습니다.
반면, 이것이 정부 데이터이고 누군가의 신원을 인증하기 위해 생년월일이 필요한 경우 (그렇지 않으면 누군가의 사회 보장 거부와 같은 나쁜 결과를 초래할 수 있음), 데이터의 정확성이 가장 중요하며 귀하는 완전 해야합니다. 데이터를 확인하십시오. 현재 제안 된 검증이 충분하지 않습니다.

급여의 경우, 음수가 될 수 없다는 몇 가지 상식이 있습니다. 그러나 비 의미적인 데이터가 입력 될 것이라고 현실적으로 예상한다면이 비 의미적인 데이터의 출처를 조사하는 것이 좋습니다. 중요한 데이터를 입력 할 수없는 경우 올바른 데이터 를 입력 할 수 없습니다 .

대신 급여가 응용 프로그램에서 계산되고 어떻게 든 음수 (정확한) 숫자로 끝나는 것이 가능하다면 Math.Max(myValue, 0)유효성 검사에 실패하지 않고 음수를 0으로 바꾸는 것이 더 나은 방법입니다 . 논리가 결과가 음수라고 결정한 경우 유효성 검사에 실패하면 계산을 다시 수행해야한다는 의미이므로 두 번째로 다른 숫자가 나올 것이라고 생각할 이유가 없습니다.
그리고 다른 숫자가 나오면 계산이 일관성이 없으므로 신뢰할 수 없음을 의심하게됩니다.

이것은 유효성 검사가 유용하지 않다는 것은 아닙니다. 그러나 무의미한 유효성 검사는 실제로 문제를 해결하지는 못하고 사람들에게 잘못된 보안 감각을 제공하기 위해 노력해야하기 때문에 좋지 않습니다.


아기가 이미 다음 날로 건너 뛴 시간대에서 태어난 경우 누군가의 생년월일은 실제로 현재 날짜를 지날 수 있습니다. 그리고 병원은“예상 생년월일”을 향후 몇 달이 될 수있는 데이터베이스에 저장할 수 있습니다. 다른 유형을 원하십니까?
gnasher729

@ gnasher729 : 나는 확실하지 않다, 당신이 나에게 동의하고있는 것 같습니다 (확인은 상황에 맞으며 보편적으로 정확하지 않습니다). 아니면 내가 잘못 읽고 있습니까?
Flater
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.