一 模块功能单一化
模块的功能要单一,这似乎是人尽皆知的原则。但是在编码设计过程中,并不是谁都能小心的处理这个问题。
首先举一个实际中的例子:在我们的Capsuit的“安全检查”部件的开发过程中,我们开发了一个模块,用于其他模块输出Log.假设这个模块输出一个函数,叫做LogOutput,只要调用这个函数,就可以输出Log到某一个文件中。这个函数定义如下:
void LogOutput(const TCHAR *format,…);这个模块需要初始化,初始化的过程,有一步是从配置文件中,得到Log文件的路径。
bool LogInit()这时候我们有另一个需求:我们要开发一个新的组件,称为“生存通知”。很自然这个模块里面也要用到Log.我们试图简单的拷贝代码来重用Log这个组件。但是这时出现了问题。我们#include “log.h”.
{
CString log_file_path = CfgFile::GetLogFilePath();
if(log_file_path.IsEmpty())
return false;
…
}
同时log.h中有#include “cfgfile.h”.cfgfile是“安全检查”模块独有的配置文件,和“生存通知”没有任何关系。但是我们不得不拷贝cfgfile.h和cfgfile.c。不过更糟糕的是,cfgfile.c中的处理非常复杂,用到了XML解析。为此,我们必须再包含XML.c和XML.h.此外,几乎所有的“安全检查模块”都包含了一个称为“def.h”的头文件。def.h中#include了几乎所有的头文件。如果我们使用这些.c文件,也必须同时拥有所有的这些头文件。其结果为,我们无法重用Log.c和Log.h组成的Log模块。除非我们把两个工程合并成一个。或者修改Log.c.
其实这个问题的核心在于,Log.c这个模块的功能不够单一。作为一个Log模块,打开文件并输出Log是其功能目标,而读取配置文件找到Log文件的路径,看似和Log相关,但是实质上并非Log的目标功能。一个Log应该是可以向任何位置的文件输出Log的。所以我们修改 Log.c中的LogInit()这个函数,给他传入一个Log文件路径,而不是调用配置文件去读取.
bool LogInit(const CString &str)
{
if(str.IsEmpty())
return flase;
log_file_path = str;
…
}