플래그 변수는 절대적으로 악한가? [닫은]


47

플래그 변수는 사악합니까? 다음과 같은 종류의 변수는 심오하게 부도덕하고 사용하는 것이 사악합니까?

"특정 위치에서 값을 할당하고 아래에서 아래로 값을 지정하는 부울 또는 정수 변수는 예를 들어 newItem = true아래의 일부 행을 사용하여 if (newItem ) then"


플래그 사용을 완전히 무시하고 더 나은 아키텍처 / 코드로 끝나는 몇 가지 프로젝트를 수행 한 것을 기억합니다. 그러나 그것은 내가 일하는 다른 프로젝트에서 일반적인 관행이며 코드가 커지고 플래그가 추가되면 IMHO 코드-스파게티도 커집니다.

플래그를 사용하는 것이 좋은 습관이거나 필요한 경우가 있다고 말합니까, 아니면 코드에서 플래그를 사용하는 것이 적신호이며 피하거나 리팩토링해야한다는 데 동의하십니까? 나 대신 실시간으로 상태를 확인하는 함수 / 메소드를 수행하면됩니다.


9
MainMa와 저는 "플래그"의 정의가 다릅니다. 전 처리기 #ifdefs를 생각하고있었습니다. 어느쪽에 대해 물어 봤어?
Karl Bielefeldt

이것은 정말 좋은 질문입니다. 나는 이것을 나 자신이 많이 궁금해했고, 실제로 "오, 그냥 깃발을 사용하자"라고 말하는 것을 발견했다.
Paul Richter

부울은 플래그입니다. (정수도 마찬가지입니다 ...)
Thomas Eding

7
내가 영업 이익은 당신이 다음 아래로 사용, 예를 들어, 뭔가 여부, 등을 할 orther에 다음 확인 아래 특정 장소에 값을 할당하는 것이 논리 값으로 다스 려 나 변수 정수 생각 @KarlBielefeldt newItem = true아래에 다음 몇 줄을if (newItem ) then
Tulains 코르도바

1
또한 이 컨텍스트에서 변수 리팩토링을 설명 하는 소개를 고려하십시오 . 방법이 짧고 경로가 적 으면 유효한 것으로 간주합니다.
다니엘 B

답변:


41

플래그를 사용하는 코드를 유지할 때 내가 본 문제는 상태 수가 빠르게 증가하고 처리되지 않은 상태가 거의 항상 있다는 것입니다. 내 경험의 한 예 :이 세 가지 플래그가있는 코드를 작성하고있었습니다.

bool capturing, processing, sending;

이 세 개는 8 개의 상태를 만들었습니다 (실제로 두 개의 다른 플래그도있었습니다). 가능한 모든 값 조합이 코드에 포함 된 것은 아니며 사용자에게 버그가 표시되었습니다.

if(capturing && sending){ // we must be processing as well
...
}

위의 if 문에서 가정이 틀린 상황이 있음이 밝혀졌습니다.

플래그는 시간이 지남에 따라 합성되는 경향이 있으며 클래스의 실제 상태를 숨 깁니다. 그래서 피해야합니다.


3
+1, "피해야한다". 나는 '일부 상황에서는 깃발이 필요하다'(일부는 '필요한 악'이라고 말할 수 있음)에 대해 뭔가를 추가 할 것입니다
Trevor Boyd Smith

2
@TrevorBoydSmith 내 경험상 그들은 그렇지 않습니다, 당신은 단지 플래그에 사용할 평균 두뇌 힘보다 조금 더 필요합니다
dukeofgaming

귀하의 예에서 그것은 부울이 아닌 상태를 나타내는 단일 열거 형이어야합니다.
user949300

지금 당면한 것과 비슷한 문제가 생길 수 있습니다. 가능한 모든 상태를 다루는 것 외에도 두 응용 프로그램이 같은 플래그를 공유 할 수 있습니다 (예 : 고객 데이터 업로드). 이 경우 하나의 업 로더 만 플래그를 사용하고 설정 한 후 향후 문제를 찾아 행운을 빕니다.
Alan

38

다음은 플래그가 유용한 예입니다.

암호를 생성하는 코드 조각이 있습니다 (암호 적으로 안전한 의사 난수 생성기를 사용하여). 이 메소드의 호출자는 비밀번호에 대문자, 소문자, 숫자, 기본 기호, 확장 기호, 그리스어 기호, 키릴 문자 및 유니 코드를 포함해야하는지 여부를 선택합니다.

플래그를 사용하면이 메소드를 호출하는 것이 쉽습니다.

var password = this.PasswordGenerator.Generate(
    CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);

심지어 다음과 같이 단순화 될 수도 있습니다.

var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);

플래그가 없으면 메소드 서명은 무엇입니까?

public byte[] Generate(
    bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols,
    bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);

이 같은 호출

// Very readable, isn't it?
// Tell me just by looking at this code what symbols do I want to be included?
var password = this.PasswordGenerator.Generate(
    true, true, true, false, false, false, false, false);

주석에서 언급했듯이 다른 접근 방식은 컬렉션을 사용하는 것입니다.

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LowercaseLetters,
        CharacterSet.UppercaseLetters,
    });

이것은 trueand 세트에 비해 훨씬 더 읽기 false쉽지만 여전히 두 가지 단점이 있습니다.

가장 큰 단점은 결합 된 값을 허용하기 위해 메소드 CharacterSet.LettersAndDigits에서 비슷한 것을 쓰는 것과 같습니다 Generate().

if (set.Contains(CharacterSet.LowercaseLetters) ||
    set.Contains(CharacterSet.Letters) ||
    set.Contains(CharacterSet.LettersAndDigits) ||
    set.Contains(CharacterSet.Default) ||
    set.Contains(CharacterSet.All))
{
    // The password should contain lowercase letters.
}

다음과 같이 다시 작성하십시오.

var lowercaseGroups = new []
{
    CharacterSet.LowercaseLetters,
    CharacterSet.Letters,
    CharacterSet.LettersAndDigits,
    CharacterSet.Default,
    CharacterSet.All,
};

if (lowercaseGroups.Any(s => set.Contains(s)))
{
    // The password should contain lowercase letters.
}

플래그를 사용하여 이것을 당신이 가진 것과 비교하십시오 :

if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters)
{
    // The password should contain lowercase letters.
}

둘째, 매우 사소한 단점은 다음과 같이 호출하면 메소드가 어떻게 작동하는지 명확하지 않다는 것입니다.

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });

10
나는 OP가 특정 장소에서 값을 할당 한 부울 또는 정수 변수를 참조한다고 생각합니다. 아래에서 아래로 확인한 다음 아래에서 newItem = true일부 줄을 사용하는 것과 같이 무언가를 수행할지 여부를 결정하십시오.if (newItem ) then
Tulains Córdova

1
@MainMa 분명히 3 분의 1이있다 : 8 개의 boolean arguments를 가진 버전은 내가 "플래그"를 읽을 때 내가 생각한 것이다 ...
Izkata

4
죄송하지만 IMHO는 메소드 체인 ( en.wikipedia.org/wiki/Method_chaining )에 대한 완벽한 경우입니다 . 또한 매개 변수 배열 (연관 배열 또는 맵이어야 함)을 사용할 수 있습니다. 생략하면 해당 매개 변수에 대한 기본값 동작이 사용됩니다. 결국 메서드 체인 또는 매개 변수 배열을 통한 호출은 비트 플래그만큼 간결하고 표현적일 수 있습니다. 또한 모든 언어에 비트 연산자가있는 것은 아닙니다 (실제로는 이진 플래그를 좋아하지만 대신 방금 언급 한 방법을 사용합니다).
dukeofgaming

3
별로 OOP가 아닙니까? 인터페이스를 ala로 만들 것입니다 : String myNewPassword = makePassword (randomComposeSupplier (new RandomLowerCaseSupplier (), new RandomUpperCaseSupplier (), new RandomNumberSupplier)); String으로 makePassword (Supplier <Character> charSupplier); 공급 업체 <문자> randomComposeSupplier (공급 업체 <문자> ... 공급자); 이제 공급 업체를 다른 작업에 재사용하고 원하는 방식으로 작성하고 generatePassword 메소드를 단순화하여 최소 상태를 사용할 수 있습니다.
Dibbeke

4
@Dibbeke 명사왕국에 대해 이야기 ...
Phil

15

거대한 기능 블록은 플래그가 아니라 냄새입니다. 5 행에서 플래그를 설정 한 경우, 354 행에서 플래그 만 확인하면 좋지 않습니다. 8 행에서 플래그를 설정하고 10 행에서 플래그를 확인하면 문제가 없습니다. 또한 코드 블록 당 하나 또는 두 개의 플래그는 괜찮으며 함수의 300 플래그는 좋지 않습니다.


10

일반적으로 플래그는 가능한 모든 전략 값으로 대체 될 수 있으며 플래그의 가능한 모든 값에 대해 하나의 전략 구현이 있습니다. 이를 통해 새로운 행동을 훨씬 쉽게 추가 할 수 있습니다.

성능이 중요한 상황에서는 간접 비용으로 인해 해체가 명확한 플래그로 표시 될 수 있습니다 . 그것은 실제로 내가해야했던 한 가지 사례를 기억하는 데 어려움을 겪고 있다고합니다.


6

아닙니다. 깃발은 나쁘지 않으며 모든 비용으로 리팩토링해야합니다.

Java의 Pattern.compile (String regex, int flags) 호출을 고려하십시오 . 이것은 전통적인 비트 마스크이며 작동합니다. Java 의 상수 를 살펴보면 2 n 의 무리가있는 곳마다 플래그가 있다는 것을 알 수 있습니다.

이상적인 리팩토링 된 세계에서는 대신 상수가 열거 형의 값이며 설명서에서 읽을 때 EnumSet을 대신 사용합니다 .

이 클래스의 공간 및 시간 성능은 기존의 int 기반 "비트 플래그"에 대한 고품질의 안전한 유형의 대안으로 사용할 수있을만큼 우수해야합니다.

완벽한 세계에서 Pattern.compile 호출은이됩니다 Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags).

모든 것은 여전히 ​​플래그입니다. 플래그가 실제로 있고 좋은 것을 수행하는 Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)것보다 Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())다른 스타일을 사용하는 것보다 작업하기가 훨씬 쉽습니다 .

시스템 레벨 작업을 수행 할 때 플래그가 종종 표시됩니다. 운영 체제 레벨에서 무언가와 인터페이스 할 때 프로세스의 리턴 값, 파일의 권한 또는 소켓을 열기위한 플래그 등 어딘가에 플래그가있을 수 있습니다. 인식 된 코드 냄새에 대한 마녀 사냥에서 이러한 인스턴스를 리팩토링하려고 시도하면 사용 된 플래그를 받아들이고 플래그를 이해하는 것보다 더 나쁜 코드로 끝날 수 있습니다.

문제는 사람들이 플래그를 잘못 사용하여 모든 종류의 관련없는 플래그를 만들거나 플래그가 아닌 곳에서 사용하려고 할 때 발생합니다.


5

메소드 서명 내 플래그에 대해 이야기하고 있다고 가정합니다.

단일 플래그를 사용하면 충분하지 않습니다.

그것은 그들이 보는 시간에 동료들에게 아무 의미가 없습니다. 그들이하는 일을하기 위해서는 메소드의 소스 코드를 살펴 봐야합니다. 당신은 아마도 당신의 방법이 무엇인지 잊었을 때 아마도 몇 달 동안 같은 위치에있을 것입니다.

메소드에 플래그를 전달하는 것은 일반적으로 메소드가 여러 가지를 담당한다는 것을 의미합니다. 메소드 내부에서 아마도 다음 라인에서 간단한 점검을 수행하고 있습니다.

if (flag)
   DoFlagSet();
else
   DoFlagNotSet();

그것은 우려의 분리가 좋지 않으며 일반적으로 그 주위에 방법을 찾을 수 있습니다.

일반적으로 두 가지 방법이 있습니다.

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

해결하려는 문제에 적용 할 수있는 메소드 이름이 더 적합합니다.

여러 개의 플래그를 전달하는 것은 두 배나 나쁩니다. 실제로 여러 플래그를 전달해야하는 경우 클래스 내에서 플래그를 캡슐화하는 것을 고려하십시오. 그럼에도 불구하고, 당신의 방법은 여러 가지 일을하고 있기 때문에 여전히 같은 문제에 직면하게 될 것입니다.


3

플래그와 대부분의 임시 변수는 강한 냄새입니다. 대부분 리팩토링되고 쿼리 메소드로 대체 될 수 있습니다.

개정 :

상태를 표현할 때 플래그와 임시 변수는 쿼리 메소드로 리팩토링되어야합니다. 상태 값 (부울, 정수 및 기타 기본 요소)은 구현 세부 사항의 일부로 거의 항상 숨겨져 야합니다.

제어, 라우팅 및 일반 프로그램 흐름에 사용되는 플래그는 제어 구조의 섹션을 별도의 전략이나 팩토리 또는 상황에 따라 적절하게 쿼리 방법을 계속 사용하도록 리팩토링 할 수있는 기회를 나타낼 수도 있습니다.


2

플래그에 대해 이야기 할 때 프로그램 실행 시간에 걸쳐 플래그가 수정 될 것이며 상태에 따라 프로그램의 동작에 영향을 줄 것임을 알아야합니다. 우리가이 두 가지를 깔끔하게 제어 할 수 있다면 그것들은 훌륭하게 작동 할 것입니다.

깃발은

  • 적절한 범위에서 정의했습니다. 적절하게 나는 스코프에 수정이 필요하지 않은 코드를 포함해서는 안된다는 것을 의미합니다. 또는 적어도 코드는 안전합니다 (예 : 외부에서 직접 호출 할 수 없음)
  • 외부에서 플래그를 처리해야하고 플래그가 많은 경우 플래그 처리기를 플래그를 안전하게 수정하는 유일한 방법으로 코딩 할 수 있습니다. 이 플래그 핸들러 자체는 플래그 및 메소드를 수정하여 플래그를 수정합니다. 그런 다음 싱글 톤으로 만들 수 있으며 플래그에 액세스해야하는 클래스간에 공유 할 수 있습니다.
  • 플래그가 너무 많으면 마지막으로 유지 관리가 용이합니다.
    • 현명한 이름을 따라야한다고 말할 필요가 없습니다.
    • 유효한 값으로 문서화해야합니다 (열거와 함께있을 수 있음).
    • WHICH CODE WWICH CONDITION으로 문서화해야하며 WHICH CONDITION을 사용하면 플래그에 특정 값이 지정됩니다.
    • 어떤 코드가 그들을 소비하고 어떤 행동이 특정 가치를 낳을 것인가

플래그가 많은 지옥이있는 경우 플래그 이후 좋은 디자인 작업이 선행되어야하며 프로그램 동작에서 키 역할을 시작하십시오. 모델링을위한 상태 다이어그램으로 이동할 수 있습니다. 이러한 다이어그램은 문서를 다루는 동안 문서화 및 시각적 지침으로도 작동합니다.

이러한 것들이 제자리에있는 한 나는 혼란을 초래하지 않을 것이라고 생각합니다.


1

QA는 함수 매개 변수의 비트가 아니라 플래그 (전역) 변수를 의미한다고 생각했습니다.

다른 가능성이 많지 않은 상황이 있습니다. 예를 들어, 운영 체제가 없으면 인터럽트를 평가해야합니다. 인터럽트가 매우 자주 발생하고 ISR에서 긴 평가를 수행 할 시간이 없다면 ISR에 일부 글로벌 플래그 만 설정하는 것이 허용되는 것이 아니라 때로는 모범 사례입니다 (가능한 한 적은 시간을 소비해야 함) ISR에서)하고 메인 루프에서 해당 플래그를 평가합니다.


0

나는 생각하지 않는다 아무것도 이제까지, 프로그램의 절대 악이다.

플래그가 순서대로있을 수있는 또 다른 상황이 있습니다.

이 Javascript 스 니펫에서 클로저 사용을 고려하십시오.

exports.isPostDraft = function ( post, draftTag ) {
  var isDraft = false;
  if (post.tags)
    post.tags.forEach(function(tag){ 
      if (tag === draftTag) isDraft = true;
    });
  return isDraft;
}

"Array.forEach"에 전달 된 내부 함수는 단순히 "true"를 반환 할 수 없습니다.

따라서 플래그를 사용하여 상태를 외부에 유지해야합니다.

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