Scj System Analyst

重构和重用


软件重构穿插在软件的整个周期中,代码重用则是在重构的基础上对已有代码改造 后再度使用或直接引用,目的在于缩短开发周期,减轻测试负担和减小出错的几率,同时有助于 将重心放在问题本身而非代码细节,提高开发效率,也有可能提升代码质量和减少维护 成本。实际上代码重用可以在不同项目中进行,也可以在同一个项目中进行。总之, 重构和重用无处不在,重构是重用的基础,重用是重构的动力。

题外话

好久没有写博客了,一来研究生毕业期间要写论文以及还有很多与论文相关的事情要做,二来六月份 还在考驾照,三来科研团队还要我在 20 天内用 MATLAB 开发一个某领域的仿真和数值计算 软件……。说好的毕业旅行呢?说好的各种嗨呢?结果统统与我无关!只能把开发这个软件 当做毕业旅行了。之前没有用 MATLAB 开发过软件,对 MATLAB 不熟悉,这么短的时间内用 一门不熟悉的语言开发一个较为复杂的软件(当然要满足性能要求),周围的人都劝我:这 任务几乎不可能完成(这种事情我可没少干),这明显在坑人,能推脱就推脱,能拖延就拖 延……。但这都不符合我的理念,同时为了能顺利毕业也得拼了!由于对 MATLAB 不熟,所 以无法预期开发周期长短,只能摸着石头过河,不过在这种情形下,良好的软件工程和系统 分析知识(哎,这些看似很虚的东西,可是我每时每刻使用的东西,也是最有用的东西之一) 给了我很大的帮助。为了提高沟通效率,采用的是快速原型迭代和测试驱动开发:快速原型是 为了试探和逐步确认需求,迭代则是先抓核心再周边,测试驱动则是分块验证并逐步集成 (毕竟不熟悉该语言)。

后面遇到了以下几个棘手的问题:MATLAB 原生不支持多线程、MATLAB 面向对象机制有缺陷 (存在大量的数据拷贝,在数百万级矩阵的大量拷贝时间是相当可观)。更要命的是这两个 问题对该软件项目都有致命的影响:第一个问题导致软件界面响应迟钝,当程序在后台计算 时,界面无法及时响应操作,也无法中断程序;第二个问题导致程序运行偏慢。这是语言本 身的缺陷,很难克服,只能更改设计,好在需求划分得比较细,模块划分得较好,各模块接 口设计较为合理,所以即使在更改设计时,这些也只需要简单的修改就可以作为零部件使用, 减轻了测试工作量。良好的功能模块划分和接口设计,使得软件重新设计变得相对容易。

  • 解决第一个问题时

采用了界面响应与计算响应分离的设计,主界面只是启动计算,启动之后托管,这样该界面 还可以进行与该计算无关的相关操作,然后跳出另一个独立窗口专门用于控制计算,同时可 以传递信息至主界面,从而实现两界面的协同。

  • 解决第二个问题时

将循环尽量改造成矩阵运算,同时减少函数间的数据传递,使大量数据尽量在函数内部使用 (这实际上是面向对象核心理念在函数粒度中的应用),从而充分利用了 MATLAB 矩阵并行 计算的优势,提高了运算速率。本来还想使用它的多核加速机制的,结果遗憾的是,经测试 在代码逻辑中无法满足条件。

在这次开发与重新设计的过程中,让我深深体会到:好的划分和代码设计可以充分应对需求 变化和性能瓶颈。而好的划分和代码设计离不开重构和重用,但需要指出的是,不要想当然 地过早优化,否则会损耗大量精力于无关痛痒的事情上,同时会拖慢开发进度,影响全局, 导致进度过度延后,从而导致项目过度复杂,最后不堪重负而导致无果而终。我想,软件工 程和系统分析等这些看起来很虚的东西,有时候可以将不可能逐渐变为可能,复杂逐步变为 简单,无头绪乱成一团变得有条理而稳健,并帮助你高效搭建起需求和实现的桥梁,甚至在 需求改变时,之前的零部件也可以很少改动就可以被重用,同时通过适当组织便可快速满 足新的需求。

编译器在重用方面的发展

首先会从机器码编程开始,逐步探索编译器在代码重用上发展。

机器语言

机器语言可以认为是最原始的编程语言,只是以二进制的形式重用了机器指令,就如 同汉语中的一个个汉字,我们说的每句话中的汉字都是已经创造出来的,这是非常细粒度的 重用。这种重用是以机器指令集(类似汉语的字库)的形式实现的。它和具体的机器有关, 一般需要在同类型同指令集的机器上才能重用。

汇编语言

汇编语言与机器指令对应,并用英文单词缩写标识机器指令,因此仍然与机器相关,所以 重用也受到了一定的限制。但在重用方面还是做出了很大的贡献:

  • 单一变量重用

基于对存储地址和常数的单词缩写,可以不断地重用该变量名和常量名。

  • 结构和记录变量

结构和记录变量类似 C 语言的结构体,如此由单一变量重用上升到了变量集合的重用。

  • 代码段重用

前面的重用都是数据层面的重用,没有“行为上的重用”。汇编语言还提供了标签用来标识 一段代码,配合堆栈和 jmp (跳转指令,类似 C 语言的 goto)便可以实现代码段的重用 (类似 C 语言的函数,只不过没有输入参数和返回值,当然通过仿照 C 语言的函数对应的 汇编代码也可以实现 C 语言的函数功能,只不过汇编语言没有原生支持函数的概念)。如此 便可以实现数据和行为相结合的重用。

  • 过程重用

与上面的代码段重用不同的是,汇编引入了过程这个概念和关键字,并区分了过程的 NEAR 调用 和 FAR 调用,结合 call(连接)指令便可实现过程调用执行,通过 ret(return)指令可以从 过程返回(非常接近 C 语言的函数调用了)。与 C 语言函数不同的是,汇编语言的过程概念 没有原生提供过程参数(类似函数参数)的概念,而是借助寄存器、堆栈或指向它们的指针 实现的(由编程人员自己实现参数传递)。

  • 中断处理程序重用

是过程的远程(FAR)调用的延伸。但它结合硬件中断可以实现程序之间的跳转和重用,多个 程序可以共用中断处理程序,所以很多中断处理程序可以做成系统模块。

  • 等价(EQU)与宏

等价可以实现程序中不断重复的小段代码,但宏表达能力更强。宏(通过 MACRO 和 ENDM 定义 宏以及 EXITM 退出宏)可以实现带参宏“函数”(类似 C 语言的宏),一定程度上弥补了“ 过程”不带参的不便。

  • INCLUDE 伪指令

INCLUDE 可以将已经编写好的汇编源程序插入到该指令所在的位置,从而达到重用整个文件 中的程序(变量、过程、宏等),大大增加了重用的粒度,但由于函数概念的缺失,需要对 待重用的源程序中的实现细节有所把握,这就增加了重用的难度和代价。

面向过程的语言

机器语言和汇编语言都可以认为是面向机器的语言,跨平台和可移植性非常差,而且没有完整 的函数概念(虽然有“过程”,但没有显示的支持过程入口和出口,即函数参数和返回值类似 的概念)。从而限制了汇编语言代码的可重用性,开发大型软件非常吃力,开发效率非常低下。

Pascal 和 C 语言是典型的面向过程的语言,接下来将以 C 语言为例阐述一下面向过程语言 在代码重用方面做出的贡献。

  • 人性化,可读性大为提升

引入了结构化语句,如条件(if else)、循环(for、while 等)以及更多的类型。这些更 接近人的思维习惯,提高了代码的可读性,开发效率得到提升,从而降低了代码重用时理解 的难度。

  • 函数和模块化

C 语言完整支持函数的概念,不同场景下只需要传入不同的参数即可重用函数中的代码得到 返回值,可以几乎不用关系函数内部的代码,只需知道函数的功能和参数以及返回值就可以 了。这形成了一个黑盒和零部件或模块,使用之前只需要知道盒子或模块的“外部形态”即可, 然后像搭积木一样,通过函数入口和出口连接所有积木,最终形成一个完整的大型程序。

由于 C 语言的跨平台性,函数不仅可以在本程序多次重用,还可以被其他程序和其他平台的 程序使用,甚至可以嵌入到其他语言中,如此大大增加了函数的可重用性。因此出现了标准库 和大量的第三方库提供给开发人员使用,这种搭积木的方式大大提高了开发效率。

  • include 关键字

C 语言头文件和实现文件分开,即函数的声明放在头文件中,实现放在 c 文件中,这样声明 和实现的分离可以大大增加程序的商业化发布。发布时,把 c 文件编译好(二进制或目标 文件等)之后只需提供头文件,其他人就可以重用其中的代码了,如此即保护了不愿公开的 内容(实现文件),也传播了价值(提供头文件和编译过后的二进制文件),从而可加快商业化, 进一步增加了代码重用的来源,同时也隐藏了实现细节,当有更好的实现时,头文件的用户不 需要修改自己的程序,只需重新笔译即可(结合动态库技术,甚至都不需要重新编译)。

面向对象编程语言

面向对象编程语言很多,典型的代表是 C++,接下来就以它来阐述。前面已经了解到,面向过程 的编程语言已经具有较好的可移植性和可重用性,为何还产生了面向对象的编程语言呢?

C 语言在大型软件开发和多人共同开发一个大项目时,很容易遇到单个人的程序跑起来没问题, 但多个人的程序组装起来就会出现例如重定义,局部作用域覆盖全局作用域等很难查明或很棘手 的问题,所以就需要约定很多规则防止这些问题的发生。大家知道,沟通是很费时间的,而且 沟通往往会出现纰漏和理解上的不一致性,既花了大量时间,也埋下了隐患。既然沟通会带来 如此大的代价,所以需要尽量地消除不必要的沟通,比如防止函数名重名导致的重定义问题, 这是语言层面的问题,不是具体业务,这种沟通是可以消除的,是不必要的。

而且,C 语言函数默认是全局的,即使为了同一目的而写的函数也可能分散在不同的文件中, 实现不同目的的函数也会被杂乱的放在同一个文件中。这导致,当需要寻找某个或某个系列 的函数来共同实现一个较大的功能时,往往很难收集到,因为太乱太分散了,于是可读性和 重用性下降,甚至有时候宁愿重写也不愿意去找现有的。要想防止这种情况,开发人员之间 又得进行协商和沟通以形成较好的编程规范。如此高的沟通代价让人望而却步。

而面向对象语言减少了很多不必要的沟通,并且很好地将相关的代码聚合,将不相关的代码 隔离,同时将不同开发人员开发的代码有效隔离,同时能很好的组装起来。(时间问题,待续)

【注意】本文属于作者原创,欢迎转载!转载时请注明以下内容:

(转载自)ShengChangJian's Blog编程技术文章地址:

https://ShengChangJian.github.io/2018/07/reconsitution-and-reuse.html

主页地址:https://shengchangjian.github.io/


上一篇: C++ 编程规范

下一篇: git 常用技巧

Comments

【目录】