Efficient Programmers' Way of Working
大部分程序员忙碌解决的问题,都不是程序问题,而是由偶然复杂度导致的问题。如何减少偶然复杂度引发的问题,让软件开发工作有序、高效地进行?
- 本质复杂度(Essential Complexity)
- 指解决问题或实现功能时,不可避免存在的复杂性
- 是由问题本身的复杂性所导致的,无法通过简化设计或改变方法来消除
- 例如:处理大规模数据时所面对的复杂性,即使采用最佳算法和数据结构,也无法避免
- 偶然复杂度(Accident Complexity)
- 指在软件开发过程中,由于设计、工具或技术选择等外部因素引入的额外复杂性
- 是可以通过改变设计、优化工具选择或改进工作流程等方式来减少或消除的复杂性
- 例如:由于技术栈选择不当或设计不良引入的额外代码复杂性,例如过多的依赖或复杂的类继承结构
思考框架
一个思考框架
- Where are we?(现状)
- Where are we going?(目标)
- How can we get there?(路径)
开发一个功能特性时,通常需要思考
- 为什么要做这个特性,它会给用户带来怎样的价值?
- 什么样的用户会用到这个特性,他们在什么场景下使用,他们又会怎样使用它?
- 达成这个目的是否有其它手段?是不是一定要开发一个系统?
- 这个特性上线之后,怎么衡量它的有效性?
四个思考原则
- 以终为始:在工作的一开始就确定好自己的目标
- 任务分解:将大目标拆分成一个一个可行的执行任务,工作分解得越细致,越能更好地掌控工作
- 沟通反馈:疏通与其他人交互的渠道(解决与人打交道出现的问题)
- 保证信息能够传达出去,减少因为理解偏差造成的工作疏漏
- 保证我们能够准确接收外部信息,以免因为自我感觉良好,阻碍了进步
- 自动化:将繁琐的工作通过自动化的方式交给机器执行(解决与机器打交道出现的问题)
现在在哪?(现状) | 要到哪去?(目标) | 如何到达那里?(路径) |
---|---|---|
你很清楚 | 以终为始 | 任务分解、沟通反馈、自动化 |
以终为始
以终为始:如何让你的努力不白费
把目光放长远是需要额外消耗能量的,“以终为始”是一种反直觉的思维方式
任何事物都要经过两次创造:
- 一次是在头脑中的创造,也就是智力上的或者第一次创造(Mental/First Creation)
- 然后才是付诸实践,也就是实际的构建或第二次创造(Physical/Second Creation)
在今天的软件开发实践中,已经有很多采用了“以终为始”原则的实践
- 测试驱动开发
- 持续集成
践行“以终为始”就是在做事之前,先考虑结果,根据结果来确定要做的事情(遇到事情,倒着想)
DoD的价值:你完成了工作,为什么他们还不满意
- DoD(Definition of Done,完成的定义)为了解决软件开发中常见的“完成”问题(理解鸿沟)而生的
- 例如:特性开发完成,表示开发人员经过了需求澄清、功能设计、编写代码、单元测试,通过了测试人员的验收,确保代码处于一个可部署的状态,相关文档已经编写完毕
- 开发完成,表示开发人员编写好功能代码,编写好单元测试代码,编写好集成测试代码,测试可以通过,代码通过了代码风格检查、测试覆盖率检查
- DoD 是一个清单,清单是由一个个实际可检查的检查项组成,用来检查我们的工作完成情况
- DoD 是团队成员间彼此汇报的一种机制,在团队层面,也可以定义 DoD:
- 某个功能的 DoD,比如:这个功能特性已经开发完成,经过产品负责人的验收,处于一个可部署的状态
- 一个迭代的 DoD,比如:这个迭代规划的所有功能已经完成
- 一次发布的 DoD,比如:整个软件处于可发布的状态,上线计划已经明确
- DoD 不仅局限在团队内部协作上,在工作中用途非常广泛
- 例如“定义接口”的 DoD,需要检查:
- 服务方提供的接口是不是和这个可运行的接口返回值是一样的
- 调用方是否可以和这个可运行的接口配合使用
- 例如“定义接口”的 DoD,需要检查:
- DoD 是一个思维模式,是一种尽可能消除不确定性,达成共识的方式
- 本着“以终为始”的方式做事情,DoD 让我们能够在一开始就把“终”清晰地定义出来
- DoD(Definition of Done,完成的定义)为了解决软件开发中常见的“完成”问题(理解鸿沟)而生的
接到需求任务,你要先做哪件事
用户故事(User Story)是站在用户的角度来描述了一个用户希望得到的功能,关注用户在系统中完成一个动作需要经过怎样的路径
一个完整的用户故事大致包含以下几个部分
- 标题,简要地说明这个用户故事的主要内容
- 概述,简要地介绍这个用户故事的主要内容,一般会用这样的格式:- As a (Role), I want to (Activity), so that (Business Value)
- 验收标准,这个部分会描述一个正常使用的流程是怎样的,以及各种异常流程系统是如何给出响应的,把详述中很多叙述的部分变成一个具体的测试用例
验收标准给出了这个需求最基本的测试用例,它保证了开发人员完成需求最基本的质量,其非常重要的一环是异常流程的描述
BDD(Behavior-Driven Development,“行为驱动开发”)
验收标准所给出实现细节应该是业务上的,程序员的发挥空间应该是在技术实现上
虽然你名义上是程序员,但当拿到一个需求的时候,你要做的事不是立即动手写代码,而是扮演产品经理的角色,分析需求,圈定任务范围
“最好维护的代码是没有写出来的代码”
持续集成:集成本身就是写代码的一个环节
- 当我们在一个团队中工作的时候,把不同人的代码放在一起,使之成为一个可工作软件的过程就是集成
- 持续集成(Continuous Integration)一个关键的思维破局是,将原来分成两个阶段的开发与集成合二为一了,也就是一边开发一边集成
- 每日构建作为早期的一种“最佳实践”被提了出来,当人们进一步“调小”参数后,诞生了一个更极致的实践:持续集成,也就是每次提交代码都进行集成
- 一个好的做法是尽早把代码和已有代码集成到一起,而不应该等着所有代码都开发完了,再去做提交
精益创业:产品经理不靠谱,你该怎么办
- 我们必须要有自己的独立思考,多问几个为什么,尽可能减少掉到“坑”里之后再求救的次数
- 软件开发的主流由面向确定性问题,逐渐变成了面向不确定性问题
- 一旦一个问题变成通用问题,就有人尝试总结各种最佳实践,一旦最佳实践积累多了,就会形成一套新的方法论
- 最早成型的面向不确定性创造新事物的方法论是精益创业(Lean Startup)
- 精益创业是在尽可能少浪费的前提下,面向不确定性创造新事物,既然是不确定的,那你唯一能做的事情就是“试”
- 最小可行产品 MVP(Minimum Viable Product)是精益创业提出的一个非常重要的概念(少花钱,多办事)
- “把软件完整地做出来是最大的浪费”
- 默认所有需求都不做,直到弄清楚为什么要做这件事
解决了很多技术问题,为什么你依然在“坑”里
- 花大力气去解决一个可能并不是问题的问题,常常是很多程序员的盲区(被自己的思考局限住)
- 不同角色工作真正的差异在于上下文的差异,在一个局部上下文难以解决的问题,换到另外一个上下文甚至是可以不解决的
- 当你对软件开发的全生命周期都有了认识之后,你看到的就不再是一个点了,而是一条线
- 扩大自己工作的上下文,别把自己局限在一个“程序员”的角色上
为什么说做事之前要先进行推演
- 一件事 → 工作列表(模糊 → 清晰)比如:
- 先从结果的角度入手,看看最终上线要考虑哪些因素
- 推演出一个可以一步一步执行的上线方案,用前面考虑到的因素作为衡量指标
- 根据推演出来的上线方案,总结要做的任务
- “最后一公里”:完成一件事,在最后也是最关键的步骤;结果是重要的。然而,通向结果的路径才是更重要的
- 一种是前期其乐融融,后期手忙脚乱;一种是前面思前想后,后面四平八稳
- 即便已经确定了自己的工作目标,我们依然要在具体动手之前,把实施步骤推演一番,完成一次头脑中的创造,也就是第一次创造或智力上的创造
- 这种思想在军事上称之为沙盘推演,在很多领域都有广泛地应用
- 一件事 → 工作列表(模糊 → 清晰)比如:
你的工作可以用数字衡量吗
- 直觉通常是一种洞见(Insight),洞见很大程度上依赖于一个人在一个领域长期的沉淀和积累,而这其实是某种意义上的大数据
- 主观 → 客观(测量指标)
- 很少把数字化的思维带到工作范围内是工作中很多“空对空”对话的根源所在
- 出现波动尤其是大幅度波动,又不能给出一个合理解释的话,就说明系统存在着隐患
迭代0: 启动开发之前,你应该准备什么
- 需求方面
- 细化过的迭代1需求
- 用户界面和用户交互
- 技术方面
- 基本技术准备(技术选型、系统架构、数据库表结构、持续集成、测试)
- 发布准备(数据库迁移、发布)
- 需求方面
任务分解
向埃隆·马斯克学习任务分解
- 不同的可执行定义差别在于,你是否能清楚地知道这个问题该如何解决
- 大多数人都高估了自己可执行粒度,低估任务分解的程度(分解出来的任务粒度偏大)
- 软件行业都在提倡拥抱变化,而任务分解是我们拥抱变化的前提
测试也是程序员的事吗
- 尽可能早地发现问题,修正问题,这样所消耗的成本才是最低的
- “以终为始”,就是在强调尽早发现问题,能从需求上解决的问题,就不要到开发阶段,在开发阶段能解决的问题,就不要留到测试阶段
- 测试框架把自动化测试作为一种最佳实践引入到开发过程中,使得测试动作可以通过标准化的手段固定下来
- 单元测试框架只是一个自动化测试的工具而已,并不是用来定义测试类型
- 关注最小程序模块的单元测试、将多个模块组合在一起的集成测试、将整个系统组合在一起的系统测试
- 根据不同测试的配比,也就有了不同的测试模型
- 冰淇淋蛋卷测试模型的人并不多,它是一种费时费力的模型,要准备高层测试实在是太麻烦了
- 行业里的最佳实践:测试金字塔(越在底层测试,成本越低,执行越快;越在高层测试,成本越高,执行越慢)
- 虽然冰淇淋蛋卷更符合直觉,但测试金字塔才是行业的最佳实践
- 在本地运行单元测试和集成测试,在持续集成服务器上运行系统测试
先写测试,就是测试驱动开发吗
- 先写测试,后写代码的实践指的是测试先行开发(Test First Development),而非测试驱动开发(Test Driven Development)
- 二者的差别在于,TDD 还有一个更重要的环节:重构(refactoring),不能忽略了新增代码可能带来的“坏味道(Code Smell)
- 重构与测试是相辅相成的:没有测试,你只能是提心吊胆地重构;没有重构,代码的混乱程度是逐步增加的,测试也会变得越来越不好写
- 先测试后写代码的方式,会让你看待代码的角度完全改变,甚至要调整你的设计,才能够更好地去测试(写代码前,先想怎么测)
- 懂 TDD 的人会把 TDD 解释为测试驱动设计(Test Driven Design)
大师级程序员的工作秘笈
极限编程对于行业最大的贡献在于,它引入了大量的实践,比如持续集成、TDD、结对编程、现场客户等等
极限编程之所以叫“极限”,它背后的理念就是把好的实践推向极限
- 如果集成是好的,我们就尽早集成,推向极限每一次修改都集成,这就是持续集成
- 如果开发者测试是好的,我们就尽早测试,推向极限就是先写测试,再根据测试调整代码,这就是测试驱动开发
- 如果代码评审是好的,我们就多做评审,推向极限就是随时随地地代码评审,这就是结对编程
- 如果客户交流是好的,我们就和客户多交流,推向极限就是客户与开发团队时时刻刻在一起,这就是现场客户
能把任务分解到很小,其实是证明你已经想清楚了;而大多数程序员之所以开发效率低,很多时候是没想清楚就动手了
TDD 实践起来却不知道如何下手,中间就是缺了任务分解的环节(或是任务分解的粒度不够“小”)
一起练习分解任务
按照一个需求、一个需求的过程走,这样,任务是可以随时停下来的
需求:用户通过输入用户名和密码登录,按照完整实现一个需求的顺序去安排分解出来的任务
- 分解前:
- 分解后:
检验每个任务项是否拆分到位,就是看你是否知道它应该怎么做了
所有分解出来的任务,都是独立的,每做完一个任务,代码都是可以提交的(小步提交)
为什么你的测试不够好
- 测试的作用是保证代码的正确性,如何保证测试的正确性?把测试写简单,简单到一目了然,不需要证明它的正确性
- 一般测试要具备的四段:前置准备、执行、断言(预期判断)和清理;没有断言的测试,是没有意义的
- 测试不好写,往往是设计的问题,应该调整的是设计,而不是在测试这里做妥协
- 当测试代码里出现各种判断和循环语句,基本上这个测试就有问题了,应该多写几个测试,每个测试覆盖一种场景
- 怎么样的测试算是好的测试呢?一段旅程(A-TRIP)
- Automatic,自动化 - 测试一定要有断言
- Thorough,全面的 - 测试覆盖率工具
- Repeatable,可重复的 - 不应依赖任何不在控制之下的环境
- Independent,独立的 - 试和测试之间不应该有任何依赖
- Professional,专业的 - 测试代码,也是代码
程序员也可以“砍”需求吗
“主题”只是帮你记住大方向,需要进一步分解;“用户故事”是需求管理的基本单位
评价用户故事有一个 “INVEST 原则”,这是六个单词的缩写,分别是:
- Independent,独立的。一个用户故事应该完成一个独立的功能,尽可能不依赖于其它用户故事,因为彼此依赖的用户故事会让管理优先级、预估工作量都变得更加困难。如果真的有依赖,一种好的做法是,将依赖部分拆出来,重新调整。
- Negotiable,可协商的。有事大家商量是一起工作的前提,我们无法保证所有的细节都能100%落实到用户故事里,这个时候最好的办法是大家商量。它也是满足其它评判标准的前提,就像前面提到的,一个用户故事不独立,需要分解,这也需要大家一起商量的。
- Valuable,有价值的。一个用户故事都应该有其自身价值,这一项应该最容易理解,没有价值的事不做。但正如我们一直在说的那样,做任何一个事情之前,先问问价值所在。
- Estimatable,可估算的。我们会利用用户故事估算的结果安排后续的工作计划。不能估算的用户故事,要么是因为有很多不确定的因素,要么是因为需求还是太大,这样的故事还没有到一个能开发的状态,还需要产品经理进一步分析。
- Small,小。步子大了,不行。不能在一定时间内完成的用户故事只应该有一个结果,拆分。小的用户故事才方便调度,才好安排工作。
- Testable,可测试的。不能测试谁知道你做得对不对。这个是我们在前面已经强调过的内容,也就是验收标准,你得知道怎样才算是工作完成。
度量用户故事大小的方式有很多种,有人用 T 恤大小的方式,也就是S、M、L、XL、XXL。也有人用费波纳契数列,也就是1、2、3、5、8等等
任务分解是基础中的基础,不学会分解,工作就只能依赖于感觉,很难成为一个靠谱的程序员(估算的结果是相对的,不是绝对精确的)
这时候再说需求调整,调整的就不再是一个大主题,而是一个个具体的用户故事了
需求管理:太多人给你安排任务,怎么办
如果不了解需求是怎么管理的,即便是进行了需求分解,最终的结果很有可能依然是深陷泥潭
一个有效的时间管理策略是艾森豪威尔矩阵(Eisenhower Matrix)
如果不把精力放在重要的事情上,到最后可能都变成紧急的事情
当有多个需求来源时,如何确认哪个需求是最重要的呢?当员工想不明白的事,换成老板的视角就全明白了
很多所谓的人生难题不过是因为见识有限造成的。比如,如果你觉得公司内总有人跟你比技术,莫不如把眼光放得长远一些,把自己放在全行业的水平上去比较,因为你是为自己的职业生涯在工作,而不是一个公司
如何用最小的代价做产品
精益创业就是通过不断地尝试在真实世界中验证产品想法,其中一个重要的实践是最小可行产品(Minimum Viable Product,MVP)
- “最小”:能不做的事情就不做,能简化的事情就简化
- “可行”:找到一条路径,给用户一个完整的体验
当时间有限时,需要学会找到一条可行的路径,在完整用户体验和完整系统之间,找到一个平衡(”刚刚好”满足客户需求)
沟通反馈
为什么世界和你的理解不一样
- 学习各种知识,是为更好地理解这个世界的运作方式,而沟通反馈,就是与真实世界互动的最好方式
- 每个人经历见识的差异,造成了各自编解码器的差异,世界是同一个世界,每个人看到的却是千姿百态
- 通过沟通反馈,不断升级自己的编解码能力,改善编解码,需要从几个角度着手
- 编码器,让信息能输出更准确
- 解码器,减少信号过滤,改善解码能力
- 编解码算法,也就是各种来自行业的“最佳实践”,协调沟通的双方
你的代码为谁而写
- 一个专业程序员,追求的不仅是实现功能,还要追求代码可维护
- 任何人都能写出计算机能够理解的代码,只有好程序员才能写出人能够理解的代码。- —— Martin Fowler
- 人要负责将业务问题和机器执行连接起来,缺少了业务背景是不可能写出好代码的
- 一个好的命名需要你对业务知识有一个深入的理解,需要额外地学习,这也是我们想写好代码的前提
轻量级沟通:你总是在开会吗
改善会议的第一个行动项是,减少参与讨论的人数,如果你要讨论,找人面对面沟通
开会的目的不再是讨论,而是信息同步
一种特殊的会议:站会
- “做了什么” ,是为了与其他人同步进展,看事情是否在计划上,这会涉及到是否要调整项目计划
- “要做什么” ,是同步你接下来的工作安排。如果涉及到与其他人协作,也就是告诉大家,让他们有个配合的心理准备
- “问题和求助”, 就是与其他人的协作,表示:我遇到不懂的问题,你们有信息的话,可以给我提供一下
多面对面沟通,少开会
可视化:一种更为直观的沟通方式
ThoughtWorks 技术雷达是由 ThoughtWorks 技术咨询委员会(Technology Advisory Board)编写的一份技术趋势报告
技术雷达用来追踪技术,在雷达图的术语里,每一项技术表示为一个 blip,也就是雷达上的一个光点
用两个分类元素组织这些 blip:象限(quadrant)和圆环(ring)
- 象限表示一个 blip 的种类,目前有四个种类:技术、平台、工具,还有语言与框架
- 圆环表示一个 blip 在技术采纳生命周期中所处的阶段:采用(Adopt)、试验(Trial)、评估(Assess)和暂缓(Hold)
雷达图是一种很好的将知识分类组织的形式,它可以让你一目了然地看到并了解所有知识点,并根据自己的需要,决定是否深入了解
看板,是一种项目管理工具,它将我们正在进行的工作变得可视化,这个实践来自精益生产
- 将工作分成几个不同的阶段,然后,把分解出来的工作做成一张卡片,根据当前状态放置到不同的阶段中
- 看板可以帮助你一眼看出许多问题,比如,当前进展是否合适,是否有人同时在做很多的事,发现当前工作的瓶颈等
多尝试用可视化的方式进行沟通
快速反馈:为什么你们公司总是做不好持续集成
持续集成的两个重要目标:怎样快速地得到反馈,以及什么样的反馈是有效的
“快速”:不能把检查只放到 CI 服务器上执行,在本地开发环境上执行
- 用好本地构建脚本(build script),保证各种各样的检查都可以在本地环境执行
- 一旦有了构建脚本,你在 CI 服务器上的动作也简单了,就是调用这个脚本(动作一致)
“反馈”,也就是怎么得到即时的、有效的反馈,持续集成监视器,也是 CI 监视器
- CI 监视器的原理很简单,CI 服务器在构建完之后,会把结果以 API 的方式暴露出来
- 只有 CI 服务器处于绿色的状态才能提交代码,CI 服务器一旦检查出错,要立即修复
开发中的问题一再出现,应该怎么办
把过程还原,进行研讨与分析的方式,就是复盘(客体化)
用别人的视角看问题,这就是客体化,由一个主观的视角,变成了一个客观的视角
回顾会议 —— 一种复盘的实践
回顾会议是一个常见的复盘实践,定期回顾是一个团队自我改善的前提
主题分类
- 比如:做得好的、做得欠佳的、问题或建议
- 或者海星图,分成了五大类:“继续保持、开始做、停止做、多做一些、少做一些”
写事实,不写感受,针对性地讨论
5个为什么(5 Whys)—— 一个常用的找到根因的方式
比如服务器经常返回504,那我们可以采用“5个为什么”的方式来问一下
为什么会出现504呢?因为服务器处理时间比较长,超时了
为什么会超时呢?因为服务器查询后面的 Redis 卡住了
为什么访问 Redis 会卡住呢?因为另外一个更新 Redis 的服务删除了大批量的数据,然后,重新插入,服务器阻塞了
为什么它要大批量的删除数据重新插入呢?因为更新算法设计得不合理
为什么一个设计得不合理的算法就能上线呢?因为这个设计没有按照流程进行评审
解决之道自然就浮出水面了:一个核心算法一定要经过相关人员的评审
“5个为什么”中的“5”只是一个参考数字,不是目标
不要用这些方法责备某个人,目标是想要解决问题,不断地改进,而不是针对某个人发起情感批判
作为程序员,你也应该聆听用户声音
- “Eat your own dog food” —— “提高自家产品在内部使用的比例”
- 我们要做一个有价值的产品,这个“价值”,不是对产品经理有价值,而是要对用户有价值
- 谁离用户近,谁就有发言权,无论你的角色是什么
尽早暴露问题:为什么被指责的总是你
- 不是所有的问题,都是值得解决的技术难题,遇到问题,最好的解决方案是尽早把问题暴露出来
- 写程序有一个重要的原则叫 Fail Fast,如果遇到问题,尽早报错
- 在程序中尽早暴露问题是很容易接受的,但在工作中暴露自己的问题,却是很大的挑战
- 比起尽早暴露问题,还有更进一步的工作方式,那就是把自己的工作透明化
结构化:写文档也是一种学习方式
你发现矛盾了吗?一方面,我们讨厌写文档,另一方面,文档却对我们的工作学习有着不可忽视的作用
很多人回避写文档的真正原因是,他掌握的内容不能很好地结构化
当你的知识都是零散的,任何新技术的出现,都是新东西,当你建立起自己的知识结构,任何新东西都只是在原有知识上的增量叠加
将零散的知识结构化,有很多种方式,但输出是非常关键的一环;输出的过程,本质上就是把知识连接起来的过程
输出的方式有很多,对于程序员来说,最常接触到的两种应该是写作与演讲,软件行业的很多大师级程序员都是对外输出的高手
把事情说清楚,把自己的知识清晰地呈现出来,金字塔原理:
即便强如乔布斯,他的演讲也是经过大量练习的,本质上,对演讲的惧怕只是因为练习不足
无他,唯手熟尔!
自动化
“懒惰”应该是所有程序员的骄傲
- Perl 语言的发明人 Larry Wall 一个经典叙述:优秀程序员应该有三大美德:懒惰、急躁和傲慢(Laziness, Impatience and hubris)
- 懒惰,是一种品质,它会使你花很大力气去规避过度的精力消耗,敦促你写出节省体力的程序,别人也能很好地利用,你还会为此写出完善的文档,以免别人来问问题
- 急躁,是计算机偷懒时,你会感到的一种愤怒,它会促使你写出超越预期的程序,而不只是响应需求。
- 傲慢,极度自信,写出(或维护)别人挑不出毛病的程序
- 做有价值的事是重要的,这里面的有价值,不仅仅是“做”了什么,通过“不做”节省时间和成本也是有价值的
- 可以从需求的角度判断哪些工作是可以不做的,但我们也要防止程序员自己“加戏”
- NIH 综合症(Not Invented Here Syndrome),人特别看不上别人做的东西,非要自己做出一套来,原因只是因为那个东西不是我做的,可能存在各种问题
- 写代码之前,先问问自己真的要做吗?能不做就不做,直到你有了足够的理由去做
- 在软件开发中,其它的东西都是易变的,唯有设计的可变性是你可以控制的
- 不懂软件设计,只专注各种工具,其结果一定是被新技术遗弃,这也是很多人经常抱怨 IT 行业变化快的重要原因
- Perl 语言的发明人 Larry Wall 一个经典叙述:优秀程序员应该有三大美德:懒惰、急躁和傲慢(Laziness, Impatience and hubris)
一个好的项目自动化应该是什么样子的
- 将工作过程自动化
- 生成 IDE 工程、编译、打包、运行测试、代码风格检查、测试覆盖率、数据库迁移、运行应用
- 将工作过程自动化
程序员怎么学习运维知识
- 每个程序员都应该学习运维知识,保证我们对软件的运行有更清楚地认识,而且部署工作是非常适合自动化的
- 但是,对运维工具的学习是非常困难的,因为我们遇到的很多工具是非常零散的,需要有体系地学习运维知识
持续交付:有持续集成就够了吗
一般来说,在构建持续交付的基础设施时,会有下面几个不同的环境
- 持续集成环境,持续集成是持续交付的前提,这个过程主要是执行基本的检查,打出一个可以发布的包
- 测试环境(Test),这个环境往往是单机的,主要负责功能验证,这里运行的测试基本上都是验收测试级别的,而一般把单元测试和集成测试等执行比较快的测试放到持续集成环境中执行
- 预生产环境(Staging),这个环境通常与生产环境配置是相同的,比如,负载均衡,集群之类的都要有,只是机器数量上会少一些,主要负责验证部署环境,比如,可以用来发现由多机并发带来的一些问题
- 生产环境(Production),这就是真实的线上环境了
通常会用几个不同的环境验证,每一个环境都是一个单独的阶段,一个阶段不通过,是不能进入下一阶段的,这种按照不同阶段组织构建的方式,称之为构建流水线(Build Pipeline)
在准备好发布包和部署的基础设施之后,我们顺着持续集成的思路,将部署过程也加了进来,这就是持续交付
持续交付,是一种让软件随时处于可以部署到生产环境的能力,让软件具备部署到生产环境的能力,这里面有两个关键点:验证发布包和部署
DevOps 包含了很多方面,对程序员最直接的影响是各种工具的发展,这些工具推动着另一个理念的发展:基础设施即代码(Infrastructure as code)
今天定义交付,就不再是一个发布包,而是一个可以部署的镜像
如何做好验收测试
- 验收测试(Acceptance Testing),是确认应用是否满足设计规范的测试,这种测试往往是站在用户的角度,看整个应用能否满足业务需求
- 让验收测试从各自为战的混乱中逐渐有了体系的是行为驱动开发(Behavior Driven Development)这个概念的诞生
- 行为驱动开发中的行为,指的是业务行为,想写好 BDD 的测试用例,关键点在用业务视角描述
- 基本格式为“Given…When…Then”,要编写步骤定义(Step Definition)将测试用例与实现连接起来
- (怎么看起来的感觉像形式化语言、形式化规格说明之类的
你的代码是怎么变混乱的
Robert Martin 提出的面向对象设计原则:SOLID,这其实是五个设计原则的缩写,分别是
- 单一职责原则(Single responsibility principle,SRP)
- 开放封闭原则(Open–closed principle,OCP)
- Liskov 替换原则(Liskov substitution principle,LSP)
- 接口隔离原则(Interface segregation principle,ISP)
- 依赖倒置原则(Dependency inversion principle,DIP)
如果说设计模式是“术”,设计原则才是“道”,设计模式并不能帮你建立起知识体系,而设计原则可以
很多代码的问题就是因为对设计思考得不足导致的,如果只能记住一件事,那请记住:把函数写短
总是在说MVC分层架构,但你真的理解分层吗
- 分层架构,实际上,就是一种在设计上的分解,因为好的分层往往需要有好的抽象
- 网络模型的分层架构好到你作为上层的使用者几乎可以忽略底层,这正是分层的价值:构建一个良好的抽象
- 为数不少的团队都在自己的业务代码中直接使用了第三方代码中的对象,第三方的任何修改都会让你的代码跟着改,你的团队就只能疲于奔命
- 解决这个问题最好的办法就是把它们分开,你的领域层只依赖于你的领域对象,第三方发过来的内容先做一次转换,转换成你的领域对象,这种做法称为防腐层
- 把领域模型看成了整个设计的核心,看待其他层的视角也会随之转变,它们只不过是适配到不同地方的一种方式而已
- 这种理念的推广,就是一些人在说的六边形架构
- 在日常工作中,我们应该把精力重点放在构建自己的领域模型上,因为它才是工作最核心、不易变的东西
为什么总有人觉得5万块钱可以做一个淘宝
作为程序员,我们需要知道自己面对的到底是一个什么样的系统,不同业务量级的系统本质上就不是一个系统
淘宝的工程师之所以要改进系统,真实的驱动力不是技术,而是不断攀升的业务量带来的问题复杂度
评估系统当前所处的阶段,采用恰当的技术解决,是我们最应该考虑的问题
- 一方面,有人会因为对业务量级理解不足,盲目低估其他人系统的复杂度
- 另一方面,也有人会盲目应用技术,给系统引入不必要的复杂度,让自己陷入泥潭
用简单技术解决问题,直到问题变复杂
先做好DDD再谈微服务吧,那只是一种部署形式
服务划分不好,等待团队的就是无穷无尽的偶然复杂度泥潭
领域驱动设计(Domain Driven Design,DDD)是 Eric Evans 提出的从系统分析到软件建模的一套方法论
- DDD 把你的思考起点,从技术的角度拉到了业务上
- 将业务概念和业务规则转换成软件系统中概念和规则,从而降低或隐藏业务复杂性,使系统具有更好的扩展性
许多团队一提起建模,第一反应依然是建数据库表。这种做法是典型的面向技术实现的做法,一旦业务发生变化,团队通常都是措手不及
DDD 分为战略设计(Strategic Design)和战术设计(Tactical Design)
- 战略设计是高层设计,它帮我们将系统切分成不同的领域,并处理不同领域的关系
- 战术设计,通常是指在一个领域内,在技术层面上如何组织好不同的领域对象
微服务真正的难点并非在于技术实现,而是业务划分,而这刚好是 DDD 战略设计中限界上下文(Bounded Context)的强项
困扰很多人的微服务之间大量相互调用,本身就是一个没有划分好边界而带来的伪命题,靠技术解决业务问题,事倍功半
即便你学了 DDD,知道了限界上下文,也别轻易使用微服,先用分模块的方式在一个工程内,让服务先演化一段时间,等到真的觉得某个模块可以“毕业”了,再去开启微服务之旅
综合运用
新入职一家公司,怎么快速进入工作状态
业务
- 如果你了解了业务,你自己就可以推演出基本的代码结构,反之几乎不可能
- 了解业务时,一定要打起精神,告诉自己,这个阶段,我要了解的只是业务,千万别给我讲技术
技术
系统的技术栈、业务架构、模块划分、项目分层结构、接口的形式(REST/RPC/MQ)
- 这个系统对外提供哪些接口,这对应着系统提供的能力
- 这个系统需要集成哪些外部系统,对应着它需要哪些支持
团队运作
- 需求是从哪来的,产品最终会由谁使用,团队需要向谁汇报,如果有外部客户,日常沟通是怎么安排的
定期的活动,比如,站会、回顾会议、周会,这些不同活动的时间安排是怎样的
- 团队的日常活动,比如,是否有每天的代码评审、是否有内部的分享机制等
- 如果有人很清楚团队现状的话,你可以去请教,也许一天就够了
大多数程序员习惯的工作方式,往往是从细节入手,很难建立起一个完整的图景,常常是“只见树木不见森林”
需要从大到小、由外而内,将要了解的内容层层分解,有了大图景之后,知道自己做的事情到底在整体上处于什么样的位置
在交流的过程中,学习一点”行话“,这会让人觉得你懂行,让你很快得到信任,尽早融入团队
面对遗留系统,你应该这样做
- 构建测试防护网,保证新老模块功能一致;分成小块,逐步替换
- 要想代码腐化的速度不那么快,一定要在软件设计上多下功夫
- 一方面,建立好领域模型,有不少行业已经形成了自己在领域模型上的最佳实践,比如,电商领域
- 另一方面,寻找行业对于系统构建的最新理解,即我们需要知道现在行业已经发展到什么水平了
- 既然选择重写代码,至少新的代码应该按照“最佳实践”来做,才能够尽可能减缓代码腐化的速度
- 改造遗留系统,一个关键点就是,不要回到老路上
我们应该如何保持竞争力
- 我们的焦虑来自于对未来的不确定性,而这种不确定性是一个特定时代加上特定行业的产物
- 有了“一专”,“多能”才是有意义的,这里的“专”不是熟练,而是深入
- 当你有了“一专”,拓展“多能”,就会拥有更宽广的职业道路。比如,我拥有了深厚的技术功底,通晓怎么做软件:
- 如果还能够带着其他人一起做好,就成了技术领导者
- 如果能够分享技术的理解,就有机会成为培训师
- 如果能够在实战中帮助别人解决问题,就可以成为咨询师
- 怎么才能让自己的水平不断提高呢?我的答案是,找一个好问题去解决
- 如果你还什么都不会,那有一份编程的工作就好
- 如果你已经能够写好普通的代码,就应该尝试去编写程序库
- 如果实现一个具体功能都没问题了,那就去做设计,让程序有更好的组织
- 如果你已经能完成一个普通的系统设计,那就应该去设计业务量更大的系
- 在学习区工作和成长
结束语
- 怎么才能有效工作呢
- 拓展自己的上下文,看到真正的目标,更好地对准靶子,比如,多了解用户,才不至于做错了方向;站在公司的层面上,才知道哪个任务优先级更高;站在行业的角度,而不局限于只在公司内成为高手,等等
- 去掉不必要的内容,减少浪费,比如,花时间分析需求,不做非必要的功能;花时间做好领域设计,别围着特定技术打转;花时间做好自动化,把精力集中在编码上,等等
- 一方面,意识上要注意自己工作中无效的部分
- 另一方面,要构建自己关于软件开发的知识体系,这是要花时间积累的