高三的一个小故事
在高中时代,我读书不算优异,但也算勤勉。记得有一次高三我突然挺进了班前几名,在班会上班主任让我上台分享下自己学习进步的成功经验。我当时说,“其实也没啥,方法就是,把全部的练习册、试卷、课外教材、包括课本上的作业题练习题全部都做完,就可以了。”。台下立即有同学问:“那你是怎么有那么多时间把全部题目都做完的?!根本做不到嘛……”。是啊,怎么会有时间呢?怎么能做得到呢?但就是有人做到了。一般这类人我们称他为:学霸,或牛人。
反思现在开发的难
直到如今,从毕业到现在,我已在软件开发领域从事浸淫了约十多年,负责过开源项目、外包项目、上市企业核心高并发系统研发、高速发展中公司的系统重构、以及创业公司的敏捷开发和快速迭代和救火。在我带领过以及共事过的软件开发工程师中,很多人,大部分的人,都会觉得开发很难,说的很多的可能也是:“没时间,根本没时间”。要么系统很烂改不动,要么这个需求太复杂要很长周期,要么能做也是觉得在痛苦中开发。
在我看来,软件开发并不难,而且我一直都乐在其中,因为通过软件开发,我们能创造出新的产品、软件、系统来服务和帮助更多人的工作或生活,这是一件很有价值和成就感的事情。
如果你也认同这个观点或价值观,刚好现在又觉得自己或团队正在处于“开发很难”的窘境,又特别想提升研发的效率,那么我们不妨继续进一步,一起来总结下成功的经验、模式、方法和工具。
最近又一次成功的开发经验
最近我们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接口和几百份接口文档、几百个上千个测试用例、还有产品需求文档、设计稿、数据库数据等。
而在用户的眼里,在需求方的眼里,它就是一个界面、一份数据、甚至一个按钮而已。
系统越智能、代码越复杂。人想偷懒、高效,就需要计算机做多一点,而作为中间的媒介就需要我们程序员多用心、多写几行代码、多做好兼容和提升系统的友好度和代码的容错性。
程序员,从小工到专家,是职场晋升的表现。系统,从混沌到稳态到智能,是需求方的感知。产品,从创新到好用到解决痛点,是用户对它的价值感知。
最后,分享一个小故事。
有这样一个故事:三个工人在砌一堵墙,有一个人过来问:“你们在干什么?”第一个人没好气地说:“没看见,在砌墙。”第二个人笑了笑说:“我们在盖高楼。"第三个人边干边唱着歌,说:“我们在建设一个新城市."十年后,第一个人仍然在砌墙,第二个人成了工程师,第三个人成了前两人的老板。
愿意或有心把代码建设成一个新产品的程序员/开发工程师,我相信,他未来的路和未来的发展空间,都充满更多可能和展望。
加油,少年!