6

我最近阅读了很多关于 TDD 和干净代码的内容,所以我开始研究一个简单的项目来使用它们,我遇到了一些我真的不确定最好的方法是什么。

我有一个将 JavaFile对象作为参数的类,期望这个File对象必须是一个目录并且必须以某个前缀开头。我的第一次通过涉及File在调用构造函数之前对对象进行检查,即检查它是否是一个目录并检查名称是否有效。但我不喜欢调用者指定什么使它有效,特别是有效前缀是什么,我认为这个逻辑应该放在类本身中。

我可以在构造函数中进行此检查并在它无效时抛出异常,但鉴于问题的性质,如果我正在迭代一个Files 列表,那么完全可以预期其中一些不会是“有效的” ' (即它们将是文件而不是目录)所以抛出一个Exception真的有保证吗?

public MyObject(File directory) {
    if (!directory.isDirectory()) {
        throw new IllegalArgumentException("Must be a directory");
    }
    if (!directory.getName().startsWith("Prefix")) {
        throw new IllegalArgumentException("Must start with Prefix");
    }
    ....
}

我考虑过可能添加一个工厂方法来创建对象,如果File无效则返回 null 。

public static MyObject createMyObject(File directory) {
    if (!directory.isDirectory() || !directory.getName().startsWith("Prefix")) {
        return null;
    }
    return new MyObject(directory);
}

或者,我考虑在调用构造函数之前为调用者验证文件的类添加一个静态方法。

public static boolean isValid(File directory) {
    return directory.isDirectory() && directory.getName().startsWith("Prefix");
}

if (MyObject.isValid(directory)) {
    MyObject object = new MyObject(directory);
}

那么就干净的代码和所有 OOP 原则(例如单一职责、耦合等)而言,这将是首选的方式?

更新:

阅读了一些已经发布的答案后,我开始考虑另一种可能性,这种可能性仅适用于我目前的情况,而不是像我的问题所涉及的那样普遍适用。

作为我的调用代码的一部分,我有一个来自文件系统的路径,并且我列出了该目录中的所有文件,然后我将每个文件传递给 MyObject 构造函数,无论它是否有效。我可以将 a 传递FileFilterlistFiles确保 listFiles 仅返回有效目录的方法。FileFilter可以在 MyObject 中声明:

public static FileFilter getFilter() {
    return new FileFilter() {
        public boolean accept(File path) {
            return path.isDirectory() && path.getName().startsWith("Prefix");
        }
    };
}

如果我的构造函数抛出异常,那么这确实是一种异常情况,因为期望它只传递有效目录。这样做意味着我可以消除对构造函数/工厂检查异常的需要,因为任何异常都表明某处存在错误,而不是预期的行为。但它仍然留下了是否将其放入构造函数或工厂方法的问题。

4

5 回答 5

3
public static MyObject createMyObject(File directory) throws IllegalArgumentException{
    if (!directory.isDirectory() || !directory.getName().startsWith("Prefix")) {
        return throw new IllegalArgumentException("invalid parameters")";
    }
    return new MyObject(directory);
}

这是您建议的两个选项的混合选项之一。使用工厂方法并验证工厂方法擅长的前提条件。所以 IMO 这可能是一个不错的选择。

为什么不选择选项 2:nulls因为当您可以抛出异常以警告用户某些先决条件未满足时, 返回是一个不好的选择。

更新: 此策略保证只会创建处于有效状态的对象。此外,如果你想模拟你的实例,MyObject你可以让它实现一些使用运行时多态性的接口并传递模拟对象。希望这是有道理的。

于 2013-03-11T11:38:21.657 回答
1

我认为验证代码所属的位置完全取决于“MyObject”类所代表的内容。

如果 MyObject 执行某些操作如果它有一个文件而不是一个目录就会失败,那么我会说它应该在其构造函数中包含验证代码 - 这使得类自包含,并允许可能重用该类之后。

如果 MyObject 只是文件/目录的容器,并且其中没有特定于目录的代码,则将验证代码放在确实需要目录而不是文件的类中。

于 2013-03-11T11:47:35.753 回答
1

当给定的参数没有完成由static boolean isValid()验证方法补充的契约时,我更喜欢提供一个构造函数的组合,该构造函数验证并抛出异常。

在循环中使用验证方法提供了一种构造有效对象的良好、可读的方式,而构造函数确保通过在需要时抛出异常来处理未验证的使用。

于 2013-03-11T11:53:17.883 回答
0

这取决于...

从编码的角度来看,最简单和最干净的方法是爆炸(未验证)构造函数。如果有合理的期望调用者会并且应该只传递目录,那么就去吧。

如果有合理的期望调用者可以传入非目录,那么您有两种选择:

  1. 如果调用者可以处理这种情况,让构造函数抛出一个检查异常
  2. 如果调用者无法处理异常,则如果调用者调用方法,您可以让您的类“什么也不做”——如果传递给构造函数的文件不是目录,则基本上忽略所有“做”某事的请求。
于 2013-03-11T12:43:47.957 回答
0

这称为按合同设计,并且有一些库可以帮助您检查传入的参数。

于 2013-05-03T15:22:01.707 回答