在升级维护老代码的过程中,一次偶然的机会发现了一个抛出除零异常的问题。我用了偶然这个词,因为原来的代码已经成功运行了将近一年多了,出现这种问题的可能性非常小,既然碰到了,就深究一下。

 

 
  
  1. private void calculateCoverage(List<Project> projects){ 
  2.         if(projects==null){ 
  3.             return ; 
  4.         } 
  5.         List<Application> apps = new ArrayList<Application>(projects.size()); 
  6.         for (Project project : projects) { 
  7.             if(StringUtils.isEmpty(project.getAppTableName())){ 
  8.                 continue
  9.             } 
  10.             String[] tableNames = project.getAppTableName().split(";"); 
  11.             BigDecimal fenZi = new BigDecimal(0); 
  12.             BigDecimal fenMu = new BigDecimal(0); 
  13.             for (String tn : tableNames) { 
  14.                 if(StringUtils.isEmpty(tn)){continue;} 
  15.                 Application app = getLatestApp(tn); 
  16.                 if(app!=null){ 
  17.                     apps.add(app); 
  18.                     fenZi = fenZi.add(app.getCoverageSumFenZi()); 
  19.                     fenMu = fenMu.add(app.getCoverageSumFenMu()); 
  20.                 } 
  21.             } 
  22.             if(fenMu.compareTo(BigDecimal.ZERO)==0){//=0 
  23.                 SysContext.logger.warn("项目"+project.getProjectName()+"行数为0"); 
  24.             }else
  25.                 project.setCoverage((fenZi.divide(fenMu.divide(oneHundred, 1, BigDecimal.ROUND_HALF_UP), 1, BigDecimal.ROUND_HALF_UP).toPlainString())); 
  26.             } 
  27.         } 
  28.     } 

您说对了,写得越长,越复杂的代码越容易产生缺陷。上面就是明证,虽然这个bug,没有产生什么损失,可是,要是这个代码是火箭发射的代码,而出现问题的几率是万分之一,而这万分之一的机会,赶巧被碰上了,那火箭发射可就失败了。这脸可就丢大了。扯远了。。。回到正题来。

先附上异常日志:

 
  
  1. [2011-10-08 16:29:31,597] [http-8083-55] ERROR com.esc  - / by zero 
  2. java.lang.ArithmeticException: / by zero 
  3.     at java.math.BigDecimal.divide(BigDecimal.java:1327) 
  4.     at com.esc.tcc.service.impl.ProjectServiceImpl.calculateCoverage(ProjectServiceImpl.java:241) 
  5.     at com.esc.tcc.service.impl.ProjectServiceImpl.findProjects(ProjectServiceImpl.java:195) 
  6.     at sun.reflect.GeneratedMethodAccessor476.invoke(Unknown Source) 
  7.     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
  8.     at java.lang.reflect.Method.invoke(Method.java:597) 
  9.     at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307) 
  10.     at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182) 

抛异常的那一行,正是红色底纹那一行。

原来代码的作者认为,有了这句代码:

 
  
  1. if(fenMu.compareTo(BigDecimal.ZERO)==0

的保护,后面执行:

 
  
  1. (fenZi.divide(fenMu.divide(oneHundred, 1, BigDecimal.ROUND_HALF_UP), 1, BigDecimal.ROUND_HALF_UP) 

这句,就应该不会遭遇除零异常。可是,错了,我自己写了一个小小的测试代码:

 
  
  1. package com.taobao.test; 
  2.  
  3. import java.math.BigDecimal; 
  4.  
  5. public class TestBigDecimal { 
  6.     public static void main(String[] args) { 
  7.         BigDecimal ONE_HUNDRED = new BigDecimal(100); 
  8.         BigDecimal fenZi = new BigDecimal(0); 
  9.         BigDecimal fenMu = new BigDecimal(4); // 如果把4改成5或者>5的数字,则程序运行正常 
  10.         BigDecimal result = fenZi.divide(fenMu.divide(ONE_HUNDRED, 1, BigDecimal.ROUND_HALF_UP), 1, BigDecimal.ROUND_HALF_UP); 
  11.         System.out.println("result: " + result.toPlainString()); 
  12.     } 

发现,正是会抛出除零异常。

修改方法也很简单,将除运算尽量转换成等价的乘运算(总之,尽量减少除运算),看下面代码:

 

 
  
  1. fenZi = fenZi.multiply(oneHundred); 
  2. BigDecimal coverageValue = fenZi.divide(fenMu, 1, BigDecimal.ROUND_HALF_UP); 
  3. String coverageStr = coverageValue.toPlainString(); 
  4. project.setCoverage(coverageStr); 

本来,在一句代码中写老长的表达式运算,就不是好的编程风格。看我改写后,代码漂亮多了吧,最最关键的是缺陷解决了。呵呵