최근에 나는 코드의 가독성에 문제가 생겼다.
나는 작업을 수행하고 나중에 참조 할 수 있도록이 작업의 ID를 나타내는 문자열을 반환했습니다 (Windows에서 OpenFile과 비슷한 핸들을 반환하는). 사용자는 나중에이 ID를 사용하여 작업을 시작하고 완료를 모니터링합니다.
상호 운용성 문제로 인해 ID는 임의의 문자열이어야했습니다. 이것은 다음과 같이 매우 불명확 한 서명을 가진 메소드를 작성했습니다.
public string CreateNewThing()
이로 인해 리턴 유형의 의도가 명확하지 않습니다. 이 문자열을 다른 유형으로 감싸서 그 의미를 더 명확하게 만듭니다.
public OperationIdentifier CreateNewThing()
형식은 문자열 만 포함하며이 문자열을 사용할 때마다 사용됩니다.
이러한 작동 방식의 장점은보다 형식적인 안전성과 명확한 의도라는 것이 명백하지만, 관용적이지 않은 훨씬 더 많은 코드와 코드를 생성합니다. 한편으로 나는 추가 된 안전을 좋아하지만 많은 혼란을 야기합니다.
안전상의 이유로 클래스에서 단순 유형을 랩핑하는 것이 좋습니다.
답변
같은 프리미티브, string
또는 int
, 비즈니스 도메인의 의미가 없습니다. 이로 인한 직접적인 결과는 제품 ID가 예상 될 때 실수로 URL을 사용하거나 가격을 기대할 때 수량을 사용할 수 있다는 것 입니다.
그렇기 때문에 Object Calisthenics 과제 는 다음과 같은 규칙 중 하나로 프리미티브 래핑을 제공합니다.
규칙 3 : 모든 기본 요소 및 문자열 랩
Java 언어에서 int는 실제 객체가 아닌 기본 요소이므로 객체와 다른 규칙을 따릅니다. 객체 지향이 아닌 구문과 함께 사용됩니다. 더 중요한 것은 int 자체는 스칼라 일 뿐이므로 의미가 없습니다. 메소드가 int를 매개 변수로 사용하는 경우 메소드 이름은 의도를 표현하는 모든 작업을 수행해야합니다. 동일한 방법으로 매개 변수로 시간이 걸리면 진행 상황을 훨씬 쉽게 확인할 수 있습니다.
같은 문서는 추가적인 이점이 있다고 설명합니다.
Hour 또는 Money와 같은 작은 물체는 다른 수업에서 흩어 졌을 행동을 넣을 수 있는 확실한 장소를 제공 합니다.
실제로 프리미티브가 사용되는 경우 일반적으로 해당 유형과 관련된 코드의 정확한 위치를 추적하는 것은 매우 어렵고, 종종 코드 중복 이 심 합니다 . Price: Money
수업 이 있으면 내부에서 범위 검사를 찾는 것이 당연합니다. 대신, A는 경우 int
(악화하는 double
) 범위를 확인해야합니다 매장 제품 가격에 사용됩니까? 제품? 리베이트? 카트?
마지막으로, 문서에 언급되지 않은 세 번째 이점은 기본 유형을 비교적 쉽게 변경할 수 있다는 것입니다. 오늘 나의 경우 ProductId
가 short
그 기본 유형으로 나중에 내가 사용해야하는 int
대신에, 기회는 전체 코드베이스를 확장 할 것입니다 변경할 수있는 코드이다.
단점은 Object Calisthenics 연습의 모든 규칙에 동일한 주장이 적용된다는 것 입니다. 경우 Product
포함 ProductPrice
에서 상속하는 PositivePrice
상속되는 Price
차례로에서 상속 Money
이는 청소하지 아키텍처, 오히려 한 가지를 찾기 위해, 메인테이너는 수십 때마다 파일을 열어야합니다 완전히 엉망이다.
고려해야 할 또 다른 요점은 추가 클래스를 작성하는 비용 (코드 라인 측면에서)입니다. 래퍼가 변경 불가능한 경우 (일반적으로 있어야 함) C #을 가져 오면 래퍼 내에 최소한 있어야합니다.
- 속성 getter,
- 지원 분야
- 백킹 필드에 값을 할당하는 생성자
- 사용자 정의
ToString()
, - XML 문서 주석 ( 많은 줄 을 만듭니다 ),
- A
Equals
및GetHashCode
재정의 (또한 많은 LOC).
그리고 결국, 관련이있을 때 :
- DebuggerDisplay의 속성,
==
및!=
연산자 의 재정의- 결국 암시 적 변환 연산자에 과부하가 발생하여 캡슐화 된 유형과의 원활한 변환
- 코드 계약 (세 가지 속성을 가진 다소 긴 방법 인 불변 포함)
- XML 직렬화, JSON 직렬화 또는 데이터베이스와 값을 저장 /로드하는 동안 사용되는 여러 변환기.
간단한 래퍼의 백 LOC는 상당히 래퍼이므로 이러한 래퍼의 장기 수익성을 완전히 확신 할 수 있습니다. Thomas Junk 이 설명하는 범위 개념 은 특히 여기에 관련됩니다. ProductId
코드베이스 전체 에서 사용되는 100 개의 LOC를 작성하면 매우 유용합니다. 단일 메서드 내에서 세 줄을 만드는 코드 조각에 대해이 크기의 클래스를 작성하는 것이 훨씬 더 의심됩니다.
결론:
-
(1) 실수를 줄이거 나 (2) 코드 중복의 위험을 줄이거 나 (3) 나중에 기본 유형을 변경하는 데 도움이되는 경우 응용 프로그램의 비즈니스 영역에서 의미가있는 클래스의 기본 랩핑을 수행하십시오.
-
코드에 자동으로 찾을 수있는 모든 기본을 포장하지 마십시오 사용하는 많은 경우가있다
string
거나int
완벽하게 괜찮가.
실제로에서에서 대신 클래스 public string CreateNewThing()
인스턴스를 반환하면 도움이 될 수 있지만 다음을 수행 할 수도 있습니다.ThingId
string
-
Id<string>
클래스 의 인스턴스 , 즉 기본 유형이 문자열임을 나타내는 일반 유형의 객체를 리턴 합니다. 많은 유형을 유지 관리 할 필요없이 가독성을 얻을 수 있습니다. -
Thing
클래스 의 인스턴스를 반환합니다 . 사용자에게 ID 만 필요한 경우 다음을 사용하여 쉽게 수행 할 수 있습니다.var thing = this.CreateNewThing(); var id = thing.Id;
답변
범위 를 경험의 원칙으로 사용합니다 . 그러한 범위를 생성하고 소비하는 범위가 좁을 values
수록이 값을 나타내는 객체를 생성 할 가능성이 줄어 듭니다.
다음 의사 코드 가 있다고 가정 해보십시오.
id = generateProcessId();
doFancyOtherstuff();
job.do(id);
범위가 매우 제한되어 있으며 유형을 만드는 데 의미가 없습니다. 그러나 한 레이어에서이 값을 생성하여 다른 레이어 (또는 다른 객체)로 전달하면 해당 유형을 작성하는 것이 좋습니다.
답변
정적 타입 시스템은 데이터의 부정확 한 사용을 막기위한 것입니다.
이를 수행하는 유형의 명백한 예가 있습니다.
- 당신은 UUID의 달을 얻을 수 없습니다
- 두 개의 문자열을 곱할 수 없습니다.
더 미묘한 예가 있습니다
- 책상의 길이를 사용하여 비용을 지불 할 수 없습니다
- 다른 사람의 이름을 URL로 사용하여 HTTP 요청을 할 수 없습니다.
double
가격과 길이 모두 에 사용 하거나 string
이름과 URL 모두에 사용하려는 유혹이있을 수 있습니다 . 그러나 그렇게하면 멋진 유형 시스템이 파괴되고 이러한 오용이 언어의 정적 검사를 통과 할 수 있습니다.
Newton-seconds와 혼동되는 파운드-초 는 런타임에 결과 가 좋지 않을 수 있습니다 .
이것은 특히 문자열에 문제가 있습니다. 이들은 종종 “범용 데이터 유형”이됩니다.
주로 컴퓨터와의 텍스트 인터페이스에 익숙하며 이러한 휴먼 인터페이스 (UI)를 프로그래밍 인터페이스 (API)로 확장하는 경우가 많습니다 . 우리는 문자로 34.25 생각 34.25 . 우리는 날짜를 05-03-2015 문자로 생각 합니다. UUID를 75e945ee-f1e9-11e4-b9b2-1697f925ec7b 문자로 생각 합니다.
그러나이 정신 모델은 API 추상화에 해를 끼칩니다.
말이나 언어는 글로 쓰거나 말한 것처럼 내 생각의 메커니즘에서 아무런 역할을하지 않는 것 같습니다.
앨버트 아인슈타인
마찬가지로 텍스트 표현은 유형 및 API를 디자인하는 데 아무런 역할을해서는 안됩니다. 조심하십시오 string
! (및 기타 지나치게 일반적인 “기본”유형)
유형은 “어떤 작업이 의미가 있는지”를 전달합니다.
예를 들어, 한 번 클라이언트에서 HTTP REST API로 작업했습니다. REST는 올바르게 완료되면 관련 엔티티를 가리키는 하이퍼 링크가있는 하이퍼 미디어 엔티티를 사용합니다. 이 클라이언트에서는 엔터티 유형 (예 : 사용자, 계정, 가입)뿐만 아니라 해당 엔터티에 대한 링크도 입력되었습니다 (UserLink, AccountLink, SubscriptionLink). 링크는 래퍼에 Uri
지나지 않았지만 별도의 유형으로 인해 AccountLink를 사용하여 사용자를 가져올 수 없었습니다. 모든 것이 명백 Uri
하거나 더 나빴다면 string
이러한 실수는 런타임에만 발견됩니다.
마찬가지로 상황에 따라 한 가지 목적으로 만 사용되는 데이터가 Operation
있습니다. 다른 용도로 사용해서는 안되며, Operation
우리가 만든 임의의 문자열로 를 식별하려고 시도해서는 안됩니다 . 별도의 클래스를 만들면 코드에 가독성과 안전성이 추가됩니다.
물론 모든 좋은 것을 과도하게 사용할 수 있습니다. 치다
-
코드에 얼마나 선명하게 추가됩니까?
-
사용 빈도
데이터의 “유형”(추상적 인 의미에서)이 고유 한 목적으로 그리고 코드 인터페이스간에 자주 사용되는 경우, 자세한 정보 대신 별도의 클래스가 될 수 있습니다.
답변
안전상의 이유로 클래스에서 단순 유형을 랩핑하는 것이 좋습니다.
때때로.
이것은 string
보다 구체적인 대신 사용하여 발생할 수있는 문제의 무게를 측정해야하는 경우 중 하나입니다 OperationIdentifier
. 그들의 심각성은 무엇입니까? 그들의 가능성은 무엇입니까?
그런 다음 다른 유형을 사용하는 비용을 고려해야합니다. 사용하기가 얼마나 고통 스럽습니까? 얼마나 많은 일을해야합니까?
경우에 따라 멋진 콘크리트 유형을 사용하면 시간과 노력을 절약 할 수 있습니다. 다른 경우에는 문제가되지 않습니다.
일반적으로 이런 종류의 일은 오늘날보다 더 많이 이루어져야한다고 생각합니다. 도메인에 무언가를 의미하는 엔터티가있는 경우 해당 엔터티가 비즈니스와 함께 변경 / 성장할 가능성이 높으므로 해당 유형을 자체 유형으로 사용하는 것이 좋습니다.
답변
나는 일반적으로 프리미티브와 문자열에 대한 유형을 여러 번 만들어야한다는 데 동의하지만 위의 답변은 대부분의 경우 유형을 만드는 것이 좋습니다.
- 공연. 여기서 실제 언어를 참조해야합니다. C #에서 클라이언트 ID가 짧고 클래스로 래핑하면 메모리가 많이 생깁니다. 메모리는 64 비트 시스템에서 8 바이트이므로 이제 힙에 할당되므로 속도가 빨라집니다.
- 유형에 대해 가정 할 때. 클라이언트 ID가 짧고 어떤 방식으로 그것을 포장하는 논리가있는 경우 일반적으로 이미 유형을 가정합니다. 모든 곳에서 이제 추상화를 해제해야합니다. 그것이 하나의 장소라면, 별 문제가되지 않습니다. 만약 그것이 모든 곳에 있다면, 당신은 당신이 기본을 사용하는 시간의 절반을 찾을 수 있습니다.
- 모든 언어에 typedef가있는 것은 아닙니다. 그리고 아직 작성되지 않은 코드와 이미 작성된 언어의 경우, 그러한 변경을하는 것은 버그를 유발할 수있는 큰 작업이 될 수 있습니다.
- 경우에 따라 가독성 이 떨어 집니다. 이것을 어떻게 인쇄합니까? 이것을 어떻게 확인합니까? null을 확인해야합니까? 유형의 정의를 자세히 설명해야하는 모든 질문이 있습니까?
답변
아니요, “모든 것”에 대한 유형 (클래스)을 정의해서는 안됩니다.
그러나 다른 답변에서 알 수 있듯이 종종 그렇게 하는 것이 유용합니다. 코드를 작성, 테스트 및 유지 관리 할 때 적합한 유형 또는 클래스 가 없기 때문에 의식적으로 가능한 경우 의식이 너무 심하게 느껴지도록 해야합니다. 저에게 너무 많은 마찰은 여러 개의 프리미티브 값을 단일 값으로 통합하거나 값을 확인해야 할 때입니다 (즉, 프리미티브 유형의 모든 가능한 값 중 유효한 값이 ‘암시 적 유형’).
내 코드에서 너무 많은 디자인을 담당하기 위해 귀하의 질문에 제기 한 것과 같은 고려 사항을 발견했습니다 . 필요 이상으로 더 많은 코드를 작성하는 것을 의도적으로 피하는 습관을 개발했습니다. 코드에 대해 좋은 (자동화 된) 테스트를 작성하기를 희망합니다. 코드를 리팩토링하고 코드를 리팩토링하고 유형 또는 클래스를 추가하면 코드 의 지속적인 개발 및 유지 관리에 순이익을 제공 할 수 있습니다 .
Telastyn의 답변 과 Thomas Junk의 답변은 모두 관련 코드의 컨텍스트와 사용법에 대해 아주 좋은 지적입니다. 단일 코드 블록 (예 : 메소드, 루프, using
블록) 내에서 값 을 사용하는 경우 기본 유형을 사용하는 것이 좋습니다. 반복적으로 그리고 다른 많은 장소에서 일련의 값을 사용하는 경우 기본 유형을 사용하는 것이 좋습니다. 그러나 값 세트를 자주 사용하고 광범위하게 사용할수록 해당 값 세트가 기본 유형으로 표시되는 값과 더 밀접하게 일치할수록 값을 클래스 또는 유형으로 캡슐화하는 것을 고려해야합니다.
답변
표면적으로는 작업을 식별하기 만하면됩니다.
또한 작업이 수행해야 할 작업을 말합니다.
작업 을 시작 하고 완료 를 모니터하기 위해
이것이 “식별 방법”과 같은 방식으로 말하지만, 이것이 동작을 설명하는 속성이라고 말하고 싶습니다. 그것은 나에게 유형 정의처럼 들리는데, 아주 잘 맞는 “명령 패턴”이라는 패턴조차 있습니다. .
그것은 말한다
명령 패턴 […]은 나중에 작업 을 수행 하거나 이벤트를 트리거하는 데 필요한 모든 정보를 캡슐화하는 데 사용됩니다 .
나는 이것이 당신이 당신의 작업으로하고 싶은 것과 비교할 때 매우 유사하다고 생각합니다. (두 따옴표로 굵게 표시된 문구를 비교하십시오) 문자열을 반환하는 대신 Operation
추상 의미에서에 대한 식별자 , 예를 들어 해당 클래스의 객체에 대한 포인터를 oop로 반환하십시오 .
코멘트에 대해
이 클래스를 명령이라고 부르고 그 안에 논리를 갖지 않는 것이 좋습니다.
아닙니다. 패턴은 매우 추상적 이라는 것을 기억하십시오 , 그들은 다소 메타 있다는 사실 너무 추상적. 즉, 그들은 종종 실제 개념이 아닌 프로그래밍 자체를 추상화합니다. 명령 패턴은 함수 호출의 추상화입니다. (또는 방법) 누군가가 매개 변수 값을 전달한 직후와 실행 직전에 일시 중지 버튼을 누르는 것처럼 나중에 다시 시작할 수 있습니다.
다음은 oop을 고려하고 있지만 그 뒤에있는 동기는 모든 패러다임에 적용되어야합니다. 논리를 명령에 배치하는 것이 나쁜 것으로 간주 될 수있는 몇 가지 이유를 알려 드리고자합니다.
- 명령에 모든 논리가 있으면 부풀어지고 지저분해질 것입니다. “이 모든 기능은 별도의 클래스에 있어야합니다.”라고 생각할 것입니다. 당신은 것입니다 리팩토링 명령에서 논리를.
- 명령에 모든 논리가 있다면 테스트 할 때 테스트하기가 어렵습니다. “거짓 쓰레기, 왜 말도 안되는이 명령을 사용해야합니까? 이 함수 가 1을 뱉어 내는지 여부 만 테스트 하고 싶습니다. 나중에 호출하고 싶지 않습니다. 지금 테스트하고 싶습니다!”
- 명령에 모든 논리가 있다면 완료되면 다시보고하는 것이 이치에 맞지 않습니다. 함수가 기본적으로 동기식으로 실행될 것이라고 생각하면 실행이 완료되면 알림을받는 것이 의미가 없습니다. 어쩌면 작업이 실제로 아바타를 영화 형식으로 렌더링하기 때문에 다른 스레드를 시작 중일 수 있습니다 (라즈베리 파이에는 추가 스레드가 필요할 수 있음). 완료되면 다시보고하면 비동기가 발생하기 때문일 수 있습니다 (단어입니까?). 스레드를 실행하거나 서버에 연결하는 것이 명령에 없어야 할 논리라고 생각합니다. 이것은 다소 논란의 여지가 있으며 논란의 여지가 있습니다. 말이 안된다고 생각되면 의견을 말해주십시오.
요약하자면, 명령 패턴을 사용하면 기능을 객체로 래핑하여 나중에 실행할 수 있습니다. 모듈화를 위해 (기능은 명령을 통해 실행되는지 여부에 관계없이 존재 함), 테스트 가능성 (기능은 명령없이 테스트 할 수 있어야 함) 및 본질적으로 좋은 코드 작성 요구를 표현하는 다른 모든 버즈 단어의 경우 실제 논리를 넣지 않습니다. 명령으로.
패턴은 추상적이기 때문에 실제의 은유를 생각해 내기가 어려울 수 있습니다. 시도는 다음과 같습니다.
“할머니, 12시에 TV의 녹화 버튼을 눌러 채널 1의 심슨을 놓치지 마세요?”
할머니는 그 기록 버튼을 눌렀을 때 기술적으로 어떤 일이 일어나는지 모릅니다. 논리는 다른 곳에 있습니다 (TV). 그리고 그것은 좋은 것입니다. 기능이 캡슐화되고 명령에서 정보가 숨겨집니다. API의 사용자이며 반드시 논리의 일부일 필요는 없습니다.