更好就足够了吗?| 驱动变革

写在前面:

“出于技艺的追求,工程师常常会以开放的心态去尝试新的工具和做法。其中有些完全可以由我们自己掌控,比如使用哪种文本编辑器、采用什么样的控制台、是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就完全废止不用了。所以只要方法正确,这种看似不可能的变化——抛弃已经采购的商业软件转而使用开源软件和不知名的小语种——也是可以顺利完成的。

我还可以列举出非常多的例子,看起来都是好的技术决策,然而最终左右这些决策成功与否的并不全是技术因素。我想你已经大概明白我的意思了:仅有更好方案是不够的。更先进的技术、更好的结果、更多的产出,并不能让我们所期待的变化自然而然地发生。哪怕只是某些技术上的改进,单单是个体的变化已经不够了。那么如果我们不希望年复一年地工作在腐烂的代码库上,使用陈旧的技术栈、落后的工具、过时的工程实践,我们必须学会驱动变革,成为卓有成效的变革者。


更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

我们真的缺前端工程师吗?

前言

这两天在好几个地方都看到了一篇关于为什么整个互联网行业都缺前端工程师?的文章,文章本身是去年的,中心思想是:其实我们并不缺前端工程师,我们缺的是优秀的前端工程师。我还是比较同意作者观点的,不过略有意犹未尽的感觉。于是我结合自己的经验,也来聊一下这个话题:我们真的缺前端工程师吗?

These walls are kind of funny like that. First you hate them, then you get used to them.Enough time passed, get so you depend on them. That’s institutionalising.

传统软件公司划分开发者的方式下,在前端部门的程序员永远不会去读缓存数据部分的代码,设计师也不太可能去和开发坐在一起,开发也不知道软件最终软件会以何种方式部署在服务器上。

什么是前端工程师

我在招聘广告和办公室的一些对话中,听到了一个新的角色:UI Dev,事实上我在知乎上还回答过一个关于ThoughtWorks的UI Dev的问题。简而言之,UI Dev可以快速的把设计师的作品实现为HTML/CSS/JavaScript代码。

01

如果按照这个标准,我觉得UI Dev对自己的要求太低了。毕竟要学会HTML/CSS实现mockup并不困难,但是成为一名前端工程师则需要掌握更多的知识:

  • 会用PS来进行图片的处理(比如切图,微调等)
  • 用HTML/CSS实现mockup(可能还有SASS/LESS等工具)
  • 熟悉JavaScript(比如前端的MVVM框架,客户端模板)
  • 前端开发的工作流程(代码检查,精简化,模块化CSS,LiveReload,调试)
  • 编写测试(静态检查,单元测试)
  • 跨浏览器、跨设备的解决方法(不同分辨率,不同厂商)
  • 会根据项目的特点选择不同的前端技术栈(移动端,Web站点,响应式设计等)

在有了基础的HTML/CSS/JS技能之后,你会尝试做的更好:

  • 如何更高效的操作DOM
  • 如何将CSS写的更加清晰易懂
  • 如何编写更加易于维护的代码(更有意义的单元测试)
  • 如何组织大型的项目结构,模块化,组件化等等

这些要求事实上已经不那么容易做到了。它可能会花费你2到3年时间来完全掌握。但是2到3年之后,即便你已经成为了一个“合格的”前端工程师,这也还远远不够。在现实世界中,一个软件产品除了前端,还有非常广阔的空间,还有很多有趣的东西值得学习:

  • HTTP协议本身(缓存,鉴权)
  • Web容器/HTTP服务器如何工作
  • 无状态的Web应用的工作原理(如何让网站正确地运行在集群上)
  • 动态,静态内容如何分离部署(反向代理配置)
  • 安全机制如何配置
  • 监控机制如何配置

有了这些,也算是有点端到端的意思了。这时你也已经不是一个纯前端工程师了,系统中的大部分问题你都可以搞定,不过日常工作中可能更多的职责还是做前端的开发。但是这些还不够,软件除了交付之外,还有一些非功能性的需求:

  • 端到端测试(UI测试,比如selenium server/web driver)
  • devops(比如数据库环境,测试服务器,CI服务器的自动化provision)
  • 基本的UI设计原则(在某些页面确实的情况下,根据系统的已有UI做设计)
  • 数据库性能优化
  • 性能测试

这时候,你才能算是一个严格意义上的“前端”工程师。不从系统的角度来思考,不真正做一些后端开发/配置,并不能算是前端工程师,或者可以被称为偏前端工程师(partial frontend developer)。但是即使称为上边这样的“前端工程师”,我想这离一个优秀的工程师还是有很大差距的。

我跟一位设计师同事聊过这个问题:

Dev眼中的世界是这样的,从墙上(物理的或者电子的)上找到一些卡片(story卡或者需求文档说明书),然后撸袖子开干,干的过程中有很多自以为是的理解,同样有一些自以为是的牛逼实践(TDD啊,自动化啊),最后功能做完,大功告成,然后接着做下一个卡片。传统的Dev,或者苦逼屌丝程序员的世界就是这样的:需求从哪儿来,不知道;做完之后谁来负责质量,不知道;最终上线的时候怎么发布,不知道;线上有问题了怎么办,不知道。

在ThoughtWorks,Dev的工作有了很大的变化,一个最明显的变化是边界的模糊。比如很多项目都不设QA角色,所有人都对质量负责,都做测试,也有OPs角色,但是大部分非生产环境都是Dev自己发布。也就是说,软件/项目生命周期中的大部分实践我们都能涉足,而且可以带来改进,提升效率。但是这只是往下游(从开发,到测试,到部署,到运维),反过来看上游,比如需求从哪儿来,Dev还是不知道。这毫无疑问是一个令人沮丧的事实,因为这需求的产生才是核心,也就是我昨天跟你聊的:一个idea如何变成一个可视化的原型,然后进一步演进为项目原型?

开发工作不应该仅仅局限在编码上,作为开发者/工程师,应该尽可能的多了解一些上下文:比如我们的项目最终是给谁用的,需求从何而来,项目是如何部署在线上的等等。

02

software life cycle

简而言之,开发者视野应该放开开阔一些。不要将自己局限在某种角色上,不但不要局限在前端/后端开发上,压根就不要局限在开发这种角色本身上,你在系统中,可以是设计师,还可以是业务分析师。虽然不一定最终要你去转行做BA,或者UX,但是更广阔的视野可以使你更加高效的发挥自己的作用,也可以在和别的角色互动式,快速的了解上下文。

我所理解的,前端不一定要熟知所有这些知识和技能,但是一定不要认为自己做好了前端的一亩三分地就足够了,不要给自己设限。跨界会给你带来难以估量的好处,一个角色做久了,难免会产生一些盲点。这时候,换个视角,从其他角色的角度来看待你的工作,又会有很多新的发现。而且不仅如此,很可能你会发现之前很麻烦,很难搞定的事情,在新的方法/视角下变得很容易。

我的故事

其实,我是一名后端开发

工作之后,我在很长一段时间是专注于“非前端”的领域。和很多刚入行的新人一样,我对计算机能触及的几乎一切领域都感兴趣:语言解释器,人工智能(遗传算法,隐式马尔科夫模型,自动纠错,模式识别),嵌入式开发,图形处理,操作系统的进程调度,进程间通信,多线程模型,各种脚本语言(python,ruby,JavaScript等等),另外,日常开发流程中的一些工具的定制化也会花去我很多的时间,比如如何配置vim,写几个小脚本来和编辑器做集成等等。更别说那些令人一听就觉得激动的编程范式:面向对象,基于消息总线,函数式编程等等。如果你感兴趣,可以看看我几年前的博客

我的上一家公司的产品是一个省级电网的收费/计费系统(电其实和我们在超市里购买的其他生活用品一样,也是一种商品)。我在那里工作了差不多两年,日常的开发方式就是ssh登陆到RHEL(Redhat Enterprise Linux)服务器上,用vim(当然有一堆的vim插件)开发C代码,调试器是gdb(对,就是那个很牛逼,但是对新手特别不友好的gdb)。

我们用C语言给Apache的httpd写了一个扩展module,大约相当于现在rack里的中间件,这个module要和后端的一个要复杂的多的模块通信,其中不但涉及网络通信,还有*nix管道,缓冲,并发等等考虑。在这两年里,我几乎没有碰过任何的Web界面上的东西(处理用php写了一两百行的页面之外)在加入这家公司之前,我在一家用Java做报表的公司工作,技术栈为J2EE。其中有一些前端的工作,但是并不很多,而且说实话,我当时有些看不太上这些技术。HTML/CSS在我心目中的地位比线程池,语言解析等差远了,所以我也没有认真地去系统学习。

在加入ThoughtWorks之前,在“前端”方面,唯一算是比较擅长的也不过是写JavaScript,而且对于前端的MVVM框架,双向绑定,模块化等高级货都没听过。且不能论HTML/CSS的最佳实践,连根据设计稿做出一个静态页面的的能力也不具备。我之前有一点JSP/HTML经验,而CSS经验也并没有超出如何画一个细线表格的范畴。换句话说,我的前端(特别是HTML/CSS)是最近才学会的。

ThoughtWorks的开发

在ThoughtWorks,很多团队是按照feature团队来组建的。相对于传统的component团队(按部门划分,比如研发组,测试组,设计组等,每个组还有可能会再细分成如用户调研,流程设计,视觉设计等等),feature团队里配备了软件开发过程中需要的几乎所有角色:业务分析,测试工程师,开发工程师,设计师(设计师一般不会常驻),有的团队还有项目经理的角色。

在feature团队里,你可以很容易看到不同的角色是如何工作的,很多时候,开发会和设计师一起来调整颜色,排版,布局,也可能和测试一起编写自动化测试用例,showcase等。也就是说,角色之间的藩篱在淡化,而就开发这一种角色而言,对于前端/后端的区分也会显得非常模糊,因为需求划分之后的story(敏捷开发中的一个术语,其实就是需求的一种展现方式)是端到端的,比如一个商品列表展示的story,会包括

  • 数据库的表结构
  • 访问数据库的ORM部分,
  • 使用ORM的业务逻辑service
  • 响应客户端的controller(消费JSON或者XML的HTTP接口)
  • 发送请求,处理响应的JavaScript代码
  • 和设计稿一致的CSS样式

而且在这个过程中还会涉及到一些外围的工具

  • 虚拟机环境准备
  • 数据库连接
  • 自动化测试(单元测试,集成测试,可能还有UI测试)
  • 数据库迁移脚本

在这个过程中,开发者需要掌握和开发过程相关的一切实践中的一切工具.

在我的ThoughtWorks的第一个项目中,我是以Java开发工程师角色加入的,下项目的时候,我学会了自动化provision,cucumber测试工具,Rails,gradle(没错,我之前用Java都是用IDE构建的,在Linux世界我用make),jasmine测试工具,Backbone.js,haml.js。

第二个项目的时候,我是以前端工程师角色加入,下项目的时候,我学会了nginx配置缓存、负载均衡服务器,gatling测试工具,Hadoop/Spark等的集群配置,还有一些和项目相关的GIS(地理信息系统)的技术栈,前后端分离策略等。

第三个项目我是以Java开发工程师角色加入的,下项目的时候,我学会了如何做性能测试,如何建立一个漂亮的Dashboard(可以用来展现CI等),而且在业余时间系统的学习了CSS3和HTML5,将之前零敲碎打的那些知识串起来,这些总结做了几次内部培训后,还整理成了一本电子书

第四个项目我又变成了一个前端工程师,但是这个项目有意思的地方是跟mobile相关,于是页面性能,体验又变成了一个重点,下项目的时候,我对无状态的Web应用,session的持久化,CSS3的动画,用Backbone.js组织多页面的方式等等又有了新的理解。

如果这些经历造成了你觉得我很牛的错觉,那我应该道歉。我觉得自己勉强可以算是个合格的程序员:对学习保持着热情,对解决问题保持着热情,仅此而已。在项目上,如果我发现了问题,我就想办法解决,如果属于知识欠缺,那我就会去学习。我还远远没有到达精通这些技术的地步,但是在工程实践领域,根据80/20原则,这些粗浅的知识足以解决80%的问题,而另外的20%,我们才真正需要一个专家来帮忙。也就是说,团队里需要有一个能解决20%的问题的前端工程师,而其他的80%的前端工作,应该可以被其他所有的开发完成,对于后端开发也是一样。

在ThoughtWorks,经常可以遇到非常优秀的跨界人才:比如做出心声(一个帮助聋哑人打电话的软件)的朱晨,他的主要角色是用户体验设计师,为了开发心声这个软件专门学习了如何开发;比如西安办公室的大牛何飞,在学完了我上边提到Web领域的绝大部分技能之后,又开始学习各种底层算法;比如UX 王倩蕾唐婉莹,也在积极的学习前端开发;同样UX角色的刘海生,又在向BA角色进发。

总结

我们缺的从来都不是前端/后端工程师,而是工程师(或者那些会系统思考,并总是想着解决问题的人)。角色划分在大的机构内可能是有意义的,就像历史上工厂里,工人被分为车工,钳工,木工,电工。但是这种模式在软件开发中未必好用,具体而微的小团队可能更具竞争力。而在一个个的小团队中,再细分前端后端就显得比较滑稽了。团队中的每个成员都应该具备基本的端到端能力(不仅仅是开发,更应该是具有业务上下文,即每个人都清楚我们要交付的最终产品是什么,以及这个产品是如何帮助最终用户的),每个成员也都需要为最终的交付物负责,而不是为自己的职责负责。

感谢张霄翀唐婉莹为本文提供的feedback

Share