标志变量是绝对邪恶吗?[关闭]


47

标志变量是邪恶的吗?以下类型的变量是否极不道德,是否邪恶地使用它们?

“布尔值或整数变量,您可以在某些位置分配值,然后向下选择以下值,然后进行其他操作,例如,使用newItem = true下面的某些行if (newItem ) then


我记得做过几个项目,在这些项目中我完全忽略了使用标志,最终得到了更好的体系结构/代码。但是,这在我从事的其他项目中是一种常见的做法,当代码增长并添加标志时,恕我直言,意大利面条也随之增长。

您是否会说在任何情况下都使用标志是一种好习惯甚至是必要的做法?或者您是否同意在代码中使用标志是...红色标志,应避免/重构。我,我只是通过执行实时检查状态的函数/方法来解决问题。


9
我和MainMa似乎对“标志”有不同的定义。我在想预处理器#ifdefs。您问的是哪一个?
Karl Bielefeldt 2012年

这是一个非常好的问题。我本人对此很纳闷,实际上发现自己说“哦,好吧,只使用一个标志”就太多了。
保罗·里希特

布尔值是标志。(整数也是如此……)
Thomas Eding

7
@KarlBielefeldt我相信OP会引用布尔值或整数变量,您在某些位置分配了一个值,然后向下检查或进行其他操作,例如使用newItem = true下面的几行if (newItem ) then
TulainsCórdova,2012年

1
还可以考虑在此背景下介绍变量重构的介绍。只要该方法仍然简短且通过它的路径数量少,我认为这是一种有效的用法。
Daniel B

Answers:


41

我在维护使用标志的代码时遇到的问题是状态数量迅速增长,并且几乎总是有未处理的状态。我的经验中的一个例子:我正在编写具有这三个标志的代码

bool capturing, processing, sending;

这三个创建了八个状态(实际上,还有两个其他标志)。该代码并未涵盖所有可能的值组合,并且用户看到了错误:

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

事实证明,在某些情况下,上述if语句中的假设是错误的。

随着时间的流逝,标记趋于复合,并且它们隐藏了类的实际状态。因此,应避免使用它们。


3
+1,“应避免”。我会添加一些有关“但标志在某些情况下是必需的”(有些人可能会说“必要的邪恶”)的信息
Trevor Boyd Smith

2
@TrevorBoydSmith根据我的经验,并非如此,您只需要比使用旗帜
所需

在您的示例中,它应该是代表状态的单个枚举,而不是3个布尔值。
user949300

您可能会遇到与我现在面临的类似问题。除了涵盖所有可能的状态之外,两个应用程序可能共享相同的标志(例如,上载客户数据)。在这种情况下,只有一个Uploader会使用该标志,将其设置为关闭,并祝您以后找到问题。
艾伦(Alan)

38

这是一个标志有用的例子。

我有一段代码可以生成密码(使用密码安全的伪随机数生成器)。该方法的调用者选择密码是否应包含大写字母,小写字母,数字,基本符号,扩展符号,希腊符号,西里尔字母和unicode。

使用标志,调用此方法很容易:

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,
    });

true和相比,它更具可读性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
TulainsCórdova2012年

1
@MainMa显然有第3个:带有8个布尔参数的版本是我读“ flags”时所想到的……
Izkata 2012年

4
抱歉,恕我直言,这是方法链接的完美案例(en.wikipedia.org/wiki/Method_chaining),此外,您可以使用参数数组(必须是关联数组或映射),其中该参数数组中的任何条目您忽略了对该参数使用默认值行为。最后,通过方法链或参数数组进行的调用可以像位标志一样简洁明了,并且,并非每种语言都具有位运算符(我实际上很喜欢二进制标志,但是会使用我刚才提到的方法)。
dukeofgaming 2012年

3
它不是非常面向对象,不是吗?我会做一个界面ala:字符串myNewPassword = makePassword(randomComposeSupplier(new RandomLowerCaseSupplier(),new RandomUpperCaseSupplier(),new RandomNumberSupplier)); 与字符串makePassword(Supplier <Character> charSupplier); 和Supplier <Character> randomComposeSupplier(Supplier <Character> ...供应商); 现在,您可以将您的供应商重用于其他任务,以您喜欢的任何方式组合它们,并简化您的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())

使用系统级事物时,通常会看到标志。在操作系统级别与某些事物进行接口时,可能在某处带有一个标志-可能是进程的返回值,文件的许可权或用于打开套接字的标志。与某些使用过的接受并理解该标志的人相比,在一些女巫中寻找可能的气味来试图重构这些实例时,最终可能会得到更糟糕的代码。

当人们滥用标志将它们扔在一起并创建各种不相关标志的frankenflag集或试图在根本不是标志的地方使用它们时,就会出现问题。


5

我假设我们正在谈论方法签名中的标志。

使用单个标志已经足够糟糕。

对于您的同事来说,这是毫无意义的。他们必须查看该方法的源代码才能确定其作用。当您忘记方法的全部内容时,您可能会在几个月后处于同一位置。

通常,将标志传递给方法意味着您的方法负责多项工作。在该方法内部,您可能需要对以下几行进行简单检查:

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

这是关注点分离的差点,您通常可以找到解决方法。

我通常有两种单独的方法:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

使用适用于您要解决的问题的方法名称将更有意义。

传递多个标志是错误的两倍。如果确实需要传递多个标志,则可以考虑将它们封装在一个类中。即使那样,您仍然会面临相同的问题,因为您的方法可能会做很多事情。


3

标记和大多数临时变量都是强烈的气味。它们很可能可以重构并替换为查询方法。

修订:

表达状态时的标志和临时变量应重构为查询方法。状态值(布尔值,整数和其他基元)几乎应始终作为实现详细信息的一部分被隐藏。

用于控制,路由和一般程序流的标志还可能指示有机会将控制结构的各个部分重构为单独的策略或工厂,或者可能在情况上适当的任何东西,这些继续使用查询方法。


2

当我们谈论标志时,我们应该知道它们将在程序执行期间被修改,并且它们将根据其状态影响程序的行为。只要我们对这两件事有很好的控制,它们就会发挥很大的作用。

标志可以很好地工作,如果

  • 您已经在适当的范围内定义了它们。适当地说,我的意思是范围不应包含任何不需要/不应修改它们的代码。或至少代码是安全的(例如,可能不能直接从外部调用)
  • 如果需要从外部处理标志,并且有很多标志,我们可以将标志处理程序编码为安全修改标志的唯一方法。该标志处理程序本身可以封装标志和修改它们的方法。然后可以使它成为单例,然后可以在需要访问标志的类之间共享。
  • 最后,如果标记太多,则是为了可维护性:
    • 无需说他们应该遵循明智的命名
    • 应记录有效值(可能包含枚举)
    • 应该用将要修改它们的每个代码进行记录,并且还应使用“将导致对标志分配特定值的条件”来进行记录。
    • 哪个代码将消耗它们,并且对于特定值将产生什么行为

如果有很多标志,那么应该先进行良好的设计工作,因为标志会开始在程序行为中发挥关键作用。您可以使用状态图进行建模。在处理这些图时,它们还可以作为文档和视觉指导。

只要这些东西到位,我认为它不会导致混乱。


1

从这个问题中,我认为质量保证是指标志(全局)变量,而不是功能参数的位。

在某些情况下,您没有太多其他可能性。例如,在没有操作系统的情况下,您必须评估中断。如果中断非常频繁,并且您没有时间在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”。

因此,您需要使用标志将状态保持在外面。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.