18.2 使代码更不易于理解的东西
有很多东西可以使代码更不易于理解;本节提供了一些例子。其中一些,如事件驱动编程,在某些情况下是很有用的,所以你可能最终会使用它们。当这种情况发生时,额外的文档可以帮助减少读者的困惑。
事件驱动的编程。在事件驱动编程中,应用程序对外部事件作出反应,如网络数据包的到来或鼠标按钮的按下。一个模块负责报告传入的事件。应用程序的其他部分对某些感兴趣的事件进行注册,要求事件模块在这些事件发生时调用一个给定的函数或方法。
事件驱动的编程使得控制流很难被跟踪。事件处理函数从未被直接调用;它们是由事件模块间接调用的,通常使用一个函数指针或接口。即使你在事件模块中找到了调用点,仍然不可能知道哪个具体的函数会被调用:这将取决于哪些处理程序在运行时被注册。正因为如此,很难对事件驱动的代码进行推理,也很难让你自己相信它是有效的。
为了弥补这一缺陷,请为每个处理程序使用接口注释,以表明它何时被调用,如本例中:
通用的容器。许多语言提供了通用类,用于将两个或多个项目组合成一个对象,例如Java中的Pair
或C++中的std::pair
。这些类很诱人,因为它们使得用一个变量来传递多个对象变得很容易。最常见的用途之一是从一个方法中返回多个值,如这个Java例子:
不幸的是,通用容器导致了不易于理解的代码,因为分组元素的通用名称会掩盖其含义。在上面的例子中,调用者必须用result.getKey()
和result.getValue()
来引用两个返回值,这对这些值的实际含义没有提供任何线索。
因此,最好不要使用通用容器。如果你需要一个容器,请定义一个新的类或结构体,专门用于特定用途。这样你就可以为元素使用有意义的名称,而且可以在声明中提供额外的文档,这对于通用容器是不可能的。
这个例子说明了一个通用的规则:软件的设计应该是为了方便阅读,而不是为了方便编写。通用容器对写代码的人来说是很方便的,但它们会给后面所有的读者带来混乱。对写代码的人来说,最好多花几分钟时间来定义一个特定的容器结构,这样写出来的代码就会更易于理解。
用于声明和分配的不同类型。思考下面这个Java的例子:
该变量被声明为一个List
对象,但实际值是一个ArrayList
对象。这段代码是合法的,因为List
是ArrayList
的超类,但它会误导看到声明但没有看到实际分配的读者。实际的类型可能会影响到变量的使用方式(ArrayList
与List
的其他子类有不同的性能和线程安全属性),所以最好将声明与分配相匹配。
违反读者期望的代码。考虑以下代码,它是一个Java应用程序的主程序:
大多数应用程序在其主程序返回时退出,所以读者很可能认为这里会发生这种情况。然而,情况并非如此。RaftClient
的构造函数创建了额外的线程,即使应用程序的主线程结束了,这些线程仍在继续运行。这一行为应该记录在RaftClient
构造函数的接口注释中,但这一行为并不显而易见,足够值得在main
的末尾加上一个简短的注释。该注释应当指出应用程序将在其他线程中继续执行。如果代码符合读者所期望的约定,那么它就是最易于理解的;如果不符合,那么记录行为就很重要,这样读者就不会感到困惑。
Last updated