HOME/Articles/

编程的方法论,读『人月神话』

Article Outline

这本书的结构比较松散,可以看成是一系列博文组合而成,但是它要表达的主题却非常简单明确:

如何在计划好的时间内完成软件项目开发。

<!--more-->

如何规划时间

如何规划时间和控制项目进度其实就是项目管理。软件系统开发是个非常复杂的过程,当软件的功能越来越多,项目越来越复杂,交互成指数级上升,代码和接口中会隐藏很多难于发现的 bug。所以在软件规划当中,项目进度规划会变得非常困难。

通常来说,项目进度会比预想的要推迟,主要是因为:

  • 对设计过于乐观。 软件实现由于所要依赖的条件通常只有计算机,所以设计人员自然而然会觉得只要思路设计好了就会顺利进行下去。但是通常思路设计会有缺陷,从而造成流程不通或者出现兼容性问题要花费大量时间去解决。
  • 觉得可以通过增加人手来加快进度。 的确存在人员和时间的换算关系,但是这种关系非常依赖项目的可分解程度和分解后的衔接和沟通成本。通常来说是斜率为负的曲线,甚至是负转正的曲线。这意味着在人员缺乏的情况下增加人手可以迅速加快项目进度,人员本来足够时临时加派人手对项目进度的影响非常有限,甚至会拖慢进度。

人月

人月指的是人员和时间是不能进行互换的,2 个人花费 5 个月的开发项目并不等于 5 个人花费 2 个月就可以完成项目。一方面是因为人员的差异性,因为对项目的熟悉程度,对编程的熟悉程度不同,一个熟练的人可能是另外一个人的开发效率的 10 倍,按平均水平来算,可以把一个熟练的人当成 5 个人月,另一个不熟悉的人当成 0.3 个人月;还有一方面的原因,人员越多,人员直接的沟通成本会越来越大,随着项目的进行,后来加入的人员因为不熟悉业务会更加拖慢项目的进度。

项目要在可把控范围内,必须尽可能减少人员之间的沟通成本,因为问题总是出现在各个子系统的衔接处,因此:

  • 参与人员要有详细的开发手册,降低人员的沟通成本
  • 独断比民主好
  • 整体设计由架构师设计而不是各自设计再拼装
  • 项目开始对人员有合理的规划,不是中途为赶进度增加人手
  • 进度评估需要持续进行和调整

那么怎么根据减少沟通成本来设计团队架构呢?借鉴手术团队的搭建。外科手术是不允许出现错误的,对进度的把控也非常严格(拖延时间会出人命的),但是也会对具体的进展情况做出调整,适当延长交期或者适当减少功能。

外科手术式团队

手术团队的构成是怎么样的呢?

  • 外科医生。手术团队的核心人物是外科医生,负责手术实施每一步骤的整理,给其他协助人员交代需要做什么,直接作用在病人身上的所有操作,整理尤其需要注意的事项。
  • 副手。副手理解外科医生的所有工作,在外科医生分不开身的时候他能准确的做出相应的反应,副手也有监督作用。
  • 相关领域的顾问。一般是教授等理论水平很高或者专业领域很强的专家。负责在某些棘手的问题上给建议。
  • 其他助手

应用到项目构成上,一个专业团队主要包含:

  • 架构师。负责整个业务的文档整理,整理架构逻辑的设计,所有核心逻辑的代码实现。
  • 助理架构师。负责补充架构师的工作。
  • 行业专家。
  • 其他工程师。负责根据架构师的文档要求编写非核心业务代码,工具设计。
  • 产品规划人员。产品规划负责设计详尽的产品原型,并对所有的逻辑分叉做出说明,这有利于架构师理清思路。
  • 外围辅助人员。

为了缩减项目设计所需要的时间,减小实验和试错等成本,软件设计师总是倾向于寻求一种一劳永逸的设计和实现方式,即追求所谓的『银弹』。

没有银弹

作者花了大量的篇幅来讨论有没有银弹的问题,通过很多年许多公司的总结得出没有银弹的结论。所有的软件工程要解决的根本任务是:

构造一个复杂的抽象系统结构,解决实际生活的需求。

而通过编程语言来编码,将系统的设计转化成机器能够执行的指令只是任务的次要部分,次要部分却占据了软件工程的大部分时间和精力,软件工程越增长,就会出现越来越多的交互,使得复杂度成指数级上升。这跟使用什么编程语言,使用什么自动化工具没有关系,这种复杂性会一直存在,所以银弹很难存在。不得不说,随着高级编程语言和现代工具的发展,编程变得越来越简单,可正是这种简单,让编程人员离解决系统的复杂性问题越来越远,他们在意的是使用的编程语言是否解决了部分功能,而对系统设计如何解决了复杂性问题毫无见解。

使用什么编程语言

有人说 python 好,有人说 Go 也简单,更有的人痴迷于 Elixir 等小众语言鄙视其他的低级语言,可是选用何种语言对理解软件复杂性问题上似乎毫无帮助。特定的语言在解决特定的问题上可能会更加方便,所以在选用何种语言时最好对这种语言是如何解决其他语言很难解决的问题有根本性认识,否则就选自己最熟悉的语言吧。企业在选用何种语言时要考虑的因素主要有:

  • 业务需要,是否高并发,高可靠
  • 主要技术负责人熟悉
  • 招人方便,是否大众
  • 成熟,社区活跃,坑少

OOP 面向对象

面向对象编程喜欢嘲笑面向流程编程,觉得代码可读性太差。函数式编程又看不起面向对象编程,觉得面向对象有点过度设计的意思。面向对象也好,面向函数也好,其实都是解决复杂性问题,是对问题的一种抽象手段,都是相当于一个盒子,把逻辑和变量存在里面,本质上他们并无差别。但是面向对象确实让类型调用更加简单,函数式抽象能够解决再复杂的问题。只要能方便的解决复杂性问题,面向对象和函数式其实并不那么重要。

项目的具体实现流程

项目成立以后,产品经理根据创意规划功能和产品原型,技术负责人根据产品经理的原型来设计整个系统的架构和实现,并写出具体的开发手册,说明接口和模块作用。根据复杂程度分工,开发人员进行开发和维护。由于产品是动态迭代的,所以系统架构应该设计成可扩展的以应对未来的需求变更。第一版设计就应该为变化而设计,如果设计没有扩展性,产品一旦迭代,又要推倒重来,会造成巨大的浪费而拖慢项目的进展。

如何避免 bug

软件工程的实施其实就是要解决系统设计的复杂性问题。任何一个语句潜在的逻辑漏洞都可能造成整个系统的奔溃,花 10 个小时编程有可能 8 个小时在处理系统报告出来的各种异常和错误,浪费了非常多的时间。

设计比编码更重要

为了解决这种时间大面积花在处理系统错误的情况,在编程开始之前就要对程序的运行做非常严谨的设计和分析,各种异常处理可以分类细致,精确定位。设计如果做得详细,一般可以做出伪代码结构,相当于程序员自己的操作手册,实际写起代码来可以参看设计和伪代码,思路会更加清晰。

系统级的 bug 更多时候是出现在模块之间衔接的地方,一方面是因为模块是为不同的业务设计,衔接的地方更容易出现逻辑漏洞,另一方面是由于不同的模块是由不同的人员负责,很难领会对方的意图。

程序主体应该由一人完成

这个人通常就是技术负责人,一个人完成程序的框架可以避免多人协作产生的沟通成本,避免很多系统级别的程序漏洞。

不是说让一个人去完成所有的逻辑处理,技术负责人可以根据自己的规划完成整个逻辑业务的梳理,包括编写类和函数并配以文档说明,完成程序最复杂和最核心的业务,包括支付系统等不容出错的。对于功能简单,交互少的模块交给其他开发人员。

这样不仅是避免了模块衔接漏洞,而且其他开发人员也可以通过已经编好的框架更加容易的熟悉业务流程。为什么大多数企业还是采用模块分工的模式呢?

领导模式和分工模式

领导模式对技术负责人的要求很高,并且非常累。从企业组织来讲,大多数企业这个位置一般是所谓的管理人员,他们可能自己不想写太多代码,如果自己都完成了,招其他人员干嘛呢?其次,由于组织结构的问题,技术领导可能还要负责其他非技术工作,不管是自己揽的还是公司给的,都完成技术领导无法集中精力在技术问题的解决上。

分工模式对企业来说有个好处,技术人员或多或少无法看见其他人的代码,减少了代码泄露的可能性。但是他无法让技术人员在编码的同时熟悉整个业务流程,极大的增大了代码出现 bug 的风险。

我个人比较赞成以领导模式为主,根据项目的交互复杂度分工。

操作手册

在飞机上常备有飞行检查单,当飞机出现故障的时候,机组人员可以依照检查单进行逐项检查。再优秀和有经验的人在复杂环境下很难考虑到所有的情况,软件设计对开发人员提出了更高的要求,不允许遗漏任何一个逻辑判断,因为任何逻辑判断都可能造成系统崩溃。如果事先备有操作手册会让开发人员在思维过程中思路更加清晰,提高开发效率。

这是一本启发思考的书,可能对编程并无具体指导意义,但是会让你明白编写的代码是为什么服务,编程是为了解决系统复杂性的抽象过程,仅此而已。