简介
最近对一段遗留代码进行了重构。这段代码看似简单,却花了我很多时间。现在记录下来一些主要的分析过程,以备参考。
主要的功能就是一个映射:string -> [int | double | string]. 接口如下:
- get(name, result); // 如果name为预定义的,则得到一个结果
- set(name, value); // 如果name为预定义的,将数据传入。
数据类型的变化
从功能描述可以看到,结果数据的类型是三个基本类型的联合结构(C++)。随着预定义名字的逐步添加,很快出现了新的数据存取要求。
- 枚举类型:例如set(”A”,1);与set(”A”,”ok”);效果相同。
- 结构类型:例如set(”A”,1);此时要求同时set(”B”,”hello”);set(”C”,0.5);…
- 别名类型:例如set(”A”,1);与set(”B”,100);效果相同。
- 集合类型:例如set(”A”,1);set(”A”,2);意味着set(”A”,{1,2});
- 互斥类型:例如set(”A”,1);意味着set(”B”,0);set(”B”,1);意味着set(”A”,0);
- 空值类型:例如set(”A”,NULL);意味着set(”A”,value);value依赖于name。
- 默认值类型:get(“A”, value);合法,即使没有调用过set。
- 强制值类型:如果set(”A”,1, FOREVER);那么”A”不能再设置任何其它值。
- 访问记次类型:如果set(”A”,value);”A”的访问次数加一,具体次数可以通过get(”A”,ACCESS)得到。
- 附加操作类型:可以设置一个钩子函数,执行附加操作.
- 组合类型:以上各种类型的组合,例如互斥集合类型。
原有的代码基于基本类型,使用了一套复杂的函数逻辑来完成。新的代码采用了明确的类型定义来完成,保持了这段程序的主要功能,即数据存取流程的简单性。
总结
通常我们会抱怨遗留代码中混乱的逻辑,但是不要忽视其中隐藏的功能需求,应该采用更简单的机制去重构,例如本文的数据类型机制。