<人月神话>读书笔记

焦油坑

不管是多么强壮的巨兽, 在焦油坑里面即使苦苦挣扎, 仍然逃脱不了死亡的命运.

大部分软件项目都成了焦油坑, 一直在投入时间和人力, 但还是一直delay. 表面上看, 没有任何一个单独的问题会导致苦难, 每个问题都能得到解决, 但当他们相互纠缠和累积到一起的时候, 团队的行动速度会变得越来越慢.

增量迭代的思想已经得到普及, 每次迭代只会产生很小的误差, 一个迭代结束之后再进行下一个, 而且一般产品只会聚焦后面1-2个版本的计划, 所以在这种节奏下, 焦油坑显得并不明显. 但只要有一个稍长远一点的, 明确的无法拆分的目标, 就很容易能体会到焦油坑的感觉. 笔者曾负责一个遗留系统的改进, 迫于进度压力只能先排计划和设置一些里程碑, 这里面的改动范围非常大, 修改范围难以预估, 导致delay了好几次. delay的原因有很多, 过于乐观的估计, 缺乏进度跟进, 团队融合不足, 处理用户反馈和线上问题占用大量的时间等等. 各种原因都会导致进度发生慢性偏离, 误差越来越大, 难以挽回.

车库软件和编程系统产品

我们总能听说某几个硅谷的极客在车库中完成了某个创新性产品, 觉得他们的效率非常高. 但到我们手里一天也做不了太多的需求, 这是为什么呢?

简单的从车库里面出来的软件只能称为程序, 而程序需要转成编程系统产品才能真正的产品, 在市场上产生价值. 但称为编程系统产品的成本也会更高. 程序在变成编程系统产品之前, 需要先转化成编程系统中的构建和编程产品.

编程系统中的构件

编程系统是指在具有一定规范的组件相互协作来完成大型任务的系统, 意味着构件必须有精确地定义, 并且能和其他构件进行对接, 还要满足对接之后整体系统运行的稳定. 这需要程序的3倍工作量.

编程产品

编程产品则要求能被别人运行, 测试, 修改, 拓展. 编程产品需要统一风格, 完善文档, 具有良好的拓展性和可读性, 并具有可用性和稳定性. 这也是需要程序的3倍工作量

工作量越来越大, 开发速度越来越慢

所以要成为一个可用的编程系统产品, 是需要类似车间产品的9倍的工作量的. 以前我在开发一个新项目的时候, 速度非常快, 觉得开发效率非常高, 但迭代了几年之后发现速度慢下来的. 不是因为我能力下降了, 而是有以下几个原因导致的.

  1. 模块与模块之间耦合度会更高, 开发一个功能还需要熟悉例如基础模块等提供的服务, 如果没有还需要对应的人开发.
  2. 团队越来越大, 需要有更详细的设计文档和更精细的规范.
  3. 质量属性越来越重要, 例如性能, 安全性等等, 都需要充分考虑.
  4. ....

所以多预留点时间总是对的. 不是帕金森定律起作用, 而是要真正写好一段程序还是挺花时间的.

程序员的快乐和苦恼

快乐:

  1. 造东西的成就感, 像小时候造积木一样.
  2. 对他人有用, 我们写的程序能满足用户需求, 解决用户的问题.
  3. 将组件组合在一起. 当自己手写一个组件, 在各个服务依赖上, 并且稳定运行的时候, 还是挺有成就感的.
  4. 持续不断地学习. 程序员的工作并不枯燥, 因为每天都在解决新的问题.
  5. 截止易于驾驭且实实在在. 编程不像建房子, 建房子要很多材料和工具, 遵守很多规则. 但编程不用这么复杂, 而且成果是实实在在的能用的软件.

苦恼:

  1. 对完美的要求. 写的程序是机器读的, 必须符合规范, 否则无法运行或者有bug.
  2. 外部约束. 工作之后就面临的资源, 时间等的约束, 没那么自由.
  3. 依赖外部. 工作之后就不是一个人单打独斗了, 而是需要和团队协作, 依赖他人的代码. 当别人的代码是意大利面条时, 会非常难受.
  4. 要找bug. 千里寻他千百度, 蓦然回首灯火阑珊处.

进度安排

在众多软件项目中, 缺乏合理的进度安排是造成项目滞后的最主要原因, 它比其他所有因素加起来还要大. 原因如何

估算技术的不成熟

估算大部分都是靠直觉和经验, 没有较为科学合理的估算技术, 导致不同人的估算差异较大.

乐观的程序员

程序员总是特别乐观, 导致程序员会认为一切都是正常运行, 任务估算和现实一致.

有人提出创造性任务会分为3个阶段, 而经过实现才能得到最清晰的结果. 这三个阶段为构思, 实现, 交流. 构思就是我们接到需求时想象的设计和实现方式, 而实现就是真正的写代码了. 由构思到实现有2个障碍导致相差甚远. 一是构想不连贯, 中间可能会有些没考虑到的坑. 二实现介质的约束, 例如组件不支持这种实现方式, 需要自己重新实现等等.

怯懦的估算

为了满足顾客期望的事件造成很多不合理的进度安排, 在软件领域非常普遍. 在KPI的指引下, 管理者希望能通过压缩时间来产生更高的产出.

程序员除了要屈服于上级意志的原因之外, 还在于对自己估算缺乏信心. 缺乏信心的原因在于行业缺少统一成熟的估算方式, 靠的更多的是经验和直觉, 自然经不起推敲.

最理想的方式是行业里面有一套估算方法, 但在没有之前相信自己的直觉比从期望中派生出来的估算于要靠谱一些.

人月神话

错误的假设人和月能互换, 错误的将进度和工作量相混淆. 用人月衡量一项工作规模的大小是危险和带有欺骗性的神话. 因为它暗示人员数量和时间是可以相互替代的.

人员数量和时间可以互换仅仅适用于下面的情况: 某些任务可以完全分解给参与人员, 且他们之间不用作交流. 这在工厂里面是可能能做到的, 但在软件领域不可能.

当任务有次序上的约束而无法彻底分解时, 加人没什么用.

当任务能被分解, 但需要一定的沟通时, 加人能减少时间, 但人越多, 能减少的时间越少. 因为人越多的沟通成本会越高, 最终抵消了加的人效.

当任务错综复杂时, 加人甚至可能会让交付时间更长.

导致人月无法互换的原因在于

  1. 软件领域难以完全分解成独立的模块, 他们之间会存在耦合, 需要进行协作和沟通.
  2. 新进来的程序员也需要经过熟悉和培训之后才能上手
  3. 加人之后工作任务需要重新安排, 计划重新调整.
  4. 加人之后如果人多了很多, 则组织方式可能都需要改变. 例如10个人和20个人的团队组织方式是不同的, 人太多可能还要分层管理.

Brooks定律: 向落后的软件项目增加人手, 会使进度更加落后.

对测试进度过于乐观

作者提出 1/3 计划, 1/6 编码, 1/4 联调自测, 1/4 系统测试. 测试占了1/2的时间. 因为测试阶段很容易出现进度偏差, 例如bug太多了, 解不完. 而此时已经是项目后期了, 很难再有其他手段去保证不delay. 而且因为发现的比较晚, 导致二次成本会更高. 例如软件发布会的时间都定了, 但项目却delay了.

解决之道

削减任务是唯一的可行的能保证时间不变方式. 另一个方式则是重新安排进度. "不要让小的偏差留着". 要给新进度预留充足的时间, 以确保工作能仔细彻底的完成, 不用重新安排进度.

外科手术团队

有研究发现最好的程序员效率是最差程序员的10倍. 所以才有了10倍程序员的说法. 而好的团队也是差的团队效率的10倍.

我们知道人越少效率肯定会越高, 少数几个人的精英团队可以非常高效的完成任务. 但对于大型任务来说, 这个速度还是太慢了. 例如某个项目要做3年, 有1000个普通程序员投入, 总共需要3000个人年. 但加入精英团队只有10个人, 每个人效率是普通程序员10倍, 那也需要30年才能完成, 还是太慢了. 所以也要考虑人多的情况如何提升效率, 降低沟通带来的成本.

Mills建议大型团队应该先进行拆分, 拆分完每部分分给一个团队负责, 这个团队像做外科手术一样, 由一人主刀, 其他人辅助.

  • 主刀的人称为首席程序员, 负责核心程序的编写
  • 副手作为部分代码的辅助开发, 方案探讨和代码review
  • 管理员则负责管理日常事务, 进度管理, 资源投入, 团队协调等
  • 程序文员负责编写文档
  • 工具维护人员负责维护通用工具, 例如组件开发, 中间件部署等等
  • 测试人员负责功能测试和系统测试

这样的好处是沟通效率比较高, 大家都各有自己的任务, 且不会冲突, 不会冲突则能减少交流的频率. 并且可以维护概念的完整性. 如果不同的功能由不同的人开发, 可能会产生不同的代码风格, 或者对概念理解的不同, 命名会有差异, 降低系统的可维护性. 而只由一个负责则能更好的对这些概念和风格进行统一.

贵族制和民主制

作者认为, 在系统设计中, 概念完整性应该是最重要的考虑因素, 也就是说, 为了反应一系列连贯的设计思路, 宁可让系统省略一些不规则的特性和改进, 也不提倡在系统中包含很多独立和无法协调的好想法. 但前提也是前人做的还不错, 否则真的无法抑制修改的冲动.

为什么要获取概念完整性呢? 因为评估一个系统好不好用, 不只是在于功能的复杂程度和简洁性. 功能太复杂使用的成本会很高, 而功能太少, 太简洁, 可能又无法实现很多功能, 所以评价系统好不好要用易用性来评价. 易用性是功能与概念的复杂程度的比值. 功能在增加的同时要减少概念的复杂程度. 因此, 概念的完整性和设计的一致性就显得很重要.

书中提到的概念完整性更多的是在功能和需求层面的, 而我更宽泛的理解是类似领域驱动设计的中的战略设计, 包括部分统一语言的共识, 粗粒度需求的定义, 限界上下文的划分和软件架构的设计. 这里又不代表是所有统一语言, 统一语言虽然统一了从需求到代码对概念的命名和定义, 而且解决了各方对概念理解有偏差的, 对需求理解起到非常重要的作用. 但在DDD里面, 统一语言的作用范围其实是被限制在一个限界上下文里面, 一个限界上下文一般对应7-8人的小团队, 所以架构师不可能深入每个上下文, 只能是说部分通用或核心的统一语言是需要提前达成共识的.

概念的完整性要求设计来自一个人, 或少数有默契的人, 而进度压力则需要很多人, 因此可以使用架构和实现分离的方式去应付. 由一个或几个人定义架构, 其他人则基于此架构去实现功能. 虽然普通实现人员也有自己的想法, 但为了保证概念的一致性, 可能需要放弃一些好想法和好特性.

架构师对外部约束的编制和程序员对功能的的实现都需要创造性的设计工作, 架构师很大程度决定系统易用性, 而程序员则决定成品的性能. 架构师决定系统需要的架构约束, 而程序员负责在约束下实现系统. 架构约束实际上是增强了开发小组的创造性, 让他们更少的讨论需求而关注实现. 架构的设计和实现也并不是严格串行的, 部分阶段可以并行实现.

第二系统效应

第二系统效应是指做第一个系统的时候设计者会比较谨慎, 他们倾向于经联合简洁, 不会添加太多功能, 而因为对第一个系统比较自信, 则容易过度设计第二个系统, 向系统添加很多修饰功能和想法. 但很多新想法可能会带来非常多的不确定性, 导致项目产生风险. 架构师需要对第二系统保持警惕, 有意识关注系统危险部分, 并加以自我约束.

大型团队的交流

  1. 非正式沟通. 沟通方式比较灵活, 效率比较高.
  2. 常规项目会议, 最好是定时, 简短, 做到信息和风险同步.
  3. 工作手册, 对于大型团队, 工作手册可以提高效率.

提升大型团队沟通效率的方式就是通过人力划分和限定职责范围来减少沟通. 文中提到团队有2种重要角色.

  1. 产品负责人: 负责组建团队, 划分工作及定时跟进进度. 这意味着它主要工作是与团队的外部进行向上沟通和水平沟通, 并建议团队内部的沟通和报告方式. 最后他确保进度目标的实现, 并根据环境的变化调整资源和团队架构.
  2. 技术主管: 他对设计进行构思, 识别系统的子部分, 指明需求, 勾画出它的内部结构. 他提供整个设计的一致性和概念完整性, 并控制系统的复杂程度.

产品负责人和技术主管分工不同, 在不同团队里面也有不同的实现方式.

  1. 产品负责人和技术主管是同一个人. 这种适合小型团队, 否则两个角色都做不好.
  2. 产品负责人为主, 技术主管为辅. 这种用的不多, 但适合项目经理使用并不擅长管理的技术天才完成工作.
  3. 技术主管为主, 产品负责人为辅.

大型项目如何评估时间

小型项目是比较容易估算时间的, 但从小型项目数据推导出大型项目的数据是没有意义的, 因为小型团队和大型团队的数据并不是线性增长的, 而是指数级上升的. 编程工作是程序规模的幂函数. 生产率会根据任务本身的复杂度和困难程度表现出显著差异.

对于软件项目管理来说, 发生灾难导致项目延期是比较好处理的, 但每个细微的进度落后是难以识别, 不容易防范和难以弥补的. 那如何去控制进度呢, 首先要定义进度表. 并且进度表上面需要定义里程碑.

里程碑必须是具体的, 特定的, 可度量的事件, 例如90%的进度, 计划完毕这种就不是好的里程碑. 因为里程碑是百分百事件, 例如过完某些模块的用例, 设计方案进行了评审等. 好的里程碑对团队来说实际上是一项可以用来向项目经理提出合理需求的服务. 而模糊的里程碑是难以处理的负担. 当里程碑没有正确的反应损失的时间, 并对大家形成误导, 以致事态无法挽回时, 它会彻底打击小组的士气, 慢性进度偏离也是士气杀手.

慢性进度偏离会导致项目经理乐观的认为偏差是可以弥补的, 以至于没及时同步给老板. 这是因为项目经理的利益和老板的利益是冲突的, 当老板发现项目经理处理不当时, 可能会降低项目经理的威信或者打乱项目经理的计划, 因此当项目经理觉得还有机会能处理问题的时候, 一般不会向上报告.

因此需要有方法解决这个问题

  1. 减少角色的冲突和鼓励状态共享. 老板不能反应过度, 或者对项目经理产生质疑. 另外可以安排常规项目会议同步项目状态.
  2. 猛的掀开地毯. 在某些里程碑节点上面同步问题.

软件开发中的主要问题和次要问题

所有软件活动包括了根本任务和次要任务.

  • 根本任务: 即打造构成抽象软件实体的复杂概念结构.
  • 次要任务: 使用编程语言表达这些抽象实体, 在空间和时间的约束下将他们映射成机器语言.

在软件开发中, 单纯有需求, 有业务流程是无法直接映射到计算机可运行的代码的. 这需要2部转换, 一部分是转换成算法和数据结构, 另一部分是用过编程语言实现这些算法和数据结构. 例如我要设计一个看病系统, 常规的看病流程是病人挂号, 然后看医生, 最后药房拿药. 首先需要转换成抽象的概念, 例如病人, 看病编号, 医生, 药, 挂号, 诊断等等, 才能到写代码的实现级别.

作者认为软件开发中最困难的部分是规格说明, 设计和测试这些概念上的结构, 而不是对概念进行表达和实现逼真程度进行验证.

软件开发没有银弹, 而即使次要问题占了开发的99%的时间, 我们能把次要问题优化到极致, 也难以实现数量级效率的提升. 因为软件系统中有几个内在特征, 这些导致了主要问题难以解决.

  1. 复杂度: 没有重复的模块, 元素之间的交互也非常多.
  2. 一致性: 大型软件有太多人开发了, 而且经历的时间非常长, 变化特别多, 导致设计缺乏一致性.
  3. 可变性: 不仅需求在变, 技术也在变, 我们每时每刻都在写遗留系统.
  4. 不可见性: 只能通过UML等形式对代码进行可视化, 而且需要从多角度才能描述出完整系统.

解决根本问题可能的方法

  1. 购买现有软件是最可能彻底解决问题的方式. 一方面比起自行开发便宜很多, 并且马上就用得上. 一方面是已经出现了很多通用工具, 例如word, excel, 这些工具非常灵活, 能处理很多问题, 且使用的门槛很低.
  2. 需求精炼和快速原型. 大部分需求其实用的人非常少, 与其在研发流程提升效率, 不如从入口处提升需求的质量. 快速原型也是基于精益的思想, 看上去丢弃原型的方式是一种浪费, 但比起整个系统都开发完, 才发现无法满足需求, 这点浪费还是值得的.
  3. 增量开发. 好的产品都是逐步完善的, 通过不断试验来获取新的认知, 从而往相对正确的方向发展. 而且通过多次迭代也能提升团队士气.
  4. 卓越的设计人员. 卓越和普通的差异接近一个数量级, 卓越的设计人员能产生更快, 更小, 更简单, 更干净, 实现代价更小的成果.

关注质量

人月更关注团队的生产力, 但很少提到质量对生产力的影响. Jones提出, 关注质量, 生产率自然随之提高, 很多代价高昂的后续项目投入了大量时间和精力来寻找和修复规则说明, 设计和实现的错误.

总结

<人月神话> 提到了很多大型项目会遇到的问题, 这点在当下也没过时. 虽然敏捷思想已经深入人心, 但当你是项目经理, 且遇到不确定性比较大的版本的时候就会深有感触, 乐观的估计, 进度的难以衡量和把控, 延期导致团队的失落都可能造成项目的失败. 这本书可能不会让你少踩坑, 因为不痛过是没有感觉的, 但踩坑之后你会发现不只你一个人会遇到这些问题, 这些是通用的问题, 唯有多花时间去思考这些问题, 才能提升成功的概率.

相关推荐
NiNg_1_23441 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
customer084 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml45 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
小码编匠6 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#