이 조건부 사용이 안티 패턴입니까?


14

나는 우리의 레거시 시스템 직장에서 다음과 같은 기능을 많이 보았습니다.

bool todo = false;
if(cond1)
{
  ... // lots of code here
  if(cond2)
    todo = true;
  ... // some other code here
}

if(todo)
{
  ...
}

즉, 함수는 두 부분으로 구성됩니다. 첫 번째 부분은 일종의 처리 (잠재적으로 루프, 부작용 등 포함)와 "todo"플래그를 설정할 수있는 방법을 수행합니다. 두 번째 부분은 "todo"플래그가 설정된 경우에만 실행됩니다.

그것은 일을하는 아주 추한 방법처럼 보입니다. 실제로 이해하는 데 걸린 시간의 대부분은 깃발 사용을 피하기 위해 리팩토링 될 수 있다고 생각합니다. 그러나 이것이 실제 반 패턴입니까, 나쁜 생각입니까, 아니면 완벽하게 수용 가능한가요?

첫 번째 명백한 리팩토링은 두 가지 방법으로 잘라내는 것입니다. 그러나 내 질문은 로컬 플래그 변수를 만들어 잠재적으로 여러 위치에 설정 한 다음 나중에 그것을 사용하여 다음 코드 블록을 실행할지 여부를 결정 해야하는지 (현대 OO 언어)가 필요한지 여부에 관한 것입니다.


2
리팩터링은 어떻게합니까?
Tamás Szelei

13
몇 가지 사소한 비 배타적 조건에 따라 할 일이 여러 곳에서 설정되었다고 가정하면, 나는 약간의 의미가있는 리팩토링을 거의 생각할 수 없습니다. 리팩토링이없는 경우 반 패턴이 없습니다. 할일 변수의 이름을 제외하고; "doSecurityCheck"와 같이보다 표현적인 이름을 지정해야합니다.
user281377

3
@ammoQ : +1; 일이 복잡하다면 그 방법입니다. 플래그 변수는 결정이 내려진 것을보다 명확하게하기 위해 상황에 따라 훨씬 더 의미가있을 수 있으며, 결정을 내린 위치를 찾기 위해 검색 할 수 있습니다.
Donal Fellows

1
@Donal Fellows : 이유를 검색해야하는 경우 변수를 목록으로 만듭니다. 비어있는 한 "거짓"입니다. 플래그가 설정 될 때마다 이유 코드가 목록에 추가됩니다. 따라서 ["blacklisted-domain","suspicious-characters","too-long"]몇 가지 이유가 적용되었음을 보여주는 목록으로 끝날 수 있습니다 .
user281377

2
나는 그것이 반 패턴이라고 생각하지 않지만 확실히 냄새입니다
Binary Worrier

답변:


23

나는 안티 패턴에 대해 모른다. 그러나 나는 이것에서 세 가지 방법을 추출 할 것이다.

첫 번째는 약간의 작업을 수행하고 부울 값을 반환합니다.

두 번째는 "일부 다른 코드"에 의해 수행 된 작업을 수행합니다.

부울 리턴이 true 인 경우 세 번째는 보조 작업을 수행합니다.

추출 된 메소드는 아마도 첫 번째 메소드가 true를 리턴 한 경우 두 번째 만 호출되는 것이 중요하다면 아마도 개인용입니다.

메소드의 이름을 잘 지정하면 코드가 더 명확 해지기를 바랍니다.

이 같은:

public void originalMethod() {
    bool furtherProcessingRequired = lotsOfCode();
    someOtherCode();
    if (furtherProcessingRequired) {
        doFurtherProcessing();
    }
    return;
}

private boolean lotsOfCode() {
    if (cond1) {
        ... // lots of code here
        if(cond2) {
            return true;
        }
    }
    return false;
}

private void someOtherCode() {
    ... // some other code here
}

private void doFurtherProcessing() {
    // Do whatever is needed
}

초기 반품이 허용되는지 여부에 대한 논쟁이 분명히 있지만, 코드 형식 표준과 마찬가지로 구현 세부 사항입니다.

요점은 코드의 의도가 더 명확해진다는 것입니다.

질문에 대한 의견 중 하나는이 패턴이 냄새를 나타내는 것으로 암시 합니다. 의도를 더 명확하게 할 수 있는지 살펴볼 가치가 있습니다.


2 개의 함수로 나누려면 여전히 todo변수 가 필요하며 이해하기 어려울 수 있습니다.
Pubby

그래, 나도 그렇게 할 것이지만 내 질문은 "todo"플래그의 사용법에 관한 것이었다.
Kricket

2
로 끝나는 경우 if (check_if_needed ()) do_whatever ();명백한 플래그가 없습니다. 코드가 너무 단순하면 코드가 너무 많이 손상되어 가독성이 떨어질 수 있다고 생각합니다. 결국, 당신이하는 일에 대한 세부 사항은 do_whatever테스트 방법에 영향을 줄 수 check_if_needed있으므로 모든 코드를 같은 화면에 함께 유지하는 것이 유용합니다. 또한 이것이 check_if_needed플래그 사용을 피할 수 있다고 보장하지는 않습니다 . 플래그를 사용하는 경우에는 여러 개의 return명령문을 사용 하여이를 수행하여 엄격한 단일 종료 옹호자를 화나게 할 수 있습니다.
Steve314

3
@ Pubby8 그는 "이것에서 2 가지 방법을 추출 한다 " 고 말하면서 3 가지 방법을 만들었다 . 실제 처리를 수행하는 두 가지 방법과 워크 플로를 조정하는 원래 방법 이것은 훨씬 더 깨끗한 디자인입니다.
MattDavey

이것은 ... // some other code here초기 반환 사례를 생략합니다
Caleth

6

추한 것은 단일 방법에 많은 코드가 있고 변수가 잘못 명명되어 있기 때문이라고 생각합니다 (두 가지 모두 자체적으로 코드 냄새가납니다 -반 패턴은보다 추상적이며 복잡한 것입니다).

따라서 @Bill이 제안한 것처럼 대부분의 코드를 하위 수준의 메소드로 추출하면 나머지는 깨끗해집니다 (적어도 나에게는). 예 :

bool registrationNeeded = installSoftware(...);
if (registrationNeeded) {
  registerUser(...)
}

또는 두 번째 방법으로 플래그 검사를 숨기고 다음과 같은 형식을 사용하여 로컬 플래그를 완전히 제거 할 수도 있습니다.

calculateTaxRefund(isTaxRefundable(...), ...)

전반적으로 로컬 플래그 변수가 반드시 그 자체로 나쁜 것으로 보이지는 않습니다. 위의 옵션 중 더 읽기 쉬운 (= 나에게 선호되는) 메소드 매개 변수의 수, 선택한 이름 및 코드의 내부 논리와 더 일치하는 형식에 따라 다릅니다.


4

todo는 변수에 정말 나쁜 이름이지만 그게 다 잘못된 것 같아요. 상황이 없으면 완전히 확신하기 어렵습니다.

함수의 두 번째 부분이 첫 번째 부분으로 작성된 목록을 정렬한다고 가정 해 봅시다. 훨씬 더 읽기 쉬워야합니다.

bool requiresSorting = false;
if(cond1)
{
    ... // lots of code here
    if(cond2)
        requiresSorting = true;
    ... // some other code here
}

if(requiresSorting)
{
    ...
}

그러나 Bill의 제안도 정확합니다. 이것은 여전히 ​​더 읽기 쉽습니다.

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

한 걸음 더 나아가는 것이 어떻겠습니까? if (BuildList (list)) SortList (list);
Phil N DeBlanc 2016 년

2

상태 머신 패턴이 잘 보입니다. 안티 패턴에는 "todo"(잘못된 이름)와 "lots of code"가 있습니다.


그래도 그것은 단지 설명을위한 것이라고 확신합니다.
Loren Pechtel

1
동의했다. 내가 전달하려고하는 것은 가난한 코드에서 익사 한 좋은 패턴이 코드의 품질에 대해 비난되어서는 안된다는 것입니다.
ptyx

1

정말 다릅니다. 지키는 코드 todo(실제로 그 이름을 사용하지 않기를 바랍니다)가 개념적으로 정리 된 코드 인 경우 안티 패턴이 있고 C ++의 RAII 또는 C #과 같은 것을 사용해야합니다 using대신 물건을 처리하도록 구성하십시오.

한편,이 개념입니다 하지 정리 단계가 아니라 단지 추가 처리 때로는 필요하고 어디에 이전에주의 할 필요가 할 수있는 의사 결정, 벌금 기록 된 것입니다. 개별 코드 청크가 물론 자체 함수로 더 잘 리팩터링되는지 여부와 이름에서 플래그 변수의 의미를 캡처했는지 여부를 고려하지만이 기본 코드 패턴은 괜찮습니다. 특히, 명확하지에 무슨 일 할 수있는 다른 기능에 너무 많이 넣어 위해 노력하고, 확실히 안티 패턴 것입니다.


분명히 정리되지는 않습니다. 항상 실행되는 것은 아닙니다. 이전에 이와 같은 사례를 겪었습니다. 무언가를 처리하는 동안 미리 계산 된 결과가 무효화 될 수 있습니다. 계산이 비싸면 필요한 경우에만 실행하려고합니다.
Loren Pechtel

1

여기의 많은 답변은 복잡도 검사를 통과하는 데 어려움을 겪습니다.

나는 이것이 당신이보고있는 것의 "반 패턴"부분이라고 생각합니다. 코드의 순환 복잡성을 측정하는 도구를 찾으십시오. 일 식용 플러그인이 있습니다. 본질적으로 코드 테스트가 얼마나 어려운지 측정하고 코드 분기의 수와 수준을 포함합니다.

가능한 해결책에 대한 총체적인 추측으로, 코드 종류의 레이아웃은 "작업"에서 생각하게 만듭니다. 만약 이것이 당신이 정말로 원하는 것이 작업 지향 아키텍처입니다-각 작업은 고유합니다. 객체와 중간 작업에서 다른 작업 객체를 인스턴스화하고 대기열에 던져 다음 작업을 대기열에 넣을 수 있습니다. 이것들은 설정하기가 놀랍도록 간단 할 수 있으며 특정 유형의 코드의 복잡성을 크게 줄입니다. 그러나 이것이 암흑에서 완전히 찌르는 것이라고 말했습니다.


1

위의 pdr의 예를 사용하면 좋은 예이므로 한 단계 더 나아가겠습니다.

그는 가졌다:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

그래서 다음과 같이 작동한다는 것을 깨달았습니다.

if(BuildList(list)) 
    SortList(list)

그러나 명확하지 않습니다.

따라서 원래 질문에 대해 다음과 같은 이유는 무엇입니까?

BuildList(list)
SortList(list)

그리고 SortList가 정렬이 필요한지 결정하도록 하시겠습니까?

BuildList 메소드는 정렬에 대해 알고있는 것처럼 보입니다. 이는 bool을 표시하므로 목록을 작성하도록 설계된 메소드에는 의미가 없습니다.


그리고 다음 단계는 왜 이것이 2 단계 과정인지를 묻는 것입니다. 어딘가에서 BuildAndSortList (list)라는 메소드를 리팩터링하는 코드를 볼 수 있습니다
Ian

이것은 답이 아닙니다. 코드의 동작을 변경했습니다.
D Drmmr

실제로는 아닙니다. 다시, 나는 내가 7 년 전에 게시 한 것에 응답하고 있다고 믿을 수 없지만 도대체 내가 말한 것은 SortList에 조건부가 포함되어 있다는 것입니다. 조건 x가 충족 된 경우에만 목록이 정렬되었다고 주장하는 단위 테스트가있는 경우 여전히 통과합니다. 조건부를 SortList로 옮기면 항상 (if (something), SortList (...))를 쓰지 않아도됩니다
Ian

0

예, 깃발을 켜고 끄는 모든 장소를 계속 추적해야하기 때문에 문제가되는 것 같습니다. 로직을 제거하는 대신 내부에 로직을 중첩 된 if 조건으로 포함하는 것이 좋습니다.

또한 풍부한 도메인 모델 인 경우 하나의 라이너 만 객체 내부에서 큰 일을합니다.


0

플래그가 한 번만 설정되면
...
코드를
... // 다른 코드 바로 다음으로 이동 한 다음
플래그를 제거하십시오.

그렇지 않으면
... // 많은 코드를
... // 다른 코드를
...
분리하면 가능한 경우 별도의 함수로 코드를 분할 하므로이 함수에는 하나의 책임이 분기 논리라는 것이 분명합니다.

가능한 한 코드 내에서
... // 많은 코드
를 둘 이상의 함수로 분리합니다. 일부 기능은 명령이며 일부는 수행 할 작업 값을 반환하거나 쿼리입니다. 매우 명시 적으로 수정하고 있습니다 (부작용을 사용하는 쿼리)

안티 패턴이 아닌 코드의 코드 자체 ... 나는 실제로 물건 (명령)을 수행하는 분기 논리를 찾는 것이 안티 패턴이라고 생각합니다.


이 게시물에 기존 답변이 누락되었다는 내용은 무엇입니까?
esoterik

@esoterik 때때로 작은 CQRS를 추가 할 수있는 기회가 종종 플래그와 관련하여 간과되는 경우가 있습니다. 때로는 둘을 분리하면 코드가 더 명확해질 수 있습니다. 또한 플래그가 한 분기에만 설정되어 있기 때문에 단순화 할 수 있다는 위의 코드에서 지적 할 가치가 있습니다. 나는 플래그가 반 패턴이 아니라고 생각하고 그 이름이 실제로 코드를 더 표현력있게 만든다면 좋은 것입니다. 가능하면 플래그에서 플래그가 생성, 설정 및 사용되는 위치가 서로 가깝게 느껴야합니다.
앤드류 페이트

0

때로는이 패턴을 구현해야한다고 생각합니다. 때로는 작업을 진행하기 전에 여러 가지 검사를 수행하려고합니다. 효율성상의 이유로, 특정 점검과 관련된 계산은 반드시 점검해야 할 필요가없는 한 수행되지 않습니다. 따라서 일반적으로 다음과 같은 코드가 표시됩니다.

// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(valuesExist) {
    try {
      // Attempt insertion
      trx.commit();
    } catch (DatabaseException dbe) {
      trx.rollback();
      throw dbe;
    }
  } else {
    closeConnection(db);
    throwException();
  }
} else {
  closeConnection(db);
  throwException();
}

유효성 검사를 실제 작업 수행 프로세스와 분리하여 단순화 할 수 있으므로 다음과 같이 더 많이 볼 수 있습니다.

boolean proceed = true;
// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(!valuesExist) {
    proceed = false;
  }
} else {
  proceed = false;
}

// The moment of truth
if(proceed) {
  try {
    // Attempt insertion
    trx.commit();
  } catch (DatabaseException dbe) {
    trx.rollback();
    throw dbe;
  }
} else {
  if(db.isOpen()) {
    closeConnection(db);
  }
  throwException();
}

이 방법으로 작성되었지만 "성공"코드와 "실패"코드는 한 번 작성되므로 논리를 단순화하고 동일한 수준의 성능을 유지합니다. 거기에서 내부 메소드 안에 전체 레벨의 유효성 검사를 적용하여 성공 또는 실패의 부울 표시를 리턴하여 상황을 더 단순화하지만 일부 프로그래머는 이상한 이유로 매우 긴 메소드를 선호합니다.


주어진 예제에서 답을 반환하는 shouldIDoIt (fieldsValidated, valuesExist) 함수를 선호한다고 생각합니다. 진행중인 결정이 몇 가지 다른 비 연속적인 지점으로 흩어져있는 직장에서 본 코드와 달리 예 / 아니오 결정이 모두 한 번에 이루어지기 때문입니다.
Kricket

@KelseyRider, 그것이 바로 요점입니다. 유효성 검사를 실행에서 분리하면 프로그램의 전체 논리를 if (isValidated ()) doOperation ()으로 단순화하기 위해 논리를 메소드로 채울 수 있습니다.
Neil

0

이것은 패턴이 아닙니다 . 가장 일반적인 해석은 부울 변수를 설정하고 나중에 그 값으로 분기한다는 것입니다. 그것은 정상적인 절차 적 프로그래밍입니다.

이제 특정 예제를 다음과 같이 다시 작성할 수 있습니다.

if(cond1)
{
    ... // lots of code here
    ... // some other code here
    if (cond2)
    {
        ...
    }
}

읽기가 더 쉬울 수 있습니다. 아니면 아닐 수도 있습니다. 생략 한 나머지 코드에 따라 다릅니다. 코드를 간결하게 만드는 데 집중하십시오.


-1

제어 흐름에 사용되는 로컬 플래그 goto는 변장 형태로 인식되어야합니다 . 플래그가 함수 내에서만 사용되는 경우 함수의 두 복사본을 작성하여 하나를 "플래그가 참"으로 레이블링하고 다른 하나를 "플래그가 거짓"으로 레이블링하고 플래그를 설정하는 모든 작업을 교체함으로써 제거 할 수 있습니다 두 버전의 기능간에 점프하여 설정이 완료되면 설정이 지워지거나 지워집니다.

대부분의 경우 플래그를 사용하는 코드는 goto대신 사용 하는 다른 코드보다 깨끗 하지만 항상 사실은 아닙니다. 어떤 경우에는 goto코드 조각을 건너 뛰는 데 사용하는 것이 플래그를 사용하는 것보다 더 깨끗할 수 있습니다 (어떤 사람들은 여기에 특정 랩터 만화를 삽입 할 수도 있음).

기본 지침 원칙은 프로그램 논리 흐름이 가능한 한 비즈니스 논리 설명과 유사해야한다는 것입니다. 비즈니스 논리 요구 사항이 이상한 방식으로 분할 및 병합되는 상태로 설명되는 경우 프로그램 논리를 사용하는 것이 플래그를 사용하여 이러한 논리를 숨기는 것보다 더 깨끗할 수 있습니다. 다른 한편으로, 비즈니스 규칙을 설명하는 가장 자연스러운 방법이 특정한 다른 행동이 수행 된 경우 행동이 이루어져야한다고 말하는 경우, 가장 자연스러운 표현 방법은 수행 할 때 설정되는 플래그를 사용하는 것일 수 있습니다. 후자의 행동과 달리 명확하다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.