代码规范_代码规范工具 - CSDN
  • 程序员必须要注意的代码规范

    千次阅读 2014-12-04 09:56:38
    您是否有因代码杂乱冗余而心生厌恶,您是否有过因代码晦涩难懂而抓狂,您是因代码低级的逻辑错误而愤概,您是否因代码结构不合常规而需要到处查找,您是否因看到几百甚至上千行代码的方法而望洋兴叹,您是否因代码...

    作为一个苦逼的程序员,因为种种原因经常需要阅读别人写的代码。您是否有因代码杂乱冗余而心生厌恶,您是否有过因代码晦涩难懂而抓狂,您是因代码低级的逻辑错误而愤概,您是否因代码结构不合常规而需要到处查找,您是否因看到几百甚至上千行代码的方法而望洋兴叹,您是否因代码缺少注释而猜测以及花很多时间去理清楚前后逻辑。苦逼的我全部遇到过并且很多很多,这些代码的作者从世界500强顶级IT公司到几十人的小公司同事,从印度美国新加坡人到中国人,从七八年的老人到刚入行的菜鸟。相信您也遇到过。这些人有一些共性,应付任务,个性十足以及不思进取!如果您是程序员菜鸟,那么您一定遇到很多并且既是肇事者又是受害者,这是必经之路;如果你是业界大牛,不用说您眼里别人的代码都或多或少都有问题,遇到的情况更多!

    都说代码是程序员的第二张脸,写了太烂的代码背后一定会遭人鄙视。请遵循一些简单的规范,写干净一致的代码!请把您的个性用在写出最简单易懂的代码上面,而不是晦涩冗余无用的代码,甚至自我签名!您的才华应该表现在设计良好的结构和模式,表现在干净易懂的代码,表现在对空间的尊重,对代码的尊重。这样您才能赢得别人的尊重!请记住代码不是一次性的,需要重复的修改和重构,为未来写点代码!以下我总结几点Java里面最基本的小规范:

    1.  写干净整洁的代码,请尊重空间,请尊重人眼的偏好

    1.1  去除没有用到的类引用,eclipse里面Ctrl+Shilft+O。看到一大片的因为类引用没有用到而报的警告信息简直就是灾难,空间和视觉都没有得到尊重。

    1.2  记得格式化代码,eclipse里面Ctrl+Shilft+F。看到一大片杂论无章,连基本的对齐都没有的代码也是很大的灾难,视觉脑力都会受到挑战。

    1.3  不要吝惜废弃的老代码,eclipse里面Ctrl+D。有些人对待已经废弃的老代码比对待自己的老婆还宝贝,大量运用注释来保留,随 着代码的演变暂用非常大量的空间。如果那段代码非常精妙,舍不得删,那么请把它移到您的私人代码库。

    1.4  请不要写冗余无用的代码。if(true)之类的代码块完全不应该出现,用空行隔开该段代码是更好的选择。

    1.5  请合理运用空行。空行可以用来隔开相对独立的代码块,有利于阅读和理解。但是不要使用超过一行的空行,对空间,别太奢侈了。

    1.6  请不要在两个地方出现完全相同的代码,您总是可以想办法重用的,不是吗?

    1.7 命名类,方法,变量慎用简写,除非大家都公认。全称我都不一定看得懂,简称您太高估我的智商了!请问qrbs代表什么,可能火星人知道吧!

    1.8 把所有的类变量放到最前面,如果比较多请按用途分组排列,不要把变量散落在大江南北,我找的真的很辛苦!

    1.9  拆分大的类,大的方法,如果您的类有一万行,如果您的方法有一千行,我真的会恐惧,是恐惧!

    2.  高效运用注释

    2.1  规范的注释类信息。请查看一下Java标准库的String.java源码吧,包括文件名,日期,作者,版本号等信息,用统一的模板。千 万别把您的大名散落于方法签名甚至于具体代码里面了。我在读业务逻辑的时候没有兴趣思考您的人生!要找您我可以在类签名里面找到的,放心啊!

    2.2  非Java Bean的public方法都需要注释,您总不会要别人读您的代码才能调用您的代码吧!即便您的英文非常好,命名也非常规范,您总不能期待别人都英文好,理解都一样吧。

    2.3  为不容易理解类变量注释。类变量特别是私有的类变量没有人要求注释,但是为了能够快速的了解您表示的是什么,还是写点什么吧!您知道我英文不算好!

    2.4  注释代码段,注释逻辑选择。上面提到运用空行分割开逻辑相对独立的代码,那么请在空行的下一行也写点下面代码段要干什么的语句吧。 如果有if else等逻辑选择的时候,麻烦也花几秒钟写上判断的依据和结果好吗?逻辑难懂且关键,您懂的!

    3. 不断学习,不断思考,不断实践,更上一层楼

    3.1 遇到不懂的,请先google,一定要学会,不然就一直不会了,回头发现工作六七年没有工作两年的小朋友懂的多!

    3.2 看别人代码时要汲取好的方法和技巧。

    3.3 接触一项技术要升入了解和实践,请问您做过的系统您现在都可以从零开始搭建起来了吗,我的意思是架构搭建哦!

    3.4 书里面有知识,有思想,有时间的话还是多精读几本经典书籍吧。您会受益匪浅!


    以上只是一点个人的想法,您可以接受或者反驳。谢谢您的时间,就这样!

    展开全文
  • 代码规范】常见编码规范

    万次阅读 2018-06-26 16:27:31
    如果一个功能将在多处实现,即使只有两行代码,也应该编写方法实现。说明:虽然为仅用一两行就可完成的功能去编方法好象没有必要,但用方法可使功能明确化,增加程序可读性,亦可方便维护、测试。 2.应明确规定对...

    文章来源:公众号-智能化IT系统。


    1.明确方法功能,精确(而不是近似)地实现方法设计。如果一个功能将在多处实现,即使只有两行代码,也应该编写方法实现。

    说明:

    虽然为仅用一两行就可完成的功能去编方法好象没有必要,但用方法可使功能明确化,增加程序可读性,亦可方便维护、测试。

     

    2.应明确规定对接口方法参数的合法性检查应由方法的调用者负责还是由接口方法本身负责,缺省是由方法调用者负责。

    说明:

    对于模块间接口方法的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。

     

    3.明确类的功能,精确(而不是近似)地实现类的设计。一个类仅实现一组相近的功能。说明:划分类的时候,应该尽量把逻辑处理、数据和显示分离,实现类功能的单一性。

    示例:

    数据类不能包含数据处理的逻辑。通信类不能包含显示处理的逻辑。

     

    4.所有的数据类必须重载toString() 方法,返回该类有意义的内容。说明:父类如果实现了比较合理的toString() , 子类可以继承不必再重写。

    示例:

    public TopoNode
     {
         private String nodeName;
         public String toString()
          {
             return "NodeName : " +  nodeName;
          }
     }

     

    5.数据库操作、IO操作等需要使用结束close()的对象必须在try -catch-finally finallyclose()


    6.异常捕获后,如果不对该异常进行处理,则应该记录日志(针对后台)。

    说明:若有特殊原因必须用注释加以说明。


    7.自己抛出的异常必须要填写详细的描述信息。

    说明:便于问题定位。

    示例:

    throw  new IOException("Writing data error! Data: " + data.toString());

     

    8. 在程序中使用异常处理还是使用错误返回码处理,根据是否有利于程序结构来确定,并且异常和错误码不应该混合使用,推荐使用异常。说明:一个系统或者模块应该统一规划异常类型和返回码的含义。但是不能用异常来做一般流程处理的方式,不要过多地使用异常,异常的处理效率比条件分支低,而且异常的跳转流程难以预测。

     

    9.避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的静态变量来代替。
    示例:

    如下的程序可读性差

    if  (state == 0)

    {

    state = 1;

    ... // program  code

    }

    应改为如下形式

    private final static int  TRUNK_IDLE = 0;
     private final static int TRUNK_BUSY = 1;
     private final static int TRUNK_UNKNOWN = -1;
     
     

    if (state == TRUNK_IDLE)

    {
         state = TRUNK_BUSY;
         ... // program code

    }

     

    10.数组声明的时候使用 int[] index ,而不要使用 int index[] 。说明:

     

    11.异常捕获尽量不要直接 catch (Exception ex) ,应该把异常细分处理。


    12.不要使用难懂的技巧性很高的语句,除非很有必要时。说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。


    公众号-智能化IT系统。每周都有技术文章推送,包括原创技术干货,以及技术工作的心得分享。扫描下方关注。



    公众号-智能化IT系统。每周都有技术文章推送,包括原创技术干货,以及技术工作的心得分享。扫描下方关注。

    展开全文
  • 解读阿里官方代码规范

    千次阅读 2017-12-27 15:30:54
    2017年开春,阿里对外公布了「阿里巴巴Java开发手册」。作为一个13年经验的码农,从头到尾浏览了一遍这份手册之后,感觉很棒。虽然其中的某些观点笔者不能...笔者将对这份代码规范中的一些细节做一些解读,包含笔者

    转载来源:https://www.cnblogs.com/winner-0715/p/7594254.html

    2017年开春,阿里对外公布了「阿里巴巴Java开发手册」。作为一个13年经验的码农,从头到尾浏览了一遍这份手册之后,感觉很棒。虽然其中的某些观点笔者不能苟同,但大部分的规范还是值得绝大多数程序员学习和遵守的。

    笔者将对这份代码规范中的一些细节做一些解读,包含笔者的观点和想法,可以作为这份代码规范的扩展阅读。对于规范中某些「显而易见」的条款,将不在解读范围之列(换言之,这都不懂,就说明你天赋不够,乘早别做程序员了)。

    当然,笔者在日常的编程过程中属于「代码洁癖偏执狂」,所以文中的某些观点仅代表个人看法,请勿人生攻击。

    阿里官方代码规范解读系列总计五篇,已在本公众号发过,本篇为合集,对之前文章中的部分内容作了修订。

    命名规约

    1.1.1 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束
    
    1.1.2 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式
    
    1.1.3 / 1.1.4 类名使用UpperCamelCase风格,必须遵从驼峰形式(某些情况诸如领域模型相关的命名除外);方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式
    
    1.1.5 常量命名全部大写,单词间用下划线隔开
    
    1.1.9 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词

    上述规则,主要是规定了你书写Java的时候,哪些字符可以用,什么时候用大写,什么时候用小写。应该说,绝大多数写Java的都遵循着上述的规范,就像笔者说的:尼玛这都不懂,乘早改行别写Java了。

    笔者在实际编程过程中,对类名的风格,可能更激进一些,根据阿里的规范:

    类名使用UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外:(领域模型的相关命名)DO / BO / DTO / VO等

    实际上还是有可能会存在着诸如:UserVO,UserDTO,UserDAO这样的命名。对不起,在笔者的团队中,这样的命名也会被禁止,这里分为2种情况:

    禁止使用 VO / BO / DTO / VO 等进行领域模型的命名

    有读者要问,那么如果万一项目中要使用DTO或者VO咋办?笔者的观点如下:

    第一,项目中避免使用DTO或者VO,DTO是一个早在2004年就被讨论并认定为一个反模式的东西;

    第二,谁规定领域模型就一定要用DTO或者VO做结尾?还原领域模型的本来意义才是命名的核心,一个User在实际业务系统中可能是一个Admin或者Supervisor,那就直接用Admin来命名,而不是把User转化成UserVO,UserVO啥都不是,是初级程序员造出来的一个怪胎。

    所有的DAO使用正常的驼峰法进行命名,例如:UserDao

    对上面这条,或许有很多DAO大写党要发飙了。其实笔者就想反问这些人一句:你咋不把UserService写成UserSERVICE呢?

    命名原则

    1.1.5 力求语义表达完整清楚,不要嫌名字长
    
    1.1.10 杜绝完全不规范的缩写,避免望文不知义

    这两条说的是命名的基本原则,总的来说其实表达了一个意思:你他妈的别给我用缩写!

    其实有很多程序员会非常神奇的患上「缩写综合症」。比如非常典型的就是:UserMgmt,这他妈是什么鬼?多敲几个字母会死么?

    类的命名

    1.1.6 抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类的名称开始,以Test结尾
    
    1.1.13 对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别
    
    1.1.13 如果是形容能力的接口名称,取对应的形容词做接口名
    
    1.1.14 枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开
    
    1.1.11 如果使用到了设计模式,建议在类名中体现出具体模式
    
    1.1.9 包名统一使用单数形式;类名如果有复数含义,类名可以使用复数形式

    上述规则,主要在讲述具体命名一个类的时候的一个「用词规范」。这些用词规范绝大多数实际上也是一种约定俗成,比如Abstract前缀,Exception后缀等等。

    对于接口的命名,笔者最为不能忍受的一种命名,就是将所有的接口以大写字母I开头,诸如:IUserService。真是一种坑爹到极致的命名:第一,IUserService纠结是一个啥玩意儿?好好的UserService,加上一个大写字母I,直接失去了阅读时的语义性;第二,谁他妈知道你这东西到底是大写字母I还是数字1啊?

    有关枚举类名是否加上Enum后缀,笔者对此有所保留。在笔者的团队中,是不使用Enum作为后缀的,但对此并不反感。至于枚举成员名称,不用大写字母并下划线隔开的,基本属于缺心眼行为。Enum的设计初衷就是对常量的规整和扩展,所以命名规范继承自常量是比较合理的一种选择。

    在命名中体现设计模式,相信这一点很多程序员都能遵守。因为在笔者看来,能在代码中熟练使用设计模式的同学,通常也不会是一个对自己毫无要求的烂货。这条命名规范在Spring以及很多优秀的开源项目中都有很深刻的体现。

    类名是否可以使用复数形式,相信主要的分歧来自于工具类。笔者的规定是:

    提供一系列静态方法的工具类,一概使用Utils作为后缀命名

    这条规范的依据,主要来自于笔者发现commons和spring这两个比较优秀的开源框架中提供的工具类通常都带s结尾。

    常量规约

    
    1.2.3 不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护
    
    1.2.4 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量
    
    1.2.5 如果变量值仅在一个范围内变化用Enum类。如果还带有名称之外的延伸属性,必须使用Enum类
    
    1.1.12 尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量

    常量规约的核心有两点:第一,别使用常量;第二,让常量可控。

    常量的存在按照笔者个人的理解是向下兼容的选择(因为JDK1.5之后才出现枚举)外加用起来足够爽(想象一下静态调用时引用的便捷性,甚至基本类型可以直接参与业务逻辑的计算)。

    所以在上述规则中,我们可以看到常量进化到枚举的趋势,也能看到由于用起来足够爽带来的常量管理需求:要求分组(1.2.3)以及要求放在合适的位置(1.2.4)。

    有关分组,笔者有不同意见:常量分组未必要分散到不同的类,在一个常量类中定义静态类也是一种分组方式,有时候这样的分组方式可能管理起来更有效。

    至于接口中只能定义常量不能定义变量,基本就属于幼儿园规则了。

    语法糖

    
    1.1.12 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的Javadoc注释
    
    1.4.2 所有的覆写方法,必须加@Override注解
    
    1.4.3 可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)
    
    1.4.4 对外暴露的接口签名,原则上不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么
    
    1.4.5 不能使用过时的类或方法
    
    1.4.10 序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败
    
    1.4.17 循环体内,字符串的联接方式,使用StringBuilder的append
    
    1.4.18 final可提高程序响应效率
    
    1.4.19 慎用Object的clone方法来拷贝对象

    有关语法糖的总结其实比较牵强,因为绝大多数的规则看上去都比较小儿科,比如像覆写方法的@Override注解,@Deprecated注解,可变参数的问题等等基本上都在IDE层面解决了。

    当一个项目在IDE中产生了一些由于使用过时方法之类的事儿导致的warning时,有洁癖的程序员应该主动修复这个warning。这也是是一个程序员的基本素养问题。

    最后的三条,笔者认为有点鸡肋,对于初级程序员,大多还到不了考虑final和clone的层次;而中高级程序员,这几条规则对他们而言并无问题。

    基本类型

    
    1.4.7 所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较
    
    1.4.8 所有的POJO类属性必须使用包装数据类型
    
    1.4.8 RPC方法的返回值和参数必须使用包装数据类型
    
    1.4.8 所有的局部变量【推荐】使用基本数据类型

    有关基本类型的声明(1.2.2)和比较(1.4.7),这两条规则比较直观,不再叙述。

    而有关基本类型和包装类型的使用,这东西一直是吵架的核心。用还是不用?这是个问题!很显然,阿里同学的观点是:为了提高程序的容错性和扩展性,尽可能使用包装类型。

    从阿里同学举的例子来说,也是有一定说服力的:

    数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险
    
    比如显示成交总额涨跌情况,即正负x%,x为基本数据类型,调用的RPC服务,调用不成功时,返回的是默认值,页面显示:0%,这是不合理的,应该显示成中划线-。
    所以包装数据类型的null值,能够表示额外的信息

    不过笔者认为,如果程序员对程序能够驾驭得比较好,基本类型也是一种很好的选择。因为基本类型有一些比较好用的特性:比如说默认值。笔者在这里也举个例子进行说明:

    通常我们都会用is_disabled字段在数据库中表示某一个表的记录是否被逻辑删除,而这个字段,在Java代码中被映射成什么类型呢?
    
    Boolean?如果被映射成包装类型,那么数据库里面的这个字段就可以为null,有些读者会说,这并没有什么问题啊。可是,数据库is_disabled字段如果为null,代表什么逻辑含义呢?
    这条记录究竟是有效还是无效? 如果这个字段不能为null,那么将其映射成基本类型是一个皆大欢喜的事情:既保证了数据库数据的完整性,我们在初始化的时候还可以忽略这个字段,因为boolean天生的默认值就是false

    所以,笔者对于包装类还是基本类型的结论是:

    一切跟着业务的实际情况而定,基本类型也有其生存空间

    方法命名

     - 获取单个对象的方法用get做前缀
     - 获取多个对象的方法用list做前缀
     - 获取统计值的方法用count做前缀
     - 插入的方法用save(推荐)或insert做前缀
     - 删除的方法用remove(推荐)或delete做前缀
     - 修改的方法用update做前缀

    有关方法的命名,笔者想多说几句不同意见。对于上述的规则,笔者认为适合在DAO这个层次进行实践,而不能应用于Service层。

    使用Hibernate作为持久层框架的读者,对Hibernate的API应该比较熟悉,而上面的命名规范,和Hibernate对外暴露的API名称是很接近的。我们知道,通常到了DAO这个层次,数据库操作相对来说是一个原子操作,所以增删改查的语义是最适合做方法命名的。这也就是笔者认为这套规则在DAO层能够被实践得很好的一个原因。

    当然,上述规则中有一个例外:

    获取单个对象用load做前缀,避免使用get

    原因很简单,get可能是getter方法的前缀,作为一个偏执狂,老子不冒风险。

    话题回到Service的命名上来,为什么笔者不认同使用相同的命名规范作用于Service层呢?因为Service层通常是对外暴露的接口,具有一定的业务意义,也就是说Service层通常也不会是简单的增删改查,而是若干原子操作的集合。

    举两个很简单的例子:发短信。发短信这个业务中可能包含了本地配置的读取、本地数据库的读写,远程服务的调用。我们可以看到这是一连串数据库操作甚至是异构系统调用的集合实现,能用简单的增删改查来命名吗?所以,笔者的观点很简单:

    Service层接口方法的命名,应还原业务的本来面目,采用动词或者动宾结构来进行方法的命名

    举例来说:resetPassword / login / sendMessage 都是比较合理的命名方式。

    方法和属性

    
    1.4.11 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中
    
    1.4.14 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读
    
    1.4.15 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法
    
    1.4.16 setter方法中,参数名称与类成员变量名称一致,this.成员名=参数名。在getter/setter方法中,尽量不要增加业务逻辑
    
    1.4.20 类成员与方法访问控制从严

    这几条规约理解起来不难,执行起来也不难。要探究背后的原因,可能就需要花点功夫。

    比如,构造方法和setter/getter方法禁止加入业务逻辑,主要是这些方法有很大概率被程序框架的反射机制调用。一旦其中含有业务逻辑,那么调试和定位就会变成灾难

    不过对于getter方法,通常要网开一面。因为在实际情况中,我们往往会在一个POJO中加入额外的getter方法用于序列化或者内部逻辑的使用。在这种情况下,避免和其他getter方法产生分歧是需要注意的地方。

    至于说到类内的方法定义顺序,笔者基本同意上述规则,但在实际执行中可能更加严格:getter和setter方法的顺序也有严格讲究,必须是先getter方法,后setter方法,而不是让它们成对出现。

    有关类成员和方法的访问控制,阿里的同学洋洋洒洒说了好几条,语法层面偏多,在这里就不再详细展开。

    格式规约

    格式规约是代码规范中争议最大的,由于条目众多,在这里就不逐一解读,挑选几条大致说一下。

    
    1.3 6. 单行字符数限不超过 1201.3.8 IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用windows格式
    
    1.3.10 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行

    绝大多数情况下,空格党和Tab党的较量是空格党完胜。笔者也不记得是多少年前被一位前辈教育说禁止使用Tab,就保持了良好的习惯至今。对于缩进,个人比较赞同4个空格,但HTML等页面上使用4个空格的话,一些复杂页面的缩进就会比较恐怖,此时可以降级为2个空格。

    对于单行字符数的限制不超过120个这条规则,笔者完全不能认同。这里面牵涉到的情况比较多,不能一棒子打死了。有些逻辑有大量的分支和循环的嵌套,如果遵循4个空格的缩进原则,碰到方法名称还比较长的状况,就要折行,这给代码阅读带来极大的困扰;另外有一种情况,就是需要额外进行比较长的注释编写,不能写在一行里的感觉真是比较糟糕,因为还得考虑断句才不影响阅读。另外,笔者有一个习惯是在条件语句边上加一句注释,这样就有很大概率会超出120字:

    有人会问,条件语句边上加注释是什么鬼?从上面的代码上可以看到,条件语句上面的一行注释实际上在解释整个代码片段,而条件语句边上的注释说明的是条件语句本身!当然如果读者有更好的写注释的位置,请及时给笔者留言。

    文件的UTF-8和Unix格式没什么好说的,IDE支持的也很好。但这一点对初级程序员尤为重要,我已经不知道多少次就这个问题惩罚过实习生了。

    有关语句组空行,是笔者极力推荐的一个做法。这不仅仅是为了空行而空行,这里的空行本身就是一种编程思路的整理。而笔者还有一个习惯就是对比较复杂的逻辑,都在语句组的前面加上注释,注释也用编号编排,这样回头debug时也会极大提升效率。

    集合类型

    阿里规范中的集合类型这个章节,感觉写得比较鸡肋。绝大多数的规范似乎都是针对初级程序员的。笔者看了半天也没总结出一条值得额外解读的,所以权当复习一遍基础知识就好。

    并发处理

    
    1.6.2 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯
    
    1.6.3 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
    
    1.6.4 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor去创建

    上面这4条规则主要是针对线程的创建和使用。由于Spring的存在,其实上述情况不太可能发生。

    1.6.5 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁
    
    1.6.7 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁
    
    1.6.8 并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据

    上面这3条规则主要是针对锁。不过这几条规则看上去更像是3道面试题的答案。这3道面试题分别是:

    使用锁同步,有什么需要注意的地方?
    
    什么是死锁?举例说明什么情况会发生死锁?
    
    什么是乐观锁?什么是悲观锁?分别用在什么场景?

    相信能解答上述面试题的同学,应该对上面的原则了然于心。

    
    1.6.10 使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法可以执行,
    避免主线程无法执行至countDown方法,直到超时才返回结果回溯
    1.6.13 volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题 1.6.14 HashMap在容量不够进行resize时由于高并发可能出现死链 1.6.15 ThreadLocal无法解决共享对象的更新问题,ThreadLocal对象建议使用static修饰。这个变量是针对一个线程内所有操作共有的,所以设置为静态变量,

    所有此类实例共享此静态变量

    上面这些规则基本上属于知识贴范畴,可以一带而过,有些可能不太会实际碰到。像定时任务,可能使用Spring的封装更多一些,而Spring默认就是使用ScheduledExecutorService的。

    而CountDownLatch的异常捕获,也是一个老生常谈的问题了,属于多线程编程的基本功。

    最后的三条对于写应用的同学接触不多,但写底层的同学应该会很熟悉。

    注释规约

    注释规约的内容比较多,这里也仅挑选一些具有代表性的进行解读 

    
    1.8.6 与其"半吊子"英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可
    
    1.8.8 注释掉的代码尽量要配合说明,而不是简单的注释掉
    
    1.8.10 好的命名、代码结构是自解释的,注释力求精简准确、表达到位

    枚举类加注释是非常必要的,因为枚举通常是都是常量的扩展,而常量是需要说明的。

    鉴于很多程序员的英语水平,笔者建议英语不够好的程序员直接使用中文写注释。

    对于注释掉的代码,笔者的意见是在绝大多数情况下应该直接删除,除非在很短的时间内还有恢复的余地。

    有关什么是好的命名和代码结构,什么样的命名能够使得代码做到自解释,笔者将另外撰文进行说明。

    数据库规约

    数据库规约本身并不属于Java规约的范畴,不过阿里的规范中包含了不少数据库规约的内容,所以笔者也同样加以解读。

    
    3.1 2 表名、字段名必须使用小写字母或数字;禁止出现数字开头,禁止两个下划线中间只出现数字
    
    3.1.3 表名不使用复数名词
    
    3.1.4 禁用保留字
    
    3.1.5 唯一索引名为uk_字段名;普通索引名则为idx_字段名
    
    3.1.10 表的命名最好是加上业务名称_表的作用
    
    3.1.11 库名与应用名称尽量一致

    上述规约主要说的是库、表、字段的命名规约。应该说绝大多数的上述规约都是参考项,需要根据实际情况进行调整,我们逐条来说。

    有关布尔值的数据库映射,对于使用is_xx进行命名没有异议,对于数据类型是否应该使用tinyint稍有保留,笔者实际上使用bit更多。由于布尔值所对应的Java类型是boolean,所以笔者通常在命名时,利用boolean的默认值特性,对一些常用的命名进行更加严格的规定。比如「是否有效」,命名成为「is_disabled」就要比「is_enabled」来的好。因为 is_disable = false 是绝大多数程序的事实逻辑,这样就可以利用boolean值默认为false的特性。

    Java中的绝大多数命名都使用驼峰法,而数据库的命名实际上更加严格。光光小写是不够的,而是要强制使用下划线命名法(主要是因为SQL是大小写不敏感的语言)。笔者在实际工作中经常看到使用驼峰法命名表名或者字段名的,这种基本上属于小学没毕业的行为。

    有关表名不能使用复数,不能使用关键字,这些属于比较基础的命名规范,应该遵守。但是笔者在这里提出更为严格的要求:不仅不能使用SQL关键字进行命名,同样不允许使用Java关键字!因为绝大多数情况,数据库字段会被映射到相应的Java对象,如果可以使用Java关键字,那么映射的时候就是自找麻烦了。

    最后三条规约属于建议,相信每个公司都有自己独特的规定。比如笔者见过有一些写Oracle出身的程序员,习惯使用tbl_做表名的前缀,使用vw_做视图的前缀。个人觉得这个方面不宜做过多规定,只要团队保持风格整体一致即可。

    3.1.6 小数类型为decimal,禁止使用float和double
    
    3.1.7 如果存储的字符串长度几乎相等,使用char定长字符串类型
    
    3.1.8 varchar是可变长字符串,不预先分配存储空间,长度不要超过5000

    这三条主要说的是数据库设计时的类型规约。

    除了上述三条之外,在笔者团队另外还会遵守如下几条:

    明确日期和时间,日期使用date类型并使用xxDate进行Java字段命名,时间使用date_time类型并使用xxTime进行Java字段命名,以示区分

    上面这条主要是和日期时间有关的,强制这样的规约,对于提升代码的可读性是有帮助的。

    枚举类型在数据库中既可以映射成int,也可以映射成varchar,视实际情况定

    通常对于排序和检索有强依赖的,枚举类型映射成int比较理想,否则可以映射成varchar。虽然从效率上说,int基本上会强于varchar,但varchar毕竟可读性更好,所以还是应该一分为二来看。

    
    3.1.8 如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率
    
    3.1.12 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释
    
    3.1.13 字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况
    
    3.1.14 单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表
    
    3.1.15 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度

    上面的规约主要涉及到一些数据库表设计上的原则。

    其中,3.1.8是非常值得大家注意的一点,笔者个人的习惯是对于大字段,拆表的同时,优化SQL,尽可能做到用主键单独取大字段,避免产生效率瓶颈。

    而3.1.14是希望提醒一些自视甚高的架构师不要过早的进行过度设计。这里笔者提一点:

    对于每一张数据库表的设计,应该预估表在未来若干时间段内的数量,以采取最佳的程序处理措施

    这里所说的最佳程序处理措施包括并不限于:使用应用级别缓存对数据库进行减压;选取合适的时间点对表进行分库分表;是否进行人为拆表以保证较快的SQL执行等等。

    有关3.1.13,我们在有关SQL编写环节还会说到。

    
    3.2.2 超过三个表禁止join
    
    3.2.3 在varchar字段上建立索引时,必须指定索引长度
    
    3.2.4 页面搜索严禁左模糊或者全模糊
    
    3.2.5 如果有order by的场景,请注意利用索引的有序性
    
    3.2.7 利用延迟关联或者子查询优化超多分页场景
    
    3.2.9 建组合索引的时候,区分度最高的在最左边

    上述规约主要讲的是和索引相关的内容。对于这块,笔者不是专业的DBA,所以只是挑了其中和程序开发特别有关的来讲一讲。

    比如3.2.2的禁止超过3个表的join,在笔者的团队中,规定更为严格:

    禁止超过2个表的join语句出现在程序中

    其实不许使用join是很多初级程序员非常不能理解的。要说明白这个问题,估计又要长篇大论,笔者会另辟文章进行说明。但这里还是引用一下robbin的观点(笔者表示深刻赞同):

    另外有关严禁使用全模糊查找,建组合索引时,区分度最高的往左放这些原则,在一定程度上会改变我们编写程序的习惯,所以应该时刻注意。

    
    3.3.5 在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句
    
    3.3.6 不得使用外键与级联,一切外键概念必须在应用层解决
    
    3.3.7 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性
    
    3.3.9 in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在1000个之内
    
    3.4.1 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明

    上面的这几条属于SQL编写规约。阿里的规范中洋洋洒洒讲了很多条,实际上都是在给程序员提个醒,笔者在这里不在赘述

    有关count(*)的争论,一直有大量的说法。此次阿里的规范总算为count(*)党找到了SQL标准,应该说也基本为这件事情画上了句号。

    有关外键和级联,笔者稍有困惑的是外键。因为按照笔者的理解,外键影响数据库插入的速度应该有限,与外键约束带来的好处相比,或许还是有外键更好一些(有这方面经验的读者可以留言指点迷津)。级联是恶魔,必须禁止。

    至于存储过程,或许Oracle出身的DBA会跳出来唱反调了。笔者的观点和阿里相同:存储过程很难移植和维护,应该抛弃

    有关表查询中不许使用 * 作为查询的字段列表,这点或许能够成为规约,但笔者并不十分认同。尤其是对于使用Hibernate作为ORM工具的同学来说,这条规则执行起来有难度。

    代码风格

    1.7.1 在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;
    在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有

    这条主要是期待程序员人为把握好代码的执行逻辑。对于switch语句,如果没有终止语句,会依次执行每一个case块。实际上,笔者认为switch语句是一个比较差的语法糖,通常情况下都可以用更加优雅的方式来写,包括并不限于使用设计模式。所以在笔者的团队中是禁止使用switch语句的。

    1.7 2 在if/else/for/while/do语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:if (condition) statements;

    这一条比较有意思,因为这种一行式的代码风格,在javascript里面经常会看到,所以很多全栈工程师也会把它引入到Java中来。笔者对此并不反感,但确实在可读性上不那么友好。

    if(condition){
    ...
    return obj;
    }
    // 接着写else的业务逻辑代码;
    
    1.7.3 如果非得使用if()...else if()...else...方式表达逻辑,【强制】请勿超过3层,超过请使用状态设计模式

    上面这点笔者比较认同,因为else不仅会带来大段的代码缩进的困扰,同时也会降低代码的可读性。不过对于那些坚持必须在代码的最后一行统一return的同学,上面的写法可能就不太容易接受了。实际上,上述代码结构比较常见于Spring的源码中,倒不是尽早return,而是else的逻辑块可能直接throw异常出去了。

    1.7.4 除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性
    boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
    if (existed) {
    ...
    }

    有关这一条,补充说明一下:将复杂的逻辑判断结果赋值给一个有意义的布尔变量名,除了提高可读性之外,实际上能够极大方便调试。但笔者认为单单只是抽取部分代码,并不能提高可读性,而是应该将复杂的逻辑判断进一步封装为一个方法

    上面的代码片段中,左边是阿里风格,右边是陆老师的风格,大家可以比较一下,哪个更好?哪个更符合面向对象的思维呢?

    1.7.5 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的try-catch操作

    这一条值得说一下,因为有些代码会走得比较深,写着写着就忘了它处于循环体的内部了。所以保持一个谨慎的心态比较重要。

    1) 调用频次低的方法。 
    2) 执行时间开销很大的方法,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
    3) 需要极高稳定性和可用性的方法。 
    4) 对外提供的开放接口,不管是RPC/API/HTTP接口。
    5) 敏感权限入口。
    
    1.7.8 方法中不需要参数校验的场景: 
    1) 极有可能被循环调用的方法,不建议对参数进行校验。但在方法说明里必须注明外部参数检查
    2) 底层的方法调用频度都比较高,一般不校验。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般DAO层与Service层都在同一个应用中,
    部署在同一台服务器中,所以DAO的参数校验,可以省略
    3) 被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数

    这两条说的是参数校验,说的比较在理,也比较全面。比起很多公司的奇葩规定来说,要人性化得多。笔者认为需要补充的是:参数的校验主要还需要从格式和业务两个层面进行考量。业务层面的校验往往要比单纯的格式校验更为复杂,所以在写代码时可以建立一定层次的假设,当然这可能也会引入团队沟通的问题,需要根据实际情况权衡。

    有关阿里代码风格方面的解读,受限于阿里自身提出的规约比较少。对此,笔者是稍有失望的。因为代码风格规约是最能够体现一个团队对于代码整洁程度的一个衡量标准。所以笔者忍不住在这里多加了几条笔者团队的共识,供读者参考:

    在任何情况下,代码量越少越容易维护

    基于上面的原则,笔者的团队会鼓励使用三目表达式对简要的if/else进行重构

    当然,像下列左侧的代码,也会重构成右侧的:

     

    一个复杂的Service层逻辑,不应超过30行,否则需要进行逻辑规整和抽象

    在业务逻辑中尽可能不要使用setter方法,而是使用构造函数或者封装成一个有逻辑意义的方法,提高代码的可读性

    什么?连setter方法都不让用?这是什么SB一样的规约啊!事实上,笔者团队确实是这么做的,我们来看一下代码:

     

    在上面的代码中,左侧代码中的setter方法调用,会被封装到ShuttleOrder对象中的cancel方法中去。在实际的service代码中,只会出现下半部分的一行代码。

    这样做的好处在于:cancel这个方法被封装后,shuttleOrder.cancel()的调用从可读性上要明显优于使用2句setter方法,同时也为将来的逻辑扩展预留了位置。这也是面向对象的一种实践。


    展开全文
  • 代码编写规范

    千次阅读 2019-04-23 22:48:44
    排版格式2.1 类及其排版格式2.2 函数的声明与定义2.3 空行2.4 代码行2.5 代码行内的空格2.6 对齐2.7 长行拆分2.8 修饰符的位置2.9 代码段中的预处理格式2.10 被注释的代码2.11 注释3.命名规则4.表达式和基本语句4.1 ...

    1.目标

    我们所认为的优秀程序员应该具备的能力是什么?高效的算法,优良的架构,设计模式,面向对象等等,这些我们每天挂在嘴上,喊着要学习的技能。确实,能很好的掌握这些技能是成为优秀程序员的条件,但是作为程序员最根本,最基础的是代码编写,如何编写高质量,可靠的,规范的代码,是作为一名合格程序员的基础和根本,本规范规定了我们编写代码的格式,排版,注释,函数,变量,命名等,从这些最基础的编码规范来纠正我们的一些编码错误,要写出高质量的代码这些都是第一步,只有把这个基础打扎实,我们才可能“建设”稳健的“代码大厦”。

    2.排版格式

    程序排版格式虽然不是十分严格的规范要求,但整个项目都服从统一的编程风格,这样使所有人在阅读和理解代码时更加容易。格式虽然不会影响程序的功能,但会影响可读性。程序的排版格式追求清晰、美观,是程序风格的重要构成因素。可以把程序的排版格式比喻为“书法”。好的“书法”可让人对程序一目了然,看得兴致勃勃。差的程序“书法”如螃蟹爬行,让人看得索然无味,更令维护者烦恼有加。请程序员们学习程序的“书法”,弥补大学计算机教育的漏洞,实在很有必要。

    2.1 类及其排版格式

    • 声明属性依次序是public:、protected:、private:。
    • 关键字public,protected,private不要缩进,声明的函数和变量缩进一个制表符。
    • 类声明前应加上注释,注明该类的主要作用。
    • 不改变对象成员变量的类成员函数都应标记为const,可以预防意外的变动,提高程序的健壮性。
    • 类中成员必须进行初始化,可以通过构造函数的初始化列表初始化成员或者写一个专门初始化成员的函数(如init())。
    • 有继承关系的基类中析构函数一定要声明为虚函数。
    • 为了防止头文件重复包含,应在头文件处加上#ifndef/#define/#endif宏。
    • 函数和成员变量的声明分开。
      类声明的基本格式如下:
      class MyClass : public OtherClass 
      { 
      public:
      MyClass(); 
      MyClass(int var);
      ~MyClass() {} 
      void someFunction(); 
      void someFunctionThatDoesNothing()
      void set_some_var(int var)
      int some_var() const 
      
      private: 
      bool someInternalFunction(); 
      
      private:
      int some_var_;
      int some_other_var_; 
      }; 
      
      

    2.2 函数的声明与定义

    • 返回类型和函数名在同一行,合适的话,参数也放在同一行。

    • 返回值总是和函数名在同一行;参数列表的左圆括号总是和函数名在同一行。

    • 函数名和左圆括号间没有空格;圆括号与参数间没有空格。

    • 左大括号总是新起一行;右大括号总是单独位于函数最后一行。

    • 函数的声明(头文件)和实现处(CPP)的所有形参名称必须保持一致。

    • 函数的内容总与左括号保持一个制表符的缩进。

    • 参数间的逗号总加一个空格。

    • 函数的大小一般不要超过50行,函数越小,代码越容易维护。

    • 函数声明前应加上注释,注明该函数的作用,如果该函数有比较多的参数,还应该加上参数含义和返回值的注释。

    • 如果函数的参数是类对象,应使用对象的指针或引用来传递,以提高效率。

      ReturnType ClassName::FunctionName(Type par_name1, Type par_name2)
      { 
      DoSomething(); 
      ...
      } 
      	如果同行一行文本较多,容不下所有参数时:
      ReturnType ClassName::FunctionName(Type p_name1,
       								   Type p_name2,
                                                                Type p_name3)
      { 
      DoSomething(); 
      ...
      } 
      
    • 如果函数为const的,关键字const应与最后一个参数位于同一行。

      ReturnType  FunctionName(Type par)  const 
      { 	
      ...
      } 
      

    2.3 空行

    空行起着分隔程序段落的作用。空行得体(不过多也不过少)将使程序的布局更加清晰。空行不会浪费内存,虽然打印含有空行的程序是会多消耗一些纸张,但是值得。所以不要舍不得用空行。

    • 在每个类声明之后、每个函数定义结束之后都要加空行。
    // 空行
    void Function1(…)
    {
      …
    }
    // 空行
    void Function2(…)
    {
      …
    }
    
    • 在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。
    // 空行
    while (condition)
    {
    	statement1;
    	// 空行
    	if (condition) 
    	{
    		statement2;
    	}
    	else
    	{
    		statement3;
    	}
    // 空行
    	statement4;
    }
    
    

    2.4 代码行

    • 一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。
    • if、for、while、do等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。

    示例2-4(a)为风格良好的代码行,示例2-4(b)为风格不良的代码行。

    int width = 0;	// 宽度
    int height = 0;	// 高度
    int depth = 0;	// 深度	
    
    2-4(a)
    
    int width, height, depth; // 宽度高度深度
    
    2-4(b)
    
    
    x = a + b;
    y = c + d;
    z = e + f;
    
    2-4(a)
    
    X = a + b;   y = c + d;  z = e + f;
    2-4(b)
    
    
    if (width < height) 
    {
      dosomething();
    }	
    for (initialization; condition; update)
    {
       dosomething();
    }
    // 空行
    other();
    
    2-4(a)
    
    if (width < height) dosomething();
    for (initialization; condition; update)
         dosomething();
    other();
     2-4(b) 
     
    示例2-4(a) 风格良好的代码行                 示例2-4(b) 风格不良的代码行
    
    • 在定义变量的同时必须初始化该变量。
      如果变量的引用处和其定义处相隔比较远,变量的初始化很容易被忘记。如果引用了未被初始化的变量,可能会导致程序错误。例如
    int width = 10; 	// 定义并初绐化width 
    int height = 10;	// 定义并初绐化height 
    int depth = 10; 	// 定义并初绐化depth 
    

    2.5 代码行内的空格

    • ‘,’之后要留空格,如Function(x, y, z)。如果‘;’不是一行的结束符号,其后要留空格,如for (initialization; condition; update)。
    • 赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。
    • 一元操作符如“!”、“~”、“++”、“–”、“&”(地址运算符)等前后不加空格。
    • 像“[]”、“.”、“->”这类操作符前后不加空格。
    void Func1(int x, int y, int z);          // 良好的风格
    void Func1 (int x,int y,int z);           // 不良的风格
    
    if(year >= 2000)                     // 良好的风格
    if(year>=2000)                      // 不良的风格
    
    if ((a>=b) && (c<=d))                // 良好的风格
    if(a>=b&&c<=d)                    // 不良的风格
    
    for (i=0; i<10; i++)                   // 良好的风格
    for(i=0;i<10;i++)                     // 不良的风格
    for (i = 0; i < 10; i ++)                 // 过多的空格
    
    x = a < b ? a : b;                      // 良好的风格
    x=a<b?a:b;                          // 不好的风格
    
    int *x = &y;                         // 良好的风格  
    int * x = & y;                        // 不良的风格  
    
    array[5] = 0;                         // 不要写成 array [ 5 ] = 0;
    a.Function();                         // 不要写成 a . Function();
    b->Function();                        // 不要写成 b -> Function();
    
    示例2-5 代码行内的空格
    

    2.6 对齐

    • 程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。
    • { }之内的代码块在‘{’右边一个制表符左对齐。

    示例2-6(a)为风格良好的对齐,示例2-6(b)为风格不良的对齐。

    void Function(int x)
    {
    … // program code
    }	
    2-6(a) 
    
    void Function(int x){
    … // program code
    }
    2-6(b)
    
    
    if (condition)
    {
    … // program code
    }
    else
    {
    … // program code
    }
    2-6(a) 
    
    if (condition){
    … // program code
    }
    else {
    … // program code
    }
    2-6(b)
    
    for (initialization; condition; update)
    {
    … // program code
    }
    2-6(a) 
    
    for (initialization; condition; update){
    … // program code
    }
    2-6(b)
    
    While (condition)
    {
    … // program code
    }
    2-6(a) 
    
    while (condition){
    … // program code
    }
    2-6(b)
    
    如果出现嵌套的{},则使用缩进对齐,如:
    	{
    	   …
    		 {
    		   …
    		 }
    	  …
        }	
    示例2-6(a) 风格良好的对齐                       示例2-6(b) 风格不良的对齐
    

    2.7 长行拆分

    • 代码行最大长度宜控制在70至80个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。
    • 长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
    if ((very_longer_variable1 >= very_longer_variable12)
    && (very_longer_variable3 <= very_longer_variable14)
    && (very_longer_variable5 <= very_longer_variable16))
    {
        dosomething();
    }
    
    virtual CMatrix CMultiplyMatrix (CMatrix leftMatrix,
                                     CMatrix rightMatrix);
    
    for (very_longer_initialization;
    	 very_longer_condition;
    	 very_longer_update)
    {
    	dosomething();
    }
    示例2-7 长行的拆分
    

    2.8 修饰符的位置

    修饰符 * 和 & 应该靠近数据类型还是该靠近变量名,是个有争议的活题。
    若将修饰符 * 靠近数据类型,例如:int* x; 从语义上讲此写法比较直观,即x是int 类型的指针。
    上述写法的弊端是容易引起误解,例如:int* x, y; 此处y容易被误解为指针变量。虽然将x和y分行定义可以避免误解,但并不是人人都愿意这样做。

    • 应当将修饰符 * 和 & 紧靠变量名
      例如:
    char  *name;
    int   *x, y;	// 此处y不会被误解为指针
    

    2.9 代码段中的预处理格式

    • 预处理指令不要缩进,从行首开始。即使预处理指令位于缩进代码块中,指令也应从行首开始。
      例如:
    if(lopsided_score)
    { 
    #if DISASTER_PENDING 	// 正确 – 预处理从行首开始
    dropEverything(); 
    #endif 
    BackToNormal(); 
    } 
    
    

    2.10 被注释的代码

    • 在代码中经常残留一下被注释的代码,如果这段代码还有价值,必须对该段代码加上被注释的原因,或者不需要有的就直接删除。

    2.11 注释

    注释虽然写起来很痛苦,但对保证代码可读性至为重要。当然,注释的确很重要,但最好的代码本身就是文档,类名和变量名意义明确要比通过注释解释模糊的命名要好的多。注释是为别人(下一个需要理解你代码的人)而写的,认真点吧,下一个人可能就是你!如何写注释请详见《合约编程》

    • 注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱。注释的花样要少。
    • 如果代码本来就是清楚的,则不必加注释。否则多此一举,令人厌烦。单行注释用//,多行注释用/* */。
      例如 i++; // i 加 1,多余的注释
    • 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
    • 注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害。
    • 尽量避免在注释中使用缩写,特别是不常用缩写。
    • 注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。

    3.命名规则

    命名规则是软件编程中重要的规则之一,规范命名风格会使代码风格保持一致,更容易被团队中其他伙伴理解。
    总体原则是所有命名必须做到见名知意。

    • 标识符应当直观且可以拼读,可见名知意,不必进行“解码”。
      标识符最好采用英文单词或其组合,便于记忆和阅读。切忌使用汉语拼音来命名。程序中的英文单词一般不会太复杂,用词应当准确。例如不要把currentValue写成nowValue。

    • 命名规则要求符合匈牙利命名法。

    • 所有的变量,函数,类的命名,若需要多个单词时,每个单词直接连写,不要用下划线(“_”)或横线(“-”)分开。如:DeviceInfo,RemoteCamera。

    • 类的名字应当使用“名词”。能准确表示被抽象的事物。首字母以大写开头。
      例如:要重新一个摄像机,可以定义一个Camera类。

    • 函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。
      例如:
      DrawBox(); // 全局函数
      box->Draw(); // 类的成员函数

    • 变量的名字应当使用“名词”或者“形容词+名词”。首字母以小写开头。
      例如:
      float value;
      float oldValue;
      float newValue;

    • 对于变量命名,禁止取单个字符(如i、j、k…),建议除了要有具体含义外,还能表明其变量类型、数据类型等,但i、j、k作局部循环变量是允许的。

    • 变量名字需要加上作用域的前缀,类的成员变量加上“m_”前缀,局部变量不加作用域前缀,全局变量加上“g_”前缀。
      变量型前缀见下表:
      |前缀 | 类型说明 |
      | ------ | ------ |
      |n |整型变量(int/short/long)|
      |c |字符型变量(char)|
      |psz| 字符型变量指针char*|
      |b |布尔型变量(bool)|
      |w |整型变量word|
      |dw |整型变量DWORD|
      |bt |字节变量byte|
      |e |枚举型变量(enum)|
      |d |浮点型变量(double)|
      |sz |字符型变量(char[n])|
      |str | 字符型变量(string)|
      |obj | 类实例变量|
      |pobj | 类实例变量指针|
      |iter | STL迭代器变量(iterator)|
      |m_ |类成员变量|
      |p_ |函数参数|
      |g_ |全局变量|
      |h |句柄|

    • 常量命名,枚举命名,宏命名都用大写,每个单词直接用下划线(“_”)分开。如:ENCODE_TYPE_H264,ENCODE_TYPE_MPEG2。

    • 项目的名字都使用“名词”,首字母以大写开头。

    • 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
      例如:

    int	minValue;
    int	maxValue;
    
    int	setValue(…);
    int	getValue(…);
    
    • 避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号。这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事)。

    • 程序中不要出现仅靠大小写区分的相似的标识符。
      例如:

    int  x,  X;		// 变量x 与 X 容易混淆
    void foo(int x);	// 函数foo 与FOO容易混淆
    void FOO(float x);
    
    • 程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。

    • 命名风格保持一致,自己特有的命名风格,要自始至终保持一致,不可来回变化。项目组或产品组对命名有统一的规定,个人服从团队。说明:个人的命名风格,在符合所在项目组或产品组的命名规则的前提下,才可使用。(即命名规则中没有规定到的地方才可有个人命名风格)。

    • 命名中若使用特殊约定或缩写,则要有注释说明。
      说明:应该在源文件的开始之处,对文件中所使用的缩写或约定,特别是特殊的缩写进行必要的注释说明。

    • 避免命名空间与类名冲突,命名空间的定义以NS_为前缀。

    4.表达式和基本语句

    4.1 运算符的优先级

    C++中的运算符有很多,并且有严格的优先级和结合律,记忆起来比较困难。

    • 如果代码行中的运算符比较多,使用用括号明确表达式的操作顺序,避免使用默认优先级。
      示例:下列语句中的表达式
     word = (high << 8) | low    (1)
     if ((a | b) && (a & c))      	(2)
     if ((a | b) < (c & d))         (3)
     如果书写为
     high << 8 | low
     a | b && a & c
     a | b < c & d
     由于
     high << 8 | low = ( high << 8) | low,
     a | b && a & c = (a | b) && (a & c),
     (1)(2)不会出错,但语句不易理解;
     a | b < c & d = a | (b < c) & d,(3)造成了判断条件出错   
    

    4.2 复合表达式

    如 a = b = c = 0这样的表达式称为复合表达式。允许复合表达式存在的理由是:(1)书写简洁;(2)可以提高编译效率。但要防止滥用复合表达式。

    • 不要编写太复杂的复合表达式。
      例如:
      i = a >= b && c < d && c + f <= g + h ; // 复合表达式过于复杂

    • 不要有多用途的复合表达式。
      例如:
      d = (a = b + c) + r ;
      该表达式既求a值又求d值。应该拆分为两个独立的语句:
      a = b + c;
      d = a + r;

    • 不要把程序中的复合表达式与“真正的数学表达式”混淆。
      例如:

    if (a < b < c)			// a < b < c是数学表达式而不是程序表达式
    并不表示	
    if ((a<b) && (b<c))
    而是成了令人费解的if ( (a<b)<c )
    

    4.3 避免直接使用数字作为标识符

    • 避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的枚举或常量来代替。
    示例:如下的程序可读性差。
    if (Trunk[index].trunk_state == 0)
    {
        Trunk[index].trunk_state = 1;
        ...  // program code
    }
    
    应改为如下形式。
    const int TRUNK_IDLE = 0;
    const int TRUNK_BUSY = 1;
    
    if (Trunk[index].trunk_state == TRUNK_IDLE)
    {
        Trunk[index].trunk_state = TRUNK_BUSY;
        ...  // program code
    }
    

    4.4 if 语句

    if语句是C++/C语言中最简单、最常用的语句,然而很多程序员用隐含错误的方式写if语句。本节以“与零值比较”为例,展开讨论。

    • 不可将布尔变量直接与TRUE、FALSE或者1、0进行比较。
      根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值究竟是什么并没有统一的标准。例如Visual C++ 将TRUE定义为1,而Visual Basic则将TRUE定义为-1。
      假设布尔变量名字为flag,它与零值比较的标准if语句如下:
      if (flag) // 表示flag为真
      if (!flag) // 表示flag为假
      其它的用法都属于不良风格,例如:
      if (flag == TRUE)
      if (flag == 1 )
      if (flag == FALSE)
      if (flag == 0)

    • 应当将整型变量用“==”或“!=”直接与0比较。
      假设整型变量的名字为value,它与零值比较的标准if语句如下:
      if (value == 0)
      if (value != 0)
      不可模仿布尔变量的风格而写成
      if (value) // 会让人误解 value是布尔变量
      if (!value)

    • 不可将浮点变量用“” 或 “!=”与任何数字比较。
      千万要留意,无论是float还是double类型的变量,都有精度限制。所以一定要避免将浮点变量用“
      ”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
      假设浮点变量的名字为x,应当将
      if (x == 0.0) // 隐含错误的比较
      转化为
      if ((x>=-EPSINON) && (x<=EPSINON))
      其中EPSINON是允许的误差(即精度)。

    • 应当将指针变量用“==”或“!=”与NULL比较。
      指针变量的零值是“空”(记为NULL)。尽管NULL的值与0相同,但是两者意义不同。假设指针变量的名字为p,它与零值比较的标准if语句如下:
      if (p == NULL) // p与NULL显式比较,强调p是指针变量
      if (p != NULL)
      不要写成
      if (p == 0) // 容易让人误解p是整型变量
      if (p != 0)
      或者
      if § // 容易让人误解p是布尔变量
      if (!p)

    • 有时候我们可能会看到 if (NULL == p) 这样古怪的格式。不是程序写错了,是程序员为了防止将 if (p == NULL) 误写成 if (p = NULL),而有意把p和NULL颠倒。编译器认为 if (p = NULL) 是合法的,但是会指出 if (NULL = p)是错误的,因为NULL不能被赋值。

    4.5 循环语句的效率

    C++/C循环语句中,for语句使用频率最高,while语句其次,do语句很少用。本节重点论述循环体的效率。提高循环体效率的基本办法是降低循环体的复杂性。

    • 在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数。例如示例4-5(b)的效率比示例4-5(a)的高。
    for (row=0; row<100; row++)
    {
        for ( col=0; col<5; col++ )
        {
          sum = sum + a[row][col];
        }
    }
    4-5(a)
    
    for (col=0; col<5; col++ )
    {
        for (row=0; row<100; row++)
        {
            sum = sum + a[row][col];
        }
    }
    4-5(b)
    
    示例4-5(a) 低效率:长循环在最外层           示例4-5(b) 高效率:长循环在最内层
    
    • 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。示例4-4©的程序比示例4-4(d)多执行了N-1次逻辑判断。并且由于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。如果N非常大,最好采用示例4-4(d)的写法,可以提高效率。如果N非常小,两者效率差别并不明显,采用示例4-4©的写法比较好,因为程序更加简洁。
    for (i=0; i<N; i++)
    {
    if (condition)
        DoSomething();
    else
        DoOtherthing();
    }	
    4-4(c)
    
    if (condition)
    {
    for (i=0; i<N; i++)
        DoSomething();
    }
    else
    {
      for (i=0; i<N; i++)
        DoOtherthing();
    }
    4-4(d)
    
    表4-4(c) 效率低但程序简洁                表4-4(d) 效率高但程序不简洁
    

    4.6 new的使用

    • new后的指针在使用前也需要进行非空判断,delete指针后应将指针置为空。

    5.常量

    5.1 为什么需要常量

    如果不使用常量,直接在程序中填写数字或字符串,将会有什么麻烦?

    1. 程序的可读性(可理解性)变差。程序员自己会忘记那些数字或字符串是什么意思,用户则更加不知它们从何处来、表示什么。
    2. 在程序的很多地方输入同样的数字或字符串,难保不发生书写错误。
    3. 如果要修改数字或字符串,则会在很多地方改动,既麻烦又容易出错。
    • 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。
      例如:
    #define    		MAX   100		/*  C语言的宏常量  */ 
    const  int  MAX = 100;		//  C++ 语言的const常量
    const  float PI = 3.14159;		//  C++ 语言的const常量
    

    5.2 const 与 #define的比较

    C++ 语言可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:

    1. const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。
    2. 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
    • 在C++ 程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。

    5.3 常量定义规则

    • 需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
    • 如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。
      例如:
    const  float   RADIUS = 100;
    const  float   DIAMETER = RADIUS * 2;
    

    6.函数

    根据《合约编程》中对函数的定义:一个正确的方法应该“不多不少”正好完成要求它完成的功能。该部分的基础理论是《TDD与单元测试》、《合约编程》。

    6.1 参数的规则

    • 参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用void填充。
      例如:

      void setValue(int p_nWidth, int p_nHeight);	// 良好的风格
      
      void setValue(int, int);					// 不良的风格
      
    • 参数命名要恰当,顺序要合理。

      例如编写字符串拷贝函数StringCopy,它有两个参数。如果把参数名字起为str1和str2,例如void stringCopy(char * p_str1, char * p_str2);那么我们很难搞清楚究竟是把str1拷贝到str2中,
      还是刚好倒过来。可以把参数名字起得更有意义,如叫strSource和strDestination。这样从名字上就可以看出应该把strSource拷贝到strDestination。还有一个问题,这两个参数那一个该在前那一个该在后?
      参数的顺序要遵循程序员的习惯。一般地,应将目的参数放在前面,源参数放在后面。
      如果将函数声明为:
      void stringCopy(char * p_strSource, char * p_strDestination);
      别人在使用时可能会不假思索地写成如下形式:
      char str[20];
      stringCopy(str, “Hello World”); // 参数顺序颠倒

    • 如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。
      例如:
      void stringCopy(char * p_strDestination,const char * p_strSource);
      函数中的指针入参必须进行非空判断

    • 如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,
      这样可以省去临时对象的构造和析构过程,从而提高效率。

    • 避免函数有太多的参数,参数个数尽量控制在5个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。

    • 尽量不要使用类型和数目不确定的参数。

      C标准库函数printf是采用不确定参数的典型代表,其原型为:int printf(const chat *format[, argument]…); 这种风格的函数在编译时丧失了严格的类型安全检查。

    6.2 返回值的规则

    • 不要省略返回值的类型。

      C语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为void类型。
      C++语言有很严格的类型安全检查,不允许上述情况发生。由于C++程序可以调用C函数,为了避免混乱,规定任何C++/ C函数都必须有类型。如果函数没有返回值,那么应声明为void类型。

    • 函数名字与返回值类型在语义上不可冲突。

      违反这条规则的典型代表是C标准库函数getchar。
      例如:
      char c;
      c = getchar();
      if (c == EOF)

      按照getchar名字的意思,将变量c声明为char类型是很自然的事情。但不幸的是getchar的确不是char类型,而是int类型,其原型如下:
      int getchar(void);
      由于c是char类型,取值范围是[-128,127],如果宏EOF的值在char的取值范围之外,那么if语句将总是失败,这种“危险”人们一般哪里料得到!导致本例错误的责任并不在用户,是函数getchar误导了使用者。

    • 不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return语句返回。

      回顾上例,C标准库函数的设计者为什么要将getchar声明为令人迷糊的int类型呢?他会那么傻吗?
      在正常情况下,getchar的确返回单个字符。但如果getchar碰到文件结束标志或发生读错误,它必须返回一个标志EOF。为了区别于正常的字符,只好将EOF定义为负数(通常为负1)。因此函数getchar就成了int类型。
      我们在实际工作中,经常会碰到上述令人为难的问题。为了避免出现误解,我们应该将正常值和错误标志分开。即:正常值用输出参数获得,而错误标志用return语句返回。
      函数getchar可以改写成 BOOL GetChar(char *c);
      虽然gechar比GetChar灵活,例如 putchar(getchar()); 但是如果getchar用错了,它的灵活性又有什么用呢?

    • 如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。
      例如:

      class String
      {…
      	// 赋值函数
      	String & operate=(const String &other);	
      // 相加函数,如果没有friend修饰则只许有一个右侧参数
      friend	String   operate+( const String &s1, const String &s2); 
      private:
      	char *m_data; 
      }
      
      	String的赋值函数operate = 的实现如下:
      String & String::operate=(const String &other)
      {
      	if (this == &other)
      		return *this;
      	delete m_data;
      	m_data = new char[strlen(other.data)+1];
      	strcpy(m_data, other.data);
      	return *this;	// 返回的是 *this的引用,无需拷贝过程
      }
      

      对于赋值函数,应当用“引用传递”的方式返回String对象。如果用“值传递”的方式,虽然功能仍然正确,但由于return语句要把 *this拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,降低了赋值函数的效率。例如:

          	String a,b,c;
          	…
          	a = b; 		// 如果用“值传递”,将产生一次 *this 拷贝
          	a = b = c; 	// 如果用“值传递”,将产生两次 *this 拷贝
      
      	String的相加函数operate + 的实现如下:
      	
             String  operate+(const String &s1, const String &s2)  
          {
          	String temp;
          	delete temp.data;	// temp.data是仅含‘\0’的字符串
          	temp.data = new char[strlen(s1.data) + strlen(s2.data) +1];
          	strcpy(temp.data, s1.data);
          	strcat(temp.data, s2.data);
          	return temp;
          }
      

      对于相加函数,应当用“值传递”的方式返回String对象。如果改用“引用传递”,那么函数返回值是一个指向局部对象temp的“引用”。由于temp在函数结束时被自动销毁,将导致返回的“引用”无效。例如:
      c = a + b;
      此时 a + b 并不返回期望值,c什么也得不到,留下了隐患。

    • 对所调用函数的错误返回值要仔细、全面地处理。

    7.建议

    7.1 使用const

    看到const关键字,C++程序员首先想到的可能是const常量。这可不是良好的条件反射。如果只知道用const定义常量,那么相当于把火药仅用于制作鞭炮。const更大的魅力是它可以修饰函数的参数、返回值,甚至函数的定义体。const是constant的缩写,“恒定不变”的意思。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。所以很多C++程序设计书籍建议:“Use const whenever you need”。

    • 用const修饰函数的参数
      如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const修饰,否则该参数将失去输出功能。
      const修饰输入参数:如果输入参数采用“指针传递”,那么加const修饰可以防止意外地改动该指针,起到保护作用。例如stringCopy函数:void stringCopy(char * p_strDestination, const char * p_strSource); 其中p_strSource是输入参数,p_strDestination是输出参数。给p_strSource加上const修饰后,如果函数体内的语句试图改动p_strSource的内容,编译器将指出错误。
      如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const修饰。用法总结一下

      1.对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。
      
      例如将void Func(A a) 改为void Func(const A &a)。
      
      因为函数体内将产生A类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为void Func(A &a),因为“引用传递”仅借用一下参数的别名而已,
      不需要产生临时对象。但是函数void Func(A &a)存在一个缺点:“引用传递”有可能改变参数a,
      这是我们不期望的。解决这个问题很容易,加const修饰即可,因此函数最终成为void Func(const A &a)。
      
      2.对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。
      
      例如void Func(int x) 不应该改为void Func(const int &x)。
      
      以上此类推,是否应将void Func(int x) 改写为void Func(const int&x),以便提高效率?
      完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,
      “值传递”和“引用传递”的效率几乎相当
      
      
    • 用const修饰函数的返回值
      如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。
      例如 函数 const char * getString();
      如下语句将出现编译错误:
      char *str = getString();
      正确的用法是
      const char *str = getString();
      如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。
      例如不要把函数int getInt() 写成const int getInt(void)。
      同理不要把函数A getA() 写成const A getA(),其中A为用户自定义的数据类型。
      如果返回值不是内部数据类型,将函数A getA() 改写为const A & getA()的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。见6.2节“返回值的规则”。

    • const成员函数
      任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。以下程序中,类stack的成员函数getCount仅用于计数,从逻辑上讲getCount应当为const函数。编译器将指出getCount函数中的错误。

      	class Stack
      {
      	  public:
      		void 	push(int elem);
      		int 	pop();
      		int		getCount()  const;	// const成员函数
      	  private:
      		int		m_num;
      		int		m_data[100];
      };
      
      	int Stack::getCount() const
      {
      		++ m_num;	// 编译错误,企图修改数据成员m_num
      	pop();		// 编译错误,企图调用非const函数
      	return m_num;
      	}
      	
      

      const成员函数的声明看起来怪怪的:const关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了。

    7.2 其他建议

    • 当心那些视觉上不易分辨的操作符发生书写错误。
    我们经常会把“==”误写成“=”,象“||”、“&&”、“<=”、“>=”这类符号也很容易发生“丢1”失误。然而编译器却不一定能自动指出这类错误。
    
    • 变量(指针、数组)被创建之后应当及时把它们初始化,以防止把未被初始化的变量当成右值使用。

    • 当心变量的初值、缺省值错误,或者精度不够。

    • 当心数据类型转换发生错误。尽量使用显式的数据类型转换(让人们知道发生了什么事),避免让编译器轻悄悄地进行隐式的数据类型转换。

    • 当心变量发生上溢或下溢,数组的下标越界。

    • 当心忘记编写错误处理程序,当心错误处理程序本身有误。

    • 当心文件I/O有错误。

    • 避免编写技巧性很高代码。

    • 不要设计面面俱到、非常灵活的数据结构。

    • 如果原有的代码质量比较好,尽量复用它。但是不要修补很差劲的代码,应当重新编写。

    • 尽量使用标准库函数,不要“发明”已经存在的库函数。

    • 尽量不要使用与具体硬件或软件环境关系密切的变量。

    • 把编译器的选择项设置为最严格状态。

    • 如果可能的话,使用PC-Lint、LogiScope等工具进行代码审查。

    • 业务逻辑等容易出错的地方建议加上错误处理,并写入日志,以便快速定位出错位置。

    • 条件语句中变量与常量比较时尽量将常量放在前面,出错时编译器会直接报错。例如,不小心写成if(true = bRet),编译时会直接报错,但是如果写成if(bRet = true),那么相对没那么容易找出错误了。

    • 在QT中,由于更多用来设计界面,为了更好地区分控件类型,成员变量的命名需要突出控件类型,建议在变量名的后面加上控件的标识,如QLineEdit* m_pIPLineEdit,QComboBox* m_pTimeIntervalComboBox,QPushButton* m_pQueryPushButton。

    • 在QT中,为了区分信号和槽函数,信号函数名建议用前缀signal_,如signal_currentTaskInfo;槽函数名建议用前缀slot_,如slot_queryIpInfo。

    • 在QT中,有继承关系的类若存在信号和槽函数,必须要在类体中加入Q_OBJECT宏,否则加入的信号和槽将会没有响应。

    • 在QT中,需要注意槽函数的参数与信号参数的匹配问题,信号的参数必须要大于等于槽函数的参数;自定义信号和槽函数若存在自定义参数,必须要用qRegisterMetaType("")进行注册,否则信号和槽将没有响应。例如

      #include <QMetaType>
      //注册自定义数据类型DeviceTime
      qRegisterMetaType<DeviceTime>("DeviceTime");
      
    • 在QT中,new时没有指定parent的指针用完后必须要释放,释放后应该置为空;而new时指定parent的指针由系统自动回收,不需要程序员释放。

    展开全文
  • 【JAVA】代码规范

    千次阅读 2019-06-27 20:39:51
    一、命名规约 1.【强制】所有编程相关命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。反例: _name / __name / $Object / name_ / name$ / Object$ 2.【强制】类名使用UpperCamelCase风格,...
  • 前端代码风格规范

    千次阅读 2018-07-25 09:54:45
    规范目的:为了提高工作效率,便于后台人员添加功能及前端后期优化维护,输出高质量的文档,在网站建设中,使结构更加清晰,代码简明有序,有一个更好的前端架构。 规范基本准则:符合web标准,使用具有语义的标签...
  • 代码规范代码规范

    2020-07-21 09:59:43
    代码规范代码规范代码规范代码规范代码规范代码规范代码规范代码规范代码规范
  • 谷歌代码规范

    千次阅读 2019-01-19 14:50:35
    英文版:http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml 中文版:... google c++ 编码规范:http://blog.csdn.net/xiexievv/articl...
  • 代码规范

    2018-01-13 09:36:37
    代码规范,写更好的代码
  • 代码规范问题

    千次阅读 2018-09-28 10:06:34
    刚换工作,公司对于规范要求很高,虽然我是野路子出生的,没人教过我规范,但这不是理由,该掌握的东西还是得掌握,该注意的东西也得注意。废话不多说,反正大神随意,这里我只给那些新手做个参考。 反面例子: ...
  • 程序员必知--代码规范

    千次阅读 2018-11-17 11:21:04
    首先用我之前的博客中的代码举例子 (C语言编程100例): 第一段代码: #include &lt;stdio.h&gt; int main(){ int i, j, n = 0, a[17] = { 0,1 }, l, r; while (n&lt;1 || n&gt;16){ printf(&...
  • IDEA安装阿里代码规范插件

    万次阅读 多人点赞 2020-06-13 11:55:53
    所以下载阿里的代码规范插件来约束自己凌乱的代码。阿里规范插件GitHub地址:https://github.com/alibaba/p3cIDEA安装该插件步骤:1.打开IDEA,File-&gt; Setteings-&gt;Plugins-&gt;Browse Repositorie...
  • 阿里巴巴代码规范

    千次阅读 2020-02-07 16:04:46
    本规范均出自阿里巴巴代码规范以及本人日常过程中的积累。由于篇幅有限,本文不予列出阿里巴巴代码规范的所有,仅列出本人觉得对日常使用过程中帮助较大且又是大家容易忽略的问题。 阿里巴巴代码规范:...
  • Unity3d 脚本开发代码规范

    千次阅读 2016-02-22 00:04:29
    代码规范 代码规范问题是这样的一个问题:高性价比,不被重视,实际做的时候难以做好 统一的规范有利于代码简洁,进而利于控制复杂度,关于复杂度可以参见之前的文章...
  • 前端代码规范手册

    千次阅读 2018-08-15 15:43:15
    前端代码规范手册       Web Coding Guidelines     前言 本手册的愿景是码出高效,码出质量。现代软件架构都需要协同开发完成,高效 协作即降低协同成本,提升沟通效率,所谓无规矩不成方圆,无规范不...
  • 代码规范的重要性

    千次阅读 2017-01-03 17:19:36
    代码规范比比皆是,但是很少有公司做好代码规范的。忍不住想谈谈代码规范的重要性,希望所有人都能够重视起来。而且,我相信,如果我们代码规范能够做好的话,且不说开发水平提高多少,至少我们也会有很多出色开源...
  • 阿里代码规范插件安装和使用技巧

    千次阅读 2020-04-23 13:56:54
    很多程序员,甚至非程序员,都认为,功能第一,性能第二,...安装阿里代码规范插件,时刻检查自己代码规范性 https://blog.csdn.net/skieske/article/details/78250137 这个是安装方法的文章,很简单。 补充...
  • IDEA安装阿里Java代码规范插件

    万次阅读 2018-04-13 17:39:10
    阿里java代码规范IDEA插件之傻瓜教程前言碎语千呼万唤始出来,阿里巴巴在首次发布java开发手册后,时隔一年终于推出了IDEA的代码检查插件了。插件应该是十月八号就悄悄的发布了,插件源码推迟了5天才上传。如果说...
  • 之前在百度上班的时候经常会收到代码规范中心发来的邮件,内容无非是我commit的代码少了个缩进,大括号的位置不对等等。当然这些是开发工具没配置好的原因。 还有很多代码规范的问题对于程序员能否写出一手漂亮(可...
  • Java基础——java代码规范详细版

    千次阅读 2019-07-03 11:41:57
    本Java代码规范以SUN的标准Java代码规范为基础,为适应我们公司的实际需要,可能会做一些修改。本文档中没有说明的地方,请参看SUN Java标准代码规范。如果两边有冲突,以SUN Java标准为准。 1. 标识符命名规范 ...
1 2 3 4 5 ... 20
收藏数 901,179
精华内容 360,471
关键字:

代码规范