这是我们的一个代码评论中的主题,我想要更多意见。
假设我正在编写一个允许我将简单的Person对象插入数据库的服务。
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调用时验证不为null。
public class PersonService
{
public void addPerson(Person personToAdd)
{
VerifyNotNull(personToAdd.getName());//throws illegal arg exception
//add code
}
}
我不喜欢在构造函数中抛出Exceptions的想法。对我来说,选项2感觉正确,但我不知道如何解释或证明它。
在构造函数中抛出异常是否可以接受?
感谢您的帮助!
答案 0 :(得分:10)
第一种方法是 fail-fast ,这将增加您更快找到错误来源的可能性。可以这样想:如果您的错误日志开始告诉您由于您尝试添加具有空名称的人而出现了许多错误,那么您将需要知道这些人的空名来自哪里, 对?根据系统的结构,人员可能会在距离人员加入服务的地方创建数英里。所以你不知道代码中的四千个地方中哪一个创建了没有名字的人。
所以,如果我必须选择,我会选择第一个选项。
当然,这取决于您的商业模式。如果一个人在数据输入阶段创建一个完全合法的东西,那么只有当你准备好坚持要确保它通过验证的那个人的信息时,那么这是一个不同的故事。在这种情况下,您甚至可能想要一个包裹ValidatedPerson
的{{1}}类,但以类型安全的方式指示只有在有人的情况下才能调用Person
方法经历了验证过程,因为创建addPerson
的唯一方法是通过检查此人姓名的特定ValidationPerson
方法。
答案 1 :(得分:5)
我会在Ctor中抛出异常。主要原因是:
如果您使用IntelliJ,您还可以使用JetBrain的@NotNull和@Nullable注释,因此如果您使用null参数调用Ctor,IDE也会发出警告。请参阅http://www.jetbrains.com/idea/documentation/howto.html。
答案 2 :(得分:3)
您的选择不受您列出的两个选项的限制。
例如,可以使用工厂方法来实现:
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);
}
}
另一种方式,如果想要避免静态方法,可以使用如下的Builder:
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);
}
}
}
答案 3 :(得分:1)
可以在构造函数中抛出异常。但是,经过多年的经验,我发现除非你有办法恢复问题,否则不需要在构造函数或方法中检查null参数或varibles。如果没有恢复计划,那么程序无论如何都必然会失败。对于最佳实践,在传递构造函数或方法之前,应确保给出的参数不为null(如果null不可接受)。
答案 4 :(得分:1)
两种不同的方法。
第一种方法是快速失败 - 即您可以尽快发现存在问题,并且错误显示了null的确切位置(堆栈跟踪)。在其他条件相同的情况下,这是最好的。
然而,只有当您确定一个人永远不需要名字时,这种方法才有效。您是否有可能需要创建一个“占位符”人员,该人员暂时存在以提供某些功能但实际上并不需要名称?如果是这样,那么你需要使用第二种方法。一般来说占位符对象是个坏主意,所以如果可以,请使用第一个占位符对象。但最终只有你知道你的申请。
答案 5 :(得分:0)
名称不能为null的规则是业务逻辑,因此属于Person实体。所以构造函数选项在两者中更好。但是,您可能希望使用Factory方法(Evans):
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;
}
...
}
答案 6 :(得分:0)
我想说最好将异常抛在服务方法中。
原因是验证检查通常取决于特定应用程序特定的业务要求;服务层是大多数特定于应用程序的业务逻辑所在的位置(警告:有反对意见的参数;另请参阅:“贫血域模型”)。如果在Person类中添加约束,则所述类变得不那么可重用/可移植,因为您可能希望在其他地方使用允许在该特定构造函数中使用null name属性的类。