多少空检查就足够了?

发布于 2024-07-08 23:50:12 字数 2355 浏览 7 评论 0原文

不需要需要检查空值时有哪些指导原则?

我最近一直在处理的许多继承代码都有令人作呕的空检查。 对普通函数进行 Null 检查、对声明非 null 返回的 API 调用进行 Null 检查等。在某些情况下,Null 检查是合理的,但在许多地方,Null 并不是合理的期望。

我听到过很多争论,从“你不能信任其他代码”到“总是防御性地编程”到“在语言保证我有一个非空值之前,我总是会检查”。 在某种程度上,我当然同意其中许多原则,但我发现过多的空检查会导致通常违反这些原则的其他问题。 顽强的空检查真的值得吗?

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

在 C++ 中,我在某种程度上理解这一点,因为取消引用空指针的行为是不可预测的; 在 Java、C#、Python 等中,null 取消引用的处理更加得体。我刚刚看到过警惕的 null 检查的糟糕示例吗?还是真的有什么问题?

尽管我主要对 C++、Java 和 C# 感兴趣,但这个问题的目的是与语言无关。


我见过的一些似乎过度的空检查示例包括以下内容:


此示例似乎考虑了非标准编译器,因为 C++ 规范表示失败的 new 会引发异常。 除非您明确支持不兼容的编译器,否则这有意义吗? 这对于 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.
}

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

如果一个你可以看到并且可以更新(或者可以对负责的开发人员大喊大叫)的方法有一个合同,是否仍然需要检查空值?

//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;
}

What are some guidelines for when it is not necessary to check for a null?

A lot of the inherited code I've been working on as of late has null-checks ad nauseam. Null checks on trivial functions, null checks on API calls that state non-null returns, etc. In some cases, the null-checks are reasonable, but in many places a null is not a reasonable expectation.

I've heard a number of arguments ranging from "You can't trust other code" to "ALWAYS program defensively" to "Until the language guarantees me a non-null value, I'm always gonna check." I certainly agree with many of those principles up to a point, but I've found excessive null-checking causes other problems that usually violate those tenets. Is the tenacious null checking really worth it?

Frequently, I've observed codes with excess null checking to actually be of poorer quality, not of higher quality. Much of the code seems to be so focused on null-checks that the developer has lost sight of other important qualities, such as readability, correctness, or exception handling. In particular, I see a lot of code ignore the std::bad_alloc exception, but do a null-check on a new.

In C++, I understand this to some extent due to the unpredictable behavior of dereferencing a null pointer; null dereference is handled more gracefully in Java, C#, Python, etc. Have I just seen poor-examples of vigilant null-checking or is there really something to this?

This question is intended to be language agnostic, though I am mainly interested in C++, Java, and C#.


Some examples of null-checking that I've seen that seem to be excessive include the following:


This example seems to be accounting for non-standard compilers as C++ spec says a failed new throws an exception. Unless you are explicitly supporting non-compliant compilers, does this make sense? Does this make any sense in a managed language like Java or C# (or even 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.
}

Another example is when working on internal code. Particularly, if it's a small team who can define their own development practices, this seems unnecessary. On some projects or legacy code, trusting documentation may not be reasonable... but for new code that you or your team controls, is this really necessary?

If a method, which you can see and can update (or can yell at the developer who is responsible) has a contract, is it still necessary to check for nulls?

//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
}

When developing a private or otherwise internal function, is it really necessary to explicitly handle a null when the contract calls for non-null values only? Why would a null-check be preferable to an assert?

(obviously, on your public API, null-checks are vital as it's considered impolite to yell at your users for incorrectly using the 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;
}

Compared to:

//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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

你列表最软的妹 2024-07-15 23:50:12

需要记住的一件事是,您今天编写的代码虽然可能是一个小团队并且您可以拥有良好的文档,但将会变成其他人必须维护的遗留代码。 我使用以下规则:

  1. 如果我正在编写一个将向其他人公开的公共 API,那么我将对所有引用参数进行 null 检查。

  2. 如果我正在向我的应用程序编写内部组件,当我需要在空值存在时执行一些特殊操作时,或者当我想使其非常清楚时,我会编写空值检查。 否则我不介意得到空引用异常,因为这也相当清楚发生了什么。

  3. 当处理来自其他人框架的返回数据时,我仅在返回 null 可能且有效时才检查 null。 如果他们的合同规定不返回 null,我就不会进行检查。

One thing to remember that your code that you write today while it may be a small team and you can have good documentation, will turn into legacy code that someone else will have to maintain. I use the following rules:

  1. If I'm writing a public API that will be exposed to others, then I will do null checks on all reference parameters.

  2. If I'm writing an internal component to my application, I write null checks when I need to do something special when a null exists, or when I want to make it very clear. Otherwise I don't mind getting the null reference exception since that is also fairly clear what is going on.

  3. When working with return data from other peoples frameworks, I only check for null when it is possible and valid to have a null returned. If their contract says it doesn't return nulls, I won't do the check.

苄①跕圉湢 2024-07-15 23:50:12

首先请注意,这是合同检查的特殊情况:您编写的代码除了在运行时验证记录的合同是否得到满足之外什么也不做。 失败意味着某处的某些代码有错误。

我总是对实现更普遍有用的概念的特殊情况有点怀疑。 契约检查很有用,因为它可以在编程错误第一次跨越 API 边界时捕获它们。 null 值有何特别之处,意味着它们是您需要检查的合约中唯一的部分? 尽管如此,

关于输入验证的主题:

null 在 Java 中很特殊:许多 Java API 都是这样编写的,即 null 是唯一可以传递到给定方法调用中的无效值。 在这种情况下,空检查“完全验证”输入,因此支持合同检查的完整论点适用。

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

那么问题就变成了,NULL 是否是一个足够常见的无效输入,可以得到 (foo *)(-1) 没有得到的特殊处理?

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

如果 NULL 不是有效输入,您可以考虑通过引用而不是指针获取参数,但许多编码风格不赞成非常量引用参数。 如果调用者传递给你 *fooptr,其中 fooptr 为 NULL,那么无论如何它对任何人都没有任何好处。 您想要做的是将更多文档压缩到函数签名中,希望您的调用者更有可能认为“嗯,这里的 fooptr 可能为空吗?” 当他们必须显式取消引用它时,而不是将其作为指针传递给您。 它只能走到这一步,但就目前而言它可能会有所帮助。

我不懂 C#,但我知道它就像 Java 一样,保证引用具有有效值(至少在安全代码中),但与 Java 不同的是,并非所有类型都有 NULL 值。 所以我猜想 null 检查很少值得:如果您使用安全代码,则不要使用可空类型,除非 null 是有效输入,如果您使用不安全代码,则适用相同的推理在C++中。

关于输出验证的主题:

出现了类似的问题:在 Java 中,您可以通过了解输出的类型以及该值不为空来“完全验证”输出。 在 C++ 中,您无法通过 NULL 检查“完全验证”输出 - 据您所知,该函数返回了一个指向其自己的堆栈上刚刚被展开的对象的指针。 但是,如果由于被调用者代码的作者通常使用的构造而导致 NULL 是常见的无效返回,那么检查它会有所帮助。

在所有情况下:

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

在编写可移植到非标准 C++ 实现的代码的情况下,我可能会有这样的函数,而不是问题中检查 null 并捕获异常的代码:

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++ 实现上编译它,那么他们基本上是靠自己,因为比这更重要的原因,所以他们必须自己做。

First note that this a special case of contract-checking: you're writing code that does nothing other than validate at runtime that a documented contract is met. Failure means that some code somewhere is faulty.

I'm always slightly dubious about implementing special cases of a more generally useful concept. Contract checking is useful because it catches programming errors the first time they cross an API boundary. What's so special about nulls that means they're the only part of the contract you care to check? Still,

On the subject of input validation:

null is special in Java: a lot of Java APIs are written such that null is the only invalid value that it's even possible to pass into a given method call. In such cases a null check "fully validates" the input, so the full argument in favour of contract checking applies.

In C++, on the other hand, NULL is only one of nearly 2^32 (2^64 on newer architectures) invalid values that a pointer parameter could take, since almost all addresses are not of objects of the correct type. You can't "fully validate" your input unless you have a list somewhere of all objects of that type.

The question then becomes, is NULL a sufficiently common invalid input to get special treatment that (foo *)(-1) doesn't get?

Unlike Java, fields don't get auto-initialized to NULL, so a garbage uninitialized value is just as plausible as NULL. But sometimes C++ objects have pointer members which are explicitly NULL-inited, meaning "I don't have one yet". If your caller does this, then there is a significant class of programming errors which can be diagnosed by a NULL check. An exception may be easier for them to debug than a page fault in a library they don't have the source for. So if you don't mind the code bloat, it might be helpful. But it's your caller you should be thinking of, not yourself - this isn't defensive coding, because it only 'defends' against NULL, not against (foo *)(-1).

If NULL isn't a valid input, you could consider taking the parameter by reference rather than pointer, but a lot of coding styles disapprove of non-const reference parameters. And if the caller passes you *fooptr, where fooptr is NULL, then it has done nobody any good anyway. What you're trying to do is squeeze a bit more documentation into the function signature, in the hope that your caller is more likely to think "hmm, might fooptr be null here?" when they have to explicitly dereference it, than if they just pass it to you as a pointer. It only goes so far, but as far as it goes it might help.

I don't know C#, but I understand that it's like Java in that references are guaranteed to have valid values (in safe code, at least), but unlike Java in that not all types have a NULL value. So I'd guess that null checks there are rarely worth it: if you're in safe code then don't use a nullable type unless null is a valid input, and if you're in unsafe code then the same reasoning applies as in C++.

On the subject of output validation:

A similar issue arises: in Java you can "fully validate" the output by knowing its type, and that the value isn't null. In C++, you can't "fully validate" the output with a NULL check - for all you know the function returned a pointer to an object on its own stack which has just been unwound. But if NULL is a common invalid return due to the constructs typically used by the author of the callee code, then checking it will help.

In all cases:

Use assertions rather than "real code" to check contracts where possible - once your app is working, you probably don't want the code bloat of every callee checking all its inputs, and every caller checking its return values.

In the case of writing code which is portable to non-standard C++ implementations, then instead of the code in the question which checks for null and also catches the exception, I'd probably have a function like this:

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

Then as one of the list of things you do when porting to a new system, you define PLATFORM_TRAITS_NEW_RETURNS_NULL (and maybe some other PLATFORM_TRAITS) correctly. Obviously you can write a header which does this for all the compilers you know about. If someone takes your code and compiles it on a non-standard C++ implementation that you know nothing about, they're fundamentally on their own for bigger reasons than this, so they'll have to do it themselves.

当梦初醒 2024-07-15 23:50:12

如果您编写代码及其合同,您有责任根据其合同使用它并确保合同正确。 如果你说“返回非空”x,那么调用者不应该检查空值。 如果该引用/指针发生空指针异常,则说明您的合同不正确。

仅当使用不受信任或没有适当合同的库时,空检查才应该发挥到极致。 如果是你的开发团队的代码,强调契约不能被破坏,并在出现bug时追查错误使用契约的人。

If you write the code and its contract, you are responsible for using it in terms of its contract and ensuring the contract is correct. If you say "returns a non-null" x, then the caller should not check for null. If a null pointer exception then occurs with that reference/pointer, it is your contract that is incorrect.

Null checking should only go to the extreme when using a library that is untrusted, or does not have a proper contract. If it is your development team's code, stress that the contracts must not be broken, and track down the person who uses the contract incorrectly when bugs occur.

眼趣 2024-07-15 23:50:12

这部分取决于代码的使用方式——例如,它是仅在项目内可用的方法还是公共 API。 API 错误检查需要比断言更强大的东西。

因此,虽然这在支持单元测试和类似内容的项目中很好:

internal void DoThis(Something thing)
{
    Debug.Assert(thing != null, "Arg [thing] cannot be null.");
    //...
}

在您无法控制谁调用它的方法中,这样的方法可能会更好:

public void DoThis(Something thing)
{
    if (thing == null)
    {
        throw new ArgumentException("Arg [thing] cannot be null.");
    }
    //...
}

Part of this depends on how the code is used -- if it is a method available only within a project vs. a public API, for example. API error checking requires something stronger than an assertion.

So while this is fine within a project where it's supported with unit tests and stuff like that:

internal void DoThis(Something thing)
{
    Debug.Assert(thing != null, "Arg [thing] cannot be null.");
    //...
}

in a method where you don't have control over who calls it, something like this may be better:

public void DoThis(Something thing)
{
    if (thing == null)
    {
        throw new ArgumentException("Arg [thing] cannot be null.");
    }
    //...
}
来世叙缘 2024-07-15 23:50:12

这取决于实际情况。 我的答案的其余部分假设 C++。

  • 我从来没有测试过 new 的返回值
    因为我使用的所有实现
    失败时抛出 bad_alloc。 如果我
    查看新回归的遗留测试
    在我正在处理的任何代码中,我
    把它剪掉,不用费心
    用任何东西替换它。
  • 除非心胸狭隘的编码标准
    禁止它,我断言有记录
    前提条件。 损坏的代码
    违反已公布的合同需求
    立即失败并且
    戏剧性地。
  • 如果 null 是由运行时产生的
    非因损坏而导致的故障
    代码,我扔。 fopen 失败和
    malloc 失败(虽然我很少 if
    曾经在 C++ 中使用过它们)会下降
    归入这一类。
  • 我不尝试从中恢复
    分配失败。 bad_alloc 获取
    陷入 main() 中。
  • 如果空测试
    是针对一个对象
    我班的合作者,我重写
    通过引用获取它的代码。
  • 如果合作者真的可能不
    存在,我使用 空对象
    设计模式来创建
    占位符在明确定义中失败
    方法。

It depends on the situation. The rest of my answer assumes C++.

  • I never test the return value of new
    since all the implementations I use
    throw bad_alloc on failure. If I
    see a legacy test for new returning
    null in any code I'm working on, I
    cut it out and don't bother to
    replace it with anything.
  • Unless small minded coding standards
    prohibit it, I assert documented
    preconditions. Broken code which
    violates a published contract needs
    to fail immediately and
    dramatically.
  • If the null arises from a runtime
    failure which isn't due to broken
    code, I throw. fopen failure and
    malloc failure (though I rarely if
    ever use them in C++) would fall
    into this category.
  • I don't attempt to recover from
    allocation failure. Bad_alloc gets
    caught in main().
  • If the null test
    is for an object which is
    collaborator of my class, I rewrite
    the code to take it by reference.
  • If the collaborator really might not
    exist, I use the Null Object
    design pattern to create a
    placeholder to fail in well defined
    ways.
剧终人散尽 2024-07-15 23:50:12

一般来说,NULL 检查是邪恶的,因为它给代码的可测试性添加了一个小的负面标记。 通过到处进行 NULL 检查,您不能使用“pass null”技术,并且在单元测试时它会打击您。 对方法进行单元测试比空检查更好。

查看 Misko Hevery 在 http: //www.youtube.com/watch?v=wEhu57pih5w&feature=channel

NULL checking in general is evil as it's add a small negative token to the code testability. With NULL checks everywhere you can't use "pass null" technique and it will hit you when unit testing. It's better to have unit test for the method than null check.

Check out decent presentation on that issue and unit testing in general by Misko Hevery at http://www.youtube.com/watch?v=wEhu57pih5w&feature=channel

疯了 2024-07-15 23:50:12

旧版本的 Microsoft C++(可能还有其他版本)不会因通过 new 分配失败而引发异常,而是返回 NULL。 必须在符合标准的版本和旧版本中运行的代码将进行您在第一个示例中指出的冗余检查。

让所有失败的分配遵循相同的代码路径会更清晰:

if(obj==NULL)
    throw std::bad_alloc();

Older versions of Microsoft C++ (and probably others) did not throw an exception for failed allocations via new, but returned NULL. Code that had to run in both standard-conforming and older versions would have the redundant checking that you point out in your first example.

It would be cleaner to make all failed allocations follow the same code path:

if(obj==NULL)
    throw std::bad_alloc();
独留℉清风醉 2024-07-15 23:50:12

众所周知,有注重程序的人(专注于以正确的方式做事)和注重结果的人(得到正确的答案)。 我们大多数人都处于中间的某个位置。 看来您发现了面向过程的异常值。 这些人会说“除非你完全理解事情,否则一切皆有可能;所以要为一切做好准备。” 对于他们来说,你所看到的就是正确完成的。 对于他们来说,如果你改变它,他们会担心,因为鸭子没有全部排成一行。

在处理别人的代码时,我尝试确保我知道两件事。
1. 程序员的意图是什么
2. 为什么他们以这种方式编写代码

对于跟踪 A 类程序员,也许这会有所帮助。

因此,“多少才足够”最终成为一个社会问题和一个技术问题——没有商定的衡量方法。

(这也让我发疯。)

It's widely known that there are procedure-oriented people (focus on doing things the right way) and results-oriented people (get the right answer). Most of us lie somewhere in the middle. Looks like you've found an outlier for procedure-oriented. These people would say "anything's possible unless you understand things perfectly; so prepare for anything." For them, what you see is done properly. For them if you change it, they'll worry because the ducks aren't all lined up.

When working on someone else's code, I try to make sure I know two things.
1. What the programmer intended
2. Why they wrote the code the way they did

For following up on Type A programmers, maybe this helps.

So "How much is enough" ends up being a social question as much as a technical question - there's no agreed-upon way to measure it.

(It drives me nuts too.)

花开柳相依 2024-07-15 23:50:12

我个人认为在大多数情况下空测试是不必要的。 如果 new 失败或 malloc 失败,您会遇到更大的问题,并且在您不编写内存检查器的情况下恢复的机会几乎为零! 此外,空测试在开发阶段隐藏了很多错误,因为“空”子句通常只是空的并且不执行任何操作。

Personally I think null testing is unnnecessary in the great majority of cases. If new fails or malloc fails you have bigger issues and the chance of recovering is just about nil in cases where you're not writing a memory checker! Also null testing hides bugs a lot in the development phases since the "null" clauses are frequently just empty and do nothing.

短叹 2024-07-15 23:50:12

当您可以指定使用哪个编译器时,对于诸如“new”之类的系统函数,检查 null 是代码中的一个错误。 这意味着您将重复错误处理代码。 重复的代码通常是错误的来源,因为通常一个代码被更改而另一个代码没有更改。 如果你不能指定编译器或编译器版本,你应该更加防御。

对于内部函数,您应该指定契约并确保契约通过单元测试得到执行。 不久前,我们的代码遇到了一个问题,如果数据库中缺少对象,我们要么抛出异常,要么返回 null。 这只会让 api 的调用者感到困惑,因此我们仔细检查并使其在整个代码库中保持一致,并删除了重复的检查。

重要的是(恕我直言)不要有重复的错误逻辑,其中一个分支永远不会被调用。 如果您永远无法调用代码,那么您就无法测试它,并且您永远不会知道它是否已损坏。

When you can specify which compiler is being used, for system functions such as "new" checking for null is a bug in the code. It means that you will be duplicating the error handling code. Duplicate code is often a source of bugs because often one gets changed and the other doesn't. If you can not specify the compiler or compiler versions, you should be more defensive.

As for internal functions, you should specify the contract and make sure that contract is enforce via unit tests. We had a problem in our code a while back where we either threw an exception or returned null in case of a missing object from our database. This just made things confusing for the caller of the api so we went through and made it consistant throughout the entire code base and removed the duplicate checks.

The important thing (IMHO) is to not have duplicate error logic where one branch will never be invoked. If you can never invoke code, then you can't test it, and you will never know if it is broken or not.

太阳男子 2024-07-15 23:50:12

我想说这有点取决于你的语言,但我将 Resharper 与 C# 一起使用,它基本上会告诉我“这个引用可能为空”,在这种情况下,我添加一个检查,如果它告诉我“这个对于“if (null != oMyThing && ....)”,它永远是 true”,然后我听它而不测试 null。

I'd say it depends a little on your language, but I use Resharper with C# and it basically goes out of it's way to tell me "this reference could be null" in which case I add a check, if it tells me "this will always be true" for "if (null != oMyThing && ....)" then I listen to it an don't test for null.

箜明 2024-07-15 23:50:12

是否检查 null 很大程度上取决于具体情况。

例如,在我们的商店中,我们检查我们创建的方法的参数是否为空。 原因很简单,作为最初的程序员,我很清楚该方法应该做什么。 即使文档和要求不完整或不太令人满意,我也了解上下文。 后来负责维护的程序员可能不理解上下文,并且可能错误地认为传递 null 是无害的。 如果我知道 null 是有害的,并且我可以预见到有人可能会传递 null,那么我应该采取简单的步骤来确保该方法以优雅的方式做出反应。

public MyObject MyMethod(object foo)
{
  if (foo == null)
  {
    throw new ArgumentNullException("foo");
  }

  // do whatever if foo was non-null
}

Whether to check for null or not greatly depends on the circumstances.

For example in our shop we check parameters to methods we create for null inside the method. The simple reason is that as the original programmer I have a good idea of exactly what the method should do. I understand the context even if the documentation and requirements are incomplete or less than satisfactory. A later programmer tasked with maintenance may not understand the context and may assume, wrongly, that passing null is harmless. If I know null would be harmful and I can anticipate that someone may pass null, I should take the simple step of making sure that the method reacts in a graceful way.

public MyObject MyMethod(object foo)
{
  if (foo == null)
  {
    throw new ArgumentNullException("foo");
  }

  // do whatever if foo was non-null
}
烟雨凡馨 2024-07-15 23:50:12

当我看到 NULL 时知道该怎么做时,我只会检查 NULL。 “知道要做什么”这里的意思是“知道如何避免崩溃”或“知道除了崩溃的位置之外还应该告诉用户什么”。 例如,如果 malloc() 返回 NULL,我通常别无选择,只能中止程序。 另一方面,如果 fopen() 返回 NULL,我可以让用户知道无法打开的文件名,并且可能是 errno。 如果 find() 返回 end(),我通常知道如何继续而不崩溃。

I only check for NULL when I know what to do when I see NULL. "Know what to do" here means "know how to avoid a crash" or "know what to tell the user besides the location of the crash". For example, if malloc() returns NULL, I usually have no option but to abort the program. On the other hand, if fopen() returns NULL, I can let the user know the file name that could not be open and may be errno. And if find() returns end(), I usually know how to continue without crashing.

佼人 2024-07-15 23:50:12

较低级别的代码应该检查较高级别代码的使用情况。 通常这意味着检查参数,但也可能意味着检查上行调用的返回值。 不需要检查上行调用参数。

目的是以直接且明显的方式捕获错误,并用不会说谎的代码记录合约。

Lower level code should check use from higher level code. Usually this means checking arguments, but it can mean checking return values from upcalls. Upcall arguments need not be checked.

The aim is to catch bugs in immediate and obvious ways, as well as documenting the contract in code that does not lie.

雨夜星沙 2024-07-15 23:50:12

我不认为这是代码。 相当多的 Windows/Linux API 调用在某种失败时返回 NULL。 因此,我当然会按照 API 指定的方式检查故障。 通常我最终会将控制流传递给某种方式的错误模块,而不是复制错误处理代码。

I don't think it's bad code. A fair amount of Windows/Linux API calls return NULL on failure of some sort. So, of course, I check for failure in the manner the API specifies. Usually I wind up passing control flow to an error module of some fashion instead of duplicating error-handling code.

笑叹一世浮沉 2024-07-15 23:50:12

如果我收到一个语言不能保证不为空的指针,并且将以 null 会破坏我的方式取消引用它,或者将我的函数传递到我说过不会产生 NULL 的地方,我检查是否为 NULL。

这不仅仅是关于 NULL,如果可能的话,函数应该检查前置条件和后置条件。

如果给我指针的函数的契约说它永远不会产生空值,那根本不重要。 我们都会犯错误。 有一个很好的规则,程序应该尽早且频繁地失败,因此我不会将错误传递给另一个模块并让它失败,而是就地失败。 使测试时的调试变得更加容易。 此外,在关键系统中,可以更轻松地保持系统正常运行。

另外,如果异常从 main 中逃逸,堆栈可能不会被卷起,从而阻止析构函数运行(请参阅终止()的 C++ 标准)。 这可能很严重。 因此,不检查 bad_alloc 可能比看起来更危险。

断言失败与运行时错误失败是完全不同的主题。

如果标准 new() 行为未更改为返回 NULL 而不是抛出,则在 new() 之后检查 NULL 似乎已经过时了。

还有一个问题,即使malloc返回了一个有效的指针,也不意味着你已经分配了内存并且可以使用它。 不过那是另一回事了。

If I receive a pointer that is not guaranteed by language to be not null, and am going to de-reference it in a way that null will break me, or pass out put my function where I said I wouldn't produce NULLs, I check for NULL.

It is not just about NULLs, a function should check pre- and post-conditions if possible.

It doesn't matter at all if a contract of the function that gave me the pointer says it'll never produce nulls. We all make bugs. There's a good rule that a program shall fail early and often, so instead of passing the bug to another module and have it fail, I'll fail in place. Makes things so much easier to debug when testing. Also in critical systems makes it easier to keep the system sane.

Also, if an exception escapes main, stack may not be rolled up, preventing destructors from running at all (see C++ standard on terminate()). Which may be serious. So leaving bad_alloc unchecked can be more dangerous than it seems.

Fail with assert vs. fail with a run time error is quite a different topic.

Checking for NULL after new() if standard new() behavior has not been altered to return NULL instead of throwing seems obsolete.

There's another problem, which is that even if malloc returned a valid pointer, it doesn't yet mean you have allocated memory and can use it. But that is another story.

日裸衫吸 2024-07-15 23:50:12

我的第一个问题是,它会导致代码中充满空检查等。 它损害了可读性,我什至可以说它损害了可维护性,因为如果您正在编写一段代码,其中某个引用确实不应该为空,那么很容易忘记空检查。 而且您只知道某些地方会缺少空检查。 这实际上使调试变得比实际需要的更加困难。 如果原始异常没有被捕获并替换为错误的返回值,那么我们将获得一个有价值的异常对象,其中包含信息丰富的堆栈跟踪。 缺少空检查会给您带来什么? 一段代码中的 NullReferenceException 让你大吃一惊:wtf? 这个引用永远不应该为空!

因此,您需要开始弄清楚代码是如何调用的,以及为什么引用可能为空。 这可能会花费大量时间,并且确实会降低调试工作的效率。 最终你会找出真正的问题,但很可能它隐藏得很深,而且你花费了比你应该花的更多的时间来寻找它。

到处都是 null 检查的另一个问题是,一些开发人员在收到 NullReferenceException 时并没有真正花时间正确思考真正的问题。 实际上,我见过不少开发人员只是在发生 NullReferenceException 的代码上方添加空检查。 太好了,异常不再发生了! 欢呼! 我们现在可以回家了! 嗯……“不,你不能,你应该用肘部打脸”怎么样? 真正的错误可能不再导致异常,但现在您可能有缺失或错误的行为......并且没有异常! 这更加痛苦并且需要更多的时间来调试。

My first problem with this, is that it leads to code which is littered with null checks and the likes. It hurts readability, and i’d even go as far as to say that it hurts maintainability because it really is easy to forget a null check if you’re writing a piece of code where a certain reference really should never be null. And you just know that the null checks will be missing in some places. Which actually makes debugging harder than it needs to be. Had the original exception not been caught and replaced with a faulty return value, then we would’ve gotten a valuable exception object with an informative stacktrace. What does a missing null check give you? A NullReferenceException in a piece of code that makes you go: wtf? this reference should never be null!

So then you need to start figuring out how the code was called, and why the reference could possibly be null. This can take a lot of time and really hurts the efficiency of your debugging efforts. Eventually you’ll figure out the real problem, but odds are that it was hidden pretty deeply and you spent a lot more time searching for it than you should have.

Another problem with null checks all over the place is that some developers don’t really take the time to properly think about the real problem when they get a NullReferenceException. I’ve actually seen quite a few developers just add a null check above the code where the NullReferenceException occurred. Great, the exception no longer occurs! Hurray! We can go home now! Umm… how bout ‘no you can’t and you deserve an elbow to the face’? The real bug might not cause an exception anymore, but now you probably have missing or faulty behavior… and no exception! Which is even more painful and takes even more time to debug.

嘿嘿嘿 2024-07-15 23:50:12

起初,这似乎是一个奇怪的问题:null 检查很棒,而且是一个有价值的工具。 检查 new 返回 null 绝对是愚蠢的。 我只是要忽略这样一个事实:有些语言允许这样做。 我确信有充分的理由,但我真的不认为我可以应付现实中的生活:) 开个玩笑,看来你至少应该指定 new 应该当内存不足时返回null

无论如何,在适当的情况下检查 null 可以使代码更简洁。 我什至可以说,从不分配函数参数默认值是下一个合乎逻辑的步骤。 更进一步,在适当的情况下返回空数组等会导致更干净的代码。 很高兴不必担心获取 null ,除非它们在逻辑上有意义。 最好避免使用 Null 作为错误值。

使用断言确实是个好主意。 特别是如果它让您可以选择在运行时关闭它们。 另外,这是一种更明确的契约风格:)

At first, this seemed like a strange question: null checks are great and a valuable tool. Checking that new returns null is definitely silly. I'm just going to ignore the fact that there are languages that allow that. I'm sure there are valid reasons, but I really don't think I can handle living in that reality :) All kidding aside, it seems like you should at least have to specify that the new should return null when there isn't enough memory.

Anyway, checking for null where appropriate leads to cleaner code. I'd go so far as to say that never assigning function parameters default values is the next logical step. To go even further, returning empty arrays, etc. where appropriate leads to even cleaner code. It is nice to not have to worry about getting nulls except where they are logically meaningful. Nulls as error values are better avoided.

Using asserts is a really great idea. Especially if it gives you the option of turning them off at runtime. Plus, it is a more explicitly contractual style :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文