8

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

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

4 回答 4

8

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

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

编辑:

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

于 2013-01-16T22:24:51.170 回答
2

我不知道自动化过程。

至于手动算法,如果您将所有类字段设为 ,则大多数final地方都会出错,因为它们被修改为. (另一种方法是重命名它们,但也会标记从它们读取的内容)因此您可以很容易地找到副作用。慢慢重构成更小的函数,直到没有错误。bark()bark()

ArrayList 爪子不会被这个final技巧所覆盖,因为你仍然可以从中add(), remove()等。所以这可能是一个要重命名的。

于 2013-01-16T22:25:44.980 回答
1

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

不,没有。

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

您可以从注释 ( ctrl + /) 对象的所有全局成员开始。所以在你的函数中,你会有红色下划线的单词,你必须通过参数传递或局部变量来修复。后来取消评论成员。在 Eclipse 中,您还可以使用快捷方式Ctrl + Shift + M进行方法提取。对于 refectoring,还有一个非常有用的快捷方式Ctrl + Shift + R来更改变量名。

于 2013-01-16T22:26:52.040 回答
0

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

否则,不幸的是,我不知道在任何 IDE 中列出(可能的)副作用的任何方式。请注意,对任何对象(例如Paws 列表中的“爪子”)的任何函数调用也可能会产生副作用。这使得几乎不可能检查每一个副作用。不过,有更多的见解会很好。


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

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

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

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

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

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


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

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

于 2021-08-05T14:02:22.007 回答