在验证 类 中使用哪种设计模式来避免 if/else?
Which design pattern to use to avoid if/else in validation classes?
我目前正在使用 HibernateConstraintValidator 来实现我的验证。但是我的审阅者不同意在代码中使用 if/else 或!运营商。我可以使用哪种设计模式来删除验证逻辑中的 if/else?
public class SomeValidatorX implements ConstraintValidator<SomeAnnotation, UUID> {
@Autowired
SomeRepository someRepository;
@Override
public boolean isValid(UUID uuid, ConstraintValidationContext context) {
return !(uuid!=null && someRepository.existsById(uuid)); //The reviewer doesn't want this negation operator
}
}
在下面的代码中,他不想要 if/else
public class SomeValidatorY implements ConstraintValidator<SomeAnnotation, SomeClass> {
@Autowired
SomeRepository someRepository;
@Override
public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
if(someObject.getFieldA() != null) { //He doesn't want this if statement
//do some operations
List<Something> someList = someRepository.findByAAndB(someObject.getFieldA(),B);
return !someList.isEmpty(); //He doesn't want this ! operator
}
return false; // He was not fine with else statement in here as well
}
}
旁注:我们必须使用领域驱动设计(如果有帮助的话)
很久很久以前,在时间的开始。有一条准则说方法应该只有一个出口点。为此,开发人员必须跟踪本地状态并使用 if/else 才能到达方法的结尾。
今天我们知道得更多了。通过尽早退出方法,在阅读代码时更容易将整个流程牢记在心。更简单的代码意味着更少的错误。更少的错误等于更少的错误。
在我看来,这就是审阅者不喜欢代码的原因。它并不像它应该的那样容易阅读。
举第一个例子:
public boolean isValid(UUID uuid, ConstraintValidationContext context) {
return !(uuid!=null && someRepository.existsById(uuid)); //The reviewer doesn't want this negation operator
}
代码说的是“不是这个:(uuid不应该为空并且必须存在)”。这容易理解吗?我觉得不是。
备选方案:“如果 uuid 不存在也可以,但如果存在,该项目可能不存在”。
或者在代码中:
if (uuid == null) return true;
return !someRepository.existsById(uuid);
更容易阅读,对吧? (我希望我的意图是正确的;))
第二个例子
if(someObject.getFieldA() != null) { //He doesn't want this if statement
//do some operations
List<Something> someList = someRepository.findByAAndB(someObject.getFieldA(),B);
return !someList.isEmpty(); //He doesn't want this ! operator
}
return false; // He was not fine with else statement in here as well
好的。你在这里说:
- 如果字段 A 不为空:
- 构建一个包含 A 和 b 的列表
- 如果该列表不为空则失败,否则成功。
- 否则失败
一个更容易得出结论的方法就是简单地说:
- 不指定字段A也可以
- 如果指定字段 A,则它必须与 B 组合存在。
翻译成代码:
if (someObject.getFieldA() == null)
return true;
return !someRepository.findByAAndB(someObject.getFieldA(),B).isEmpty();
在 C# 中,我们有 Any()
,它与 isEmpty
相反,在这种情况下我更喜欢后者,因为它消除了否定。
有时需要否定。在存储库中编写新方法来避免它是没有意义的。但是,如果 findByAAndB
仅被此使用,我会将其重命名为 ensureCombination(a,b)
以便它可以 return 为有效案例。
试着边说边写代码,这样可以更容易地在脑海中创建代码的画面。你不是在说“我没吃饱,我们去吃午饭吧”,是吗? ;)
您可以检查空对象模式。
一般模式是从您的代码中完全禁止 null
。这消除了丑陋的 null
检查。在这一点上,我同意您的代码审查者的意见。
遵循以下建议将导致:
public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
return someRepository.containsAAndB(someObject.getFieldA(), B);
}
避免空检查
在引入空对象模式之前,只需应用该模式或约定来强制初始化所有引用。这样您就可以确保整个代码中没有 null
引用。
因此,当您遇到 NullPointerException
时,您不会通过引入 null
检查来解决问题,而是通过初始化引用(在构造时),例如,通过使用默认值、空集合或空对象。
大多数现代语言都支持通过注释进行代码分析,例如 @NonNull
,它会检查参数等引用,并在参数 null
/未初始化时抛出异常。 javax.annotation
例如提供这样的注释。
public void operation(@NonNull Object param) {
param.toString(); // Guaranteed to be not null
}
使用此类注释可以防止库代码出现空参数。
空对象模式
不是 null
个引用,而是用有意义的值或专用的空对象初始化每个引用:
定义空对象契约(不需要):
interface NullObject {
public boolean getIsNull();
}
定义基本类型:
abstract class Account {
private double value;
private List<Owner> owners;
// Getters/setters
}
定义空对象:
class NullAccount extends Account implements NullObject {
// Initialize ALL attributes with meaningful and *neutral* values
public NullAccount() {
setValue(0); //
setOwners(new ArrayList<Owner>())
@Override
public boolean getIsNull() {
return true;
}
}
定义默认实现:
class AccountImpl extends Account implements NullObject {
@Override
public boolean getIsNull() {
return true;
}
}
使用 NullAccount
class:
初始化所有 Account
引用
class Employee {
private Account Account;
public Employee() {
setAccount(new NullAccount());
}
}
或使用 NullAccount
到 return 失败状态实例(或默认)而不是 returning null
:
public Account findAccountOf(Owner owner) {
if (notFound) {
return new NullAccount();
}
}
public void testNullAccount() {
Account result = findAccountOf(null); // Returns a NullAccount
// The Null-object is neutral. We can use it without null checking.
// result.getOwners() always returns
// an empty collection (NullAccount) => no iteration => neutral behavior
for (Owner owner : result.getOwners()) {
double total += result.getvalue(); // No side effect.
}
}
尝试模式
您可以使用的另一种模式是 Try-Do 模式。您无需测试操作的结果,而只需测试操作本身。该操作对return操作是否成功负责。
在文本中搜索字符串时,return 是否找到结果的布尔值比 return 空字符串更方便,甚至更糟 null
:
public boolean tryFindInText(String source, String searchKey, SearchResult result) {
int matchIndex = source.indexOf(searchKey);
result.setMatchIndex(matchIndex);
return matchIndex > 0;
}
public void useTryDo() {
SearchResult result = new Searchresult();
if (tryFindInText("Example text", "ample", result) {
int index = result.getMatchIndex();
}
}
在您的特殊情况下,您可以将 findByAAndB()
替换为 containsAAndB() : boolean
实现。
组合模式
最终解决方案实现了Null-Object模式并重构了find方法。原来的findByAAndB()
的结果之前被丢弃了,因为你想测试A
和B
的存在。另一种方法 public boolean contains()
将改进您的代码。
重构后的实现如下所示:
abstract class FieldA {
}
class NullFieldA {
}
class FieldAImpl {
}
class SomeClass {
public SomeClass() {
setFieldA(new NullFieldA());
}
}
改进的验证:
public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
return someRepository.containsAAndB(someObject.getFieldA(), B);
}
我目前正在使用 HibernateConstraintValidator 来实现我的验证。但是我的审阅者不同意在代码中使用 if/else 或!运营商。我可以使用哪种设计模式来删除验证逻辑中的 if/else?
public class SomeValidatorX implements ConstraintValidator<SomeAnnotation, UUID> {
@Autowired
SomeRepository someRepository;
@Override
public boolean isValid(UUID uuid, ConstraintValidationContext context) {
return !(uuid!=null && someRepository.existsById(uuid)); //The reviewer doesn't want this negation operator
}
}
在下面的代码中,他不想要 if/else
public class SomeValidatorY implements ConstraintValidator<SomeAnnotation, SomeClass> {
@Autowired
SomeRepository someRepository;
@Override
public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
if(someObject.getFieldA() != null) { //He doesn't want this if statement
//do some operations
List<Something> someList = someRepository.findByAAndB(someObject.getFieldA(),B);
return !someList.isEmpty(); //He doesn't want this ! operator
}
return false; // He was not fine with else statement in here as well
}
}
旁注:我们必须使用领域驱动设计(如果有帮助的话)
很久很久以前,在时间的开始。有一条准则说方法应该只有一个出口点。为此,开发人员必须跟踪本地状态并使用 if/else 才能到达方法的结尾。
今天我们知道得更多了。通过尽早退出方法,在阅读代码时更容易将整个流程牢记在心。更简单的代码意味着更少的错误。更少的错误等于更少的错误。
在我看来,这就是审阅者不喜欢代码的原因。它并不像它应该的那样容易阅读。
举第一个例子:
public boolean isValid(UUID uuid, ConstraintValidationContext context) {
return !(uuid!=null && someRepository.existsById(uuid)); //The reviewer doesn't want this negation operator
}
代码说的是“不是这个:(uuid不应该为空并且必须存在)”。这容易理解吗?我觉得不是。
备选方案:“如果 uuid 不存在也可以,但如果存在,该项目可能不存在”。
或者在代码中:
if (uuid == null) return true;
return !someRepository.existsById(uuid);
更容易阅读,对吧? (我希望我的意图是正确的;))
第二个例子
if(someObject.getFieldA() != null) { //He doesn't want this if statement
//do some operations
List<Something> someList = someRepository.findByAAndB(someObject.getFieldA(),B);
return !someList.isEmpty(); //He doesn't want this ! operator
}
return false; // He was not fine with else statement in here as well
好的。你在这里说:
- 如果字段 A 不为空:
- 构建一个包含 A 和 b 的列表
- 如果该列表不为空则失败,否则成功。
- 否则失败
一个更容易得出结论的方法就是简单地说:
- 不指定字段A也可以
- 如果指定字段 A,则它必须与 B 组合存在。
翻译成代码:
if (someObject.getFieldA() == null)
return true;
return !someRepository.findByAAndB(someObject.getFieldA(),B).isEmpty();
在 C# 中,我们有 Any()
,它与 isEmpty
相反,在这种情况下我更喜欢后者,因为它消除了否定。
有时需要否定。在存储库中编写新方法来避免它是没有意义的。但是,如果 findByAAndB
仅被此使用,我会将其重命名为 ensureCombination(a,b)
以便它可以 return 为有效案例。
试着边说边写代码,这样可以更容易地在脑海中创建代码的画面。你不是在说“我没吃饱,我们去吃午饭吧”,是吗? ;)
您可以检查空对象模式。
一般模式是从您的代码中完全禁止 null
。这消除了丑陋的 null
检查。在这一点上,我同意您的代码审查者的意见。
遵循以下建议将导致:
public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
return someRepository.containsAAndB(someObject.getFieldA(), B);
}
避免空检查
在引入空对象模式之前,只需应用该模式或约定来强制初始化所有引用。这样您就可以确保整个代码中没有 null
引用。
因此,当您遇到 NullPointerException
时,您不会通过引入 null
检查来解决问题,而是通过初始化引用(在构造时),例如,通过使用默认值、空集合或空对象。
大多数现代语言都支持通过注释进行代码分析,例如 @NonNull
,它会检查参数等引用,并在参数 null
/未初始化时抛出异常。 javax.annotation
例如提供这样的注释。
public void operation(@NonNull Object param) {
param.toString(); // Guaranteed to be not null
}
使用此类注释可以防止库代码出现空参数。
空对象模式
不是 null
个引用,而是用有意义的值或专用的空对象初始化每个引用:
定义空对象契约(不需要):
interface NullObject {
public boolean getIsNull();
}
定义基本类型:
abstract class Account {
private double value;
private List<Owner> owners;
// Getters/setters
}
定义空对象:
class NullAccount extends Account implements NullObject {
// Initialize ALL attributes with meaningful and *neutral* values
public NullAccount() {
setValue(0); //
setOwners(new ArrayList<Owner>())
@Override
public boolean getIsNull() {
return true;
}
}
定义默认实现:
class AccountImpl extends Account implements NullObject {
@Override
public boolean getIsNull() {
return true;
}
}
使用 NullAccount
class:
Account
引用
class Employee {
private Account Account;
public Employee() {
setAccount(new NullAccount());
}
}
或使用 NullAccount
到 return 失败状态实例(或默认)而不是 returning null
:
public Account findAccountOf(Owner owner) {
if (notFound) {
return new NullAccount();
}
}
public void testNullAccount() {
Account result = findAccountOf(null); // Returns a NullAccount
// The Null-object is neutral. We can use it without null checking.
// result.getOwners() always returns
// an empty collection (NullAccount) => no iteration => neutral behavior
for (Owner owner : result.getOwners()) {
double total += result.getvalue(); // No side effect.
}
}
尝试模式
您可以使用的另一种模式是 Try-Do 模式。您无需测试操作的结果,而只需测试操作本身。该操作对return操作是否成功负责。
在文本中搜索字符串时,return 是否找到结果的布尔值比 return 空字符串更方便,甚至更糟 null
:
public boolean tryFindInText(String source, String searchKey, SearchResult result) {
int matchIndex = source.indexOf(searchKey);
result.setMatchIndex(matchIndex);
return matchIndex > 0;
}
public void useTryDo() {
SearchResult result = new Searchresult();
if (tryFindInText("Example text", "ample", result) {
int index = result.getMatchIndex();
}
}
在您的特殊情况下,您可以将 findByAAndB()
替换为 containsAAndB() : boolean
实现。
组合模式
最终解决方案实现了Null-Object模式并重构了find方法。原来的findByAAndB()
的结果之前被丢弃了,因为你想测试A
和B
的存在。另一种方法 public boolean contains()
将改进您的代码。
重构后的实现如下所示:
abstract class FieldA {
}
class NullFieldA {
}
class FieldAImpl {
}
class SomeClass {
public SomeClass() {
setFieldA(new NullFieldA());
}
}
改进的验证:
public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
return someRepository.containsAAndB(someObject.getFieldA(), B);
}