可空类型是否比魔术数字更可取?


22

最近我一直在和一位同事辩论。我们专门使用C#,但这可以应用于具有可空类型的任何语言。假设您有一个代表最大值的值。但是,此最大值是可选的。我认为可为空的数字是可取的。我的同事援引先例,赞成使用零。当然,诸如网络套接字之类的东西通常使用零来表示无限超时。如果我今天要编写处理套接字的代码,我个人会使用可为空的值,因为我认为它可以更好地表示没有超时这一事实。

哪个代表更好?两者都需要条件检查其含义为“ none”的值,但是我相信可以为null的类型可以更好地传达意图。


6
如果使用数字,则将其放在常量中,而不要直接放在代码中。
Renato Dinhani 2012年

@RenatoDinhaniConceição不能成为一般规则。否则,最终将对所有内容进行软编码
西蒙·贝格

Answers:


24

考虑:

  • 语言,

  • 框架

  • 上下文。

1.语言

使用∞是最大的解决方案。

  • 例如,JavaScript具有无穷大。C#不会¹。

  • 例如,Ada具有范围。C#没有。

在C#中,存在int.MaxValue,但是您不能在这种情况下使用它。int.MaxValue是最大整数2,147,483,647。如果在您的代码中,您具有某个最大值,例如某个事物爆炸之前的最大接受压力,则使用2,147,483,647是没有意义的。

2.框架

.NET Framework在这一点上并不一致,并且它对魔术值的使用可能会受到批评。

例如,"Hello".IndexOf("Z")返回一个魔术值-1。这可能使操作结果更容易(是吗?):

int position = "Hello".IndexOf("Z");
if (position > 0)
{
    DoSomething(position);
}

而不是使用自定义结构:

SearchOccurrence occurrence = "Hello".IndexOf("Z");
if (occurrence.IsFound)
{
    DoSomething(occurrence.StartOffset);
}

但一点都不直观。为什么-1-123呢?初学者可能还会错误地认为这也0意味着“未找到”,或者只是打字错误(position >= 0)

3.上下文

如果您的代码与网络套接字中的超时有关,那么使用几十年来为保持一致而被每个人使用的东西并不是一个坏主意。特别是,0超时非常清楚:它的值不能为零。在这种情况下,使用自定义类可能会使事情变得更难以理解:

class Timeout
{
    // A value indicating whether there is a timeout.
    public bool IsTimeoutEnabled { get; set; }

    // The duration of the timeout, in milliseconds.
    public int Duration { get; set; }
}
  • 我可以将其设置Duration为0 IsTimeoutEnabled吗?
  • 如果IsTimeoutEnabled为假,设置Duration为100会怎样?

这可能导致多个错误。想象一下下面的代码:

this.currentOperation.Timeout = new Timeout
{
    // Set the timeout to 200 ms.; we don't want this operation to be longer than that.
    Duration = 200,
};

this.currentOperation.Run();

该操作将运行十秒钟。您可以在不阅读Timeout类文档的情况下看到这段代码有什么问题吗?

结论

  • null很好地表达了价值不在这里的想法。没有提供。无法使用。它既不是数字,也不是零/空字符串。请勿将其用于最大值或最小值。

  • int.MaxValue与语言本身密切相关。请勿int.MaxValue将最高速度限制用于Vehicle某类飞机,或将其用于飞机等的最高可接受速度。

  • 避免像-1代码中那样使用魔术值。它们具有误导性,并导致代码错误。

  • 创建您自己的类,使用指定的最小/最大值将更简单。例如VehicleSpeed可以有VehicleSpeed.MaxValue

  • 如果这是几十年来在非常特定的领域中普遍使用的惯例,并且大多数人在该领域编写代码时都使用过,则不要遵循任何先前的指南并使用魔术值。

  • 不要忘记混合使用方法。例如:

    class DnsQuery
    {
        public const int NoTimeout = 0;
    
        public int Timeout { get; set; }
    }
    
    this.query.Timeout = 0; // For people who are familiar with timeouts set to zero.
    // or
    this.query.Timeout = DnsQuery.NoTimeout; // For other people.
    

¹您可以创建自己的类型,包括无穷大。在这里,我int仅在谈论本机类型。


1
“使用每个人几十年来为了保持一致而使用的某种东西不是一个坏主意” /“如果在特定领域使用了数十年的常规惯例,则不要遵循任何先前的指南并使用魔术值。大多数人在该领域编写代码。” -我想某处有错字?
deworde 2012年

1
@deworde我相信MainMa指的是他本人在那之上给出的指导方针。
2012年

1
我不同意indexOf的示例,因为-1在字符串之外,Z最肯定是在字符串之外。
约书亚·德雷克

5
“例如,JavaScript具有无穷大。C#没有。” - 嗯?
BlueRaja-Danny Pflughoeft 2012年

我特别建议+1,特别是针对“创建自己的课程”。任何时候只要裸露的人int对类型的表达不足以约束问题,请考虑使用具有更多信息的新结构(例如,表示魔术值的结构的const实例,或在其上的枚举表示)。或考虑合同编程或其他解决方案,但我认为自定义结构最简单。
CodexArcanum 2012年

12

空无比魔数更好。

重要的是要命名具有魔术效果的值(如果必须具有这些值),并确保这些名称的定义在任何碰到魔术值和wtf的人都可以看到的地方。

if (timeout == 4298435) ... // bad.
if (timeout == null) ... // bad.
if (timeout == NEVER_TIME_OUT) ... // yay! puppies and unicorns!

2
好的,也许确实更多地取决于语言,但是在C#中,您可能会这样做:if(timeout.HasValue)而不是直接比较为null。
Matt H

2
空没有比魔术数字差。使用幻数,您永远不会知道幻数是什么...它可能是0,-1或其他。null只是null。
marco-fiset 2012年

9
空意味着没有价值。这是许多魔术数字打算表达的概念。能够将null与可为null的类型一起使用是比从数据类型的可能值范围中选择一个任意值更好的解决方案。

2
如果类型为“ null”,则使用“ null”作为魔术值就可以了。重要的是要命名,因为确定要射击下一个要来的家伙不会知道你的意思。Null可以表示“无穷大”,“尚未指定”,“创建数据结构的代码中的错误”或任何其他数目。只有一个名称可以让下一个编码器知道您要存储的值以及要触发的行为。
mjfgates 2012年

1
@CodeInChaos:我知道你可以两者都做,但是我更喜欢HasValue。实际上,我通常并不喜欢null,但是使用HasValue的可为null的类型对我来说更接近Option / Maybe类型,而我很喜欢。
Matt

10

MAGIC_NUMBER绝对应尽可能避免使用任何代码。null是更清晰的意图表达。


6

在C#中,许多CLR类都有一个静态Empty成员:

  • System.String.Empty
  • System.EventArgs.Empty
  • System.Guid.Empty
  • System.Drawing.Rectangle.Empty
  • System.Windows.Size.Empty

这使您不必记住是否要使用魔术值或使用null来构造空对象。

但是,如果您要处理的是像 int呢?在这种情况下,请考虑您是否是原始痴迷的受害者。您看似简单的数字属性很可能会从其自己的类或结构中受益,这将使您可以指定Empty成员并添加特定于该值的其他行为。


3

在这种情况下,空值是指示没有最大值的好方法。通常,当特殊情况意味着所讨论的值不适用,或者您只是不想使用它配置的功能时,null就是一个很好的指示。

使用null表示特殊情况的一个问题是只有一个null值,并且可能有多个特殊情况。在这种情况下,我将枚举作为附加参数传递,这可以表示一种特殊情况,或者可以正常使用int值。(这实际上是Nullable <>为您所做的,尽管它使用布尔值而不是枚举并将参数组合为单个结构。)


3

在这种情况下,我认为可以为null的类型是很有意义的。

空意味着没有价值。这是与值为0的数字截然不同的概念。

如果您想说“如果我不给您一个值,请使用最大值”,那么传入null是表达该值的确切正确方法。


1

空:常见错误值,未指定,无效或不存在值。

零:实际(但不一定是逻辑或直观值)(在此情况下)。也是初始化中的常用值。

在您遇到问题时, timeoutInMilliseconds遇到属性是可选的,并且没有提及此方法的开销会使它失去选择的资格。

结论:有例外,解决方案因语言和领域而异;在这种情况下,我将选择Null。(我相信)有些人误解的地方是他们没有很好地将数据与接口分开。他们只是希望任何客户阅读文档(或实现)来确定如何使用/处理这些特殊值-特殊情况会泄漏到客户程序中,并且可能还不清楚。通过添加良好的抽象层,用法可以更加清晰。


0

使用Null比使用Null更糟糕MagicNumber。Null代表了更好表达的想法,但是在平台上它的行为方式并不一致,使用MagicNumber总是一样,这是有益的。

根据使用的环境/语言,null可以

  • 就是0
  • 可能不合法
  • 可能由于三向逻辑而产生意外结果

MagicNumber 总是表现相同。


0

如果您忘记检查幻数(即将发生),那么幻数将在不正确的数据下持续一会儿。最好使null尽快导致异常。


-1

Null不是幻数的唯一替代方法。

public static int NO_TIMEOUT = 0;  // javaish

空是邪恶的。在上面的示例中,您可能可以摆脱它,因为代码显然可以处理null。但是总的来说,当您开始传递空值时,迟早会收到一个空指针异常。第一次编写代码时可能不会发生,但是代码的维护时间远比第一次发行的时间长。它通常由对系统了解不多的人(最初的开发人员)来维护。

例如,Scala在Option类中有一个不错的选择。Option类具有两个值之一,“某些”-包装您真正想要的值,“无”-没有值。

对于任何开发人员来说,这显然都是没有价值的,您最好为此编写代码。好吧,无论如何应该使它变得显而易见。

并不是所有的幻数都是问题。取决于上下文0、1、1024等都是显而易见的。347?是的,您应该避免这种情况。:-)


4
-1:证明“无效为恶”。
deworde 2012年

4
为数字定义别名不会改变它仍然是魔术数字的事实。

好吧,也许您对魔术数字的定义与我不同。请参阅en.wikipedia.org/wiki/…–
乔恩·

1
我在这里同意乔恩·斯特雷耶(Jon Strayer)的观点。Null是ADT的一种示例,该语言实际上并不支持ADT。OP也许可以在这里摆脱它,但是总的来说,我认为任何语言为null的语言都会使程序员稍微失败。
杰里米·沃尔
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.