本文要点:
敏捷的力量在于应对未解决的问题,但同样的力量也会导致技术债务和减少产品价值。
根据本质的不同,未解决的问题应该分为两种:过早出现的、可预见的。
过早出现的问题和可预见的问题,二者之间的区别在于强调重要性还是可能性。
重要的小改变,可以让敏捷开发走得更远。
背后的原则,可以弥合团队心理层面的距离,调和架构师与开发人员的差异。
如今的敏捷出了什么问题?
敏捷开发让人感觉良好,开发人员很喜欢它,但有时候它会出问题。这一点,我们都能感受到,并对其展开争论。
很多敏捷项目随时间推移变得不再成功。比如出现拖延,技术债务不断累积,进度放慢,士气低落。开发人员已经很努力了,但每个冲刺交付的价值却在不断减少。
突然,容易干的活都解决了,剩下的待办任务列表(backlog)中满是庞大、必要、而且对于利益干系人没什么价值的条目。团队与利益干系人开始冲突,项目拖拖拉拉,甚至有可能功亏一篑。
有人说,更严格遵守敏捷的核心原则可以避免此类情况。的确如此。但要想做到敏捷和自我管理,每个人必须可以激励自己,并且严格遵守规则。规则越严格,团队越大,纪律感就越容易削弱。
(关于恢复核心价值观,可深入阅读免费电子书《理解敏捷价值观和原则》。)
还有什么其他办法?
应对潜在原因
没有解决的问题属于待办任务列表。理论上,产品负责人要处理待办任务列表中的所有条目,去除无关紧要的,将最重要的排定优先级,放入冲刺中,直至清空待办任务列表,项目完成。
但在实际情况中并非如此。待办任务列表会一直增长。它收集的工作会与技术债务和无法轻松处理的“烫手山药”一起,进入等待状态。
开发人员认为:待办任务列表相当于泄洪道,让他们有事情可做。敏捷指出:对于你还不了解的东西,或是目前不需要的工作,先放在待办任务列表里,然后暂时置之脑后。有必要时,它会再次出现。很多时候,这么做没问题。敏捷的力量就在与此。
但是到了未解决的问题再次出现时,烫手山药已经变得太烫了,难以处理,技术债务已经变得过于昂贵而无法支付、解决。可用的资源已经根本不足以支持这些工作。
只要在敏捷的做法中加入一些关键的理念,做出一些重要的小改变,就能避免类似问题。
其根源是系统层面的弱点:敏捷方法认为,所有未解决的问题,都是过早出现的问题。没有这么简单,有些问题是可预见的。
过早出现的/可预见的
有两种未解决的问题,它们有本质上的不同。
过早出现的问题
过早出现的问题是当下不需要担心的、未解决的问题。现在去管它们就是浪费时间。可以确定:时机合适,它们自会再度出现。
根据预测,过早出现的问题有四种:
变得无关紧要:比如一个脚本运行起来很慢,但很少运行,几乎无人注意。它是没有解决,但不值得任何人的时间。
保持不变:比如改变窗口大小就会持续闪动的 UI 界面,确实不好看,但不会阻碍关键流程,而且不管什么时候解决它,整体的工作量没有变化。
变得容易:比如复杂的配置 UI 界面。等到后来,你会更深入了解项目,而且更好地理解数据模型。你有更多可重用的 UI 组件,是因为其它原因加进来的。难度就会随项目发展降低,再稳定在某个最低程度上。当你让它在待办任务列表中慢慢变成熟时,这就是你所期望的结果。
消失不见:比如某个复杂的功能特性需求,再过六个月,都无法确定用户是否还需要。不需任何工作就能解决此类问题。
当环境发生变化,或是过早出现的问题发生变化时,它就会再次出现。将某个问题视为过早出现,有点赌博的意味。对于很多问题来说,先放在一边是正确选择。
可预见的问题
可预见的问题是会随时间推移变得更难处理的问题。技术债务和烫手山药就属于它们。敏捷会产生很多此类问题。
越早处理此类问题越好。如果你总是无视它们,总有一天,可预见的问题会变得超过“可行性边界”(feasibility limit),过于庞大而无法解决。
经验表明,在一定时间内,难度会缓慢增加,当环境发生变化,问题难度就会陡增。
可预见的问题有两种:
直接:问题本身就是造成问题的根本原因。比如执行缓慢的门户初始化脚本,在不断增长的数据库中费力地运行。
间接:根本原因不在于造成问题本身,而是导致其它地方问题恶化。比如侵入式框架(intrusive framework),依赖它的代码越多,它导致的冲突越多,以后就难以去掉这个框架。
要想判断一个问题是否可预见,需要一些预测技巧:
对于表面原因和根本原因的感觉
理解为什么某些模式会以后导致特定问题
考虑各种场景,估算它们的概率
预测哪些环境有可能变化,以及何时变化
在问题达到可行性边界之前,你还有多少时间
这些技巧一般要靠架构师具备,而且需要跟每天疯狂写代码的工作有点儿距离。
可预见的问题有一个或是多个 可预见的场景。你可以预测到:如果不行动,情形会如何恶化。你可以预测到:当特定条件变化时,它如何跨越可行性边界。
会随时间恶化的问题总是可预见的,正是由此得名。
架构师的职责,就是要判断哪些问题是过早出现的,哪些是可预见的。
敏捷中可预见的问题
敏捷的本质缺点在于:所有未解决的问题都被视为过早出现的问题,不存在可预见的问题。
开发人员相信:当工作不那么繁忙的时候,他们可以处理待办任务列表中的工作。同时,可预见的问题确实存在。既然隐藏在待办任务列表中,工作任务如何改变是不可见的。有些保持不变,有些变得简单,或者不再重要,或者消失不见。但是有些会变得更糟糕。
在最不恰当的时候,问题的困难程度会突破可行性边界。这些问题从坟墓中成群用处,变得太大而无法解决,让所有人都感到恐惧。
资金充足的组织,可以用钱解决此类问题。上更多服务器,找来更多开发者。可行性边界会再次提高,此后再次发作。没有太多资金的组织,项目就会失去创新能力,逐渐过时,最终死去。
架构师应该为敏捷团队做的事
除了日常职责之外,架构师应该时常浏览待办任务列表,并将问题标识为过早出现的和可预见的;同时说明可预见的场景、可能的情境变化风险和可能造成的影响。
架构师应该选择最迫切的、可预见的待办任务列表条目,并产出可以解决它们的设计。然后将这些设计扼要转化为日常工作任务。设计应该足够成熟,可以切分难点,变为能够直接动手开工的任务。
设计是有必要的。把一个老问题直接丢回给程序员,又没有任何计划,这会造成压力、阻力和抵抗。问题在待办任务列表中出现是有原因的,可以预见也是有原因的。
为可预见的问题排定优先级很复杂,涉及到多个层面,包括:价值、工作量、破坏程度、达到可行性边界的概率和剩余时间。这些参数很难量化,最好由经验出发加以评定。
一条根本原则是:最“间接”的可预见的问题应该首先解决,因为它们造成的麻烦最严重,而且到处都是,先解决它们可以释放出资源,以解决优先级稍低的问题。
敏捷角色中的信任
程序员应该相信:那些一旦忽略就会变得恶劣的未解决问题,有架构师可以搞定它们。有必要时,这个架构师可以让问题再次出现在程序员的日程中,而且提供了相应设计,并且规划了优先级,所以不会产生压力。
唯一要注意的是,程序员不能过度依赖这个“服务“,不能把待办任务列表当成垃圾堆,把所有尚未认真想清楚的问题都丢进去。
补偿产品负责人?
处理过早出现的问题和可预见的问题,这是不是意味着架构师在做一个糟糕的产品负责人的工作?并非如此。这种方法将待办任务列表变得清晰、可行,产品负责人可以利用其中的信息规划冲刺迭代。
一般来说,产品负责人无法承担识别这两种问题的责任,他们缺少相应的深入技术知识,而且存在利益冲突:在压力时刻,保证客户和团队开心,这是头等大事。产品负责人不应该去刨根问底翻旧账。
所以,敏捷可以成功
用上述方法,敏捷就能像期望的那样成功:成为可持续的开发方法,让开发人员的生活可以承受得了,同时不必积累技术债务和烫手山药。
必须付出的小代价是要认识到:只有开发人员,没有人能承担架构师的角色,这样的自管理团队不可持续。
(深入阅读:《也许敏捷才是问题》、《敏捷宣言:软件架构师角度的思考》)
还有更多东西要考虑
这个过早出现的和可预见的问题分类原则还涉及更多主题。
总而言之,该方法是要具体化一种没有说出的张力——短期内写代码并带来成效,长期内的可维护性和可演化性,这两者之间的张力。
(深入阅读:《构建可演化的架构》一书,特别是第 2、6、8 三章。)
开发人员的心态
所有架构师都知道:开发人员从完全不一样的角度对待项目,这会导致很多误解。使用过早出现的和可预见的原则,有助于弥合差距。
待办任务列表作为分散注意力的垃圾堆
程序员是手艺人,手上忙着各种事情。他们希望干扰越少越好,从而保持“心流”状态。程序员善于将干扰从自己的思维空间中赶出去。
为了让代码工作,他们有无数的问题要解决,再加上一大堆问题,只会造成压力。对于偶然出现的问题,程序员需要待办任务列表将它们置于一边。
架构师有时候会认为这是置问题于不顾。但程序员觉得这么做没问题。此时缺少的是整体掌控,需要有人可以过滤可预见的问题,在以后的开发周期中再放进来。
这样的待办任务列表掌控不应是程序员的职责,他们还有日常开发工作。否则就会导致利益冲突和压力。此类压力会触发绝望,使得大家想从头重新开发,拉低整个团队士气。
不去评判未解决的问题
程序员相信自己的编码能力,相信自己可以解决遇到的任何问题。这是他们的日常工作。
在程序员看来,架构师的设计让他们感到被评判,似乎他们的编码能力不足,然而正是他们在做所有的实际工作。但是架构师的意图是中性的:有了好设计,程序员的编码能力可以走得更远。
在日常对话中引入过早出现的和可预见的问题分类原则,可以让此类讨论免去评判的意味。这与谁工作效率低下、能力不足、谁更好或是更聪明无关。关键是要判断会发生什么问题,从而加以预防。
当待办任务列表中加入新条目时,如果程序员可以针对可疑的可预见的问题附加警告标签,并添加可预见的场景,对大家都有帮助。
将框架作为可预见的问题
程序员会不小心将框架放到代码库中。他们遇到某个问题,而且发现很难解决,那就看看还有谁也碰到类似情况。找到一个框架,没问题,看上去挺好,不错,继续写代码。
架构师认为,框架是结构性的决策。程序员认为,框架是可以直接用的代码。如果程序员每次选择框架的时候,都要暂停冲刺迭代,保证选择过程深入彻底,那他们什么都做不了。
对于架构师而言,匆忙决策会减少可选项。常常出现的是,过早选择框架会在后面让人失望。一旦用上框架,再想去掉就很麻烦。如果过了很久再说,那就完全不可逆了。
这是可预见的问题。框架选择应该在冲刺之前完成。提前选择框架,应视为可预见的问题的解决方案。
减少对英雄的依赖
敏捷希望程序员在面对难题时充满勇气:团队是自治的,可以相信的,知道该做什么。实际上并非总是如此。一般来说,随着项目进展,利益干系人的信任会不断销蚀,当交付的产品成功时才能恢复。
信任销蚀得越多,团队要想说服郁闷的利益干系人,让团队开发“模糊”的任务,则需要的勇气越多。慢慢地,对于很多人而言,就不再具有这样的勇气。他们不愿意提出难以解释的讨论主题。逃避会让可预见的问题向可行性边界慢慢前行。
和利益干系人讨论时,说明某个问题是可预见的,可以去掉勇气的因素,让其成为更中性的议题。能做到以下几点就更容易:
需求明确
不做评判
结果有益
每个人都能理解置之不理的后果
(深入阅读:《敏捷组织中沟通勇气的重要性》)
开发人员抵触设计
架构师希望程序员理解、支持自己的设计,并且能和自己辩论,发现其中的潜在问题,同时积极提出想法,加以改进。有价值的设计应该经得起健康的辩论。过早出现的和可预见的问题分类原则能够让辩论更有价值。
抵触复杂性
如果程序员认为某个设计太过复杂,他们就会抵触,同时相信更简单的解决方案更好。如果存在更简单的解决方案,那当然再好不过,赶紧用起来!可惜,复杂方案常常是有必要的,因为简单方案力有不逮。
程序员可能公开抵触,抑或消极听命,拒绝主动贡献想法。这是有成本的,会带来更多潜在设计问题、写代码时更多返工、将来更多要修复的 bug。
通常,架构师的理由是考虑未来远景和设计原则。但如果去跟程序员解释这些整体图景,那就过于庞大而有些杂乱了。越想讲清楚,程序员越是固执己见。
抵触设计常常伴随这些情绪:失望、畏惧、感到别人更聪明、或是被夺去自主权。光靠讨论很难克服,因为程序员总是用技术论点讨论技术和情绪话题。此种讨论难以捉摸,而且无人受益。架构师应该一起来预防对设计的抵触。
抵触设计中的潜在问题
设计的问题会增加抵触心理。人会犯错误。架构师也不例外。设计越复杂,架构师就越有可能漏掉某些东西,留下设计问题。
程序员认为:遇到潜在问题,就得丢弃刚刚写好的代码,还增加了复杂性,错过截止日期。设计潜在问题常常难以察觉,而且很不容易解决。程序员从经验出发,担心越是复杂的设计,越容易遇到潜在设计问题。
为什么展示整体图景没有作用
程序员说:“我看不清大局”,或者“你把这个弄得太漂亮了”,他们是在说:如果他们找不到一个做得这么复杂的合理的理由,那就不存在合理的理由。
用展示整体图景来说明你的选择,这么做很诱人,但是会有反效果。
程序员认为:雄心勃勃的设计,就好比架构师用大油漆滚筒画出了西斯廷天顶画的轮廓,留下无数细节,等着程序员去完成。
左:架构师如何看待设计 右:程序员担心自己要做的事情
展示整体图景会让程序员崩溃,因为他们看到有那么多东西,就会想:“天啊,我怎么做的了这个。”庞大的设计常常是人脑海中难以容纳的,还得担心存在潜在设计问题。
如果你的庞大设计包括未知因素,那就更糟糕了。存在不了解的东西,在架构师设计过程中很正常。在程序员心中,想到糟糕的客户需求以及自己要为之写出的代码,自然会将未知因素转换为不确定性、更多要解决的问题、更担心潜在问题,从而错误认为:架构师不知道自己在干什么。
如果程序员想了解大局,那就要找到根本原因。最好只跟有经验的人分享庞大的整体设计,他们不会晕头转向。
为什么抽象的讨论没有意义
架构师认为,使用某些抽象是必要的,由此就不用在各步骤之间想得太多。当然,这是架构师的必要能力。而且很容易就会向程序员解释这些设计抽象。
程序员认为原则和设计模式就像汽车:它们都能把你带到目的地,那为什么要花钱去买最贵的呢?
与程序员讨论抽象设计,会将讨论带到品味上,或是争论谁能产生“最干净”的抽象。两者都不会带来进展。
为什么业务原因不起作用
架构师认为:某些技术决定是出于商业原因。比如,能让商业合作伙伴更轻松的技术能力,会让组织成为市场领导者,而不是偏安一隅。架构师应该参与此类决策讨论。
业务原因不能说服程序员。相反,他们会把讨论转向商业人士的不理性头脑、产生的无理需求,从而否决架构师设计的合理性。
程序员畏惧商业元素。过去与合作伙伴的经验,常常导致程序员要去收拾烂摊子,由合作伙伴要求无度而且能力不足的 IT 人员留下的烂摊子。程序员认为:商业上的成功,意味着由一意孤行的管理者提出不切实际的要求,导致自己被迫做出让人后悔的应急方案,而且总被事故推着走。
很多程序员不喜欢商业、销售和市场,无论是人还是相关主题。三十年的呆伯特漫画可以告诉你为什么。
那么,到底该怎么做才有用?
从整体设计产生可执行的设计方案,避免论及宏大的、深奥的、未知的因素,这是架构师的职责所在。
设计应该从一系列你想解决的、可预见的问题和场景开始。这样一来,架构师就能说明为什么简单方案不行,或是无法坚持足够长时间,因此不能使用。
最好能写下多个可预见的场景,让它们彼此关联。为什么?可预见的场景可以将抽象和相关问题转变为具体例子。如果只有一个场景,程序员可能会倾向于找到某种简单的权宜之计,只解决这个场景的问题,而不是它代表的整个问题类别,从而低估问题范围。
有些相关问题很明显,但是过早出现,那就要归类为“不在范围之内”。
列出过早出现的和可预见的问题的设计列表,可以让程序员知道不要担心什么, 应该重点关注什么。这让架构师在抵触到达沸点之前,可以中性地展开设计辩论。
对于程序员来说,由过早出现和可预见驱动的设计更容易理解,更易于寻找潜在设计问题,更便于找到改进方案。这么做的同时,还不会产生复杂设计带来的更多压力、不确定性和过多问题导致的负担。
这还能为程序员留出更多空间,让他们可以自己决定,同时保证他们深入了解架构师的意图,不需要过多详细需求,还能让设计切合当前冲刺。
(观看关于设计抵触、前期大型设计、冲刺中交付架构的视频。)
整而合之
还得聊聊可能性
现在,讨论从争辩一个问题的相关性转移到了可能性。它有多大机会发生?情境改变的可能性有多少?还需要多久,它会达到可行性边界?
为了简化,可预见的问题的可能性可以分为三个级别:
不可避免:必将发生,必须行动
火灾风险:难以发生,一旦发生,必须有类似自动喷水设备的措施
流星撞地球的风险:几乎不可能发生,而且不值得去修个太空盾牌那样的防范措施
必要的同理心
你必须理解程序员的心态,看到在新的做事逻辑生效之前,为什么会发生某些事情。突然就将问题标识为“可预见的”,会让人觉得过于生硬,让人觉得在评判程序员的脑子、工作素质或编码能力。开发人员也许会将未解决的问题作为秘密保守,自己承担相应负担,就像程序员们一直以来自然而言的做法。你会用一个问题取代另一个问题。
(深入阅读:《同理心是一种技术能力》。)
如果程序员觉得说出想法不是问题,讨论未解决的问题就不会觉得自己能力不行、懒惰或是不上进。未解决的问题是努力工作和良好意图的中性后果,搞定麻烦的那些问题正是架构师的职责。这样敏捷就能起作用了。
(深入阅读:给架构师的提示,包括“垂直分片”建议,可以帮助大家概要了解可预见的问题,同时不会让架构师听上去像个末日预言家。)
那么关于留出更多选择呢?
留出更多选择,这是架构师的工作:你无法预言,所以你要让自己有更多选择。听上去似乎与过早出现的和可预见的问题分类原则直接冲突。
实际上,留出更多选择是积极地安排你的未解决问题,这样你就可以将尽可能多的问题变成过早出现的问题,只剩下尽可能少的可预见的问题。
架构师能做的最有用的事情,就是解决一个可预见的问题,该问题可以将很多其他可预见的问题变为过早出现的问题,让你有更多选择。
结论
引入过早出现的问题和可预见的问题之间的区别,是一种全新的思考和讨论习惯,它可以:
让烫手山药和技术债务变得可以讨论,同时不会让程序员因为压力而崩溃
让架构师承担职责,应对那些因为没人管就会失控的问题
有助于以中性的方式讨论设计,每个人都可以理解
针对更加复杂但有必要的解决方案,让大家都能更好地了解
预防潜在设计问题和 bug
区分过早出现的和可预见的问题,可以让敏捷成为本应成为的可持续的开发方法。
作者介绍
Bas Groot 住在阿姆斯特丹,已经创业 19 年。他开发了 web 应用平台和内容管理系统,那时还是 1996 年,类似软件尚无名称。接下来的 7 年,他为某金融服务软件供应商工作,担任使用同一平台的 web 解决方案部门的架构师。在其职业生涯中,Bas 主管并培训开发人员团队,作为架构师领导产品开发和软件设计。他实施落地了众多客户项目,包括政府、金融、法律、非盈利、汽车和广播媒体等多个行业,用到多种敏捷方法。Bas 主导开发的有:两种编程语言、一种图形化底层代码应用编程环境、一种具备实时集群编码能力的 web 应用框架、一个数据建模框架、一个软件和内容分发部署框架、一个 web 开发代码库等等。Bas 有实际上手开发和使用框架的经验,同时对于设计软件和方法论有深厚的抽象能力。
“出现问题时,我要做的第一件事,是看人们遵循什么规则,以及背后的原因。”
原文链接:
Categorise Unsolved Problems in Agile Development: Premature & Foreseeable
评论