程序员的自我救赎
作为程序员,我们经常身处这样的困境中
程序员的世界很单纯,写新的代码、或是修改旧的代码。对我们来说最直接的混乱
是什么?
- 创造新事物的时候,总是在茫茫文件中寻找
以前是怎么做的
- 修复缺陷的时候,总是在揣摩着代码内心深处的
想法
,为什么让她喝热水的时候,她会跟我生气 - 我们每天由多少时间是用来
读
而不是写
,多少次在阅读代码的时候,内心温习着我们知道的所有脏话 混乱
也许包含着超负荷的工作安排、变化多端的需求、经常死机的电脑等等突入其来的状况,但是对我们来说,最多的还是一坨肮脏的代码
:凌乱的文件结构、纠结到死的逻辑、晦涩难懂的表达等等。我们总是找不到,或者很难找到我们想要的
这些肮脏的代码又是来源于哪里?
- 所有东西都是由我们自己编写的,作为程序员,我们是
一切的起源
。也许是自己、同伴、或者是经常背锅的前任,终归是我们这群猴子
- 无论是无论是主观问题还是客观原因,迫于压力还是心存侥幸,我们总是在
制造混乱
为什么开发者会制造混乱?
抛开个人技能或者经验的问题的讨论,作为技术人员,终归是要不断学习和提升自己。
实际上,每个层级的人都在做着与自己能力匹配或略高于自身能力的工作,但是实际上,每个层级的开发者都经常在制造混乱。
每个人都有过类似的经历,在回顾自己的工作产出时,都会发现,自己其实可以做的更好
,我们经常将原因归结于压力太大,没有时间
,并且认为这是由于是需求太多
和历史缺陷
所造成的。
所以,我们常常把希望寄托在需求变少
上,让我们有时间清空缺陷
,然后在整顿混乱
,与其花时间讨论这个问题,我们不如去聊聊中国足球,至少我们还能选择看哪场比赛。
- 不要以为特朗普当选了美国总统,就真的一切皆有可能
- 现实点说,如果需求真的变少,最大的可能是:我们的收入比压力降的更快
与其被动的承受这个循环的无尽折磨,或者把希望寄托在这些不可能的事情上,不如从我们自身,或者说从我们所能控制的事情上寻找逃生通道。
实际情况中,面对交付压力和项目延期,通常使用的解决方式有两种:
- 给现有成员更大的压力
- 加入更多的成员
通过者两种方式,真的能打破这个循环么?
就如我们之前所说,在个体技能没有明显提升的情况下,增加压力只会进一步加剧混乱。虽然眼前的问题解决了,但是从长远来看得不偿失
。
而增加成员呢,想必大家都有切身的体会,如果为了短期利益而增加成员,在眼下会造成更多的混乱,除非有健全的培养体系
和长远的人力储备计划
,但实际情况恰恰相反。
在这样的怪圈里面,混乱
越积累越多,最终成为一锅黏糊糊的沥青,拖垮整个团队
也许我们尚且无力改变周围的环境,无法拒绝需求的涌入,无法指望NPC降临将我们从苦难中解决出来。但是与其被动的承受和喋喋不休的抱怨,不如在我们能力范围内想办法自我解救。
暂且不要将希望完全寄托在高深的技能
和先进的开发理念
上
- 依赖注入和控制反转帮助我们构建低耦合的业务系统
- 设计模式是良好的解决方案,帮助我们灵活的应对变化
- Angular的组件化帮助我们化整为零、各个击破
- React的单向数据流让我们能够灵活而且优雅的管理业务模型
- 自动化测试帮助我们探索未知的城堡,保护我们已有财产
- 微服务很具诱惑,让我们轻装上阵,并且专心致志
- 敏捷和DevOps为我们创造了”乌托邦”一样的世界,一切都是那么灵活和轻松
但是现实是,对于学习新技能、应用新技术、改善组织架构和工作环境,我们总是有一个非常“正当”的理由,我们没有时间
,而且源自于我们自身创造的混乱
也许留给我们的出路,只有用最低的成本提高代码的质量,最大程度的改善我们的生存环境
,我们自身才有余力去学习先进的技能,我们身处的团队才有余地去改善环境。
在我们日常工作中,什么是最不值得浪费的时间?
日常工作中,我们花费最多的时间是用来阅读代码
;同样的,这也是最不应该浪费的时间,尤其是命名可以轻易的理解意图,却因为编码质量差
,导致我们被误导、猜测、陷入到不必要的复杂度当中时。
如果从我们内部的因素来分析这些问题的来源呢?
也许源自于我们过度的自信
和不自信
1.自信
我们经常毫无理由的充满自信:
- 能够一次性解决问题,加上这句就万事大吉
- 需求不会发生变更,我不需要再来重新阅读和扩展
- 不会产生缺陷,我不需要再来重新阅读和理解这段东西
总是觉得自己写好的东西
不需要反复调试
、不会再重新阅读和修改
,总是觉得能够运行
就已经足够了。 可实际情况是,满足这三条场景的,只有没人使用的东西
,静静的躺在仓库的角落,直到被人删除。 代码总是被人反复阅读
,被自己或他人,其频率远远大于编写或者修改,并且它只是偶尔被计算机执行,更多的是面向人
。
2.不自信
我们总是在担心:
- 不能按时完成需求交付或者缺陷的清理
- 清理
混乱
会引入新的问题 - 随便进别人家打扫地面会被赶出来
我们常常急于编码、运行,并且开始下一个任务,无论是为了尽快交付需求还是清空缺陷,或者是为了腾出时间学习新的知识。对于
混乱
常常妥协
、推卸
或者视而不见
。
3.破窗效应
一幢有少许破窗的建筑为例,如果那些窗不被修理好,可能将会有破坏者破坏更多的窗户。 一面墙,如果出现一些涂鸦没有被清洗掉,很快的,墙上就布满了乱七八糟、不堪入目的东西。 一条人行道有些许纸屑,不久后就会有更多垃圾,最终人们会视若理所当然地将垃圾顺手丢弃在地上。 这个现象,就是犯罪心理学中的破窗效应!
因为历史问题,我们的系统中充斥这随意的代码和凌乱的设计,这往往导致我们对混乱视而不见并且轻易妥协,并最终进一步加剧混乱。
从哪里开始?
首先,不要急
、不要心存侥幸
,像乌鸦一样一颗一颗的丢石子
从实际经验得到的教训:
- 做的越快,死的越惨
- 凡是心存侥幸,总是会打脸
无欲速,无见小利。欲速则不达;见小利,则大事不成。 —— 《论语·子路》
其次,改善编码质量的意义在哪里?
- 从自身来说不是为了达成指标或者彰显个人能力,只为为了
自救
- 对他人而言,因为阅读并理解肮脏代码
耗费时间
,导致降低自身的工作产出,直接影响到个人成长和经济收入;或者因此占用了本该陪伴家人的时间,在办公室内通宵调试。不负责任的代码罪大恶极
。
第三,我们始终强调低成本
,不使用任何有技术门槛
,或者统一整改
的方式,还是那个原因,我们“没有时间”。
第四,后面提到的很多方式方法,都是来源于流传多年的经典著作,只是根据性价比
摘取简单的实践方案。这里仅仅只是开始,而不是我们的最终目标
命名
名称是索引,是我们探索的路标
。
通过命名来找到我们的目标,很类似通过“问路”来到达指定地点。
描述性(最为重要)
起名的目的,是为了让人理解,这里真的不需要过份的简洁。
就像你询问我如何找到宝藏,得到的答案却是:右、左、绕、左
。
我为了节省些口水,却把你丢在茫茫的大海里晕头转向。
如果我的答案是:右转直行,5海里后左转、绕过小山、在海岛左转
,你还会在路上耽误大量的时间去猜测和探索么?
使用模糊的命名,根源只有一个:懒
。
哪怕查词典一个个单次累上去,也能为自己、为后来者提供极有帮助的线索。
名称不怕长,floorIndexOfTheBuilding
一定好过f
,如果有必要,即使把名称写成一篇抒情散文,我们也愿意认真的阅读。
切记
- 不要使用拼音缩写或者单次的首字母缩写
分享一个刚工作时的经历,进入公司接手一个老项目的维护工作,在阅读源码的时候遇到一个神奇的变量
cwkpzz
,历经千辛万苦才从一位前辈口中得知,这是财务开票制作
。
标准化
使用大家约定俗称
的名称,不要在起名字的时候标新立异。
问路的时候,你希望听到方言
么?
无歧义
谣言猛于虎
。
你明明告诉我走到“摩天大楼”左转,实际上那就是个“违建的二层民房”。
多少人被一个错误的名称引入深山老林,艰苦跋涉后才恍然大悟。
名称长度
名称的长度取决于他的作用范围
- 在简单的循环里,一个
i
已经足够 - 在一个类里面,
id
已经很清晰 - 在一个系统里面,
nameOfTheUser
才足够明确
对于非静态类型语言,IDE难以提供精确的定位,可搜索的长名称可以帮助你快速和准确的定位
- 淘宝的收件地址只填写
A2栋309
,足以证明你是真正的土豪
方法名
方法是用来做事的,动词+名次
才能让人理解意图。
指路的时候告诉你过河
这样你才知道怎么做,如果我只说一个河
,是应该潜下去还是喝干它?
如果能够进一步告诉你乘船过河
,是不是可以避免你尝试游过去?
长度的游戏
有没有过这样的体验:
- 打开一个几千行的文件导致编辑器卡死
- 查看一个方法的时候上下翻页,看了后面忘了前面,想起前面找不到后面
- 调用的方法拥有十几个参数,两根手指同步移动才能辨别谁是谁
过长的东西往往伴随着复杂
和理解上的困难
。我们生来畏惧困难,对于这类东西经常望而却步;即使硬着头皮去阅读和理解,在这个过程中也会耗尽耐心和精力
。
游戏规则
- 方法的长度 < 30行
- 文件的长度 < 500行
- 参数的长度 < 4个
开荒攻略
for/if/else/try/catch
这些带有大括号
是最明显的提取点解析参数 - 执行操作 - 封装结果
是最常见的划分顺序- 重复利用IDE的
提取方法
功能 - 多于3个参数就提取为
参数对象
- 如果每个方法都足够短小,在
文件超长
的时候,才能快速、准确的拎出来一个新的文件
通关奖励
- 短小的东西都是
简单
的 - 自带
单一职责
光环 - 即使不使用任何技巧或者模式,看起来也是“高x格”的
- 整洁而且有序,便于发现和应用各种
模式
顺序
我们习惯于固定的顺序
,无论是生活、工作、或是其他。
在习惯的顺序中,我们会更加的游刃有余。
先总体、后细节
我们在理解事物的时候,已经习惯于先总体、后细节
,这也是我们大脑的正常运转方式,我们不应该跟自己过不去。
就像我们在刷朋友圈的时候,一定是先看标题,再决定是否点进去看细节
。如果反过来呢,浪费大把时间,漫无目的的阅读文字和图片,绞尽脑汁的猜测,最后才知道他只是想炫耀“自己刚买了个新手机”。
阅读程序也是一样,总体概念在前、细节逐步展开
,写代码不是在写悬疑小说,铺开一堆细节吊足观众胃口,最后才把谜底揭开。
凑热闹
相关的、类似的
东西放在一起,总是能给我们带来便捷
。
想想我们出去办个业务有多难,今天去提个申请,明天去开个健在证明,隔几天在跑到第三个地方提交一堆材料……,最后才能在一个从没去过的地方办好自己的事情。 大量的时间用来寻找下一站、赶路和排队,每一次都是刻骨铭心的体验。
阅读程序也是一样,如果相关的东西总是四分五裂,总是在不停的寻找、跳转,哪里还有什么效率和便捷。
具体一些:
- 物以类聚 - 类似的操作放在一起,比如
解析参数
- 父子团聚 - 父方法在前,子方法紧挨着展开
- 三八线 - 通过
空行
给眼睛一些提示,分割不同的“区块”
测试
我一直在逃避
这一话题,对于开发者来说,自动化测试的收益不可限量
。
我们随处都能见到测试的好处,数不胜数的工具和方法。
但是在实践中,我们总是花费很多的时间,却没有帮助我们解决问题。
似乎自动化测试是一种很低效的实践方式,他只是为了达成指标,为我们的工作带来负担。
事实上,高效的自动化测试是有一定的前提条件的:
- 简洁、单一的实现
- 待测逻辑要低耦合
- 框架提供良好的支持
- 持之以恒,日积月累
为什么我们的测试没有带来明显的收益?
- 现有的系统本身缺少良好的基础,耦合度过高、缺少合适的抽象、太多的外部依赖等等,我们每次编写测试,都花费大量的时间来
模拟依赖
- 降低测试有效性的最常见原因就是
重复
,回想一下我们常见的数据查询场景,无数次重复的数据库链接、查询语句拼接、取值和资源释放,每次我们都要重复的编写这些重复的测试以覆盖各种边界场景,而我们实际关注的可能只有拼接出来的语句是否正确、是否在正确的地方取值。 - 我们所使用的框架没有提供足够便利的
工具集
来帮助我们快速构建测试,如果能够通过依赖注入系统快速的构建依赖,或者通过模拟服务器提供模拟响应,我们编写测试的效率和有效性都会得到极大的提升。 - 我们没有在短期内
见到收益
,当我们编写测试的功劳没有被发现,花费大量时间导致项目延期,或者失败的用例阻碍了我们的紧急工作时,我们常常会放弃他。
测试的作用在很多时候并不是直接产生收益的
除非拥有良好的框架支撑、或者测试能够直接帮助我们的工作时,我们才有驱动力
来编写测试,甚至是以TDD方式
来开始我们的编码工作。
例如:
- 在开发后台插件的时候,框架本身提供的隔离和运行机制,让我们专注于独立的问题领域,并宽素完成测试用例的编写;同时,因为插件的运行和调试相对困难,需要配置和等待执行周期,相对来说通过测试来调试是多么幸福的体验。
- 另外一面,在前端部分的开发体验中,我们编写的逻辑依赖于浏览器当前状态、html结构、css样式、并且要兼顾后台服务的响应和零散的全局变量,我们的
关注点分散
,而且处处受制
,在没有框架支撑的情况下,编写测试的成本极高。在调试手段上,刷新一下浏览器
是多么便捷。但是实际的代价就是,我们的验证过程是不可重复
的,甚至这样的验证过程在积累到一定程度以后,会成为我们的负担。
即使不考虑直接收益,测试也能够在侧面为我们提供极大的帮助
- 能测试的东西,一定是低耦合的
- 能测试的东西,职责一定是单一的
- 能运行的测试,本身就是说明文档
- 完善的测试集,让我们放心的重构和扩展
测试与质量是相辅相成,循环提升的关系。优秀的质量便于测试、丰富的测试帮助改善质量。虽然短期收益不明显,但是在反复循环中创造巨大的价值。
复利效应:复利效应指资产收益率以复利计息时,经过若干期后资产规模(本利和)将超过以单利计息时的情况。事实上,复利计息条件下资产规模随期数成指数增长,而单利计息时资产规模成线性增长,因此长期而言复利计息的总收益将大幅超过单利计息。
总的来说,我们始终在讨论时间和收益,最终的目标是获得良好的设计,帮助我们自己脱离困境
但是在当前情况下,我们最大的问题是没有时间
,历史积累的债务拖慢了我们的脚步,阻止我们学习更好的技术、阻碍构建完整的测试集、迫使我们所在的团队无力改善组织结构和工作模式。
现实点说,我们能够做到的只有不要浪费时间、用尽可能少的成本带来收益,并且为以后的发展打好基础
。
就像我们给出的方案,对于命名、长度、顺序
的改进,节省了大家摸索和猜测的时间,成本很低,但是带来容易理解、单一职责、低耦合的设计
。而且这样的实践是一次投入、持续收益
,我们所做的仅仅是保持整洁
。
童子军规则:“让营地比你刚来时更干净。”
我们期望的结果是:
最后,请牢记,整洁
是我们当前能够轻松做到的,但仅仅是开始
,在释放出足够的时间后,我们需要继续走下去
- 完善测试集,代替手工验证,让我们安心的进行修改
- 引入各种模式和思想,来应对系统的发展和变化
- 应用新的工具、技术、框架来改进我们的工作环境
- 为团队提供信心和余力,改进组织架构和管理方式
自助者天助,助人着人助
延伸阅读
- 《代码整洁之道》
- 《重构:改善既有代码的设计》
- 《敏捷软件开发:原则、模式与实践》