【翻译】 Demeter 法则 - 编写害羞的代码

#translation #demeter #refactoring

【翻译】 Demeter 法则 - 编写害羞的代码

在我所有的编写服务端代码的生涯中,我已经发现项目能够长期成功的关键,并不是一个快速的算法和炫酷的框架,而是代码的复杂程度。难以管理的复杂度,对大型项目的可维护性有深远的影响。难以理解的应用是很难重构的。引入新的特性是一个缓慢痛苦的过程,增加了到市场发布的时间。我曾经见过一个系统,开发者即使想做一些小的修改都会被吓尿,因为这可能会无意间的弄坏应用的其他的部分。或者,这些扭曲的代码只有一个或者一小撮开发者可以理解,当他们离职后,这个项目将会变成噩梦。

有许多的因素会影响代码的复杂度。一个很重要的因素就是耦合的总数,或者说是应用的模块之间的依赖性。让我们来看下下面这段代码。假设这儿有一台服务器允许用户连接。当用户连接并且认证后,他们会被封装到User类中:

// 用来表示一个用户
class User {
  public final String username;
  public final int id;
  public final Socket socket;

  public void disconnect() {
    socket.close();
  }

  // 其他的才做socket的方法。
}

如果你需要发送消息给用户,我们将会这样做:

// Another class
class Message {
  // Send "Hello." string to a User
  public void sayHello(User user) {
    Socket s = user.socket; // Get the user's socket
    OutputStream outputStream = s.getOutputStream();
    PrintWriter out = new PrintWriter(outputStream);
    out.println("Hello."); // send the message to the socket.
  }
}

User类有个明显的缺陷:它没有封装socket对象,将它暴露给了所有人。如果我们想改变这个实现(比如使用异步的socket库),我们必须改变代码的许多地方。

然而,Message类的sayHello(...) 方法也同样有问题。它与socket对象直接交互。它获取了不相干的“第三方”对象(socket)并且直接使用它。这个例子可能有点随意,但是我在真实的应用代码中见到了太多的这样的代码。好消息是,这个能够非常容易被一项技术检测出来,即 Demeter法则。这个法则(其实叫一项技术或者一个guideline更加合适)总结如下:

  • 每一个单元仅仅对其他的单元有非常有限的了解:即联系非常紧密的单元
  • 每一个单元只与他的朋友交互;不要与陌生人说话
  • 只与亲密的朋友交互。 基本原则为:**一个给定的对象,要尽可能少的了解它的结构、属性等等(包括他的子组件),与“infomation hiding”原则一致。

简单的说,罗辑不相干的模块的紧耦合违背了Demeter 法则。尽管那是缺少封装导致的副作用,这个法则并不鼓励访问和使用第三方的对象,下面是一个例子来解释Demeter法则:

public class Foo {
    /**
     * 这个例子将会导致两个违反原则的地方
     */
    public void example(Bar b) {
        // 这个方法调用是ok的, 因为 "example" 的一个参数
        C c = b.getC();

        // 这个方法调用有问题, 因为我们使用的 c, 是从 B获取的。
        // 我们应该从b直接访问, e.g. "b.doItOnC();"
        c.doIt();

        // 这同样有问题, 这只是函数链来表示相同的罗辑,只是少了临时变量.
        b.getC().doIt();

        //构造函数,而不是方法调用
        D d = new D();
        // 这样也是ok的,因为我们创建了一个新的d的实例
        d.doSomethingElse();
    }
}

现在回到 sayHello(...)方法,它违背了Demeter法则里面的与陌生人(socket)交流,尽管这个方法他自己病没啥卵用,但是它帮助我们检测紧耦合。问题最开始是因为User类没有隐藏好它的内部细节,现在我们修复它:

class User {
  public final String username;
  public final int id;
  // 把类变成私有的,以防止暴露给其他人.
  private final Socket socket;

  // 封装消息功能的简单的实现
  void sendMessage(String message) {
    OutputStream outputStream = socket.getOutputStream();
    PrintWriter out = new PrintWriter(outputStream);
    out.println(message);
  }
}

现在我们能够修改sayHello(...)方法,让其不再依赖socket对象。

class Message {
  // Doesn't violate the Law of Demeter anymore.
  public void sayHello(User user) {
    user.sendMessage("Hello.");
  }

耦合问题就解决了。在书 The Pragmatic Programmer, Andrew 建议编写 不会与太多对象交互的,“害羞”的代码

我平时在写代码和review代码的时候,都会关注Demeter法则。但是这个是可以自动化的,使用源码分析工具。虽然我没用用过他。

原文链接 http://codeahoy.com/2016/06/17/the-law-of-demeter-writing-shy-code/

comments powered by Disqus