论坛首页 Java企业应用论坛

大话重构连载:遗留系统——软件工业时代的痛

浏览 13332 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2014-06-18   最后修改:2014-06-29
我常常感到幸运,我们现在所处的是一个令人振奋的时代,我们进入了软件工业时代。在这个时代里,我们进行软件开发已经不再是一个一个的小作坊,我们在进行着集团化的大规模开发。我们开发的软件不再是为某个车间、某个工序设计的辅助工具,它从某个单位走向整个集团,走向整个行业,甚至整个社会,发挥着越来越重要的作用。一套软件所起到的作用与影响有多大,已经远远超越了所有人的想象,成为一个地区、一个社会,乃至整个国家不可或缺的组成部分。慢慢地,人们已经难以想象没有某某软件或系统的生活和工作会是怎样的。这就是软件工业时代的重要时代特征。

然而,在这个令人振奋的软件工业时代,处于时代中心的各大软件企业却令人沮丧。软件规模越来越庞大,软件结构越来越复杂的同时,却是软件质量越来越低下,软件维护变得越来越困难,以至于每个小小的变更都变得需要伤筋动骨。研发人员为此举足无措,测试人员成为唯一的救星,每个小小的变更都需要付出巨大代价进行测试。软件企业在这样一种恶性循环中苦苦支撑。毫无疑问,这也成为这个令人振奋的时代的另一个特征。

是的,面对软件工业时代我们并没有做好准备。过去,一套软件的生命周期不过2~3年时间,随着软件需求的变化,我们总是选择将软件推倒了重新开发,但是现在这样的情况在发生着改变。随着软件规模的扩大,软件数据的积累,软件影响力的提升,我们,以及我们的客户,都真切地感受到,要推倒一套软件重新开发,将变得越来越困难而不切实际。这样的结果就是,我们的软件将不停地修改、维护、再修改、再维护……直到永远。这是一件多么痛苦的事情啊!

一套软件,当它第一次被开发出来的时候,一切都十分清晰:清晰的业务需求、清晰的设计思路、清晰的程序代码。但经历了几次需求变更与维护以后,一切就变得不那么清晰了。业务需求文档变得模糊不清,设计思路已经跟不上变更的脚步,程序代码则随着业务逻辑的复杂而臃肿不堪。程序员开始读不懂代码,软件开发工作变得不再是一种乐趣。

随着时间的推移,软件经过数年、数十次的变更与维护,情况变得越来越糟。最初的程序员已经不愿再看到自己的代码而选择离去。他的继任者们变得更无所是从,由于看不懂程序,代码的每一次修改如同在走钢丝。测试人员变成了唯一的希望,开发人员的每一次修改都意味着测试人员需要把所有程序测试一遍。继任者们开始质问最初的设计者们,程序是怎么设计的。如果此时恰巧又有什么新技术出现,就会更显得原有系统的破旧与不堪。

相信这就是软件工业时代的所有企业都不得不面对的尴尬境地。难倒真的是我们最初的设计错了吗?是的,我们都这样质问过我们自己,因此我们开始尝试在软件设计之初投入更多的精力。我们开始投入更多的时间作需求调研,考虑更多可能的需求变化,做更多的接口,实现更加灵活但复杂的设计。然后呢,我们解决了我们的问题了吗?显然是没有。需求并没有像我们想象的那样发生变更:我们之前认为可能发生的变更并没有发生,使我们为之做出的设计变成了摆设;我们之前没有考虑到的变更发生了,让我们猝不及防,软件质量开始下降,我们被打回了原形。难倒真的是无药可解了吗?在我看来,如果我们没有看明白软件开发的规律与特点,那么我们永远找不到那份向往已久的解药。现在,让我们真正静下心来分析分析软件开发的规律与特点吧。

软件,特别是管理软件,其实质是对真实世界的模拟。我们通过对真实世界的模拟,实现计算机的信息化管理,来提高我们的生产效率。然而,真实的世界复杂而多变的,我们认识世界却是一个由简单到复杂循序渐进的过程,这是一个我们无法改变的客观规律。因此,毫无疑问,遵循着这样一个客观规律,我们的软件开发过程必然也是一个由简单到复杂循序渐进的过程。

最初,我们开发的是一个对真实世界最简单、最主要、最核心部分的模拟。因为简单,我们的思路变得清晰而明了。但是,我们的软件不能永远只是模拟那些最简单、最主要、最核心的部分。我们的客户在使用软件的过程中,如果遇到那些不那么简单、不那么主要、不那么核心的情况时,我们的软件就无法处理了,这是客户无论如何不能接受的。因此,但软件的第一个版本交付客户以后,客户的需求就开始变更。

客户的需求永远不会脱离真实世界,也就是说,真实世界不存在的事物、现象、关系永远都不可能出现在软件需求中。但是,真实世界的事物、规则与联系并不是那么的简单与清晰的。随着我们的软件对它模拟得越来越细致,程序的业务逻辑开始变得不再那么清晰而易于理解,这就是软件质量下降最关键的内因。

任何一个软件的设计,总是与软件的复杂度有密切的关系。举例来说吧,客户资料是许多系统都必须要记录的重要信息。起初,我们程序简单,客户资料只记录了一些简单的信息,如客户名称、地址、电话等等,但随着程序复杂度的增加,客户资料开始变得复杂。比如,起初“地址”字段就仅仅需要一个字符串就可以了,但随着需求的变更,它开始有了省份、城市、地区、街道等信息。随后还会有邮政编码、所属社区、派出所等信息。起初增加一个两个字段时我们还可以在“客户信息表”里凑合一下,但后来我们必须要及时调整我们的设计,将地址提取出来单独形成一个“地址信息表”。如果不及时予以调整,“客户信息表”将越来越臃肿,由10来个字段,变成50个、80个、上100个……

信息表尚且如此,业务操作更是如此。起初的业务操作是如此的简单而明了,以至于我们不需要花费太多的类就可以将它们描述清楚。比如开票操作,最初的需求就是将已开具的票据信息读取出来,保存,并统计出本月开票量及金额。这样一个简单操作,设计成一个简单的“开票业务类”合情合理。但随后的业务逻辑变得越来越复杂,我们要检查客户是否存在、开票人是否有权限、票据是否还有库存,等等。起初的开票方式只有一种,但随着非正常开票的加入,开票方式不再单一,而统计方式也随之变化……随着业务的不断增加,软件代码的规模也在发生着质的变化。如果这时我们不及时调整我们的设计,而是将所有的程序都硬塞进“开票业务类”,那么程序质量必然会退化。“开票业务类”由原有的数十行,激增到数百行,甚至上千行。这时的代码将难于阅读,维护它将变成一种痛苦,毫无乐趣可言。

面对这样的状况,我们应当怎样走出困境呢?毫无疑问,就是重构,软件的重构。开票前的校验真的属于“开票业务类”吗?它们是否应当被提取出来,解耦成一个一个的校验类。正常开票与非正常开票真的应该写在一起吗?是否我们应当把“开票业务类”抽象成接口,以及正常开票与非正常开票的实现类。这就是我给大家的良方:当软件因为需求变更而开始渐渐退化时,运用软件重构改善我们的结构,使之重新适应软件需求的变化。

大话重构连载首页:http://fangang.iteye.com/blog/2081995
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
   发表时间:2014-06-20  
重构能解决个屁啊,没把破系统读懂一遍,你敢动手么
0 请登录后投票
   发表时间:2014-06-21  
ray_linn 写道
重构能解决个屁啊,没把破系统读懂一遍,你敢动手么

呵呵,起初的重构你甚至可以不必完全读懂代码。这是一个连载,你慢慢就可以明白,其实重构没有那么复杂,那么困难。
0 请登录后投票
   发表时间:2014-06-23   最后修改:2014-06-23
貌似完全没啥参考价值的说。
一个业务系统,根据个人经验通常由下面几个基本要素构成:
1.基础数据的保存和展现
2.过程数据的保存和展现
3.流程数据的保存和展现
4.数据处理、分析(执行业务逻辑)
5.结果数据的展现

简单解释一下:
1.基础数据的保存和展现:
模拟场景:
楼主说的开发票的业务,录入发票内容,保存、修改、删除、查询、打印等等,这是基本数据的CRUD

2.过程数据的保存和展现
模拟场景:
财务月初有个起初余额A,当做了X笔业务,财务系统里保存Y笔凭证之后,得到期末余额B,那么从A到B之间的过程数据——业务流水也好,财务凭证也好,这些数据反映了A到B的过程,是过程数据。

3.流程数据的保存和展现
模拟场景:
某个业务环节需要审批流程,我们以最简单的一个字段保存流程状态的方式,流程状态在业务处理时候发生变迁,反映着业务处于处理流程中的哪个阶段。

4.数据处理、分析(执行业务逻辑)
模拟场景:
从银行接口数据的流水单中选取一条A元的收款做收入确认操作的数据处理过程:收入总额增加了A元,未确认的收入记录去除这条记录。

5.结果数据的展现
与基础数据的展现略同

其中1和5是很容易的事情,不管怎么做,无非是CRUD的事情。完全没有逻辑,可以随意重构,再重构无非是CRUD的方式方法不同,本质上是一样的。
2,3,4项,是业务系统的核心,通常都是2,3,4步骤都是掺杂在一起完成的,像楼主都没说怎么重构,就能“简单”的重构了么?那很多时候干嘛还要不断的开发新系统?“简单”重构不就完了么。。。。
另外,不搞清楚2,3,4核心内容中的每一处细节,你就敢重构么?搞清楚细节无非两个途径:一是文档,二是扒代码,貌似楼主一不看文档,二不扒代码,“甚至可以不必完全读懂代码”就能重构?那很多人看过这样喊了半天重构就是没提怎么重构的文章都只能呵呵了。
像楼主说的加加字段之类的,1和5变了,貌似只能说是扩展维护,还谈不上重构,因为按楼主的意思,核心的东西根本就不动。就像换个马甲,换个包装而已。或者1的数据组织形式变了,单表变多表了,行变列,列变行了,1个字段变成100个字段了,这是程序员玩的花活,都不是根本性的变化。大多数人头疼的是2,3,4变了怎么处理,甚至有时候数据的来源不是1了而是从1' 、 1''了 ,伴随着 2,3,4整体的变化,经常很多系统代码又没有从业务逻辑层面上做系统的规划和设计。。。导致了想分开的分不开,想捋清的捋不清,一个数据流程有多个数据入口和多个数据出口,跟河流一样,河里的水污染了,当大家都分不清是从哪个上游支流引发的污染,又会对哪些下游支流产生影响时,这时候怎么办?
0 请登录后投票
   发表时间:2014-06-23  
没有谁比我更感受深刻了,维护一个十年左右的电子商务网站,每年几百亿的流水,系统没有一个人能说明白,需求还是源源不断。。。。。。。。
0 请登录后投票
   发表时间:2014-06-23  
lzzgym 写道
没有谁比我更感受深刻了,维护一个十年左右的电子商务网站,每年几百亿的流水,系统没有一个人能说明白,需求还是源源不断。。。。。。。。

N久都没有登陆过了,忍不住要上来发一句,楼上的,你的职业对得起你的LOGO么,转行做媒人去啊,整个相亲节目,就要“神州第一媒”
0 请登录后投票
   发表时间:2014-06-23   最后修改:2014-06-23
代码的重构,只能解决局部范围内代码结构清晰不清晰,易读不易读,易维护不易维护的问题。
需求的不断变更和复杂化,貌似是代码级的重构所克服不了的。数据结构可能变化,算法(业务逻辑)也可能变化,这是避免不了的,还是不懂所谓“软件的重构”是什么意思。
软件=数据结构+算法,所谓“软件的重构”是什么意思呢?不知道要重构数据结构呢,还是要重构算法呢?假设是一个大型的业务系统的话,已经存在大量数据,为了个别新需求对整个数据结构做重构,要看是怎么个重构法,加字段还说的过去,要是复杂一点的重构貌似就不可取了。对算法的重构貌似楼主意思是说的代码级别的重构,说白了就是一大自然段分成n小自然段,每段有个独立的中心思想,个别重复的自然段可以引用,这个对新增的需求可以这么做,对已经存在的大量业务逻辑,一些数据结构和业务逻辑的紧耦合已经形成,不捣鼓明白每一条业务流程谁敢这么做?

简单的说下我的观点吧,重构仅适用于简单情况和系统的开发阶段,对于已成型的运营中的大型业务系统非常不适用。
0 请登录后投票
   发表时间:2014-06-23  
@boywukong
你的问题也是许许多多人的问题:还没有明白重构是什么,或者以为自己懂了,就开始在讨论重构的对与错了,叫人只能呵呵。记住,重构绝对不是重写,他们有天壤之别。
系统重构是个什么玩意儿

重构分为很多个层次,从代码级一直到架构级,但即使架构级重构也是从代码级一步一步开始的。重构与重写的最重要区别有2点:
1.是运用重构方法进行一步一步等量变换,你首先要明白那些重构方法。
2.不能改变软件外部行为,即重构对用户完全透明,这是验证你是否正确的根本保证。
而重写则完全没有章法,因此你理解的重构实际是重写,做人要谦虚。
0 请登录后投票
   发表时间:2014-06-24  
我现在也处在一个做电信项目的环境,接触到的系统(群)也都是(最多一个有十年+)的老系统,就像上面那位“十年电子商务网站”一样痛苦,“系统没有一个人能说明白,需求还是源源不断”,从这个角度上来说,重构什么的已经是不可能的,从任何角度都不可能。

但是我依然要说作者的想法是对的,不能更多。因为这些所谓的难以维护的,难以阅读的糟糕的老系统,并不能说明真实的业务需求就真的复杂到刚写完就无法挽回的程度,因为当你看那些老代码时,发现一个函数上百行代码中if else + for嵌套了近10层(真实故事),你就知道:为难你的不是上帝,而是人。当然这么low的代码并不是一次性写出来的,的确大家都有苦衷,“需求还是源源不断”,于是这里有场景,补一个分支,那里有一个异常,补一个try catch,这里忽然变数组了,加个遍历吧。

“业务说一句,你写一行”,这样的代码必然变成不可控的无限蔓延下去。这时候,有重构思维的人就能在业务不可控之前,将代码重新架设好,提供合理的扩展渠道(比如提供抽象类,比如提供匿名类传入,比如设计泛型T等等),如果你能熟练的在(网上所谓的含金量低的)业务系统中设计这些只有在server系统,spring等框架系统中才常见的高级用法。那么你的业务系统一样会很优秀。

1 请登录后投票
   发表时间:2014-06-24   最后修改:2014-06-24
@fangang
为了保证咱们两个在同一个频道内说话,没有各说各的,我用你文章里的例子:
比如:
1.有个开发票的业务,发票元数据根据种类的不同有好几种元数据格式:
    个人所得税发票data,购房契税发票data,餐饮营业税发票data,增值税发票data等等(可能举例不准确,就说个意思)

2.根据元数据的不同可能有不同的发票业务业务逻辑:
    个税bussiness,契税票bussiness,餐饮税bussiness,增值税bussiness

3.不同的业务逻辑里可能有不同的核心方法:
    个税method,契税票method,餐饮税method,增值税method

4.不同的发票对应不同的统计维度:
   个税statistic,契税票statistic,餐饮税statistic,增值税statistic

OK不?楼主说的是这个意思吧?
楼主的意思是当系统复杂的时候,通过重构来解决软件遗留系统不易扩展结构不明晰的弊病么?
那么OK,假设就一个发票种类:
当时设计初期就没好好设计扩展性,结果就是

data、bussiness、method、statistic X1 耦合的很紧(X1的意思大致反映了复杂度)

比如需求变化了,变成10个发票种类:
这回楼主来了,好好设计了一把,对于这10个开发票的情况做了重构,结果就是

data、bussiness、method、statistic X10 松耦合

very done,你这么做我举双手赞成,假设后面又来了10种特殊的发票,并且业务流程发生变化,甚至输入输出都跟着变呢,比如原来是1张一张开,现在改成n张n张开(n张的发票data开在同一个票据里,只作为一个举例,不考虑实际是否相符),m张m张统计,这时候傻眼了,是不是又要重构?
好吧,楼主再重构一把,把当前20个发票逻辑在做整体设计、整体抽象、整体重构。。。
那么以后又有新变化呢?楼主是否还要将以前的全部再重新整理、重新抽象、重新设计、重构呢?

那么这里我问个问题:比如到最后系统中有100种开发票的情况,楼主是否还针对当前所有100种做重新整理、重新抽象、重新设计、重构呢?目的只为了代码看起来清晰?这种情况下能清晰易懂的了么?

所以我说:重构是代码级别的,只适用简单的情况或者系统的开发阶段。
如果目前系统中已经有100种开发票的方法了,还要再增加一种极其特殊的情况,怎么办?
理智的方式是看:系统当前的方式里能不能解决新的需求,不能的话,就根据新的发票data做为输入,全新的统计statistic 做为输出,输入输出中间那段数据处理,随便怎么写,无论怎么写都是代码级别的,从系统级别来看,只关心每个用例下一个数据输入能准确达成一个数据输出就OK,不需要再把以前那100种开发票的方法在纠结进来。

另:重构:refactor 字面上理解是重新整理、重新组装的意思。要对一个“遗留系统”做refactor?貌似有点过吧?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics