代码重构原则

1. 总则 
总则规定了一些大体原则,必须要作的、最需要注意的事项。也是面向目前我们的代码中亟需解决的一些问题: 
(1)头文件、源文件布局混乱,直接影响编译效率 
(2)有编程规范,但遵守的很差 
(3)过长函数 
(4)大量重复代码

1.1 源文件 
源文件原则:

● 函数行数尽量不要超过50行,超过50行的目前阶段并非严格禁止,但需要说明理由

● 源文件长度尽量不要超过500行, 不同子功能、子模块的代码不要放在一个源文件中;理论上源文件分的越细越好。保证同一源文件中的代码“强内聚”。

● 无特殊情况,不允许使用全局变量,一律局部静态化,对外封装访问接口,对有并发可能的业务, 
均需在访问接口中作并发控制。

● 函数拆分封装基本原则:明显的重复代码一律封装函数;何时该拆分较长函数、封装新函数?只要能提高代码的可读性、可复用性就该拆分:哪怕只有一行。

1.2 头文件 
头文件总体原则:头文件应该越简单越好,包含的其他头文件越少越好

●头文件包含其他头文件的原则: 仅包含且必须包含那些头文件中所出现的类型需要的头文件(如,头文件A中出现的类型定义在头文件B中,则头文件A必须包含头文件B,除此以外的其他头文件不允许包含)。头文件应该包含哪些头文件应该仅决定于它自己,而不是那些包含此头文件的源文件(如,将源文件编译时需要用到的头文件B,这时源文件已经包含头文件A了,索性将头文件B包含在此头文件A中,这是错误的做法)。

●头文件划分原则:类型定义、宏定义应与函数声明相分离,分别位于不同的头文件中。内部函数声明头文件与外部函数声明头文件相分离,内部类型定义头文件,与外部类型定义头文件相分离。需要特别注意的是,外部函数声明头文件与外部宏、结构定义头文件一定要独立出来,这样可以保证外部调用者引用的头文件肯定是“最简化的”,不会包含外部调用者不需要的内部函数声明或者接口定义。

●头文件的语义层次化原则:头文件需要有语义层次,不同语义层次的类型定义不要放在一个头文件中,不同层次的函数声明不要放在一个头文件中。

●仅在一个源文件中用到的类型和宏定义,就放在此源文件中定义,不要放入任何头文件中。

●头文件的语义相关性原则:同一头文件中出现的类型定义、函数声明应该是语义相关的、有内部逻辑关系的,避免将风马牛不相及的定义和声明放在一个头文件中。

●对开放给其他模块使用的外部头文件尤其要注意其所包含的头文件的合理性,如包含太多不必要的其他头文件,将影响所有引用此头文件的模块,因此影响更严重。 
1.3 编程规范 
编程规范中的规则必须遵守,如有特殊情况不能遵守的,必须说明理由,编程规范见附件

1.4 过长函数拆分 
说明见附录部分《函数设计》 
函数行数尽量不要超过50行,超过50行的需要说明理由

1.5 代码复用 
原则: Write once and only once 
只写一次,需要作些灵活化处理,需要动脑子,而不是简单的拷贝粘贴 
只写一次,则只需要修改一次,代码更容易维护与扩展 
原则上超过3行的重复代码应该复用。

代码复用分成下面若干情况,复用的方法一个比一个更抽象,复用的难度也依次增加,下面依次描述。

● 库。 现成的通用数据结构及基于其上的通用算法、标准函数库、通用模板库 均可直接拿来使用,如C++的STL、BOOST库。业务程序员应该集中精力于上层业务模型构建,而不是数据存储、数据访问这些“做轮子”的重复性的、机械的、还很容易出错的基础劳作。C语言这方面的标准库非常欠缺,但非标准库并不缺少,这些库多半是由少数高级程序员开发,且在实践中久经考验,安全性、性能、可靠性方面可以说远超过大部分水平一般的代码,比如原来的TMS框架中就有相当多的基础库,ROS中也有一些。能用的就要直接使用。库复用的难度在于必须按照库规定的使用方式来正确使用,越是复杂的库使用方式与注意事项也就越复杂。

● 完全一样的代码, 直接封装函数予以复用,这是最简单的情况,没有任何难度,必须做到。

● 基本上完全一样的代码,只是其中用到的变量不一样,封装成函数,将不一样的变量作为函数的参数传入。

● 大部分一样的代码,只有其中少数几行代码不一样,封装一样的部分。(1)将不一样的部分封装成接口类型一致的函数,将函数作为函数指针参数传入可复用的函数,一般可以封装成一种接口的代码,我们称作可复用的是一种“代码结构”。(2) 如果不一样的地方差异比较大,无法封装成接口一致的函数,就需要考虑“要复用的东西倒底是什么”,也许你需要复用的不是“代码结构”,而是其他的东西,比如某种获取操作数据的方式。

● 代码模式类似的代码,抽象出相同的“模式”,对模式进行编码,将代码的抽象层次升级。

● 设计复用。 这是最高层次的复用,有现成的可行的优秀的解决方案,就采用之。 如面向对象领域已经蔚成风气的“设计模式”。设计模式即:在某种上下文环境适于采用的设计方案,用专用术语予以表达,减低沟通成本,形成一族“设计语汇”。

代码复用具体代码实例见附录部分example.c

2.细则 
细则中针对目前我在代码中发现的其他一些常见问题进行详细说明

2.1 全局变量 
全局变量的主要问题:

● 非常难对访问进行合法性检查。 全局变量的可见性是“整个工程”,工程中的所有文件可以直接对之进行读写,没有任何保护措施可以加以防范(如值域、下标合法性检查),靠客户代码去作检查是不可靠、代价也是不可容忍的(因为所有调用处都需要检查)的。

● 可扩展性问题。 也许当前的需求只是简单的对值进行读取、设置即可。如果以后需要增加复杂一点的访问逻辑如何增加? 当前用静态数组一次性开足空间直接用下标定位非常方便,但后来内存不够了需要替换成动态分配的链表,如何扩展?

● 可复用性问题。对复杂全局数据对象(链表等)如果没有封装访问接口以复用其访问代码,对其的访问将非常痛苦,可以想见会有通篇的拷贝粘贴代码。

● 并发性问题。全局变量(尤其是访问方式比较复杂的全局数据,如链表等)的直接访问是很难进行并发保护的。

● 语义单薄。通过变量名称所能传递的语义是非常单薄的,通过访问接口函数命令可以提供层次更丰富的业务语义信息。

全局变量的主要“好处”:

全局变量普遍被认可的好处当然是: 
●使用方便, 既然非常方便,没有任何限制,就难免会有使用失误-没有控制的东西最危险。如何确保每次对此变量进行访问时都对值进行了合法性检查? 如何确保对一个复杂全局数据对象(如链表)进行操作时没有其他的并发操作正在进行? 因此,所谓的“好处”也许正是它的害处。

全局变量的替换性方法: 
●定义静态变量,以减少其可见性。注意:可见性和生存期是两码事情。全局变量之所以有害主要在于其“可见性”太广,而非其生存期。局部静态变量生存期和全局变量是一样的。 
●对静态变量封装访问接口。 
●复杂数据对象访问接口可以根据索引、关键字(简单相等逻辑判断,通过 “==” 直接判断)、eqaul函数(复杂逻辑相等判断函数,可按函数指针参数方式传给访问接口)对之进行定位。 
访问接口可以作到什么? 
●前面提到的全局变量问题将全部得到解决。 
原则: 数组、链表等复杂数据类型全局数据必须封装访问接口,作合法性检查。

2.2 平台业务代码中项目宏、业务宏的处理方式 
业务宏开关相关的函数和代码应能尽量独立在某文件中最好,这样查找阅读更方便,项目相关的特色与通用框架部分应尽可能区分开来,各自独立变化,不要纠缠错节在一起。

这样做的附加好处是: 
框架代码的可复用性、可扩展性、可读性得到提高。 
要做到这一点,尤其是优美的用代码作到这一点是对框架代码的灵活性提出了更高的要求,对框架设计提出了更高的要求,设计质量也可因此得到升级。 
框架代码和特性代码都更显眼了,彼此可以各自独立扩展,提高了框架代码的可复用性、可扩展性,因为特性代码已经被抽象语义化了,被赋予了显示框架语义。框架代码的抽象性更好,而不再被各类特性代码填塞,方便从更高级别、更抽象的的语义层次阅读框架代码,业务语义逻辑的可读性得到了提高。 
在面向对象编程领域有成熟的语言特性可以支持这种设计,那就是“派生与多态”,在范型编程(面向类型编程)领域可以使用语言的模板特化萃取机制,对于C语言,使用类似范型的萃取机制更合适(用C语言写面向对象的程序是没问题的,只是要自己实现派生机制,这个不符合目前的代码现状,因此采用后者更合适)。

在C语言里对特性的一种“萃取”实现方式是可以采用利用函数指针向框架注册各个项目的特化接口,通过查找注册接口,很容易查找不同的项目在有区别的业务关注点上不同的处理策略,代码可读性更好,对重复的特性处理代码,也很方便予以复用。 
如:

#if INSTALL_MSAN_SLAVESHELF_SUPPORT 
for(shelfNo = 0; shelfNo < IGMP_MAX_SHELF_NUM; shelfNo ++) 
#endif

可以演变为: 
for(shelfNo = 0; shelfNo < IgmpFuncTbl.GetMaxShelfNo(); shelfNo ++)

IgmpFuncTbl为框架注册函数指针表,其中注册了各类项目、业务的特性处理接口函数,在注册时根据项目宏进行一次性注册,此处,由隐式项目特性处理代码转化为显示框架语义接口的是“GetMaxShelfNo”。

框架代码中可以不再出现项目宏嵌入的项目相关代码,成为纯粹的框架代码。反之亦然。

2.3 标识符命名 
全局对象(全局变量、全局函数, 宏)应包含尽可能丰富的语义信息,以防对全局命名空间的污染(命名冲突)效应,局部对象(静态数据,静态函数)可以弱化语义信息(没有命名冲突问题),因为可以通过文件名信息提供这层附加语义。 
标识符命名现在代码中的现状可谓一片混乱。无规则。随意而为。 
具体命名原则可参考附录部分:标识符命名.ppt,及《代码大全》相应章节。

2.4 循环的使用 
●何时应该使用while,何时应该使用for 
while主要用于循环次数不一定的循环 
for 用户循环次数固定的循环 
对while循环, 应防止“死循环”(可以通过循环最大次数限制,如链表遍历,可通过链表最大理论长度限制遍历次数)。

● 对for 循环, 不应该在循环体内修改循环变量,否则,你需要的可能是一个while循环

● 减少循环的嵌套层次的方法 
运用防卫子句及早break或continue,封装循环体为函数

2.5 使用宏,不要使用魔数 
这是非常关键,非常实用,也非常容易做到的一条规则,但往往容易被忽视没有遵守好。 
原则: 
●一切有语义、会重复出现的数字常量都应该用宏替代 
●宏不应该重名, 不应该被重定义 
●必要时,使用enum组织一组语义相关的宏

注:原文链接 http://www.douban.com/group/topic/11152037/

时间: 2024-11-08 21:49:54

代码重构原则的相关文章

代码重构原则--很重要,供以后参阅

重构目的 相同的代码最好只出现一次 主次方法 主方法 只包含实现完整逻辑的子方法 思维清楚,便于阅读 次方法 实现具体逻辑功能 测试通过后,后续几乎不用维护 重构的步骤 新建一个方法 新建方法 把要抽取的代码,直接复制到新方法中 根据需求调整参数 调整旧代码 注释原代码,给自己一个后悔的机会 调用新方法 测试 优化代码 在原有位置,因为要照顾更多的逻辑,代码有可能是合理的 而抽取之后,因为代码少了,可以检查是否能够优化 分支嵌套多,不仅执行性能会差,而且不易于阅读 测试 修改注释 在开发中,注释

代码重构方向原则指导

http://www.aqee.net/hill-climbing-wonkish/ 重构是一种对软件进行修改的行为,但它并不改变软件的功能特征,而是通过让软件程序更清晰,更简洁和更条理来改进软件的质量.代码重构之于软件,相当于结构修改之于散文.每次人们对如何对代码进行重构的讨论就像是讨论如果对一篇文学作品进行修订一样无休无止.所有人都知道应该根据项目的自身情况来对代码进行重构,而重构是无止境的.莫扎特从来不不对他的作品进行修订,特罗洛普对自己作品修订的恰到好处,大多数作家认为他们俩这样做都是合

代码重构之单一职责原则在实际中使用

单一职责原则:Single Responsibility Principle,以下举例说明我在代码重构方面对单一职责原则的使用. 1.单行代码职责单一 private double GetSubtotalAmount(doube singlePrice,int productCount) { return singlePrice*productCount; } 上文中的return语句行代码职责不单一,将其改为: double subtotalAmount=singlePrice*product

Windows程序代码重构

代码重构:在程序功能实现之后,对代码进行一定规模的整理,使之符合"高内聚.低耦合"的软件设计原则,便于维护和使用. ①用函数封装消息处理代码--对Windows程序窗口函数中的每一个case程序段进行封装以形成一个消息处理函数,而在case中调用这个函数. ②利用数组或链表实现消息映射表进一步实现代码的隔离--因为窗口函数switch-case结构实质上实现的就是一个根据消息标识来查找消息处理代码的功能,故可以用消息映射表和一段查表程序来替代它,表中的每一项可以使用一个函数指针来指向消

代码重构实例之数据聚集

敏捷开发强调,要经常重构代码.在开发过程中,往往是开发和重构交替进行.短暂的重构,可以使得后续的开发维护更加容易.我觉得,代码重构可以分为逻辑重构和数据结构重构.数据结构的重构往往需要对代码进行多处改动:但是,数据结构的重构也可以为后续的开发维护带来更大的便利.这里就是一个数据结构重构的例子. 这是以前的一次代码重构经历,今天想起了,就记下来,帮助自己记忆.当然,既然是重构,总得承认自己写的第一版丑陋的代码. 为了方便描述,采用javascript来进行说明. 故事是这样的.刚开始,任务是画一些

CSS代码重构

CSS代码重构的目的 我们写CSS代码时,不仅仅只是完成页面设计的效果,还应该让CSS代码易于管理,维护.我们对CSS代码重构主要有两个目的:1.提高代码性能2.提高代码的可维护性 提高代码性能 提高CSS代码性能主要有两个点:1.提高页面的加载性能提高页面的加载性能,简单说就是减小CSS文件的大小,提高页面的加载速度,尽可以的利用http缓存2.提高CSS代码性能不同的CSS代码,浏览器对其解析的速度也是不一样的,如何提高浏览器解析CSS代码的速度也是我们要考虑的 提高代码的可维护性 提高CS

CSS代码重构与优化之路

写CSS的同学们往往会体会到,随着项目规模的增加,项目中的CSS代码也会越来越多,如果没有及时对CSS代码进行维护,CSS代码不断会越来越多.CSS代码交错复杂,像一张庞大的蜘蛛网分布在网站的各个位置,你不知道修改这行代码会有什么影响,所以如果有修改或增加新功能时,开发人员往往不敢去删除旧的冗余的代码,而保险地增加新代码,最终的坏处就是项目中的CSS会越来越多,最终陷入无底洞. CSS代码重构的目的 我们写CSS代码时,不仅仅只是完成页面设计的效果,还应该让CSS代码易于管理,维护.我们对CSS

我的代码重构经验

说明 本文在<MDU某产品OMCI模块代码质量现状分析>一文的基础上,分享作者对该模块进行重构时的实践经验. 具体的重构手段可参考<代码大全2>或<重构:改善既有代码的设计>,本文不再班门弄斧,而侧重重构时一些粗浅的“方法论”,旨在提高重构效率. 作者未采用重量级的重构工具,仅用到Source Insight的”Smart Rename”功能.也未使用CUnit等单元测试工具,而是通过在线调测和自动化测试保证代码的正确性. 一 背景 MDU系列产品从他处接手,OMCI模

关于代码重构

最近几天实习做需求,很多都是代码优化,代码重构方面的,有必要阅读相关的文章或书籍,整理整理形成点小方法论指导受用. 相关不错的文章:代码重构之道 代码重构方向原则指导 重构代码的7个阶段 书籍——<重构:改善既有代码的设计> 可以在哪些方面对代码进行重构: 1.重命名:对类,接口,方法,属性等重命名,以使得更易理解 2.抽取代码:将方法内的一段代码抽取为另一个方法,以使得该段代码可以被其他方法调用,这是重构中很重要很常用的,此举可以极大的精炼代码,减少方法的代码行数 3.封装字段:将类的某个字