부울을 더 명확하게 호출하려면 어떻게해야합니까? 부울 트랩


76

@ benjamin-gruenbaum의 의견에서 언급했듯이 이것을 부울 트랩이라고합니다.

이런 기능이 있다고 해

UpdateRow(var item, bool externalCall);

내 컨트롤러에서 해당 값 externalCall은 항상 TRUE입니다. 이 함수를 호출하는 가장 좋은 방법은 무엇입니까? 나는 보통

UpdateRow(item, true);

그러나 나는 '진정한'가치가 무엇을 의미하는지 나타 내기 위해 부울을 선언해야합니까? 함수 선언을 보면 알 수 있지만 다음과 같은 것을 본다면 분명히 더 빠르고 명확합니다.

bool externalCall = true;
UpdateRow(item, externalCall);

PD :이 질문이 여기에 실제로 맞는지 확실하지 않은 경우, 그 질문에 대한 자세한 정보를 어디서 얻을 수 있습니까?

PD2 : 매우 일반적인 문제라고 생각했기 때문에 어떤 언어에도 태그를 달지 않았습니다. 어쨌든, 나는 C #으로 일하고 C #에서 허용되는 대답은 작동합니다.


10
이 패턴은라고도 부울 트랩
벤자민 Gruenbaum

11
대수 데이터 형식을 지원하는 언어를 사용하는 경우 부울 대신 새로운 adt를 사용하는 것이 좋습니다. data CallType = ExternalCall | InternalCall예를 들어 하스켈에서.
Filip Haglund

6
나는 열거 형이 똑같은 목적을 채울 것이라고 생각합니다. 부울의 이름을 얻고 약간의 유형 안전.
Filip Haglund

2
의미를 나타 내기 위해 부울을 선언하는 것이 "분명히 더 명확"하다는 것에 동의하지 않습니다. 첫 번째 옵션을 사용하면 항상 사실을 전달할 수 있습니다. 두 번째로 확인해야합니다 (변수는 어디에 정의되어 있습니까? 값이 변경됩니까?). 물론 두 줄이 함께 있으면 문제가되지 않지만 누군가 코드를 추가 할 수있는 완벽한 장소가 두 줄 사이에 있다고 결정할 수도 있습니다. 일어난다!
AJPerez

부울을 선언하는 것이 더 깨끗하지 않고 문제의 방법에 대한 문서를 살펴 보는 단계를 뛰어 넘을뿐입니다. 서명을 알고 있다면 새로운 상수를 추가하는 것이 명확하지 않습니다.
insidesin

답변:


154

항상 완벽한 솔루션은 아니지만 다음 중에서 선택할 수있는 많은 대안이 있습니다.

  • 귀하의 언어로 제공되는 경우 명명 된 인수를 사용하십시오 . 이것은 매우 잘 작동하며 특별한 단점이 없습니다. 일부 언어에서는 모든 인수를 명명 된 인수로 전달할 수 있습니다 updateRow(item, externalCall: true)( 예 : ( C # ) 또는 update_row(item, external_call=True)(Python)).

    별도의 변수를 사용하라는 제안은 명명 된 인수를 시뮬레이션하는 한 가지 방법이지만 관련 안전 이점이 없습니다 (해당 인수에 대해 올바른 변수 이름을 사용했다고 보장 할 수는 없습니다).

  • 더 나은 이름으로 공용 인터페이스에 고유 한 기능 을 사용하십시오 . 이것은 이름에 paremeter 값을 넣어 명명 된 매개 변수를 시뮬레이션하는 또 다른 방법입니다.

    이것은 매우 읽기 쉽지만 이러한 기능을 작성하는 많은 상용구로 이어집니다. 또한 부울 인수가 여러 개인 경우 조합 폭발을 잘 처리 할 수 ​​없습니다. 중요한 단점은 클라이언트가이 값을 동적으로 설정할 수 없지만 올바른 함수를 호출하려면 if / else를 사용해야한다는 것입니다.

  • 열거 형을 사용하십시오 . 부울의 문제점은 "true"및 "false"입니다. 따라서 더 나은 이름을 가진 유형을 소개하십시오 (예 :) enum CallType { INTERNAL, EXTERNAL }. 추가 혜택으로, 프로그램 의 형식 안전성 이 향상 됩니다 (언어가 열거 형을 고유 한 형식으로 구현하는 경우). 열거 형의 단점은 공개적으로 보이는 API에 유형을 추가한다는 것입니다. 순전히 내부 함수의 경우 이것은 중요하지 않으며 열거 형에는 큰 단점이 없습니다.

    열거 형이없는 언어에서는 짧은 문자열 이 대신 사용되는 경우가 있습니다. 이것은 작동하며 원시 부울보다 낫지 만 오타에 매우 취약합니다. 그런 다음 함수는 인수가 가능한 값 세트와 일치한다고 즉시 주장해야합니다.

이러한 솔루션 중 어느 것도 엄청나게 성능에 영향을 미치지 않습니다. 명명 된 매개 변수와 열거 형은 컴파일 타임에 컴파일 할 수 있습니다 (컴파일 된 언어의 경우). 문자열을 사용하면 문자열을 비교할 수 있지만 작은 문자열 리터럴과 대부분의 응용 프로그램에서는 비용이 무시할 수 있습니다.


7
방금 "명명 된 인수"가 존재한다는 것을 알게되었습니다. 나는 이것이 최고의 제안이라고 생각합니다. 해당 옵션에 대해 약간의 설명을 할 수 있다면 (다른 사람들이 구글에 대해 너무 많이 필요하지 않도록)이 답변을 수락합니다. 고맙습니다
Mario Garcia

6
짧은 문자열로 인한 문제를 완화시키는 데 도움이되는 한 가지는 문자열 값으로 상수를 사용하는 것입니다. @MarioGarcia 정교하게 할 것이 많지 않습니다. 여기에 언급 된 내용 외에도 대부분의 세부 사항은 특정 언어에 따라 다릅니다. 여기서 언급하고 싶은 특별한 것이 있었습니까?
jpmc26

8
우리가하는 방법에 대해 얘기하고 언어에서 열거 내가 일반적으로 열거를 사용하는 클래스에 범위가 될 수있다. 그것은 완전히 자체 문서화 (enum 유형을 선언하는 단일 코드 행에서 너무 가벼우므로), 메소드가 벌거 벗은 부울로 호출되는 것을 완전히 막고 공개 API에 대한 영향은 무시할 수 있습니다 (식별자 때문에) 클래스에 적용됩니다).
davidbak

4
이 역할에서 문자열을 사용하는 또 다른 단점은 열거 형과 달리 컴파일 타임에 유효성을 확인할 수 없다는 것입니다.
Ruslan

32
enum 이 승자입니다. 매개 변수가 두 가지 가능한 상태 중 하나만 가질 수 있다고해서 자동으로 부울이어야한다는 의미는 아닙니다.
Pete

39

올바른 해결책은 제안한대로하는 것이지만 미니 외관으로 패키지하는 것입니다.

void updateRowExternally() {
  bool externalCall = true;
  UpdateRow(item, externalCall);
}

가독성은 미세 최적화보다 우선합니다. 부울 플래그의 의미를 한 번만 찾아야하는 개발자의 노력을 감당할 수있는 것보다 확실히 함수 호출을 추가 할 수 있습니다.


8
이 캡슐화는 컨트롤러에서 해당 기능을 한 번만 호출 할 때 실제로 가치가 있습니까?
마리오 가르시아

14
예. 그렇습니다. 위에서 쓴 것처럼 그 이유는 비용 (함수 호출)이 이익보다 적기 때문 true입니다. CPU 시간이 훨씬 저렴하기 때문에 개발자 시간을 절약하는 것만 큼 많은 CPU 시간이 소요됩니다.
Kilian Foth

8
또한 유능한 옵티마이 저가이를 인라인 할 수 있어야하므로 결국에는 아무것도 잃지 않아야합니다.
firedraco

33
@KilianFoth 허위 가정을 제외하고. 관리자 두 번째 매개 변수의 기능과 그 이유를 알아야하며 거의 항상 수행하려는 작업의 세부 사항과 관련이 있습니다. 1 천 단위의 작은 기능에서 기능을 숨기면 유지 관리 성이 떨어지고 증가하지는 않습니다. 나는 그것이 슬프게도 스스로 일어나는 것을 보았습니다. 함수로의 과도한 파티셔닝은 실제로 거대한 신 함수보다 나쁠 수 있습니다.
Graham

6
나는 UpdateRow(item, true /*external call*/);그 주석 구문을 허용하는 언어로 더 깨끗 하다고 생각 합니다. 주석 작성을 피하기 위해 추가 기능으로 코드를 작성하는 것은 간단한 경우에는 가치가없는 것 같습니다. 어쩌면이 기능에 대한 다른 인수가 많거나 주변 코드가 어수선하고 까다로워지면 더 매력적일 것입니다. 그러나 디버깅을하는 경우 래퍼 기능을 검사하여 기능을 확인하고 라이브러리 API 주위의 얇은 래퍼 기능과 실제로 논리가있는 기능을 기억해야한다고 생각합니다.
Peter Cordes

25

UpdateRow (var item, bool externalCall)와 같은 기능이 있다고 가정 해보십시오.

왜 이런 기능이 있습니까?

어떤 상황 에서 externalCall 인수를 다른 값으로 설정하여 호출합니까?

하나는 외부 클라이언트 응용 프로그램에서 왔고 다른 하나는 동일한 프로그램 (예 : 다른 코드 모듈) 내에있는 경우 두 가지 다른 방법 을 사용해야한다고 주장합니다. 인터페이스.

그러나 비 프로그램 소스에서 가져온 일부 데이텀 (예 : 구성 파일 또는 데이터베이스 읽기)을 기반으로 호출 한 경우 부울 전달 방법이 더 적합합니다.


6
동의한다. 그리고 다른 독자들에 대한 요점을 망치기 위해 참, 거짓 또는 변수를 전달하는 모든 발신자에 대한 분석을 수행 할 수 있습니다. 후자가 발견되지 않으면 부울이있는 두 가지 방법 (부울이없는)에 대해 논쟁 할 것입니다. 부울이 사용되지 않거나 불필요한 복잡성을 유발한다는 것은 YAGNI 인수입니다. 일부 호출자가 변수를 전달하고 있더라도 상수를 전달하는 호출자에 사용할 (부울) 매개 변수가없는 버전을 제공 할 수 있습니다. 더 간단합니다.
Erik Eidt

18

가독성과 가치 안전을 모두 구현하기 위해 언어 기능을 사용하는 것이 이상적 이지만 , 실제 접근 방식 인 통화 시간 설명을 선택할 수도 있습니다 . 처럼:

UpdateRow(item, true /* row is an external call */);

또는:

UpdateRow(item, true); // true means call is external

또는 (Frax가 제안한대로) :

UpdateRow(item, /* externalCall */true);

1
공감할 이유가 없습니다. 이것은 완벽하게 유효한 제안입니다.
Suncat2000

<3 @ Suncat2000을 감사하십시오!
감독

11
(아마도 바람직한) 변형은 그냥 일반 인수 이름을 넣는 것 UpdateRow(item, /* externalCall */ true )입니다. 전체 문장 주석은 구문 분석하기가 훨씬 어렵습니다. 실제로 대부분 소음입니다 (특히 두 번째 변형은 인수와 매우 느슨하게 결합됩니다).
Frax

1
이것이 가장 적합한 답변입니다. 이것이 바로 주석의 의도입니다.
Sentinel

9
이와 같은 의견에 대한 팬은 아닙니다. 리팩토링하거나 다른 변경을 수행 할 때 주석을 건드리지 않으면 주석이 부패하기 쉽습니다. 부울 매개 변수가 두 개 이상 있으면 언급이 빠르므로 혼동이 빠릅니다.
John V.

11
  1. 당신은 당신의 바보를 '이름'할 수 있습니다. 다음은 OO 언어 (제공 클래스에서 표현할 수있는 UpdateRow())의 예이지만 개념 자체는 모든 언어로 적용될 수 있습니다.

    class Table
    {
    public:
        static const bool WithExternalCall = true;
        static const bool WithoutExternalCall = false;
    

    그리고 전화 사이트에서 :

    UpdateRow(item, Table::WithExternalCall);
    
  2. 아이템 # 1이 더 낫다고 생각하지만 함수를 사용할 때 사용자가 새로운 변수를 사용하도록 강요하지는 않습니다. 유형의 안전이 당신을 위해 중요하다면, 당신은 만들 수 있습니다 enum종류와 만드는 UpdateRow()대신 이것을 받아 들일 bool:

    UpdateRow(var item, ExternalCallAvailability ec);

  3. 어떻게 든 bool매개 변수 의 의미를 더 잘 반영하도록 함수 이름을 수정할 수 있습니다 . 확실하지 않지만 어쩌면 :

    UpdateRowWithExternalCall(var item, bool externalCall)


8
로 함수를 호출하면 # 3이 마음에 들지 않으므로 externalCall=false이름은 절대 의미가 없습니다. 나머지 답변은 좋습니다.
궤도에서 가벼운 레이스

동의했다. 잘 모르겠지만 다른 기능에 대한 실행 가능한 옵션 일 수 있습니다. I
simurg

@LightnessRacesinOrbit 실제로. 가는 것이 더 안전합니다 UpdateRowWithUsuallyExternalButPossiblyInternalCall.
Eric Duminil

@EricDuminil : 😂 on Spot
궤도에서 가벼움 경주

10

아직 읽지 않은 또 다른 옵션은 다음과 같습니다. 최신 IDE를 사용하십시오.

예를 들어 IntelliJ IDEA는 true또는 null또는 같은 리터럴을 전달하는 경우 호출하는 메소드에서 변수의 변수 이름을 인쇄합니다 username + “@company.com. 이것은 작은 글꼴로 이루어 지므로 화면의 공간을 너무 많이 차지하지 않으며 실제 코드와 매우 다르게 보입니다.

나는 여전히 어디서나 부울을 던지는 것이 좋은 생각이라고 말하지 않습니다. 작성하는 것보다 훨씬 더 자주 코드를 읽는다는 주장은 매우 강력하지만,이 경우에는 귀하 (및 동료)가 귀하의 업무를 지원하는 데 사용하는 기술에 크게 의존합니다. IDE를 사용하면 예를 들어 vim보다 문제가 훨씬 적습니다.

테스트 설정의 일부를 수정 한 예 : 여기에 이미지 설명을 입력하십시오


5
팀의 모든 사람들이 IDE를 사용하여 코드를 읽은 경우에만 해당됩니다. 비 IDE 편집기의 보급에도 불구하고 코드 검토 도구, 소스 제어 도구, 스택 오버플로 질문 등이 있으며 이러한 상황에서도 코드를 계속 읽을 수 있어야합니다.
Daniel Pryden

@DanielPryden, 따라서 내 '및 동료'의 의견. 회사에서 표준 IDE를 사용하는 것이 일반적입니다. 다른 도구에 관해서는, 나는 도구의 그 종류도이 기능을 지원 전적으로 가능하거나 미래에, 또는 그 인수는 단순히 (2 인 페어 프로그래밍 팀 등)이 적용되지 않는 것이라고 주장 할 것이다
Sebastiaan 반 덴 Broek

2
@Sebastiaan : 동의하지 않는 것 같습니다. 솔로 개발자 일지라도 정기적으로 Git diffs에서 내 코드를 읽습니다. Git은 IDE가 할 수있는 것과 같은 종류의 상황 인식 시각화를 할 수 없을 것입니다. 코드 작성을 돕기 위해 좋은 IDE를 사용하고 있지만 IDE 없이는 작성하지 않은 코드를 작성하기위한 변명으로 IDE를 사용해서는 안됩니다.
Daniel Pryden

1
@DanielPryden 나는 매우 거친 코드 작성을 옹호하지 않습니다. 흑백 상황이 아닙니다. 과거에는 특정 상황에서 40 %가 부울을 가진 함수를 작성하는 데 유리하고 60 %는 그렇지 않은 경우가있을 수 있습니다. 아마도 좋은 IDE 지원으로 균형은 55 %, 그렇지 않으면 45 %입니다. 그리고 그래도 여전히 IDE 외부에서 그것을 읽어야 할 수도 있으므로 때로는 완벽하지 않습니다. 그러나 코드를 명확하게하기 위해 다른 방법이나 열거를 추가하는 것과 같은 대안에 대한 유효한 균형입니다.
세바스티안 반 덴 브 ek

6

2 일 동안 아무도 다형성을 언급하지 않았습니까?

target.UpdateRow(item);

항목으로 행을 업데이트하려는 클라이언트 인 경우 데이터베이스 이름, 마이크로 서비스 URL, 통신에 사용되는 프로토콜 또는 외부 호출 여부에 대해 생각하고 싶지 않습니다. 그렇게하기 위해 사용되어야합니다. 나에게 이러한 세부 사항을 밀어 넣지 마십시오. 내 항목을 가져 와서 이미 어딘가에 행을 업데이트하십시오.

그렇게하면 건설 문제의 일부가됩니다. 여러 가지 방법으로 해결할 수 있습니다. 여기 하나가 있습니다 :

Target xyzTargetFactory(TargetBuilder targetBuilder) {
    return targetBuilder
        .connectionString("some connection string")
        .table("table_name")
        .external()
        .build()
    ; 
}

당신이 그것을보고 "내 언어가 논쟁을 명명했다면, 나는 이것을 필요로하지 않는다"고 생각하고 있다면. 좋아요, 좋아요 무슨 말을하는지조차 모르는 발신 클라이언트로부터이 말이되지 않도록하십시오.

이것은 의미상의 문제 이상이라는 점에 유의해야합니다. 디자인 문제이기도합니다. 부울을 전달하면 분기를 사용하여 처리해야합니다. 객체 지향적이지 않습니다. 둘 이상의 부울을 전달해야합니까? 언어가 여러 번 발송되기를 바랍니다. 컬렉션을 중첩 할 때 수행 할 수있는 작업을 살펴보십시오. 올바른 데이터 구조는 당신의 인생을 훨씬 쉽게 만들어 줄 수 있습니다.


2

updateRow함수 를 수정할 수 있다면 아마도 두 가지 함수로 리팩토링 할 수 있습니다. 함수가 부울 매개 변수를 사용한다고 가정하면 다음과 같이 보입니다.

function updateRow(var item, bool externalCall) {
  Database.update(item);

  if (externalCall) {
    Service.call();
  }
}

코드 냄새가 약간납니다. externalCall변수 설정 에 따라 기능이 크게 다를 수 있으며 ,이 경우 두 가지 다른 책임이 있습니다. 한 가지 책임 만있는 두 가지 기능으로 리팩토링하면 가독성이 향상 될 수 있습니다.

function updateRowAndService(var item) {
  updateRow(item);
  Service.call();
}

function updateRow(var item) {
  Database.update(item);
}

이제이 함수를 호출하는 모든 곳에서 외부 서비스가 호출되는지 한 눈에 알 수 있습니다.

물론 항상 그런 것은 아닙니다. 상황과 맛의 문제입니다. 부울 매개 변수를 두 함수로 리팩토링하는 것은 일반적으로 고려할 가치가 있지만 항상 최선의 선택은 아닙니다.


0

UpdateRow가 제어 코드베이스에 있다면 전략 패턴을 고려할 것입니다.

public delegate void Request(string query);    
public void UpdateRow(Item item, Request request);

여기서 Request는 일종의 DAO (사소한 경우 콜백)를 나타냅니다.

실제 사례 :

UpdateRow(item, query =>  queryDatabase(query) ); // perform remote call

틀린 경우 :

UpdateRow(item, query => readLocalCache(query) ); // skip remote call

일반적으로 엔드 포인트 구현은 더 높은 수준의 추상화로 구성되며 여기서 사용합니다. 유용한 무료 부작용으로, 코드를 변경하지 않고도 원격 데이터에 액세스하는 다른 방법을 구현할 수있는 옵션을 제공합니다.

UpdateRow(item, query => {
  var data = readLocalCache(query);
  if (data == null) {
    data = queryDatabase(query);
  }
  return data;
} );

일반적으로, 이러한 제어 역전은 데이터 스토리지와 모델 간의 결합을 줄입니다.

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