当前位置: 首页 > 面试题库 >

多少个null检查就足够了?

孔甫
2023-03-14
问题内容

什么是当它的一些准则 不是 必要的检查空?

到目前为止,我一直在处理的许多继承代码都带有null检查广告。对琐碎的函数进行空检查,对声明非空返回的API调用进行空检查,等等。在某些情况下,空检查是合理的,但在许多地方,空值不是合理的期望。

我听到过许多参数,从“您不能信任其他代码”到“始终防御性地编程”再到“直到语言保证我没有非空值,我总是要检查”。在某种程度上,我当然同意其中的许多原则,但是我发现过多的null检查会导致其他通常违反这些原则的问题。顽强的null检查真的值得吗?

通常,我观察到带有过多空值检查的代码实际上质量较差,而不是质量较高。许多代码似乎都集中在null检查上,以至于开发人员已经忽略了其他重要特性,例如可读性,正确性或异常处理。特别是,我看到很多代码都忽略了std
:: bad_alloc异常,但是对进行了空检查new

在C
++中,由于解除引用空指针的行为具有不可预测的特性,因此我在某种程度上理解了这一点。在Java,C#,Python等中,可以更轻松地处理null取消引用。我刚刚看过一些不良示例(例如,警惕的null检查)还是真的有什么用?

尽管我主要对C ++,Java和C#感兴趣,但该问题旨在与语言无关。

我见过一些看起来 过分 的空检查示例,包括:

此示例似乎是对非标准编译器的解释,因为C 规范说失败的新代码会引发异常。除非您明确支持不兼容的编译器,否则这有意义吗?这是否 任何
像Java或C#(甚至C
/ CLR)托管的语感?

try {
   MyObject* obj = new MyObject(); 
   if(obj!=NULL) {
      //do something
   } else {
      //??? most code I see has log-it and move on
      //or it repeats what's in the exception handler
   }
} catch(std::bad_alloc) {
   //Do something? normally--this code is wrong as it allocates
   //more memory and will likely fail, such as writing to a log file.
}

另一个示例是在处理内部代码时。特别是,如果是一个可以定义自己的开发实践的小组,这似乎是不必要的。在某些项目或旧代码中,信任文档可能并不合理……但是对于您或您的团队控制的新代码,这真的有必要吗?

如果您可以查看并可以更新的方法(或者可以大喊大叫的开发人员)已签有合同,那么是否仍然需要检查null?

//X is non-negative.
//Returns an object or throws exception.
MyObject* create(int x) {
   if(x<0) throw;
   return new MyObject();
}

try {
   MyObject* x = create(unknownVar);
   if(x!=null) {
      //is this null check really necessary?
   }
} catch {
   //do something
}

开发私有或其他内部函数时,当合同仅要求非空值时,是否真的需要显式处理空值?为什么空检查比断言更可取?

(很明显,在您的公共API上,空检查至关重要,因为不正确地使用API​​会对用户大吼大叫是不礼貌的)

//Internal use only--non-public, not part of public API
//input must be non-null.
//returns non-negative value, or -1 if failed
int ParseType(String input) {
   if(input==null) return -1;
   //do something magic
   return value;
}

相比:

//Internal use only--non-public, not part of public API
//input must be non-null.
//returns non-negative value
int ParseType(String input) {
   assert(input!=null : "Input must be non-null.");
   //do something magic
   return value;
}

问题答案:

首先请注意,这是合同检查的一种特殊情况:您编写的代码除了在运行时验证是否满足书面合同外,别无其他。失败意味着某处的某些代码有错误。

对于实施更普遍有用的概念的特殊情况,我总是有些怀疑。合同检查很有用,因为它会在首次跨越API边界时捕获编程错误。空值有什么特别之处,这意味着它们是您要检查的合同中唯一的一部分?仍然,

关于输入验证的主题:

null在Java中是特殊的:编写了许多Java
API,使得null是唯一甚至可以传递给给定方法调用的无效值。在这种情况下,空检查会“完全验证”输入,因此适用支持合同检查的完整参数。

另一方面,在C ++中,NULL只是指针参数可以接受的几乎2 ^ 32(在新体系结构中为2 ^
64)无效值之一,因为几乎所有地址都不是正确类型的对象。除非您在该类型的所有对象的某处都有列表,否则您无法“完全验证”您的输入。

那么问题就变成了,NULL是足够普遍的无效输入,可以得到(foo *)(-1)没有得到的特殊待遇吗?

与Java不同,字段不会自动初始化为NULL,因此未初始化的垃圾值与NULL一样合理。但是有时候C
++对象的指针成员明确地用NULL初始化,这意味着“我还没有”。如果您的调用者这样做,则存在大量的编程错误,可以通过NULL检查来诊断。与他们没有源的库中的页面错误相比,对于他们而言,调试异常可能更容易。因此,如果您不介意代码膨胀,可能会有所帮助。但这不是您自己,而是您应该考虑的呼叫者-
这不是防御性的编码,因为它仅针对NULL而不针对(foo *)(-1)来“防御”。

如果NULL不是有效的输入,则可以考虑通过引用而不是指针来获取参数,但是许多编码方式均反对使用非const引用参数。并且,如果调用者通过了*
fooptr(其中fooptr为NULL),那么无论如何它都无济于事。您想要做的是将更多文档压缩到函数签名中,希望您的调用者更有可能认为“嗯,在这里fooptr是否为null?”。当他们不得不显式地取消引用它时,就好比将它们作为指针传递给您一样。它只是走了很远,但就它可能会有所帮助。

我不了解C#,但我了解这就像Java,保证引用具有有效值(至少在安全代码中),但是与Java不同,并非所有类型都具有NULL值。因此,我猜想null检查几乎没有什么价值:如果您使用的是安全代码,则不要使用可为空的类型,除非null是有效的输入;如果您使用的是不安全的代码,则适用与在C
++中。

关于输出验证的主题:

出现类似的问题:在Java中,您可以通过了解输出的类型来“完全验证”输出,并且该值不为null。在C ++中,您无法使用NULL检查“完全验证”输出-
因为您所知道的所有函数都返回了指向其自身已被取消缠绕的对象的指针。但是,如果由于被调用者代码的创建者通常使用的结构而导致NULL是常见的无效返回,则进行检查将有所帮助。

在所有情况下:

尽可能使用断言而不是“真实代码”来检查合同-应用程序正常工作后,您可能不希望每个被调用方检查其所有输入,而每个调用方都检查其返回值的代码膨胀。

在编写可移植到非标准C ++实现的代码的情况下,我可能会拥有一个类似于以下功能的函数:而不是问题中检查空值并捕获异常的代码:

template<typename T>
static inline void nullcheck(T *ptr) { 
    #if PLATFORM_TRAITS_NEW_RETURNS_NULL
        if (ptr == NULL) throw std::bad_alloc();
    #endif
}

然后,作为移植到新系统时要做的事情之一,您可以正确定义PLATFORM_TRAITS_NEW_RETURNS_NULL(以及其他一些PLATFORM_TRAITS)。显然,您可以编写一个标头,以了解所有已知的编译器。如果有人采用您的代码并在您一无所知的非标准C
++实现上对其进行编译,则出于根本原因,他们基本上是出于自己的意愿,所以他们必须自己做。



 类似资料:
  • 问题内容: 我正在尝试微调MySQL服务器,以便检查设置,分析慢速查询日志并在可能的情况下简化查询。 有时,如果我正确地建立索引就足够了,有时却不行。我读过某个地方(如果这是愚蠢的话,请纠正我的意思),比我需要的索引更多,可以达到相同的效果,就像我没有任何索引一样。 多少个索引足够?您可以说这取决于数百个因素,但是我很好奇如何清理自己的内容以减轻服务器负载。 此外,我看到了一些“有趣的”日志条目,

  • 我不想告诉每个布局都适合容器,而是想调整每个容器的大小,以完美地适应它的子画布。换句话说,我需要一种方法来询问Cytoscape画布的每个实例,“0缩放时您的宽度和高度是多少”,或者“在每个方向上需要多少像素才能完全绘制”,然后适当地调整每个容器的大小。 Cytoscape确实提供了获取画布的和的方法,但它们最终是实际的尺寸(可能不适合容器),而不是“想要的”/需要的尺寸。 我突然想到,我可以想出

  • 问题内容: 这是下面的代码: 如果值是我仍然得到未选中的框。我是不是应该从上面做错事,应该工作? 有任何想法吗? 问题答案: 使用is_null或运算符。

  • 问题内容: 这个问题一再被问到,但我仍然有疑问。当人们说同步创建了一个内存障碍时,这个内存障碍适用于什么缓存变量?这看起来不可行。 因此,由于这个疑问,我编写了一些看起来像这样的代码: 我想知道是否有可能只用简单的double []代替total的类型:这将要求synced(总计)(在run()方法中)确保我不使用索引中的每个索引的本地副本双精度数组,即内存围栏不仅适用于自身的值(在指针的背后),

  • 我是OptaPlanner的新手,我正在尝试创建一个尽可能简单的应用程序,将少数员工分配到某些班次。唯一的规则是每天可以分配一名员工到一个班次。我想知道以下求解器配置是否还不够: 因为ShiftPlanningSolution类中的ShiftAssignment集合仍然为空,即使求解器。solve()完成,getBestSolution()返回一些内容。更重要的是,似乎我的规则在规则。drl根本没

  • 问题内容: 您如何检查Java字符串中有多少个字母? 您如何检查字符串中某个位置的哪个字母(即字符串的第二个字母)? 问题答案: 一个) http://download.oracle.com/javase/7/docs/api/java/lang/String.html#length%28%29 编辑 如果您要计算中的特定类型字符的数量,则一种简单的方法是根据您的测试用例来检查每个索引。 其中可以