[讨论] 【暑假酷学】写一个程序(工程项目)该怎么一步步去做?

辛昕   2012-7-27 00:48 楼主

    昨晚上写这个帖子的时候写的有点随意,呵呵,就像一个没有做好 规划 和 设计的程序。这里,请允许我补充一些说明。
    尽管刚开始发的这些帖子看起来有点脱离题目,但那只是因为我为当前的工作状态所影响,所以,当我想到这个题目的时候,我首先想到的就是如何 整理/注释 一个程序。
   实际上,对于这个题目而言,这当然只是其中一部分,而且似乎是属于最后一阶段的工作,正如我现在所做的一样。
   但事实上,在这个帖子里,你也许会听到我不厌其烦地强调一点:包括 规划 ,抽离子函数,注释/整理 在内,这些内容其实应该放在 整个项目启动之初就全盘考虑好(实际上,是不可能全盘考虑到,所以,应该说是,在启动之初就应该被认真考虑过。)
   翻开一些讲述软件工程和项目管理的书籍和文章,我们往往会听到一些似乎很大很空的步骤,诸如:
   市场调研,需求分析,系统规划,概要设计,细化模块 等等等等。
   是的,它们的确是空话,因为,如果没有经历过一个完整的比较长时间的规模适度的项目开发过程,你是根本体会不到 这些工作存在的必要性和合理性,也不会发出感叹:如果我真的在项目启动之初,或者说,在项目进行过程中,不断地迭代进行,将会带来多大的好处,减少后来多少的麻烦和重复性工作——甚至可以说,有些部分,比如说 整理/写注释,当你真的把它放在 最后一阶段去做,那么,最好的结果也只是,对你的维护者(大多数时候,这个维护者就是以后的你自己)有一些好处以外,你无法体会,这些注释在项目进行过程中对于编码者的你自己的好处——因为,你的编码已经完成了。
    也许,特别对于初学者,正如曾经的我来说,要真正经历一个完整的项目开发,并且,你不能只是一个“打杂”的(他们都是这么说的),而是能在开发中起到比较关键作用的,甚至整个项目的直接开发,是有点难度的。
而我认为,假如没有,分享一下,这个过程中,经历过的人所体会到的,以及他所总结出的对于下一个项目所要改进的/增加的一些工作,应该也是有不小用处的。
因为他是用他亲身经历的故事让你理解 那些看起来很空很大的 条条框框 背后存在的合理性。
    这一次,一方面也是总结我这个即将完成的项目,另一方面且当抛砖引玉,希望能够对没有经历过的朋友有一些感性认识,也希望得到更多经验丰富的前辈的更多指点,谢谢!
但这个任务会比简单地 总结应该如何整理/注释 项目程序 更加庞大,所以,它们将在以后的回帖里一点一点补充完善。
    呵呵,中午了,大家吃饭没?我先走一步了


最近的工作,一直都是在整理自己那个已经完成的项目程序。然而,也越来越感到这项看起来比起 debug 要轻松的工作,其实并不轻松,而且富有挑战性。
因为,在你不断地重新整理代码中的 全局变量,抽离子函数,划分模块源文件 时,你不停的在逼问自己一个问题:
经过这番整理,如果把这个项目交给从来没参与一行代码编写的维护者,你写的注释足够吗?
当有新的bug反馈回来,他能马上定位到具体的模块甚至 函数群 时,然后快速分析出 问题大概出在哪个点上,找到需要彻查出问题的全局标志.......
当你被这些问题纠缠上的时候,这项工作就变得非常困难,甚至有时候,你停止工作,一个小时对着屏幕发呆,在纸上乱写乱画,似乎有很多不错的方案,但似乎又不行,这个时候,你就会想起那个著名的观点:

 

提示词:如果您需要查看本帖隐藏内容,请登录或者注册
强者为尊,弱者,死无葬身之地

回复评论 (45)

一个关键原则:功能比实现重要

这里,首先先明晰一个概念,何为 “实现”

在这里,“实现”被定义为 用代码去达到预期功能,与它非常贴近的一个概念是 函数的实现。
函数的实现,就是我们常说的函数体(区分与 函数声明),实际上, 实现 的定义应该稍大于 函数的实现,但是,用 函数的实现 去进一步解释 “实现” 这个在这种语境下的精确定义 是有助于理解的。

举个很简单的例子:
我要在数码管上显示1到1000这些数字。
我们命名这个函数为:   ShowOnSegment(short value);

这是我要求的功能,至于它具体怎么实现,我可以细分为:
1.先把这个数字拆成每个位的数码;
   命名为: Split_Digit(short value);
2.通过查表的方法,把每个数码显示在对应位置的数码管上;
   命名为: ShowSingleDigit(U8 digit,U8 SegmentPosition);

当然,功能本身是可以不断细分的,比如,我最初说的功能,它就可以往下细分成 两个子功能;
再往下,可以更具体:
以第一个功能为例
1.要拆开一个数字,方法是 通过 模10,获得最低位,或者除10,去掉最低位,依次不断重复,直到这个数字为零(所有有效位都取完了)。
  这一部分内容,实际上就是 Split_Digit(short value);的具体实现
2.但这个时候,这些数码的位置是相反的,比如说
  324 会拆成 ‘4’,‘2’,‘3’;
所以第二步,我们要把它顺序再反回来。
   命名为: SwapArray();

到了这一步,已经可以不往下再细分了,因为这两个操作已经可以直接 转化成代码了。

那么,反过来呢,我们如何来说明,注释这个小小的函数群呢?

首先,我们要清楚一点的是,在读代码的人看来,他绝对不关心你是怎么做的,他更关心的是,你这一个函数群,到底是要实现什么功能?

所以,他最希望的是,你告诉他,嗯,这一个函数群实现的功能就是,在数码管上显示数字。
因为,他很可能就是遇到了数码管显示出现故障了,所以他很快地找到那个名叫 ShowOnSegment(short value); 的函数。他无需去全部阅读你的代码,就可以首先判断,那个编码的王八蛋,一定是在这一小块里犯了什么低级错误,从而他不用再看任何代码,快速定位。

接下来,他要知道,你都是怎么实现这个具体功能,然后他看到你的
Split_Digit(short value);
假设他收到的报告是  : 无法显示 超过 500以上的数值,或者说 显示的数据与预期的不一致,他就会想,嗯,不用看了,这个家伙一定是在拆数码的时候搞错了

而如果他收到的报告是 出现 乱码,那么他除了会怀疑你有可能拆数码搞错了,还可能在 查表时出了错,或许是你 数组越界了,或者是两者综合起来......

总之,因为你的功能说明,他不需要首先看代码,去理解你的每一个函数到底是干嘛的,而可以直接锁定问题可能出在哪个点上。

现在,来照应一下题目。
功能比实现重要,因为首先,实现是为功能服务的。
而对于维护者看来说,在可能的情况下,他一点都不想阅读你的代码,他并不想通过修改你的代码去显示他比你更优秀。
他只是想在定位bug时,快速地锁定你实现某个功能的哪个函数,因为,反馈bug的用户,他不可能在愤怒地敲打着桌子时,说,靠!那个狗血的程序员一定是 数组越界了!或者说,天啊,那个家伙不会是没有初始化变量吧!
他只知道,我的天啊,数码管出现乱码了,或者说,我的天啊,明明是30.5RMB,怎么显示9043快!我会赔死的........

所有不参与编码的用户(维护者从某个角度来说,也是相对于你的“用户”),甚至说 用户根本就不想也不能理解 具体(还涉及到不同的编程语言)实现,所以,对他说,你的代码是透明的,他看到的只是功能。
至于你怎么实现,那是你的事情。

所以,写注释,我们永远只会写 功能,而不会用自然语言再重复描述一次你用代码向一个数组写了数据,初始化成某个存储单元为3,而不是0.....

功能,比实现重要
强者为尊,弱者,死无葬身之地
点赞  2012-7-27 01:16
一点补充:

无论是对于函数,还是变量,命名时,请尽可能说明通过名字,就能说明这个函数是干什么的,这个变量设置的用意何在。

比如前面那个数码管的例子,它的名字就能比较好地说明其功能。

这也叫 自注释。
能实现自注释的 函数,变量,可以减少额外的注释,简化代码本身。
强者为尊,弱者,死无葬身之地
点赞  2012-7-27 01:26
在很多编程环境都提供调试版debug和发布版release。在实际编程的时候该怎么操作
点赞  2012-7-27 09:17

回复 4楼 zca123 的帖子

像我是这样做的——我也是遵循一些编程规范所要求的那样,比如 华为编程规范中所说:

不要有几个版本,而要在同一个项目中实现 这两种版本,从而避免两处甚至都要修改引起出错。

我不知道 在ide中如何设置这个选项。
所以我们是通过宏来实现的。

简单说就是 条件编译。
因为 release和debug版本一般就是差了一些调试用的测试用例 或者 是输出一些调试信息 的差别,我们都是用这个宏来控制是否编译和调用这一部分的函数。

我想这不是你想要的答案,不过目前我也只会这么做,至于在ide里如何实现这个功能还不会。
有时用一些盗版的缺乏支持的(其实正版的也经常缺乏支持)ide,基本上会发现很多功能用不了或者很难用,可是,看help文档也通用用不了,就更别提写邮件咨询了。

所以,也许,如果是在一个比较好的多人用的ide上,去查这个功能怎么实现,比如iar,我个人觉得会现实一些。
强者为尊,弱者,死无葬身之地
点赞  2012-7-27 10:52
楼主所说似乎都是软件工程中的部分思想  建议大家可以详细阅读下软件工程方面的书 会更加全面 更加深入
点赞  2012-7-28 12:15
看好楼主,我会持续关注的、、、
点赞  2012-7-28 12:25
生活就是油盐酱醋再加一点糖,快活就是一天到晚乐呵呵的忙 =================================== 做一个简单的人,踏实而务实,不沉溺幻想,不庸人自扰
点赞  2012-7-28 13:18

回复 6楼 zjh9678 的帖子

是的,我也打算开始看这方面的书
强者为尊,弱者,死无葬身之地
点赞  2012-7-28 14:29
冲楼主这么认真的态度,就应该加精华,,尽管我看的不大懂,,
点赞  2012-7-28 15:58

回复 10楼 kobe1941 的帖子

这是我的失误,我会努力地写地更加易懂的!!!
强者为尊,弱者,死无葬身之地
点赞  2012-7-28 21:26

回复 10楼 kobe1941 的帖子

我也是希望大家多多交流,互相学习。
强者为尊,弱者,死无葬身之地
点赞  2012-7-28 21:38
我奇怪的是,,是不是走arm这条路算是偏软件部分么?你在研究的感觉很像软件工程的人搞的,,我对这数据结构啥的这些东西很恐惧,,,还有你以后个人发展方向是怎么样的?或者说玩arm的技术人员以后会往哪些方向发展呢?请指教,多谢
点赞  2012-7-29 10:16

回复 13楼 kobe1941 的帖子

呵呵,这个问题,还真被你问倒了,因为已经提出辞职,估计最多到年底会离开现在的公司,下一份工作,我想做 传感器/MEMS这一个行当。

但是,看着杂乱纷纷的 招聘信息,我才发现自己真的是几乎一无所知。

我的确是越来越偏向软件了,我最初的确是从硬件电路出身的,然而,我渐渐发现自己的兴趣更多地集中在 计算机/程序(软件),所以我就越来越往这个方向走。

实际上这跟ARM没什么关系,因为对于ARM我没怎么做,我只是曾经接触过stm32,有一块ARM A8的芯片,我曾经在它上面写过linux下的几个通信程序,但对我来说,它们跟ARM没啥关系。

数据结构,算法这种东西,基本上人人都怕——我认识的网上一些圈内的朋友,有一些,在编程,计算机系统 方面的了解层度也算得上很深,远远不是我能比的。但他们中也有人坦诚,对于 算法这东西,我也看不懂。
而我自己对这些东西也没有太多了解,我想,还是 应用驱动学习吧,但你要用的时候,你怎么都会逼着自己去熟悉,了解,学习的。

实际上ARM只是一种技术而已,我觉得,说到 以后的个人发展方向,这些都不能作为一个重要的依据。
因为ARM 只是一种应用技术,想C一样,你爱用在哪就用在哪,爱怎么用就怎么用,ARM可以做多媒体应用,也可以做机械控制系统,还可以做各种路由器交换器,所以,关键是你自己想要做哪一个具体的细分的应用领域。

这才能决定个人的发展方向。

而我自己,现在也看不懂了。
以前刚毕业时,傻乎乎的一门心思想从事 仪表类,测控,传感器 什么的,当时觉得目标很清晰,但实际工作了,是越工作越糊涂越看不懂了。
因为这些方向都是很大很空的,必须找到更具体的。

然后还要考虑一些其他方面,比如这个行业,在你所在你所想工作的地区,在这个行当里到底有多大的造化,以及以后发展的前景,你希望自己在这个行当中做到哪一个位置,作为什么角色.....
还要能有自己现在能够进入的条件。
比如我擅长的东西是 单片机C编程,所以,这是我进入的跳板,也是我安生立命的根本。

这些事情就很复杂了,一年半载都想不明白,所以我现在也想,先找相关的工作,慢慢做,慢慢看,才能知道怎么回事。

这不,这两天看了好多招聘信息,然后想 看看 MEMS,传感器系统集成一类的。
强者为尊,弱者,死无葬身之地
点赞  2012-7-29 12:45

功能的2个基本载体:变量 和 函数

本来是打算按照 从最开始的 系统规划 和 结构设计 开始说的。
但是发现,无从说起——因为一旦说到那些内容,就无可避免地考虑到 后面才会提到的 模块的划分以及功能的细化。

于是,决定反其道而行之,从逻辑上排在最后的 设计子函数 说起。
反正,正着说难以避免的 显得很空洞。 反过来说,也许会更容易顺势往上说到每一步的必要性。

前面的一个两个帖子,反复强调了  一个 观点: 功能 至上。

现在,我们首先来说说,这个功能指的是啥。

应该说,这里说的这个“功能”包含的是两方面 功能。 我认为可以划分它们:

一种是最终面向用户的 功能;
另一种是实现第一种功能需要的(或者说,转化到具体硬件,机器上的)功能。
而往往,把一个具体的功能  从 第一种个功能 转化到 第二种功能,这个过程就可以视为 自顶向下的设计过程。

举个例子:
呵呵,其实,不用举例子,参考我在 沙发贴 里 的那个数码管 的显示 的过程就是一个最好的例子。

“我要在数码管上显示1到1000这些数字。”
这就是 第一种 功能;

这种功能往往来自客户,因为他们并不知道/也无需考虑 如何实现,他们要的仅仅只是实现他们预期的功能。

而后,我不断一步一步往下细化。
得到 以下几个不同阶段的功能(在下文中,我将会称它们 为 子功能,对应于每一个上一层功能而言。)
1.先把这个数字拆成每个位的数码;
2.通过查表的方法,把每个数码显示在对应位置的数码管上;

按照我的划分,这两步都可以视为 第一层,终端用户功能定义的第一层子功能,并且是它的完全子功能——我的意思是说,如果这一层子功能全部实现,集成起来,就是用户功能的实现了。

类似的,要实现这一层子功能,就要实现它们各自的下一层子功能......

这是一个不断迭代的过程,总会到某一个层,这个迭代过程就到头了,然后一个一个实现,最后反过来,一级一级往上集成,就可以完整实现最高层的功能。

这种思想就是著名的 自顶向下的设计思想。
它利用适当的分层思想,把一个复杂的任务分解成多个简单的子任务。
它基于一种基本的观点:人对事物的关注范围和关注点是有限的,也就是说,你没有办法一次考虑到所有细节。

因此,它一层一层往下走,对于每一层而言,它只关心它的下一层向上的输出,至于再往下如何,它一概不管。
因此,这种 洋葱式结构 的 分层思想,利用屏蔽更具体细节的思想,实现了人们在每一层只需要关注有限的一些问题,但是最从通过 系统集成却可以完成一个可能非常复杂的系统功能。

这个帖子写的有点长了,看来 题目中说的内容只能写到下一个帖子了。

这里先简单提纲挈领一下:

每一层的设计,正是我们通过函数实现的。 “函数”这个概念 和 数学上说的 概念 实际上 毫无差异。
它指的是 对于一个确定的输入,提供一个唯一的确定的输出。

对于 程序中的函数也是如此。
一般来说,每一个函数,都会有一组输入参数,然后都会有一个返回值。

我们可以这样认为,这个输入是来自于这个函数实现的功能的上一层 对 它 的 激励;
而这个函数的返回值,则是对其上层的反馈。

当然,作为特例,有的函数没有输入参数有返回值,也有的 有输入参数没有返回值,还有一种更特别的,没有输入参数也没有返回值。

这一类函数,一般来说,它们要么是通过对全局变量的读写来完成某种过程的记录和控制;要么就是这个函数纯粹只是一个操作过程,然而,它即不需要上层反馈一个结果,也无需对其下层函数提供参数。

比方说:通过延迟实现的延迟函数。
它要的只是一系列纯粹的空操作,从而实现延迟。

这类函数,不带输入参数,也不带返回值的函数,在有些语言中,被称为 过程——应该说,这是一个更为确切的名词。

好了,关于 变量 和 函数 如何作为功能的载体,以及更多我想要在这一部分说的内容里,将在下一贴子里 描述。
强者为尊,弱者,死无葬身之地
点赞  2012-7-29 23:27

功能的2个基本载体:变量 和 函数(2)

前面说了 自顶向下设计,把 最终的终端用户功能 一步一步细化为 可操作的具体功能。

现在说说,具体如何实现。

首先,我们可以这样说:
函数 是 功能的最小实现单位。(函数,英文单词funtion,它也有 功能 的意思,老外想这玩意真的有点意思。)

函数 恰好是 分层思想 的绝佳实现载体。
首先,这个函数内部完成了具体的实现,对于其调用者——也就是我们前面说的 这个函数功能的上一层功能 来说,是完全透明的。
也就是说,它就是个黑盒子,我只是输入你需要的激励,然后得到我需要的响应,反馈,至于你内部如何实现,我不关心也不费劲,那是你的职责。

现在来说第二个载体,为什么我会说 变量 也是 功能的载体 呢?
具体的代码,是通过运算符 和 变量,数值来完成,这个不言而喻,但这个不足以让我认为 变量 和 函数 一样,都是功能的基本载体。
因为,我们所最熟知的一种变量,局部变量,它只是在调用函数时被创建,被读写,在离开函数后就自动销毁,对于这样一个东西,它的所有意义 只局限在那个 函数里——也就是说那个具体的 功能实现内部,所以它对外部不存在太大的太直接的影响,我们是无需对它太过关心的。

但是,实际上,变量是好几种的,最基本的,它按照 作用域 和 生存期 划分为 四大类:
全局变量   (源文件外可外部引用)
静态全局变量 (局限于本源文件)
静态局部变量(作用域 局限于本函数)
局部变量(作用域 生存期 局限于本函数)

前面我们说了,通过自顶向下,我们把一个具体的功能细分成很多层次的功能(有时,比如在华为编程规范里)这种层次,也被称为中间层。
所以,很多时候,我们实现一个过程,是通过一个相关的函数群来完成的。
在函数群之间,如何传递参数 是一个需要仔细考虑的问题。

1.有的参数,通过形参列表传递,有可能很累赘。
    比如说,一个数组,它可能在好几个函数中都用到的,我们且不论它很长的情形——事实上,如果只是长也不要紧,通过按地址传递方式传递,倒也不是太大的事情。
   但是,如果在好几个函数中都要用到,我们就会觉得 烦了,而且——烦的是我们,对于程序运行来说,也是一个不小的负担。主要体现在 堆栈空间 和 函数编译时,声明接口时要分配更多的存储空间来存放这些接口参数。

2. 有时候,由于某些操作过程不可能一次完成(或者需要等待一些时间,才能完成),这种时候,我们不可能让函数运行在某个操作过程中进行无休无止地等待,所以,经常的做法是,把一个操作循环执行,或者循环判断一个事件的标志,从而知道 操作进行到什么状态,是否往下一个动作操作。

这种最后在程序结构上,会以 类似于状态机的思想完成,但是,对于我们用来记录的一些过程相关的数量,标志,就不能单纯用局部变量了,因为它离开某个函数就会销毁。
但是我们又不能为了这个目的,就把所有相关操作都放在一个函数中,更不可能让它一直执行,直到完成才退出。

这不仅违背了我们前面说的 循环判断/操作 的思想,同时,也把我们利用函数把功能自顶向下分解的思想完全丧失,得不偿失。

这个时候,我们就可以动用 另外两类变量,静态变量 或者 全局变量。有了这两个利器,我们可以随心所欲地分离子函数,让整个函数群的分层更加合理,更加符合逻辑,更加易于管理。

关于四类变量的具体语法上的区别,这里不多讲,百度看多几个答案综合一下就差不多。如果你还是不懂,请自己设计简单的程序验证你自己的猜想。

但是,这里,讲利用好四类变量来实现我们的分层和更好地管理我们的函数群以外,我们还要 讲一个更深入的问题:

如何决定使用哪一种变量?这样做是基于一种什么理由?还有没有更优的选择?
这一部分,将放在 这个话题的 3 里。
强者为尊,弱者,死无葬身之地
点赞  2012-7-30 00:43

功能的2个基本载体:变量 和 函数(3)

如何选用要用的 变量类型,这个话题,似乎是个很多余的话题。

如果不需要一直保存变量的结果,就局部呗,如果需要,就全部咯。
这个结论很正确也很简单,然而,它考虑的事情就太少了,它仅仅只是考虑了 全局变量 的 生存期 和作用域 可以跨越整个程序 而已,仅此而已。

实际上我们要考虑的问题很多。

首先,它们带来的开销。
开销,指的是两方面的开销,空间,时间。
具体地说就是,编译出来的程序,在给这些变量分配存储空间时占据的大小 以及 对这些变量读写时花费的时间。

一个基本的事实是。
除了局部变量以外, 其余三类变量它们都是一直固定地从程序编译载入内存时(或者烧写入裸机单片机系统里)存储在某个存储器空间中,它们自始自终占据着那个空间。
与此相比,局部变量则灵活得多,它只在函数被调用时,在堆栈上分配,用完就直接销毁。它们对空间的利用是非常高效的。

另一方面,因为 局部变量就在堆栈上,故而,对它们的存取很直接,要比去固定寻址去操作另外三个变量,都要花费一些计算地址上的花费。
所以,速度上也不及 对局部变量的存取来得快。

故而,一个基本原则是,除非必要,否则,请尽量用 局部变量,少用 静态,全局变量。

另一个方面,关于数据的安全性
这是一个很容易被忽略,并且经常引出大乱子的问题。
前面说了,因为,静态,全局变量被固定的存储在某个存储空间,而且,它们不会被销毁,也不会被重新创建,就这么一直地存在哪个位置。
加上它们在地址上是关联的——只是,除非看map文件,你一般是很难准确猜测到这些变量是按照什么顺序排列的——当然,你也没必要知道。

C语言而言,有一个很大的特点,它允许直接操作内存的地址,比如指针,所以,经常有一类很恐怖的错误,比如 数组越界,比如指针乱指,当然——如果从操作内存地址的角度考虑,你会知道,它们带来麻烦的本质原因实际上是一回事。

与之相比,如果你的变量是生存在堆栈上,这样的事情基本就不会发生了(其实,任何时候,内存地址操作错误都会带来很大的麻烦),只是,相对于 固定存储的静态,全局变量,局部变量引起的乱子,相对没那么危险,也更容易引起注意——我个人认为这是因为,通过函数的返回值反馈,因而有可能更容易更早被发现。
而全局静态变量则不然,它被悄悄的非法篡改,而你可能根本毫无察觉。

3.最后说的是 变量的管理。
   这是一个更容易被忽略的问题。
  因为很多编码者并不会考虑管理这个问题,一来,在编码时,他们没有充分考虑到,有一天,当他们面对一个相对较复杂,涉及较多全局,静态变量的函数群,他们可能对它们可能在这个函数被修改(也就是写),在哪个函数又被访问(也就是读)。
这个时候,他们就会像看到一根麻花一样,所有事情都会拧成一团,想想就会疯掉——相信我,这种恶梦,我是体会过好多次,才会对此印象深刻。
   调试时还是一时,因为,编码者刚完成编码,他可能对这些细节还记忆深刻,不过,如果是日后维护,不管维护者是别人还是编码者本人,我相信,他要么就自己诅咒自己,要么就被别人诅咒。

   因为这样滥用全局变量或者静态变量的行为,造成整个程序非常难理解,让对程序的重构,bug都是极大的障碍
——你可以想象一下,一个全局变量,它在好几个函数都被修改,又被好几个函数中作为条件被访问,有时候,你真的会很头疼这些行为的操作是一个什么顺序——大多数时候,它们是不存在严格先后顺序的。
   这个时候,你想重构?你想修改?——最大可能的是,在你仔细梳理出整个变量的流向以前,你只会把问题越改越遭,然后开始诅咒那个制造出这根麻花藤的混蛋。

     所以为了更好地管理这些变量,我来简单说一下我实际上是怎么做,当然它们仅仅作为参考,因为我还在不断学习这方面的技能。
      以下是一些原则:
     1.能用局部,就用局部,不行的话,我会选择 局部静态变量,再不行,我就是用 (源文件内)静态全局变量,不到万不得已,我是不会使用 全局变量 的。

       这里,我们解释一下 源文件 的概念。
      初学编程时,我一直是在一个源文件里写主函数和各种函数,因为那时候我写过最长的一个程序也就上千行。
       但是事实上你会碰到越来越大的程序,上万行,甚至十万行,百万行都可能。(当然,我目前做的最大的规模也就在1W行左右,还包括注释)。
        所以我们经常要把整个程序分成好几个乃至十几个几十个模块——这些模块不是随便划分的,更不是单纯为了让单个源文件变短 而已。

      模块实际上,是 比 功能更高一级的概念,整个系统往下可以分成几大模块,模块下可以划分相应要完成的功能,或者这样说,模块实际上是一个相对统一的功能集合。

     我们前面已经说了,函数 是 功能的最小执行单位,而从前面的叙述来说,我想,你应该理解为什么我说,变量也是功能的载体。
   因为,它可能记录了该功能的某个过程的执行情况——标志量,逻辑判断,或者计数累积。
   因此,如果我们把相应的功能集合到一个模块里,我们就可以把相关的变量也跟着分类,然后伴随着模块一组一组独立出来。

   这正是模块化思想的基础,也是我们如何通过 控制 变量 的 作用域 和 生存期 来实行 变量的管理 的 基础。


    何为 静态变量?静态变量者,它的生存期,和全局变量一样,是一直存在的,只不过,它的作用域只在其函数内部,当CPU离开该函数时,这个变量实际上跟隐身了一样,它是安全的,因为外部无法 改写它,也无法访问它。(当然,有一个例外情况,那就是数组越界,指针乱指,这种情况,至少在C里,说真的,大罗神仙也救不了你。不管是 哪一种变量,遇到直接操作地址,都难逃厄运,所以,一定要特别小心这个问题。)

    全局变量实际上已经说完了,它有点像全能全知的上帝,任何地方都可以访问它,任何地方都可以修改它——不过,呵呵,这看起来很潇洒很帅很酷毙的特权,其实有时候,也是 一种 苦逼。

     想象一下,现在有一个 全局变量式 的仁兄,整个公司里每天都有人要从他这里咨询情况(好比访问全局变量,一般用以判断或者提取数据进行运算),每天也要有人不断告诉他新的变化。
    这个时候,悲剧往往就发生了。
   我们假设一个情形,公司老总正在等一个很重要的信息(比如,某场战争的胜败),而这个信息是某个在公司外部做跑腿的哥们来告诉他的。
    现在我们假设,一般情况下,这个跑腿的哥们在每个整点就告诉他当前的最新情况,而,每个十五分钟里,也就是一刻钟后,老总就来他这询问最新情况。
    在一般情况下,是没有任何问题的,但事实上,这个世界上从来就没有什么可以一直遵照这个 一般情况。于是有可能出现这样的情况:
    跑腿的哥们因为事在路上耽搁了,他直到二十分的时候,才跑过来汇报最新情况,很悲哀的是,老总在五分钟以前已经咨询过了,老总下一次咨询要在55分钟后,更悲哀的是,这次跑腿的带来的是一个不一样的情况,可能,战争的结果已经出来了,英国赢了,而法国输了。之前说的 英国节节败退的信息,实际上是 那个操持英国国券的混蛋放出的假信息。
    而老总在5分钟之前,听到的结果却与此相反,于是,老总做出了错误的决策,他把手中持有的所有英国国券全部抛售了,然后,55分钟后他必然就会暴跳如雷,自己被套走了不少钱......

     这个有点生动形象的故事,据称是 现在被称为 第六帝国 的 金钱帝国 当年干的一票很大的买卖的真实案例。

     不管它真实与否,我只想告诉你一个事实,一个任何时间,任何区域都能访问和修改的 全局变量,假如它没有被妥善赋予 修改 和 访问 的权利,那么,在程序运行中,它绝对会是悬在用户和开发者,特别是维护者 心中的一把剑——因为谁也不知道它什么时候就会惹出 类似 前面那个故事中说到的 极其不幸的 事故。

     所以,原则之二:
     一定要妥善安排好,一个变量什么时候被修改,什么时候被访问,会不会有冲突,假如发生冲突,会有什么后果?这个后果能够通过什么措施来避免——当然,最好的办法就是,干脆不要用它!

      呵呵,写的有点乱了,不好意思,还写了个故事,很晚了,就写到这里吧,过后看来要好好整理。
强者为尊,弱者,死无葬身之地
点赞  2012-7-30 01:26

回复 沙发 辛昕 的帖子

在KEIL的TARGET的OPTION里可以直接定义宏,而不是在源文件里定义宏,这样直接条件编译出不同的版本。
能力越大,责任越大;知道越多,未知更多
点赞  2012-7-30 08:51
顶 一个先
点赞  2012-7-30 21:29
谢谢了
点赞  2012-7-31 00:16
123下一页
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复