前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >修改代码的艺术——如何高效开发、维护和重构复杂的现有系统

修改代码的艺术——如何高效开发、维护和重构复杂的现有系统

原创
作者头像
dogstar
发布2024-05-30 11:35:29
750
发布2024-05-30 11:35:29
举报

高三的一个小故事

在高中时代,我读书不算优异,但也算勤勉。记得有一次高三我突然挺进了班前几名,在班会上班主任让我上台分享下自己学习进步的成功经验。我当时说,“其实也没啥,方法就是,把全部的练习册、试卷、课外教材、包括课本上的作业题练习题全部都做完,就可以了。”。台下立即有同学问:“那你是怎么有那么多时间把全部题目都做完的?!根本做不到嘛……”。是啊,怎么会有时间呢?怎么能做得到呢?但就是有人做到了。一般这类人我们称他为:学霸,或牛人。

反思现在开发的难

直到如今,从毕业到现在,我已在软件开发领域从事浸淫了约十多年,负责过开源项目、外包项目、上市企业核心高并发系统研发、高速发展中公司的系统重构、以及创业公司的敏捷开发和快速迭代和救火。在我带领过以及共事过的软件开发工程师中,很多人,大部分的人,都会觉得开发很难,说的很多的可能也是:“没时间,根本没时间”。要么系统很烂改不动,要么这个需求太复杂要很长周期,要么能做也是觉得在痛苦中开发。

在我看来,软件开发并不难,而且我一直都乐在其中,因为通过软件开发,我们能创造出新的产品、软件、系统来服务和帮助更多人的工作或生活,这是一件很有价值和成就感的事情。

如果你也认同这个观点或价值观,刚好现在又觉得自己或团队正在处于“开发很难”的窘境,又特别想提升研发的效率,那么我们不妨继续进一步,一起来总结下成功的经验、模式、方法和工具。

最近又一次成功的开发经验

最近我们YesDev项目管理平台收到了一个客户的需求,希望能把YesDev原来任务只有固定的3个任务状态(待办、进行中、已完成),动态扩充到任意多个任务状态(例如:期望流程:待开发 -> 开发中 -> 开发完成 -> 待测试 -> 测试通过 -> 待上线 -> 已上线 -> 已关闭)。

这个需求,就一句话,看似简单,实质上开发起来是有一定难度的。为什么呢?原因在于:

1、“牵一发而动全身”。对于现在复杂的系统,但凡动到系统主流程以及核心交互单元的,势必都是全局性的改动,而此处的任务协作也正好是YesDev项目管理最小颗粒度、最核心的管理协作单元,和业务有着千丝万缕的关系。

2、“数据结构决定上层算法”。从数据库的角度,原来定义好的字段语义(任务字段 0待办、1进行中、2已完成),需要重新从数据结构底层设计,既要合理又不能过度设计,还要能向前兼容旧数据旧逻辑、也要能向后兼容未来的可扩展性,还别忘了要处理历史旧数据!

3、“一个都不能少”。为了这一句话需求(约几十个汉字的需求描述),我们改写和新增约上万行代码。你以为难度在于这上万行的代码改动吗?非也,难在改动的这些代码一行也不能、一行也不能多、一行也不能错,最后改完好,这些全部的代码都要能符合编程语法、要能通过机器计算机编译、还要能充分贴合最终业务逻辑。代码错一行一个Bug,或者说源代码错一个字母就是一个故障。

4、“多个产品线要一起调整”。公司越大,部门越多,系统越杂,特别在某个需求需要多个产品线一起调整时,尤其吃力耗时和大成本。更别说还要同步更新接口文档、产品使用手册、FAQ、销售物料、内部培训等。本次的任务状态调整需要同步调整PC端、企业管理后台、H5移动端等,可能还会涉及计划任务、MQ、底层服务这些“看不见”的程序。

简单来说,对于现在已经有用户客户在使用的业务系统,如果需要修改核心、底层、主流程的逻辑,是一件 高风险、高成本、复杂而又困难的事。雪上加霜的是:原来的系统就已经很脆弱、开发人员又严重不足、需求方给到的开发时间又是急了又急、挤了又挤、系统前期的很多情况和内部都不透明、更致命的是需求就一句话!这些都是让开发人员抓狂和接近崩溃边缘的原因。

但这一次,我们又一次即将完美交付此复杂核心的需求,并又成功经历了一次核心主流程业务需求开发迭代的经验,同时收获了又一次有成就感的产品研发、设计与交付。

资深开发告诉你:做需求,先分析、再设计、后研发

拿到需求,新手开发人员先别着急立即开始写代码开发,要先把技术开发思路捊清楚,把整个技术开发方案先描画出来,提前搞清楚难点在哪?卡点在哪?耗时点在哪?不可控点在哪?注意事项在哪?还有哪些需要补充的?

要养成做需求,边做边整理文档的好习惯。首先,我们可以在YesDev项目管理工具,创建一个主需求:“需求#634715 状态扩充预留:项目、需求、任务、问题、测试用例 等(第一站:任务)”。注意,做需求也要举一反三,虽然需求方需要的只是任务状态扩充,但我们在规划产品需求时可以把项目、需求、问题的状态也一并设计,但可以分批实施,先做第一站:任务状态扩充。谋定而动。随后,再根据主需求拆解子需求。

采用领域驱动设计DDD的理念

核心的设计,先看图,结合 领域驱动设计DDD的理念,描述关键的业务规划和领域语言。即先从概念视角出发,再到规约设计,最后到代码实现视图。

为了平衡任务协作本质的情况、企业客户研发团队目前的需求、以及软件系统开发的完美设计,YesDev的任务状态从原来只有3个离散状态的点分布,升级设计成了3个区间状态段的设计,其中应用了“前等后不等、开区间、闭区间”等通俗的数学用语,而且通过坐标轴方式也清楚描述了业务逻辑。

再看数据库设计

下一步是数据库的字段变更设计:

代码语言:javascript
复制
-- 任务状态
ALTER TABLE `pp_tasks` CHANGE COLUMN `task_status` 
`task_status` smallint(4) NOT NULL DEFAULT 600 
COMMENT '任务状态,600 TODO,1500 DOING,2000 DONE';

以及别忘了对旧数据、历史数据的处理,复杂的处理需要额外编写一次性处理脚本。

代码语言:javascript
复制
update  `pp_tasks` set task_status = 600 where task_status = 0;
update  `pp_tasks` set task_status = 1500 where task_status = 1;
update  `pp_tasks` set task_status = 2000 where task_status = 2;

展开设计就是,数据库设计及变更:

  • 统一使用smallint
  • 1000超步初始值(方便以后扩展更前置的状态)
  • 2000作为完成状态的分割值
  • 3000封顶。
  • 中间 100至3000再取逢百作为系统预设预留的状态值
  • 采用【/】屏蔽掉不需要的状态。
  • 共30个状态(先扩充24组)。

最后代码实现

最后,是相关代码片段的实现。如,后端PHP的任务业务领域类,在里面通过常量来描述任务状态,通过API接口及释意接口描述获取任务状态名称、任务是否延期等方法。请留意细节,通常情况下不建议使用静态static方法,除非真的是和实体状态无关的,是属于服务类的工具方法才建议允许用static,不然代码容易僵化。

代码语言:javascript
复制
./src/base/Domain/Tasks.php  
<?php
namespace Base\Domain;
class Tasks extends Base {
      // 任务状态,TODO,DOING,DONE
    const TASK_STATUS_100 = 100;
    const TASK_STATUS_200 = 200;
    const TASK_STATUS_300 = 300;
    const TASK_STATUS_400 = 400;
    const TASK_STATUS_500 = 500;
    const TASK_STATUS_TODO = 600; // 初始 待办

    const TASK_STATUS_1000 = 1000;
    const TASK_STATUS_1100 = 1100;
    const TASK_STATUS_1200 = 1200;
    const TASK_STATUS_1300 = 1300;
    const TASK_STATUS_1400 = 1400;
    const TASK_STATUS_DOING = 1500; // 进行中
    const TASK_STATUS_1600 = 1600;
    const TASK_STATUS_1700 = 1700;
    const TASK_STATUS_1800 = 1800;
    const TASK_STATUS_1900 = 1900;

    const TASK_STATUS_DONE = 2000; // 已完成
    const TASK_STATUS_2100 = 2100;
    const TASK_STATUS_2200 = 2200;
    const TASK_STATUS_2300 = 2300;
    const TASK_STATUS_2400 = 2400;

    public static function getTaskStatusMap($isHtml = false) {
    }

    public static function getTaskStatusName($status, $isHtml = false) {
    }

    // 释意接口,是否延期任务
    public function isDelayTask($taskStatus, $taskFinishTime) {
    }
}

以上,只是开发的冰山一角,但思路大体类似。

解决之道:高效开发、维护和重构复杂系统的经验分享

我时常和团队开会时说,也和不太懂技术的老板说,系统为什么开发了这么多年,现在做个新需求还这么难、这么吃力?原因是:本身做这个需求不难,但要把这个需求完美嫁接到现有的系统则很难。因为有很多历史包袱、技术债务、旧的问题要处理。

当然,办法总比困难多。把问题量化了,自然就会解决之道。

1、改一处,记录一处,验证一处

对于主要界面,从管理后台配置、到前台使用、到辅助功能和新页面,改一处就在需求文档上记录一次,把页面功能、网站链接和实现效果,在自我测试验证后进行记录。例如:

记录的好处,有利于查漏补缺,也方便后面的测试回归。最好是把git代码提交也和需求进行关联。YesDev支持各类Git代码仓库的自动关联。

2、学会代码全局搜索和分析,一行也不能放过

如果一个开发人员和你说,这个需求非常大、很难做,那么你可以要求让这位技术开发人员提供需要修改的代码范围。如果你自己是开发人员,如何才能知道有多少相关的代码需要修改呢?思路方法很简单也很有效。就是根据数据库字段名去全局搜索源代码。

例如这里的,PHP代码及API调整共有306行,

代码语言:javascript
复制
$ pwd
~/dogstar/yesinew_www
$ grep task_status ./src/* -R | wc -l
306

Vue前端调整 共156行,

代码语言:javascript
复制
~/projects/codeup/xiaozhi on  dogstar! ⌚ 22:06:15
$ grep task_status ./src/* -R | wc -l
     156

还可以把每个源文件的代码行号罗列出来,以便后续逐个调整修改。如:

代码语言:javascript
复制
$ grep task_status ./src/* -Rn > task_status_todo.txt

将能看到类似以下将要可能修改调整的代码位置。简单、直接、有效。

3、提前做好自动化单元测试,收益真不少

代码要改这么多?!怎么知道有没改出问题?!要一个个自己手动测试吗?要一个个API接口重新测试吗?!要每个页面、每个功能都手动人眼看一次操作一遍吗?!不行不行,是个人都做不到,而且每个人手上还有这么多需求、这么多事情、这么多工作要做。没时间没时间!

那么解决的办法是什么呢?那就是:自动化单元测试!有没问题,一键测试,就知道。

可能你还会问,那单元测试怎么来?谁来写?没有怎么办?

我的回答:现在写,你写。现在不补写单元测试,永远都不会有。

又问:现在也没时间怎么办?

我的回答:把本次要新增单元测试的时间也加上。“功在当代,利在未来”。单元测试也是开发工作的一部分,而且也很有价值。

4、不要害怕,该重构就重构

原来的代码,肯定会有这样那样的问题,例如:一个规则逻辑放在了多处、重复代码函数甚至类比比皆是、方法参数过长、一个类文件非常庞大甚至都有成千上万行代码、注释掉的代码或没有的代码都没删(也没人敢删)、到处写死的代码、更别说不规范的代码命名和风格了。

5、适当引入新的模式、方法和新设计(包括设计模式、混入、枚举、原型链接等)

应对新的需求场景,结合应有的设计,恰当引入新的模式、方法和新设计,例如:设计模式、minin混入、枚举、原型链接、特质等。

本次开发过程中,有趣的是,为了避免写数字魔数,在前端我封装 $enum 枚举值,达到和后端对齐的效果。参考了张瑞丰的博文《Vue项目常量的使用》,实现了以下的效果。

相关核心前端代码片段,

代码语言:javascript
复制
import store from './store'
// 引入全局枚举值(避免魔数)
import './enum'

// 在视图文件使用
<Tooltip :content="`请确保任务为【${getConstantItem('TASK_STATUS', $enum.TASK_STATUS_DONE, 'name')}】状态`" 
placement="right" theme="light">
  
// 在js中使用
this.taskCheckDisabled = task_status == this.$enum.TASK_STATUS_DONE ? false : true  

6、别忘了做好SQL变更记录及上线清单准备

除此之外,还要把有关的工作、变更、调整做好记录。先记录,再执行。不要遗漏、不要记事。因为错误的代价成本很大,不容犯错。

办法总比困难多,能力比努力更重要

有个对联是这么说的:

上联:说你行你就行不行也行;

下联:说你不行你就不行行也不行;

横批:不服不行。

有一次,我听到一位高管在给下属分配完工作后打气说:“再努努力力,加加油,你可以的!”。而还有一次,在一家企业中,我看到了这样的文化标识:“我们需要的是指点,不是指指点点”。

Anyway,任何工作、任何项目、任何系统开发都会有困难,毕竟方法总比困难多。在你觉得开发困难时,不妨反思一下是不是自己的能力还不够?为什么同样的时间,别的开发就能做出来而自己却不行?是功力不够,还是能力不够?有时,能力比努力更重要。你固然很努力,但能力达不到就会很痛苦。最好的状态是,你有能力驾驭本次开发,而且还提前拥有了下一阶段的知识储备,不断正向循环,勇往直前,向前一步、快人一步。

知识,千万别总等到要到用的时候,再来学(更别说不学也不补)。

来自《童年》的歌词:

“总是要等到睡觉前 才知道功课只做了一点点 总是要等到考试以后 才知道该念的书都没有念 ……”。

掌握方法,回归代码修改的艺术

如果作为专业的技术开发人员,想要进一步掌握专业的方法,洞察代码修改的艺术、掌握系统遗留旧系统维护的密码,那么继续推荐我前面也有介绍过的几本好书,和编程开发语言无关,和做什么项目无关,都是普适性的经典著作。排名也分先后,谁看谁受益。

领域驱动设计 软件核心复杂性

重构 改善既有代码的设计

修改代码的艺术 [美] 费瑟

如果前面的书太抽象、过于高阶,可以先看下基础夯实的入门书,例如:《Vue.js设计与实现(图灵出品)》。

从小工到专家,领悟软件开发的本质

开发,从来都不是一件简单的事情。

一个产品、一套系统,做下来,会有十万行以上的代码、几百个API接口和几百份接口文档、几百个上千个测试用例、还有产品需求文档、设计稿、数据库数据等。

而在用户的眼里,在需求方的眼里,它就是一个界面、一份数据、甚至一个按钮而已。

系统越智能、代码越复杂。人想偷懒、高效,就需要计算机做多一点,而作为中间的媒介就需要我们程序员多用心、多写几行代码、多做好兼容和提升系统的友好度和代码的容错性。

程序员,从小工到专家,是职场晋升的表现。系统,从混沌到稳态到智能,是需求方的感知。产品,从创新到好用到解决痛点,是用户对它的价值感知。

最后,分享一个小故事。

有这样一个故事:三个工人在砌一堵墙,有一个人过来问:“你们在干什么?”第一个人没好气地说:“没看见,在砌墙。”第二个人笑了笑说:“我们在盖高楼。"第三个人边干边唱着歌,说:“我们在建设一个新城市."十年后,第一个人仍然在砌墙,第二个人成了工程师,第三个人成了前两人的老板。

愿意或有心把代码建设成一个新产品的程序员/开发工程师,我相信,他未来的路和未来的发展空间,都充满更多可能和展望。

加油,少年!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 高三的一个小故事
  • 反思现在开发的难
  • 最近又一次成功的开发经验
  • 资深开发告诉你:做需求,先分析、再设计、后研发
  • 采用领域驱动设计DDD的理念
  • 再看数据库设计
  • 最后代码实现
  • 解决之道:高效开发、维护和重构复杂系统的经验分享
  • 办法总比困难多,能力比努力更重要
  • 掌握方法,回归代码修改的艺术
  • 从小工到专家,领悟软件开发的本质
相关产品与服务
项目管理
CODING 项目管理(CODING Project Management,CODING-PM)工具包含迭代管理、需求管理、任务管理、缺陷管理、文件/wiki 等功能,适用于研发团队进行项目管理或敏捷开发实践。结合敏捷研发理念,帮助您对产品进行迭代规划,让每个迭代中的需求、任务、缺陷无障碍沟通流转, 让项目开发过程风险可控,达到可持续性快速迭代。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档