重复到这(1到9不重复)

文章插图
温馨提示:本文较长 , 同学们可收藏后再看 :)一 前言开发技术的发展 , 从第一次提出“函数/子程序” , 实现代码级重用;到面向对象的“类” , 重用数据结构与算法;再到“动态链接库”、“控件”等重用模块;到如今流行的云计算、微服务可重用整个系统 。技术发展虽然日新月异 , 但本质都是重用 , 只是粒度不同 。所以写代码的动机都应是把重复的工作变成可重用的方案 , 其中重复的工作包括业务上重复的场景、技术上重复的代码等 。合格的系统可以简化当下重复的工作;优秀的系统还能预见未来重复的工作 。
本文不谈框架、不谈架构 , 就谈写代码的那些事儿!后文始终围绕一个问题的解决方案 , 不断发现其中“重复”的代码 , 并提炼出“可重用”的抽象 , 持续“重构” 。希望通过这个过程和大家分享一些发现重复代码和提炼可重用抽象的方法 。
二 问题作为贯穿全文的主线 , 这有一个任务需要开发一个程序来完成:有一份存有职员信息(姓名、年龄、工资)的文件“work.txt” , 内容如下:
William 35 25000Kishore 41 35000Wallace 37 30000Bruce 39 29999要求从文件(work.txt)中读取员工薪酬 , 并输出到屏幕上 。为所有工资小于三万的员工涨 3000 元 。在屏幕上输出薪资调整后的结果 。把调整后的结果保存到原始文件 。即运行的结果是屏幕上要有八行输出 , “work.txt”的内容将变成:William 35 28000Kishore 41 35000Wallace 37 30000Bruce 39 32999三 测试在明确了需求之后 , 第一步要做的是写测试代码 , 而不是写功能代码 。《重构》一书中对重构的定义是:“在不改变代码外在行为的前提下 , 对代码做出修改 , 以改进程序的内部结构 。”其中明确指出“代码外在行为”是不改变的!在不断迭代重构时 , “保证每次重构的行为不变”也是一项重复的工作 , 所以测试先行不仅能尽早地校验对需求理解的正确性、还能避免重复测试 。本文通过一段 Shell 脚本完成以下工作:初始化work.txt文件 。检查标准输出的内容与期望的结果是否一致 。检查修改后work.txt文件的内容是否与期望一致 。清理现场 。

文章插图
将上述代码保存成check.sh , 待测试的源文件名作为参数 。如果程序通过 , 会显示“PASS” , 否则会输出不同的行以及“FAIL” 。
四 可维护代码【重复到这(1到9不重复)】第一版:It works
每位熟练的程序员都能快速地给出自己的实现 。本文示例代码使用ANSI C99编写 , Mac下用gcc能正常编译运行 , 其他环境未测试 。选择C语言是因为主流编程语言都或多或少借鉴它的语法 , 同时它的语法特性也足够用于演示 。
问题很简单 , 简单到把所有代码都塞到 main 函数里也不觉得长:
#include < stdio.h>int main(void) {struct {char name[8];int age;int salary;} e[4];FILE *istream, *ostream;int i;istream = fopen("work.txt", "r");for (i = 0; i < 4; i++) {fscanf(istream, "%s%d%d", e[i].name, &e[i].age, &e[i].salary);printf("%s %d %dn", e[i].name, e[i].age, e[i].salary);if (e[i].salary < 30000) {e[i].salary += 3000;}}fclose(istream);ostream = fopen("work.txt", "w");for (i = 0; i < 4; i++) {printf("%s %d %dn", e[i].name, e[i].age, e[i].salary);fprintf(ostream, "%s %d %dn", e[i].name, e[i].age, e[i].salary);}fclose(ostream);return 0;}其中第一个循环从work.txt中读取4行数据 , 并把信息输出到屏幕(需求#1);同时为薪资小于三万的职员增加三千元(需求#2);第二个循环遍历所有数据 , 把调整后的结果输出屏幕(需求#3) , 并保存结果到 work.txt(需求#4) 。试试将上述代码保存成1.c并执行 ./check.sh 1.c , 屏幕上会输出“PASS” , 即通过测试 。
第二版:清晰的代码 , 重构的基础
第一版代码解决了问题 , 让原来重复的调薪工作变成简便的、可反复使用的程序 。如果它是C语言课堂作业的答案 , 看起来还不错——至少缩进一致 , 也没混用空格和制表符;但从软件工程的角度来讲 , 它简直糟糕透了 , 因为没有清晰的表达意图:
魔法常量 4 重复出现 , 后续负责维护的程序员无法判断它们是碰巧相等还是有其他原因必须相等 。文件名work.txt重复出现 。重复且不清晰的文件指针类型定义 , 容易忽略 ostream 前面的 * 。e 和 i 变量命名不顾名思义 。变量的定义与使用离得太远 。无异常处理 , 文件可能不可读 。借乔老爷子的话说:“看不见的地方也要用心做好”——这些代码的问题用户虽然看不见也不在乎 , 但也要用心做好——已有几处显眼的地方出现重复 。不过 , 在代码变得清晰之前 , 不应急着动手去重构 , 因为清晰的代码更容易找出重复!针对上述意图不明的问题 , 准备对代码做以下调整:
确认数字 4 在三处的意义都是员工记录数 , 因此定义共享常量 #define RECORD_COUNT 4 。常量"work.txt"和 4 不同 , 内容虽然相同但意义不同:一个作输入 , 一个作输出 。如果也只简单的定义一个常量 FILE_NAME 共用 , 后续两者独立变化时 , 工作量并没减少 。所以去除重复代码时 , 切忌只看表面相同 , 背后意义相同的才是真正的相同 , 否则就像给所有常量 1 定义 ONE 别名一样没有意义 。所以需要定义三个常量 FILE_NAME、INPUT_FILE_NAME 和 OUTPUT_FILE_NAME 。用自定义的文件类型 typedef FILE
- 孕妈|临近预产期,每周一次的胎心监护做还是不做?认识到这两点很重要
- 石章|女子学车不到一个月,就和教练谈起了恋爱,怀孕后坚持要生下孩子
- 新生命|“不顺产我就不伺候月子”婆婆被邻床指着鼻子训斥老糊涂蠢到家
- 甜到腻的狗粮番(10部高校恋爱狗粮番推荐)
- 出血|产妇要想产后恶露排干净,做到此几点,宝妈先了解
- 粪便|城市里每天几千吨的粪便,最后都去哪了?真回到我们嘴里了吗?
- 和小姑娘谈恋爱(遇到没谈过恋爱的女生)
- 生长激素|发育“增速剂”找到了,儿科:敞开吃,胃口好体质强,身高不会差
- 脑洞大开|买不到“冰墩墩”就生一个?王冰墩墩走红,家长还真是脑洞大开
- 家长|下架网络游戏受到家长力挺,孩子发出“灵魂拷问”,父母语塞了
