我来到凯捷已有一年时间,结识了许多可爱的伙伴...期间作为开发者参与了两个敏捷项目,过程中有可贵之处,也有没做好的地方, 所以我很自然的经历了一个反思过程. 我想把它们拿出来说一说——当身边的队友遇到困难,你总是急迫地说出自己的想法,哪怕它未必成熟合理.
本文主要讨论的是我在项目中作为开发者实际经历的情况,但反思的过程是从各方角度出发的,主要是聊一聊我参与的敏捷项目中的用户故事在面对一些复杂需求时出现的问题,问题的根本所在,我们又该做些什么.
在甲项目中,我们要开发的是产品生命周期管理系统,系统一部分需求相当复杂.我们使用用户故事卡片管理需求,通常的做法是在卡片中粘贴原型图,加入一些需求描述以及AC.简单的增删改查通过这些卡片得到了良好的表达,然而面对复杂的需求时,这些卡片中总是缺失了一些致命的信息: 角色, 业务规则, 需求对于客户的原始价值等... 这些隐含的信息随着开发过程逐渐显露,不停影响开发的进行,甚至产生推倒重做的情况. 我们用这些卡片追踪了需求的生命周期,但却没能清楚地表达需求.
用户故事与上下文
当我们用系统思维来理解,世界是上图的样子,由系统及其子系统构成.每一个系统都有自己的上下文, 系统A,B的上下文限制着C,但当我们进入C的内部,又更注重C的上下文,而不至于迷失在庞大的B,A中.我认为以系统思维来理解、记录、管理需求,明确各部分的上下文内容,是我们完全没有认识到的,用户故事的本质之一, 也是面对庞大需求时团队内统一需求理解的关键.
通常描述一个卡片的最佳形式为: 作为[角色], 需要进行[操作], 因为[价值]. 其实这种形式,目的就是要清楚表达卡片的上下文环境. 有了清楚的上下文,才能清晰认识一张卡片,对其业务规则进行足够的思考,不至于出现上文中提到的隐含信息在实现阶段不断浮现出来的情况.在众多被丢失的信息中,原始价值是概率最高的,然而我们要交付的即是价值,缺失价值信息很容易导致团队对需求的误解!
没有层级的上下文是很难被理解的.在甲项目中我们的每张需求卡片都包含一个页面的原型和描述.对于一个复杂的需求,信息量非常大,包括各种角色,复杂的业务规则与逻辑等.我从来不能通过这样一张卡片来认清其上下文.要想清晰表达上下文,我们需要明白:
1. 作为需求的最小单元,用户故事卡片的上下文应该是简单明确的.比如应该只包含一个角色.
2. 有人会说有些卡片的需求本身就是不简单的,那是因为他们忽略了上下文是有层级的,没有层级结构是绝对无法管理好上下文信息的.在上图中,如果系统C是用户故事卡片,那系统B即是Epic.我们应当使用这些概念表达需求上下文的层级结构,从而有效管理需求的复杂度.
一个项目的全局上下文是非常重要的.上图中没了A的限制,我们便无法认清B,C.甲项目出现的一些需求误解中,一部分便是由于团队没有对整体上下文完成统一: 有的时候团队成员对一些领域术语各有自己的见解,有的时候把某一个卡片放到整个系统中到底是为什么困惑着团队成员.团队非常需要对整个系统有统一的认知,即使我们面临用户需求不确定的情况,也应该力求达成统一的认知(一种假设的认知也远远好过没有).因为当团队没有对整个系统统一认知,那么为了理解各个卡片,团队成员一定对整体上下文做出了某种假设(常常是无意识的).这种未统一的假设是致命的,在如领域驱动设计以及事件风暴等一些著作中,作者都在强调统一领域语言对于理解全局上下文的重要性.
我们应该为每个项目建立领域术语表,包括领域的各种角色之类的领域名词等,它是全局上下文非常重要的一部分.? 同时用户的整体旅程也将帮助团队统一对系统价值的认知,它也是全局上下文中非常重要的一部分. 使用用户故事地图可以清晰表达这种旅程.
从理解价值到交付价值,我们需要设计的力量
在业内常常有这样的情况: 企业在快速持续向客户交付价值,响应变化.然而实际情况正如那张漫画,不停工作的印钞机内部,其实是不停工作的人类.劳动力的有限性没有写在敏捷价值观中,但我们去追踪敏捷方法的历史便会发现,正因为西方工程行业将劳动力的有限性作为基本原则,才促使科学、工程水平的不断提升,敏捷方法也正由此而生.那么为什么目前业内响应变化的常用办法,是不断的加班呢?这也是一个值得反思的问题.
我发现我们在实现需求(主要是复杂需求)之前,缺少了极其重要的一步: 建模设计. 为了应对逐渐复杂的系统,对象建模范式取代了过程范式, 为了实现对象建模, Java语言代替了C语言. 而如今我们使用Java语言构建复杂企业应用,却在以过程范式来进行逻辑展开... 我们用一个个过程开发极其复杂的业务逻辑,当需求变化, 这些代码难以修改,于是我们鼓励重构.在重构时,有经验的同事向比较新的同事传授思想:“把通用的部分抽取成一个方法”...? 我们一直在使用过程思维,然而过程思维无法面对复杂系统,要响应变化,我们必须要使用对象建模,必须要具备对象建模能力.这不是新生的方法,而是长期的取巧造成的债务.
那么对象模型到底是如何从容应对变化,与领域知识共同进化的?在甲项目和乙项目中,我都在对复杂业务进行对象建模实践,这里我举出在乙项目中的一个需求作为例子: 项目需要与某外部系统的接口进行数据同步(要求通过rest 接口完成),但是面临着需要同步的次数和时长不固定(达到过数个小时之久),数据量不固定(单次HTTP返回数据达到过几百Mb之多),数据的过滤过程不固定(一条数据只有一部分字段是有意义需要保留的)等问题. 转手过几人的代码逻辑都无法应对这些变化, 因为这些代码都在使用过程思维. 在足够了解需求的确定部分及不确定部分后,我对此作出了如下图的对象建模:
上图的各组件均使用Java语言提供的对象建模能力建造.Spider负责与外部接口进行交互,应对请求次数、时长和数据量的变化,可以实现单线程或多线程的Spider. Spider抓到的数据总是流经DataBus,在这里,可以通过向DataBus添加不同的Plugin应对变化的数据过滤过程. 流经DataBus后数据暂存入DataBuffer中,当数据量较少,DataBuffer可以直接在内存中;当数据量很大,DataBuffer可以是Redis;当有核对需要,DataBuffer也可以是Excel.抓取过程完成后Spider遵循事件模型,发起完成通知,此时系统经由类似的DataBus从DataBuffer不断取出数据并存入DB.这样遵守基本的设计原则,加上一些最佳设计实践以及从各种框架学到的设计经验,我就建造了这个模型,此模型跟随着需求共同变化,快速响应需求并积累领域经验.
我们常在面试中谈论技术,框架的原理,但结合目前的形势,我们最迫切需要的不是这些原理,而是它们是如何被设计出来的. 很多同行对框架原理,技术方案夸夸其谈,却连几十年前已经成为基础能力的对象建模能力都不具备.当然这不是同行的错误,我们能力的落后有多严重,这需要具备更高视角的角色去思考.
关于对象建模能力的构建,我想我们的架构师应该具备足够的对象建模能力, 并传递给开发者. 建模应该在实现之前进行,如果我们想开始做这件事,我想一开始应该是架构师来负责的,随着能力建设的深入,开发人员将可以拿出建模结果供团队评审.
战略意义
一直以来我们在软件领域解决问题最重要的方式都是透支劳动.回首过往,这为我们带来了长足的发展.然而祸根也由此埋下: 我们使用Java语言,却千篇一律地进行过程式开发,这样的问题产生的根源,便是我们不具备更新思维和能力的基础条件——我们有更便捷的方式,又何必自讨苦吃呢
我们都知道,这种便捷的办法只能使用一段时间,只是潜意识觉得它会很久罢了.而展望未来我认为这一段时间很快将迎来它的终结. 可悲的是很多管理者从未思考过此事,透支劳动解决问题是他们的致胜真理.这让人不敢去想拐点到来的景象.
人间正道是沧桑, 透支劳动不是最好的办法, 能力建设才是未来的钥匙. 若我们在拐点到来之前忍受阵痛,积极进行能力建设, 如对象建模之类的能力,都将成为我们的核心竞争力,帮助我们应对困难,实现理想.
同时作为咨询公司,我们常常面对一些同事是初次接触某个业务的情况.建造准确深刻反映业务的领域模型,并长期将其积累到公司的知识库,可以使我们的整个敏捷团队从中获益,甚至帮助客户衍生新的价值.
一年来,我看到的凯捷是一家非常优秀的公司,对过去一年的相处有很多地方需要感谢.然而保持进步是不变的真理,直言我所见到的问题,这是我最大的真诚,也是对你包容度的信任.将这真诚与肺腑献给朋友,愿我们携手走过更多的路.