导读
技术债在软件开发过程中不可避免。但是,在紧急的产品需求面前,技术债往往会被人忽略,解决时期一拖再拖。长此以往,技术债像滚雪球般越滚越大,给我们的迭代开发将会带来致命影响,偿还技术债的成本也会成倍增加,所以我们必须重视技术债,积极还债。本文会带领大家一起探讨什么是技术债,技术债的危害,日常工作中哪些场景容易产生技术债,如何管理我们的技术债以及如何偿还技术债。希望本文能够起到抛砖引玉的作用,让大家在技术债面前少踩坑,争取早日达到“债少一身轻”。
目录
1 什么是技术债
2 技术债会带来哪些严重后果
3 实际开发场景中的技术债以及避坑指南
4 关于技术债的几个误区
5 如何偿还技术债
6 总结
什么是技术债
Wiki 之父 Ward Cunningham 在1992年第一次提出技术债的概念。他说:“代码写好就提交,意味着欠债的开始。稍微欠点儿技术债的确可以加快开发速度,但是前提是事后及时重写代码。如果只借不还,后果很危险。在不准确的代码上所花的每一分钟,都算是技术债的应付利息。不稳固,脆弱的代码实现所引发的债务负担,会使整个工程组织陷入裹足不前的艰难境地”。技术债概念提出后,软件行业对以上定义做了一些修改,如今,技术债既指我们有意识选择非最佳捷径而所犯的错误,又指许多损害软件系统的不良实践,具体包含以下几种类型:
1)不合适(糟糕)的设计。设计存在巨大漏洞,或者业务发生重大改变,之前曾经有效的设计变得不再适用;
2)缺陷。已知的,但还没有时间解决的软件中的问题;
3)测试覆盖不充分。有些地方我们明明知道该做更多测试却没有做;
4)手工测试过多。实际该做自动化测试的时候我们还在做手工测试;
5)集成和版本管理不善。在做集成和版本管理的时候,采用的方式既费时又容易出错;
6)缺乏平台经验。例如,我们大型机应用需要用 COBOL 来写,但是身边精通 COBOL 的程序员却不多;
我们常常可以把技术债归纳总结为以下三类:
低级技术债:又被称为草率的技术债或无心的技术债。只要能够提供适当的培训、更好地理解技术实践的运用、做出合理的业务决策,这些技术债是可以消除的。
不可避免的技术债:这种技术债通常无法预测,也无法预防。例如,我们使用了第三方的一个组件。该组件的接口随着时间的推移不断发展,变得越来越臃肿庞大,从而欠下了技术债,该技术债是我们无法预见第三方组件开发人员将来怎样进一步发展组件而带来的。
策略性技术债:这种技术债可以作为一种工具,帮助组织从经济角度更好地量化和权衡重要的、实效性强的决策。例如:某团队特意做了一个策略性决策,打算把实效性强,但是带有技术债的产品推向市场,等有收益之后再用自筹资金进行后续开发。
技术债会带来哪些严重后果
俗话说:欠债还钱,天经地义。同样,欠下了技术债,也是需要及时去偿还的,如果日积月累,将会造成严重后果。具体后果有以下几种:
1)爆发点不可预期。技术债往往以不可预测的非线性方式增长。在原有债务基础上增加任何一点点技术债,都会产生严重危害,远远超过新技术债自身隐含的危害。当技术债达到“临界量”的时候,产品也就达到爆发点,变得不可管理甚至崩溃。这种非线性特征是一个不容忽视的业务风险。因为我们不知道最后一根稻草何时会压垮骆驼,然而一旦发生,后果不堪设想。
2)交付时间延长。偿还技术债意味着我们需要向未来借用工作时间,从而会影响我们开发的进度和节奏;
3)缺陷数量增加。技术债务严重的产品会变得越来越复杂,因而也让开发人员更难把事情做对。
4)开发和支持成本上升。技术债一增加,开发和支持成本会开始增加。过去一直简单又便宜,现在却变得复杂而且昂贵,如下图所示。
5)产品萎缩。如果因为技术债的存在,对老产品停止增加新特性或修复缺陷使其失去活力,它对当前或潜在客户就会变得越来越没有吸引力。
6)可预测性降低。如果产品确实已经债台高筑,基本上不太可能进行任何形式的预测啦。
7)表现越来越差。随着技术债越积越多,人们开始预计工作表现逐渐越变越差,进而降低他们对结果的期望;
8)挫折感四处弥漫。高技术债所导致的最不幸的后果是,价值链中的所有人都因此而备受挫折。所有小而烦人的捷径积累在一起,使产品开发工作痛苦不堪。
9)客户满意度降低。随着挫败感增强,客户的满意度也会下降。
实际开发场景中的技术债以及避坑指南
在实际开发过程中,很多人都常常将技术债与糟糕的代码等同看待,认为只要我们编写出了足够好的代码,就不会产生技术债——这是一种误解!其实,在我们的日常工作中,技术债产生的原因五花八门,无处不在,让我们防不胜防。现总结出来,供大家参考,以免掉坑。由于本人能力和经历有限,总结难免周全,也欢迎大家评论区查缺补漏。
1)UI 设计阶段考虑不周全所留下的技术债。
经常遇到的场景是,我们在做UI样式还原的时候,为了开始开发比较方便,很多背景色,或者文字颜色都是写死的具体的颜色值。但是,等系统实际要上线的时候,产品突然告诉我们系统需要同时支持普通样式和暗黑模式的样式。写死的固定颜色值,如何同时支持普通模式和暗黑模式?傻眼了吧!类似的场景还包括,在进行客户端非 Native 应用开发的时候,比如 App 中嵌套 H5 的应用,或者类似于 Hippy 这样的离线包应用,刚开始是一个初始版本的样式。可是,随着时间的推移,需要进行皮肤样式的升级,更换另外一套皮肤。如果我们一开始,在应用中把所有的颜色样式都写死,这个时候麻烦又来了,因为我们在这类型的应用中,需要同时支持2种皮肤样式的:旧版本的 App 里面的应用需要仍然支持旧版皮肤,新版本的 App 应用才需要支持新版本的皮肤。简简单单一个皮肤样式的升级,由于一开始我们考虑不周而留下来的技术债,会使我们的样式开发大部分都得重新来过,损失不可谓不大。
那这种情况下,我们该如何避坑呢?一开始,我们在规划 UI 样式开发的时候,就需要引入变量而非写死固定值,如下图所示。我们在一开始就可以规划好,如需要支持2种样式,需要把不同的样式放到不同的文件中,或者把颜色值都放到对应的变量中,在 UI 样式还原的时候,尽量不要写死颜色值,可以用这种变量的形式。用变量还有一个好处,就是能够保证全局样式的统一,就是所有的一级标题颜色都用 C1,所有的二级标题颜色都用 C2……依次类推。后续当我们需要全局更换一级标题颜色的时候,我们只需要修改变量的颜色值即可,不用整个系统查找颜色值来替换。
2)使用框架引起的技术债。
我们在选择某种技术框架的时候,由于对框架的本身调研不够,或者对系统的长远发展思虑不周,导致选用某个技术框架,系统上线迭代一段时间后,突然发现这种框架由于某种技术限制,不能支持系统进一步的开发迭代,需要更换系统框架。这种情况下引起的技术债是非常严重的,最坏情况下可能需要重构整个系统。重构期间,既要重构新系统,又要支持旧系统功能的迭代,苦不堪言。我们曾经就遇到过这样的情况,旧框架完全没法继续下去了,最后只能重新选择新框架,用新框架重新全部重构系统。那么,我们要如何尽量避免框架带来的坑呢?
首先,选择一个框架,我们需要考量框架的生命力,它是不是一个长期有技术团队支持的框架,有没有完善的文档和活跃的开发者社区。如果一个框架的生命力很短,没有技术团队维护,这样的框架后续一定会有坑。
其次,从产品短期发展和5年后长期发展看,该框架是否满足产品需求。很多时候,我们选择一个框架的时候,往往只看眼前。但是,当一个产品功能模块很少,或者用户基数很小的时候,很多问题不容易爆发出来。比如框架的高并发处理能力,框架的性能问题,框架的内存消耗情况,是否存在 OOM 的问题等等。所以我们应该从5年,甚至10年这种长远的眼光来选择一个框架,应该三思而后行,因为一旦后续要更换框架,将会是要付出血的代价。
最后,选择技术框架需要考量开发上手的成本。看看该框架所用的技术栈跟本团队的技术栈是否接近或者一致;看看该框架是否有完善的技术文档可供参考,遇到了棘手的问题,是否有技术支持可以帮忙,这些也很关键。
3)产品需求变更引发的技术债。
在敏捷开发模型下,产品负责人为了快速验证产品是否能够被用户所接受,会快速的验证多个功能,频繁的变更需求,从而会引发以下两种类型的技术债:
第一种类型:某些功能之间是互斥的,导致选用的技术框架也是不同的。比如针对用户反馈模块,在初期版本,产品负责人想要实时聊天类型的反馈功能,主打一个实时性,这个时候,开发团队需要选用一个实时通信的技术架构来支持,应用程序的部署也是不同的;可是等该功能上线后,发现用户的使用率并不高,而且需要特别大的运营人力来支持,很不合适;于是,在下一个迭代周期,废弃了实时聊天的反馈功能,修改为列表展示用户的反馈问题,线下来回复的形式。那么原来的技术架构都需要作废,都需要重新设计,从而带来了额外的技术债。针对这种情况,我们应该跟产品负责人多沟通,多问问产品的长远规划,从长远规划来进行技术选型,避免过度的浪费而引入技术债;
第二种类型:产品负责人在开发过程中,发现了某个紧急情况,需要修改需求文档。该需求文档是经过需求评审的,并且估算了耗时和上线日期。如果在开发过程中修改需求文档,就会增加开发人员的工作量,在开发日期不变的压力下,开发人员会走偏激路线,采用错误的开发模式来赶进度,从而引发大量的技术债。针对这种情况,我们应该杜绝在开发过程中随意修复需求文档,一定要学会说“不”,把新的调整作为一个新的高优 PBI 参与下期的迭代开发,而不是在本期开发过程中来实现。
4)使用组件引起的技术债。
该类技术债主要分为两大类:
其一,是偷懒使用组件而引起的技术债。通常我们只需要组件中的一小部分,或者某一个功能函数,但是却往往引入了整个组件库,这样会带来很多的潜在风险:一方面会增加我们应用打包的体积,或者 js 文件的大小,影响系统的速度;另外一方面风险更隐蔽,由于组件的升级迭代,某个没有被我们用到的部分由于技术升级,与本系统的技术栈不兼容了,导致该组件安装不上,增加了组件引入的维护成本。举个例子,特别常见的是:import * from lodash;但是,lodash 里面包含的函数功能非常多,并不是每一个都是我们需要的,应该需要用到哪一个具体的函数,就引入哪个函数,比如:import get from 'lodash/get'; import filter from 'lodash/filter';
其二,是组件本身的更新迭代带来的技术债。组件本身会经常进行升级迭代,在升级迭代中可能会引入新的功能,需要被迫升级某些底层的依赖库,比如需要升级 NodeJS 的版本。但是,为了这个组件去升级 NodeJS 版本,却给整个系统带来了其它的问题,从而需要修复这些问题,这就是间接引入的技术债。所以,组件的升级,需要提前预告,提前沟通,杜绝“先斩后奏”!
5)编写代码过程中所引入的技术债。
这类型的技术债占比非常高。主要有以下场景:
其一,是技术编码不规范所导致的技术债。例如对象的引用,没有判断是否为空,就直接去用它里面的属性,在正常情况下可能没有问题,但是在某些特殊情况下,该对象一旦为空,则整个程序会报错引发功能异常。例如:this.data.user.salary,data 和 user 对象都可能为空,引用前,做好是否为空的判断可以避免后续的异常;还有,就是文件夹的命名规范,文件的命名规范,函数的命名规范等,如果不认真对待,都会引入技术债。针对代码规范引入的技术债,大家可以去好好学习各类语言的技术规范,这里不一一列举;
其二,是我们偷懒引入的技术债。我们在编码过程中,经常爱偷懒写死一些常量值,特别是中文字符,比如:alert('密码输入错误'); 一般情况下,这么做没有什么问题。但是,如果系统需要支持国际化,这下你可惨啦,修改的代价将会很大。所以,常量建议要放到对应的常量文件里,通过枚举类型来引用;
其三,代码中太多的 // todo,这些都是我们的技术债。我们在开发过程中,由于跟上下游同事有依赖,需要的东西暂时不能提供,于是,就在代码中写了// todo 作为提示。但是,时间一长,我们早就忘记了这样的 // todo, 从而造成了潜在的系统风险,增加了我们的技术债;所以,每一个// todo需要作为一个需求单记录下来,放入需求列表中,时时刻刻提醒我们,以免遗忘;
最后,是由于经验不足,在不经意间引入的技术债。例如:
if (getCookie('uid')) {
// todo some login logic
}
在正常情况下,用户登陆后,都会在 cookie 里面种下 uid,退出,会把 cookie 里面的 uid 删除,看起来一切都没有问题。但是,如果系统接入了企业微信登陆,企业微信登陆并不会在 cookie 里面种下 uid,从而导致系统的登陆异常。如果系统各处的鉴权判断都是引用上面的方式,那么系统的改造工作量可不小!针对这种不稳定的因果关系可能导致程序变得难以修改甚至引入 bug,我们在编码时,需要养成良好的编码习惯,所有的技术细节需要进行函数的封装,例如:我们可以把 getCookie('uid') 这种技术细节,封装到一个 isLogin 的函数中,通过引用 isLogin 函数来判断用户的登录与否。后续一旦要修改登录逻辑,也只需要修改 isLogin 这一个函数,不用维护整个系统。
if (isLogin()) { // todo some login logic }
function isLogin() { return getCookie('uid') || getCookie('uin')}
针对这类型的技术债,我们还需要做好代码的 code review,找更高级别的同事帮忙 review 代码,尽早找出代码中的异常,将损失减小到最小。
6) 由于误用数据从而引发的技术债。
这种场景常常发生在 APP 系统开发中,一个系统数据的输出,直接是另外一个系统数据的输入,没有做任何版本的隔离限制。例如,我们在进行“明星意图”模块的迭代开发,开始用播放量作为二级标题;后来由于某种管制,不允许外露播放量,于是去掉了播放量字段,替换为来源字段。如果不做任何版本的隔离限制,直接发出去,旧版本的用户在前端还是用播放量字段作为二级标题,但是现在该字段已经在后端删除了,将会引发程序的功能异常。所以,在进行 APP 功能模块迭代的时候,尽量做好版本的隔离限制,避免引入技术债。
7)由于数据库的设计不合理从而引发的技术债。
在应用开发早期,由于数据库的设计或者规划不合理,从而引发的技术债,非常常见。场景有:
其一,某些字段的类型定义错误了,比如把数字类型定义为了字符串类型,或者没有考虑到系统的发展,少定义了字段,需要进行更多字段的添加,从而造成了技术债;
其二,分库分表设计的不合理,从而造成技术债。分库分表对与数据库的设计异常关键,设计不好,会造成很多问题,比如,分页,排序,跨节点联合查询的问题;事务的一致性问题;全局唯一关键字的问题;历史数据的迁移问题等等。所以,数据库的分库分表设计最好采用比较成熟的架构模式来进行,减小不必要的技术债;
其三,由于 SQL 语句引发的技术债。常见的在 SQL 语句中写死很多字段值,查询值等,或者关联更新和删除等,都会引发系统后续的潜在问题;
8)由第三方系统引入的技术债。
我们在应用开发过程中,无可避免会使用很多第三方的系统,比如 CI/CD 系统,比如监控系统,比如日志系统等等。正常情况下,我们开发流畅,相谈甚欢。但是,突然有一天,某个 CI/CD 系统突然告诉我们说某某日,我们的系统由于降本增效,人力不足,不能再维护了,需要下线,那么系统必须要增加工作量迁移到别的 CI/CD 系统中去,将会给系统增加技术债。
关于技术债的几个误区
在进入技术债务的偿还阶段前,我们有必要理清一下关于技术债的几个误区。
误区一:只要代码足够完美,就可以避免技术债。很多人都认为技术债是写代码的人一手造成的。只要代码写的足够完美,考虑的足够全面,就可以避免技术债,这个是错误的。上篇我们也分析了技术债产生的众多场景,有很多不是代码人员可以掌控的,有的可能是产品提的需求文档的不完善,也有可能是某些第三方系统所引起的,让人防不胜防。
误区二:减少测试可以提高速率。很多人认为测试属于额外开销,只要减少测试,就可以提高速率,这也是错误的。实际是减少测试既会增加债务又会减缓速率。因为问题潜伏很深,越晚发现,修复所花时间就越长,所产生的技术债就越大。例如:我们之前在进行一个列表功能开发的时候,用了一个列表组件,技术人员为了快速上线,只做了功能方面的测试,忽略了性能测试,等系统上线后,一个月内都没有发现问题。一个月后,广告的开发团队在列表中接着增加了广告位和三级列表筛选。广告位很快就卖出,广告功能着急上线,突然做性能测试的时候发现之前用的列表组件有性能问题,三级筛选功能的增加,导致列表记录数大增,会导致大量的OOM的问题,列表必须重构,否则系统无法上线。后来实在没有办法,只能跟客户协商,阉割三级筛选功能的同时,快速替换列表组件,列表页面和广告功能都需要重新开发,造成了非常不好的影响。
误区三:所有的技术债都必须尽快偿还。其实并不是所有的技术债都应当偿还。有些情况下的技术债是可以不用偿还的,现总结为以下三种:
1)行将就木的产品,技术债可以不用偿还。如果产品已经累计大量技术债且已经临近生命周期的终点,再投入大量精力偿还技术债就是财务不尽责了。如果产品的价值不高,就不要偿还技术债了,可以让产品退市,把资源投入性价比更高的产品当中去;
2)一次性原型,不用偿还技术债。有时我们为了获得知识或者验证某个结论而创建的一次性原型,它不是为了市场而设计的,所以可能会欠下一些技术债。由于它们是一次性的,所以也没有必要偿还其中的技术债;
3)短命的产品,由于经济因素可能支持不偿还技术债。组织通常不会开发预期只有三个月生命力的产品,我们更愿意开发能够长销的产品。所以,那些短命的产品,哪怕是多花一个子儿去清理系统,都将会是罪过。
如何偿还技术债
如何偿还技术债?首先就是要管理好我们的技术债。
技术债和财务债一样,必须加以管理。没有哪个产品可以做到无债一身轻,我们应该尽量少欠技术债,使其不至于明显影响后续产品的开发。技术债的管理要求综合考量技术和业务因素,因此离不开技术人员和业务人员的参与。管理技术债有三个主要活动:
1)管理应计技术债。在达到临界量之前,我们能够承担的技术债就那么多,在某个时刻,我们必须叫停,否则后果很严重。如何叫停?首先,我们需要停止向产品增加低级债务,再也不粗心大意和添乱了。所以,我们需要使用良好的技术实践,理解并使用这些实践;同时,还需要多学学其他团队所走过的坑,有效避坑。其次,对于积累下来的技术债,在适当的时机进行代码重构是一个非常重要的减轻债务的工具。重构用于改变既有代码主体的一种规范技术,在不改变软件外在行为的前提下调整其内部结构,即把软件内部清理一遍。我们力图通过重构改善可维护性和扩展性,同时降低复杂度,从而让我们的手头开发工作更容易。
2)使用“强完成”。在敏捷开发中,很多团队对PBI的验收,采用“完成”和“完成”-“完成”的概念。针对这两个概念举个例子,周末老师给我儿子布置了家庭作业。周末睡觉前,我问儿子,明天就要上学了,老师布置的作业你都做完了吗?儿子回答说,我做完了,这是儿子口中的“完成”。第二天,我去学校接儿子放学,在校门口碰到了老师,我寒暄几句后,顺口问老师,儿子的家庭作业做得怎么样,老师说:“他家庭作业没有做完呀,作文让写500字,他才写了300字。。。”,这是老师口中的“完成”。应用到工作中,就是,我们在验收一个功能需求的时候,不能开发自己认为完成就结束了,还需要获得客户所认为的“完成”。没有满足客户需要的工作不能叫完成。使用强完成,就是要强化需求的验收标准,制定一个完成定义检查表,可以要求团队在宣布工作潜在可发布之前,根据完成定义检查表完成各项工作检查,如下图所示。完成定义检查表中所包含的技术细节越多越好,积累的技术债的可能性就越少。
3)让技术债可见。让技术债可见,需要在整个团队中达成共识。一般有这样两个方面:
其一,让技术债在业务层面可见。很多产品开发团队中,技术开发人员还能看出来产品技术债处于什么状态,但是产品和运营人员却是一问三不知,不知道技术债的数量和类型。要偿还技术债,首先需要让我们的产品和业务人员能够清晰的看到产品的技术债。我们可以采用量化技术来量化技术债,使其在业务层面可见。量化技术债的技术很多,可以根据各个团队不同情况来进行。举个例子,有的团队是用金钱来量化技术债,形成技术负债表,从而使其在业务层面可见。如我们在进行敏捷项目开发的时候,每个固定迭代开发成本为10万元,开发需求的速率是20个需求点,那么每个需求点就是5000元。然后我们可以对所有的技术债进行一个需求点的估算,进而转化为技术债所对应的金额,如下图所示。
其二,让技术债在技术层面可见。这里有三种方法:
1)方法一,我们可以把技术债当作缺陷录入缺陷管理系统,从而把技术债跟系统的功能缺陷放在一起,让技术开发们在解决功能缺陷的时候,时时刻刻都看到技术债的缺陷。
2)方法二,为技术债创建 PBI,每一个技术债都是一个 PBI,将其放入产品需求列表中,定义它们的优先级,参与每个迭代的需求排期。
3)方法三,创建一个特殊的技术债列表,在其中清晰的列出每一项技术债。任何时候,只要发现新的技术债,开发团队成员就可以创建一个新的技术债条目加入技术债列表。技术债列表以何种形式存在以团队而定。有的采用技术债白板贴在墙上,有的采用便利贴或卡片记录在白墙上,在进行产品需求迭代会的时候,团队就可以实时看到这些技术债,从而考虑该迭代中是否要安排偿还某一些技术债。
如何偿还技术债?常常有以下一些方式和方法。
1)并非所有的技术债都需要偿还。在技术债的几个误区中,我们已经详细说明,行将就木的产品,一次性原型,短命的产品,这些产品产生的技术债,我们不需要偿还;
2)应用童子军规则:有债就还。有一条童子军规则:“离开营地时,要让它比你进去时更干净。如果发现营地脏乱,就应该清理,不管罪魁祸首是谁”。按照这个规则,在每次产品改动时,我们都要尝试让产品的设计和实现方案更好,而不是更差。如果开发团队成员在做某产品的时候,发现了一个技术债问题,就会清理问题。这么做不只是因为对他自己有好处,更是对整个开发团队都有好处。为了解决这个问题,团队可以在每次迭代开发的时候,预留出来一定比例的时间来用于偿还技术债。一种方法是提高某些单个 PBI 的大小估算,从而为额外技术债预留时间;另一种方式是在团队迭代计划会上,将技术债作为 PBI 参加评审,单独为偿还技术债预留一定的时间。
3)分期偿还技术债。在某些产品中,技术债的水平非常高,团队要一次性付清所有的技术债难度很大,就可以采取分期,分步骤来偿还技术债。我们可以把技术债拆分为更小,更容易修复的一个个的小 PBI,分布在不同的迭代开发中,像偿还房贷一样,每个月都去偿还一部分。每次迭代开发需要承担多少目标技术债,由整个开发团队在迭代计划会议上协商解决。
4)先偿还高息技术债。例如,有这样一种类型的技术债,一个经常改动的模块,其他很多代码都依赖它,由于它已经变得越来越难以修改,严重影响到开发效率,所以我们迫切需要对它进行重构。我们一直都在为这个债务付息,修改越多,在其上所欠的债务就越多,很快就会出现债务累累的情况。针对这类型的债务,我们要重点关注,优先偿还。
5)一边做产品需求,一边偿还技术债。把偿还技术债,溶于产品的需求开发中,避免专门用单独的迭代开发周期来削减债务,这样做的好处有:其一,将技术债的削减工作与客户有价值的需求相结合,有利于产品负责人合理安排需求的优先级;其二,让整个开发团队清晰的认识到,减少技术债是大家共同的责任,不得拖延或授权给其他人或团队来完成;其三,它能够让我们避免浪费时间去清偿实际不必要的技术债。
总结
将技术债划分为低级技术债,不可避免的技术债和策略性的技术债,可以让我们更清晰的了解技术债的特点,每种技术债所产生的原因和具体场景,从而让我们能够有针对性的去偿还产品技术债。偿还技术债,一定要首先管理好我们的技术债,让技术债可见,然后,再来制定相应的偿还计划。由于技术水平有限,分析总结难免有疏漏之处,还希望大家能够在评论区留言讨论。
-End-
原创作者|熊才刚
你有什么处理技术债的经历或技巧吗?欢迎分享。我们将选取1则最有价值的评论,送出腾讯标准型Q哥1个(见下图)。11月6日中午12点开奖。
欢迎加入腾讯云开发者社群,社群专享券、大咖交流圈、第一手活动通知、限量鹅厂周边等你来~
(长按图片立即扫码)