프로그래머는 코드를 "문서화"하기 위해 부울 변수를 사용해야합니까?


79

저는 McConell의 Code Complete를 읽고 있으며 그는 부울 변수를 사용하여 코드를 문서화하는 방법에 대해 설명합니다. 예를 들어, 대신 :

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
   (elementIndex == lastElementIndex)){
       ...
}

그는 다음과 같이 제안합니다.

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

이것은 저에게 논리적이고 좋은 관행이며 매우 자기 문서화라는 인상을줍니다. 그러나 나는 거의 본 적이 없기 때문에이 기술을 정기적으로 사용하는 것을 주저합니다. 희귀하다는 이유로 혼란 스러울 수도 있습니다. 그러나 내 경험은 아직 그다지 방대하지 않기 때문에이 기술에 대한 프로그래머의 의견을 듣고 싶습니다. 누군가이 기술을 정기적으로 사용하는지 아니면 코드를 읽을 때 자주 본 적이 있는지 알고 싶습니다. 이것이 채택 할 가치있는 컨벤션 / 스타일 / 기술입니까? 다른 프로그래머가 이해하고 감사할까요, 아니면 이상하다고 생각할까요?


@Paul R-감사합니다. 나는 자기 문서화로 시작했고 그것에 대한 태그가 없다는 것을 깨달았으므로 이미 존재하는 자기 문서화로 바꾸려고 노력했습니다. :)
froadie

테스트에서 무슨 일이 일어나고 있는지에 대한 논평 만하는 것이 더 낫지 않습니까?
JS

3
적절한 경우, 특히 방법의 다른 부분에서 조건을 확인해야하는 경우에도이 작업을 수행합니다. 또한 훨씬 더 설명 적입니다.
geffchang

"이는 저에게 논리적이고 좋은 관행이며 매우 자기 문서화라는 인상을줍니다." 귀하의 경험을 바탕으로 처음 접한 다른 프로그래머도 쉽게 이해할 수 있습니다.

2
이 질문이 아직 열려있는 이유는 무엇입니까?
orangepips

답변:


55

너무 중첩되고 복잡한 표현식을 지역 변수에 할당 된 더 간단한 하위 표현식으로 분할 한 다음 다시 합치는 것은 매우 일반적이고 널리 사용되는 기술입니다. 하위 표현식 및 / 또는 전체 표현식이 부울인지 다른 유형에 대해. 잘 선택된 이름을 사용하면 이런 종류의 세련된 분해가 가독성을 높일 수 있으며, 좋은 컴파일러는 원래의 복잡한 표현과 동일한 코드를 생성하는 데 문제가 없어야합니다.

Haskell과 같이 그 자체로 "할당"이라는 개념이없는 일부 언어는 "부 표현식에 이름 제공"기술 ( where하스켈 의 절) 을 사용할 수 있도록 특수 구조를 도입 하기도합니다. 문제의 기술에 대한 인기!-)


6
그것은 간단하고 쉽게 읽을 수 있다면, 나는 그것이 꽤 명확한 경우 말할 윈 - 윈 :)
djcouchycouch

나는 동의한다. 많은 경우에 당신은 아마도 짧은 코멘트로 도망 갈 수있을 것입니다. 그러나 이것이 실제로 어떻게 상처를 입힐
Max E.

16

일반적으로 부울 논리를 재사용 가능한 메서드로 래핑하지만 (여러 위치에서 호출되는 경우) 사용했습니다.

가독성을 높이고 논리가 변경되면 한 곳에서만 변경하면됩니다.

다른 사람들은 그것을 이해하고 이상하다고 생각하지 않을 것입니다 (즉, 수천 줄 함수를 작성하는 사람들을 제외하고).


답변 해주셔서 감사합니다! 재사용 가능한 메서드와 관련하여 다른 (관련되지 않은) 유효한 이유가 있습니다. 따라서 제 질문은 가독성 외에 다른 이유가 없을 때 일회성 부울 표현식을 제거해야하는지 여부입니다. (물론 그 자체만으로도 충분한 이유입니다 :)) 지적 해 주셔서 감사합니다.
froadie

로직이 변경 될 때 필요한 코드 변경을 줄이고 수천 줄 함수 프로그래머를 조롱하기 위해 +1.
Jeffrey L Whitledge

9

가능한 한 그렇게하려고합니다. 물론, 코드의 "추가 라인"을 사용하고 있지만 동시에 두 값을 비교하는 이유를 설명하고 있습니다.

귀하의 예에서 저는 코드를보고 스스로에게 "값이 0보다 작은 이유를 보는 사람이 무엇입니까?"라고 자문합니다. 두 번째에서 당신은 이것이 발생했을 때 일부 프로세스가 완료되었음을 분명히 말하고 있습니다. 두 번째 질문에서는 당신의 의도가 무엇인지 추측하지 마십시오.

저에게 가장 큰 것은 다음과 같은 방법을 볼 때 DoSomeMethod(true); 입니다. 자동으로 true로 설정되는 이유는 무엇입니까? 다음과 같이 훨씬 더 읽기 쉽습니다.

bool deleteOnCompletion = true;

DoSomeMethod(deleteOnCompletion);

7
나는 정확히 이런 이유로 부울 매개 변수를 싫어한다. 결국 "createOrder (true, false, true, true, false)"와 같은 호출을 받게되는데 그게 무슨 뜻입니까? 저는 enum을 사용하는 것을 선호하므로 "createOrder (Source.MAIL_ORDER, BackOrder.NO, CustomOrder.CUSTOM, PayType.CREDIT)"와 같이 말합니다.
Jay

그러나 Kevin의 예를 따르면 그것은 당신의 것과 같습니다. 변수가 2 개 값 또는 2 개 이상의 값을 가질 수 있는지 여부는 어떤 차이가 있습니까?
Mark Ruzon

2
Jay 's를 사용하면 특정 상황에서 확실히 더 명확 해지는 이점을 얻을 수 있습니다. 예를 들어 PayType을 사용합니다. 부울이면 매개 변수 이름이 isPayTypeCredit 일 수 있습니다. 당신은 대안이 무엇인지 모릅니다. 열거 형을 사용하면 PayType이 신용, 수표, 현금 인 옵션을 명확하게 볼 수 있으며 올바른 옵션을 선택할 수 있습니다.
kemiller2002

값 컨트롤은 말 그대로 완료 있도록 ++ 또한 열거 널 할당을 허용하지 않으며, 열거의 자동 문서 상단에 육즙입니다
Hardryv

Objective-C와 Smalltalk는 Objective-C에서이 문제를 실제로 해결합니다.[Object createOrderWithSource:YES backOrder:NO custom:YES type:kCreditCard];
Grant Paul

5

제공된 샘플 :

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

가독성을 높이고 부울 논리를 보존하는 메서드를 사용하도록 다시 작성할 수도 있습니다 (Konrad가 지적했듯이).

if (IsFinished(elementIndex) || IsRepeatedEntry(elementIndex, lastElementIndex)){
   ...
}

...

private bool IsFinished(int elementIndex) {
    return ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
}

private bool IsRepeatedEntry(int elementIndex, int lastElementIndex) {
    return (elementIndex == lastElementIndex);
}

물론 두 가지 추가 방법 인 대가가 따릅니다. 이 작업을 많이 수행하면 코드를 더 읽기 쉽게 만들 수 있지만 클래스의 투명성은 떨어집니다. 그러나 다시 추가 메서드를 도우미 클래스로 이동할 수도 있습니다.


C #으로 많은 코드 노이즈가 발생하면 부분 클래스를 활용하고 노이즈를 부분으로 이동할 수 있으며 사람들이 IsFinished가 확인하는 것에 관심이 있다면 쉽게 이동할 수 있습니다.
Chris Marisic

3

이것이 잘못되는 것을 볼 수있는 유일한 방법은 부울 조각에 의미가있는 이름이없고 어쨌든 이름이 선택되는 경우입니다.

//No clue what the parts might mean.
if(price>0 && (customer.IsAlive || IsDay(Thursday)))

=>

first_condition = price>0
second_condition =customer.IsAlive || IsDay(Thursday)

//I'm still not enlightened.
if(first_condition && second_condition)

"모든 코드에 주석 달기", "3 개 이상의 부분이있는 모든 if- 기준에 명명 된 부울 사용"과 같은 규칙을 만드는 것이 일반적이기 때문에 다음과 같은 의미 상 비어있는 주석 만 가져 오는 것이 일반적이기 때문에 이것을 지적합니다.

i++; //increment i by adding 1 to i's previous value

2
귀하의 예에서 조건을 잘못 그룹화 했습니까? '&&'는 '||'보다 더 빡빡합니다. 이를 사용하는 대부분의 언어 (쉘 스크립트는 예외 임).
Jonathan Leffler

Parens가 추가되었습니다. 따라서 이것은 명명 된 변수로의 분할에 대한 또 다른 공격이 될 것이며, 명확하지 않은 방식으로 그룹화를 변경할 수있는 기회를 제공합니다.
MatthewMartin

2

이렇게함으로써

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
   ...
}

뇌 에서 논리 를 제거하고 코드에 넣습니다. 이제 프로그램 당신이 의미하는 바를 알고 있습니다. 이름을
지을 때마다 물리적 표현 을 제공합니다 . 존재합니다. 조작하고 재사용 할 수 있습니다.

전체 블록을 술어로 정의 할 수도 있습니다.

bool ElementBlahBlah? (elementIndex, lastElementIndex);

그 기능에서 (나중에) 더 많은 일을합니다.


그리고 더 중요한 것은 코드를 살펴볼 다음 개발자도 당신이 의미하는 바를 알게 될 것입니다! 이것은 훌륭한 연습이며 저는 항상 그것을합니다.
Chris Thornton

1
또한 다음 개발자는 종종 자신이 될 수 있으며 몇 달 (또는 몇 주) 후에 코드를 다시보고 자체 문서화가 잘되어 기뻐할 수 있습니다.
Louis

2

표현식이 복잡하면 bool예 를 반환하는 다른 함수로 이동 isAnEveningInThePubAGoodIdea(dayOfWeek, sizeOfWorkLoad, amountOfSpareCash)하거나 복잡한 표현식이 필요하지 않도록 코드를 재고합니다.


2

이런 식으로 필요 이상으로 계산한다는 것을 기억하십시오. 코드에서 조건을 제거하기 때문에 항상 두 가지를 모두 계산합니다 (단락 없음).

그래서:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
   (elementIndex == lastElementIndex)){
   ...
}

변형 후 :

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) |
   (elementIndex == lastElementIndex)){
   ...
}

대부분의 경우 문제는 아니지만 일부에서는 성능 저하 또는 기타 문제를 의미 할 수 있습니다 (예 : 두 번째 표현에서 첫 번째 표현이 실패했다고 가정 할 때).


사실-이 방법을 사용하여 몇 개의 if 문을 리팩터링하기 위해 일부 코드로 돌아 갔을 때 나는 이것을 알아 차 렸습니다. && (첫 번째 부분이 거짓 인 경우 두 번째 부분은 NPE를 던짐)로 단락 평가를 활용하는 if 문이 있었고 이것을 리팩토링 할 때 내 코드가 실패했습니다 ( 항상 두 가지를 모두 평가하고 부울에 저장 했기 때문). 변수). 좋은 지적, 감사합니다! 그러나 나는 이것을 시도하면서 궁금했다- 논리 를 변수 에 저장 하고 실제 if 문까지 평가를 지연시키는 방법이 있습니까?
froadie

2
나는 실제로 컴파일러가 그것을 해결할 것이라고 의심합니다. 두 번째 호출이 효과적이지 않은 경우 일반적으로 일부 함수의 호출로 인한 것이며 AFAIK 컴파일러는 호출 된 함수에 부작용이 없는지 확인하려고하지 않습니다.
JS

IF를 중첩 할 수 있으며 첫 번째 테스트가 진행 여부를 결정하기에 불충분하지 않는 한 이후 계산을 수행하지 마십시오.
Jay

@froadie Kotlin (및 곧 Dart)과 같은 일부 언어는 사용할 때만 계산되는 "지연"변수를 허용합니다. 또는 변수 대신 함수에 논리를 넣으면 여기에서 동일한 효과가 발생합니다. 10 년 후에도 여전히 알고 싶은 경우를 대비하여.
hacker1024 2012

2

임시 변수 대신 함수 / 메소드를 만드는 것이 더 낫다고 생각합니다. 이렇게하면 메서드가 짧아지기 때문에 가독성이 높아집니다. Martin Fowler의 저서 Refactoring은 코드 품질을 개선하기위한 좋은 조언을 제공합니다. 특정 예제와 관련된 리팩토링을 "Replace Temp with Query"및 "Extract Method"라고합니다.


2
일회성 기능이 많은 클래스 공간을 어지럽히면 가독성이 높아진다는 말입니까? 설명 해주십시오.
Zano

항상 트레이드 오프입니다. 원래 기능의 가독성이 향상됩니다. 원래 기능이 짧으면 그만한 가치가 없을 수도 있습니다.
mkj

또한 "클래스 공간을 복잡하게 만드는 것"은 사용 된 언어와 코드 분할 방법에 따라 달라집니다.
mkj

2

개인적으로 이것은 좋은 습관이라고 생각합니다. 코드 실행에 미치는 영향은 미미하지만 적절하게 사용하면 제공 할 수있는 명확성은 나중에 코드를 유지 관리 할 때 매우 중요합니다.


1

메서드에 성공 알림이 필요한 경우 : (C #의 예)

bool success = false;

시작합니다. 코드를 다음과 같이 변경할 때까지 오류가 발생합니다.

success = true;

그런 다음 끝 :

return success;

0

당신 / 당신의 팀이 선호하는 스타일에 달려 있다고 생각합니다. "변수 소개"리팩토링이 유용 할 수 있지만 때로는 그렇지 않습니다. :)

그리고 나는 이전 게시물에서 Kevin의 의견에 동의하지 않습니다. 그의 예는 도입 된 변수가 변경 될 수있을 때 사용할 수 있다고 생각하지만 하나의 정적 부울에 대해서만 도입하는 것은 쓸모가 없습니다. 왜냐하면 메서드 선언에 매개 변수 이름이 있기 때문에 코드에서 복제하는 이유는 무엇입니까?

예를 들면 :

void DoSomeMethod(boolean needDelete) { ... }

// useful
boolean deleteOnCompletion = true;
if ( someCondition ) {
    deleteOnCompletion = false;
}
DoSomeMethod(deleteOnCompletion);

// useless
boolean shouldNotDelete = false;
DoSomeMethod(shouldNotDelete);

0

내 경험상, 나는 종종 오래된 대본으로 돌아가서 '그때 내가 도대체 ​​무엇을 생각하고 있었는지'궁금해했습니다. 예를 들면 :

Math.p = function Math_p(a) {
    var r = 1, b = [], m = Math;
    a = m.js.copy(arguments);
    while (a.length) {
        b = b.concat(a.shift());
    }
    while (b.length) {
        r *= b.shift();
    }
    return r;
};

다음과 같이 직관적이지 않습니다.

/**
 * An extension to the Math object that accepts Arrays or Numbers
 * as an argument and returns the product of all numbers.
 * @param(Array) a A Number or an Array of numbers.
 * @return(Number) Returns the product of all numbers.
 */
Math.product = function Math_product(a) {
    var product = 1, numbers = [];
    a = argumentsToArray(arguments);
    while (a.length) {
        numbers = numbers.concat(a.shift());
    }
    while (numbers.length) {
        product *= numbers.shift();
    }
    return product;
};

-1. 이것은 솔직히 원래의 질문과 약간 관련이 있습니다. 문제는 일반적으로 좋은 코드를 작성하는 방법에 관한 것이 아니라 다르고 매우 구체적인 것에 관한 것입니다.
P Shved

0

별도의 변수를 거의 생성하지 않습니다. 테스트가 복잡해질 때 내가하는 일은 IF를 중첩하고 주석을 추가하는 것입니다. 처럼

boolean processElement=false;
if (elementIndex < 0) // Do we have a valid element?
{
  processElement=true;
}
else if (elementIndex==lastElementIndex) // Is it the one we want?
{
  processElement=true;
}
if (processElement)
...

이 기술의 인정 된 결점은 다음 프로그래머가 로직을 변경할 수 있지만 주석을 업데이트하는 것을 귀찮게하지 않는다는 것입니다. 일반적인 문제인 것 같지만 "고객 ID 확인"이라는 댓글을 많이 보았고 다음 줄에서 부품 번호 등을 검토하고 있는데 고객이 어디 있는지 궁금합니다. 이드가 들어온다.

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