为什么要有编码规范
编码规范对于程序员而言尤为重要,有以下几个原因:
- 一个软件的生命周期中,80%的花费在于维护
几乎没有任何一个软件,在其整个生命周期中,均由最初的开发人员来维护
编码规范可以改善软件的可读性,可以让程序员尽快而彻底地理解新的代码
如果你将源码作为产品发布,就需要确任它是否被很好的打包并且清晰无误,一如你已构建的其它任何产品
为了执行规范,每个软件开发人员必须一致遵守编码规范
遵循阿里的的编码规范(P3C)
什么是代码复查(Code Review)?
代码复查又叫“代码审查”,其基本思想是在开发人员编写完自己的代码后,由其他人进行复查,检查代码中存在的问题。
p代码复查的一个基本理论是,当越早发现代码存在的缺陷,解决缺陷的代价就越低。
代码复查往往分成以下一个方面进行审查:
代码风格
- 代码格式
- 重大缺陷
- SQL注入问题
- 性能问题
- 设计逻辑与思路的审查
- 代码逻辑控制是否有问题
- 代码是否与最初的设计存在出入
代码复查常见问题
1.代码格式
- 未安装静态代码检查工具
- 代码的缩进仍然使用TAB键,而不是空格
- 未使用快捷键“Ctrl+Shift+F”格式化
2.代码注释
- 存在大段代码没有注释的情况,最好有简单的注释说明一下。
- 程序的重要分支没有注释,分支的注释有利于理解业务逻辑。
- 代码注释与实现不一致。
- 代码注释多余或者没有实际意义。
- 常量注释最好采用多行注释的风格而非单行注释。
- 说明:这样做的原因是,在IDE中,外部调用常量的地方可以看到注释
3.命名规范
- 常量定义问题,很多数字(魔法数字)、字符应该定义为常量,并指定有意义的名称,便于理解和维护
4.代码结构
- 程序没有分层的概念
- 每层的职责要分明
- 类方法的参数列表过长,应该抽象为一个参数类
- 存在多层if嵌套和多层while嵌套的情况
无效的引用,代码中存在很多无效的包、类引用,应该去掉没用到的引用,或者使用快捷键CTRL+SHIFT+O重新组织一下导入
无效代码
- 存在空的方法或者没用到的变量,建议删除。
一些业务处理类的方法参数中有pageNo相关的参数,但是实际上该函数并没有进行分页处理。
复查时,经常看到大段的代码被注释掉,建议删除。 咱们有svn,如果想找到,可以查看svn历史版本。
第一章 正确性检查
1. 数据类型处理
定义了一个Integer类型的a,在对a进行switch(a)等操作的时候,没有对a进行判空,导致空指针异常
double数字处理时,直接进行加减、比较、零值判断等,可能存在精度问题。正确的判断方式应是约等于零,这个方法可以定义为公用的工具方法。
Double dValue = …; if(dValue> -0.00001 && dValue< 0.0001) { // 约等于0 // do something } **else** { // do others }
- int类型的数据作除法时要注意,得到的有可能不是想要的结果。
int iFz= 2;
int iFm = 3; // 可能为0
// 错误的做法
Double present = iFz / iFm; // 实际得到的是0
*********************
int iFz= 2;
int iFm = 3; // 可能为0
// 正确的做法
if(iFm != 0) {
Present = (double)iFz/iFm; // 做强制类型转换
} else {
Present = 100;
}
- 字符串判断时,空串和空格串的差异需要注意,apache common包中提供了相关的判断方法。
String str="";
if (str != null &&str.length() != 0){
//do sth
}
=====================================================================
String str="";
if (StringUtils.isNotBlank(str)){
//do sth
}
- Equals方法的使用
- 字符串比较应当使用equals方法。
- 应该用常量字符串在前,比较变量,避免null错误。
String str_temp="temp";
if(str_temp=="test"){
//do sth
}
******************
//正确的方法
String str_temp="temp";
if("test".equals(str_temp)){
//do sth
}
日期处理时,增加一个月,有代码实现时采用了加30天的做法。应该使用
java8
里的日期处理类不应当使用字符串拼接的方式,应该用
StringBuffer
或者StringBuilder
代替多个数据库操作,没有使用事务(编程式事务、声明式事务)
当发生异常时,一般先回滚事务,然后记录日志或者再抛异常,回滚事务意味着及早释放锁资源,并且防止记录日志时出异常,导致无法回滚。
try {
//do sth
commit(status);
} catch (Exception e) {
rollback(status);//首先回滚事务,释放锁资源
LOG.error("处理失败:", e);//再记录日志
}
关于分页列表count统计的处理,有很多不合理的代码。
- Count方法中,用list.size()返回记录条数,这样会把所有的列表数据加载到内存,浪费资源。正确做法是用select count(*)来获取记录条数。
记录日志时避免进行正常业务逻辑的处理,如以下debug操作进行了一次数据库查询,统计记录数量。
//debug时执行了一次昂贵的数据库交互 logger.debug(faDao.getFaxxList(kssj,jssj).size ()); //正确的做法,应该复用之前业务逻辑的查询结果,不要再次执行查询 int count=faDao.getFaxxList(kssj,jssj).size (); //do sth use count //需要先判断日志级别,不然会有性能消耗,因为日志级别只影响日志输出,但是内容都会计算出来,例如日志里有tostring方法,不管日志级别是什么,都会运行,只是不一定显示 if(logger.isDebug()){ logger.debug(count); }
说明首先,类似代码不应该提交到svn;其次,如果debug中的代码确实需要执行,则可以增加一个if(logger.isDebug)判断,根据调试级别,避免无效执行。
删除数据库中的一条记录,常见有先使用hibernate的get方法,获取一个bean对象,然后在调用hibernate的delete方法进行删除,这样会有两个数据库操作。实际上直接根据主键删除对象即可,不用先把对象查出来。
数据库查询时,sql中where条件左侧尽量不要使用函数(包括日期函数),以避免降低查询效率。
Select * from tab where d_larq+3=?;//将不会使用索引 Select * from tab where trunc(d_larq)=?;//将不会使用索引 Select * from tab where to_number(C_BH)=?//将不会使用索引 Select * from tab where c_bh||’01’=?//不会使用索引
- Select *的使用,程序中不建议类似方式,需要明确写出要查询的列,避免列太多。
DAO类中,插入数据到数据库,有hibernate的bean,但是仍然使用sql进行插入。应该用hibernate的insert方法,把bean传入即可。
推荐用Integer.valueOf(int)创建Integer对象(其他封装类类似),该方法有可能通过缓存经常请求的值而显著提高空间和时间性能。
不要重复实现apache common包中很多工具类方法。
分页和全部查询可以写一个方法
不应当有空的异常处理块。同时异常信息应该使用日志框架进行记录,不应当调用printStackTrace()方法。该问题在技术备忘录中有要求。
部分try{}catch(..){} 进行了后台日志报错,但是没有给出前台错误提示,这样可能导致用户在已经报错的操作上进行下一步操作,从而引发更严重问题。如果确实不需要提示用户,则可以再注释中进行明确说明。
Try代码块不必一味的缩小,相关的业务在一个try中,可确保一个失败而后面的不做。
定时任务不能抛出异常,因为没有程序去管理。定时任务需要自己处理所有的异常信息,并根据异常的情况确定后续的处理流程。可以忽略、重做、发通知等等。
异常的处理最好区分捕获,而不是使用一个try.catch捕获所有的异常
List的判空,建议用isEmpty方法,而不要用size()==0
集合类的遍历,建议采用迭代器进行。这是目前比较高效、快捷的。下面列举几种常见的迭代处理代码
Map<String, String> map = new HashMap<String, String>(); for (Entry<String, String> entry : map.entrySet()) { // 遍历Map entry.getKey(); entry.getValue(); } List<String> list = new ArrayList<String>(); for (String item : list) { // 遍历List、Set } Properties props = new Properties(); for (Entry<Object, Object> entry : props.entrySet()) { // 遍历Propties } String[] arrString = new String[0]; for (String item : arrString) { // 遍历数组
- 打开文件后,没有关闭,导致文件占用。
- 未使用finally处理,导致异常情况下占用资源未释放。
使用了finally处理,多个资源,但是在释放前面资源时发生了异常,导致后面的资源释放失败
Dao中使用了getSession,但是没有调用releaseSession,导致数据库链接不释放,应该使用hibernateTemplate提供的方法进行数据库操作,一般情况下不需要getSession。
做文件操作时,直接把文件读入内存,然后进行相应的业务操作,当文件较大,或者线程较多时,会造成内存溢出,导致服务器宕机
- 压缩程序,接受文件参数,用byte[]数组进行操作,容易造成内存溢出。
应用程序提供文件下载,如果文件较大,线程较多时也会造成内存溢出。
SimpleDateFormat有线程安全问题,使用的时候注意线程安全问题,尽量不要使用全局的SimpleDateFormat对象
单例对象的实现,代码中要进行同步的处理,否则有可能创建多个对象。同时对于单例对象的状态、全局属性的修改方法,也要增加synchronized关键字,避免线程安全问题
异常处理中,捕获异常后,没有使用日志框架记录日志的
日志
- 记录日志时应该避免出现NullPointException。
每个方法起始应该打印入参数信息,对于对象类型可以转成Json字符串,对于不多的参数用占位符方式输出。
记录trace/debug/info 级别的日志时应使用{}占位符,避免字符串连接减少String对象(不可变)带来的内存开销。
日志内容应该易读、清晰、可描述。要给出当前操作做了什么, 使用的什么数据。
不要在日志中记录密码和个人隐私信息。
记录日志的时候,不要查询数据库。
在for循环中输出日志要谨慎,避免输出大量无用日志。
对于ERROR级别或者WARN级别的日志需要打印异常时相关的参数信息。
应该避免记录日志后有抛出异常,保证日志只记录一次。
代码中不应该出现System print(包括System.out.println和System.error.println)语句
出现异常时应该输出Exceptions的全部Throwable信息,因为logger.error(msg)和logger.error(msg,e.getMessage())这样的日志输出方法会丢失掉最重要的StackTrace信息
SQL
在循环中(for、wihle),存在查询或更新数据库的脚本,将导致产生大量SQL的问题
在循环中(for、wihle),存在查询或更新数据库的脚本,且没有对垃圾数据进行处理,导致无限循环或一个跨度较大的循环,将导致产生大量SQL的问题
在一次业务处理中,反复从数据库装载同一对象,将导致产生重复大量SQL的问题。
SQL脚本操作一批数据,是通过in来实现的,没有预估到in的个数量,导致拼接的SQL很长
SQL查询中,不能使用select * 进行查询
SQL查询时,条件语句中带or的话,很容易造成全表扫描。最好不要出现这样的sql,可以使用in、union、uion all 代替
查看原文:http://yuyy.info/uncategorized/%e7%bc%96%e7%a0%81%e8%a7%84%e8%8c%83/