简体   繁体   English

我应该在私有/内部方法中抛出null参数吗?

[英]Should I throw on null parameters in private/internal methods?

I'm writing a library that has several public classes and methods, as well as several private or internal classes and methods that the library itself uses. 我正在编写一个包含多个公共类和方法的库,以及库本身使用的几个私有或内部类和方法。

In the public methods I have a null check and a throw like this: 在公共方法中,我有一个空检查和抛出这样的:

public int DoSomething(int number)
{
    if (number == null)
    {
        throw new ArgumentNullException(nameof(number));
    }
}

But then this got me thinking, to what level should I be adding parameter null checks to methods? 但是这让我思考,我应该在什么级别添加参数null检查方法? Do I also start adding them to private methods? 我是否也开始将它们添加到私有方法? Should I only do it for public methods? 我应该只为公共方法做吗?

Ultimately, there isn't a uniform consensus on this. 最终,对此没有统一的共识。 So instead of giving a yes or no answer, I'll try to list the considerations for making this decision: 因此,我将尝试列出做出此决定的注意事项,而不是给出是或否答案:

  • Null checks bloat your code. 空检查会使代码膨胀。 If your procedures are concise, the null guards at the beginning of them may form a significant part of the overall size of the procedure, without expressing the purpose or behaviour of that procedure. 如果您的程序简明扼要,那么它们开头的空值守卫可能构成程序整体规模的重要部分,而不表达该程序的目的或行为。

  • Null checks expressively state a precondition. 空检查表达了一个先决条件。 If a method is going to fail when one of the values is null, having a null check at the top is a good way to demonstrate this to a casual reader without them having to hunt for where it's dereferenced. 如果某个方法在其中一个值为null时失败,那么在顶部进行空检查是一种很好的方式,可以向一个随意的读者证明这一点,而不必去寻找它被解除引用的位置。 To improve this, people often use helper methods with names like Guard.AgainstNull , instead of having to write the check each time. 为了改善这一点,人们经常使用名为Guard.AgainstNull帮助方法,而不是每次都要写支票。

  • Checks in private methods are untestable. 检查私有方法是不可测试的。 By introducing a branch in your code which you have no way of fully traversing, you make it impossible to fully test that method. 通过在代码中引入一个无法完全遍历的分支,您无法完全测试该方法。 This conflicts with the point of view that tests document the behaviour of a class, and that that class's code exists to provide that behaviour. 这与测试记录类的行为以及该类的代码存在以提供该行为的观点相冲突。

  • The severity of letting a null through depends on the situation. 让null通过的严重程度取决于具体情况。 Often, if a null does get into the method, it'll be dereferenced a few lines later and you'll get a NullReferenceException . 通常情况下,如果空进入的方法,这将是一个几行后提领,你会得到一个NullReferenceException This really isn't much less clear than throwing an ArgumentNullException . 这实际上并不比抛出ArgumentNullException更清楚。 On the other hand, if that reference is passed around quite a bit before being dereferenced, or if throwing an NRE will leave things in a messy state, then throwing early is much more important. 另一方面,如果该引用在被取消引用之前传递了很多,或者如果抛出NRE会使事情处于混乱状态,那么提前投掷就更为重要了。

  • Some libraries, like .NET's Code Contracts, allow a degree of static analysis, which can add an extra benefit to your checks. 某些库(如.NET的代码约定)允许一定程度的静态分析,这可以为您的检查增加额外的好处。

  • If you're working on a project with others, there may be existing team or project standards covering this. 如果您正在与他人合作开展项目,可能会有现有的团队或项目标准。

If you're not a library developer, don't be defensive in your code 如果您不是图书馆开发人员,请不要在代码中采取防御措施

Write unit tests instead 改为编写单元测试

In fact, even if you're developing a library, throwing is most of the time: BAD 事实上,即使你正在开发一个图书馆,投掷也是大部分时间:不好

1. Testing null on int must never be done in c# : 1.在int上测试null绝对不能在c#中完成

It raises a warning CS4072 , because it's always false. 它引发了警告CS4072 ,因为它总是错误的。

2. Throwing an Exception means it's exceptional: abnormal and rare. 2.抛出异常意味着它是例外:异常和罕见。

It should never raise in production code. 它永远不应该提高生产代码。 Especially because exception stack trace traversal can be a cpu intensive task. 特别是因为异常堆栈跟踪遍历可以是cpu密集型任务。 And you'll never be sure where the exception will be caught, if it's caught and logged or just simply silently ignored (after killing one of your background thread) because you don't control the user code. 而且你永远不会确定异常将被捕获的位置,是否被捕获和记录,或者只是默默地忽略(在杀死你的一个后台线程之后)因为你不控制用户代码。 There is no "checked exception" in c# (like in java) which means you never know - if it's not well documented - what exceptions a given method could raise. c#中没有“已检查的异常” (就像在java中一样),这意味着你永远不知道 - 如果没有详细记录 - 给定方法可以引发什么异常。 By the way, that kind of documentation must be kept in sync with the code which is not always easy to do (increase maintenance costs). 顺便说一句,这种文档必须与代码保持同步,这并不总是容易做到(增加维护成本)。

3. Exceptions increases maintenance costs. 3.例外会增加维护成本。

As exceptions are thrown at runtime and under certain conditions, they could be detected really late in the development process. 由于在运行时和某些条件下抛出异常,因此可以在开发过程的后期检测到异常。 As you may already know, the later an error is detected in the development process, the more expensive the fix will be. 您可能已经知道,在开发过程中检测到的错误越晚,修复的成本就越高。 I've even seen exception raising code made its way to production code and not raise for a week, only for raising every day hereafter (killing the production. oops!). 我甚至看到异常提升代码进入生产代码而不是提前一周,只是为了以后每天提高(杀死生产。哎呀!)。

4. Throwing on invalid input means you don't control input . 4.抛出无效输入意味着您无法控制输入

It's the case for public methods of libraries. 公共图书馆方法就是这种情况。 However if you can check it at compile time with another type (for example a non nullable type like int) then it's the way to go. 但是,如果您可以在编译时使用其他类型(例如像int这样的非可空类型)检查它,那么它就是要走的路。 And of course, as they are public, it's their responsibility to check for input. 当然,由于它们是公开的,因此检查输入是他们的责任。

Imagine the user who uses what he thinks as valid data and then by a side effect, a method deep in the stack trace trows a ArgumentNullException . 想象一下,使用他认为有效数据的用户然后通过副作用,堆栈跟踪深处的方法会产生ArgumentNullException

  • What will be his reaction? 他的反应是什么?
  • How can he cope with that? 他怎么能应付这个?
  • Will it be easy for you to provide an explanation message ? 您是否容易提供解释信息?

5. Private and internal methods should never ever throw exceptions related to their input. 5.私有和内部方法永远不应该抛出与其输入相关的异常。

You may throw exceptions in your code because an external component (maybe Database, a file or else) is misbehaving and you can't guarantee that your library will continue to run correctly in its current state. 您可能会在代码中抛出异常,因为外部组件(可能是数据库,文件或其他)行为不正常,您无法保证您的库将继续在其当前状态下正常运行。

Making a method public doesn't mean that it should (only that it can) be called from outside of your library ( Look at Public versus Published from Martin Fowler ). 将方法公之于众并不意味着它应该(只有它可以)从你的库外部调用( 看看公共与发布的Martin Fowler )。 Use IOC, interfaces, factories and publish only what's needed by the user, while making the whole library classes available for unit testing. 使用IOC,接口,工厂并仅发布用户需要的内容,同时使整个库类可用于单元测试。 (Or you can use the InternalsVisibleTo mechanism). (或者您可以使用InternalsVisibleTo机制)。

6. Throwing exceptions without any explanation message is making fun of the user 6.在没有任何解释信息的情况下抛出异常就是取笑用户

No need to remind what feelings one can have when a tool is broken, without having any clue on how to fix it. 无需提醒工具被破坏时可以有什么样的感受,而不需要知道如何修复它。 Yes, I know. 是的我知道。 You comes to SO and ask a question... 你来到SO并问一个问题......

7. Invalid input means it breaks your code 7.输入无效意味着它会破坏您的代码

If your code can produce a valid output with the value then it's not invalid and your code should manage it. 如果您的代码可以生成带有该值的有效输出,则它不是无效的,您的代码应该对其进行管理。 Add a unit test to test this value. 添加单元测试以测试此值。

8. Think in user terms: 8.用用户术语思考:

Do you like when a library you use throws exceptions for smashing your face ? 你喜欢当你使用的图书馆抛出异常粉碎你的脸时,你喜欢吗? Like: "Hey, it's invalid, you should have known that!" 喜欢:“嘿,这是无效的,你应该知道的!”

Even if from your point of view - with your knowledge of the library internals , the input is invalid, how you can explain it to the user ( be kind and polite ): 即使从您的角度来看 - 根据您对库内部的了解 ,输入无效,您如何向用户解释( 善良和礼貌 ):

  • Clear documentation (in Xml doc and an architecture summary may help). 清晰的文档(在Xml doc和体系结构摘要中可能有所帮助)。
  • Publish the xml doc with the library. 使用库发布xml doc。
  • Clear error explanation in the exception if any. 如果有的话,在异常中清除错误说明。
  • Give the choice : 给出选择:

Look at Dictionary class, what do you prefer? 看看Dictionary类,你更喜欢什么? what call do you think is the fastest ? 你觉得哪个电话最快? What call can raises exception ? 什么电话会引发异常?

        Dictionary<string, string> dictionary = new Dictionary<string, string>();
        string res;
        dictionary.TryGetValue("key", out res);

or 要么

        var other = dictionary["key"];

9. Why not using Code Contracts ? 9.为什么不使用代码合同

It's an elegant way to avoid the ugly if then throw and isolate the contract from the implementation, permitting to reuse the contract for different implementations at the same time. 这是一种避免丑陋的优雅方法, if then throw并将契约与实现隔离开来,允许同时重用不同实现的契约。 You can even publish the contract to your library user to further explain him how to use the library. 您甚至可以将合同发布给您的库用户,以进一步向他解释如何使用该库。

As a conclusion, even if you can easily use throw , even if you can experience exceptions raising when you use .Net Framework, that doesn't mean it could be used without caution. 总而言之,即使您可以轻松使用throw ,即使您在使用.Net Framework时可能会遇到异常提升,但这并不意味着可以毫不谨慎地使用它。

Here are my opinions: 以下是我的意见:


General Cases 一般情况

Generally speaking, it is better to check for any invalid inputs before you process them in a method for robustness reason - be it private, protected, internal, protected internal, or public methods. 一般来说, 最好在出于稳健性原因的方法中处理它们之前检查任何无效输入 - 无论是private, protected, internal, protected internal, or public方法。 Although there are some performance costs paid for this approach, in most cases, this is worth doing rather than paying more time to debug and to patch the codes later. 虽然这种方法需要支付一些性能成本 ,但在大多数情况下,这是值得做的,而不是花费更多时间来调试和稍后修补代码。


Strictly Speaking, however... 严格说来,但......

Strictly speaking, however, it is not always needed to do so . 然而,严格地说, 并不总是需要这样做 Some methods, usually private ones, can be left without any input checking provided that you have full guarantee that there isn't single call for the method with invalid inputs . 某些方法(通常是private方法)可以在没有任何输入检查的情况下保留只要您完全保证没有单独调用具有无效输入的方法。 This may give you some performance benefit , especially if the method is called frequently to do some basic computation/action . 这可能会为您带来一些性能优势 ,特别是如果经常调用该方法来执行某些基本计算/操作 For such cases, doing checking for input validity may impair the performance significantly. 对于这种情况,检查输入有效性可能会显着影响性能。


Public Methods 公共方法

Now the public method is trickier. 现在public方法比较棘手。 This is because, more strictly speaking, although the access modifier alone can tell who can use the methods, it cannot tell who will use the methods. 这是因为,更严格地说,虽然访问修饰符单独可以告诉谁可以使用该方法,它不能告诉谁将会使用的方法。 More over, it also cannot tell how the methods are going to be used (that is, whether the methods are going to be called with invalid inputs in the given scopes or not). 更重要的是,它也无法告诉方法将如何使用(即,是否将在给定范围内使用无效输入调用方法)。


The Ultimate Determining Factor 终极决定因素

Although access modifiers for methods in the code can hint on how to use the methods, ultimately, it is humans who will use the methods, and it is up to the humans how they are going to use them and with what inputs. 虽然代码中方法的访问修饰符可以提示如何使用这些方法,但最终, 人类将使用这些方法,并且由人们决定如何使用它们以及使用什么输入。 Thus, in some rare cases, it is possible to have a public method which is only called in some private scope and in that private scope, the inputs for the public methods are guaranteed to be valid before the public method is called. 因此,在极少数情况下,可以使用仅在某个private范围内调用的public方法,并且在该private范围内, public方法的输入在调用public方法之前保证有效。

In such cases then, even the access modifier is public , there isn't any real need to check for invalid inputs, except for robust design reason. 在这种情况下,即使访问修饰符是public ,除了稳健的设计原因外,没有任何实际需要检查无效输入。 And why is this so? 为什么会这样呢? Because there are humans who know completely when and how the methods shall be called! 因为有些完全知道何时以及如何调用这些方法!

Here we can see, there is no guarantee either that public method always require checking for invalid inputs. 在这里我们可以看到,不能保证public方法总是需要检查无效输入。 And if this is true for public methods, it must also be true for protected, internal, protected internal, and private methods as well. 如果对于public方法也是如此,那么对于protected, internal, protected internal, and private方法也必须如此。


Conclusions 结论

So, in conclusion, we can say a couple of things to help us making decisions: 因此,总之,我们可以说几件事来帮助我们做出决定:

  • Generally , it is better to have checks for any invalid inputs for robust design reason, provided that performance is not at stake. 通常 ,如果性能不受影响 ,最好对稳健的设计原因检查任何无效输入。 This is true for any type of access modifiers. 对于任何类型的访问修饰符都是如此。
  • The invalid inputs check could be skipped if performance gain could be significantly improved by doing so , provided that it can also be guaranteed that the scope where the methods are called are always giving the methods valid inputs. 如果通过这样做可以显着提高性能增益,则可以跳过无效输入检查,前提是还可以保证调用方法的范围始终为方法提供有效输入。
  • private method is usually where we skip such checking, but there is no guarantee that we cannot do that for public method as well private方法通常是我们跳过这种检查的地方,但不能保证我们也不能为public方法这样做
  • Humans are the ones who ultimately use the methods. 人类是最终使用这些方法的人。 Regardless of how the access modifiers can hint the use of the methods, how the methods are actually used and called depend on the coders. 无论访问修饰符如何暗示方法的使用,实际使用和调用方法的方式取决于编码器。 Thus, we can only say about general/good practice, without restricting it to be the only way of doing it. 因此,我们只能说一般/良好实践,而不是限制它是唯一的方法
  1. The public interface of your library deserves tight checking of preconditions, because you should expect the users of your library to make mistakes and violate the preconditions by accident. 您的图书馆的公共界面值得仔细检查前提条件,因为您应该期望图书馆的用户犯错误并违反前提条件。 Help them understand what is going on in your library. 帮助他们了解您图书馆的动态。

  2. The private methods in your library do not require such runtime checking because you call them yourself. 库中的私有方法不需要这样的运行时检查,因为您自己调用它们。 You are in full control of what you are passing. 你完全可以控制你的传球。 If you want to add checks because you are afraid to mess up, then use asserts. 如果你想添加检查,因为你害怕搞砸,那么使用断言。 They will catch your own mistakes, but do not impede performance during runtime. 它们会捕获您自己的错误,但不会妨碍运行时的性能。

Though you tagged language-agnostic , it seems to me that it probably doesn't exist a general response. 虽然你标记了language-agnostic ,但在我看来它可能不存在一般的反应。

Notably, in your example you hinted the argument: so with a language accepting hinting it'll fire an error as soon as entering the function, before you can take any action. 值得注意的是,在你的例子中,你暗示了这个论点:所以当一个语言接受暗示时,它会在你进行任何动作之前立即触发错误。
In such a case, the only solution is to have checked the argument before calling your function ... but since you're writing a library, that cannot have sense! 在这种情况下,唯一的解决方案是在调用函数之前检查参数...但是因为你正在编写一个库,所以没有意义!

In the other hand, with no hinting, it remains realistic to check inside the function. 另一方面,没有提示,检查功能内部仍然是现实的。
So at this step of the reflexion, I'd already suggest to give up hinting. 所以在反思的这一步,我已经建议放弃暗示。

Now let's go back to your precise question: to what level should it be checked? 现在让我们回到您的确切问题:应该检查什么级别 For a given data piece it'd happen only at the highest level where it can "enter" (may be several occurrences for the same data), so logically it'd concern only public methods. 对于给定的数据片段,它只发生在它可以“进入”的最高级别(可能是相同数据的几次出现),所以逻辑上它只涉及公共方法。

That's for the theory. 那就是理论。 But maybe you plan a huge, complex, library so it might be not easy to ensure having certainty about registering all "entry points". 但也许您计划一个庞大而复杂的库,因此要确保注册所有“入口点”可能并不容易。
In this case, I'd suggest the opposite: consider to merely apply your controls everywhere, then only omit it where you clearly see it's duplicate. 在这种情况下,我建议相反:考虑只是在任何地方应用你的控件,然后只在你清楚地看到它重复的地方省略它。

Hope this helps. 希望这可以帮助。

In my opinion you should ALWAYS check for "invalid" data - independent whether it is a private or public method. 在我看来,你应该总是检查“无效”数据 - 无论是私人还是公共方法。

Looked from the other way... why should you be able to work with something invalid just because the method is private? 从另一个方面来看......为什么你应该能够处理一些无效的东西,因为这个方法是私有的? Doesn't make sense, right? 没有意义,对吧? Always try to use defensive programming and you will be happier in life ;-) 总是尝试使用防御性编程,你会在生活中更快乐;-)

This is a question of preference. 这是一个偏好问题。 But consider instead why are you checking for null or rather checking for valid input. 但请考虑为什么要检查null或者检查有效输入。 It's probably because you want to let the consumer of your library to know when he/she is using it incorrectly. 这可能是因为你想让你的图书馆的消费者知道他/她错误地使用它。

Let's imagine that we have implemented a class PersonList in a library. 让我们假设我们在库中实现了一个PersonList类。 This list can only contain objects of the type Person . 此列表只能包含Person类型的对象。 We have also on our PersonList implemented some operations and therefore we do not want it to contain any null values. 我们还在PersonList实现了一些操作,因此我们不希望它包含任何空值。

Consider the two following implementations of the Add method for this list: 考虑以下两个Add列表的实现:

Implementation 1 实施1

public void Add(Person item)
{
    if(_size == _items.Length)
    {
        EnsureCapacity(_size + 1);
    }

    _items[_size++] = item;
}

Implementation 2 实施2

public void Add(Person item)
{
    if(item == null)
    {
        throw new ArgumentNullException("Cannot add null to PersonList");
    }

    if(_size == _items.Length)
    {
        EnsureCapacity(_size + 1);
    }

    _items[_size++] = item;
}

Let's say we go with implementation 1 假设我们采用实现1

  • Null values can now be added in the list 现在可以在列表中添加空值
  • All opoerations implemented on the list will have to handle theese null values 列表上实现的所有操作都必须处理theese null值
  • If we should check for and throw a exception in our operation, consumer will be notified about the exception when he/she is calling one of the operations and it will at this state be very unclear what he/she has done wrong (it just wouldn't make any sense to go for this approach). 如果我们应该检查并在我们的操作中抛出异常,当他/她正在调用其中一个操作时,消费者将被告知该异常,并且在此状态下将非常不清楚他/她做错了什么(它只是不会采取这种方法是没有任何意义的。

If we instead choose to go with implementation 2, we make sure input to our library has the quality that we require for our class to operate on it. 如果我们选择使用实现2,我们确保对我们的库的输入具有我们的类对其进行操作所需的质量。 This means we only need to handle this here and then we can forget about it while we are implementing our other operations. 这意味着我们只需要处理这个问题,然后在实施其他操作时我们可以忘记它。

It will also become more clear for the consumer that he/she is using the library in the wrong way when he/she gets a ArgumentNullException on .Add instead of in .Sort or similair. 当消费者在.Add而不是.Sort或similair上获得ArgumentNullException时,他/她以错误的方式使用库也将变得更加清楚。

To sum it up my preference is to check for valid argument when it is being supplied by the consumer and it's not being handled by the private/internal methods of the library. 总结一下,我的偏好是在消费者提供它时检查有效参数,而不是由库的私有/内部方法处理。 This basically means we have to check arguments in constructors/methods that are public and takes parameters. 这基本上意味着我们必须检查public的构造函数/方法中的参数并获取参数。 Our private / internal methods can only be called from our public ones and they have allready checked the input which means we are good to go! 我们的private / internal方法只能从我们的公共方法调用,他们已经检查了输入,这意味着我们很高兴!

Using Code Contracts should also be considered when verifying input. 验证输入时还应考虑使用代码合同

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM