反射意味隐秘的耦合 - yegor256


当您的代码动态更改自身时,就会发生反射式编程(或反射)。例如,一个类的方法,当我们调用它时,会向该类添加一个新方法(也称为猴子补丁)。
Java、Python、PHP、JavaScript都有这个“强大”的特性。
它有什么问题?
嗯,它很慢,很危险,而且很难阅读和调试。
但与它引入代码的耦合性相比,这一切又都算不了什么。

在很多情况下,反思可以“帮助”你。让我们通过所有这些,看看为什么它们添加到代码中的耦合是不必要和有害的。
这是代码:

public int sizeOf(Iterable items) {
  return ((Collection) items).size();
}

我不确定每个人都会同意这是反射,但我相信它是:我们在运行时检查类的结构,然后调用Iterable. 此方法仅在运行时“显示”,当我们在字节码中对其进行动态快捷方式时。可以看到其背后机制

为什么这很糟糕,除了

  • 1)它很慢,
  • 2)它更冗长且可读性更低,
  • 以及 3)它引入了一个新的故障点,因为对象items可能不是 class 的实例Collection,导致MethodNotFoundException?

上面的代码给整个程序带来的最大问题是它在自身和客户端之间引入了耦合,例如:

public void calc(Iterable<?> list) {
  int s = sizeOf(list);
  System.out.println("The size is " + s);
}

这种方法可能有效,也可能无效。这将取决于list. 如果是Collection,则调用sizeOf将成功。否则,将出现运行时故障。

通过查看方法calc,我们无法判断什么是正确的处理方法list以避免运行时失败。
我们需要阅读sizeOf内部代码,然后才能更改calc为这样的内容:

public void calc(Iterable<?> list) {
  if (list instanceof Collection) {
    int s = sizeOf(list);
    System.out.println("The size is " + s);
  } else {
    System.out.println("The size is unknown");
  }
}


到目前为止,此代码似乎还可以。
但是,当sizeOf将其实现更改为这样的东西时会发生什么(我从这篇关于强制转换的文章中得到它):

public int sizeOf(Iterable items) {
  int size = 0;
  if (items instanceof Collection) {
    size = ((Collection) items).size();
  } else {
    for (Object item : items) {
      ++size;
    }
  }
  return size;
}


现在,sizeOf完美地处理任何传入的类型,无论它是否是实例Collection。

现在,sizeOf完美地处理了进来的任何类型,不管它是否是Collection的实例。然而,方法calc并不知道方法sizeOf的变化。相反,它仍然认为,如果它得到除Collection之外的任何东西,sizeOf就会中断。为了使它们保持同步,我们必须记住,calc对sizeOf的了解太多,当sizeOf发生变化时,它将不得不修改它。因此,说 calc 与 sizeOf 耦合是有效的,这种耦合是隐藏的:很可能,当 sizeOf 得到更好的实现时,我们会忘记修改 calc。此外,程序中可能还有很多与calc类似的地方,当sizeOf方法发生变化时,我们必须记得修改这些地方。很显然,我们会忘记其中的大部分。

这种耦合是一个很大的可维护性问题,它是由于Java中反射的存在而引入的。

详细点击标题