程序员的分类

最近几年,我在多个不同类型的项目上,以不同的角色工作过:有时候会为项目前期做一些预研、然后为后续的交付估算工作量;有时候则在项目中期加入团队,做本职的交付工作(就是写业务代码);而另外有些时候则会帮助客户的团队进行能力建设等等。

由于要在不同的场景和不同的上下文中频繁切换,我和很多不同水平、有着不同变成习惯和思维方式的程序员一起工作过。我觉得对于程序员这样一种角色,还可以再细化成两大类。虽然都是程序员,但是其职责,技能要求,甚至思维方式都可能有很大的不同。

这两大类程序员分别为:

  • 原型类程序员(prototyper)
  • 产品类程序员(engineer)

原型类程序员

如果通过敏捷的方式来运作一个项目,在项目开始的时候,开发往往需要客户/用户,BA(业务分析师)等角色一起协作。在有一个对需求的粗略梳理之后,通过技术手段快速实现应用程序的原型,用以快速验证业务场景。由于对需求的梳理是较为粗略的,其中有很多待验证的假设,如果按照传统的方式来运作,则会有很多不必要的浪费

  1. 设计师通过Photoshop作出页面
  2. 开发根据PS文件来开发
  3. 开发的同时,业务分析可能有了新的发现
  4. 设计师对Photoshop作出修改
  5. 开发根据新的PS来做修改

这种重量级的,瀑布方式的工作体验非常糟糕,不论是设计师,还是开发,总觉得很多事情都在变化,自己花费了很大力气做的像素级别的精准调整可能在第二天就要被推翻。不过现实世界就是这样:需求的变化是不可避免的,我们唯一能做的是积极拥抱变化,而不是在最开始就尝试设计、实现完美的效果。

与其在一开始就试图设计一个完美的、确定的系统,不如放弃幻想,每次只做简陋而可以示意的原型,然后不断去迭代,最后达到可用的系统。

比如,如果设计师完全不使用Photoshop,也不产出任何的PS文件,而是和开发坐在一起,开发一个简陋,但是易于修改(比如HTML+CSS)的页面,然后向业务分析师或者客户来确认,然后根据反馈来快速调整。在迭代过程中,每次确认之前,只做非常少量的工作,这样既可以避免返工,又可以让将来的调整更容易。

如果用这种方式来运作,我们队程序员的能力要求是这样的:

  • 手速快(可以在很短时间内作出一个可以工作的原型)
  • 技术广博(前后端)
  • 可以与不同角色一起工作(最好是通过结对的方式)

比如通过python -m http.server 8080来运行一个Web服务器,而不是去考虑负载均衡或者HTTP缓存等,也无需考虑在为前端代码加入动态路由。

很有可能,一个在<head>里通过CDN引入外部依赖,然后将所有代码都写入main.js就可以让我们的想法变成触手可及的应用,在建立起快速反馈的机制之后,就可以和我们的BA,甚至客户一起来协作,并快速打磨原型,使之和客户的期望契合。

原型驱动

去年我参加了一个非常有意思的项目,在我加入的时候,客户只有一个非常粗糙的想法:在网络(不是计算机网络)中,如果某个实体被攻击了,那么对整个系统的影响是什么?比如某个变电站由于气温太高爆炸了(6·18西安变电站爆炸事故),那么哪些地铁站会受到影响?以及收到多大影响?又或者某个运营商的网络中断了,那么它对该城市的各大银行的转账业务有多大影响?

客户希望有一个比较直观的方式来展现网络中的节点,以及当故障发生时会有什么样的影响。我们从原型开始,没有设计师的参加,也没有专门的业务分析师,一开始只有有一个简单的文档来描述,然后我根据文档画了一个草图:

和客户确认了,我用bootstrap+leaflet做了一个简单的页面:

客户表示大致是这么个意思。这时候我的代码是这样的:

  • 一个HTML文件
  • CDN来获取jQuerybootstrapleaflet
  • inline的script中写了一点微小的代码
  • inline的css

确定了这就是客户想要的效果之后,我做了一些简单的调整:

  • 将第三方依赖下载下来,放到本地的一个目录中
  • 将inline的JavaScriptCSS抽取到文件
  • 写了一个简单的shell script用来启停本地的HttpServer

几周之后,客户对右侧的两个panel有一些新的需求,要求有一棵树,可以点击,然后要做各种同步:

这时候,我的代码还是几个微小的jQuery写的文件,通过简单的事件机制来完成交互。很快,客户有了一个新的需求:中间区域的图中的非叶子节点要可点击,点击之后右侧的一个panel显示该节点下的所有叶子,同样,右侧panel中的节点也需要可点击。

也就是说,中间的树需要两个不同的视图,而背后的数据只有一份(这样点击时的展开和收起才会是同步的),这时候用事件机制就很难做到了,而且代码的复杂度直线上升,而且可维护性基本没有。

基本满足客户的需求之后,团队里加入了两位同事,这时候我做了一个比较大胆的决定,用vue.js将整个应用重写,将大部分功能都抽象为组件,方便以后的扩展。完成的时候,界面看起来是这样子的:

如果整理一下思路,原型类开发的流程是这样的:

  • CDN+胶水代码迅速实现
  • 及时与客户沟通,并根据反馈修改
  • 在需求相对稳定,方向确定之后,再做实际的技术选型
  • 重构 — 甚至重写(使用靠谱的技术站进行)

比如上面这个项目,如果现在让我再来做一次技术选型,我可能会这样选择:

  • 前端框架:React + Redux + React-router
  • 界面原型:AntDesign
  • 地图:Leaflet
  • 可视化:D3

产品类程序员

另一方面,当idea已经经过验证,我们需要通过严谨的工程实践将其产品化,那么对开发的要求却又有不同:

  • 非功能需求(安全/性能/容灾等)
  • 深刻而周全
  • 自动化测试
  • 软件架构

我在去年的另外一个项目上,负责一个具体模块的开发,一起在项目上有位同事,叫侯晓成。在kick off用户故事(开发和业务分析师,QA等角色一起确认需求,进入开发阶段之前的一个敏捷实践)的时候,他常常可以把BA问的答不上话来。

我们构建的系统需要管理零售中的促销活动,促销活动可以被多个不同的门店执行,这些门店可能位于全球任何一个地方。这时候就会涉及促销活动开始和结束的事件与时区的问题。比如管理员创建了一个圣诞促销,所有商品9折。但是由于时区的存在,各个国家的圣诞节并不是同一天,诸如此类的场景,在编码是都要考虑到。

侯晓成会非常全面的考虑各种业务场景。另一方面,大的系统往往涉及多个集成点,性能、安全,缓存的使用等等,都需要开发人员在编码时就考虑到。为了保证软件的内部质量,程序员还需要有非常好的测试意识和写测试的习惯,不论是比较高层次的集成测试还是更底层单元测试。

除了交付本身,产品型程序员可能还会考虑这样一些工程实践,来保证产品从开发到上线甚至到运维的各个方面:

  • 持续集成、持续交付
  • 自动化测试
  • DevOps

小结

毫无疑问,我们在实际项目中,这两种程序员都是缺一不可,相互补充的。在需求还没有完全明朗,需要快速验证市场,探索业务方向的正确性,我们需要原型类程序员。他们通过和业务分析师,设计师等角色一起工作,可以快速且轻量级的迭代出产品的原型。而一旦市场基本确定,我们就需要产品型程序员的帮助,将产品的开发、测试、部署、运维整个生命周期都规范化,引入相对重量且长期的工程实践,这样产品在后续的迭代中才可能保持非常高的内部质量。