软件开发中的上游思维

最近学习了万维钢老师解读的新书《上游:在问题发生之前解决问题》,收获不少。联想和类比自己从事的行业,谈谈一些浅显的体会,也能借鉴书中的思想。
软件开发中的上游思维
首先,上游思维有三个条件。

眼光

在别人发不现问题时,你能看出问题。

这里的重点是问题不易被察觉,因而目前不是紧急的事情。如果问题大家都能看出来,可能很快就被解决,也就失去上游的意义。如果问题亟需解决,不然就会遭受巨大损失,那么也没有上游的意义。

你发现软件系统里有个潜在的bug。这个bug有这样两个特点,一是发现它需要对代码逻辑和架构非常熟悉,而且要有丰富的调试和排查经验。二是这个bug在大多数用户场景不会出现,不会严重影响用户体验。

那么这个bug的就是上游的问题,但是作为资深工程师的你发现了它,并不一定回去解决,这就涉及上游思维的其他条件。

责任

有时候问题不是没人发现,而是没人愿意负责,这就会出现所谓“不注意盲点”(inattentional blindness)。在制度上安排特定的人负责是一种办法,但毕竟外在压力不如内在动力。如果有人主动愿意负责,消除盲点,就会及时发现并解决更多问题。

上面那个bug是属于系统A模块B的,而你是负责模块C的开发与维护,那么很多工程师判断是不是该主动解决这个问题时,可能会采取责任>能力的原则:我能修掉,但这不是我该做的。

这确实是一个比较现实的问题,在一个组织中,不同团队的合作不仅仅是技术交流和分工那么简单。每个团队都有自己目标和资源,这些也可能与考核挂钩,开发资源的使用会比较慎重。然而,如果能够准确发现问题,就已经是上游思维了。

资本

这里的资本不一定是指财富和社会地位,而是一种*的、有“余闲”的精神状态。

如果一个软件工程师每天的日程排的满满的,他的精力应付自己的开发任务尚且不足,更别提一些不必马上做的事了。

想要所有工程师都有“余闲”去处理上游的事情不太现实,然而但凡有些条件的同仁们可以想想自己的时间安排,是否能够规划固定的余闲时间来处理一些重要但不紧急的开发任务呢?长期来看,这些余闲的积累可能给你和团队带来惊喜。

上游思维还有七个方法。

系统思维

以前我听到系统思维,总是理解为要综合考虑问题,其实这只是一方面。好的系统应该是不用时刻考虑方方面面,一切都是自然的、井井有条的,从而感觉不到系统的存在。设计软件系统和流程也可以借鉴这个思想。

如果我们在开发一个软件系统时发现,同类型的问题经常出现,并且不易察觉和调试,那么很可能原来的系统架构设计就有问题。这时一次重构也许是必要的,而不是头痛医头脚痛医脚。

很多系统尤其是运维(DevOps)团队的系统,其实可以实现一些自我反馈的算法和逻辑,当系统出现问题,自动采取一些修复的动作,最大限度减少人为的干预。毕竟,人为干预可能引入新的问题。

联手解决

让不同背景的人共同参与讨论和实现,可能是高效的做法。只是人多还不够,还需要多样性。

一个开发团队如果有不同技术背景的工程师,那在遇到难解的问题,或者进行架构设计时,就会获得从不同技术角度得出的思路和想法。避免因为团队整体的技术短板,导致开发结束才暴露出设计上的缺陷。

寻找支点

支点是系统中最关键或者最危险的因素,但由于分布的不均匀,并不容易发现。数据分析是判断支点的工具。有些“坏事”的模式还可能隐藏的很深,只有数据还是不够,更需要好的分析方法。

每个开发团队都会额外开发一些监控系统、日志收集和分析系统或者bug分布图表等,目的是分析软件产品在实际运行中的表现,好在哪里、坏在哪里可以一目了然。

这些数据有助于团队决策者调整研发和测试的方向,最终提高产品的体验和质量。有时,趋势和瓶颈不是那么明显,还需要借助机器学习算法来辅助分析,进而发现一些潜在的、有用的模式。

软件开发中的上游思维

提前预警

预警是通过一些蛛丝马迹对即将可能发生的“坏事”提出警示,将一些恶行扼杀在萌芽中,防患于未然。预警也常常依赖于对数据的收集和分析。

识别“假成功”

警惕三种假成功:跟随大潮的、靠运气的成功;找出替代方案回避原始问题的成功;完成指标伤害使命的成功。

程序员们可能都有这样的经历,有时候代码正常运行需要靠“运气”,产品上线前要找个“祖师爷”拜拜。这当然不是长久之计,软件程序正常运转不代表软件没有问题,尤其是你已经意识到程序某个部分可能有问题、但实际没有问题发生的时候。

此时最关键的做法是分析运气成分,即为什么明明有潜在问题而没有出问题。当然,很多人觉得还有更紧急的事情要做,但欠的债早晚要还的。如果它可能出问题,那么早晚会出问题。

寻找替代方案(workaround)也是类似,替代方案可用会让你放松对代码质量的把控,这样可能导致冗余、扩展性差、耦合性高的代码。一时的成功可能是未来的大坑。

开发流程中也有各种指标来评价和规范流程,比如测试的覆盖率、bug的修复率等等。过度关注这些指标,可能会忽视代码的功能本身。一个开发者可能为了满足一些静态代码扫描工具的指标,而放弃原有的代码结构,而原有的结构可能是质量更高的。

反馈

你照顾系统的部分,就可能会伤害整体;你从整体考虑,又可能会伤害某个部分。要想尽量避免伤害,你需要反馈。及时的反馈有助于发现一些行为的二级效应或者副作用,这可能需要走一步看一步,先做一些试点,再慎重推广。

在软件发布阶段的测试里有一种方法叫“金丝雀(Canary)测试”。大致的思路是,先让产品部署到一小部分用户群体中,看看是否有严重问题出现,根据反馈的结果决定是否扩大部署范围,最终产品会部署到所有用户终端。很久以前,煤矿的矿工就是放一只金丝雀到井口,看看是否有毒气出现。

让人买单

找人为上游的行动买单,就要找到利益相关者,并让他相信上游的问题确实会出现,解决问题确实会带来收益。

有时工程师会对产品的流程或者设计提出优化方案,这可能需要额外的人员支持,管理和开发工具的购买,已有流程和工具的调整等等。

这就需要说服利益相关者采取行动,你需要给出对比的数据,在优化前是什么表现,优化后提升了多少,这些提升是否能成为改变的理由。有时不同团队之间的沟通和说服,还需要向上寻求帮助,从更高层的战略角度来向下推行,这取决于公司的结构和文化。