Java 构造函数中的空检查与服务方法中的空检查?
这是我们的代码审查中的一个主题,我想听听更多的意见 假设我正在编写一个服务,它允许我将一个简单的Person对象插入数据库Java 构造函数中的空检查与服务方法中的空检查?,java,oop,object,soa,Java,Oop,Object,Soa,这是我们的代码审查中的一个主题,我想听听更多的意见 假设我正在编写一个服务,它允许我将一个简单的Person对象插入数据库 public class Person { private String name; .... } 我们有一个简单的VerifyNotNull方法抛出IllegalArgumentException 你会选择哪条路线?为什么。 备选案文1: 验证Person对象的构造函数中是否不为null public Person(String name) {
public class Person
{
private String name;
....
}
我们有一个简单的VerifyNotNull方法抛出IllegalArgumentException
你会选择哪条路线?为什么。
备选案文1: 验证Person对象的构造函数中是否不为null
public Person(String name)
{
VerifyNotNull(name);//throws illegal arg exception if name is null
this.name = name;
}
备选案文2: 允许使用null构造Person,在addPerson调用时验证NOTNULL
public class PersonService
{
public void addPerson(Person personToAdd)
{
VerifyNotNull(personToAdd.getName());//throws illegal arg exception
//add code
}
}
我不喜欢在构造函数中抛出异常的想法。对我来说,选项2感觉不错,但我不知道如何解释或证明它
在构造函数中抛出异常是否可以接受
谢谢你的帮助 第一种方法是更快地失败,这将增加更快地找到bug来源的可能性。这样想:如果你的错误日志开始告诉你,由于你试图添加有空名字的人,出现了许多错误,你会想知道这些人的空名字来自哪里,对吗?根据您的系统的结构,可能是在距离用户添加到服务的位置几英里远的地方创建了用户。因此,您不知道代码中的4000个位置中的哪一个正在创建没有名字的人 所以如果我必须选择的话,我会选择第一个
当然,这取决于你的商业模式。如果一个人在数据输入阶段创建一个空名字是完全合法的,并且直到你准备好保存这个人的信息,你才想确保它通过了验证,那么情况就不同了。在这种情况下,您甚至可能希望生成一个
ValidatedPerson
类,该类包装Person
,但以类型安全的方式指示只有在有人完成验证过程时才能调用addPerson
方法,因为创建一个ValidationPerson
的唯一方法是通过一个特定的validate
方法来检查这个人的名字。我会在Ctor中抛出异常。主要原因是:
- 它甚至不允许创建无效的Person对象
- 您不需要在应用程序中使用Person对象的每个点检查Person对象是否有效
- 异常被抛出到导致无效人员对象存在的代码附近(例如,当您创建对象时,它被正确调用,当谁知道谁已经处理了对象时,它在代码中被调用)
- 代码会很快失败——这是一个很好的实践
如果您使用的是IntelliJ,那么还可以使用JetBrain的@NotNull和@Nullable注释,因此,如果您使用null参数调用Ctor,IDE也会向您发出警告。请参阅。在构造函数中抛出异常是可以接受的。然而,经过多年的经验,我发现检查构造函数或方法中的空参数或变量并不是必需的,除非您有办法恢复问题。如果没有恢复计划,那么程序肯定会失败。为了获得最佳实践,在传递构造函数或方法之前,应确保给定给它们的参数不为null(如果null不可接受)。您的选择不受列出的两个选项的限制
- 完全可以保证对象验证而不从构造函数引发异常
class Person {
private final String name;
private Person(String name) { // private constructor
this.name = name;
}
public static Person newPerson(String name) { // factory method
VerifyNotNull(name); // IAE not from c'tor, guaranteed check
return new Person(name);
}
}
另一种方法是,如果希望避免使用静态方法,可以使用如下生成器:
class Person {
private final String name;
private Person(String name) { // private constructor
this.name = name;
}
/**
* Usage: Person p = new Person.Builder(name).build();
*/
public static class Builder {
private final String name;
public Builder(String name) {
this.name = name;
}
public Person build() {
VerifyNotNull(name); // IAE not from c'tor, guaranteed check
return new Person(name);
}
}
}
两种不同的方法 第一种方法是快速失效——也就是说,您会尽快发现存在问题,错误会准确地显示空值是如何出现的(堆栈跟踪)。如果其他条件都相同,那就更好了
然而,只有当你确信一个人永远不需要名字时,这种方法才有效。您是否可能需要创建一个“占位符”人员,该人员暂时存在以提供某些功能,但实际上不需要姓名?如果是这样,那么您需要使用第二种方法。一般来说,占位符对象是一个坏主意,因此如果可以,请使用第一个占位符对象。但最终只有您知道您的应用程序。名称不能为null的规则是业务逻辑,因此属于Person实体。因此,构造函数选项是两者中较好的。但是,您可能希望使用工厂方法(Evans):
我认为最好在服务方法内部抛出异常
原因是验证检查通常取决于特定应用程序的特定业务需求;服务层是大多数特定于应用程序的业务逻辑所在的地方(注意:有人反对这一点;另请参见:“贫血的域模型”)。如果在Person类中添加约束,则该类的可重用性/可移植性会降低,因为您可能希望在其他地方使用该类,该类允许在特定构造函数中使用null name属性。我也不喜欢从初始值设定项引发异常——然而,我更希望使用该类,而不是带有“无效”(不可变)状态。选项#2的一个主要问题/区别是它可能永远不会通过
addPerson
:因此,空名称的问题只与PersonService有关。+1如果不能创建有效对象,我看不出有任何理由不在构造函数中抛出异常。而且它肯定是一个更好的选择周围有无效对象。因此,我们已将一个简单的new Person(name)
转换为new Person.Builder(name).build()
,其名称为..具体是什么?从中引发异常有什么问题
public class Person
{
public Person()
{
//Nothing much here.
}
public static Person Create(String name)
{
VerifyNotNull(name);//throws illegal arg exception if name is null
Person person = new Person();
person.name = name;
return person;
}
...
}