문제

제가 사용한 모든 언어에서 저를 괴롭히는 문제입니다. if 문이 있지만 조건부 부분에 검사가 너무 많아서 여러 줄로 나누어야 하거나, 중첩된 if 문을 사용하거나, 보기 흉하다는 점을 인정하고 계속 진행해야 합니다. 내 인생과 함께.

나와 같은 문제를 겪고 있는 다른 사람에게 유용할 수 있는 다른 방법이 있습니까?

예를 들어, 모두 한 줄에 있습니다:

if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true)
{

예, 여러 줄:

if (var1 = true && var2 = true && var2 = true
 && var3 = true && var4 = true && var5 = true
 && var6 = true)
{

예제 중첩:

if (var1 = true && var2 = true && var2 = true && var3 = true)
{
     if (var4 = true && var5 = true && var6 = true)
     {
도움이 되었습니까?

해결책

조건을 여러 부울로 분리한 다음 마스터 부울을 조건으로 사용합니다.

bool isOpaque = object.Alpha == 1.0f;
bool isDrawable = object.CanDraw && object.Layer == currentLayer;
bool isHidden = hideList.Find(object);

bool isVisible = isOpaque && isDrawable && ! isHidden;

if(isVisible)
{
    // ...
}

더 나은 점은 다음과 같습니다.

public bool IsVisible {
    get
    {
        bool isOpaque = object.Alpha == 1.0f;
        bool isDrawable = object.CanDraw && object.Layer == currentLayer;
        bool isHidden = hideList.Find(object);

        return isOpaque && isDrawable && ! isHidden;
    }
}

void Draw()
{
     if(IsVisible)
     {
         // ...
     }
}

기능보다는 실제로 의도를 나타내는 변수 이름을 지정해야 합니다.이는 개발자가 코드를 유지 관리하는 데 큰 도움이 될 것입니다.그것은 당신일 수 있습니다!

다른 팁

아직 아무도 이것을 얻지 못했다는 것이 놀랍습니다.이러한 유형의 문제를 위한 리팩토링이 특별히 있습니다:

http://www.refactoring.com/catalog/decomposeConditional.html

여기에서 해결해야 할 두 가지 문제가 있습니다.가독성과 이해성

"가독성" 솔루션은 스타일 문제이므로 해석이 가능합니다.내 취향은 이렇습니다.

if (var1 == true && // Explanation of the check
    var2 == true && // Explanation of the check
    var3 == true && // Explanation of the check
    var4 == true && // Explanation of the check
    var5 == true && // Explanation of the check
    var6 == true)   // Explanation of the check
    { }

아니면 이거:

if (var1 && // Explanation of the check
    var2 && // Explanation of the check
    var3 && // Explanation of the check
    var4 && // Explanation of the check
    var5 && // Explanation of the check
    var6)   // Explanation of the check
    { }

즉, 이러한 종류의 복잡한 검사는 코드를 스캔하는 동안 정신적으로 분석하기가 매우 어려울 수 있습니다(특히 원본 작성자가 아닌 경우).일부 복잡성을 추상화하기 위해 도우미 메서드를 만드는 것을 고려해보세요.

/// <Summary>
/// Tests whether all the conditions are appropriately met
/// </Summary>
private bool AreAllConditionsMet (
    bool var1,
    bool var2,
    bool var3,
    bool var4,
    bool var5,
    bool var6)
{
    return (
        var1 && // Explanation of the check
        var2 && // Explanation of the check
        var3 && // Explanation of the check
        var4 && // Explanation of the check
        var5 && // Explanation of the check
        var6);  // Explanation of the check
}

private void SomeMethod()
{
    // Do some stuff (including declare the required variables)
    if (AreAllConditionsMet (var1, var2, var3, var4, var5, var6))
    {
        // Do something
    }
}

이제 "SomeMethod" 메서드를 시각적으로 스캔할 때 테스트 논리의 실제 복잡성은 숨겨지지만 의미론적 의미는 인간이 높은 수준에서 이해할 수 있도록 보존됩니다.개발자가 세부 사항을 정말로 이해해야 하는 경우 AreAllConditionsMet 메서드를 검사할 수 있습니다.

내 생각에 이것은 공식적으로 "조건부 분해" 리팩토링 패턴으로 알려져 있습니다.Resharper 또는 Refactor Pro와 같은 도구!이런 종류의 리팩토링을 쉽게 할 수 있습니다!

모든 경우에 읽기 쉽고 이해하기 쉬운 코드를 갖는 핵심은 현실적인 변수 이름을 사용하는 것입니다.나는 이것이 인위적인 예라는 것을 이해하지만 "var1", "var2" 등은 ~ 아니다 허용되는 변수 이름.그들은 그들이 나타내는 데이터의 기본 특성을 반영하는 이름을 가져야 합니다.

나는 종종 이것을 구성 요소 부울 변수로 나눌 것입니다.

bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...

먼저 모든 항목을 제거하겠습니다. == true 부품을 사용하면 길이가 50% 더 짧아질 것입니다 ;)

나는 큰 질환이 있을 때 그 이유를 찾아봅니다.때로는 다형성을 사용해야 하고 때로는 상태 개체를 추가해야 할 때도 있습니다.기본적으로 이는 리팩토링이 필요함을 의미합니다(코드 냄새).

때때로 나는 사용한다 드모건의 법칙 부울 표현식을 약간 단순화합니다.

확인해 보세요 구현 패턴 켄트 벡 지음.이 상황에 도움이 될 수 있다고 생각하는 특정 패턴이 있습니다.그것은 "가드"라고 불립니다.수많은 조건을 갖는 대신 이를 하나의 가드로 분리하여 메소드의 불리한 조건이 무엇인지 명확하게 할 수 있습니다.

예를 들어, 어떤 작업을 수행하는 메서드가 있지만 다음과 같은 작업을 수행하지 않아야 하는 특정 조건이 있는 경우:

public void doSomething() {
    if (condition1 && condition2 && condition3 && condition4) {
        // do something
    }
}

다음과 같이 변경할 수 있습니다.

public void doSomething() {
    if (!condition1) {
        return;
    }

    if (!condition2) {
        return;
    }

    if (!condition3) {
        return;
    }

    if (!condition4) {
        return;
    }

    // do something
}

좀 더 장황하지만 훨씬 더 읽기 쉽습니다. 특히 이상한 중첩이 시작될 때 가드가 도움이 될 수 있습니다(추출 방법과 결합).

그런데 나는 그 책을 적극 추천합니다.

나는 많은 사람들과 편집자들이 하나의 탭으로 if 문의 각 조건을 들여쓰기하거나 열린 괄호와 일치시키는 것을 보았습니다.

if (var1 == true
    && var2 == true
    && var3 == true
   ) {
    /* do something.. */
}

나는 보통 마지막 조건과 같은 줄에 가까운 괄호를 넣습니다:

if (var1 == true
    && var2 == true
    && var3 == true) {
    /* do something.. */
}

하지만 제 생각에는 그다지 깨끗하지는 않습니다.

스티브 맥코넬의 조언 코드 완성:다차원 테이블을 사용합니다.각 변수는 테이블에 대한 인덱스 역할을하며 IF 문은 테이블 조회로 바뀝니다.예를 들어 (size == 3 && weight> 70)이 테이블 입력 결정 [size] [weight_group]으로 변환됩니다.

Functors와 Predicates를 살펴보세요.Apache Commons 프로젝트에는 조건부 논리를 객체로 캡슐화할 수 있는 다양한 객체 세트가 있습니다.사용 예는 O'reilly에서 확인할 수 있습니다. 여기.코드 예시 발췌:

import org.apache.commons.collections.ClosureUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.functors.NOPClosure;

Map predicateMap = new HashMap();

predicateMap.put( isHonorRoll, addToHonorRoll );
predicateMap.put( isProblem, flagForAttention );
predicateMap.put( null, ClosureUtils.nopClosure() );

Closure processStudents = 
    ClosureUtils.switchClosure( predicateMap );

CollectionUtils.forAllDo( allStudents, processStudents );

이제 모든 isHonorRoll 조건자의 세부 정보와 이를 평가하는 데 사용되는 클로저:

import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;

// Anonymous Predicate that decides if a student 
// has made the honor roll.
Predicate isHonorRoll = new Predicate() {
  public boolean evaluate(Object object) {
    Student s = (Student) object;

    return( ( s.getGrade().equals( "A" ) ) ||
            ( s.getGrade().equals( "B" ) && 
              s.getAttendance() == PERFECT ) );
  }
};

// Anonymous Predicate that decides if a student
// has a problem.
Predicate isProblem = new Predicate() {
  public boolean evaluate(Object object) {
    Student s = (Student) object;

    return ( ( s.getGrade().equals( "D" ) || 
               s.getGrade().equals( "F" ) ) ||
             s.getStatus() == SUSPENDED );
  }
};

// Anonymous Closure that adds a student to the 
// honor roll
Closure addToHonorRoll = new Closure() {
  public void execute(Object object) {
    Student s = (Student) object;

    // Add an award to student record
    s.addAward( "honor roll", 2005 );
    Database.saveStudent( s );
  }
};

// Anonymous Closure flags a student for attention
Closure flagForAttention = new Closure() {
  public void execute(Object object) {
    Student s = (Student) object;

    // Flag student for special attention
    s.addNote( "talk to student", 2005 );
    s.addNote( "meeting with parents", 2005 );
    Database.saveStudent( s );
  }
};

우선, 왜 안 될까요?

if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
...

또한 추상 코드 예제를 리팩토링하는 것도 매우 어렵습니다.구체적인 예를 보여주면 문제에 맞는 더 나은 패턴을 식별하는 것이 더 쉬울 것입니다.

더 좋지는 않지만 과거에 내가 한 일은 다음과 같습니다.(다음 방법은 단락 부울 테스트를 방지하며 첫 번째 테스트가 거짓이더라도 모든 테스트가 실행됩니다.반환하기 전에 항상 모든 코드를 실행해야 한다는 것을 알지 않는 한 권장되는 패턴은 아닙니다. 내 실수를 찾아준 ptomato에게 감사드립니다!)

부울 ok = cond1;
알았어 &= cond2;
알았어 &= cond3;
알았어 &= cond4;
알았어 &= cond5;
알았어 &= cond6;

이는 다음과 같습니다: (동일하지는 않습니다. 위의 참고 사항을 참조하십시오!)

ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);

나는 별도의 부울 값을 사용합니다.

Bool cond1 == (var1 && var2);
Bool cond2 == (var3 && var4);

if ( cond1 && cond2 ) {}

다른 사람들이 언급했듯이, 가독성을 높이기 위해 조건을 다른 방법으로 아웃소싱할 수 있는 방법이 있는지 알아보기 위해 조건을 분석하겠습니다.

PHP와 같은 반사 언어에서는 변수-변수를 사용할 수 있습니다.

$vars = array('var1', 'var2', ... etc.);
foreach ($vars as $v)
    if ($$v == true) {
        // do something
        break;
    }

나는 그것들을 수준별로 분류하고 싶기 때문에 다음과 같은 예를 작성하겠습니다.

if (var1 = true
 && var2 = true
 && var2 = true
 && var3 = true
 && var4 = true
 && var5 = true
 && var6 = true){

다음과 같이 더 많은 중첩이 있을 때 편리합니다(분명히 모든 것에 대해 "= true"보다 실제 조건이 더 흥미로울 것입니다).

if ((var1 = true && var2 = true)
 && ((var2 = true && var3 = true)
  && (var4 = true && var5 = true))
 && (var6 = true)){

Python으로 프로그래밍하는 경우 내장된 기능을 사용하면 매우 쉽습니다. all() 변수 목록에 적용되는 함수(여기서는 부울 리터럴만 사용하겠습니다):

>>> L = [True, True, True, False, True]
>>> all(L) # True, only if all elements of L are True.
False
>>> any(L) # True, if any elements of L are True.
True

귀하의 언어(C#?)에 해당 기능이 있습니까?자바?).그렇다면 이것이 가장 깨끗한 접근 방식일 것입니다.

맥도웰,

표현식의 양쪽이 평가되는 단일 '&' 연산자를 사용할 때 맞는 말입니다.그러나 '&&' 연산자(적어도 C#에서는)를 사용하는 경우 false를 반환하는 첫 번째 식이 평가되는 마지막 식이 됩니다.이렇게 하면 평가를 FOR 문 앞에 두는 것이 다른 방법과 마찬가지로 좋습니다.

@tweakt

더 좋지는 않지만 과거에 내가 한 일은 다음과 같습니다.

부울 ok = cond1;알았어 &= cond2;알았어 &= cond3;알았어 &= cond4;알았어 &= cond5;알았어 &= cond6;

이는 다음과 같습니다:

ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);

사실, 이 두 가지는 대부분의 언어에서 동일하지 않습니다.두 번째 표현식은 일반적으로 조건 중 하나가 거짓이 되는 즉시 평가가 중지됩니다. 이는 조건 평가 비용이 많이 드는 경우 성능이 크게 향상될 수 있습니다.

가독성을 위해 저는 개인적으로 위의 Mike Stone의 제안을 선호합니다.장황하게 설명하기 쉽고 조기에 종료할 수 있는 모든 계산상의 이점이 유지됩니다.조건부 평가를 다른 함수에서 멀리 이동시켜 코드 구성을 혼란스럽게 하는 경우 함수에서 동일한 기술을 인라인으로 수행할 수도 있습니다.약간 뻔하지만 언제든지 다음과 같은 작업을 수행할 수 있습니다.

do {
    if (!cond1)
       break;
    if (!cond2)
       break;
    if (!cond3)
       break;
    ...
    DoSomething();
} while (false);

while (false)는 좀 촌스럽습니다.언어에 "once"라는 범위 지정 연산자나 쉽게 벗어날 수 있는 연산자가 있었으면 좋겠습니다.

Perl에서 이 작업을 수행했다면 이것이 검사를 실행하는 방법입니다.

{
  last unless $var1;
  last unless $var2;
  last unless $var3;
  last unless $var4;
  last unless $var5;
  last unless $var6;

  ... # Place Code Here
}

서브루틴을 통해 이것을 사용할 계획이라면 last ~와 함께 return;

나는 각 조건을 설명 변수로 나누는 것을 좋아합니다.

bool isVar1Valid, isVar2Valid, isVar3Valid, isVar4Valid;
isVar1Valid = ( var1 == 1 )
isVar2Valid = ( var2.Count >= 2 )
isVar3Valid = ( var3 != null )
isVar4Valid = ( var4 != null && var4.IsEmpty() == false )
if ( isVar1Valid && isVar2Valid && isVar3Valid && isVar4Valid ) {
     //do code
}
    if (   (condition_A)
        && (condition_B)
        && (condition_C)
        && (condition_D)
        && (condition_E)
        && (condition_F)
       )
    {
       ...
    }

반대로

    if (condition_A) {
       if (condition_B) {
          if (condition_C) {
             if (condition_D) {
                if (condition_E) {
                   if (condition_F) {
                      ...
                   }
                }
             }
          }
       }
    }

그리고

    if (   (   (condition_A)
            && (condition_B)
           )
        || (   (condition_C)
            && (condition_D)
           )
        || (   (condition_E)
            && (condition_F)
           )
       )
    {
       do_this_same_thing();
    }

반대로

    if (condition_A && condition_B) {
       do_this_same_thing();
    }
    if (condition_C && (condition_D) {
       do_this_same_thing();
    }
    if (condition_E && condition_F) {
       do_this_same_thing();
    }

코드 검사를 위한 대부분의 정적 분석 도구는 연산자 우선순위 규칙과 더 적은 괄호를 사용하는 대신 여러 조건식이 표현식 분석을 지시하는 명시적 괄호를 사용하지 않는 경우 불만을 표시합니다.

여는/닫는 중괄호 {}, 여는 닫는 괄호(), 괄호와 왼쪽에 연산자가 있는 조건식을 동일한 들여쓰기 수준으로 수직 정렬하는 것은 모든 것을 방해하는 대신 코드의 가독성과 명확성을 크게 향상시키는 매우 유용한 방법입니다. 수직 정렬, 공백 또는 괄호 없이 한 줄에 걸릴 수 있습니다.

연산자 우선순위 규칙은 까다롭습니다.&&는 ||보다 우선 순위가 높지만 | &&보다 우선합니다.

그래서, ...

    if (expr_A & expr_B || expr_C | expr_D & expr_E || expr_E && expr_F & expr_G || expr_H {
    }

단순한 인간이 잘못 읽고 평가하기에는 정말 쉬운 다중 조건식입니다.

    if (   (  (expr_A)
            & (expr_B)
           )
        || (  (expr_C)
            | (  (expr_D)
               & (expr_E)
              )
           )
        || (   (expr_E)
            && (  (expr_F)
                & (expr_G)
               )
           )
        || (expr_H)
       )
    {
    }

가로 공간(줄 바꿈), 세로 정렬 또는 표현식 평가를 안내하는 명시적 괄호에는 아무런 문제가 없으며 모두 가독성과 명확성을 향상시킵니다.

이렇게 하면:

if (var1 == true) {
    if (var2 == true) {
        if (var3 == true) {
            ...
        }
    }
}

그러면 사실이 아닌 경우에도 대응할 수 있습니다.예를 들어 입력의 유효성을 검사하는 경우 사용자에게 입력 형식을 올바르게 지정하는 방법 등에 대한 팁을 제공할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top