|
前构(Prefactoring)与重构(Refactoring),就像前馈与反馈,二者分别位于天平的两端,在做软件设计时,您会做出怎样的选择?您是否喜欢玩“零-和”游戏,只前构不重构,或者只重构不前构?“零-和”游戏是危险的,也是不现实的。在现实的软件开发中,前构和重构,一个都不能少!
巧合的是,在软件开发中,程序员、架构师的思维模式、思考方式与专业棋手非常相似,软件设计者可以学习和借鉴棋艺中的思维技巧和策略。
围棋中的“前构”是在落子之前,对下一步棋局的各种可能变化、对策进行预分析和探索,对己方的战略、战术进行事先的规划和选择。围棋中的“重构”是在落子之后,频繁地根据对手的实际应招、棋局的具体情况和实时变化,灵活动态地调整原先的“设计”(战略、战术),制定新的、有效的对策和“设计”。
在对弈中,很多棋友连初学者都下不过的情况也会时有发生。我想主要原因很可能在于,他们在每一步落子之前,考虑的很不周全,思考过于简单,用软件术语讲,就是他们的“前构”能力太弱。
事实上,在常规围棋比赛中,走一步看一步、临时抱佛脚的做法,是绝对不行的,注定要输棋。
而初学者能够赢得对手,无非是比对手往前多看了几步,比如下一步看十步、看二十步,然而古力、常昊、马晓春和聂卫平等大师们下棋又岂止是下一步看二十步?他们满腹经纬,大脑“机器”里装着数不清的棋局变化。围棋大师们的超强记忆力和逻辑判断能力、计算能力,令人惊叹!而且专业棋手往往会充分利用目前局势、用足时间进行足够的“前构”,一般总是要等到读秒即将结束时才肯落子,以避免落下考虑不周、草率决策的遗憾。
围棋高手需要自幼通过长年累月的训练,学习、记忆大量的棋谱、棋局、定式等专业知识,从而熟练掌握在落子之前“前构”设计的能力,最终成为职业大师。如果一名棋手思维简单,缺乏足够的想象力、预见力和“前构”力,那么中盘告负对他而言很可能是家常便饭。
有的读者可能会反问我:难道在棋类比赛中,就没有以“重构”为主、靠走一步看一步(或少看几步)取胜的情况?
这种情况也有。其实,在围棋快棋赛中,根本没有充裕的时间去思考、前构,超时即定胜负。棋艺稍逊的人面对空前的压力和任务密集程度,没有超强的记忆力、计算力和应变力是很难胜任的,而只有围棋大师才能凭自己多年炼就的良好“感觉”、经验和素养巧妙应对。
应对快棋和车轮大战,由于读秒或时间(进度)的严格限制,棋手被迫只能采取“重构”的策略,走一步看一步(或少量的几步),非常敏捷地根据对手的下法和临时局面,采取灵活应变的应对措施,不断地调整己方的战略战术,与以“前构”为主的常规赛相比,这么做反而是合理有效的,用我们软件术语讲叫作“拥抱变化”,此时过多的考虑、前构、乃至长时间思考显然是无用、多余、不合时宜的。
然而,为什么在这种情况下,大师棋手仍然可以拥有很高的获胜率呢?同样的时间限制,同样采用走一步看一步的战术,外部约束也都差不多,为什么业余棋手还是下不过专业棋手?
我们认为,原因恰恰就在于大师级棋手所拥有的强大“前构”能力。车轮大战中的上百名棋手再怎么下,下出来的变化可能都逃不出棋艺大师的预计和设计,大师事先所见过的棋局变化,演习过的和所掌握的应对方案实在是太多了(前构能力),所以他可以连“想都不想”,轻松应对,“跟着感觉走”。表面上双方都是走一步看一步,无暇深虑,但实际上双方“前构”能力的差距是如此悬殊,而大师的强大的前构能力与重构能力早已合二为一,获胜自然在情理之中了,这正是由于内功的巨大差异所造成的。
重构依靠应变,应需而变,而这种强大的应变力恰恰是建立在日积月累地、掌握了已有的大量棋局、对策、技法的基础之上。试想,遇到一种突发的局面、状况,如果您不知道应对措施,又谈何重构呢?可见,重构与前构完全是不矛盾的,关键是我们如何选择前构或重构的时机,而学习大量前构知识显然可以促进我们更好地重构,反之亦然。
无论前构还是重构,目标是统一的,方向也是一致的,都是为了取得一局棋的最终胜利。岂止是围棋,我们发现,在中国象棋、国际象棋、桥牌等脑艺竞技类项目中,人类大脑的前构与重构设计活动都能够达到和谐、完美的统一。
我拥有丰富的前构知识,本可以前构,但却根据实际需要,主动选择了重构;反过来,我娴熟地掌握了重构技能,却可以放弃重构,在合适的时机具有先见之明地选择了预先前构,从而避免了后期不必要的“折腾”。程序员、软件架构师应该同时掌握前构和重构能力,二者可以适时地互为转化,这就是我想阐明的软件设计前构与重构的辩证关系。
那么,我们应该向围棋大师、象棋大师学习些什么?
近五年来,随着国际上敏捷方法尤其是 XP(极限编程)过程的兴起,软件设计的重构技术在国内软件工程界也获得了越来越多的关注和重视。当然,随之而来的,还有网络上、程序员社群中,因一些所谓的主流技术媒体、技术作家、技术传播者的误导、夸张宣传,所形成的对XP、重构等新技术的大量误解和错误认识。
有一种片面的错误观点认为,传统的预先软件设计步骤在XP过程中消失了,软件开发可以完全通过编写测试代码、编写实现代码、测试通过、重构、测试这样一个循环(即所谓的TDD)来完成,从而实现架构乃至整个产品、系统的演进式设计。
更有甚者,有些软件工程基础知识薄弱的程序员还把重构发挥到了极致,在开发中只注重片面地实现功能,完成工作量,而不顾、不愿考虑软件、架构、代码的设计质量,即便设计、代码质量再差也无所谓,并美其名曰:反正将来我们还可以“重构”么,现在又何必顾虑?这样做真的很好么?这是正确的做法,是Kent Beck、Martin Fowler大师的本意吗?
Martin Fowler有一篇著名的文章《Is Design Dead?》(设计已死?)。前构设计真的可能消失吗?设计真的会死吗?明确答案:不可能。只要我们仔细阅读Kent Beck、Martin Fowler的著作就不难发现,其实 XP/TDD中的编写测试代码,编写实现代码,重构,这三个动作都离不开设计,或者说它们本身就是一种设计,只不过Kent Beck、Martin Fowler换了一种说法/语汇,对此没有强调罢了。而我们知道,重构动作本身就是一种设计,是对原先设计的调整,从一个设计到另一个设计的变换步骤。
与棋艺一样,软件开发中前构与重构始终并存,是一个问题的两面,现实中缺一不可。那么,究竟是前构多好,还是重构多好,我们如何把握好这个度以及前构与重构的时机呢?
对于一个具体的软件开发项目而言,由于最终交付期限和成本的限制,无论软件设计的前构,重构,都必须在某一时刻终止(收敛)。重构是有代价的,每一次重构,对已有设计的改动,都需要时间和人力上的投入,最终这些投入都可以换算成项目的成本。
所以在理想情况下,应该从总体上减少重构的次数,而不是重构次数越多、越频繁越好,程序员、架构师应该从主观上争取做到每次设计方案都一次到位,改动越少越好,减少返工和浪费,这样对于项目而言,成本和时间才最节省。
当然,在客观上,就像棋赛的定时读秒限制一样,软件项目允许我们进行前构设计的时间也是有限的,而实践证明在项目前期脱离编码、测试和有效的用户反馈,让软件研发团队单方面地通过瀑布式的所谓“设计阶段”,进行大规模的详细设计,存在着很大的风险。
另一方面,软件开发中有太多的外部因素会影响到软件架构和设计的变化,比如客户需求的变化、市场竞争条件的变化以及技术革新等,因此在实践中,我们往往很难做到料事如神,设计方案一次到位,这个时候就需要采取重构策略,通过多次迭代、调整和试验,来最终稳定需求,逐步减少架构的变动,直至在项目结束前获得一个最佳设计方案。
总之,不管前构,还是重构,就像专业棋手下棋一样,我们应该力求每一次作出的软件设计决策(落子),在当时的约束条件和资源情况下,都是最优或次优的设计,以节省项目的时间和成本资源。
正如一名专业棋手,一位经验丰富的软件架构师必然要在他的职业生涯中不断地学习、掌握大量的、已有的成熟设计经验和成果(定式和模式)。一些程序员认为有了重构技术,就可以放弃对前构技术,以及大量成熟的软件设计技术,比如OOA、OOD、UML 建模、架构设计模式的学习,肯定是一个错误。与棋类艺术一样,优秀的软件开发团队可以娴熟地做到前构与重构的完美统一,积极地响应变化,敏捷地达成目标。
|