在升级维护老代码的过程中,一次偶然的机会发现了一个抛出除零异常的问题。我用了偶然这个词,因为原来的代码已经成功运行了将近一年多了,出现这种问题的可能性非常小,既然碰到了,就深究一下。
- private void calculateCoverage(List<Project> projects){
- if(projects==null){
- return ;
- }
- List<Application> apps = new ArrayList<Application>(projects.size());
- for (Project project : projects) {
- if(StringUtils.isEmpty(project.getAppTableName())){
- continue;
- }
- String[] tableNames = project.getAppTableName().split(";");
- BigDecimal fenZi = new BigDecimal(0);
- BigDecimal fenMu = new BigDecimal(0);
- for (String tn : tableNames) {
- if(StringUtils.isEmpty(tn)){continue;}
- Application app = getLatestApp(tn);
- if(app!=null){
- apps.add(app);
- fenZi = fenZi.add(app.getCoverageSumFenZi());
- fenMu = fenMu.add(app.getCoverageSumFenMu());
- }
- }
- if(fenMu.compareTo(BigDecimal.ZERO)==0){//=0
- SysContext.logger.warn("项目"+project.getProjectName()+"行数为0");
- }else{
- project.setCoverage((fenZi.divide(fenMu.divide(oneHundred, 1, BigDecimal.ROUND_HALF_UP), 1, BigDecimal.ROUND_HALF_UP).toPlainString()));
- }
- }
- }
您说对了,写得越长,越复杂的代码越容易产生缺陷。上面就是明证,虽然这个bug,没有产生什么损失,可是,要是这个代码是火箭发射的代码,而出现问题的几率是万分之一,而这万分之一的机会,赶巧被碰上了,那火箭发射可就失败了。这脸可就丢大了。扯远了。。。回到正题来。
先附上异常日志:
- [2011-10-08 16:29:31,597] [http-8083-55] ERROR com.esc - / by zero
- java.lang.ArithmeticException: / by zero
- at java.math.BigDecimal.divide(BigDecimal.java:1327)
- at com.esc.tcc.service.impl.ProjectServiceImpl.calculateCoverage(ProjectServiceImpl.java:241)
- at com.esc.tcc.service.impl.ProjectServiceImpl.findProjects(ProjectServiceImpl.java:195)
- at sun.reflect.GeneratedMethodAccessor476.invoke(Unknown Source)
- at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
- at java.lang.reflect.Method.invoke(Method.java:597)
- at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
- at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
抛异常的那一行,正是红色底纹那一行。
原来代码的作者认为,有了这句代码:
- if(fenMu.compareTo(BigDecimal.ZERO)==0)
的保护,后面执行:
- (fenZi.divide(fenMu.divide(oneHundred, 1, BigDecimal.ROUND_HALF_UP), 1, BigDecimal.ROUND_HALF_UP)
这句,就应该不会遭遇除零异常。可是,错了,我自己写了一个小小的测试代码:
- package com.taobao.test;
- import java.math.BigDecimal;
- public class TestBigDecimal {
- public static void main(String[] args) {
- BigDecimal ONE_HUNDRED = new BigDecimal(100);
- BigDecimal fenZi = new BigDecimal(0);
- BigDecimal fenMu = new BigDecimal(4); // 如果把4改成5或者>5的数字,则程序运行正常
- BigDecimal result = fenZi.divide(fenMu.divide(ONE_HUNDRED, 1, BigDecimal.ROUND_HALF_UP), 1, BigDecimal.ROUND_HALF_UP);
- System.out.println("result: " + result.toPlainString());
- }
- }
发现,正是会抛出除零异常。
修改方法也很简单,将除运算尽量转换成等价的乘运算(总之,尽量减少除运算),看下面代码:
- fenZi = fenZi.multiply(oneHundred);
- BigDecimal coverageValue = fenZi.divide(fenMu, 1, BigDecimal.ROUND_HALF_UP);
- String coverageStr = coverageValue.toPlainString();
- project.setCoverage(coverageStr);
本来,在一句代码中写老长的表达式运算,就不是好的编程风格。看我改写后,代码漂亮多了吧,最最关键的是缺陷解决了。呵呵