“出于技艺的追求,工程师常常会以开放的心态去尝试新的工具和做法。其中有些完全可以由我们自己掌控,比如使用哪种文本编辑器、采用什么样的控制台、是VIM还是Emacs风格快捷键等等。但更多的情况是,我们发现的“更好的方法”不仅会影响我们自己,还会影响到团队、部门甚至整个公司。如果我们坚信自己的选择并由衷地希望以更好的方式工作,那就必须能够说服其他人,让他们认可我们的做法,并在工作上作出改变。”
本文是徐昊所写的《驱动变革》第一篇,后续他还将从“职权不是决定因素”、“行为的改变”、“获取信任和确立愿景”以及“提升紧迫感”等几个角度分别阐述驱动变革的意义及方式。敬请期待。
作为工程师的我们为什么要学习驱动变革?为什么必须成为变革者?请先试想一下这两个场景:
• 你在代码库中发现非常多的重复代码,这些代码不仅功能趋同而且结构类似。在进行了简单的重构之后,你发现这些代码其实是一类公用组件,可以在很多业务场景下重复使用。这个时候,你准备把它提取成一个单独的共用项目或者叫基础模块,在消除掉这些重复的同时,改善代码结构并且让未来的功能开发更加简单有效。你希望其他人支持你的做法,并着手修改他们所负责的代码。
• 你所工作的小组正在为某企业客户开发一款调查问卷软件的升级版,采用的技术栈
是 .NET、ASP .NET MVC和SQL Server。随着项目的进行,你发现所有数据都可以由问卷实体进行聚合(aggregating)。如果使用关系型数据库,这种聚合关联关系复杂而且效率不高。而使用文档数据库(Document Database)不仅能简化开发,而且常用查询的执行效率会提高很多倍。你希望能由SQL Server切换到文档数据库——比如MongoDB。
这两个场景你应该觉得非常熟悉,这是工程师经常遇到的境况:由于非常了解手头所做的工作,通常会比组织中其他人更早、也更敏锐地发现更好工作方式。比如更好的编码方式、更恰当的编程语言和工具,还有更合理的工程实践等等。出于技艺的追求,我们会以更开放的心态去尝试新的工具和做法。其中有些完全可以由我们自己掌控,比如使用哪种文本编辑器、采用什么样的控制台、是VIM还是Emacs风格快捷键等等。但更多的情况是,我们发现的“更好的方法”不仅会影响我们自己,还会影响到团队、部门甚至整个公司。如果我们坚信自己的选择并由衷地希望以更好的方式工作,那就必须能够说服其他人,让他们认可我们的做法,并在工作上作出改变。
作为工程师,通常我们对这个问题最直接的反应是以结果说话。相信只要有更先进的技术、更好的结果、更多的产出,那么我们所期待的变化就会自然而然地发生。然而实际情况恰恰不是这样的,我们容易低估变革的难度和阻力,而高估技术因素在变革中的影响力。比如上面所说的两个场景: 第一个是重构代码并调整项目结构,第二个是更换技术栈中的组件。看起来都是收益明确的“技术决定”,但这些决定带来的变化可能是非常深远的,阻力也可能完全来自意想不到的地方。
首先来看第一个例子——重构代码并调整项目结构。这里的盲点是Conway法则——组织结构是软件架构的倒影,阻力来自因为架构变化带来的组织结构变化。
你所重构出来的公共模块会在很多业务场景下重复使用,假使让所有使用了这些模块的小组共同维护这些组件,就会带来非常大的沟通成本。无论是API的变化还是特性的取舍,都没有办法在某个小组内单独决定,因为它现在已经是共用模块了。最简单的情况是由某个人——很可能就是你——来协调对公共模块的修改。那么在不久的将来,如果公共模块的需求足够多,实际上就会出现一个独立的公共模块小组或团队。这是由技术决策带来的团队结构和人力分配的调整。如果不能把这个因素纳入考量,重构就可能会失败。
你可能会想这真的会发生吗?重构一下代码结构能有这么大的影响吗?这当然不是编造的,我来讲个现实中发生的案例。
曾经有一个团队,他们采用的技术栈是 .NET。最开始的架构也很简单,前端采用
ASP.NET MVC,后端使用Entity Framework进行存储。然而这个系统的领域模型比较复杂,有很多小的细力度对象。使用Entity Framework进行持久化的时候,类型映射的代码非常复杂,但这些对象引用关系相对比较简单。这时候有个技术非常不错的小伙子,为了解决底层的持久化问题,开始寻找不同的解决方案。于是他边写功能代码边自己做了一套不同于Entity Framework的持久化框架,通过SQL Server的XML字段和 .NET对象序列化完成所有持久化的操作。
他做完演示之后,大家都觉得很不错,于是就开始着手重构其他代码。这时候麻烦来了,他做框架的时候,是根据他所熟悉的业务进行的抽象,有很多没有考虑到的地方。不过他手很快,白天提的需求晚上就能改好,后来慢慢发现由于整组人都在用这个东西,别人又没他了解的多,不如让他专门来做这个框架。渐渐他就不做其他的需求而是专门来做框架,于是他自己就成了一个小团队。
这就是因重构而产生团队结构变化的例子。这个案例之所以能够成功,除了他个人的一些做法之外,主要是因为这个团队负责整个软件的交付,责任结构比较简单,内部分工的变化对整体交付没有影响。如果不是这样的结构,又不事先考虑组织变革的成本,那么架构重构基本上就会失败。
很多年前,我在Java社区认识一个朋友,他所在的公司在国内某行业做定制软件开发。这家公司的团队是按业务模块进行划分的,每个小组负责一个模块的交付。我这位朋友算是想法比较活泼的,他希望能够抽取与业务相关的公共组件,从而在现有的项目中提炼出产品和行业解决方案。他在他负责的小组内做了尝试,并取得了一定成功。
他的领导让他征求其他团队的意见,看看是否有推广的前途。然而在这个过程中,他受到项目其他模块团队的质疑:如果公共模块出了问题谁来负责?当他回答说,“我可以用业余时间尽量帮助大家”的时候,最终实施结果也就可想而知了。没有人愿意把交付的成败依靠在某个人的业余时间投入上,而领导又没有足够的动力改变整个团队的结构,那么这个方案也就不了了之了。
再看第二个例子——更换技术栈中的组件。这里的盲点是协作部门的技能变更。
你所作出的技术决定是正确的。针对这种聚合关系的对象结构,无论从数据存储的效率、查询的效率还是开发的效率来说,文档数据库都会是更好的选择。但是开发仅仅是整个软件生命周期中的一环,除了开发之外还有运维支撑,比如监控、备份、数据恢复、故障诊断等方面,也是非常重要的。特别是对于已经运营了一段时间的软件产品来说,就更是这样了。
在这个例子里,因为是后续升级而不是全新系统,那么客户已经围绕SQL Server建立了完整的运维体系。那么如果把数据库换成MongoDB,就需要围绕MongoDB建立新的运营方式,那么运维团队就需要新的技能和工具才能继续运营这个产品。这是因技术决策而为协作部门带来新的技能要求,如果不能把这个因素纳入考量,就会影响软件的最终投产。
同样为了告诉你这样的事情真的会发生,我举个例子。
最近有个团队,在帮助某客户研发医疗管理系统。该系统是个有多年历史的遗留系统。由于设计的早,架构上有很多不尽合理的地方。在这次的研发中,客户希望可以撷取业内较好的工程实践,以支撑未来的架构演化。这个团队采用了micro-service的方式架构整个应用,把独立的模块分解成独立的服务。部署上为了更好的弹性,抛弃了原有系统的应用程序服务器模型,而是采用内嵌Web容器的方式。
在开发了一段时间之后,有医院方的人员来参加成果展示。这时候,负责这个系统运营的IT部门表达了强烈的不满。原因是,他们的运营人员主要通过应用程序服务器的控制台管理所有应用程序。而一旦变成多进程的micro-service后,原有运维工作就需要依赖linux平台上的日志、进程管理监控工具才能完成。他们的运维人员完全没有这方面的经验。当时的结果是,micro-service仅在测试环境中使用,而生产系统必须采用应用服务器的模式。
这是好的、先进的技术因为没有预估好协作部门的技能水平而失败的例子。当然,考虑到这个问题而把一些相对激进的好技术推行成功的故事也是有的。
在Ruby还不是很流行的时候,有个团队想在项目中使用Ruby Watir作自动化功能测试。而客户的情况是:他们已经花了大价钱购买了HP的Quality Center和QuickTest Pro。这两个都是商业软件,都需要高昂的版权费用。这两个软件分别由业务人员和QA团队使用,业务人员使用Quality Center来记录需求和测试案例,自动化功能测试主要由QA团队完成。QA团队主要采用录制脚本结合VB Script的方式来编写测试,测试全部由QuickTest Pro执行。
力主使用Ruby Watir的是研发团队,因为当时ruby很新潮同时Watir的执行效率比QuickTest Pro要好很多,但QA团队并没有表现出对Ruby的热衷。这时候团队里的工程师研究了QuickTest Pro的编码风格。发现QuickTest Pro中测试主要依赖三个部分:Object Repository用于表示页面元素和常量、Data Sheet用于记录测试数据、VB Script用于实现测试步骤。于是他们使用Ruby做了一套框架:PageObjet用于表示页面元素和常量、Fixture用于记录测试数据、定制的领域专用语言(DSL)用于实现测试步骤,同时整合Quality Center以统计测试报告。这个框架让QA团队感觉到工作方式其实没有什么太大变化,基本都能与QuickTest Pro中的概念一一对应,并且不会影响Quality Center的使用。就同意尝试
使用它。大约四周之后,整个测试部门就开始了由QuickTest Pro到Ruby Watir的迁移,QuickTest Pro就完全废止不用了。所以只要方法正确,这种看似不可能的变化——抛弃已经采购的商业软件转而使用开源软件和不知名的小语种——也是可以顺利完成的。
我还可以列举出非常多的例子,看起来都是好的技术决策,然而最终左右这些决策成功与否的并不全是技术因素。我想你已经大概明白我的意思了:仅有更好方案是不够的。更先进的技术、更好的结果、更多的产出,并不能让我们所期待的变化自然而然地发生。哪怕只是某些技术上的改进,单单是个体的变化已经不够了。那么如果我们不希望年复一年地工作在腐烂的代码库上,使用陈旧的技术栈、落后的工具、过时的工程实践,我们必须学会驱动变革,成为卓有成效的变革者。