接手了严重过时的软件,到底是该逐步重构还是摧毁重写呢?

HelloWorld官方 等级 521 2 1

有一个应用程序充斥着技术债,严重的过时了,或者只是对用户服务不足,因此,我们需要了解我们的最佳选择是什么——是继续艰难地探索并逐步进行重构更有意义,还是把它全部摧毁并从头开始重写更有意义呢?这就是我们将在本文中探讨的基本难题。所以让我们开始吧……

但是没有那么快!在我们进一步研究之前,需要解决一个大家“避而不谈”的问题,即:对于任何需要改进的遗留应用程序,下一步要做什么并不是一个这样或那样就可以了的简单决定。我们通常会将我们的选项框定为重写或重构,但这些术语,正如我们将要看到的那样,实际上只是摆在我们面前的一系列选项的替代品。

接手了严重过时的软件,到底是该逐步重构还是摧毁重写呢?

通过重构,在现代化遗留应用程序的场景中,通常意味着我们将会保持应用程序基本不变,但会进行一些微小的内部改进以解决特定的问题(如可维护性、可扩展性等)。另一方面,通过重写,则意味着我们打算“从头开始”,或者换句话说,进行重大的变更。

但这只会引出下一个问题!较小变更(Minor)和重大变更(Major)到底是什么意思?如果我们打算将前端框架从 AngularJS 升级为 React,但保留后端服务不变,这是重构还是重写呢?或者,如果我们想要将一个单体应用拆分成三个不同的微服务,但只是复制粘贴业务逻辑到新的版本控制存储库中,那这是重写、重构还是其他什么呢?我们真的在乎吗?给我们的努力贴上标签真的很重要吗?

是的,确实是。虽然我们的工作是构建可运行的软件,而不是对语义进行哲学思考,但我们使用的词汇确实会产生影响。当我们提议走重写或重构的道路时,业务和技术涉众应该能够准确地理解我们的意思,以及需要付出什么样的努力。换句话说,我们措辞的精确性将有助于我们更好地设定预期。此外,当我们穿过一些概念上的迷雾并找到更清晰的定义时,也会让我们对这个决定有一个更细致的看法,并能使我们脱离狭隘的重写或重构框架。

所以,就像任何一次长途旅行一样,在我们跳上车出发之前,让我们花点时间整理行李。我们不想出现在海滩上才发现自己忘了带泳衣。

功能改进

一个好的起点是定义重写和重构不是什么,它们是改进应用程序功能的策略。这种类型的工作,无论是修复缺陷,交付新特性,还是清理用户界面,我们都可以称之为增强。它是关于改进应用程序为用户所做的事情的,正如我们稍后将看到的那样,它是正常的开发状态。

接手了严重过时的软件,到底是该逐步重构还是摧毁重写呢?

但在某些情况下,功能增强的范围可能相当大。例如,企业可能想要确定某应用程序是为正确的用户群提供服务的,但所有的功能都需要彻底检查。这种情况也可能被称为重写,但是在这里我们要做一个区分。因为这种类型的工作需要构建所有新的功能,所以它与新建项目基本上没有区别。当需要定义新的功能需求时,从零开始开发一个独立的系统,并且不能继承原逻辑或代码,我们会将其视为新开发的应用程序,而不是重写。

重构是什么

向应用程序添加功能并不是本文的重点。我们的场景是:应用程序通常会执行预期的操作,但缺少如何执行的能力,换句话说,即缺少系统的非功能或质量属性。例如,用户可能对这些功能感到满意,但应用程序可能过于难以维护,或者可能频繁崩溃,或者在峰值负载下性能很差。当这些非功能属性缺失时,我们才会考虑重写或重构。

关于重构,我们经常使用这个术语来指代不同的工作范围。 Martin Fowler 在他的《重构》一书中是这样定义重构:

重构是一种用于重组现有代码主体,在不更改其外部行为的情况下更改其内部结构的规范技术。

从这种纯粹的意义上讲,重构主要是为了使代码更易于维护。这可能是分解冗长的或复杂的函数,修复不一致的命名,添加单元测试,或者重组类的层次结构、数据结构或模式。请注意,没有更改任何对用户可见的内容,但是修改了内部的代码结构,使其更容易为开发人员所使用,从而提高了我们的工作效率(和幸福感!)。

然而,在我们做重写或重构决策的场景中,这个定义过于严格。在我们的场景中,当我们谈论重构时,我们通常不会区分内部和外部,而是会区分功能和非功能。例如,我们可能会说,我们选择重构现有的代码库,以提高应用程序的可靠性或性能。从技术上讲,这些质量属性不是系统的内部属性(用户可以明显感知到它们,因为它们直接影响用户),它们只是非功能性的。这可能是一个过于学术的区别,但本着精确的精神,我认为有必要指出来。在本文中,我们将使用更广泛的重构定义:

重构是一种方法,通过这种方法对现有的代码主体进行增量重组,以提高系统的质量属性。

最后,需要注意的是重构是关于迭代变更的。它会对应用程序进行细微的调整,将其交付,然后冲洗并重复。在功能增强的基础上,重构可以使我们的用户满意,使我们的代码库保持健康,并能最大限度地减少技术债和功能缺陷。然而,如果被忽略,我们可能需要考虑更重的替代方案。

重写是什么

与重构一样,重写也有着相同的基本目标:改善应用程序的非功能性。区别在于更改了多少。简单地说,如果重构是管道胶带,那么重写就是一个大锤或一个反铲。它不是要对现有的功能进行渐进式的改进,而是要摧毁它,重新构建。对应于我们讨论过的其他类型的开发工作,我们可以这样可视化地展示重写:

接手了严重过时的软件,到底是该逐步重构还是摧毁重写呢?

可以说,重写是一项涉及到对系统进行重大更改的工作,以便对其质量属性进行根本性的改进。但也有灰色区域。重写工作通常会扩散到其他象限。例如,一个应用程序可能会因为技术债而瘫痪,以至于几乎不可能再添加新特性。我们可能会选择重写然后建立一个新的基础来提高可维护性和可扩展性(质量属性),但是在重写的过程中,我们也可能会加入一些新的特性来满足业务需求。它基本上是重写的,但也进行了一些增强。

同样地,在重写和重构的边界上也存在一些模糊性。在一些“平移”(lift-and-shift)的情况下,系统被迁移到一个新的平台上,使得它在本质上成为了一个不同的应用程序,但其中的代码实现基本相同,即没有重构。这感觉像是重写了,但真的是这样吗?需要做多少更改才能被视为是重写呢?

再次,让我们看看是否可以增加一些精度。在本书中,我们使用以下定义:

重写就是重新构建存在于遗留应用程序中的相同功能,但使用不同的语言 / 框架,在新的代码库(不仅仅是分支)中维护,并作为一个全新的构件进行部署(可能部署到不同的平台上,如服务器、硬件、无服务器、客户端等)。

换句话说,我们要画一些明确的界限。例如,如果我们要重写一个重要的函数、类甚至模块,但是我们的工作是在代码库主线的分支上完成的,那么这就不是重写。同样地,如果我们重新实现了应用程序的一部分,但是系统本身仍作为同一构件(二进制文件、WAR 等)部署,这也不是重写。在我们的场景中,重写是很大的(BIG)。它们是涉及需要构建和部署全新应用程序的重大更改。是的,在实现这一目标的过程中可能会有一些渐进的步骤,我们稍后将会看到,但这是一种与重构根本不同的工作。

为了帮助自己梳理具体的情况,你可以把它画出来。即对于可能现代化或改进应用程序的不同途径,究竟要更改些什么?这里有一个例子:

接手了严重过时的软件,到底是该逐步重构还是摧毁重写呢?

实际上,变更的性质可能与重写或重构的定义不一致,但这没有关系。例如,上图可能表示了这样一种情况:我们提议使用一组更现代化的技术来重新实现某个服务,但同时保持公开的 API 和底层持久层结构不变。这是一个较小变更和重大变更的混合体,所以应该如何确切地标记它可能仍然不清楚。然而,重要的是,我们已经了解了更深层次的细节,这将有助于我们更好地思考和证明这个决定。

现在我们的准备工作已经差不多完成了,但是在我们开始我们的旅程之前,让我们先把它们放在一起,看看这些不同类型的开发工作是如何适应给定应用程序生命周期的。换言之,让我们探究一下起源故事,以便重写。

重写的起源故事

这一切都是从新建开发阶段开始的,在这个阶段我们有了一个想法,并开始从中构建一个功能强大的应用程序。经过数周或数月的不懈努力,某些产品最终被投放到“市场”(可能是实际的付费客户,或只是一组内部业务用户,等等)。如果该应用程序很受欢迎,它将会在一段时间内处于增强的平衡阶段,在此阶段会添加新功能并修复缺陷。每个人都很开心。但最终,技术债会累积起来,我们开始看到努力的回报在递减,虽然在过去一周的开发足以添加一个全新的功能,但现在一周却不足以改变一个按钮的颜色。

接手了严重过时的软件,到底是该逐步重构还是摧毁重写呢?

在这一点上,我们可能会质疑是否值得投入更多的时间和金钱。此外,自从应用程序首次发布以来,可能已经出现了一些令人兴奋的新技术,我们可能会对如何利用这些技术来使我们的应用程序更具弹性、更易于使用、更具性能等抱有一些宏伟的设想,因此我们开始制定重写计划。其想法是在短时间内冻结现有系统的开发,然后将资源转移到替换系统上。我们将首先构建基础(使用更现代化的模式、工具、语言等等),然后将现有的功能迁移到该基础中。用户只需要安然度过“暂停”(即不需要任何新的更新),但当重写系统就位时,工作效率就会是之前的两倍(或更多!)。

虽然这个计划看起来很直接,但它掩盖了一些关键的风险:技术、组织和心理因素,所有这些因素都会导致重写阶段是极不稳定的。随着这一阶段的拖延,我们成功替换的机会会越来越渺茫。在接下来的文章中,我们将探讨一些隐藏在重写工作中的危险,以及为什么我们总是不顾这些危险勇往直前的原因。

收藏
评论区

相关推荐

接手了严重过时的软件,到底是该逐步重构还是摧毁重写呢?
有一个应用程序充斥着技术债,严重的过时了,或者只是对用户服务不足,因此,我们需要了解我们的最佳选择是什么——是继续艰难地探索并逐步进行重构更有意义,还是把它全部摧毁并从头开始重写更有意义呢?这就是我们将在本文中探讨的基本难题。所以让我们开始吧…… 但是没有那么快!在我们进一步研究之前,需要解决一个大家“避而不谈”的问题,即:对于任何需要改进的遗留应用程序,
金三银四该准备起来了?
020年是不平凡的一年,一场突如其来的疫情改变了很多人的人生轨迹;作为一名互联网程序员来说,最大的感受是不是某某公司有裁员的消息,之前网传程序员到了35岁之后必须要转行,现在又有人传言:“疫情之下,程序员今年要过苦日子了,降薪裁员是大趋势“。我们不可否认,从去年年底开始,疫情就导致了很多企业陷入危机,继而停发薪资、变相裁员,在如此“难搞”的大环境下,对程序员
用CSS也可以任意改变PNG图标颜色?
在开发中图标跟我们是形影不离的,在开始之前先聊一些题外话,图标有什么作用? 通用图标可以代替文字说明,能更简洁地表达某个设计的意图,让用户看到图标就知道它代表什么?有什么用途? LOGO类图标可以起到品牌宣传,同时因图标更醒目,占据更大的位置,用户更容易点击。 看图片更符合人的视觉需求,简洁的图片能降低人的信息焦虑。试想我们如果看到一个满满
深入理解 Go Slice
(https://imghelloworld.osscnbeijing.aliyuncs.com/0ce8a8773a658d4b843e5796a0dbf001.png) image 原文地址:深入理解 Go Slice(https://github.com/EDDYCJY/blog/blob/master/golang/pkg/20
大佬说:“不想加班你就背会这 10 条 JS 技巧”
(https://imghelloworld.osscnbeijing.aliyuncs.com/83909ede68f61936ac3ae10c9ce8b223.png) 为了让自己写的代码更优雅且高效,特意向大佬请教了这 10 条 JS 技巧 1\. 数组分割 const listChunk (list
Android Service 流程分析
启动Service过程 Android Service启动时序图 (https://imghelloworld.osscnbeijing.aliyuncs.com/039313fdaaf1e7dea3bde222b3ec9934.png) Android Service启动时序图.png 上图就是Android
JavaScript中本地存储的方式有哪些?
(https://imghelloworld.osscnbeijing.aliyuncs.com/1f907f0895e2be23aa56604dd42e3626.png) 一、方式 javaScript本地缓存的方法我们主要讲述以下四种: cookie sessionStorage loc
原来Python绘图也可以这么漂亮,这次真的是学习到了!
(https://imghelloworld.osscnbeijing.aliyuncs.com/8830803f033eeed85783e9058cf08968.png) 作者:朱小五 来源:快学Python 👆人生苦短,快学Python! 最近看了一篇文章《一个牛逼的Python 可视化库:PyG2Plot》,可惜只是简单介
30个前端开发人员必备的顶级工具
在本文中,我为前端Web开发人员汇总了30种顶级工具,从代码编辑器和代码游乐场到CSS生成器,JS库等等。 (https://imghelloworld.osscnbeijing.aliyuncs.com/2e7966318084a45d05a0926cbd749a02.png) 目录 CSS代码生成器 CSS3 Generator
SQL 诞生 48年了!为什么我们仍在使用它?
(https://imghelloworld.osscnbeijing.aliyuncs.com/a784e838a6d497b87a19798716e69fe8.png) 为什么我们仍在使用 SQL? SQL 和关系数据库管理系统(RDBMS)都是在 上世纪70 年代早期开发的。Edgar F. Codd 开发了 RDBMS,而 Do
被“词云”包围的冰冰会更好看吗?安排
(https://imghelloworld.osscnbeijing.aliyuncs.com/b299933deefc692934e8cc6141ab3894.png) 大家好,我是小五🐶 昨天「凹凸数据」发了一篇张同学投稿的文章《用Python爬取王冰冰vlog弹幕并制作词云(https://mp.weixin.qq.com/
关于:父组件中的子组件中的子组件(孙级)添加数据后怎么样去调用父组件中的方法呢?
1、需求引入image.png 这是父组件下的子组件的子组件,一个绑定子设备的弹出窗modal,那么我们怎么样在这个组件中选中子设备后点击确定时去触发父组件的父组件的table列表数据刷新呢? image.png image.png image.png 组件的层级关系.png上述的图示便是部分代码的展示,一般的组件传值有父组件传子组件(
讲解Excel的16种图表类型的“含义”,知道该怎么画图了!
大家好,我是小五🐶 相同的数据,使用不同的图表进行体现,效果也会千差万别,那么我们应该如何正确选择,才能让图表的作用发挥到极致呢? 1.柱形图 柱形图是最常见的图表类型,它的适用场合是二维数据集(每个数据点包括两个值,即X和Y),但只有一个维度需要比较的情况。例如,如下图所示的柱形图就表示了一组二维数据,【年份】和【销售额】就是它的两个维度,但只需要比
面试系列-3 限流场景实践
英国弗兰明曾说过一句话:“不要等待运气降临,应该去努力掌握知识。” 1 前言大家好,我是阿沐!你的收获便是我的喜欢,你的点赞便是对我的认可。今天呢,我们就不聊redis面试系列,我们一起来聊一聊限流操作以及使用场景。很奇怪哈,为啥突然转变画风了,之前一篇文章中提到 redis的限流操作,并没有实际给小伙伴们演示以及场景的使用演练。所以呢,既然有人私聊问我了,
JAVA回调机制(CallBack)之小红是怎样买到房子的??
JAVA回调机制CallBack 序言最近学习java,接触到了回调机制(CallBack)。初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义。当然了,我在理解了回调之后,再去看网上的各种讲解,确实没什么问题。但是,对于初学的我来说,缺了一个循序渐进的过程。此处,将我对回调机制的个人理解,按