正确理解Less is Better
在编码时,我们一般都遵守“Less is Better”教条。按照代码行数,大部分时候也是对的。但是,对整个系统而言,并不是所有的场景都满足“Less is Better”。敏捷宣言中的“简单设计”不是没有设计,编码之前进行适当的软件架构设计是非常有必要的。
1. 先看一个故事
在我指导C++桌面软件做Web服务化改造的时候,自己参照boost::serialization做一个对象与JSON互转库。C++没有像Java那样的反射机制,每个对象必需实现其成员serialize()函数,每个属性必需对应一行代码ar & MAKE_NVP(myMember)
,如下:
class MyClass {
friend class boost::serialization::access;
OtherClass myMember;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & MAKE_NVP(myMember);
}
};
1.1. 清晰流程的例子1
我们一个大的业务组下面有3个小组,我指导其中的两组上Web。那时我也只看过敏捷软件开发原则与模式、重构这些书籍,还没有深入到软件架构设计。我指导这两组的代码流程都是这样的:
// 例子:易懂的流程
Error XXXRequestHandler(const string &inputJson, string &outputJson) {
// STEP 1: 获取输入参数
InputXXXParam input;
parser::fromJson(inputJson, input);
// STEP 2: 加载所需数据到DC
// STEP 3: 业务处理
...
// STEP 4: 组装前端所需的数据
OutputXXXParam output;
...
// STEP 5: 转换成输出JSON
outputJson = parser::toJson(output, outputJson);
return ERROR_OK;
}
class DataCenter {};
class XXXCoreBusiness {};
class XXXBusiness {};
STEP 2
的DC部分,包含业务模型、数据加载等,Handler中只需调用少量函数。STEP 3
业务处理部分,主要是调用相对稳定的业务规则核心层。在InputXXXParam、OutputXXXParam中,只进行数据转换与JSON转换。大家都按照这个模式去实现更多的请求处理,沟通非常方便,一切进展比较顺利。
1.2. 有瑕疵的例子2
另一小组也由一位C++技能也比较强的同事带领上Web,其中用到了PIMPL模式、RAII机制等。我已经到前端组之后,回去帮修改两个问题与优化JSON转换性能时,发现其中的代码有些问题。其代码流程大概如下:
// 不好的例子
class OutputXXXParam {
friend class boost::serialization::access;
DataModel1 data1;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
// 该函数不满足“单一职责原则”
SomeThing st;
data1 = st.doFunc();
ar & MAKE_NVP(data1);
DataModel2 data2;
...
ar & MAKE_NVP(data2);
}
};
class SomeThing {
public:
SomeThing() {
LoadData();
}
doFunc() {
...
}
};
class XXXDataCenter {};
class XXXCoreBusiness {};
Error XXXRequestHandler(const string &inputJson, string &outputJson) {
// STEP 1: 获取输入参数
InputXXXParam input;
parser::fromJson(inputJson, input);
// STEP ...
// STEP ...
OutputXXXParam output;
output.setInput(input); // 可能是这样传入参的
// STEP end: 转换成输出JSON
outputJson = parser::toJson(output, outputJson);
return ERROR_OK;
}
刚开始跟踪这样代码的时候,见到serialize()
函数里面还干了一些与序列化无关的事情,非常吃惊!
1.3. 小结
对比上面的两个例子,例子1的边界比例子2划分清晰,没有把不同的功能柔和到一块,更加满足单一职责原则。
例子1跨越边界时(输入/输出与业务模型之间的转换),需要单独函数做数据转换,总代码行数比例子2的多,但是它有拥抱变化的灵活性。
在我未提交离职之前,去帮忙优化JSON转换性能,例子1小组的负责人跟我聊到这领域的代码思路比较清晰,说明我以前给他们打的基础还不错,我感到比较欣慰。
在我提交离职之后,例子2小组的新负责人也跟我聊了很多,也想让我跟他再合作一把,把这领域的软件问题解决掉;但是,那时的我心已不在此,他们的软件问题应该不止我看到的这一点,要解决掉大概需要一年的时间,只能跟他聊一下“业务专家与软件专家的关系,业务专家如何也能够成为软件专家”。
2. 满足Less is Better的场景
在设计高内聚低耦合的系统时,我们必需遵守5大设计原则。在满足5大设计原则的前提下,以下场景满足“Less is Better”:
- 单个函数的代码量。
- 单个类的代码量。
- 单个文件的代码量。
少更好,但是也不要少到极端(比如很多函数都是一个函数一行代码),过犹不及。
3. 不满足Less is Better的场景
随着系统复杂性的增加,我们需要把系统分解成各个部分,再把它们组装到一块构成一个稳定的系统。这时,我们还需要考虑沟通成本、扩展性、复用性等,很多场景就不再是“Less is Better”,如下:
- 项目的总代码行数。
- 接口、类、组件的个数。
- 软件架构设计。
只有划分出复用的组件之后,项目代码的增长率才会减小。接口、类、组件的个数,需要根据实际情况来定夺,当其混杂功能或内部的体量大到难以理解时,我们会分出新的接口、类、组件。最好在编码之前做一些软件架构设计,只是不需要详细的设计,能满足当前的需求且容易沟通即可。