如何确定Java函数的副作用?

时间:2013-01-16 22:19:06

标签: java eclipse

我正在研究一个对象,特别是它的一个函数,它看起来像这样:

public class Dog {
    private ArrayList<Paw> paws;
    private double age;
    private Tongue tongue;

    public Dog(ArrayList<Paw> paws, double age, Tongue tongue) {
        this.paws = paws;
        this.age = age;
        this.tongue = tongue;
    }

    public void bark() {
        // ... about 100 lines of side effects operating
        // on the object's global members ...
    }
}

我真的想修复这些副作用并将对象重构为只做一件事的函数。

Eclipse中是否有自动化过程来标记可能的副作用?

如果没有,是否有我可以遵循的手动算法,所以我不会迷失在兔子洞里?

4 个答案:

答案 0 :(得分:8)

我会创建一个单元测试来测试你的旧Dog类,包括所有“副作用”(不太清楚你的意思)。

假设您的单元测试通过,您可以开始重构(使用Eclipse,选择适当的行并使用右键单击,Refactor,Extract Method可能是您的朋友)并继续使用您的单元测试来检查重构是否已经什么都坏了。

编辑:

要了解还有哪些内容正在改变PawTongue类的属性,您可以设置修改观察点(即在属性上设置断点,右键单击断点,断点属性,对bark()使用的任何属性取消“访问”),然后当您在调试器中运行应用程序时,每次更改属性时,调试器都将停止。

答案 1 :(得分:1)

我不知道自动化过程。

对于手动算法,如果您创建了所有类别字段final,则会出现错误大多数位置,将其修改为bark()的副作用。 (另一种方法是重命名它们,但也标记从它们读取)所以你可以很容易地找到副作用。慢慢重构为较小的函数,直到bark()没有错误。

final技巧将覆盖ArrayList爪子,因为你仍然可以add(), remove()等。所以这可能是重命名的。

答案 2 :(得分:1)

  

Eclipse中是否有自动化流程来标记可能的方面   效果

不,没有。

  

如果没有,是否有我可以遵循的手动算法,所以我不会迷路   兔子洞下来?

您可以首先评论(ctrl + /)所有对象的全局成员。因此,在您的函数中,您将在红色下划线,您必须使用参数传递或局部变量进行修复。后来取消注释成员。在Eclipse中,您还可以使用快捷方式Ctrl + Shift + M进行方法提取。对于重构,还有一个非常有用的快捷方式Ctrl + Shift + R用于更改变量名称。

答案 3 :(得分:0)

至少您可以在使用标记出现次数时检查任何直接引用,然后指向任何字段。您还可以搜索搜索菜单中每个字段的写入权限但请注意,这不会显示间接更改。要查找间接更改,您还需要搜索读取访问,然后找出所有(可变)引用的使用方式。您也应该对参数执行此操作,以避免对您的类产生外部副作用。最后,应审查对静态方法和字段的任何调用。

否则我很遗憾不知道在任何 IDE 中列出(可能的)副作用的任何方式。请注意,对 Paw 列表中的任何对象(例如“爪子”)的任何函数调用也可能产生副作用。这使得检查所有副作用几乎是不可能的。不过,如果能有更多的洞察力就好了。


您可以手动执行以下操作(准备好!):

首先要做的是最小化状态变化。如果你最小化状态,那么你也会自动最小化状态的可能变化,即副作用。在这种情况下,字段 age 是一个众所周知的陷阱,因为 age 随时间变化:您应该记录生日,并在类之外计算年龄,或者使用接受(当前) 日期。 如果您删除所有可能的状态更改,那么您的类实例是不可变的,您无需再担心副作用。

已经提到了使基本值(整数、字符等)和引用不可变的方法:使它们成为最终字段。但是还有其他一些事情:如果您没有将它设计为使用子类进行扩展,您还应该标记您的类 final。如果类本身不是 final,那么您可以将特定函数设为 final。始终使用正确的访问修饰符公开最少的方法集。

另一个技巧是在将给定的一组爪子分配给字段时使用 List.copyOf(Collection)(当然假设您没有 removePaw 方法)。这会创建一个防御性副本,但前提是认为有必要。但是,为了避免应用程序其他部分的副作用,您应该确保 Paw 也是不可变的。 Guava 和其他库也有不可变的列表,无论是类还是构造类的用户都不能更改。

请注意,创建不可变类与类设计直接相关。如果您的狗因为吠叫而产生副作用,那么您做错了。如果有的话,狗本身不会因为它吠叫而发生显着变化。如果你例如想要计算树皮的数量或响度,你应该在类实例之外注册它,而不是在。始终尝试并尽量减少类的功能;它本质上允许您最大限度地减少状态更改。

在较新的 Java 版本中,您可以改用记录。这些是默认情况下不可变的数据类。密切相关的想法是 Java 枚举、工厂和工厂方法。如果您想了解更多信息,请阅读最新版本的“Effective Java”。


如果您没有留下任何状态更改,那么您的对象是不可变的,您不必再担心副作用。请注意,内部创建的实例不需要是不可变的,只要您不在类之外共享它们的引用即可。问题中显示的 Dog 类非常适合创建不可变数据类(通常也有 hashCodeequals 方法并标记为 Serializable) .

如果您仍有产生副作用的方法,那么您应该使用单元测试来测试它们。当然,在这个阶段,如果调用者可以执行状态更改的方式数量最小化,并且他们可以进行的更改有明确的界限,那就太好了。