东方有线发票稽核项目阶段总结

东方有线发票稽核项目暂时告一段落,本文对项目开发阶段作一个总结。

发票稽核模块在原东方有线业务数据平台基础之上完成,系统环境Oracle9i + jdk1.5 + weblogic9.2 ,架构采用Spring + Struts + Hinernate。

由于需求较为简单,所以计划开发时间较短,计划开发任务从 10 9 始, 11 17 实施部署;项目负责人1人,主管需求;DM1人,负责软件质量、Demo及公用代码编写、解决疑难问题;编码3人,负责业务代码编写。任务安排上DM似乎承担了过多的编码工作,事实上也正是如此,这是后话。

实际上从 9 2 (没错,就是 9 2 ,我来方正的第三天)就开始着手准备需求及部分Demo 的编写。整个9月份平淡无奇,完善需求、设计表结构、设计界面、技术规范文档、测试用例……一切按计划进行。

十一后,我让每个人估算一下自己的工作量,以下是各人填写的结果:

1 工作量估算

这是一份很怪异的结果,至少我认为并不是所有作估算的人都想努力完成工作。先来看看第一个人,三个“发票××维护”功能极为相似,只有简单的增、删、改、查操作,与以往不同的仅仅是增、改、查操作都在同一个页面进行(客户明确提出了此项需求,其它功能也是如此)。此前还有一个“业务类型维护”,我将其作为一个Demo已经完成,极为相似的功能竟然打算9天完成!那个“资金录入”也没有任何难度。第二个人的估算我认为过于乐观,其中有各种关于权限的判断需要反复测试,而且这部分如果没有好的设计极易产生大量的垃圾代码(一段时间后我担心的事发生了)。第三个人经历过一期的开发,我本以为能够给我一个很快的预算,结果恰恰相反(其中的“发票使用信息录入”十一之前就开始编写了)。

此时我犯了一个错误——也正是这个错误导致了修改时期时间紧迫——我没有开会或以其它任何方式调整这个有问题的估算。这似乎是有原因的,我觉得这个系统需求稳定、功能简单、时间充裕、开发人员至少有一、二年以上工作经验,我们甚至可以免去测试组的工作,下图是统计的需求变更数:

2 需求变更数

此前我从没见过如此稳定的需求和如此好说话的客户,看来这将是个轻松的项目。

没想到的是,整个10月我都在繁忙中度过。

为了实现一些动态功能及简化开发,我编写了部分标签,在文档上注明了各种标签的用法。此后的一段时间里,这个文档的页数不断增加,我也渐渐忙了起来,要命的是,总有人向我提出各种技术问题,java调试问题、JS问题、框架问题、还有让我及其气愤的SVN用法(难道大家一直在用CVS?可是这两者又太大差别吗?)。每天晚上我会检查新提交的代码,让我奇怪的是,尽管我多次强调规范问题,仍然没有人注意!查了一下邮件,至少五封附件中有《技术规范手册》,口头强调的已经不计其数。我自认为手册写的不错,有命名规范、注意事项、页面布局规范、代码风格、文档规范等,难道手册中有大量的方言?

当公用问题过多的时候,我开始无暇顾及他人的代码质量。事实上大部分并非公用问题,只不过是稍微费时的问题都有我来解决罢了(比如不会写正则表达式,不知道一个处理方法),一个简单的系统会有什么复杂的问题吗?

两周后,进度表上部分完成度被标为100%,我点开了部分页面,发现到处都是“Page not found”,超过一半的输入框写入长度没有限制。此时我发现了另一个问题,大家似乎在等待进度表上的时间点,当一个功能提前做完时没有仔细测试,没有重构,也没有马上开始下一个功能,而是在MSN,在论坛。这样的工作太有情趣了。

例会上,我提出了这个问题,指明了部分页面的哪些条件没有限制,本意是给出通用的提示,期待大家各自找出解决的方法,这似乎是一个错误,至少在这个项目中如此,大家期待的是给出具体的解决方法和更多的细节。几天后我发现只有我指出的那些特定输入框加入了校验。

磕磕绊绊的10月过去了,测试组也正式开始了工作,大量低级bug被记录在案,以下是bug列表及代码数量:

3 累计bug数量

4 bug捡出及关闭情况

5 bug严等级记分布图

6 代码量

这无疑是一份令人窝火的统计图表,估算表中的第三个人制造了大多数bug为了避免在页面硬编码实现一些权限校验,我设计了权限配置方案,大致是通过XML进行页面元素的权限设置,并写了详细的文档。奇快的问题又出来了,当我再次看到有人在页面硬编码时我问为什么不用文档中的方案,没有收到邮件吗?没有看文档服务器吗?回答是:谁看邮件啊?写代码还得看文档?最终部署期限慢慢接近,bug数依然有增无减,我强压火气吩咐加班,但是遭到强烈反对(当时我怎么想的?要是我的双节棍带在身上多好)。这没错,我也反对,毕竟我一向认为生活第一,工作第二,但是有什么办法呢?我们不应该为自己的行为负责吗?下图是最后得出结果的估算表:

 

 

7 得出结论的估算表

 

 

这些统计并没有算上全部修改的时间。值得注意的是第三个人的结果,这是我始料未及的。这个功能最终由我接手,我发现修改困难是有原因的,过紧耦合让整个代码像是一个小型衣柜,程序员想要将大量的衣服塞进去,但由于衣柜的质量不好(或许它是令人诟病的“中国制造”),在超过阈值时每次塞入一件衣服衣柜就会破裂,程序员不仅要塞入衣服(可能有些衣服还有不平整的包装袋),还要修补衣柜,如此反复……终于有一次全部赛进去了,高兴过度的程序员使劲拍了一下自己的杰作,于是,衣柜破碎了。我不想跟干木匠的强饭碗,所以我的结论是此部分没有修改的价值,重写的代价更小。

以下是原程序的代码片段:

  1. public String checkInvoiceInfRecord(String[] invoiceInfRecordIDArray)
  2.     {
  3.         boolean flag = true;  // 是否全部可以复核
  4.         boolean b = true;     // 是否全部可以结存
  5.         StringBuffer invoiceInfRecordIDCheckBuffer = new StringBuffer();   // 未复核且未结存的记录的ID数组
  6.         String message = new String();   // 页面alert的提示信息
  7.         for(String invoiceInfRecordID:invoiceInfRecordIDArray)
  8.         {
  9.             InvoiceInfRecord invoiceInfRecord = invoiceInfRecordDAO.getInvoiceInfRecordByID(invoiceInfRecordID);
  10.             
  11.             if(invoiceInfRecord.getAuditState().equals(String.valueOf(Variable.CHECKSTATUS_YES)))
  12.             {
  13.                 flag = false;
  14.                 break;
  15.             }else
  16.             {
  17.                 if(invoiceInfRecord.getFreezeState().intValue() == Variable.FREEZESTATUS_NOT)  // 以下表示未复核且未结存的记录
  18.                 {
  19.                     invoiceInfRecordIDCheckBuffer.append(invoiceInfRecord.getUseID()).append(",");
  20.                 }
  21.             }
  22.         }
  23.         for(String invoiceInfRecordID:invoiceInfRecordIDArray)
  24.         {
  25.             InvoiceInfRecord invoiceInfRecord = invoiceInfRecordDAO.getInvoiceInfRecordByID(invoiceInfRecordID);
  26.             
  27.             if(invoiceInfRecord.getFreezeState().intValue() == Variable.FREEZESTATUS_YES)
  28.             {
  29.                 b = false;
  30.                 break;
  31.             }
  32.         }
  33.         if(flag)          // 以下表示全部未复核
  34.         {
  35.                if(b)      // 以下表示全部未结存
  36.                {
  37.                    if(doCheckInvoiceInfRecord(invoiceInfRecordIDCheckBuffer.toString()))    // 复核成功
  38.                        message = Variable.CHECK_SUCCESSFUL;
  39.                    else                            // 复核失败
  40.                        message = Variable.CHECK_FAILURE;
  41.                }
  42.                else       // 以下表示全部未复核但是有部分已经结存
  43.                {
  44.                    if(doCheckInvoiceInfRecord(invoiceInfRecordIDCheckBuffer.toString()))    // 复核成功
  45.                        message = "复核成功!(但是已经结存的发票使用信息不能被复核)";
  46.                    else                            // 复核失败
  47.                        message = Variable.CHECK_FAILURE;
  48.                }
  49.                    
  50.             
  51.         }else           // 以下表示有部分记录已经复核
  52.         {
  53.                 if(b)   // 以下表示全部未结存
  54.                 {
  55.                     if(doCheckInvoiceInfRecord(invoiceInfRecordIDCheckBuffer.toString()))
  56.                     {
  57.                         if(invoiceInfRecordIDCheckBuffer.toString().trim().length() > 1)
  58.                               message = "复核成功!(但是已经复核的发票使用信息不能再复核)";
  59.                         else
  60.                               message = "复核成功!";
  61.                     }
  62.                     else
  63.                        message = Variable.CHECK_FAILURE;
  64.                 }else   // 以下表示有部分记录已经复核和已经结存
  65.                 {
  66.                     if(doCheckInvoiceInfRecord(invoiceInfRecordIDCheckBuffer.toString()))
  67.                            message = "复核成功!(但是已经复核或者已经结存的发票使用信息不能再复核)";
  68.                         else
  69.                            message = Variable.CHECK_FAILURE;
  70.                 }   
  71.         }
  72.         return message;
  73.     }

看到那个超大的for循环和其中杂乱无章的if-else了吧,这就是传说中的上帝代码。重写后的代码如下:

  1. public MessageBean checkInvoiceInfRecord(String[] ids) {
  2.         MessageBean msg = new MessageBean();
  3.         
  4.         for(String useId : ids) {
  5.             InvoiceInfRecord invoiceInfRecord = invoiceInfRecordDAO.getInvoiceInfRecordByID(useId);
  6.             IFreezesuminfo freezesuminfo = freezesuminfoDao.getIFreezesuminfoByID(invoiceInfRecord.getCompanyID(), 
  7.                     invoiceInfRecord.getSubCategory().getCategoryId());
  8.             if(!alreadyBalance(invoiceInfRecord, msg)) 
  9.                 break;
  10.             else if(!emptyBalance(freezesuminfo, msg)) 
  11.                 break;
  12.             else if(!moreThanBalance(freezesuminfo, invoiceInfRecord, msg)) 
  13.                 break;
  14.             else if(!checkInvoice(freezesuminfo, invoiceInfRecord, msg))
  15.                 break;
  16.         }
  17.         
  18.         return msg;
  19.     }

我想原作者大概舍不得自己的中国制造。

东方有线项目并没有结束,还有很多问题需要改进,拭目以待吧。

 

把团队比作球队真的准确吗?球队可以凭借球星的力量赢得比赛,当年的魔术队可以凭借麦迪一人之力杀入季后赛,我们像球队吗?即使像,也没有球星,不过是个瘪脚的居民社区球队。团队更像一个乐队,即使一个长笛手在演奏中吹错了音符,整场演出也会以失败告终。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值