由 Mybatis 源码畅谈软件设计(九):“能用就行” 其实远远不够
到本节 Mybatis 源码中核心逻辑基本已经介绍完了,在这里我想借助 Mybatis 其他部分源码来介绍一些我认为在编程中能 最快提高编码质量的小方法,它们可能比较细碎,希望能对大家有所启发。
关于方法的长度和方法拆分
之前我在读完《代码整洁之道》时,非常痴迷于写小方法这件事,它强调“每个方法只做一件事,方法的长度不能超过 5 行”等观点。
记得某次代码评审时,有同事对将一个大方法拆分成多个小方法提出了异议:拆分出的小方法不能算作做了一件事,它们都只是大方法中的一个“动作”而已,所以不应该拆分巴拉巴拉。
这个观点让我说不出什么,后来我也在想:如果按照这个观点,多大的方法都可以概括成只做了一件事,那么我们就需要将所有的逻辑都“摊”到一个方法中吗?我觉得拆分方法目的不是在界定一件事还是一个动作上,而是 关注方法的可读性,拆分方法太多确实让代码变得不好读,需要辗转在多个方法之间,但是不拆的可读性也会差,所以接下来我想根据 Mybatis 这段代码来简单谈谈我对写方法的观点:
public class XMLConfigBuilder extends BaseBuilder {
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfsImpl(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginsElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlersElement(root.evalNode("typeHandlers"));
mappersElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
如上是 Mybatis 解析配置文件中各个标签的方法,它将每个标签的解析都单独定义出了一个方法,这也是我一直遵循的写方法的观点:最顶层的入口方法应该是短小清晰的步骤,在主方法中编排好方法的执行内容,这样主方法便是清晰明了的执行流程,我们便能一眼清晰的知道该方法做了什么事情,而针对各个具体的环节或者要改动哪些逻辑,直接跳转到对应的方法即可。
至于该不该将某段逻辑抽象成一个方法,我的观点是 能不能一眼看明白这段逻辑在干什么,如果不能,那么就应该被抽象到一个方法中,否则将其保留在原方法中也是没有问题的,对方法的抽象从来都不在于方法的长度,可读性 应得到更多的关注。
此外,还有一个能提高代码可读性的方法是:“合理使用换行符”,如下代码所示:
public class Configuration {
// ...
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver