这些天写程序被老大教训古怪,没见过这样写的。我其实是一个很讲究这些的人,只可惜我的个人编程风格来源于各种我接触的代码,书,因此缺乏一个系统的规范。
国内,我们这一行当,最牛逼的公司应该就是 华为了,而且他们还有一套流传出来的编程规范
网上各种无节操的下载名字乱七八糟,最后下载到的你会发现大多数是一样的。
当然了,不同语言,比如 java python当然是不一样的咯,不过,那些写着什么 C C++的都是一样的,基本上就是那个59页的总则。
这些天手头的活儿是焊接板子,而且是两个人搭配,所以有些空档我就悄悄看这个文档,因为前车之鉴,这次我不再着急一次看完,我决定看腻了就停下,一天大概看个十几页就差不多了,然后第二天再巩固一下。
到周五就看完了,于是需要做个笔记了。
首先我们先看一下目录,然后从这里出发总结一下全文内容
总共12小节
1排版 也就是我们说的代码外观
2注释
3标识符命令 它不写 变量 函数 我就觉得它特别有文化
4可读性
5变量 结构
6 函数 过程
7可测性
8程序效率
9质量保证
10代码编辑 编译 审查
11代码测试 维护
12宏
从内容上来看,这些内容有很多属于 听了照着做就好了的事情,你要非说什么很技术的道理没有。唯一的道理就是你要会将心比心,考虑他人,也考虑以后你自己还要维护
比如第一章就是,而另外就是大多数人只要不是太过分偷懒或者叛逆,他写的代码基本上都符合一定的规范
只是华为这个规范说的更细更系统。
所以呢,咱照例还得来一遍,但为了简单,我只从我的角度来做一些说明,因为他提到的规范都比较一般,常见,所以大多数无需多说
一 排版
1 缩进问题,这个问题最纠结的地方不是缩进4个空格还是8个空格
最大的问题在于,究竟是tab缩进,还是 空格缩进。
我们来分析一下双方的优劣:
1.tab 爽,这是最直接的,当然爽,一下tab,如果对于玩惯 命令行 的还更爽呢。
2.tab能对齐,是啊,tab缩进的时候,它会自动对齐,即使你修改代码时删除掉几个字母,它还会自动保持好位置;
3.tab不用数,真好,妈妈再也不担心我数手指都能数错了。
可是呢,tab的行为非常依赖于编辑器,这是最不好的,你也许经常在网站上看到各种排版乱七八糟的代码,相信我,他们的作者绝对不是故意这样恶心你和自己的,只是因为万恶的tab,因为不同的编辑器甚至是网页,它对tab的解释是不一样的,所以空格就可以避免这种问题了。
但是,基本上来说,上面的123tab的优点就是它的缺点。
而更加苦逼的地方在于~~你不惜辛苦,选择空格吧,咱数数还是可以的,可惜你的老大(或者项目组)却喜欢tab,那还有什么好说的呢?tab吧
——我们会遇到各种这种纠结的情形,相信我,我的选择是
——老大,我听你的!
2.语句与空行
一般来说,我们一行只写一条语句;一般来说,我们在几句稍微没点关系的语句之间会加空行,以示逻辑关系的亲疏......
很可惜,我总是遇到不愿意这么干的,并且他们找到的理由可能比你还多~~
其实呢,在这里我是这样想的,第一,我们程序员拿钱不是按代码行数的,所以你再实诚也不用这么老实;
第二,空行就是一个enter是会少根胳膊还是少腿啊!......你想一次看多几行?你以为你丫......算了,要斯文,要斯文。
我说的当然没什么分量,但既然华为都这么说了,大家就从了吧~~
3.长语句的断行
其实各种长语句都应该断行,我们应该知道,unix下有个很美好的编程风格,那就是每一行的最大长度不超过80个字符。
因此,我实在是十分喜欢iar那里有一个灰白的边框。
至于怎么断,那当然就是 从 语句之间 各部分的 优先级 以及 逻辑关系了,只要你不写成这样我想一般别人不会打死你的
if(Architecure || da
ta)
.......
你知道嘛,它其实是想写 if(Architecure || data)
当然,这样写实际上编译不过去的,我这里只是故意恶心了一把~~
本帖最后由 辛昕 于 2014-5-11 16:03 编辑
二、注释
注释同样是一个非常让人大打出手的地方,所以我们要保持冷静。。。。。。。
在这一段里,以下几个地方对我来说是比较新鲜的:
1.源文件和头文件的头注释。
它们的大部分是相同的,唯一本质的区别是 名字叫 description也就是描述的部分。
头文件的 这一部分应该是描述它的接口,和外部文件的关系......简而言之,它应该是为外部引用,调用这个模块的相关函数的说明;
而源文件,它更多的描述这个模块内部各个部分的关系,如何实现,内部依赖。
在华为规范里,源文件的这部分它还有涉及提到对外部依赖的部分,我有点迷糊,不知道出于什么理由,但我想,如果有这种与外部关系的依赖描述,它也必然是与内部实现有关系才有必要!
——因为我突然意识到,这两个描述的差异的根本原因是,它们面对的东西不同,源文件是实现,而头文件是供外部调用的接口。自然也就一个描述倾向于内部,一个倾向与外部。
2.注释尽量少使用缩写——其实任何地方我都不喜欢缩写,因为本来我们就不是英语为母语的人,谁英语比谁好?好的估计都跑国外去了吧?
而且,即使是后来的 标示符命令部分 提到有些缩写法,像缩写掉元音,什么temp写成tmp,flag写成flg,我只想,你就那么想少打那个a或者e?
程序语言和任何语言一样,都是一种沟通交流的媒介,本身就会因为语言的隔阂造成了沟通的多多少少麻烦——别说英文,就算我们都用中文,一样可能又沟通麻烦。
所以,咱就别再那么在乎把四个字母变成三个字母的事情了可以么?
3.这个地方我有点纠结。
不管是函数注释,还是全局变量的注释,我发现它都非常详细,详细到有 函数列表,甚至有函数的调用和被调列表。
更夸张的是,对于变量,它居然还要完整描述其含义,取值范围,取值含义......
其实说实话,我写注释算是比较多的了,多到经常被公司的同事嘲笑我在写作文,然而即便如此,我仍然觉得这样的注释太过完整。
所以,到底写不写,怎么写,这个地方确实要有所取舍。
但是有一点,我觉得是很有启发性的。
描述清楚 重要变量 或者 函数 之间的依赖,层次关系是非常重要的。
但是说实话,我觉得这个东西用文字注释非常乏力,我们应该借助 图 比较好,这大概也就是uml这类东西的意义。
可是,见鬼,也许在oop领域,用这个的人还多点,反正在我这个行当,不管网上的圈子还是现实中的同行,我都极少极少听到他们提到UML。
4.对分支语句做注释
这个注释习惯我过去是没有的。因为我一般是在 switch-case 的每个分支,用宏描述每个字面量,然后我希望通过这个字面量去表达 这个分支 的意图,也就是希望通过这种宏的方式达到自注释。
然而,事实上,这个地方,这份规范说的是对的。
一个分支的情况,很多时候绝对不是一个宏化的状态值 或者 数量值就可以说明的。这个地方加一些必要的注释,的确效果很好,甚至取代设计文档——
是的,可以的话,我希望0设计文档,而把所有文字说明,包括设计意图,都通过代码和注释传达。——因为就我个人而言,我也极不喜欢边看代码边翻什么文档对比看。
三、标识符命令
在这一部分里,绝大多数我很信服,可能我本身就是这样做的,只有两个:
1.我不喜欢缩写,包括temp-tmp flag-flg我都不喜欢,理由前面提了,从四个字母到三个字母,有意思么?
2. 3-5这一条,提到 命名风格要与系统风格相似。
比如它提到了unix系统。
在示例中提到,unix系统用 大小写 或者 纯小写加下划线来划分命名,所以,
Add_User这种是不允许的,而 add_user 或者 AddUser是允许的。
这个地方我想了想,还是相当合理的,回顾了一下我干的事,果然是不够规范,因为我没有特别理解这个细微的地方,我经常是混合的。
这三种命名我都干过。
四、可读性
这一小节内容也不多,同样的,我认为特别需要提及的就是
不要依赖默认的优先级和结合性(这当然是指c/c++)
C的结合性和优先级确实是个很复杂的东西,其实光优先级就复杂了,一旦加上结合性基本上就让人不想活了。
所以我很喜欢 小括号 这个 救火队员。
加一个小括号是不会死人的,但它却会减少 阅读者和书写者 因为 对结合性和优先级 的理解和造诣上的不同而造成的 理解困难。
当然,更加严重的事情是,编码者对这两者的理解如何有点偏差或者和编译器的实现有点差别,那事情就闹大了。
相信我,在这个位置上我吃过的苦头不比越界或者数据类型不匹配吃的苦头少。
所以我永远不吝啬加个小括号。
另外就是当小括号多了,造成层次比较多的时候,可是适当利用 小括号之间的空格来表达层次。
其实,这里还可以利用换行——前提是在语句比较长的时候,还是很有用的。
——和其他地方一样,这种书写上的 散落,是为了更加强化结构的视觉效果。
五、变量 和 结构
这一小节内容多,触动的点也多
1.公共变量
我想这大概是某些 全局变量,当然之所以叫 公共变量,更多的是表达它为多个不同部分所共同使用。它们是造成程序耦合性高的主要原因,所以自然要尽力减少其使用,这不是什么太新鲜的说道。
规范没办法告诉我们的一个问题是: 怎样才能从根本上减少 公共变量 的使用和存在。
2.仔细定义 变量,非常细节,包括 取值范围 与其他函数,变量之间的关系等等——我对它们的问题和前面 第二部分 注释 的时候 是一样的,那就是,我有没必要写得如此详细?
定义的如此仔细,是否会过多地让我拖延?
3 5-4这一条款,提到 向公共变量传递数据时要特别小心,以防止其造成越界
然而这个地方我却有不同的看法
因为我们是不能过多地指望使用者有多遵守规范的。
因此这里我的想法是,我们在定义某个 公共变量的时候,同时提供相关的操作。
大致的做法是 和类的设计类似,不允许其直接赋值,甚至不允许通过函数接口赋值,而是直接让其在操作的函数里完成相关的赋值,当然,对变量的访问是允许的。
这样最大的好处就是,你怎么都没办法破坏我的数据。
这一点和下面的一个条款(但是好像编号出了点问题,22页 1/2 5-1)提到的是类似的。
它要构建的是 只有一个函数可以改写变量数值,然后多个函数允许访问。
而我比它更进了一步,我希望,根本就不提供直接改写数值的函数,一切都隐藏在一系列动作里间接完成。
4.不使用与硬件环境或者软件环境密切相关的数据类型
这个其实是不言而喻的,举个例子,51的 __at__,这是一个非常爽,但是在其他绝大多数MCU里都不可能提供的c操作。
你完全可以想象,如果一个程序里到处都有这个操作,而且还是一些很重要的操作,现在让你移植到stm32上,你是一种什么感受?
5.不要追求面面俱到
其实不管是模块,还是函数,还是变量都一样。
这个对我来说也是一个很大的教训,有一段时间,我特别崇尚 通用函数,通用性越强我越高兴。
甚至直到现在,我还没完全改过来这种意识。
——在构造函数时,我明明已经知道,划分函数,至少有两大类,第一类,通用型,它们成为函数是很不言而喻的。
第二类,专用型,它们之所以成为函数,是为了构成,模块实现的层次感,函数提供了接口,简化了理解,天然为 自上而下 设计而生(我个人感觉)。
但也因为这样,让我有时候在划分函数的时候,始终对这个度把握不好。
函数的长短不是一个主要问题,主要是它的功能是否单一,扇出扇入是否合理,然而这种把握度的艺术史非常考究人的,没足够的时间和阅历是做不好的。
看了版主的笔记,才发现我现在的代码写得非常不规范,明天我也去找来看看
六、 函数与过程(一)
这部分很长,也是这个话题里最重要的部分之一。因此,这部分的笔记很长,分两部分发。
1.精确地实现函数,而不是近似,大概。
我个人觉得,这是非常必要的,很可惜的是我们经常遇到一种困境:我们是否有那么多时间,或者值得花那么多时间在 函数级 上进行精确设计。
对于绝大多数不那么重要,或者简单的函数显然没这种需要。
而对于复杂的函数,重要的函数,往往我们刚开始的时候,是很模糊的。这个时候我们需要一系列下层函数或者一种尝试性的迭代,来慢慢找到一个方案。
这是现实存在的困境,然而更多的时候是,我们最后偷懒了,拼凑或者说迭代出以后,没有重新进行一次精确设计。
我们,对这个问题,我们需要的就是在迭代完成后,再进行一次精确实现——或者精确检查。
2.可重入函数
我需要查一下可重入函数的概念,但在我看来,一个不带任何静态性或者全局性变量的函数就具有可重入性,它的重要意义在于,可在多线程环境下无忧使用。
否则,就需要进行 线程保护,比如利用信息量。
3.对接口参数合法性检查,应由谁完成
由 函数调用者还是接口函数提供者。
默认情况下,是由函数调用者完成。
这是为了防止两种极端情况:两个都没检查或者两个都检查。
前者造成参数具有隐患,后者造成冗余,降低效率。
4.防止将形参当作工作参量
这个分两种情况:
1.按值传递过来的,比如一般的变量,结构体,这种情况下不会造成改写的危险,但是一种情形是,函数中的多个地方都可能要使用这个形参值,如果你直接拿形参来操作,到了后来它已不再是原来传递来的数值了;
2.按地址传递,这个危险是不言而喻的。你有可能覆盖,篡改,越界,什么事都有可能发生。
5.条款6-6中提到的 所谓带 内部“存储器”的函数,这类函数大多是为了在裸机等简单环境下异步执行而构建的函数。
华为编程规范里提地更多的是如何防止函数写成这类函数,并希望它的功能是可预测的——然而,往往我们都有必要的理由去拥有这种函数。
存储器这个提法,让我忽然想起了 数字电路 里的 时序逻辑电路。
如果我以前可以联想到用 组合逻辑 的方法去简化 条件判断,那么,我应该也可以从成熟古老的多的 时序逻辑电路设计 里去寻找设计这种函数的 技巧。
6.调度函数 控制参数 数据参数 的概念
调度函数也就是那种根据传入的信息或者状态值,切向具体执行不同功能的函数的函数,这种函数本身不实现任何具体功能;
控制参数,这个概念在这里才有,指的是那种能影响函数功能的参数,显然,它应该是用来和调度函数的信息或者状态值相区别的。
这一条的意思就是,对于调度函数,你传入的参数不要带有具体的 控制参数,而尽可能只有信息或者状态值——否则,你的调度函数就不再只是一个调度器而已。
六、函数与过程(二)
7.无论是函数返回还是参数传入,都避免不必要的类型转换——对此,我很是赞同,然而,我渐渐发现,有些时候,类型转换可以提供非常灵活的实现方案,比如void*指针的使用。
所以这个地方,非常值得考究——到底用不用,用的话会有什么风险?你如何规避?
我用的方案是,先写一个简单纯粹的测试函数,去把我担心的这种转换所可能有的危险全部测试一次。
或者试图用不同的应用情形(尽可能符合我马上要用或者我曾经用过的),去激励,看是否有难以接受或者处理的危险。
这是一个雷池,然而,并非不可越近,因为,它确实可以提供非常灵便的接口。
8.对于扇入过小的函数,特别是功能不明确或者只被一个函数调用的函数,没有单独封装的价值,可归入调用函数成其一部分。
9.减少函数的递归调用;
第一,造成栈空间的压力,因为调用层次过深;
第二,递归固有的危险,容易变成死循环,除非必要,否则能不用就不用。
10.使函数的 扇入扇出较为合理;
扇入 扇出 分别指 函数被调用 和 调用其他函数的数目,它们可以用来量化一个函数在结构中的地位,过高或者过低都是不合理的层次划分。
11.6-25里提到,根据模块的功能图 数据流图 映射出 函数结构是 常用的方法。
确实如此,我有意无意地曾经画过好几种不同的图,有流程图,有一种看起来像 状态转移图,等等等等——然而,对于这个问题,我突然意识到,真的应该好好去学一下UML了,因为只有这样才知道如何更好地图形化表达思维。
而此前我曾试图阅读此类书籍,可是看得太辛苦,他们不是绑架到了某种工具上,就是一堆的概念,最近我下载到一本台湾人翻译的,martin follwer写的书!
这是一个非常口语化写作的程序大家。
也在我最近的阅读计划里,只是因为这个华为编程规范的存在,而耽搁了。
马上,这个华为编程规范 笔记写完,就暂时到此为止,除去一些专门的论题要展开去做(主要是测试部分,过后会详细提到)。我就要抓紧看完这本书!
12.6-29里提到 对于提供返回值的函数,引用时最好使用返回值。
这句话很简单,却很显然指的是那种返回 操作状态 的函数——如果是函数作用的结果,谁能不用呢?!
这句话背后的意思其实就是,在使用函数的结果时,要注意检查函数的返回状态,妥善处理。
13.还有一个函数内聚的问题。本来不想提,因为这是一个大家潜意识里都知道的问题,但想想还是提提
函数内聚的概念,通俗的说,就是在一个函数里,代码服务于什么,多少个功能。
理想的函数是 只实现一个功能,但现实往往不是那么回事。
所以我们希望函数里的代码,尽可能地只针对更少的功能,更加集中。
内聚度是一个试图定性或者定量描述这种性质的概念。比如说,只实现加法的简单函数,它可能只有一句话,但它的内聚度是最强的。
七、可测性
这一部分说的是 测试(当然, 第十一小节 也是,但两者侧重不同)
关于这一部分,是我特别关心的,因为我对于 如何做测试 这个话题已经就结了有一两年。
不过也许那个时候真的是 没到那个能力和时候未到,而现在,似乎有了一些感觉了,加上这份编程规范的提醒和启发,我想,我已经知道怎么做了。
1.第一条它就提到了,要为项目 准备一套打印函数 和 调测开关。
的确如此,调测开关,是为了保证 带测试的版本(Debug)和 发布版Release是同一个项目
——在任何时候,保持代码的唯一性是最重要的,否则你永远不知道你什么地方没有同步,天天想这个事情是会疯掉的。
调测开关正是拿来干这个事情的。它通常是一个宏,对的,就是大家熟悉的 #ifndef #endif
宏就是这样,简单,功能却如此强大。
而一套打印函数,在任何时候都是一个测试系统,尤其是自动测试系统的基础
——这也是Unity CUnit CppUnit等测试框架的基础。
对于我们最头疼的单片机,没有console的情形来说,我想,(我自己也是,不必纠结了),就是一个串口,加个电脑就好了。
当然了,有时我们会吹毛求疵地想,如果只有一个串口或者串口都被用了呢——或者更严肃的问题。如果我要测试串口呢?
这个时候,我希望可以,“给我两个IO,我还你一个console”,通过外部增加廉价通信桥(比如就是一个廉价stm8s003 i2c/spi通信转串口 就可以了)。
当然,这些都只是提供一个基本的console通信管道,并不在这个帖子的话题里,就此打住。
2.测试打印出来的信息要有统一格式,比如模块名,乃至源文件名,行数——我觉得,这个,可能它非常务实地要用于定位问题,这是非常必要的,特别是统一打印格式。当然了,这和 James Grenning Myer的书里提到的不太一样,这自然是因为 两者关注的点,和 对 测试的理解不太一样所致,但应该说,对于我现在来说,这份规范提到的这种更加适合我,更加能回收价值。
3.测试应该作为一个独立的子模块(包含在被测模块里),但两者应该独立分隔开来。
这个建议非常好,因为最后测试是要剥落出来的。
这也就是意味着我们不能像平时那样乱printf或者值标志位那样,入侵函数内部去测试函数。
这样除了可以收到前面提到的方便剥落好处以后,它还有另外的好处,那就是——
这迫使你写出 可测性 好,接口设计优良的程序
——因为,没有这样的程序,你是不可能实现不入侵函数内部就可以进行充分测试的。
4.关于断言
断言这一部分,我一直以来对它缺乏足够认识,包括在琢磨stm库的断言——但说实话我觉得它的设计很不合理。
以及看pjp的C标准库时提到的,我的认识都很模糊——这和我分不清各类测试是一样的。
而现在,慢慢地,我加深了,加之这份规范的启发。不过这足以作为一个单独的话题,所以这里提一下,下一楼全部用来描述 断言。
关于断言(一)
1.什么断言?
简单地说,断言,专业术语叫 assert,意思就是,在程序中加上一些条件判断,通过打印结果等形式,来证明我们对代码,程序的某些假设 成立与否 的 手段。
举个例子,你想确认你的一个状态变量,在进入中断以后的确变成了 TRUE。你怎么断言呢?
你可以写到 if(ASSERT(status == TRUE))
.....
ASSERT当然是你根据系统和你的反馈方式自己实现的一个宏或者函数。
ASSERT里面做的事情很可能是
printf("status is true!");
至于printf你是通过串口发送还是其他形式,那就是实现这一套assert和测试的辅助工作了。
在这里,我们需要区分一个很重要的概念。
在写程序的时候,我们需要做相当的 防范代码,什么叫防范代码呢?
仍然举个例子。
你在写一个FLASH读写函数,你会把从某个位置读出来的一个数据当做一个状态,根据它选择是重新初始化FLASH还是不初始化,直接进入执行函数。
但是,读FLASH这个动作本身是有可能出现错误的,你可能是FLASH芯片(或者外设)坏了,或者读写操作出现错误等等等等。
这种错误是有可能发生的,并且客观存在的。
这个时候,你不该用 assert断言去处理。
你应该专门写一段合适的处理手段,比如说,根据某种校验方式,或者检查其取值范围,是否正常,否则不予以判断。
总结一下,简单地说,断言,断言其实是一个意义非常精确的翻译,它就是“我敢断言说你说的是对的”里那个“断言”的意思,我们对程序,对系统,对某些操作有我们的假设,这些假设影响了我们调试时的判断或者直接影响了我们原先的实现方式——但它们往往不会永远如我们所假设,这个时候,通过断言这种手段来矫正我们的错误假设,在许多时候,特别是因为假设不符合的时候(这种时候非常多),对调试的帮助极其大。
断言通常在debug时使用,一旦我们完成调试,它们也就没有太大意义,应该关闭掉,不影响程序正常运行。
而 防范性的代码 则不同,因为客观现实的原因,任何操作都有可能出现异常,硬件可能坏了,某些操作可能出错了,为了让我们的代码更加强健,不易被这些异常情况摧毁了,我们需要妥善处理。和断言相比,它不属于我们的假设,它是绝对存在的。因此,它们是会和程序长期运行同时存在的,它们是不可或缺的。
关于断言(二)
前面啰啰嗦嗦说了一通,只希望你能了解 断言 是什么东西——因为说实话,我也很久没真正理解这个东西是什么,我个人觉得 华为编程规范 这一部分的总结 已经非常完整,所以同样的额,我只提及其中我觉得比较新鲜的部分——这个规范写的很细,有很多部分,我个人觉得是属于 举一反三的,无需多次反复提及:
1.断言里的动作 应该是这样 动作的:
1-1 在你打开 断言开关 的前提下,只有断言失败(也就是你的假设不符合)的时候,它才会执行断言动作(比如打印一条信息告诉你)。
假设你没有 打开 断言开关——通常是一个宏 的话,即使断言失败,也不会作出任何动作。
——所以换句话说,这个断言开关,正是用来切换debug和release版本用的——正式release版本,一般不允许,也没有打开断言的意义。
2.用断言来判断参数的合法性。
这个,应该说最好的例子,就是stm库函数的做法,它就是这么做的。
然而,我个人认为它的做法太过粗暴简单,大多数时候,它给的例程(库本身不提供断言动作机制,这是合理的)。它的例程的操作就是直接给我while,试问这个时候,我怎么知道是哪个参数断言了呢?
当然它这么做是迫于它不能强求或者假设每个人都实现了一个串口或者LED等表现断言的效果。
对此,我认为,没啥好纠结的,实现一个串口console吧。
3.调测开关分不同级别和类型
这个说法其实同样适用于 防范性代码。
当程序复杂的时候,我们不可能每次都对程序做全面调试,为了简化测试或者更加集中精力进行单元测试,我们需要关闭无关断言——断言太多了,影响了调试效率。
所以我们需要对它分级,分类。在需要的时候只打开需要的部分。
额!给个赞吧!现在很多课程要结课,我可以找个理由说没什么时间看吗?
八、程序效率
1.程序效率分 全局效率 局部效率 时间效率 空间效率,概念无需解释。
不过它区分了一个 全局 和 局部 效率。只是不知道如何衡量?
2.提高空间效率的根本手段,改进系统数据结构的划分和组织。
3.循环体内工作量最小化——这可以扩展开来说,就是越是频繁,或者越多调用的越要精简,越要工作量最小化。
4.与第三条对应的也就是,那些不频繁调用的函数不值得花太多时间去优化。
5.对于调用频繁或者要求极高的函数要仔细的构造,乃至直接用汇编写,但是这种混合编程,需要对硬件系统较为熟悉才可以用,它是存在较大风险的。
5.在多重循环中,应将最忙的循环放在最内层——其实道理很简单,因为最内层循环次数最多。
那什么叫最忙的循环呢?
规范举了个很典型的例子。一个二维数组,a[5][100];
第一,它没定义成a[100][5],这本身就是个效率更高的行为——当然大多数时候这种定义与具体的变量含义有关系。
其次,对这个数组的遍历,如果你是先0到5,再0到100,和 先0到100再0到5,那效果是差很远的。
都是500次,关键是 前面是5×100 后边是100×5,我们知道每个循环本身都有一些附带的开销。
100乘5的时候,里外两层循环的附加开销都进行了100次,而5乘100则是5次;
6.用浮点乘法代替浮点除法,浮点运算要占用较多CPU资源,尤其是除法。
资源不资源倒没研究,不过运算时间倒是很明显的,以经典的51为例,乘法指令是6个机器周期,而除法指令是,,,40个,谢谢!
九、质量保证
1.只引用属于自己的存储空间。
它说的比较简单,只要模块封装的较好,就不会发生这种事。
这当然也是事实,可是~~~
具体的手法,其实在其他部分中有所具体说明,比如说 前面提到的对 公共变量或者全局变量,其实还有静态变量,都要做一些限制,否则死定了。
2.内存空间的申请,释放问题
这是属于程序员可以自行分配和释放的,那么必然是 堆上的空间,也就是malloc free函数可以操作的空间。
这里的一个概念就是,申请了要记得释放,才退出函数(或者相应的代码单元),另外就是使用以前要检查内存空间是否已经被释放。
话说回来,这也是我当初琢磨那个c-style string/vector的原因,因为有很多情形,要立即释放掉申请的内存是比较麻烦和困难的,而且总要自己去琢磨太痛苦了。
这个时候如果是使用c++就好得多了——所以,在单片机上尝试c++编程还是很值得试试的,虽然大家都说,它太耗内存和空间——问题是,内存不值钱了,亲!
试试吧,小马过河!
3.认真的处理程序所能遇到的各种出错情况。
这个,是个难题!
4.系统启动之时,要妥善初始化,防止误用未初始化的值,对,特别是 右值呢!
启动时,同理要干好的事情是检查数据的一致性和有效性。
5.不要随意改动程序的接口,这是绝对必需的,否则就造成混乱,新旧代码不兼容。
6.要充分了解系统的接口,再使用其功能
我觉得这个特别适合在使用第三方库的时候,要记住,c标准库都是要测试的,更别说其他,本规范里也提到“不要过分相信第三方库的正确性”。
7.对关键的算法,要对它的效率,性能进行测试确认。
8.防止表达式产生 上溢,下溢;
9.留心系统的空间资源,如代码大小,数据大小,堆栈空间大小等,是否超出系统限制;
10.系统应具有一定的容错能力,能自动处理一些错误事件;
11.高危操作要仔细考虑,比如写硬盘,删除数据,提高系统的安全性;
为楼主手工点赞!
十、代码编辑 编译 审查
1.打开编译所有 警告开关 编译程序,这个,绝对可以有。尤其鄙视那些轻易无视警告的人!!
2.使用相同的编辑器,同一项目组内——其实同一项目组内,最好什么都统一,比如偶们公司,虽然我极其不喜欢mdk这个编译器而是喜欢iar.还有jlink,NND。
3.小心使用编辑器提供的快拷贝功能,太先进了,还没用过怎么办!
4.软件系统目录?没听说过~~
5.这个新鲜,如果某些语句产生了编译,但你认为他是对的,你可以通过一些手段去掉警告,比如,borland c里,它可以这么干 #pragma warn 长见识了!~~
6.pc-lint 这,这是个悲伤的故事~我当时被st库报出的大堆错误直接吓坏了~~
7.使用软件工具进行代码审查,如LogicSCOPE,这个不认识,看看~