代码整洁之道
程序员的职业素养
专业主义
清楚你要什么
担当责任
首先,不行损害之事
- 不要破坏软件功能
- 让QA找不出任何问题
- 要确信代码正常运行
- 自动化QA
- 不要破坏结构
职业道德
- 了解你的领域
- 专业软件开发人员必须精通事项
- 设计模式
- 设计原则
- 方法
- 实践
- 工件
- 专业软件开发人员必须精通事项
- 坚持学习
- 练习
- 业精于勤。真正的专业人士往往勤学苦干,以求得自身技能的纯属精炼。
- 合作
- 学习的第二个最佳方法是与人合作
- 辅导
- 想迅速牢固地掌握某些事实和观念,最好的办法就是与你负责指导的人交流这些内容。这样,传道受业的同时,导师也会从中受益。
- 了解业务领域
- 每位专业软件开发人员都有义务了解自己开发的解决方案所对应的业务领域
- 与雇主/客户保持一致
- 雇主的问题就是你的问题
- 谦逊
说“不”
能就是能,不能就是不能。不要说“试试看”
因为只有敢于说“不”,才能真正做成一些事情
对抗角色
- 最关键的是要找到那个共同目标,而这往往有赖于协商
“为什么”重要吗
- 有时候解释很重要,但是事实更重要
高风险时刻
- 最要说“不”的是那些高风险的关键时刻。越是关键时刻,“不”字就越具价值
要有团队精神
- 具备团队精神,意味着恪尽职守,意味着当其他队员遭遇困境时你要援手相助
- 有团队精神的人会频繁与大家交流,会关心队友,会竭力做到尽职尽责
说“是”的成本
- 运作良好的团队的经理和开发人员,会相互协商,直至达成共同认可的行动方案
- 有时候,获取正确决策的唯一途径,便是勇敢无畏地说出“不”字
说“是”
承诺用语
- 三步骤
- 口头上说自己将会去做
- 心里认真对待做出的承诺
- 真正付诸行动
- 识别“缺乏承诺”的征兆
- 包含的用词和短语
- 需要/应当
- 希望/但愿
- 让我们
- 包含的用词和短语
- 真正的承诺听起来是怎样的
- 你对自己将会做某件事做了清晰的事实陈述,而且还明确说明了完成期限。那不是指别人,而是说的自己。你谈的是自己会去做的一项行动,而且,你不是可能去做,或是可能做到,而是必须做到
- 没能做到“言必信,行必果”的一些原因
- 之所以没成功,是因为我寄希望于某某去做这件事
- 之所以没成功,是因为我不太确信是否真能完成得了
- 之所以没成功,是因为有些时候我真的无能为力
学习如何说“是”
- “试试”的另一面
- “试试”是“可能做得到,也可能做不到”的意思,但是问题往往需要得到明确的回答,“能”或是“不能”,而不是“试试”这种含糊不清的答案
- 坚守原则
结论
- 专业人士不需要对所有请求都回答“是”。不过,他们应该努力寻找创新的方法,尽可能做到有求必应。当专业人士给出肯定回答时,他们会使用正式的承诺,以确保各方能明白无误地理解承诺的内容
编码
要精熟掌握每项技艺,关键都是要具备“信心”和“出错感知”能力
做好准备
- 首先,代码必须能够正常工作
- 代码必须能够帮你解决客户提出的问题
- 代码必须要能和现有系统结合得天衣无缝
- 其他程序员必须能读懂你的代码
流态区
- 高效率状态,这种状态通常称为“流态”,也叫“流态区”
- 进入流态区可以敲出更多的代码,但是放弃了顾全全局,后面有可能会需要重新审视这些代码
- 避免进入流态区
阻塞
- 遇到什么代码也写不出来,可以尝试进行结对编程
调试
- 不管是否采纳TDD或其他一些同等效果的实践,衡量你是否是一名专业人士的一个重要方面,便是看你是否能将调试实践尽量降到最低。绝对的零调试时间是一个理想化的目标,无法达到,但要将之作为努力方向
- 医生不喜欢重新打开病人的胸腔去修复此前犯下的错误。律师不喜欢重新接手此前搞砸的案子。经常重新返工的医生或律师会被认为不专业。同样,制造出许多bug的软件开发人员也不专业
保持节奏
- 软件开发是一场马拉松,而不是短跑冲刺
进度延迟
- 期望
- 一个特性完不成,就不要让其他任何人对此抱有期望
- 盲目冲刺
- 不要经受不住诱惑盲目冲刺
- 必须明白告诉老板、团队和利益相关方,让他们不要抱有可以靠冲刺完成特性的这种期望
- 加班加点
- 需满足三个条件
- 你个人能挤出这些时间
- 短期加班,最多加班两周
- 你的老板要有后备预案,以防万一加班措施失败
- 需满足三个条件
- 交付失误
- 在程序员所能表现的各种不专业行为中,最糟糕的是明知道还没有完成任务却宣称已经完成
- 定义“完成”
- 可以通过创建一个确切定义的“完成”标准来避免交付失误
帮助
- 编程并非易事
- 编程很难,事实上,仅凭一己之力无法写出优秀的代码。即使你的技能格外高超,也肯定能从另外一名程序员的思考与想法中获益
- 帮助他人
- 互相帮助是每个程序员的职责所在
- 作为专业人士,要以能够随时帮助别人为荣
- 接受他人的帮助
- 如果有人向你伸出援手,要诚挚接受,心怀感激地接受帮助并诚意合作
- 为了能够实现高效编程,好的协作至为重要
- 辅导
- 辅导缺乏经验的程序员是那些经验丰富的程序员的职责
- 培训课程无法替代,书本也无法替代。除了自身的内驱力和资深导师的有效辅导之外,没有东西能将一名年轻的软件开发人员更快地提升为敏捷高效的专业人士
- 因此,再强调一次,花时间手把手地辅导年轻程序员是资深程序员的专业职责所在。同样道理,向资深导师寻求辅导也是年轻程序员的专业职责
测试驱动开发
TDD的三项法则
- 在编好失败单元测试之前,不要编写任何产品代码
- 只要有一个单元测试失败了,就不要再写测试代码;无法通过编译也是一种失败情况
- 产品代码恰好能够让当前失败的单元测试成功通过即可,不要多写
TDD的优势
- 确定性
- 如果这些测试全部通过,我就确信它可以随时交付
- 缺陷注入率
- 勇气
- 拥有一套值得信赖的测试,便可完全打消对修改代码的全部恐惧
- 当程序员不再惧怕整理代码时,他们便会动手整理!整洁的代码更易于理解,更易于修改,也更易于扩展
- 文档
- 单元测试即是文档
- 设计
- 为了编写测试,你必须找出将这个函数和其他函数解耦的办法
- 测试先行的需要,会迫使你去考虑什么是好的设计
- 专业人士的选择
TDD的局限
- 即使做到了测试先行,仍有可能写出糟糕的代码。没错,因为写出的测试代码可能就很糟糕
练习
专业人士都需要通过专门训练提升自己的技能,无一例外
自身经验的拓展
- 开源
- 保持不落伍的一种方式是为开源项目贡献代码,就像律师和医生参加公益活动一样
- 关于练习的职业道德
- 职业程序员用自己的时间来练习
结论
- 无论如何,专业人士都需要练习
- 他们这么做,是因为他们关心自己能做到的最好结果
- 更重要的是,他们用自己的时间练习,因为他们知道保持自己的技能不落伍是自己的责任,而不是雇主的责任
验收测试
专业开发人员既要做好开发,也要做好沟通
需求的沟通
- 过早精细化
- 做业务的人和写程序的人都容易陷入一个陷阱,即过早进行精细化
- 不确定原则
- 在工作中,有一种现象叫观察者效应,或者不确定原则。每次你向业务方展示一项功能,他们就获得了比之前更多的信息,这些新信息反过来又会影响他们对整个系统的看法
- 预估焦虑
- 需求是一定会变化的,所以追求那种精确性是徒劳的
- 评估可以而且必须基于不那么精确的需求,这些评估只是评估而已
- 迟来的模糊性
- 解决分歧的方案,是寻找各方都同意的关于需求的表述,而不是去解决争端
- 即便客户与程序员当面沟通,也可能出现因语境产生的模糊
验收测试
- “完成”的定义
- 专业开发人员的“完成”只能有一个含义:完成,就是完成
- 完成意味着所有的代码都写完了,所有的测试都通过了,QA和需求方已经认可。这,才是完成
- 沟通
- 验收测试的目的是沟通、澄清、精确化
- 开发方、业务方、测试方对验收测试达成共识,大家都能明白系统的行为将会是怎样
- 各方都应当记录这种准确的共识
- 自动化
- 验收测试都应当自动进行。原因很简单:要考虑成本
- 额外工作
- 写大量测试根本不是什么额外工作。写这些测试是为了确定系统的各项指标符合要求
- 验收测试什么时候写,由谁来写
- 在理想状态下,业务方和QA会协作编写这些测试,程序员来检查测试之间是否有冲突或矛盾
- 通常,业务分析员测试“正确路径”,以证明功能的业务价值
- QA则测试“错误路径”、边界条件、异常、例外情况,因为QA的职责是考虑哪些部分可能出问题
- 遵循“推迟精细化”的原则,验收测试应该越晚越好,通常是功能执行完成的前几天。在敏捷项目中,只有在选定了下一轮迭代或当前冲刺所需要的功能之后,才编写测试
- 开发人员的角色
- 开发人员有责任把验收测试与系统联系起来,然后让这些测试通过
- 测试的协商与被动推进
- 身为专业开发人员,与编写测试的人协商并改进测试是你的职责
- 身为专业开发人员,你的职责是协助团队开发出最棒的软件。也就是说,每个人都需要关心错误和疏忽,并协力改正
- 验收测试和单元测试
- 验收测试不是单元测试。单元测试是程序员写给程序员的,它是正式的设计文档,描述了底层结构及代码的行为。关心单元测试结果的是程序员而不是业务人员
- 验收测试是业务方写给业务方的(虽然可能最后是身为开发者的你来写)。它们是正式的需求文档,描述了业务方认为系统应该如何运行。关心验收测试结果的是业务方和程序员
- 图形界面及其他复杂因素
- 持续集成
- 立刻中止
- 保持持续集成系统的时刻运行是非常重要的
- 持续集成不应该失败,如果失败了,团队里的所有人都应该停下手里的活,看看如何让测试通过
- 立刻中止
结论
- 交流细节信息是件麻烦事
- 要解决开发方和业务方沟通问题,我所知道的唯一有效的办法就是编写自动化的验收测试
测试策略
QA应该找不到任何错误
- QA也是团队的一部分
- 需求规约定义者
- 特性描述者
自动化测试金字塔
- 单元测试
- 这些测试由程序员使用与系统开发相同的语言来编写,供程序员自己使用
- 组件测试
- 系统的组件封装了业务规则,因此,对这些组件的测试便是对其中业务规则的验收测试
- 组件测试差不多可以覆盖系统的一半。它们更主要测试的是成功路径的情况,以及一些明显的极端情况、边界状态和可选路径
- 大多数的异常路径是由单元测试来覆盖测试的
- 在组件测试层次,对异常路径进行测试并无意义
- 集成测试
- 这些测试只对那些组件很多的较大型系统才有意义
- 集成测试是编排性测试。它们并不会测试业务规则,而是主要测试组件装配在一起时是否协调。它们是装配测试,用以确认这些组件之间已经正确连接,彼此间通信畅通
- 系统测试
- 这些测试是针对整个集成完毕的系统来运行的自动化测试,是最终的集成测试
- 人工探索式测试
- 覆盖率并非此类测试的目标
- 探索式测试不是要证明每条业务规则、每条运行路径都正确,而是要确保系统在人工操作下表现良好,同时富有创造性地找出尽可能多的“古怪之处”
结论
- TDD很强大,验收测试是表达和强化需求的有效方式
- 开发团队要和QA紧密协作,创建由单元测试、组件测试、集成测试、系统测试和探索式测试构成的测试体系
- 应该尽可能频繁地运行这些测试,提供尽可能多的反馈,确保系统始终整洁
时间管理
会议
- 拒绝
- 受到邀请的会议没有必要全部参加
- 领导的最重要责任之一,就是帮你从某些会议脱身。好的领导一定会主动维护你拒绝出席的决定,因为他和你一样关心你的时间
- 离席
- 如果会议让人厌烦,就离席
- 选个合适的机会商量如何离席,并非不专业的做法
- 确定议程与目标
- 为了合理使用与会者的时间,会议应当有清晰的议程,确定每个议题所花的时间,以及明确的目标
- 立会
- 到场人依次回答3个问题
- 我昨天干了什么?
- 我今天打算干什么?
- 我遇到了什么问题?
- 到场人依次回答3个问题
- 迭代计划会议
- 迭代计划会议用来选择在下一轮迭代中实现的开发任务
- 会议召开前必须完成两项任务
- 评估可选择任务的开发时间
- 确定这些任务的业务价值
- 会议的节奏应该足够快,简明扼要地讨论各个候选任务,然后决定是选择还是放弃
- 迭代回顾和DEMO展示
- 争论/反对
- 技术争论很容易走入极端。每一方都有各种说法来支持自己的观点,只是缺乏数据。在没有数据的情况下,如果观点无法在短时间(5~30分钟)里达成一致,就永远无法达成一致。唯一的出路是,用数据说话
- 如果争论必须解决,就应当要求争论各方在5分钟时间内向大家摆明问题,然后大家投票。这样,整个会议花的时间不会超过15分钟
注意力点数
- 选择注意力点数充裕的时候编程,在注意力点数匮乏时做其他事情
- 睡眠
- 一觉醒来,注意力点数是最充裕的
- 咖啡因
- 对有些人来说,适量的咖啡因可以帮他们更有效地使用注意力点数
- 恢复
- 在你不集中注意力的时候,注意力点数可以缓慢恢复
- 肌肉注意力
- 搏击、太极、瑜伽之类体力活动使用的注意力是不同的
- 定期训练肌肉注意力,可以提升心智注意力的上限
- 输入与输出
- 关于注意力,另一重点是平衡输入与输出
时间拆分和番茄工作法
要避免的行为
- 优先级错乱
- 专业开发人员会评估每个任务的优先级,排除个人的喜好和需要,按照真实的紧急程度来执行任务
死胡同
- 专业开发人员不会执拗于不容放弃也无法绕开的主意
- 保持开放的头脑来听取其他意见,所以即使走到尽头,他们仍然有其他选择
泥潭
- 比死胡同更糟的是泥潭
- 在泥潭中继续前进的危害是不易察觉的
- 发现自己身处泥潭还要固执前进,是最严重的优先级错乱
结论
- 专业开发人员会用心管理自己的时间和注意力
预估
什么是预估
- 问题在于,不同的人对预估有不同的看法。业务方觉得预估就是承诺。开发方认为预估就是猜测。两者相同迥异
- 承诺
- 承诺是必须做到的
- 承诺是关于确定性的
- 预估
- 预估是一种猜测。它不包含任何承诺的色彩。它不需要做任何约定。预估错误无关声誉
- 暗示性承诺
- 专业开发人员能够清楚区分预估和承诺
- 只有在确切知道可以完成的前提下,他们才会给出承诺
- 专业开发人员会小心避免给出暗示性的承诺。他们会尽可能清楚地说明预估的概率分布
PERT
- O:乐观预估
- N:标称预估
- P:悲观预估
预估任务
- 德尔菲法
- 亮手指
- 规划扑克
- 关联预估
- 三元预估
大数定律
- 把大任务分成许多小任务,分开预估再加总,结果会比单独评估大任务要准确很多
结论
- 专业开发人员懂得如何为业务人员提供可信的预估结果,以便做出计划
- 专业开发人员一旦做了承诺,就会提供确定的数字,按时兑现
- 对需要妥善对待的预估结果,专业开发人员会与团队的其他人协商,以取得共识
压力
避免压力
- 承诺
- 有时有人会代我们做出承诺。比如业务人员可能在没有事先咨询我们的情况下就向客户做出了承诺。发生这种事情时,出于责任感我们必须主动帮助业务方找到方法来兑现这些承诺,但是一定不能接受这些承诺
- 其中的差别至关重要。专业人士总会千方百计地帮助业务方找到达成目标的方法,但并不一定要接受业务方代为做出的承诺。
- 最终,如果我们无法兑现业务方所做出的承诺,那么该由当时做出承诺的人来承担责任
- 保持整洁
- 快速前进确保最后期限的方法,便是保持整洁
- 脏乱只会导致缓慢!
- 让系统、代码和设计尽可能整洁,就可以避免压力
- 危机中的纪律
- 如果在危机中依然遵循着你守持的纪律,就说明你确实相信那些纪律
- 当困境降临时,也不要改变行为。如果你遵守的纪律原则是工作的最佳方式,那么即使是在深度危机中,也要坚决秉持这些纪律原则
应对压力
- 不要惊慌失措
- 沟通
- 让你的团队和主管知道你正身陷困境之中
- 依靠你的纪律原则
- 当事情十分困难时,要坚信你的纪律原则
- 寻求帮助
结论
- 应对压力的诀窍在于,能回避压力时尽可能地回避,当无法回避时则勇敢直面压力
协作
程序员与雇主
- 专业程序员的首要职责是满足雇主的需求
- 专业程序员会花时间去理解业务
程序员与程序员
- 代码个体所有
- 从技术角度来看,这无疑是一场灾难
- 协作性的代码共有权
- 专业开发人员是不会阻止别人修改代码的
- 结对
- 最有效率且最有效果的代码复查方法,就是以互相协作的方式完成代码编写
结论
- 也行我们不是因为通过编程可以和人互相协助才选择从事这项工作的。但真不走运,编程就意味着与人协作。我们需要和业务人员一起工作,我们之间也需要互相合作
团队与项目
只是简单混合吗
- 有凝聚力的团队
- 形成团队是需要时间的
- 有凝聚力的团队通常有大约12名成员
- 7名程序员
- 2名测试人员
- 2名分析师
- 1名项目经理
- 发酵期
- 6个月至1年
- 团队和项目,何者为先
- 专业的开发组织会把项目分配给已形成凝聚力的团队,而不会围绕着项目来组建团队
- 如何管理有凝聚力的团队
- 每个团队都有自己的速度
- 团队的速度,即是指在一定时间段内团队能够完成的工作量
- 项目承包人的困境
- 反对意见:这会让项目承包人失去些安全感和权力
- 组建和解散团队只是人为的困难,公司不应受到它的束缚。如果公司在业务上认为一个项目比另一个项目的优先级更高,应该要快速重新分配资源
结论
- 团队比项目更难构建。因此,组建稳健的团队,让团队在一个又一个项目中整体移动共同工作是较好的做法
辅导、学徒期与技艺
失败的学位教育
- 在学校中所学的内容和在工作中发现的实际需要,这两者之间通常会有巨大的差异
辅导
- 真正的“老师”、“大师”或是“导师”
- 能够深入浅出地指导我跨过其中的沟沟壑壑
- 我可以在给他打下手完成一些小任务时观察他的工作方式
- 他会对我的工作进行审查,指导我的早期工作
- 他会专门教导我建立正确的价值观和反思内省的习惯
学徒期
- 软件学徒期
- 大师
- 他们是那些已经领到过多个重要软件项目的程序员
- 熟练工
- 他们还处在受训期中,不过已能胜任工作,而且精力充沛。在职业生涯的当前阶段,他们将会学习如何在团队中卓越工作和成为团队的领导者
- 学徒/实习生
- 毕业生会从学徒这一步开始他们的职业生涯。学徒没有“自治权”,他们需要在熟练工的紧密督导下工作
- 学徒期至少应持续一年
- 大师
- 现实情况
- 上述这些描述是假设的一种十分理想化的状况
- 观念上最大的差别在于,专业主义价值观和技术敏锐度需要进行不断的传授、培育、滋养和文火慢炖,直至其完全渗入文化当中
- 我们当前的做法之所以传承无力,主要是因为其中缺失了资深人士辅导新人向其传授技艺的环节
技艺
- 技艺是工匠所持的精神状态
- 技艺的“模因”(meme)中包含着价值观、原则、技术、态度和正见
- 技艺模因经由口口相传和手手相承而来,需要由资深人士向年轻学徒殷勤传授,然后再在学徒之间相互传播
- 觉者觉人
- 你无法说服别人成为一名匠者,你无法说服他们去接受技艺模因
- 只要技艺模因可以被人观察到,它便具有传染性。因此只需让技艺模因可以被他人观察到即可
- 你自己首先要成为表率。你自己首先要成为能工巧匠,想人们展示你的技艺。然后,将剩余的事情交给技艺模因的自然运行之道即可
结论
- 学校并不会也无法传授作为一名编程匠者所需掌握的原则、实践和技能
- 指引下一代软件开发人员成熟起来的重任无法寄希望于大学教育,现在这个重任已经落到了我们肩上