开篇记:关于程序编写和阅读的一些思考
以下是自己关于程序编写及阅读中的一些体会,仅供参考。写了多年程序,走了很多弯路,有了点粗浅的认识,写在这里,欢迎指正。
第一部分,命名规则
基本原则:望文生意,直观,不要太长也不要太短。
好的命名规则能有效提高开发效率。但现实开发中,我们会遇到各种复杂的情况,即使有这样那样的指导原则,还是有个“度”的问题需要把握。
从这个角度上讲,指导原则的有效性值得商榷。
先列举下自己现用的命名规则,大多取自Google编码规范[1]。
1. 文件名
所有字母小写,单词之间用下划线。
2. 变量名
所有字母小写,单词之间用下划线,"名词" 或者“形容词+名词”形式:
(1)类的成员变量使用下划线结尾(但不适用于结构体成员变量,结构体可看成一种封装手段);
(2)全局变量以g_开头(尽量少用或不用全局变量);
3. 类名
所有单词首字母大写,不用下划线,用C打头(接口类用I打头),结构体名可不用C打头。
4. 常量命名
用k打头,其后所有单词首字母大写,不用下划线。
5. 函数命名
(1)一般使用“动词”或者是“动词+名词形式”,但对取函数(只返回属性而不改变状态)可使用“名词”。
(2)首字母小写,其后单词首字母大写,中间不加下划线,但存取函数(对类成员变量进行存取)例外:取函数的名字=变量名去掉结尾下划,存函数=set_变量名去掉结尾下划线。
(3)private/protected函数以下划线开头。
(4)回调函数以on或_on开头。
(5)虚函数以do或_do开头。
6. 枚举命名 枚举名所有单词首字母大写(使用单数而不是复数),不用下划线(同结构体名规则)。枚举类型采用全大写+下划线的方式。
总体而言,对物理实体,命名要指明是什么,而不是做什么;反之,对逻辑实体,命名要指明做什么而非是什么。
两点细节:
1 教科书中常用的抽象用词,诸如Data, Manager, Helper等,实际上,这种太抽象的词无法提供足够的信息,比如函数processData(...), “说了等于没说”,倒不如validateUserLogin(),eliminateFalseEntries(),computeTax(),或connection_pool来得直接。另外就是一些无明确含义的词做成员变量,如category,type,kind等。
2 在循环中常用的单字母循环变量i,j,k,如果只一层循环且意义明确,大多没问题,如果多层变量嵌套,最好指明含义,如,
for(int student_id = 1; student_id < 10; student_id ++)
for(int book_id = 1; book_id < 5; book_id++)
....
如此,循环体中不会出现i,j,k搅和得一团乱麻的现象了,便于查错。
很多时候,多花些时间琢磨命名,把握好这个度的概念是值得的!
以下是从网上摘录的内容:
关于类型命名(类,接口,和结构)
1 名字应该尽量采用名词 Happy -> Happiness
2 不要使用类似名字空间的前缀 SystemOnlineMessage-> System::OnlineMessage
3 形容词不要用太多,能描述清楚就行 IAbstractFactoryPatternBase -> IFactory
4 在类型中不要使用Manager 或Helper 等没意义的单词。
如果一定要在某类型上加上Manager或Helper,那么该类型要么就是命名甚至是设计可能存在问题。
5 如果某个类不能通过简单的命名来描述它具有的功能,可以考虑用类比的方式来命名IncomingMessageQueue->Mailbox,但如果使用类比,你就应该保持相关概念类比的一致性Mailbox,DestinationID->Mailbox,Address
关于函数(方法和过程)
1 简洁list.getNumberOfItems()->list.count(),不要太简洁list.verify()->list.containsNull(),又是个度的问题要把握
2 避免缩写 list.srt()->list.sort()
3 对于完成某件事情的函数使用动词,对于返回布尔型的函数,使用类似提问的方式list.empty()->list.isEmpty();或者是如下动词形list.contains(item);。
4 不要在函数名字中重复参数的名称 list.addItem(item);->list.add(item); handler.receiveMessage(msg);->handler.receive(msg);
5 不要方法的名字中重复此方法的类的名称 list.addToList(item);->list.add(item);
6 不要在函数的名字中加入返回类型,list.getCountInt(); ->list.getCount();除非函数名必须以返回类型进行区别,如message.getIntValue();和message.getFloatValue();
7 不要名字中使用And 或则 Or,如果你以此来连接函数名,那么该函数肯定是做了太多的事情,更好的做法是将其分成更小的函数来处理(类似面向对象设计准则中的责任单一原则)。 如果你想确保是这是一个原子的操作,那么你应该用一个名字来描述这个操作或一个类来封装
mail.verifyAddressAndSendStatus();->
mail.verifyAddress();
mail.sendStatus();
第二部分 软件开发相关
1 不论模块,函数还是其他,尽量保持其简单,Keep it simple stupid,如此能有效控制编写&维护成本,使代码更易读,更易重用,更好维护。2 对类而言,职责要尽量单一(同样牵扯到度的把握,太细致地抠有时候也会让人有吹毛求疵的嫌疑)。
3 Don't repeat yourself, 对于相似的代码,抽出共性。重复的内容会让代码中散发坏味道。
4 关于注释:
教科书大抵都强调注释很重要,可也有人说注释是“魔鬼”,见仁见智。
个人认为:合理的注释能有效帮助程序员梳理思路,方便未来阅读;
可现实中深受注释之苦的不在少数:
(1)很多时候,注释等于废话,相当于复述了一遍代码。
(2)更糟糕的是,有时候代码修改了,对应的注释没改,反而将读代码的人引向歧途。
实际上,注释应该解释的是why(为什么要做这个),而不是how(怎么做)
大多数情况下,合理的命名能让代码“自注释”,这是比较推荐的;在关键路径或操作上可附上简短说明。
那种先赶代码,最后为了形式主义添加注释的做法委实不可取。
5 排版,代码是适于人读的,小组内部最好采用统一的缩进格式,推荐采用自动化的工具,诸如Astyle等。
6 实际工程包含诸多模块和代码,合理组织架构很重要。架构既包括逻辑架构(模块,类接口等),也包括物理架构(不同文件夹存放)。
7 不存在完美的代码,也不存在一遍OK的代码。很多代码都是在演进和迭代重构中进步的。
尽管如此,动手/设计之前,还是要对整体架构和接口有教深入的理解;
因为,面向接口(what)编码,而非面向实现(How)编码,能节省大量的劳动。
现实中,很多程序员过度关注在小细节上,追求细微精妙处,但如果设计不合理,接口频繁变更,即使细节技术十分微妙,却可能在未来版本中根本派不上用场。本人也被不断变化的接口弄得头晕脑涨,理不顺关系,导致代码bug频出;
维茨金学习理论[2] 也指明先掌握学习时,先掌握脉络很重要,然后是“划小圈”理论,体会细微精妙之处,最后指出成功要靠不断地坚持和重复。
8 code review 很重要,是人总会犯错。理论上说审查者的水平应高于编码者的,但并非一定要如此。标准code review有个检查列表,在实际中并不建议使用。将过程弄得太正式会降低效率,规整地将检查列表打出来,导致的后果基本上就是只有检查表上的内容被“形式”化地检查,review的时间也不必过长,5分钟10分钟亦可。
9 单元测试,这是最接近BUG的地方,也是修改BUG成本最低的地方。有时间的情况下,应该写更多的,更好的单元测试案例。但个人并不推荐现在流行的“测试驱动开发”,虽然先写unit test case再编码可防止不会因为一个改动而引入Bug(写出不会出错的代码),但这并不会促进写出更好的代码,相反会让人束手束脚。有人指出,“测试驱动开发”不过是防止糟糕的程序员写出糟糕的代码!而且,代码能通过单元测试会成为很多程序员的唯一目标和为自己代码辩解的一种托辞
10 设计模式,不可否认是好的工具和概念,但要有点功底才能驾驭住。换言之,看用在谁手里,是手拿锤子看见什么都像钉子的入门者还是手中无剑胜有剑的高手,没有内力(对计算机的理解,对数据结构和算法的融会贯通),好的工具只会有更大的破坏力。
第三部分 如何读代码
1 从架构入手,有哪些文件夹,有哪些逻辑模块,相互之间的接口有那些。
2 判断所读代码是否值得阅读和借鉴,成千上万的代码,并不是每个都有看的必要。
3 从main入手,分析整理,结合UML类图进行思考。
4 遇到不懂/晦涩/精彩的代码等,问自己:作者为什么要这么写,有没有漏洞?有没有更好的思路?对以后自己写代码有何借鉴。
一家之言,许多偏颇遗漏,请指正,欢迎转载,不用注明出处。
[1] http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
[2] 《学习的艺术》,乔希·维茨金/著,中国青年出版社,2008年1月

浙公网安备 33010602011771号