큰 부울 표현식이 동일한 표현식이 술어 메소드로 분류 된 것보다 더 읽기 쉽습니다? [닫은]


63

이해하기 쉬운 것, 큰 부울 명령문 (quite complex) 또는 동일한 명령문을 술어 메소드 (세분화 할 추가 코드)로 분류 한 것입니까?

옵션 1, 큰 부울 표현식 :

    private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
    {

        return propVal.PropertyId == context.Definition.Id
            && !repo.ParentId.HasValue || repo.ParentId == propVal.ParentId
            && ((propVal.SecondaryFilter.HasValue && context.SecondaryFilter.HasValue && propVal.SecondaryFilter.Value == context.SecondaryFilter) || (!context.SecondaryFilter.HasValue && !propVal.SecondaryFilter.HasValue));
    }

옵션 2, 조건은 술어 메소드로 분류됩니다.

    private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
    {
        return MatchesDefinitionId(context, propVal)
            && MatchesParentId(propVal)
            && (MatchedSecondaryFilter(context, propVal) || HasNoSecondaryFilter(context, propVal));
    }

    private static bool HasNoSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
    {
        return (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);
    }

    private static bool MatchedSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
    {
        return (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
    }

    private bool MatchesParentId(TValToMatch propVal)
    {
        return (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
    }

    private static bool MatchesDefinitionId(CurrentSearchContext context, TValToMatch propVal)
    {
        return propVal.PropertyId == context.Definition.Id;
    }

메서드 이름을 주석으로 볼 수 있기 때문에 두 번째 방법을 선호하지만 코드의 기능을 이해하기 위해 모든 메서드를 읽어야하므로 코드의 의도를 추상화하기 때문에 문제가 있음을 이해합니다.


13
옵션 2는 Martin Fowler가 리팩토링 책에서 권장하는 것과 유사합니다. 또한 메소드 이름은 모든 임의 표현식의 의도로 사용되며 메소드의 컨텐츠는 시간이 지남에 따라 변경 될 수있는 구현 세부 사항 일뿐입니다.
프로그래머 1

2
실제로 같은 표현입니까? "또는"이 "그리고"보다 우선 순위가 낮습니다. 어쨌든 두 번째는 당신의 의도를 말하고 다른 하나는 기술입니다.
thepacker

3
@thepacker가 말한 것. 첫 번째 방법으로 실수를했다는 사실은 첫 번째 방법이 대상 고객의 매우 중요한 부문에서 쉽게 이해할 수 없다는 아주 좋은 단서입니다. 당신 자신!
Steve Jessop

3
옵션 3 : 어느 쪽도 마음에 들지 않습니다. 두 번째는 어리석게 장황하며 첫 번째는 두 번째와 동일하지 않습니다. 괄호가 도움이됩니다.
David Hammen 2016 년

3
이 학자가 될 수 있지만이없는 어떤 if 코드 중 하나 블록에서 문을. 귀하의 질문은 부울 식 에 관한 것 입니다.
Kyle Strand

답변:


88

이해하기 쉬운 것

후자의 접근법. 이해하기 쉬울뿐만 아니라 작성, 테스트, 리팩토링 및 확장도 더 쉽습니다. 각각의 필수 조건은 안전하게 분리되고 자체 방식으로 처리 될 수 있습니다.

코드를 이해하기 위해 모든 메소드를 읽어야하기 때문에 문제가됩니다

메소드의 이름이 올바르게 지정 되어도 문제가되지 않습니다. 실제로 메소드 이름이 조건의 의도를 설명하므로 이해하기가 더 쉬울 것입니다.
구경꾼 if MatchesDefinitionId()보다 설명이 더if (propVal.PropertyId == context.Definition.Id)

[개인적으로 첫 번째 접근법은 내 눈을 아프게한다.]


12
분석법 이름이 양호하면 이해하기도 쉽습니다.
BЈовић

그리고 그것들을 의미 있고 짧게 만드십시오. 20 + 문자 방법 이름이 내 눈을 아프다. MatchesDefinitionId()경계선입니다.
Mindwin

2
@Mindwin 메소드 이름을 "짧게"유지하고 의미를 유지하는 것 중에서 선택하는 경우 매번 후자를 사용한다고합니다. 짧지 만 좋지만 가독성이 떨어집니다.
Ajedi32

@ Ajedi32는 메소드 이름에서 메소드가 수행하는 작업에 대한 에세이를 쓰거나 문법적으로 건전한 메소드 이름을 가질 필요가 없습니다. 작업 그룹이나 조직 전체에서 약어 표준을 명확하게 유지하더라도 짧은 이름과 가독성에는 문제가되지 않습니다.
Mindwin

Zipf의 법칙을 사용하십시오 : 사용 을 방해 하기 위해 더 장황하게하십시오 .
hoosierEE

44

이 술어 함수가 사용될 유일한 위치 인 경우, bool대신 로컬 변수를 사용할 수도 있습니다 .

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    bool matchesDefinitionId = (propVal.PropertyId == context.Definition.Id);
    bool matchesParentId = (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
    bool matchesSecondaryFilter = (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
    bool hasNoSecondaryFilter = (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);

    return matchesDefinitionId
        && matchesParentId
        && matchesSecondaryFilter || hasNoSecondaryFilter;
}

이것들은 또한 더 잘 읽을 수 있도록 더 세분화되고 순서가 바뀔 수 있습니다.

bool hasSecondaryFilter = propVal.SecondaryFilter.HasValue;

그런 다음의 모든 인스턴스를 교체합니다 propVal.SecondaryFilter.HasValue. 즉시 눈에 띄는 한 hasNoSecondaryFilter가지는 부정 HasValue속성 matchesSecondaryFilter에 논리 AND 를 사용하는 반면 부정 부정에 논리 AND 를 사용 한다는 것입니다. HasValue따라서 정반대가 아닙니다.


3
이 솔루션은 꽤 좋고 비슷한 코드를 많이 작성했습니다. 매우 읽기 쉽습니다. 내가 게시 한 솔루션과 비교할 때 단점은 속도입니다. 이 방법을 사용하면 조건에 관계없이 조건부 테스트를 수행합니다. 내 솔루션에서 처리 된 값을 기반으로 작업을 크게 줄일 수 있습니다.
BuvinJ

5
@BuvinJ 여기에 표시된 것과 같은 테스트는 상당히 저렴해야하므로 일부 조건이 비싸거나 성능에 민감한 코드가 아니라면 더 읽기 쉬운 버전으로 이동하십시오.
svick

1
@svick 의심 할 여지없이 이것은 대부분의 시간에 성능 문제를 야기하지 않을 것입니다. 여전히 가독성을 잃지 않고 작업을 줄일 수 있다면 왜 그렇게하지 않습니까? 나는 이것이 내 솔루션보다 훨씬 더 읽기 쉽다고 확신하지 못한다. 테스트에 자체 문서화 "이름"을 제공합니다. 이는 훌륭합니다 ... 특정 사용 사례와 테스트 자체가 얼마나 이해 가능한지 생각합니다.
BuvinJ

댓글을 추가하면 가독성도 향상 될 수 있습니다.
BuvinJ

@BuvinJ이 솔루션에서 내가 정말로 좋아하는 것은 마지막 줄을 제외한 모든 것을 무시함으로써 그것이 무엇을하고 있는지 빠르게 이해할 수 있다는 것입니다. 나는 이것이 더 읽기 쉽다고 생각합니다.
svick

42

일반적으로 후자가 바람직하다.

통화 사이트를보다 재사용 가능하게 만듭니다. DRY를 지원합니다 (기준이 변경 될 때 변경할 장소가 적고보다 안정적으로 수행 할 수 있음). 그리고 종종 이러한 하위 기준은 다른 곳에서 독립적으로 재사용되어 그렇게 할 수 있습니다.

아, 그리고 이것들은 단위 테스트가 훨씬 쉬워 져서 올바르게 수행했다는 확신을줍니다.


1
그렇습니다. 그러나 repo정적 필드 / 속성, 즉 전역 변수로 나타나는 의 사용을 수정해야 합니다. 정적 메서드는 결정적이어야하며 전역 변수를 사용해서는 안됩니다.
David Arno

3
@DavidArno-그것은 좋지는 않지만, 당면한 질문에 접하는 것 같습니다. 그리고 더 많은 코드 가 없으면 디자인이 그렇게 작동하는 데 반 올바른 이유가 있다는 것은 그럴듯 합니다.
Telastyn

1
예, repo를 신경 쓰지 마십시오. 나는 :) 인터 웹에-그대로 클라이언트 코드를 공유하고 싶지 않은, 약간의 코드를 당황하게했다
윌렘

23

이 두 가지 선택 사이에 있다면 후자가 더 좋습니다. 그러나 이것이 유일한 선택은 아닙니다! 단일 함수를 여러 if로 나누는 것은 어떻습니까? 단일 라인 테스트에서 "단락"을 대략적으로 모방하여 추가 테스트를 피하기 위해 기능을 종료하는 방법을 테스트하십시오.

읽기가 더 쉽습니다 (예를 들어 논리를 다시 확인해야 할 수도 있지만 개념은 적용됩니다).

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    if( propVal.PropertyId != context.Definition.Id ) return false;

    if( repo.ParentId.HasValue || repo.ParentId != propVal.ParentId ) return false;

    if( propVal.SecondaryFilter.HasValue && 
        context.SecondaryFilter.HasValue && 
        propVal.SecondaryFilter.Value == context.SecondaryFilter ) return true;

    if( !context.SecondaryFilter.HasValue && 
        !propVal.SecondaryFilter.HasValue) return true;

    return false;   
}

3
게시 후 몇 초 내에 왜이를위한 공감대를 얻었습니까? downvote 때 의견을 추가하십시오! 이 답변은 빠르게 작동하며 읽기 쉽습니다. 그래서 무엇이 문제입니까?
BuvinJ

2
@BuvinJ : 절대로 아무 문제가 없습니다. 12 개의 괄호와 화면 끝을 지나는 한 줄로 싸울 필요가 없다는 점을 제외하면 원래 코드와 동일합니다. 해당 코드를 위에서 아래로 읽고 즉시 이해할 수 있습니다. WTF 수 = 0
gnasher729 15.20에

1
함수의 끝이 아닌 다른 곳으로 돌아 가면 코드를 읽을 수없고 읽을 수없는 IMO로 만듭니다. 나는 단일 출구 지점을 선호합니다. 이 링크에서 두 가지 좋은 주장이 있습니다. stackoverflow.com/questions/36707/…
Brad Thomas

5
@ 브래드 토마스 나는 단일 출구 지점에 동의 할 수 없습니다. 일반적으로 깊은 중첩 괄호로 연결됩니다. 반환은 경로를 끝내므로 훨씬 쉽게 읽을 수 있습니다.
Borjab

1
@BradThomas Borjab에 전적으로 동의합니다. 깊은 중첩을 피하는 것은 실제로 긴 조건문을 어기는 것보다이 스타일을 더 자주 사용하는 이유입니다. 나는 수많은 중첩으로 코드를 작성하는 데 사용합니다. 그런 다음 하나 또는 두 개 이상의 중첩을 거의 수행하지 않는 방법을 찾기 시작했으며 결과적으로 코드를 읽고 유지 관리하기가 훨씬 쉬워졌습니다. 기능을 종료하는 방법을 찾을 수 있으면 가능한 빨리 종료하십시오! 깊은 중첩과 긴 조건을 피할 수있는 방법을 찾으면 그렇게하십시오!
BuvinJ

10

나는 옵션 2를 더 좋아하지만 하나의 구조적 변화를 제안합니다. 조건부 마지막 줄의 두 검사를 단일 통화로 결합합니다.

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    return MatchesDefinitionId(context, propVal)
        && MatchesParentId(propVal)
        && MatchesSecondaryFilterIfPresent(context, propVal);
}

private static bool MatchesSecondaryFilterIfPresent(CurrentSearchContext context, 
                                                    TValToMatch propVal)
{
    return MatchedSecondaryFilter(context, propVal) 
               || HasNoSecondaryFilter(context, propVal);
}

내가 이것을 제안하는 이유는 두 가지 검사가 단일 기능 단위이고 조건부로 중첩 괄호는 오류가 발생하기 쉽다는 것입니다. 코드를 처음 작성하는 관점과 그것을 읽는 사람의 관점에서. 표현식의 하위 요소가 동일한 패턴을 따르지 않는 경우에 특히 그렇습니다.

MatchesSecondaryFilterIfPresent()조합에 가장 적합한 이름 인지 확실하지 않습니다 . 그러나 더 좋은 것은 즉시 떠오르지 않습니다.


메소드 내부에서 수행되는 작업을 설명하는 것은 실제로 호출을 재구성하는 것보다 낫습니다.
klaar

2

C #에서는 코드가 객체 지향적이지 않습니다. 정적 메소드를 사용하고 있으며 정적 필드처럼 보입니다 (예 :) repo. 정적은 일반적으로 코드를 리팩토링하기 어렵고 테스트하기가 어렵고 재사용 가능성을 저해하며, 질문에 따르면 정적 사용은 객체 지향 구조보다 읽기 쉽고 유지 관리가 쉽지 않습니다.

이 코드를보다 객체 지향 형태로 변환해야합니다. 그렇게 할 때 객체, 필드 등을 비교하는 코드를 넣을만한 합리적인 장소가 있다는 것을 알게 될 것입니다. 그러면 객체를 스스로 비교하도록 요청할 수 있습니다. 그러면 큰 if 문이 간단한 비교 요청 (예 : if ( a.compareTo (b) ) { }모든 필드 비교가 포함될 수 있음)

C #에는 개체와 해당 필드를 비교할 수있는 다양한 인터페이스와 시스템 유틸리티가 있습니다. 명백한 넘어 .Equals방법, 우선, 조사 IEqualityComparer, IEquatable및 유틸리티 등 System.Collections.Generic.EqualityComparer.Default.


0

후자는 확실히 선호되며, 첫 번째 방법으로 사례를 보았으며 거의 ​​항상 읽을 수 없습니다. 나는 첫 번째 방법으로 실수를 저지르고 술어를 술어로 변경하라는 요청을 받았습니다.


0

가독성을 위해 공백을 추가하고 독자가 더 모호한 부분을 읽는 데 도움이되는 의견을 추가하면 두 가지가 거의 동일하다고 말할 수 있습니다.

기억하십시오 : 좋은 주석은 독자 가 코드를 작성할 때 생각 했던 것을 알려줍니다 .

내가 제안한 것과 같은 변화로, 나는 덜 어수선하고 분산되어 있기 때문에 아마도 이전의 접근법과 함께 갈 것입니다. 서브 루틴 호출은 각주와 같습니다. 유용한 정보를 제공하지만 읽기 흐름을 방해합니다. 술어가 더 복잡하다면, 그것들을 구체화 한 개념을 이해할 수있는 덩어리로 만들 수 있도록 별도의 방법으로 나눌 것입니다.


+1해야합니다. 다른 답변을 기반으로 인기있는 의견은 아니지만 생각하기에 좋은 음식. 감사합니다 :)
willem

1
@willem 아니요, +1을받을 자격이 없습니다. 두 가지 접근 방식은 동일하지 않습니다. 추가 의견은 어리 석고 필요하지 않습니다.
BЈовић

2
좋은 코드는 절대 이해할 수있는 주석에 의존하지 않습니다. 실제로 주석은 코드가 가질 수있는 최악의 혼란입니다. 코드 자체를 말해야합니다. 또한 OP가 평가하려는 두 가지 접근 방식은 추가 한 공백의 수에 관계없이 "거의 동일"할 수 없습니다.
wonderbell

주석을 읽는 것보다 의미있는 기능 이름을 갖는 것이 좋습니다. "Clean Code"책에 명시된 바와 같이 주석은 Throw 코드를 표현하지 못합니다. 함수가 훨씬 더 명확하게 설명 할 수있을 때 무엇을하고 있는지 설명하십시오.
Borjab

0

재사용하고 싶은 부분이 있다면, 적절하게 명명 된 기능으로 분리하는 것이 가장 좋습니다.
재사용하지 않아도 조건을보다 잘 구성하고 의미를 나타내는 레이블을 제공 할 수 있습니다 .

이제 첫 번째 옵션을 살펴보고 들여 쓰기와 줄 바꿈이 모두 유용하지도 않고 조건부 구조도 잘 구성되지 않았 음을 인정합시다.

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal) {
    return propVal.PropertyId == context.Definition.Id && !repo.ParentId.HasValue
        || repo.ParentId == propVal.ParentId
        && propVal.SecondaryFilter.HasValue == context.SecondaryFilter.HasValue
        && (!propVal.SecondaryFilter.HasValue || propVal.SecondaryFilter.Value == context.SecondaryFilter.Value);
}

0

첫 번째는 끔찍합니다. ||를 사용하고 있습니다 같은 줄에 두 가지가 있습니다. 그것은 코드의 버그이거나 코드를 난독 화하려는 의도입니다.

    return (   (   propVal.PropertyId == context.Definition.Id
                && !repo.ParentId.HasValue)
            || (   repo.ParentId == propVal.ParentId
                && (   (   propVal.SecondaryFilter.HasValue
                        && context.SecondaryFilter.HasValue 
                        && propVal.SecondaryFilter.Value == context.SecondaryFilter)
                    || (   !context.SecondaryFilter.HasValue
                        && !propVal.SecondaryFilter.HasValue))));

그것은 적어도 절반 정도 형식이 적당합니다 (서식이 복잡하면 if 조건이 복잡하기 때문입니다). 쓰레기 형식과 비교하면 다른 것이 더 좋습니다. 그러나 if 문을 완전히 엉망으로 만들거나 완전히 무의미한 4 가지 방법 중 하나만 극단적으로 할 수있는 것처럼 보입니다.

(cond1 && cond2) || (! cond1 && cond3)는 다음과 같이 쓸 수 있습니다.

cond1 ? cond2 : cond3

혼란을 줄일 수 있습니다. 내가 쓸거야

if (propVal.PropertyId == context.Definition.Id && !repo.ParentId.HasValue) {
    return true;
} else if (repo.ParentId != propVal.ParentId) {
    return false;
} else if (propVal.SecondaryFilter.HasValue) {
    return (   context.SecondaryFilter.HasValue
            && propVal.SecondaryFilter.Value == context.SecondaryFilter); 
} else {
    return !context.SecondaryFilter.HasValue;
}

-4

나는 그 해결책 중 하나를 좋아하지 않으며, 추론하기가 어렵고 읽기가 어렵습니다. 더 작은 방법을 위해 더 작은 방법으로 추상화하는 것이 항상 문제를 해결하는 것은 아닙니다.

이상적으로는 속성을 metaprogrmatically 비교할 것이라고 생각하므로 새 메서드를 정의하거나 분기마다 새 속성 집합을 비교하려는 경우가 없습니다.

나는 C #에 대해 확신하지 못하지만 자바 스크립트에서는 이와 같은 것이 훨씬 나을 것이고 적어도 MatchesDefinitionId 및 MatchesParentId를 대체 할 수 있습니다

function compareContextProp(obj, property, value){
  if(obj[property])
    return obj[property] == value
  return false
}

1
C #에서 이와 같은 것을 구현하는 데 문제가되지 않아야합니다.
스눕

~ 5 호출 compareContextProp(propVal, "PropertyId", context.Definition.Id)의 부울 조합이 폼의 ~ 5 비교의 OP의 부울 조합보다 읽기 쉬운 방법을 보지 못했습니다 propVal.PropertyId == context.Definition.Id. 호출 사이트에서 복잡성을 숨기지 않으면 서 훨씬 더 길고 추가 계층을 추가합니다. (중요하다면, 나는
공감
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.