当前位置: 首页 > 工具软件 > null > 使用案例 >

系统错误null是什么意思_为什么NULL是错误的?

应翰飞
2023-12-01

系统错误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的介绍和整个对象思想由大卫-韦斯特的书。

在这里,我将尝试总结所有的参数,并举例说明如何避免使用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重构,以便其方法get()在未找到任何内容的情况下将引发异常,则我们的代码将如下所示:

if (!employees.containsKey("Jeffrey")) { // first search
  throw 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() {
      @Override
      public String name() {
        return "anonymous";
      }
      @Override
      public 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您的代码!

相关文章

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

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

系统错误null是什么意思

 类似资料: