为了代码复用,拆了很多个模块,导致代码不好阅读了怎么办? 之前代码虽然写的挫,但是 ctrl+f 在一个文件里就可以找到对应的代码,现在找段代码可费劲了。
过去的模式是,生产环境看到了一个页面,肯定对应的代码都在代码里的一个函数里。如果点击了一个按钮,这个按钮的 handler 肯定也是写在代码里的一个函数里。 也就是但凡是同一个时刻执行的 CPU 指令,都可以在同一个源代码文件里找到原因。 这种一对一的映射关系是非常利于人类的理解的。只要做了模块化,其实质就是把代码从这个文件移动出去。 必然就是在一个文件里 ctrl+f 不到了。
产生这个现象的第二个原因是之前,我使用了某个函数,比如 login(),会在文件头部有 import 的声明,说明这个 login 函数是哪里提供的。但是如果你使用的是运行时装配的模块化技术,比如面向对象的多态,函数式编程的高阶函数,这里的 login 可能只有一个定义,你是无法在编辑的时候找到这个 login 的实现代码的。模块化并不等于用运行时模块去装配,可读性降低很多时候是因为过于依赖运行时装配导致了。Java 系的代码里经常出现滥用 interface 和依赖注入的代码,很多都是没有必要的。
第三个原因是模块之间的边界不恰当。这个不恰当体现为一个模块没有办法独立讲一个完整的故事。当然这是一个非常主观的判断,就像“不好读”也是一个无法量化的主观判断。但是有一个侧面印证的办法,就是观察一个程序员在读一段代码的过程中,多少次进行了代码跳转。如果模块边界比较恰当,那么在阅读的时候应该是可以从上往下比较顺畅的读完的。如果边界切得犬牙交错,就经常看到一个地方不明白,就要跳过去看看实现代码。
第四个原因是为了复用,经常需要插入扩展点。这些扩展点必然会是一部分使用方需要,另外一部分使用方式不需要的。这些额外的扩展点,往往其默认实现都是一些空函数。这就导致在代码阅读的时候,要知道一个函数调用是有功能实现在里面,还是留了一个空白的扩展点。当阅读一段代码,要跳过很多个空函数的时候,阅读体验是下降了的。这个现象和上一个原因其实是同时出现的。都是模块之间的边界切得不合理导致的。
尽量少用运行时的模块化方案(面向对象的多态,函数式编程的高阶函数),静态组装的模块更好阅读。模块的边界多尝试几种,一般来说得让一个模块能独立讲一个完整的故事,要不然阅读起来就要跳来跳去。