c++

来源:互联网 发布:河南中原云大数据集团 编辑:程序博客网 时间:2024/06/10 22:59
有人认为后期的C++趋向学院派风格,走进了一味追求技术和难度的误区,
逐渐脱离主流的Programmers.
  有人认为C++由于复杂度导致在现代软件工程中的地盘不断缩水.
  也有人认为,C++无任对特定领域应用还是研究来说,都足够的优秀.

一、面对Object-Pascal、Java、C#等语言,还需要C++ ?

  首先,就方法学来说,不可否认,OO方法学本身具有构造的系统随着工程的进行复杂
度膨胀速度惊人.而且,对于上点规模的工程,为了构建OO系统,在OOA、OOD阶段,设计
者需要极高的水准.而以OOP为一典范亦作为OOP代表的C++在语法语义的设计上明显采用为了功能而不惜增加设计复杂度的策略.然而这一切的复杂,自然带来了好处,包括系统的可扩展性、可重用性等.这里好象存在一层很微妙的关系,上规模的系统为了可扩展性、可重用性等优点选择OO方法学,然而在OOA、OOD阶段就需要极大的投入(也许在00方法学中,A和D在软件工程中的地位体现的更明显吧~).

  这个世界关于语言之间的讨论可能时刻进行着,个人一直认为:
1.首先每种语言有自己的适用领域,就应用而言,没有必要将语言勉强的拿到一起比较.
  构建企业信息系统,自然会选择Java、dotNET或其它的方案(事实上,这种系统一般应
  该多种语言协同开发,以获得部件性能最佳).可能多种语言同适合某个项目,那么具体就自己裁决了.譬如做一个信息管理系统,选择C++环境的绝对很少,PB或Delphi、VB都是不错的选择.在初学习阶段"因爱而选(更多的具有偶然性或者与身处环境有关)"、对于开发者基本是"因用而选".在成熟后,"因用而学"我感觉是根本也是最实际的指导原则.

2.关于语言复杂度的问题:语言是工具,是不需要也不能太复杂的.我一直支持语言应该朝专业化(这里指针对义务和特定应用场合)和简单化方向发展.这才是语言的本质之所在.业务是软件实施的根本.对于软件开发着来说很多情况下,业务是最难搞定的,或者说,用软件来真实细致安全的模拟业务是很困难的.前几天,学籍管理科的老师还跟我说,他们的一个学籍管理软件让两个研究生不断的完善,三年才算[完全符合他的业务要求].如果你只是耳闻某某语言何等的复杂、难学而学之,那么可能你错了~就个人而言,为了技术而技术是不可取的:)

3.然而这个世界是现实的,不如你想象的那样.并不是所有领域都有简单、强大、贴近业务 的语言.在这个层次上说,对于献身企业级信息应用系统开发者,Java、C#、Object-Pascal等的确是福音~~然而在系统软件和其它对性能、控制程度要求较高的如
 工控系统、高性能计算,实时系统,军用软件等领域可就缺不了C++(C)(不过这些领域  明显的专业知识占绝对部分的重量).可见,紧从语言上说,C++(C)依然占据着一片天地. 严格来讲,C是作为C++的一竞争对手出现的(不管这些了:)).


二、C++是否真的走进了追求技术的误区
 
  自从,GP思想在C++上的第一个作品STL正式纳入标准库,关于C++走进技术误区的流言便没有停止过,Boost、作为GP领军人物之一的Andrei Alexandrescu提倡并实践着(Loki)的编译时编程、将设计模式思想引入库设计,使得流言快被提上日程讨论了:).
然而,GP真的只是技术吗?我一直认为,就OO来说,本人曾经作过这样的思考:
-----------------------------------------------------------------------------
真正的对象应该至少能够具有:自主的适应环境(维护足够的"意识")
                            1.自发行为的意识更强
                            2.反应能力更强
                            3.对外有足够的独立性
现在的OO,更像是建立在PO上的,不过是对象代替了函数.
但事实上,无论对象能力多高,过程表现绝对少不了.

Agent :我想是表达和反应能力最强的.
COM : 比Object-Based手法构造出的对象表达、反应、适应能力高很多.
          不过There is no magic.还不过是一般的技巧构造出来的,看透了什么都没有了:)
GP :不能将它看作无谓的技术追求,[从我们谈论的这个节点看来,它的实质我认为[至
        少]是]:提高了构造的对象的适应能力(不是自发行为能力)的一种手段.
     Andrei Alexandrescu的《Generic Programming :类型的else-if-then机制》
     这篇文章可作为一精彩的证明.
-----------------------------------------------------------------------------

三、C++怎么学?
 
  首先声明:本人对之也只能说略之一二(C++太博大了:))
  孟岩先生曾经提过"C++需要自由的心",我要说"C++需要自由的心和手",我敢肯定我想的自由和孟岩先生的自由是不同的.
因为我的自由就个人诠释是 :"用自己的思路来自由的写验证性的usecase"
  就个人的学习心得而言 :

1.在你学习C++的过程中,你首先需要扎实的实践一本C++基础教程,这个教程不在深而在全.使你能够全览之.最好结合基本数据结构来练习.不要整天Hello World~~Hello MM的.

2.再下来你需要《(More)Effective C++》,它使你可以对C++也多了份思考,也了解到一些技巧和误区,不过,你需要同步实践,不然可能一时你并不能真正掌握这些技巧、避开误区.

3.提高,你需要:(下面的书可能已经讲烂了:))

  《Design Pattern》 :个人感觉,设计模式虽说是一种思维方式,具体实现上,只是
                        对OOP语言的发掘和巧妙组合而已.而且这里组合是主要的,
                        特性是有限的,这本书中有几个模式没用虚特性的?
   C++ Standard Document: 在你用它来做专项研究的时候,就会体会到什么才叫真正的
                                          全而深 (自然指在语法和语义的阐述上).
  《STL源码剖析》 :没有深厚的功底,想来个闭门造车独挑STL源码是不可能的.
                                  不过,这本说也重在关键技术的讲解和引导罢了~~
                                   这里关于GP和STL的名著不少,本人没看过。不做品评。
  《 Inside The C++ Object Model》:最具价值的一本书,没了它,C++永远是个迷,哪怕
                                    你浸淫之N载~~
  《Moden In C++ Design》 :这里的很多思路是你自己的思维很难接触到的~~:)
                            我不得不佩服Andrei Alexandrescu.

  市面上其它的C++书籍可牛车载,我感觉除了《The Design And Evolution Of C++》是异品,值得一读.其它的不建议花太多的时间,哪怕是Bjarne Stroustrup、Stanley B.Lippman等的作品.自然,你有时间读更好,反正我现在有点后悔,当初只顾多,不顾深读,反复读.经典的书不在本数多,在于每本读的遍数多.一经验之谈,BBS上经常有人,在介绍COM技术书籍时,想也不想的指出,入门级<<Inside The COM>>.是这样的吗?我想,正如Dale Rogerson所说,将这本书完全看懂,你就是COM专家了~~书中,作者很多话可能你没有注意到,因为你还不懂之,对之没感觉,一遍翻下来,感觉 哦~~简单~全看了 :) 这些书,跟国内的很多书籍最大的不同就是
国内书籍的作者写的出,可能自己还不懂:)Copy什么资料上的:)??

4.浸淫一门语言本身的语法语义再久,你不一定能够深入它的精妙之处.
  你需要学习应用这门语言的实作品(技术),你可以研究一些FrameWork或是一些具体的技术 如CORBA、COM等.就个人而已,有心接触C++十个月左右,对于Virtual的认识有过几次较大的的改变.在《 Inside The C++ Object Model》中算第一次,在《COM本质论》中关于COM对二进制兼容布局需求而用虚机制来解决是第二次,到目前为止,使我对virtual流下最深刻印象的是在Automation技术中组件由于环境是否有能力分析virtual结构而导致是否需要分发接口的问题.如果,整天抱着《C++语法语义深入》这样的书,我不知你何时才能真正了解C++很多的特性.
  即使你可以对别人说上一大套,这行啊~那不行啊~~云云~~:)

在这整个的过程中,我喜欢这样对学弟说,你需要经常将C++的各种特性在脑中反复混合酝踉,这也是我强调学基础时,要求教材知识点全的原因.经常的,为了研究某些特性而自由的写、修改、增加UseCase,是我自认为很好的一个习惯.整天记他人的经验之言,不知几个月后还记得几层:)?

   也许上面的叙述是概括了些:)?

   总之,我认为学习C++,需要多思考(譬如你想想为什么模板类继承不支持virtual、模板
                                   类继承,基类实例和继承类实例产生的关系是什么样的)、
                      多写usecase、
                      多将一堆特性混合酝晾(譬如模板类
                                  成员签名、多种嵌套模板类成员签名、嵌套类与包裹类生命期
                                 控制等等)
                     
                      要尽早选择应用方向(这点很重要,附加个人观点:大多数人认为理论很
                                         难,可是我要说:应用一样是有难度的:)).

                      将00工程学中的理论适时的来对照自己的行为.
    

后话:
    上面提到,就应用而已,比较语言的是没有什么意义的.然而我想说的是,不敢想象
没有经过C++锤炼的人,可以深入的研究C#(事实上,我一直也不人为C#比C++好学,只是他们的深入点是不同的,冒昧说一句,C++中很多难度是人为制造出来的),就目前的情况来说,还有就是由于C++历史、
社团、资源等因素.

共勉 :          
           因用而择、因爱而择
           学究其深
           勿以善小而不为 :)
 ======================================================================================
做了一阵子项目经理,有点想法,和大家交流一下。

个人觉得项目经理比较重要的是
1、“随时可中断”。必须随时做好准备去应付一些突发的事情。
   因此项目经理不应该给自己分配需要集中精力和较长时间才能完成的事情。
   一旦必须要这么做的时候,尽快安排人接替。
   明确哪些事情自己该做,哪些事情不该做,不该做的事情要坚决扔出去。

   换句话说,项目经理的第一任务是“让自己空下来”。

2、“团队优先”。
   我一般给团队中人分配两件任务,一件是主要的、固定的,有时间限制的,
   一般是需要八到九成时间。
   一件是次要的、灵活的,无时间限制的,但是有目标要求。
   通常后一件会带有一些技术探索意味,属于该成员个人比较感兴趣的部分。

   换句话说,项目经理的第二任务是“让团队中的所有人都有事情做”

3、“控制客户”
   控制客户包括多方面的意思,搞好关系是一层,但如果软件做的实在太差,
   没法用,关系再好都没有用。
   而且关系好是双面的,客户同样可能以关系好为由来要求你这里改改那里
   改改,结果造成项目无限期拖延。尤其是市场/销售人员经常去开空头支票。
   我觉得,应该是对客户领导要搞好关系,告诉他一切没问题,一般这是
   市场/销售人员搞定的
   但是对软件的实际使用者也就是客户的底层人员诉苦。这个就要项目小组
   自行解决了。

   如果是小项目,往往是开发人员直接见用户。前者由项目经理来自行,后
   者直接由软件开发人员进行,挑一个比较会说话的,多往用户那边跑跑,
   聊聊天。当软件投入运行时,这是很有用的。再好的软件也架不住用户
   不断地需求变更,因此最好的办法是“把需求变更扼杀在襁褓之中”。
   如果是大项目,用户方应该会有专门的项目负责人(用户代表),和用户
   代表绑定在一起是非常必要的。但是底层的人员一样要搞定,那就可以有
   专门的支持人员,更需要和用户多接触了。
  
   操作的时候注意多留文档,哪怕是再小的问题也要留档,过一个月整理一
   下,往客户领导(或者用户代表)那边一放。统计一下本月共发现多少问
   题,哪些是新需求,哪些是软件BUG,哪些是用户使用问题,解决了多少,
   有哪些遗留,我方技术人员现场支持多少小时,诸如此类。总之让对方感觉
   我们劳苦功高,服务周到,响应及时。
   来上几次就可以由销售出面谈维护或者二期之类的事了。这样就赚到了。
   即使软件还有很多问题一样可以继续操作。

4、对付领导/老板
  
    要对付领导,首先要学会站在领导角度看问题。

    其实做领导也挺不容易的,听话的手下往往能力不够,能力强的
手下往往不太听话。
    因此做领导的最希望手下能力又强又听话,什么事情交下去就完
全搞定,而且效果很好。
    当然领导也不是笨蛋,也知道需要提供必要的支持,他也不会逼
死你。但是他会认为在他合适的时候来逼你。
    什么都不懂,只会压榨下属的领导往往也没什么前途,当然如果
他是老板的亲戚就例外,如果跟了这种领导就只好认倒霉了。

    在国内,绝大部分是树型管理架构,因此这里主要针对树型管理
架构,矩阵式的可以自行类推。
    作为项目经理,一般接触的主要领导就是部门经理了。如果公司
比较小,也可能接触总监/副总甚至总经理。
    对于项目经理来说,部门经理有两个身份。
    一方面,在整个公司里,部门经理是项目经理的上级,直接掌握
项目经理的奖惩,尤其是奖金和升职(我想绝大多数人都希望钱多职
高吧)。
    另一方面,对于单个项目来说,部门经理又是项目经理的后盾,
提供项目所需要的人力、物力甚至财力,以确保项目的完成。必要的
时候需要请部门经理出面进行较高层次的协调。
    对于软件开发来说,最关键的就是人。尤其是在一个部门中有多
个项目在进行的时候,这个时候一些能力比较强的人肯定是稀缺资源。
大家都要抢的。
    我想做项目经理最痛苦的莫过于自己团队中的主力支援其他小组
去了,结果最后领导还怪你项目没做好。

    那么如何争夺人才呢?有以下要点
    1)自我定位。
       我在部门中,在领导眼里是什么地位?
       我手头目前负责的项目在领导心中是否重要?
       这个项目做好了对部门,对领导有什么好处?
       这个项目做坏了对部门,对领导有什么坏处?
       如果这个项目不是关键性的项目,那么就要有使用较差人员的
   准备,硬要去和关键项目抢资源是绝对没有好处的。
       如果这个项目是关键性的项目,那么就要充分估计风险,狮子
   大开口一些,点名要人。
       一般是否关键性的项目只要看看客户是不是大客户,合同金额
   多不多就行了。

    2)了解他人
       至少要知道公司里面哪些人擅长什么,哪些人什么时候有空,
    这样可以有的放矢。
       这个需要日常和别人搞好关系,而且要了解其他项目的进展情
    况,包括即将启动的项目。

    3)掌握时机
       向领导要人是要看时机的。
       要的太晚不行,一旦给别的小组捷足先登,则除非你的项目特
   别关键,否则就只好拣剩下的了。
       要的太早也不行,因为如果要来了人没事干,那么就会给领导
   造成不好的印象,下次要人就困难了。而且会让领导质疑你的管理
   才能。
       个人建议是项目初期启动的时候大概的估算一下,然后调研一
   段时间以后再稍细的估算一下,到需求比较明确的时候就要出很详
   细的计划,把要多少人都列清楚。
       要有个一两周的提前量,一般情况下都会有一些拖延的。
       然后就催着领导落实这个计划,只要计划没落实,就要一直催
    ,当然催得时候要注意方式方法。
       另外,到了项目后期,也要注意及时的释放人力,不要占着人
   一直用。这样领导就会对你留下个好印象。
       如果有别的项目差不多时间启动的话,一定要比对方快那么一
   点点,即使只是一天。


    4)方式方法
       前面三点都是战略性的,这一点就是战术性的。大多数情况下
   项目经理去要人,都很难得到完全满意的结果。
       个人认为有以下几点需要注意:
       a、准备充分。至少要有一份比较详细的文档,对现阶段进行
          总结,并给出大致的计划,然后提出人员要求。
       b、人员质量要求。对人员的质量应该有所要求,一个高手和
          一个新手差别极大。如果已经看中部门中某个人了,则可
          以按照那个人的水准来套,引诱领导往那个人的方向去考
          虑。只要领导说一声,“那xxx你觉得怎么样?”立刻就
          说“好啊好啊”,领导再想反口也来不及了。
       c、讨价还价。其实向领导要资源也就是和领导谈判的过程,
          本质上和小菜场讨价还价差不多。项目经理是卖方,领导
          是买方。你要让领导相信提供资源可以买到项目的成功。
          但是领导会在多个方面上进行挑剔,甚至可能召集其他人
          来一起评审你的计划。一般情况下项目经理的要求总是
          会被砍掉一些的。
          因此有时要稍稍高开一些(但也不能太过分了,很容易就
          会被别人拆穿的),以给领导砍价的余地。
       d、软硬兼施。此招慎用
          如果领导比较开明,你在公司的地位比较高,有的时候也
          可以撂狠话,不满足我的要求我就没办法作这个项目经理
          了!
          说这种话的时候要注意给自己留好台阶,避免留下不良后果

      以上种种只是一些辅助性的措施,最关键最首要的前提还是本人
      水平够高。如果本人水平不够,再好的人才到手里也只是浪费,
      那还不如给别人用呢。
1、项目经理的职责
   项目经理的两个主要职责是项目策划、项目的跟踪与监控。如果项目较小或者公司的规模不大,往往项目经理还得担任软件需求经理的角色。
2、项目经理的具体工作
   围绕着上面的两个职责(可能还有软件需求角色),项目经理的具体工作就比较杂而多。项目策划的内容很多:组织需求分析,项目规模估计,风险估计,进度安排。此外在项目的跟踪与监控方面,包括周报内容的收集及整理,风险的跟踪与监控,问题日志的维护,项目变更及重新策划。如果还要担任需求分析的角色与客户谈具体的需求,那么在软件需求分析、系统架构方面项目经理都是主要决策者,我认为这样会影响项目管理,如果是小项目尚可,要是规模大周期长的项目,建议有专门的需求分析师,系统构架师。

另外我想谈谈"把需求变更扼杀在襁褓之中":
   很多老前辈常常认为是不断变更的需求导致了大多数项目的失败,我想开发人员把角色可能倒置了。我们不可能因为需求变更导致系统架构变动很大 而不让用户去改变他们的需求。现在的竞争很激烈,用户需求变动很大,常理使然! 所以我们应该尽力去调研需求,了解客户所在那个行业,抽取出不变的或变动很小的需求,构建成我们的主要架构,再辅以常变的部分。现在系统架构都相当"柔性",我们可以尽量向那种分布式的、多层体系方向迈进。

2. 任务分配
    进行任务分配时应当注意任务之间的耦合程度以及任务的内聚程度,以减少项目内耗。尽量做到低耦合,以降低对成员之间交流的依赖程度,让大多数成员(需要把握全局的骨干成员除外)无需考虑太多繁杂的、不相干的东西;尽量做到高内聚,让成员可以尽量发挥他的能力以及已经获得的项目相关信息。
    如果工作量实在太大,或是工期要求太紧,不得不把高耦合工作分给多个成员负责,这时候就要特别注意成员间工作相关知识的同步的问题。
    要给所有成员一份详细的工作内容说明,并要注意所有成员得到的相关资料的一致性。在所有成员都做好自己工作的有代表性的一部分的时候,要把这些成员集中起来进行一次review,以消除成员之间知识/理解上的差异。如果在开发过程中出现了需求变更,则要及时通知与这部分需求相关任务的负责成员。做一个简明的变更索引,按索引定期review,以检查变更的落实情况。
    如果由于成员调度、个人进度、需求变更、以前遗漏的任务或者某种不可抗力等原因,而不得不更改任务分配,这时候一定要考虑如何最大化地利用项目人员已经做过的工作、已经获得的项目相关信息,尽量减少任务更改而引起的交流、培训和再教育花费。
3. 成员教育
    在成员的教育上要注意知识层次上的连续性,切不可出现断层。每个层次都应当有足够的候补要员,以便在非常情况下在层次间流动。
    在项目的早期,要全力打造几个能深刻理解全局的骨干成员,以在关键时刻减轻leader的工作压力。如果项目时间允许,早期加入到项目的人员可以适度拔高,让他们参与一些稍高于他们现有能力的工作。可以与他们一起理解需求,让他们参与分析模型的评估。由于有较长的时间来验证他们对知识的理解,相对来说可以比较放心。但是也要尽早检验,以减小修改错误的代价。
    而对于项目中后期加入的成员,不必让其了解太多的全局知识,尽量让其负责单一的工作。此时需要注意把对知识剪裁的尺度。什么样的知识是必需的,什么样的知识是需要大概了解一下的,什么样的知识是完全没有必要的。这些都需要leader仔细地斟酌。由于是中后期加入,应该在第一时间验证他们的任务相关知识的正确性和准确性,以保证最终产品的质量。


大家说的太好了,我没做过项目经理,但我喜欢到软件工程/项目管理中来学习学习前辈们的经验。
    我感觉我所在公司的项目管理真是太乱了,举例来说我本来被定位是现场施工的负责人,到后来我发现我的角色似乎变了很大。不仅仅是把开发组的软件在现场安装、调试、运行,更多的职责是
        1. 做系统测试,还有很多单元测试就应该解决的问题;
        2. 长达半年的测试(我不认为是施工)中,要整理用户的需求,与用户讨价还价,自然还要做客户关系;
        3. 因为在现场施工,系统上除了问题,总是替开发人员挨骂,所以不得不协调各项目小组的活动,抓进度、抓质量。
    感觉整个项目乱糟糟的,特郁闷。
    记得n年前看过一本企业管理的书,说管理的首要原则是则、权、利的统一,整个项目职责就是不清,有权利的人不怎么管,没权利的不得不管。有的事好象有几个人可以管,好象谁不管也都没责任。
    领导们在斗争,员工就更没有沟通、协做的精神了,再加上超负荷工作,money又少,领导的工作方式还有问题,哪个士气低呀,项目怎么能搞好!!!

1、第一条,随时中断,让自己空下来。
  非常赞成。
2、第二条,团队优先。
  除了让每人有事情做以外,还需要以整个团队的进度为第一。
  先满足解决组员的要求,项目经理就是组员的仆人。这也是为什么要第一条。
3、第三条,控制客户。
其实是注意客户的反映和要求,很多时候客户提出修改和新增功能,
将心比心替客户考虑一下,有些是没有必要的或者违反其他客户要求的或者实现违反已经
定义的大的框架的,的确不能给他做,通常能解释得明白。
很多时候客户的要求没有实现也不是很要紧,但是一定要给他反馈,
告诉他为什么,客户会觉得自己受尊重,不能口头答应,回头就忘。

4、第四条,控制领导。呵呵,没有这么大能耐。

5、个人意见: 团队人不是越多越好,而是要有合适的人。
一些不合要求的坚决不要。特别是缺乏合作精神的绝对不能要。
最好要有女组员,能够调节开发气氛。

6、注意要有计划节点,在开发的时候某个时间交出什么样的东西要和用户沟通,
尤其是现在用户不停地改变需求的时候。不能用户一提就修改,那样会影响质量。

7、团队开发的时候一个功能模块最好能够有2个组员知道,一个开发,另外一个
了解,在项目人员变动或者任务分配的时候有余地。

8、做好版本控制,不许组员修改他人东西。没有经过测试的东西不要交给客户,哪怕整个软件只改了一个很小的地方。

9、注意组员的状态和情绪,帮助解决组员问题,组员矛盾,让他们集中精力在工作上。
1.通常项目经理身兼数职(程序经理、业务分析人员等)才会出现需要“随时可中断”的情况,而且这种思想有些不对,感觉PM成了救火队员。我做过一项目,有专门的业务分析人员、程序经理,我一周只要花两到三天的时间就可以做下来,剩下的时间是把部门做过程改进,呵呵。
2.“团队优先”这还要说吗,做什么事不要团队优先;
“让团队中的所有人都有事情做”有些莫名奇妙,怎么会有人没事做,成员自己的任务完成了可以自行学习或者部门有培训计划,不用PM管,PM还需要管理成员的个人发展不成,有些职责不清;如果一直没事做,会调到其他项目组。
3.“控制客户”感觉象搞公关一样,做PM的自然有一定的沟通能力,当然不会能客户,也根本没那个必要,做之前,项目的目标需求应该比较明确,只会有一些细节上会有出入,需求不明确,当然会有扯皮了,不是搞不好关系的问题,对于新的需求让客户写好,要与客户代表、销售代表(在我们公司叫CCB)开会商定到底怎么做,一定不能由开发人员直接修改,如果要继续做,要定立协议盖章生效,不要以为烦,以后就会有好处。
4.“对于领导”怎么要什么“争夺人才”、“软硬兼施”,需要与部门经理做成那样程度,需要什么讨价还价等,这种项目也不要做了。

综合以上几点,对楼主的观点,真是不敢恭维,尽是些旁门左道的东东。也难怪,国内很多项目都是这么做过来的,形成了恶性循环。
=================================================================================================================
编程修养
————

什么是好的程序员?是不是懂得很多技术细节?还是懂底层编程?还是编程速度比较快?我觉得都不是。对于一些技术细节来说和底层的技术,只要看帮助,查资料就能找到,对于速度快,只要编得多也就熟能生巧了。

我认为好的程序员应该有以下几方面的素质:

  1、有专研精神,勤学善问、举一反三。
  2、积极向上的态度,有创造性思维。
  3、与人积极交流沟通的能力,有团队精神。
  4、谦虚谨慎,戒骄戒燥。
  5、写出的代码质量高。包括:代码的稳定、易读、规范、易维护、专业。

这些都是程序员的修养,这里我想谈谈“编程修养”,也就是上述中的第5点。我觉得,如果我要了解一个作者,我会看他所写的小说,如果我要了解一个画家,我会看他所画的图画,如果我要了解一个工人,我会看他所做出来的产品,同样,如果我要了解一个程序员,我想首先我最想看的就是他的程序代码,程序代码可以看出一个程序员的素质和修养,程序就像一个作品,有素质有修养的程序员的作品必然是一图精美的图画,一首美妙的歌曲,一本赏心悦目的小说。

我看过许多程序,没有注释,没有缩进,胡乱命名的变量名,等等,等等,我把这种人统称为没有修养的程序,这种程序员,是在做创造性的工作吗?不,完全就是在搞破坏,他们与其说是在编程,还不如说是在对源程序进行“加密”,这种程序员,见一个就应该开除一个,因为他编的程序所创造的价值,远远小于需要在上面进行维护的价值。

程序员应该有程序员的修养,那怕再累,再没时间,也要对自己的程序负责。我宁可要那种动作慢,技术一般,但有良好的写程序风格的程序员,也不要那种技术强、动作快的“搞破坏”的程序员。有句话叫“字如其人”,我想从程序上也能看出一个程序员的优劣。因为,程序是程序员的作品,作品的好坏直截关系到程序员的声誉和素质。而“修养”好的程序员一定能做出好的程序和软件。

有个成语叫“独具匠心”,意思是做什么都要做得很专业,很用心,如果你要做一个“匠”,也就是造诣高深的人,那么,从一件很简单的作品上就能看出你有没有“匠”的特性,我觉得做一个程序员不难,但要做一个“程序匠”就不简单了。编程序很简单,但编出有质量的程序就难了。

我在这里不讨论过深的技术,我只想在一些容易让人忽略的东西上说一说,虽然这些东西可能很细微,但如果你不注意这些细微之处的话,那么他将会极大的影响你的整个软件质量,以及整个软件程的实施,所谓“千里之堤,毁于蚁穴”。

“细微之处见真功”,真正能体现一个程序的功底恰恰在这些细微之处。

这就是程序员的——编程修养。我总结了在用C/C++语言(主要是C语言)进行程序写作上的三十二个“修养”,通过这些,你可以写出质量高的程序,同时也会让看你程序的人渍渍称道,那些看过你程序的人一定会说:“这个人的编程修养不错”。

    ————————————————————————
       
        01、版权和版本
        02、缩进、空格、换行、空行、对齐
        03、程序注释
        04、函数的[in][out]参数
        05、对系统调用的返回进行判断
        06、if 语句对出错的处理
        07、头文件中的#ifndef
        08、在堆上分配内存
        09、变量的初始化
        10、h和c文件的使用
        11、出错信息的处理
        12、常用函数和循环语句中的被计算量
        13、函数名和变量名的命名
        14、函数的传值和传指针
        15、修改别人程序的修养
        16、把相同或近乎相同的代码形成函数和宏
        17、表达式中的括号
        18、函数参数中的const
        19、函数的参数个数
        20、函数的返回类型,不要省略
        21、goto语句的使用
        22、宏的使用
        23、static的使用
        24、函数中的代码尺寸
        25、typedef的使用
        26、为常量声明宏
        27、不要为宏定义加分号
        28、||和&&的语句执行顺序
        29、尽量用for而不是while做循环
        30、请sizeof类型而不是变量
        31、不要忽略Warning
        32、书写Debug版和Release版的程序
1、版权和版本
———————
好的程序员会给自己的每个函数,每个文件,都注上版权和版本。

对于C/C++的文件,文件头应该有类似这样的注释:
/************************************************************************
*
*   文件名:network.c
*
*   文件描述:网络通讯函数集
*
*   创建人: Hao Chen, 2003年2月3日
*
*   版本号:1.0
*
*   修改记录:
*
************************************************************************/

而对于函数来说,应该也有类似于这样的注释:

/*================================================================
 *
 * 函 数 名:XXX
 *
 * 参    数:
 *
 *        type name [IN] : descripts
 *
 * 功能描述:
 *
 *        ..............
 *
 * 返 回 值:成功TRUE,失败FALSE
 *
 * 抛出异常:
 *
 * 作    者:ChenHao 2003/4/2
 *
 ================================================================*/

这样的描述可以让人对一个函数,一个文件有一个总体的认识,对代码的易读性和易维护性有很大的好处。这是好的作品产生的开始。

 

2、缩进、空格、换行、空行、对齐
————————————————
i) 缩进应该是每个程序都会做的,只要学程序过程序就应该知道这个,但是我仍然看过不缩进的程序,或是乱缩进的程序,如果你的公司还有写程序不缩进的程序员,请毫不犹豫的开除他吧,并以破坏源码罪起诉他,还要他赔偿读过他程序的人的精神损失费。缩进,这是不成文规矩,我再重提一下吧,一个缩进一般是一个TAB键或是4个空格。(最好用TAB键)

ii) 空格。空格能给程序代来什么损失吗?没有,有效的利用空格可以让你的程序读进来更加赏心悦目。而不一堆表达式挤在一起。看看下面的代码:

    ha=(ha*128+*key++)%tabPtr->size;

    ha = ( ha * 128 + *key++ ) % tabPtr->size;

    有空格和没有空格的感觉不一样吧。一般来说,语句中要在各个操作符间加空格,函数调用时,要以各个参数间加空格。如下面这种加空格的和不加的:
    
if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){
 }

if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){
}

iii) 换行。不要把语句都写在一行上,这样很不好。如:

    for(i=0;i<len;i++) if((a[i]<'0'||a[i]>'9')&&(a[i]<'a'||a[i]>'z')) break;
   
    我拷,这种即无空格,又无换行的程序在写什么啊?加上空格和换行吧。   
   
    for ( i=0; i<len; i++) {
        if ( ( a[i] < '0' || a[i] > '9' ) &&
             ( a[i] < 'a' || a[i] > 'z' ) ) {
            break;
        }
    }

    好多了吧?有时候,函数参数多的时候,最好也换行,如:

    CreateProcess(
                  NULL,
                  cmdbuf,
                  NULL,
                  NULL,
                  bInhH,
                  dwCrtFlags,
                  envbuf,
                  NULL,
                  &siStartInfo,
                  &prInfo
                 );

    条件语句也应该在必要时换行:
   
    if ( ch >= '0' || ch <= '9' ||
         ch >= 'a' || ch <= 'z' ||
         ch >= 'A' || ch <= 'Z' )

                 
iv) 空行。不要不加空行,空行可以区分不同的程序块,程序块间,最好加上空行。如:

    HANDLE hProcess;
    PROCESS_T procInfo;

    /* open the process handle */
    if((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) == NULL)
    {
        return LSE_MISC_SYS;
    }

    memset(&procInfo, 0, sizeof(procInfo));
    procInfo.idProc = pid;
    procInfo.hdProc = hProcess;
    procInfo.misc |= MSCAVA_PROC;

    return(0);
                 
v) 对齐。用TAB键对齐你的一些变量的声明或注释,一样会让你的程序好看一些。如:

typedef struct _pt_man_t_ {
    int     numProc;    /* Number of processes                 */
    int     maxProc;    /* Max Number of processes             */
    int     numEvnt;    /* Number of events                    */
    int     maxEvnt;    /* Max Number of events                */
    HANDLE* pHndEvnt;   /* Array of events                     */
    DWORD   timeout;    /* Time out interval                   */
    HANDLE  hPipe;      /* Namedpipe                           */
    TCHAR   usr[MAXUSR];/* User name of the process            */
    int     numMsg;     /* Number of Message                   */
    int     Msg[MAXMSG];/* Space for intro process communicate */
} PT_MAN_T;

怎么样?感觉不错吧。

这里主要讲述了如果写出让人赏心悦目的代码,好看的代码会让人的心情愉快,读起代码也就不累,工整、整洁的程序代码,通常更让人欢迎,也更让人称道。现在的硬盘空间这么大,不要让你的代码挤在一起,这样它们会抱怨你虐待它们的。好了,用“缩进、空格、换行、空行、对齐”装饰你的代码吧,让他们从没有秩序的土匪中变成一排排整齐有秩序的正规部队吧。


                 

3、程序注释
——————
养成写程序注释的习惯,这是每个程序员所必须要做的工作。我看过那种几千行,却居然没有一行注释的程序。这就如同在公路上驾车却没有路标一样。用不了多久,连自己都不知道自己的意图了,还要花上几倍的时间才看明白,这种浪费别人和自己的时间的人,是最为可耻的人。

是的,你也许会说,你会写注释,真的吗?注释的书写也能看出一个程序员的功底。一般来说你需要至少写这些地方的注释:文件的注释、函数的注释、变量的注释、算法的注释、功能块的程序注释。主要就是记录你这段程序是干什么的?你的意图是什么?你这个变量是用来做什么的?等等。

不要以为注释好写,有一些算法是很难说或写出来的,只能意会,我承认有这种情况的时候,但你也要写出来,正好可以训练一下自己的表达能力。而表达能力正是那种闷头搞技术的技术人员最缺的,你有再高的技术,如果你表达能力不行,你的技术将不能得到充分的发挥。因为,这是一个团队的时代。

好了,说几个注释的技术细节:

i) 对于行注释(“//”)比块注释(“/* */”)要好的说法,我并不是很同意。因为一些老版本的C编译器并不支持行注释,所以为了你的程序的移植性,请你还是尽量使用块注释。

ii) 你也许会为块注释的不能嵌套而不爽,那么你可以用预编译来完成这个功能。使用“#if 0”和“#endif”括起来的代码,将不被编译,而且还可以嵌套。

 


4、函数的[in][out]参数
———————————

我经常看到这样的程序:
FuncName(char* str)
{
    int len = strlen(str);
    .....
}

char*
GetUserName(struct user* pUser)
{
    return pUser->name;
}

不!请不要这样做。

你应该先判断一下传进来的那个指针是不是为空。如果传进来的指针为空的话,那么,你的一个大的系统就会因为这一个小的函数而崩溃。一种更好的技术是使用断言(assert),这里我就不多说这些技术细节了。当然,如果是在C++中,引用要比指针好得多,但你也需要对各个参数进行检查。

写有参数的函数时,首要工作,就是要对传进来的所有参数进行合法性检查。而对于传出的参数也应该进行检查,这个动作当然应该在函数的外部,也就是说,调用完一个函数后,应该对其传出的值进行检查。

当然,检查会浪费一点时间,但为了整个系统不至于出现“非法操作”或是“Core Dump”的系统级的错误,多花这点时间还是很值得的。

 


5、对系统调用的返回进行判断
——————————————
继续上一条,对于一些系统调用,比如打开文件,我经常看到,许多程序员对fopen返回的指针不做任何判断,就直接使用了。然后发现文件的内容怎么也读出不,或是怎么也写不进去。还是判断一下吧:

    fp = fopen("log.txt", "a");
    if ( fp == NULL ){
        printf("Error: open file error/n");
        return FALSE;
    }

其它还有许多啦,比如:socket返回的socket号,malloc返回的内存。请对这些系统调用返回的东西进行判断。

6、if 语句对出错的处理
———————————
我看见你说了,这有什么好说的。还是先看一段程序代码吧。

    if ( ch >= '0' && ch <= '9' ){
        /* 正常处理代码 */
    }else{
        /* 输出错误信息 */
        printf("error ....../n");
        return ( FALSE );
    }

这种结构很不好,特别是如果“正常处理代码”很长时,对于这种情况,最好不要用else。先判断错误,如:

    if ( ch < '0' || ch > '9' ){
        /* 输出错误信息 */
        printf("error ....../n");
        return ( FALSE );
    }
   
    /* 正常处理代码 */
    ......


这样的结构,不是很清楚吗?突出了错误的条件,让别人在使用你的函数的时候,第一眼就能看到不合法的条件,于是就会更下意识的避免。

 


7、头文件中的#ifndef
——————————
千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。

还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:

    #ifndef  <标识>
    #define <标识>
   
    ......
    ......
   
    #endif
   
<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h

    #ifndef _STDIO_H_
    #define _STDIO_H_
   
    ......
   
    #endif
   
(BTW:预编译有多很有用的功能。你会用预编译吗?)   
   

 

8、在堆上分配内存
—————————
可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲,stack上分配的内存系统自动释放,heap上分配的内存,系统不释放,哪怕程序退出,那一块内存还是在那里。stack一般是静态分配内存,heap上一般是动态分配内存。

由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。用free释放,不然就是术语——“内存泄露”(或是“内存漏洞”)—— Memory Leak。于是,系统的可分配内存会随malloc越来越少,直到系统崩溃。还是来看看“栈内存”和“堆内存”的差别吧。

    栈内存分配
    —————
    char*
    AllocStrFromStack()
    {
        char pstr[100];
        return pstr;
    }
   
   
    堆内存分配
    —————
    char*
    AllocStrFromHeap(int len)
    {
        char *pstr;
       
        if ( len <= 0 ) return NULL;
        return ( char* ) malloc( len );
    }

对于第一个函数,那块pstr的内存在函数返回时就被系统释放了。于是所返回的char*什么也没有。而对于第二个函数,是从堆上分配内存,所以哪怕是程序退出时,也不释放,所以第二个函数的返回的内存没有问题,可以被使用。但一定要调用free释放,不然就是Memory Leak!

在堆上分配内存很容易造成内存泄漏,这是C/C++的最大的“克星”,如果你的程序要稳定,那么就不要出现Memory Leak。所以,我还是要在这里千叮咛万嘱付,在使用malloc系统函数(包括calloc,realloc)时千万要小心。

记得有一个UNIX上的服务应用程序,大约有几百的C文件编译而成,运行测试良好,等使用时,每隔三个月系统就是down一次,搞得许多人焦头烂额,查不出问题所在。只好,每隔两个月人工手动重启系统一次。出现这种问题就是Memery Leak在做怪了,在C/C++中这种问题总是会发生,所以你一定要小心。一个Rational的检测工作——Purify,可以帮你测试你的程序有没有内存泄漏。

我保证,做过许多C/C++的工程的程序员,都会对malloc或是new有些感冒。当你什么时候在使用malloc和new时,有一种轻度的紧张和惶恐的感觉时,你就具备了这方面的修养了。
   
对于malloc和free的操作有以下规则:

1) 配对使用,有一个malloc,就应该有一个free。(C++中对应为new和delete)
2) 尽量在同一层上使用,不要像上面那种,malloc在函数中,而free在函数外。最好在同一调用层上使用这两个函数。
3) malloc分配的内存一定要初始化。free后的指针一定要设置为NULL。   

注:虽然现在的操作系统(如:UNIX和Win2k/NT)都有进程内存跟踪机制,也就是如果你有没有释放的内存,操作系统会帮你释放。但操作系统依然不会释放你程序中所有产生了Memory Leak的内存,所以,最好还是你自己来做这个工作。(有的时候不知不觉就出现Memory Leak了,而且在几百万行的代码中找无异于海底捞针,Rational有一个工具叫Purify,可能很好的帮你检查程序中的Memory Leak)

 

9、变量的初始化
————————
接上一条,变量一定要被初始化再使用。C/C++编译器在这个方面不会像JAVA一样帮你初始化,这一切都需要你自己来,如果你使用了没有初始化的变量,结果未知。好的程序员从来都会在使用变量前初始化变量的。如:

    1) 对malloc分配的内存进行memset清零操作。(可以使用calloc分配一块全零的内存)
    2) 对一些栈上分配的struct或数组进行初始化。(最好也是清零)
   
不过话又说回来了,初始化也会造成系统运行时间有一定的开销,所以,也不要对所有的变量做初始化,这个也没有意义。好的程序员知道哪些变量需要初始化,哪些则不需要。如:以下这种情况,则不需要。
       
        char *pstr;  /* 一个字符串 */
        pstr = ( char* ) malloc( 50 );
        if ( pstr == NULL ) exit(0);
        strcpy( pstr, "Hello Wrold" );

但如果是下面一种情况,最好进行内存初始化。(指针是一个危险的东西,一定要初始化)

        char **pstr;  /* 一个字符串数组 */
        pstr = ( char** ) malloc( 50 );
        if ( pstr == NULL ) exit(0);
       
        /* 让数组中的指针都指向NULL */
        memset( pstr, 0, 50*sizeof(char*) );
       
而对于全局变量,和静态变量,一定要声明时就初始化。因为你不知道它第一次会在哪里被使用。所以使用前初始这些变量是比较不现实的,一定要在声明时就初始化它们。如:

    Links *plnk = NULL;  /* 对于全局变量plnk初始化为NULL */

 

 

10、h和c文件的使用
—————————
H文件和C文件怎么用呢?一般来说,H文件中是declare(声明),C文件中是define(定义)。因为C文件要编译成库文件(Windows下是.obj/.lib,UNIX下是.o/.a),如果别人要使用你的函数,那么就要引用你的H文件,所以,H文件中一般是变量、宏定义、枚举、结构和函数接口的声明,就像一个接口说明文件一样。而C文件则是实现细节。

H文件和C文件最大的用处就是声明和实现分开。这个特性应该是公认的了,但我仍然看到有些人喜欢把函数写在H文件中,这种习惯很不好。(如果是C++话,对于其模板函数,在VC中只有把实现和声明都写在一个文件中,因为VC不支持export关键字)。而且,如果在H文件中写上函数的实现,你还得在makefile中把头文件的依赖关系也加上去,这个就会让你的makefile很不规范。

最后,有一个最需要注意的地方就是:带初始化的全局变量不要放在H文件中!

例如有一个处理错误信息的结构:

    char* errmsg[] = {
        /* 0 */       "No error",               
        /* 1 */       "Open file error",       
        /* 2 */       "Failed in sending/receiving a message", 
        /* 3 */       "Bad arguments", 
        /* 4 */       "Memeroy is not enough",
        /* 5 */       "Service is down; try later",
        /* 6 */       "Unknow information",
        /* 7 */       "A socket operation has failed",
        /* 8 */       "Permission denied",
        /* 9 */       "Bad configuration file format", 
        /* 10 */      "Communication time out",
        ......
        ......
    };
   
请不要把这个东西放在头文件中,因为如果你的这个头文件被5个函数库(.lib或是.a)所用到,于是他就被链接在这5个.lib或.a中,而如果你的一个程序用到了这5个函数库中的函数,并且这些函数都用到了这个出错信息数组。那么这份信息将有5个副本存在于你的执行文件中。如果你的这个errmsg很大的话,而且你用到的函数库更多的话,你的执行文件也会变得很大。

正确的写法应该把它写到C文件中,然后在各个需要用到errmsg的C文件头上加上 extern char* errmsg[]; 的外部声明,让编译器在链接时才去管他,这样一来,就只会有一个errmsg存在于执行文件中,而且,这样做很利于封装。

我曾遇到过的最疯狂的事,就是在我的目标文件中,这个errmsg一共有112个副本,执行文件有8M左右。当我把errmsg放到C文件中,并为一千多个C文件加上了extern的声明后,所有的函数库文件尺寸都下降了20%左右,而我的执行文件只有5M了。一下子少了3M啊。

[ 备注 ]
—————
有朋友对我说,这个只是一个特例,因为,如果errmsg在执行文件中存在多个副本时,可以加快程序运行速度,理由是errmsg的多个复本会让系统的内存换页降低,达到效率提升。像我们这里所说的errmsg只有一份,当某函数要用errmsg时,如果内存隔得比较远,会产生换页,反而效率不高。

这个说法不无道理,但是一般而言,对于一个比较大的系统,errmsg是比较大的,所以产生副本导致执行文件尺寸变大,不仅增加了系统装载时间,也会让一个程序在内存中占更多的页面。而对于errmsg这样数据,一般来说,在系统运行时不会经常用到,所以还是产生的内存换页也就不算频繁。权衡之下,还是只有一份errmsg的效率高。即便是像logmsg这样频繁使用的的数据,操作系统的内存调度算法会让这样的频繁使用的页面常驻于内存,所以也就不会出现内存换页问题了。

11、出错信息的处理
—————————
你会处理出错信息吗?哦,它并不是简单的输出。看下面的示例:

    if ( p == NULL ){
        printf ( "ERR: The pointer is NULL/n" );
    }
   
告别学生时代的编程吧。这种编程很不利于维护和管理,出错信息或是提示信息,应该统一处理,而不是像上面这样,写成一个“硬编码”。第10条对这方面的处理做了一部分说明。如果要管理错误信息,那就要有以下的处理:

    /* 声明出错代码 */
    #define     ERR_NO_ERROR    0  /* No error                 */
    #define     ERR_OPEN_FILE   1  /* Open file error          */
    #define     ERR_SEND_MESG   2  /* sending a message error  */
    #define     ERR_BAD_ARGS    3  /* Bad arguments            */
    #define     ERR_MEM_NONE    4  /* Memeroy is not enough    */
    #define     ERR_SERV_DOWN   5  /* Service down try later   */
    #define     ERR_UNKNOW_INFO 6  /* Unknow information       */
    #define     ERR_SOCKET_ERR  7  /* Socket operation failed  */
    #define     ERR_PERMISSION  8  /* Permission denied        */
    #define     ERR_BAD_FORMAT  9  /* Bad configuration file   */
    #define     ERR_TIME_OUT   10  /* Communication time out   */
   
    /* 声明出错信息 */
    char* errmsg[] = {
        /* 0 */       "No error",               
        /* 1 */       "Open file error",       
        /* 2 */       "Failed in sending/receiving a message", 
        /* 3 */       "Bad arguments", 
        /* 4 */       "Memeroy is not enough",
        /* 5 */       "Service is down; try later",
        /* 6 */       "Unknow information",
        /* 7 */       "A socket operation has failed",
        /* 8 */       "Permission denied",
        /* 9 */       "Bad configuration file format", 
        /* 10 */      "Communication time out",
    };
                             
    /* 声明错误代码全局变量 */
    long errno = 0;
   
    /* 打印出错信息函数 */
    void perror( char* info)
    {
        if ( info ){
            printf("%s: %s/n", info, errmsg[errno] );
            return;
        }
       
        printf("Error: %s/n", errmsg[errno] );
    }

这个基本上是ANSI的错误处理实现细节了,于是当你程序中有错误时你就可以这样处理:

    bool CheckPermission( char* userName )
    {
        if ( strcpy(userName, "root") != 0 ){
            errno = ERR_PERMISSION_DENIED;
            return (FALSE);
        }
       
        ...
    }
   
    main()
    {
        ...
        if (! CheckPermission( username ) ){
            perror("main()");
        }
        ...
    }
                             
一个即有共性,也有个性的错误信息处理,这样做有利同种错误出一样的信息,统一用户界面,而不会因为文件打开失败,A程序员出一个信息,B程序员又出一个信息。而且这样做,非常容易维护。代码也易读。

当然,物极必反,也没有必要把所有的输出都放到errmsg中,抽取比较重要的出错信息或是提示信息是其关键,但即使这样,这也包括了大多数的信息。

 


12、常用函数和循环语句中的被计算量
—————————————————
看一下下面这个例子:

    for( i=0; i<1000; i++ ){
        GetLocalHostName( hostname );
        ...
    }
   
GetLocalHostName的意思是取得当前计算机名,在循环体中,它会被调用1000次啊。这是多么的没有效率的事啊。应该把这个函数拿到循环体外,这样只调用一次,效率得到了很大的提高。虽然,我们的编译器会进行优化,会把循环体内的不变的东西拿到循环外面,但是,你相信所有编译器会知道哪些是不变的吗?我觉得编译器不可靠。最好还是自己动手吧。

同样,对于常用函数中的不变量,如:

GetLocalHostName(char* name)
{
    char* funcName = "GetLocalHostName";
   
    sys_log( "%s begin......", funcName );
    ...
    sys_log( "%s end......", funcName );
}

如果这是一个经常调用的函数,每次调用时都要对funcName进行分配内存,这个开销很大啊。把这个变量声明成static吧,当函数再次被调用时,就会省去了分配内存的开销,执行效率也很好。
   

 

13、函数名和变量名的命名
————————————
我看到许多程序对变量名和函数名的取名很草率,特别是变量名,什么a,b,c,aa,bb,cc,还有什么flag1,flag2, cnt1, cnt2,这同样是一种没有“修养”的行为。即便加上好的注释。好的变量名或是函数名,我认为应该有以下的规则:
   
    1) 直观并且可以拼读,可望文知意,不必“解码”。
    2) 名字的长度应该即要最短的长度,也要能最大限度的表达其含义。
    3) 不要全部大写,也不要全部小写,应该大小写都有,如:GetLocalHostName 或是 UserAccount。
    4) 可以简写,但简写得要让人明白,如:ErrorCode -> ErrCode,  ServerListener -> ServLisner,UserAccount -> UsrAcct 等。
    5) 为了避免全局函数和变量名字冲突,可以加上一些前缀,一般以模块简称做为前缀。
    6) 全局变量统一加一个前缀或是后缀,让人一看到这个变量就知道是全局的。
    7) 用匈牙利命名法命名函数参数,局部变量。但还是要坚持“望文生意”的原则。
    8) 与标准库(如:STL)或开发库(如:MFC)的命名风格保持一致。
   


   
14、函数的传值和传指针
————————————
向函数传参数时,一般而言,传入非const的指针时,就表示,在函数中要修改这个指针把指内存中的数据。如果是传值,那么无论在函数内部怎么修改这个值,也影响不到传过来的值,因为传值是只内存拷贝。

什么?你说这个特性你明白了,好吧,让我们看看下面的这个例程:

void
GetVersion(char* pStr)
{
    pStr = malloc(10);
    strcpy ( pStr, "2.0" );
}

main()
{
    char* ver = NULL;
    GetVersion ( ver );
    ...
    ...
    free ( ver );
}

我保证,类似这样的问题是一个新手最容易犯的错误。程序中妄图通过函数GetVersion给指针ver分配空间,但这种方法根本没有什么作用,原因就是——这是传值,不是传指针。你或许会和我争论,我分明传的时指针啊?再仔细看看,其实,你传的是指针其实是在传值。

 

15、修改别人程序的修养
———————————

当你维护别人的程序时,请不要非常主观臆断的把已有的程序删除或是修改。我经常看到有的程序员直接在别人的程序上修改表达式或是语句。修改别人的程序时,请不要删除别人的程序,如果你觉得别人的程序有所不妥,请注释掉,然后添加自己的处理程序,必竟,你不可能100%的知道别人的意图,所以为了可以恢复,请不依赖于CVS或是SourceSafe这种版本控制软件,还是要在源码上给别人看到你修改程序的意图和步骤。这是程序维护时,一个有修养的程序员所应该做的。

如下所示,这就是一种比较好的修改方法:

    /*
     * ----- commented by haoel 2003/04/12 ------
     *
     *   char* p = ( char* ) malloc( 10 );
     *   memset( p, 0, 10 );
     */
    
    /* ------ Added by haoel   2003/04/12 ----- */
     char* p = ( char* )calloc( 10, sizeof char );
    /* ---------------------------------------- */
    ...

当然,这种方法是在软件维护时使用的,这样的方法,可以让再维护的人很容易知道以前的代码更改的动作和意图,而且这也是对原作者的一种尊敬。

以“注释 — 添加”方式修改别人的程序,要好于直接删除别人的程序。
16、把相同或近乎相同的代码形成函数和宏
—————————————————————

有人说,最好的程序员,就是最喜欢“偷懒”的程序,其中不无道理。

如果你有一些程序的代码片段很相似,或直接就是一样的,请把他们放在一个函数中。而如果这段代码不多,而且会被经常使用,你还想避免函数调用的开销,那么就把他写成宏吧。

千万不要让同一份代码或是功能相似的代码在多个地方存在,不然如果功能一变,你就要修改好几处地方,这种会给维护带来巨大的麻烦,所以,做到“一改百改”,还是要形成函数或是宏。

 

17、表达式中的括号
—————————

如果一个比较复杂的表达式中,你并不是很清楚各个操作符的忧先级,即使是你很清楚优先级,也请加上括号,不然,别人或是自己下一次读程序时,一不小心就看走眼理解错了,为了避免这种“误解”,还有让自己的程序更为清淅,还是加上括号吧。

比如,对一个结构的成员取地址:

    GetUserAge( &( UserInfo->age ) );

虽然,&UserInfo->age中,->操作符的优先级最高,但加上一个括号,会让人一眼就看明白你的代码是什么意思。

再比如,一个很长的条件判断:

if ( ( ch[0] >= '0' || ch[0] <= '9' ) &&
     ( ch[1] >= 'a' || ch[1] <= 'z' ) &&
     ( ch[2] >= 'A' || ch[2] <= 'Z' )    )
    
括号,再加上空格和换行,你的代码是不是很容易读懂了?    


18、函数参数中的const
———————————

对于一些函数中的指针参数,如果在函数中只读,请将其用const修饰,这样,别人一读到你的函数接口时,就会知道你的意图是这个参数是[in],如果没有const时,参数表示[in/out],注意函数接口中的const使用,利于程序的维护和避免犯一些错误。

虽然,const修饰的指针,如:const char* p,在C中一点用也没有,因为不管你的声明是不是const,指针的内容照样能改,因为编译器会强制转换,但是加上这样一个说明,有利于程序的阅读和编译。因为在C中,修改一个const指针所指向的内存时,会报一个Warning。这会引起程序员的注意。

C++中对const定义的就很严格了,所以C++中要多多的使用const,const的成员函数,const的变量,这样会对让你的代码和你的程序更加完整和易读。(关于C++的const我就不多说了)

 

19、函数的参数个数(多了请用结构)
—————————————————

函数的参数个数最好不要太多,一般来说6个左右就可以了,众多的函数参数会让读代码的人一眼看上去就很头昏,而且也不利于维护。如果参数众多,还请使用结构来传递参数。这样做有利于数据的封装和程序的简洁性。

也利于使用函数的人,因为如果你的函数个数很多,比如12个,调用者很容易搞错参数的顺序和个数,而使用结构struct来传递参数,就可以不管参数的顺序。

而且,函数很容易被修改,如果需要给函数增加参数,不需要更改函数接口,只需更改结构体和函数内部处理,而对于调用函数的程序来说,这个动作是透明的。

 


20、函数的返回类型,不要省略
——————————————

我看到很多程序写函数时,在函数的返回类型方面不太注意。如果一个函数没有返回值,也请在函数前面加上void的修饰。而有的程序员偷懒,在返回int的函数则什么不修饰(因为如果不修饰,则默认返回int),这种习惯很不好,还是为了原代码的易读性,加上int吧。

所以函数的返回值类型,请不要省略。

另外,对于void的函数,我们往往会忘了return,由于某些C/C++的编译器比较敏感,会报一些警告,所以即使是void的函数,我们在内部最好也要加上return的语句,这有助于代码的编译。

 


21、goto语句的使用
—————————

N年前,软件开发的一代宗师——迪杰斯特拉(Dijkstra)说过:“goto statment is harmful !!”,并建议取消goto语句。因为goto语句不利于程序代码的维护性。

这里我也强烈建议不要使用goto语句,除非下面的这种情况:


    #define FREE(p) if(p) { /
                        free(p); /
                        p = NULL; /
                    }

    main()
    {
        char *fname, *lname, *mname;

        fname = ( char* ) calloc ( 20, sizeof(char) );
        if ( fname == NULL ){
            goto ErrHandle;
        }

        lname = ( char* ) calloc ( 20, sizeof(char) );
        if ( lname == NULL ){
            goto ErrHandle;
        }

        mname = ( char* ) calloc ( 20, sizeof(char) );
        if ( mname == NULL ){
            goto ErrHandle;
        }
       
        ......
    
       
     ErrHandle:
        FREE(fname);
        FREE(lname);
        FREE(mname);
        ReportError(ERR_NO_MEMOEY);
     }

也只有在这种情况下,goto语句会让你的程序更易读,更容易维护。(在用嵌C来对数据库设置游标操作时,或是对数据库建立链接时,也会遇到这种结构)

 


22、宏的使用
——————

很多程序员不知道C中的“宏”到底是什么意思?特别是当宏有参数的时候,经常把宏和函数混淆。我想在这里我还是先讲讲“宏”,宏只是一种定义,他定义了一个语句块,当程序编译时,编译器首先要执行一个“替换”源程序的动作,把宏引用的地方替换成宏定义的语句块,就像文本文件替换一样。这个动作术语叫“宏的展开”

使用宏是比较“危险”的,因为你不知道宏展开后会是什么一个样子。例如下面这个宏:

    #define  MAX(a, b)     a>b?a:b

当我们这样使用宏时,没有什么问题: MAX( num1, num2 ); 因为宏展开后变成 num1>num2?num1:num2;。 但是,如果是这样调用的,MAX( 17+32, 25+21 ); 呢,编译时出现错误,原因是,宏展开后变成:17+32>25+21?17+32:25+21,哇,这是什么啊?

所以,宏在使用时,参数一定要加上括号,上述的那个例子改成如下所示就能解决问题了。

    #define  MAX( (a), (b) )     (a)>(b)?(a):(b)
   
即使是这样,也不这个宏也还是有Bug,因为如果我这样调用 MAX(i++, j++); , 经过这个宏以后,i和j都被累加了两次,这绝不是我们想要的。
   
所以,在宏的使用上还是要谨慎考虑,因为宏展开是的结果是很难让人预料的。而且虽然,宏的执行很快(因为没有函数调用的开销),但宏会让源代码澎涨,使目标文件尺寸变大,(如:一个50行的宏,程序中有1000个地方用到,宏展开后会很不得了),相反不能让程序执行得更快(因为执行文件变大,运行时系统换页频繁)。

因此,在决定是用函数,还是用宏时得要小心。

23、static的使用
————————
static关键字,表示了“静态”,一般来说,他会被经常用于变量和函数。一个static的变量,其实就是全局变量,只不过他是有作用域的全局变量。比如一个函数中的static变量:

char*
getConsumerName()
{
    static int cnt = 0;
   
    ....
    cnt++;
    ....
}

cnt变量的值会跟随着函数的调用次而递增,函数退出后,cnt的值还存在,只是cnt只能在函数中才能被访问。而cnt的内存也只会在函数第一次被调用时才会被分配和初始化,以后每次进入函数,都不为static分配了,而直接使用上一次的值。

对于一些被经常调用的函数内的常量,最好也声明成static(参见第12条)

但static的最多的用处却不在这里,其最大的作用的控制访问,在C中如果一个函数或是一个全局变量被声明为static,那么,这个函数和这个全局变量,将只能在这个C文件中被访问,如果别的C文件中调用这个C文件中的函数,或是使用其中的全局(用extern关键字),将会发生链接时错误。这个特性可以用于数据和程序保密。

 

24、函数中的代码尺寸
——————————
一个函数完成一个具体的功能,一般来说,一个函数中的代码最好不要超过600行左右,越少越好,最好的函数一般在100行以内,300行左右的孙函数就差不多了。有证据表明,一个函数中的代码如果超过500行,就会有和别的函数相同或是相近的代码,也就是说,就可以再写另一个函数。

另外,函数一般是完成一个特定的功能,千万忌讳在一个函数中做许多件不同的事。函数的功能越单一越好,一方面有利于函数的易读性,另一方面更有利于代码的维护和重用,功能越单一表示这个函数就越可能给更多的程序提供服务,也就是说共性就越多。

虽然函数的调用会有一定的开销,但比起软件后期维护来说,增加一些运行时的开销而换来更好的可维护性和代码重用性,是很值得的一件事。


25、typedef的使用
—————————

typedef是一个给类型起别名的关键字。不要小看了它,它对于你代码的维护会有很好的作用。比如C中没有bool,于是在一个软件中,一些程序员使用int,一些程序员使用short,会比较混乱,最好就是用一个typedef来定义,如:

    typedef char bool;
   
一般来说,一个C的工程中一定要做一些这方面的工作,因为你会涉及到跨平台,不同的平台会有不同的字长,所以利用预编译和typedef可以让你最有效的维护你的代码,如下所示:

    #ifdef SOLARIS2_5
      typedef boolean_t     BOOL_T;
    #else
      typedef int           BOOL_T;
    #endif
   
    typedef short           INT16_T;
    typedef unsigned short  UINT16_T;
    typedef int             INT32_T;
    typedef unsigned int    UINT32_T;
   
    #ifdef WIN32
      typedef _int64        INT64_T;
    #else
      typedef long long     INT64_T;
    #endif
   
    typedef float           FLOAT32_T;
    typedef char*           STRING_T;
    typedef unsigned char   BYTE_T;
    typedef time_t          TIME_T;
    typedef INT32_T         PID_T;
   
使用typedef的其它规范是,在结构和函数指针时,也最好用typedef,这也有利于程序的易读和可维护性。如:

    typedef struct _hostinfo {
        HOSTID_T   host;
        INT32_T    hostId;
        STRING_T   hostType;
        STRING_T   hostModel;
        FLOAT32_T  cpuFactor;
        INT32_T    numCPUs;
        INT32_T    nDisks;
        INT32_T    memory;
        INT32_T    swap;
    } HostInfo;


    typedef INT32_T (*RsrcReqHandler)(
     void *info,
     JobArray *jobs,
     AllocInfo *allocInfo,
     AllocList *allocList);

C++中这样也是很让人易读的:

    typedef CArray<HostInfo, HostInfo&> HostInfoArray;

于是,当我们用其定义变量时,会显得十分易读。如:

    HostInfo* phinfo;
    RsrcReqHandler* pRsrcHand;

这种方式的易读性,在函数的参数中十分明显。

关键是在程序种使用typedef后,几乎所有的程序中的类型声明都显得那么简洁和清淅,而且易于维护,这才是typedef的关键。

 

26、为常量声明宏
————————
最好不要在程序中出现数字式的“硬编码”,如:

    int user[120];
   
为这个120声明一个宏吧。为所有出现在程序中的这样的常量都声明一个宏吧。比如TimeOut的时间,最大的用户数量,还有其它,只要是常量就应该声明成宏。如果,突然在程序中出现下面一段代码,

    for ( i=0; i<120; i++){
        ....
    }

120是什么?为什么会是120?这种“硬编码”不仅让程序很读,而且也让程序很不好维护,如果要改变这个数字,得同时对所有程序中这个120都要做修改,这对修改程序的人来说是一个很大的痛苦。所以还是把常量声明成宏,这样,一改百改,而且也很利于程序阅读。

    #define MAX_USR_CNT 120
   
    for ( i=0; i<MAX_USER_CNT; i++){
        ....
    }

这样就很容易了解这段程序的意图了。

有的程序员喜欢为这种变量声明全局变量,其实,全局变量应该尽量的少用,全局变量不利于封装,也不利于维护,而且对程序执行空间有一定的开销,一不小心就造成系统换页,造成程序执行速度效率等问题。所以声明成宏,即可以免去全局变量的开销,也会有速度上的优势。


27、不要为宏定义加分号
———————————

有许多程序员不知道在宏定义时是否要加分号,有时,他们以为宏是一条语句,应该要加分号,这就错了。当你知道了宏的原理,你会赞同我为会么不要为宏定义加分号的。看一个例子:

    #define MAXNUM 1024;

这是一个有分号的宏,如果我们这样使用:

    half = MAXNUM/2;
   
    if ( num < MAXNUM )

等等,都会造成程序的编译错误,因为,当宏展开后,他会是这个样子的:

    half = 1024;/2;
   
    if ( num < 1024; )
   
是的,分号也被展进去了,所以造成了程序的错误。请相信我,有时候,一个分号会让你的程序出现成百个错误。所以还是不要为宏加最后一个分号,哪怕是这样:

    #define LINE    "================================="
   
    #define PRINT_LINE  printf(LINE)

    #define PRINT_NLINE(n)  while ( n-- >0 ) { PRINT_LINE; }
   
都不要在最后加上分号,当我们在程序中使用时,为之加上分号,

    main()
    {
        char *p = LINE;
        PRINT_LINE;
    }

这一点非常符合习惯,而且,如果忘加了分号,编译器给出的错误提示,也会让我们很容易看懂的。

28、||和&&的语句执行顺序
————————————
条件语句中的这两个“与”和“或”操作符一定要小心,它们的表现可能和你想像的不一样,这里条件语句中的有些行为需要和说一下:

    express1 || express2
       
    先执行表达式express1如果为“真”,express2将不被执行,express2仅在express1为“假”时才被执行。因为第一个表达式为真了,整个表达式都为真,所以没有必要再去执行第二个表达式了。

    express1 && express2

    先执行表达式express1如果为“假”,express2将不被执行,express2仅在express1为“真”时才被执行。因为第一个表达式为假了,整个表达式都为假了,所以没有必要再去执行第二个表达式了。


于是,他并不是你所想像的所有的表达式都会去执行,这点一定要明白,不然你的程序会出现一些莫明的运行时错误。

例如,下面的程序:


    if ( sum > 100 &&
         ( ( fp=fopen( filename,"a" ) ) != NULL )   {
        
         fprintf(fp, "Warring: it beyond one hundred/n");
         ......
    }
   
    fprintf( fp, " sum is %id /n", sum );
    fclose( fp );

本来的意图是,如果sum > 100 ,向文件中写一条出错信息,为了方便,把两个条件判断写在一起,于是,如果sum<=100时,打开文件的操作将不会做,最后,fprintf和fclose就会发现未知的结果。

再比如,如果我想判断一个字符是不是有内容,我得判断这个字符串指针是不为空(NULL)并且其内容不能为空(Empty),一个是空指针,一个是空内容。我也许会这样写:

    if ( ( p != NULL ) && ( strlen(p) != 0 ))

于是,如果p为NULL,那么strlen(p)就不会被执行,于是,strlen也就不会因为一个空指针而“非法操作”或是一个“Core Dump”了。

记住一点,条件语句中,并非所有的语句都会执行,当你的条件语句非常多时,这点要尤其注意。

 

29、尽量用for而不是while做循环
———————————————
基本上来说,for可以完成while的功能,我是建议尽量使用for语句,而不要使用while语句,特别是当循环体很大时,for的优点一下就体现出来了。

因为在for中,循环的初始、结束条件、循环的推进,都在一起,一眼看上去就知道这是一个什么样的循环。刚出学校的程序一般对于链接喜欢这样来:

    p = pHead;
   
    while ( p ){
        ...
        ...
        p = p->next;
    }

当while的语句块变大后,你的程序将很难读,用for就好得多:

    for ( p=pHead;  p; p=p->next ){
    ..
    }

一眼就知道这个循环的开始条件,结束条件,和循环的推进。大约就能明白这个循环要做个什么事?而且,程序维护进来很容易,不必像while一样,在一个编辑器中上上下下的捣腾。

 

30、请sizeof类型而不是变量
—————————————

许多程序员在使用sizeof中,喜欢sizeof变量名,例如:

int score[100];
char filename[20];
struct UserInfo usr[100];

在sizeof这三个的变量名时,都会返回正确的结果,于是许多程序员就开始sizeof变量名。这个习惯很虽然没有什么不好,但我还是建议sizeof类型。

我看到过这个的程序:

    pScore = (int*) malloc( SUBJECT_CNT );
    memset( pScore, 0, sizeof(pScore) );
    ...
   
此时,sizeof(pScore)返回的就是4(指针的长度),不会是整个数组,于是,memset就不能对这块内存进行初始化。为了程序的易读和易维护,我强烈建议使用类型而不是变量,如:

对于score:     sizeof(int) * 100   /* 100个int */
对于filename:  sizeof(char) * 20   /* 20个char */
对于usr:       sizeof(struct UserInfo) * 100   /* 100个UserInfo */

这样的代码是不是很易读?一眼看上去就知道什么意思了。


另外一点,sizeof一般用于分配内存,这个特性特别在多维数组时,就能体现出其优点了。如,给一个字符串数组分配内存,

/*
 * 分配一个有20个字符串,
 * 每个字符串长100的内存
 */

char* *p;

/*
 * 错误的分配方法
 */
p = (char**)calloc( 20*100, sizeof(char) );


/*
 * 正确的分配方法
 */
p = (char**) calloc ( 20, sizeof(char*) );
for ( i=0; i<20; i++){
    p = (char*) calloc ( 100, sizeof(char) );
}

为了代码的易读,省去了一些判断,请注意这两种分配的方法,有本质上的差别。

 

31、不要忽略Warning
——————————
对于一些编译时的警告信息,请不要忽视它们。虽然,这些Warning不会妨碍目标代码的生成,但这并不意味着你的程序就是好的。必竟,并不是编译成功的程序才是正确的,编译成功只是万里长征的第一步,后面还有大风大浪在等着你。从编译程序开始,不但要改正每个error,还要修正每个warning。这是一个有修养的程序员该做的事。

一般来说,一面的一些警告信息是常见的:

    1)声明了未使用的变量。(虽然编译器不会编译这种变量,但还是把它从源程序中注释或是删除吧)
    2)使用了隐晦声明的函数。(也许这个函数在别的C文件中,编译时会出现这种警告,你应该这使用之前使用extern关键字声明这个函数)
    3)没有转换一个指针。(例如malloc返回的指针是void的,你没有把之转成你实际类型而报警,还是手动的在之前明显的转换一下吧)
    4)类型向下转换。(例如:float f = 2.0; 这种语句是会报警告的,编译会告诉你正试图把一个double转成float,你正在阉割一个变量,你真的要这样做吗?还是在2.0后面加个f吧,不然,2.0就是一个double,而不是float了)
   
不管怎么说,编译器的Warning不要小视,最好不要忽略,一个程序都做得出来,何况几个小小的Warning呢?

 

32、书写Debug版和Release版的程序
————————————————
程序在开发过程中必然有许多程序员加的调试信息。我见过许多项目组,当程序开发结束时,发动群众删除程序中的调试信息,何必呢?为什么不像VC++那样建立两个版本的目标代码?一个是debug版本的,一个是Release版的。那些调试信息是那么的宝贵,在日后的维护过程中也是很宝贵的东西,怎么能说删除就删除呢?

利用预编译技术吧,如下所示声明调试函数:

    #ifdef DEBUG
        void TRACE(char* fmt, ...)
        {
            ......
        }
    #else
        #define TRACE(char* fmt, ...)
    #endif

于是,让所有的程序都用TRACE输出调试信息,只需要在在编译时加上一个参数“-DDEBUG”,如:

    cc -DDEBUG -o target target.c

于是,预编译器发现DEBUG变量被定义了,就会使用TRACE函数。而如果要发布给用户了,那么只需要把取消“-DDEBUG”的参数,于是所有用到TRACE宏,这个宏什么都没有,所以源程序中的所有TRACE语言全部被替换成了空。一举两得,一箭双雕,何乐而不为呢?

顺便提一下,两个很有用的系统宏,一个是“__FILE__”,一个是“__LINE__”,分别表示,所在的源文件和行号,当你调试信息或是输出错误时,可以使用这两个宏,让你一眼就能看出你的错误,出现在哪个文件的第几行中。这对于用C/C++做的大工程非常的管用。

 

--------------------------------------------------------------------------------


综上所述32条,都是为了三大目的——

    1、程序代码的易读性。
    2、程序代码的可维护性,
    3、程序代码的稳定可靠性。
   
有修养的程序员,就应该要学会写出这样的代码!这是任何一个想做编程高手所必需面对的细小的问题,编程高手不仅技术要强,基础要好,而且最重要的是要有“修养”!

好的软件产品绝不仅仅是技术,而更多的是整个软件的易维护和可靠性。   

软件的维护有大量的工作量花在代码的维护上,软件的Upgrade,也有大量的工作花在代码的组织上,所以好的代码,清淅的,易读的代码,将给大大减少软件的维护和升级成本。

原创粉丝点击