OverOps, an Israeli company which helps developers understand what happens in production, carried out research on what the top Java exceptions were in production. Want to guess which one is in #1 place? NullPointerException
.
以色列公司OverOps帮助开发人员了解生产中发生了什么,它对生产中最常见的Java异常进行了研究 。 想猜猜哪个在#1位吗? NullPointerException
。
Why is this exception is so frequent? I argue (as does Uncle Bob ?) that it is not because developers forget to add null checks.
为什么这种例外如此频繁? 我认为(一样Bob大叔?),它为n OT因为开发商忘了加上null检查。
The reason: developers use nulls too often.
原因: 开发人员经常使用null。
那么所有这些NULL是从哪里来的呢? (So where do all those NULLs come from?)
In C# and Java, all reference types can point to null
. We can get a reference to point to null
in the following ways:
在C#和Java中,所有引用类型都可以指向null
。 我们可以通过以下方式获得指向null
的引用:
- “uninitialized” reference type variables — variables which are initialized with nulls and are assigned their real value afterward. A bug can cause them never to be reassigned. “未初始化的”引用类型变量-使用空值初始化并在之后分配其实际值的变量。 错误可能导致它们永远不会被重新分配。
- uninitialized reference-type class members. 未初始化的引用类型的类成员。
explicit assignment to
null
or returningnull
from a function显式分配给
null
或从函数返回null
Here are some patterns I noticed in functions returning null
:
这是我在返回null
函数中注意到的一些模式:
错误处理 (Error handling)
Returning null
when the input is invalid. This is one way of returning error codes. I think it is an old school programming style, originating in the time when exceptions didn’t exist.
输入无效时返回null
。 这是返回错误代码的一种方法。 我认为这是一种古老的编程风格,起源于不存在异常的时期。
缺少实体的可选数据 (Missing optional data for entities)
An entity’s property can be optional. When there is no data for an optional property, it returns null
.
实体的属性可以是可选的。 如果没有可选属性的数据,则返回null
。
层次模型 (Hierarchical models)
In hierarchical models, we usually can navigate up and down. When we are at the top, we need a way to say so, usually it is by returning null
.
在分层模型中,我们通常可以上下导航。 当我们位于顶部时,我们需要一种表达方式,通常是通过返回null
。
查找功能 (Find functions)
When we want to find an entity by criteria in a collection, we return null
as a way to say the entity was not found.
当我们要通过集合中的条件查找实体时,我们返回null
来表示未找到该实体。
使用空值有什么问题? (What are the problems with using nulls?)
它会炸毁。 最终…… (It will blow up. Eventually…)
The code in which the NullPointerException
is raised can be very far from where the bug is. It makes tracing the real problem harder. Especially if the code is branched.
引发NullPointerException
的代码与错误所在的位置可能相去甚远。 这使得跟踪实际问题更加困难。 特别是如果代码是分支的。
In the following code example, there is a bug, somewhere in class A, causing entity
to be null. But the NullPointerException
is raised inside a function of class B. Real-life code can be much more complicated.
在下面的代码示例中,在类A中某个地方存在一个错误,导致entity
为null。 但是NullPointerException
是在类B的函数中引发的。现实生活中的代码可能要复杂得多。
隐藏的错误 (Hidden errors)
I encounter null
checks which seems like the developer was thinking:
我遇到null
检查,这似乎是开发人员在想的:
“I know I should check for
null
but I don’t know what it means when the function returnsnull
and I don’t know what to do with it,” or“我知道我应该检查
null
但是我不知道当函数返回null
并且不知道如何处理时,这意味着什么,”或- “ I think this cannot be null but just to make sure, I don’t want it to blow up production” “我认为这不能为空,只是为了确保我不希望它破坏生产”
It usually looks like this:
通常看起来像这样:
Those kinds of null
checks cause some code logic to not trigger, without the ability to know about it. Writing that kind of code means that some logic of a flow failed but the whole flow succeeded. It also can cause a bug in some other functionality which assumed the other function did its job.
这些null
检查会导致某些代码逻辑无法触发, 而又无法知道它 。 编写此类代码意味着流程的某些逻辑失败了,但整个流程成功了。 它也可能导致某些其他功能中的错误,这些错误假定其他功能已完成工作。
Imagine you buy a ticket to a show online. You got a success message! The day of the show finally arrived, you leave work early, arrange a babysitter, and go to see the show. When you arrive you discover you don’t have tickets! and there are no empty seats. You return home upset and confused?. Can you see how this kind of null check can cause this situation ?
想象一下,您在线购买了演出门票。 您收到一条成功消息! 演出的日子终于到了,您可以早点下班,安排保姆,然后去看演出。 当您到达时发现您没有门票! 而且没有空座位。 您回到家难过并感到困惑吗? 您知道这种空检查如何导致这种情况吗?
It also makes the code branched and ugly ?
这还会使代码分支而变得丑陋吗?
在C和Java中缺少非空引用类型 (Missing non-nullable reference types in C and Java)
In C# and Java reference types can always point to null
. This leads to a situation that we cannot know, by looking at a function signature, if null
is a valid input or output of it. I believe most of the functions don’t return or accept null
.
在C#和Java中, 引用类型始终可以指向null
。 通过查看函数签名,这将导致我们无法得知null
是其有效输入或输出的情况。 我相信大多数函数不会返回或接受null
。
Because it is hard to know if a function returns null
or not (unless documented), developers are either inserting null
checks when not needed, or don’t check for nulls
when needed — and yes, sometimes putting null checks when needed ?.
因为很难知道函数是否返回null
(除非有说明),所以开发人员要么在不需要时插入null
检查,要么在需要时不检查nulls
—是的,有时在需要时放置null检查?
This poor design choice causes the problems I described before in “Hidden errors” and a lot of NullPointerException
errors, of course. Lose-lose situation. ?
当然,这种糟糕的设计选择会导致我在“隐藏错误”中描述的问题以及许多NullPointerException
错误。 输的情况。 ?
There are languages like Kotlin that aim to eliminate NullPointerException
errors by differentiating between nullable references and non-nullable references. This allows catching the null
assigned to non-null
references, and making sure developers check for null
before dereferencing nullable references, all at compile time.
像Kotlin这样的语言旨在通过区分可空引用和不可空引用来消除NullPointerException
错误。 这使得捕捉null
分配给非null
引用,并确保开发人员检查null
解引用在编译时可空引用, 所有之前。
Microsoft is adopting the same approach by introducing Nullable Reference Types in C#8.
Microsoft通过在C#8中引入可空引用类型来采用相同的方法。
那我们该怎么办呢? (So what should we do?)
听鲍伯叔叔 (Listen to Uncle Bob)
Robert C. Martin, who is widely known as “Uncle Bob,” wrote one of the most famous books about clean code called (surprisingly) “Clean Code”. In this book, Uncle Bob claims, we should not return nulls
and should not pass null
to a function.
罗伯特·C·马丁 ( Robert C. Martin )被广泛称为“鲍勃叔叔”,他写了一本最著名的有关干净代码的书,被称为“干净代码” 。 Bob叔叔声称,在这本书中, 我们不应返回nulls
,也不应将null
传递给函数。
但是如何? (But how?)
I want to propose some technical patterns for eliminating null usage. I am not saying this is the best solution for every scenario — just options.
我想提出一些消除空使用的技术模式 。 我并不是说这是每种情况下的最佳解决方案-只是选项 。
Using the option type
使用选项类型
The option type is a different way to represent an optional value. This type asks if a value exists and, if so, accesses the value. When trying to access the value which doesn’t exist, it raises an exception. This solves the problem of NullPointerException
raised in code areas away from the bug. In Java there is Optional<T>;class. In C# (until C# 7 ) there is the Nullable type which is only for value types but you can create your own or use a library.
选项类型是表示可选值的另一种方式。 此类型询问值是否存在,如果存在,则访问该值。 尝试访问不存在的值时,将引发异常 。 这解决了在远离该错误的代码区域中引发NullPointerException
的问题。 在Java中,存在Optional <T> ; class。 在C#(直到C#7)中,存在Nullabl e类型,仅适用于值类型,但您可以创建自己的数据库或使用数据库 。
A straightforward approach is to replace a reference that can be null
(by logic) with this type:
一种直接的方法是用此类型替换可以为null
的引用(按逻辑):
Splitting the function into two
将功能一分为二
Each function that returns null
will be converted to two functions. One function with the same signature throws an exception instead of returning null
. The second function returns a boolean representing if it is valid or not to call the first function. Let’s see an example:
每个返回null
函数都将转换为两个函数。 具有相同签名的一个函数将引发异常,而不是返回null
。 第二个函数返回一个布尔值,表示调用第一个函数是否有效。 让我们来看一个例子:
If the code holding an IEmployee
instance assumes this employee has a manager the code should call to Manager
. But if this assumption doesn’t exist the code should call to HasManager
and handle the two possible outputs.
如果保存IEmployee
实例的代码假定该雇员有一位经理,则该代码应调用Manager
。 但是,如果不存在此假设,则代码应调用HasManager
并处理两个可能的输出。
Let’s see another example:
让我们看另一个例子:
The logic of ContainsEmployeById
is basically the same as FindEmployeById
but without returning the employee. Now let’s say that those functions reach the DB, we have a performance problem here. Let’s introduce a similar but different pattern: the boolean
function when returning true
will also return the data we search for. It looks like this:
的逻辑 ContainsEmployeById
与FindEmployeById
基本相同 但不返回员工。 现在让我们说这些功能到达了数据库,这是一个性能问题。 让我们介绍一个类似但不同的模式: boolean
函数在返回true
时也会返回我们搜索的数据。 看起来像这样:
A common use of this pattern is int.Parse
and int.TryParse
.
此模式的常见用法是int.Parse
和int.TryParse
。
The fact that I can separate a function to two functions and each has its own usages is a sign that returning null
is a code smell for violating the Single Responsibility Principle.
我可以将一个函数分为两个函数,每个函数都有自己的用法,这一事实表明,返回null
是违反单一职责原则的代码味道 。
分割界面 (Splitting the interface)
A practical guideline we can derive from the Liskov principle is that a class must implement all functions of an interface it implements. Returning null
or throwing an exception are ways to not implement a function. So returning null
is a code smell for violating the Liskov principle.
我们可以从Liskov原理中得出的实用指导原则是,类必须实现其实现的接口的所有功能 。 返回null
或引发异常是不实现函数的方法。 因此,返回null
是违反Liskov原理的代码味道。
If a class can’t implement a specific interface’s function we can move that function to another interface and each class will implement only the interface it can.
如果一个类不能实现特定接口的功能, 我们可以将该函数移至另一个接口 ,每个类将仅实现其可以实现的接口。
Now instead of asking employee.HasManager
— which we will do if we used the first approach “Splitting the function into two” — we ask employee is IManagedEmployee
.
现在,而不是询问employee.HasManager
(如果我们使用第一种方法“将功能拆分为两个”,则将执行此操作),而是询问employee是IManagedEmployee
。
我不是一个人工作,也不是在新建项目。 现在怎么办? (I am not working alone and not on a greenfield project. What now?)
In existing codebases, there area lot of code returning reference types. We cannot know if null
is valid output or not.
在现有的代码库中,有很多代码返回引用类型。 我们不知道null
是否是有效输出。
The first quick win I wish you to have is to change your coding conventions so null
is not a valid input or output to a function. Or, at least when you decide that null
is a valid output, use the Option type.
我希望您获得的第一个捷径是更改编码约定,因此null
不是有效的输入或输出 功能。 或至少 当您确定null
为有效输出时,请使用Option类型。
There are some tools which can help to enforce this convention like ReSharper and NullGuard. I guess, although I haven’t tried this yet, you can add a custom rule to SonarQube which will alert when the word null
appears.
有一些工具可以帮助强制执行此约定,例如ReSharper和NullGuard 。 我想,尽管我还没有尝试过,但是您可以向SonarQube添加一个自定义规则,当出现null
时会发出警报。
I would love to know what you think. Are you going to embrace this convention? And if not, why? What’s holding you back?
我很想知道你的想法。 您要接受这个约定吗? 如果没有,为什么? 是什么让你退缩?
If you encounter a scenario in which you think returning null
is the right design choice, or the patterns I suggested are not good, I would love to know.
如果遇到您认为返回null
是正确的设计选择,或者我建议的模式不好的情况,我很想知道。
Thanks Mark Kazakov for the funny meme, Alex Zhitnitsky from OverOps for answering my questions, Baot for organizing a great writing event for new bloggers, Itzik Saban , Amitay Horwitz and Max Ophius for giving me feedback.
感谢马克·卡扎科夫的搞笑米姆, 亚历克斯Zhitnitsky从OverOps回答我的问题, Baot组织新的博客,一个伟大的写作活动伊茨克萨班 , Amitay霍维茨和最大Ophius给我反馈。
翻译自: https://www.freecodecamp.org/news/how-to-get-rid-of-nullpointerexception-3cdf9199f9fb/