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

简介: 这篇文章回忆了作者在高三时期通过努力进入班级前列的故事,并引申到软件开发领域。作者指出,开发工作往往被认为困难重重,但实际上,通过良好的方法、设计和工具,可以提高开发效率和享受编程带来的成就感。文章以最近完成的一个复杂核心需求为例,详细介绍了如何分析、设计和实现这个需求,包括采用领域驱动设计(DDD)理念,数据库字段变更,代码实现,自动化单元测试,重构和代码维护的重要性。最后,作者推荐了几本关于软件开发的经典书籍,并鼓励开发者不断提升自己,以更好地应对挑战。

高三的一个小故事

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


反思现在开发的难

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

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

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


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

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


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

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

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

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

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


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

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


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

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


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


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

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

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


再看数据库设计

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

-- 任务状态
ALTER TABLE `pp_tasks` CHANGE COLUMN `task_status` 
`task_status` smallint(4) NOT NULL DEFAULT 600 
COMMENT '任务状态,600 TODO,1500 DOING,2000 DONE';

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

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,不然代码容易僵化。

./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行,

$ pwd
~/dogstar/yesinew_www
$ grep task_status ./src/* -R | wc -l
306

Vue前端调整 共156行,

~/projects/codeup/xiaozhi on  dogstar! ⌚ 22:06:15
$ grep task_status ./src/* -R | wc -l
     156

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

$ grep task_status ./src/* -Rn > task_status_todo.txt

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


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

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

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

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

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

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

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


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

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


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

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

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

相关核心前端代码片段,

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接口和几百份接口文档、几百个上千个测试用例、还有产品需求文档、设计稿、数据库数据等。


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

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

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


最后,分享一个小故事。

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

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

加油,少年!

相关文章
|
7月前
|
Linux 测试技术 C++
【代码实践】编码精粹:打造高效与可维护的代码艺术
【代码实践】编码精粹:打造高效与可维护的代码艺术
155 0
|
3月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
220 3
|
5月前
软件复用问题之在哪些情况下,复制可能是一个更好的选择
软件复用问题之在哪些情况下,复制可能是一个更好的选择
|
设计模式 程序员 开发者
重构·改善既有代码的设计.01之入门基础
近期在看Martin Fowler著作的《重构.改善既有代码的设计》这本书,这是一本经典著作。书本封面誉为软件开发的不朽经典。书中从一个简单的案例揭示了重构的过程以及最佳实践。同时给出了重构原则,何时重构,以及重构的手法。用来改善既有代码的设计,提升软件的可维护性。
640 1
重构·改善既有代码的设计.01之入门基础
|
算法
第七章 多用模板专注设计(上)
第七章 多用模板专注设计
104 0
第七章 多用模板专注设计(上)
|
移动开发 JavaScript 前端开发
前端性能优化实践之代码层面更改(3)
前端性能优化实践之代码层面更改(3)
174 0
重构改善既有代码的设计---笔记
重构改善既有代码的设计---笔记
225 0
|
数据库
高质量代码优化!谈谈重构项目中if-else代码的几点建议
本篇文章探讨了代码的重构以及优化,主要针对代码中大量的条件判断if-else语句问题提出了具体的优化建议。介绍了优化if-else语句的几种有效的方法,包括switch,接口interface以及数据库实现对条件语句进行的优化。
176 0
高质量代码优化!谈谈重构项目中if-else代码的几点建议
|
设计模式 Java 程序员
《重构:改善既有代码的设计》-学习笔记一(+实战解析)
《重构:改善既有代码的设计》-学习笔记一(+实战解析)
212 0
《重构:改善既有代码的设计》-学习笔记一(+实战解析)
下一篇
DataWorks