为什么NULL是错误的?

Java中NULL用法的简单示例:

public Employee getByName(String name) {int id = database.find(name);if (id == 0) {return null;}return new Employee(id);
}

这种方法有什么问题?

它可能返回NULL而不是对象-这是错误的。 在面向对象的范例中, NULL是一种可怕的做法,应不惜一切代价避免使用NULL 。 关于此出版物已经有很多意见,包括“ 空引用”, Tony Hoare撰写的《十亿美元的错误》演讲和David West撰写的整本《 Object Thinking 》。

在这里,我将尝试总结所有参数,并举例说明如何避免使用NULL并使用适当的面向对象的结构代替它们。

基本上,可以使用NULL两种替代方法。

第一个是Null Object设计模式(最好的方法是使其成为常数):

public Employee getByName(String name) {int id = database.find(name);if (id == 0) {return Employee.NOBODY;}return Employee(id);
}

第二种可能的替代方法是在无法返回对象时抛出异常 ,从而快速失败 :

public Employee getByName(String name) {int id = database.find(name);if (id == 0) {throw new EmployeeNotFoundException(name);}return Employee(id);
}

现在,让我们看看反对NULL的参数。

除了上面提到的Tony Hoare的演示文稿和David West的书以外,我在写这篇文章之前还阅读了这些出版物:Robert Martin的Clean Code ,Steve McConnell的Code Complete ,John Sonmez的“ No”到“ Null” , 是否返回无效的不良设计? StackOverflow上的讨论。

临时错误处理

每次获取对象作为输入时,都必须检查它是否为NULL或有效的对象引用。 如果忘记检查,则NullPointerException (NPE)可能会在运行时中断执行。 因此,您的逻辑将受到多次检查以及if / then / else分支的污染:

// this is a terrible design, don't reuse
Employee employee = dept.getByName("Jeffrey");
if (employee == null) {System.out.println("can't find an employee");System.exit(-1);
} else {employee.transferTo(dept2);
}

这就是应该用C和其他命令式程序语言处理异常情况的方式。 OOP引入了异常处理,主要是为了摆脱这些临时的错误处理块。 在OOP中,我们让异常冒泡直到它们到达应用程序范围的错误处理程序,并且我们的代码变得更加简洁明了:

dept.getByName("Jeffrey").transferTo(dept2);

考虑NULL引用是过程编程的继承,请改用1)Null对象或2)异常。

模棱两可的语义

为了明确传达其含义,必须将函数getByName()命名为getByNameOrNullIfNotFound() 。 每个返回对象或NULL函数都应该发生同样的情况。 否则,对于代码阅读器来说,模棱两可是不可避免的。 因此,为了保持语义的明确性,应为函数指定更长的名称。

要摆脱这种歧义,请始终返回真实对象,空对象或引发异常。

有人可能会争辩说,为了性能起见,我们有时必须返回NULL 。 例如,当地图中没有这样的项目时,Java中接口Map get()方法将返回NULL

Employee employee = employees.get("Jeffrey");
if (employee == null) {throw new EmployeeNotFoundException();
}
return employee;

由于Map使用NULL ,因此此代码仅搜索一次Map 。 如果我们将Map重构,以便其方法get()在未找到任何内容的情况下将引发异常,则我们的代码将如下所示:

if (!employees.containsKey("Jeffrey")) { // first searchthrow new EmployeeNotFoundException();
}
return employees.get("Jeffrey"); // second search

显然,此方法的速度是第一个方法的两倍。 该怎么办?

Map界面(对其作者没有冒犯)具有设计缺陷。 它的方法get()应该一直返回一个Iterator以便我们的代码如下所示:

Iterator found = Map.search("Jeffrey");
if (!found.hasNext()) {throw new EmployeeNotFoundException();
}
return found.next();

顺便说一句,这正是C ++ STL map :: find()方法的设计方式。

计算机思维与对象思维

有人知道Java中的对象是指向数据结构的指针,而NULL是指向0x00000000的指针(在Intel x86处理器中为0x00000000 if (employee == null)可以理解if (employee == null)语句。

但是,如果您开始以对象为对象进行思考,那么这种说法就没有意义了。 这是从对象角度看我们的代码的样子:

- Hello, is it a software department?
- Yes.
- Let me talk to your employee "Jeffrey" please.
- Hold the line please...
- Hello.
- Are you NULL?

对话中的最后一个问题听起来很奇怪,不是吗?

相反,如果他们在我们请求与Jeffrey通话后挂断电话,这会给我们造成麻烦(异常)。 此时,我们尝试再次致电或通知主管,我们无法联系Jeffrey并完成更大的交易。

或者,他们可以让我们与不是Jeffrey的其他人交谈,但是可以帮助我们解决大多数问题,或者在我们需要“特定于Jeffrey”的东西时拒绝帮助(空对象)。

缓慢失败

上面的代码没有快速失败 ,而是尝试缓慢消失,从而杀死了其他人。 它没有让所有人都知道出了什么问题并且应该立即开始异常处理,而是向客户端隐藏了此故障。

该参数与上面讨论的“临时错误处理”非常接近。

最好使代码尽可能脆弱,并在必要时让代码中断。

使您的方法对它们操作的数据极为苛刻。 如果提供的数据不足或根本不适合该方法的主要使用场景,请让他们通过引发异常来进行抱怨。

否则,返回一个Null对象,该对象暴露一些常见行为,并在所有其他调用上引发异常:

public Employee getByName(String name) {int id = database.find(name);Employee employee;if (id == 0) {employee = new Employee() {@Overridepublic String name() {return "anonymous";}@Overridepublic void transferTo(Department dept) {throw new AnonymousEmployeeException("I can't be transferred, I'm anonymous");}};} else {employee = Employee(id);}return employee;
}

可变和不完整的对象

通常, 强烈建议设计时牢记不变性的对象。 这意味着对象在实例化过程中会获得所有必需的知识,并且在整个生命周期中都不会改变其状态。

通常,在延迟加载中使用NULL值,以使对象不完整且可变。 例如:

public class Department {private Employee found = null;public synchronized Employee manager() {if (this.found == null) {this.found = new Employee("Jeffrey");}return this.found;}
}

该技术尽管被广泛使用,但却是OOP中的反模式。 主要是因为它使对象负责计算平台的性能问题,而这是Employee对象不应该意识到的。

对象不必管理状态并公开其与业务相关的行为,而必须处理其自身结果的缓存-这就是延迟加载的意义所在。

缓存不是员工在办公室里做的事情,对吗?

解决方案? 请勿以上述原始方式使用延迟加载。 相反,请将此缓存问题移至应用程序的另一层。

例如,在Java中,您可以使用面向方面的编程方面。 例如, jcabi-aspects具有@Cacheable批注,用于缓存方法返回的值:

import com.jcabi.aspects.Cacheable;
public class Department {@Cacheable(forever = true)public Employee manager() {return new Employee("Jacky Brown");}
}

我希望这种分析令人信服,您将停止NULL您的代码!

相关文章

您可能还会发现以下有趣的帖子:

  • Java代码中的典型错误
  • 实用程序类的OOP替代
  • 避免字符串串联
  • 对象应该是不可变的

翻译自: https://www.javacodegeeks.com/2014/09/why-null-is-bad.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362172.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网易原来也是个骗子

当初开通photo.163.com网易相册时,就是看着网易的宣传口号:免费而且不限容量!结果现在坏了,规则说改就改,容量一下子收到1G,超过部份要么给钱¥¥¥¥&#xffe5…

不同设备屏幕尺寸和DPR适配

为什么需要适配 目前市面上设备屏幕属性十分多样化(宽度和DPR并不一致),而作为设计和前端开发,无法为每个尺寸的设备单独设计一套UI并将其转为前端代码,这不现实。所以我们需要一套方案来将一套设计稿完美呈现在不同尺…

Test 6.29 T3 小学生

问题描述 “不错,不错!那么,准备好迎接下一道题了么?”一道白光闪过,CJK 眼前出现了 1e100 个小学生。“他们中,有一些人轨了我的机子。现在,我需要你在 1S 之内找出他们,并让他们认错!”凭借自己无所不知的神(xuan)奇(xue)力量, CJK 立刻发现了轨了 JesseLiun的机子的那 n 个…

web安全之CSRF

CSRF是什么 CSRF(Cross Site Request Forgery)跨站请求伪造,是一种攻击方式。通过名字可以看出这个攻击通常是在其他网站发出的,并不是在目标网站。 该攻击会在用户不知情的情况下盗用用户的登录信息请求目标网站完成对目标网站…

AssertJ Fest Hamcrest

我以前曾在博客中介绍过Hamcrest ,并使用其assertThat方法优先于JUnit的Assert 。 但是,我很快发现了FEST断言 ,并愉快地切换到它。 它提供了与Hamcrest相同的改进的测试可读性,并改善了故障消息,但具有启用IDE自动完…

Edge浏览器开发人员工具

UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240" 本地存储/会话存储模拟达到上限 资源终于全部列表出来了 删除 Cookie 和 删除会话 Cookie 样式可以实时编辑了 …

作为入门开发者应该知道的事

前言 如果你是开始学习编程并且决心学好,或者你刚离开学校还没有工作,这篇文章刚好适合你 我将分享作为开发者在工作过程中积累的关键点和隐藏的真相 编程是件困难的事,不仅仅对你而言 没有人说过编程是简单的事情,如果是的话&…

linux 第一个内核模块Hello World

内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kern…

懒惰和贪婪-正则回溯

需要一定的正则基础,并且是基于JS写的文章。 正则表达式是从左往右匹配的。在使用正则表达式的时候我们知道/.*/可以匹配一个字字符串中所有的字符,/.*?/却一个字符都匹配不到。/(.*)\d/中的.\*可以匹配除了最后一位数字的所有字符,但是之前…

简单的Java SSH客户端

可以使用jcabi-ssh在Java中通过几行代码通过SSH执行shell命令: String hello new Shell.Plain(new SSH("ssh.example.com", 22,"yegor", "-----BEGIN RSA PRIVATE KEY-----...") ).exec("echo Hello, world!");jcabi-ssh…

【JS复习笔记】00 序

作为一个前端苦手,说是复习,你就当我是重学好了。 好吧,我当然不可能抱着一个砖头去复习,所以捡了本薄的来读——《JavaScript语言精粹》。 当初带我的人说这本书挺好,就看这本书好了。我觉得他说的挺对。我喜欢这么…

高并发网络架构解决方案分析

1:html静态化2:图片服务器分离3:数据库集群4:缓存5:负载均衡大型高并发高负载网站的系统架构我在Cernet做过拨号接入平台的搭建,而后在Yahoo3721负载搜索引擎前端平台开发,又在猫扑处理过大型社…

Generator执行步骤浅析

在Generator函数出现之前JS的函数只能返回一个值,返回的方式就是return,但是Generator函数可以返回多个值,返回的方式是yield。并且Generator赋予了外部动态影响函数内部的执行顺序的能力。 基础语法 function* f () {const a yield 1cons…

使用 jQuery.Pin 垂直滚动时固定导航

ZKEACMS的导航默认是不能固定的,随着页面的滚动而滚动,为了有更好的用户体验,当页面往下滚动时,可以将导航固定在顶端,这样方便用户点击。 jQuery Pin 借助jQuery的一个插件 jQuery.Pin,这个插件可在用来…

班级名称

在Java中,每个类都有一个名称。 类位于软件包中,这使我们程序员可以一起工作,避免名称冲突。 我可以为A类命名,也可以为A类命名,只要它们位于不同的程序包中,它们就可以很好地协同工作。 如果您查看Class的…

MDK升级后的头文件冲突

////TITLE:// MDK升级后的头文件冲突//AUTHOR:// norains//DATE:// Friday 17-June-2011//Environment:// Keil MDK 4.2// .NET Micro Framework Porting 4.1// 因为在移植的时候,发现了不少MDK编译的一些问题,于是便想升级到最新版本&a…

内置假对象

尽管模拟对象是进行单元测试的理想工具,但通过模拟框架进行模拟可能会将您的单元测试变成难以维护的混乱。 这种复杂性的根本原因是我们的对象太大。 他们有很多方法,这些方法返回其他对象,这些对象也有方法。 当将此类对象的模拟版本作为参…

微信小程序面试题

小程序与原生App哪个好? 答: 小程序除了拥有公众号的低开发成本、低获客成本低以及无需下载等优势,在服务请求延时与用户使用体验是都得到了较大幅度 的提升,使得其能够承载跟复杂的服务功能以及使用户获得更好的用户体验。 简单…

阻止默认事件

在JS中经常需要阻止元素的默认事件。而阻止默认事件的方法都是使用事件对象的preventDefault()方法或者在函数中return false。在最近一次开发中使用preventDefault()方法的时候遇到一个问题&#xff0c;现在才想/猜明白原因&#xff0c;场景是这样的&#xff1a; <a href&…

MySQL之SQL优化详解(三)

目录 MySQL 之SQL优化详解&#xff08;三&#xff09; 1. 索引优化2. 剖析报告:Show ProfileMySQL 之SQL优化详解&#xff08;三&#xff09; 1. 索引优化 一旦建立索引&#xff0c;select 查询语句的where条件要尽量符合最佳左前缀的原则&#xff0c;如若能做到全值匹配最好。…