最近我一直在和一位同事辩论。我们专门使用C#,但这可以应用于具有可空类型的任何语言。假设您有一个代表最大值的值。但是,此最大值是可选的。我认为可为空的数字是可取的。我的同事援引先例,赞成使用零。当然,诸如网络套接字之类的东西通常使用零来表示无限超时。如果我今天要编写处理套接字的代码,我个人会使用可为空的值,因为我认为它可以更好地表示没有超时这一事实。
哪个代表更好?两者都需要条件检查其含义为“ none”的值,但是我相信可以为null的类型可以更好地传达意图。
最近我一直在和一位同事辩论。我们专门使用C#,但这可以应用于具有可空类型的任何语言。假设您有一个代表最大值的值。但是,此最大值是可选的。我认为可为空的数字是可取的。我的同事援引先例,赞成使用零。当然,诸如网络套接字之类的东西通常使用零来表示无限超时。如果我今天要编写处理套接字的代码,我个人会使用可为空的值,因为我认为它可以更好地表示没有超时这一事实。
哪个代表更好?两者都需要条件检查其含义为“ none”的值,但是我相信可以为null的类型可以更好地传达意图。
Answers:
考虑:
语言,
框架
上下文。
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
仅在谈论本机类型。
int
对类型的表达不足以约束问题,请考虑使用具有更多信息的新结构(例如,表示魔术值的结构的const实例,或在其上的枚举表示)。或考虑合同编程或其他解决方案,但我认为自定义结构最简单。
空无比魔数更好。
重要的是要命名具有魔术效果的值(如果必须具有这些值),并确保这些名称的定义在任何碰到魔术值和wtf的人都可以看到的地方。
if (timeout == 4298435) ... // bad.
if (timeout == null) ... // bad.
if (timeout == NEVER_TIME_OUT) ... // yay! puppies and unicorns!
在C#中,许多CLR类都有一个静态Empty
成员:
System.String.Empty
System.EventArgs.Empty
System.Guid.Empty
System.Drawing.Rectangle.Empty
System.Windows.Size.Empty
这使您不必记住是否要使用魔术值或使用null来构造空对象。
但是,如果您要处理的是像 int
呢?在这种情况下,请考虑您是否是原始痴迷的受害者。您看似简单的数字属性很可能会从其自己的类或结构中受益,这将使您可以指定Empty
成员并添加特定于该值的其他行为。
Null不是幻数的唯一替代方法。
public static int NO_TIMEOUT = 0; // javaish
空是邪恶的。在上面的示例中,您可能可以摆脱它,因为代码显然可以处理null。但是总的来说,当您开始传递空值时,迟早会收到一个空指针异常。第一次编写代码时可能不会发生,但是代码的维护时间远比第一次发行的时间长。它通常由对系统了解不多的人(最初的开发人员)来维护。
例如,Scala在Option类中有一个不错的选择。Option类具有两个值之一,“某些”-包装您真正想要的值,“无”-没有值。
对于任何开发人员来说,这显然都是没有价值的,您最好为此编写代码。好吧,无论如何应该使它变得显而易见。
并不是所有的幻数都是问题。取决于上下文0、1、1024等都是显而易见的。347?是的,您应该避免这种情况。:-)