突破web 应用研发效能的叹息之墙

突破web 应用研发效能的叹息之墙

这篇文章可以看做是《长夜未央——企业级研发提效的下一阶段》的后续。相比两年前只是架构思路和局部的技术积累,随着240 计划 的推进,我对架构中关键部分做了更详尽可执行的设计。并且以上线一个真实社区类产品为目标,完成了”最小化“的实现。

同时这一两年我还做了一些质量、项目管理相关的工作,这些经验给了我更全面的视角去看待”技术架构如何结合团队职能等方面的改进来进一步突破 web 研发中的效能瓶颈“这一问题。

这篇文章的内容分成两个部分:

  • 对架构的关键部分的详细设计以及实现
  • 架构对对工程质量、项目管理产生的意义

希望能为读者提供一些启发。

第一部分 关键部分的详细设计以及实现

先回顾《长夜未央》中最后设计的架构图作为讨论的基础:

在下面的内容中我们都将使用”功能描述“来代替”业务DSL“一词。这是考虑到狭义的 DSL 应该包含语法解析等要素,而我们的业务功能描述可以不只是 DSL,也可以是编程语言实现的一种数据结构,所以用功能描述会更准确。

功能描述方式的设计

正如文末的一条评论中所说。对“创造业务逻辑描述,与实现分离”的尝试几十年前就开始了,主要难点还是”如何创造出足够好的描述方式”。

240 计划中我的角色为这个问题创造了一个极好的思考切入点。从”功能描述“到”实现“,这是个不断具象的过程,创造好的描述方式就是在中间找到一个平衡点。在 240 中,一方面我既是 这种描述方式 的使用者,我希望它认知成本和使用成本足够低,能让我来快速创造或者长期迭代,能让我快速教会他人,能迅速看懂并复用他人的逻辑,实现大规模协作。另一方面我又是它的实现者,要考虑认知和使用成本的每一点降低可能都意味实施成本数量级级别的增高。

为了保障创造出的功能描述方式认知复杂度足够低,我定了这样一个指标:

  • 应该能在 1 小时内教会没有任何产品设计经验,但能理解并使用互联网产品的用户来使用。
  • 当需要描述更复杂的需求,他知道学习什么能让他描述好。
  • 他知道他能描述的边界,知道在何时需要工程师介入。

同时,不断具象的过程意味着不断引入概念。在执行过程中,始终保持着优先引入“符合产品设计直觉”的概念。最终我得到的方案如下:

  • 以产品设计认为的互联网产品能提供的“能力”来将功能分类,在每个分类下提供符合认知的原子操作。这是功能描述的基础单位。
  • 主要以“推演”的方式(描述过程的方式)来描述功能,引入推演必须的概念:事件、参与用户等,引入“分支”、“循环”、”并行“等基本的过程控制结构。
  • 如果有“规则”或者“规律”,那就用它(通常是声明式的描述)来描述功能。这很可能需要工程师帮助设计数据结构和提供可视化的编辑器。

更具体一点。

一、什么是设计师认为的互联网产品能提供的能力?可从三个大类来看:

  • 认为计算机能提供的能力:
    • 数据存取。例如保存文件、保存数据。
    • 计算。例如“转换图片格式”。
    • 自动化。例如自动抓取网页。
  • 认为网络提供的能力:
    • 中心/分布 模式的数据共享
    • 用户间通信
  • 已经形成了的其他功能概念:
    • 用户系统
      • 身份
      • 关系
      • 组织
    • 其他外部领域扩展(例如资金转账系统)

二、什么是以推演的方式描述功能?

互联网产品的功能绝大部分都可以描述成“事件”+“系统如何响应”的方式。结合上一中所提供的“原子能力”,一个“建立好友关系”的功能可描述成:

(圆角图形表示事件,矩形表示系统响应)

三、什么是用“规则”或者“规律”描述功能?

当响应的过程已经是一种共识,并且功能的重点并不是响应过程时,规则或者规律就出来了。例如发布文章、发布评论的功能,我们发现系统的响应都是“增删改查”,于是可以通过只描述实体属性等要素,让系统自动生成相应的响应过程。

这个结论看似“平平无奇”,甚至不优雅:它不正交——例如”通信“的能力也可以用”数据存取“来实现;不”唯一“——有的功能既可以用推演也可以规律来表达;不“完美”——没有提出任何范式来保障完备。但却是我认为这些年得到的最有价值的结论

得到这个结论的过程经过了不断切换角色的思考。其中既有工程师关于实现成本的研究,也有过去设计产品的经验,还有对实际用户测试反馈的参考。它的目标不是,也不应该是完美,而应该是“还原需求本来的样子,符合产品设计的直觉”。接受这一点,才能真正看到它能带来的重大影响。

希望读者保持耐心,我会在第二部分详细说明。下面我们先来看最小化实现,产生更具体的印象之后再讨论影响会更容易一些。概览如下:

(蓝色表示的是用户输入)

对整体的说明:没有创造新的 DSL 语言,而是对其中适合声明表达的地方设计了数据结构和编辑器。没有做前端的可视化搭建类的工作,而是创造了前端框架和组件库用来解决一些关键的领域问题。

对每一种功能描述设计的目标都是:同时从产品设计和工程师的角度出发,探寻产品设计中能表达的极限。产品设计能表达得越多,其能独立创造的价值越大。同时也意味着工程师做的”翻译工作“越好,整体效能提升越大。

在这一章节的最后会说明这个最小化实现如何渐进变成完整的架构,先看其中的重要部分的设计和实现:

数据存取能力

互联网产品的大部分功能都可以用数据存取来描述,其设计和实现都是整个架构中最重要的。为了探索产品设计师在描述存取功能时能表达的极限,我们要先搞清楚数据存取的需求是如何变复杂的。

把数据相关的应用逻辑看做是很多次存取操作的集合的话,有两个变复杂方向:

  • 从局部看,数据之间逻辑关系越来越多。例如,人的“已婚属性”和他的”配偶“属性是有内部联系的,”已婚“甚至可以表达成"有没有配偶"。
  • 从整体看,领域的动作会越来越复杂,一个动作可能有很多存取操作。

我们注意到,越是模拟现实,也越复杂,因为需要引用现实中的逻辑关系和动作。而越是虚拟的应用,例如任务管理系统等,产品经理的对存取功能的描述几乎与实现别无二致。

那复杂到什么程度是极限,需要工程师介入呢?

结论是多复杂都不需要!这个结论并不奇怪,领域的复杂逻辑本来就应该由产品设计者来负责,设计者都不能完全掌握领域逻辑,如何设计产品?并且实际上,处理复杂业务逻辑的一些常见工具,如决策树、驱动表都不需要具体的编程知识,复杂度也并不高,应该是产品设计基本素质中必须掌握的思维工具。

那是不是我们提供了足够的表达能力后,产品设计者就能完全自己来写数据存取相关的逻辑,自己实现“领域模型”了呢?我们应该再反过头来结合工程师的经验,看看还有哪些工作是需要工程师做的:

  • 提供更多的存取能力和相应表达方式。例如“查询某人几度内的朋友”,需要用到图关系查询。
  • 因为性能要求,而不得不与具体实现强结合在一起的逻辑。例如现在流行的“热榜”功能,热度需要随时间下降,我们不可能做到读取时实时计算。因此需要用到缓存计算结果,需要综合性能和体验考虑何时触发计算。这时真实实现就与过程式的描述偏差得比较大了。
  • 互联网产品可能会用到一些互联网相关的领域知识,这时需要工程师作为领域专家参与。例如“需要查询某个 URL 相同子域下的所有页面”,关于子域、路径等信息就是需要工程师提供的领域知识,对存储的数据结构也有相应的要求。

综合以上的考虑,我得到的方案是:

  • 以 ER 为主要抽象,辅助以表等其他简单抽象。对 ER 提供了可视化的编辑能力。产品设计中的 ER 区别于工程中的 ER,实体是指有明确生存周期,需要管理的概念,属性则不是。
  • 对于复杂的数据间关系,提供了计算属性作为一种逻辑载体,此外具有一定领域知识的属性可以请工程师介入提供。
  • 提供“统称”的能力,可以理解成编程中的“泛型”。通常,有混合查询才需要“统称”。例如任务管理系统中的“需求”和“缺陷”都看作是“工作项”,如果有对“工作项”的统一查询,那么我们落地到关系型数据库时可能要将共同的字段要放到同一张表。
ER 图

在进行实现之前考察了大部分流行的 ORM 类型工具,没有找到符合要求的,存在的主要问题是:

  • ER 能力不完整。Relation 上也是可能存在数据,很多工具没有支持。
  • 不支持自己扩展属性。
  • 表结构优化达不到要求。

要保证产品能达到线上质量,这些问题都必须克服。最终我的实现方案:

  • 存储仍然落到了关系型数据库上。
  • 将“复杂属性”模板化了,通过实现接口就能为产品设计提供新的属性,以及属性相关的方法。
  • 自动根据 ER 等信息提供增删改查的接口,同时会汇聚复杂属性所提供出来的接口。例如前面例子中所说的“查询 URL 子路径的 xxx”。这样使用了 URL 作为属性类型的实体就都能被这样查询到。
  • 数据库的优化策略写成了代码。根据 ER 数据中所提供的“消费信息”来自动决定是否应用。例如我们将一个字段标记为“lazy”,意味着它可能是大字段,可以延迟读,那么会自动被拆出来。
代码扩展复杂属性

目前表结构优化实现了:

  • 1: 1 关系合表
  • 字段上标记 unique:拆表出去,将属性用 id 替代值。
  • 字段上标记 lazy:大字段、可延迟加载的字段拆出去。

在实现中,从 ER 到真实的建表过程中的每一步都产生了中间数据结构,利用这个中间数据可以快速实现中间件机制。也可以用来调试、查看。特别是在真实场景中,最终线上数据库变更都是需要管控的,有了这些中间数据结构,就可以方便地从 table 变更追溯优化策略甚至追溯到 ER 变更。

查看表生成的中间数据结构

推演描述设计(事件 & 响应)

决定以推演来作为主要的功能描述的背后,是对”什么是最好的功能描述方式“这个问题的探索。注意,它并不是这个问题答案。为了回答这个问题我花了很多时间去回顾设计模式、编程范式、逻辑形式等等相关主题的历史和发展。

最后完整的结论是:

推演并不是”最好“的,而是最常见的,几乎没有认知成本的,因为人类的绝大部非思考都是基于推演。并且用它一定可以描述出想要的功能,因为我们对于功能是否满足需求的”第一判断“就是基于”我怎样输入,系统应该怎样输出”。这就是事件和响应。

那为什么不能说是”最好“的?因为也有的功能描述中响应过程不重要,重要的是规则和规律。例如做个做个公式计算器,输入输出过程极其简单,逻辑的主要部分是其中的公式。

另外,从对范式的深入中也可以看到,不同范式的意义其实在于”从哪个角度去管理逻辑最合适“。如何定义”合适“?即”对逻辑中最复杂、最易变的部分是否形成了有效的管理”。例如 FBP 是从”数据会被谁处理,处理的顺序是怎样的“这个角度去管理所有逻辑的,所以对于资金账单类的业务逻辑极其合适,因为这类系统通常是有固定的流水处理过程。

再进一步,如何定性定量地去评判是否对逻辑的复杂部分有效管理了?管理,最终是人脑去管理,人觉得易读易写易修改,就是有效管理。继续探知人的认知会得到结论:人善于线性思考,思考中切换上下文的成本极高,甚至上下文的“出入栈”成本也很高,此外思考中的上下文记忆容量很有限。因此当复杂的东西是非线性的时候,最好使用图等工具辅助推演。

根据这些结论,我们才得到了以 “推演” 和 “规律规则” 描述共同存在的结论。

再进一步看对于如何表达推演的设计。首先根据复杂度我们将描述可以分成三个等级:

  • 单次事件 & 响应的描述。例如用户触发存文章的事件,系统响应存入,功能描述就结束了。
  • 跨事件的描述。通常可能有多用户参与,例如好友申请,需要审批的内容发布等。
  • 将跨事件的描述看做一次会话,那么更复杂还有跨多次会话的描述,可以无限嵌套下去。

实际上除了最低等级的可以直接用线性的代码描述外,往上都适合用图等结构描述。以前面最简单的交友示例来看,复杂程度还远不止画出来这个样子,其中还有分支,例如用户取消了申请,用户ID被注销了等等。现在流行的 web 框架以 controller 作为调用的入口,基本都只适合处理等级一的逻辑,当要处理更复杂的情况的时候就会发现:

  • 流程的代码散落在各个 controller 方法里面。
  • 片段代码往往要自己加载流程中的上下文,进行各种意外情况检测。

再加上事件的触发往往来自于界面,要联系到用户的触发行为来理解整个流程会更加难。如果没有正确的文档指引,从代码角度来维护逻辑的难度会急遽上升。而关于文档的有效性和写文档的意愿,又回到了前面的问题。如果框架、工具能直接提供等级一以上的逻辑表达能力,那么研发效率和可维护性又能上一个新的台阶。

在最小化实现中作了一个流程图工具,参考了 UML 活动图,flow language 等。具备并串行、分支等能力。框架可以根据这个数据结构会自动为每个响应的代码片段注入上下文,进行各种边间情况检测。

流程编辑器

在响应逻辑中要提供“应用当前状态”、“历史事件栈”、用户等必备元素保障一定可以完备的描述出功能。

什么是“应用当前状态”,实际上数据库等持久化存储里的所有数据就是整个应用的状态。

什么是“历史事件栈”,为什么提供?整个应用里之前发生过的“事件”总和就是历史事件栈。举个需要用到历史事件栈的例子:在好友申请的功能中,我们可能会设置“对同一用户的申请,如果被拒绝过三次,那么就不能再申请”的规则。在这个描述里就需要查询“过去是否发生过被拒绝”的事件。在一般的实现中,我们可能会手动再创建一个“针对某用户被拒绝过的次数”的状态,然后在“好友申请”的事件回调里判断,如果拒绝了就手动“+1”。虽然这样写也可以实现功能,但语义又发生了一些偏离。同时使得工程可维护性又下降了一些。

在实际的质量工作经验中看到,当逻辑里有太多这样的“历史状态”时,我们需要在每个影响它的事件中都正确维护它。同时,一个事件可能会影响很多历史状态,这会使得一个事件响应的代码越来越臃肿,并且代码间的逻辑关联变得越来越小。而在消费这些状态的地方,我们得到的只有一个状态名字,当缺少正确的注释和文档时,想去通过“操作”这些状态的代码处去推断它的意思,就会变得极为困难。

如果框架本身就能提供对“历史事件栈”的查询,那么会极大地提升逻辑的清晰程度和和维护性。

但记录所有事件会有性能负担。在实现中我们可以通过简单的语义分析,去“自动决定”需要记录哪些事件,同时可以进一步提供一些常见操作(例如 COUNT)作为标记,来判断是否要进行“合并”等优化来提升性能。

// 根据语义分析得到需要记录的事件为 event.refuse,需要记录的数据是 COUNT 后的总数,那么可优化为每次发生时 +1即可。
const refusedTimes = defineState.COUNT(event.refuse, {index: ['sourceId', 'targetId']})
if (refusedTimes.find({ sourceId, targetId }) > 3) {
  // ...
}

最后关于推演描述还有两个有意思的设计:

一是我们可以将过程描述本身也当成一种可以使用的数据结构,这样做可以使逻辑复用发生巨大的变化。我们在复用时看到的将不再是一个简单的过程(函数)的名字,而是能看到具体流程(复杂的机构可以用图),甚至能够动态的去覆写、扩展而无需提供者手动支持。这对于不管是使用者还是提供者都能降低很多负担。但具体的实现就需要“基于非行列式字符编辑器的 IDE“的支持了,这也是在 240 中提到的原因。

第二个是应用中的”事件“不只是来自于界面上的用户行为,应该还包括”应用中的任何变化”。我们在描述逻辑时也常常会用到“当 XXX 数据更新时,就 YYY”这样的描述,本质上这是一种归纳逻辑。如果我们严格限制应用中的任何变化都需要使用封装好的 api,那么就可以把 api 的触发来当做事件使用。针对调用时传入的不同参数,也可以提供一定机制来将其命名成业务语义更强的事件。这使得不用再手动维护一个事件总线,也彻底解决了业务事件爆炸的问题。

目前的最小化实现中,我已经完成了基于 api 调用的事件机制。也完成了提供逻辑控制的编辑器,用来产出复杂程度 1 以上的逻辑数据结构。

基于 api 的事件响应

视图层

对视图层的设计和实现分成两部分:

一是写了个轻量的框架,直接将原子 api 和有动作语义的 api 暴露到前端。传统的 MVC 框架一般将 http 请求映射到 controller,再由controller去调用 model/service,前端通过 ajax 来发出调用请求。很多时候发现 controller 只是转发了调用到 model 而已,像权限验证之类的工作基本上也都是中间件了。再探索一下会发现 controller 实际上只在两类情况下有意义:

  • 因为安全需要不允许使用原子操作
  • 一个过程中有多次原子调用,前后调用间有数据依赖,全部放到前端会出现中间大量中间数据进行网络传输,产生性能问题。

但这两类情况,我们都可以结合前端编译工具的能力将相应的调用从前端代码中剥离出来,生成服务端的 api,将前端调用重新替换为对生成的 api的调用即可。在我一个人同时负责前后逻辑的开发中,这个小小的设计极大地改善了开发体验。

二是写了个前端框架(Axii)、以及组件库等周边设施。主要是为了解决以下几个问题:

一、绝大部分应用中为了体验的一致性也好、为了减少研发工作也好,都需要复用。现有的框架对“复用”的实现支持得还不够。对组件的提供者来说,被复用的场景越多,组件可能越臃肿越难维护,即使是使用允许外部扩展的机制,也会慢慢发现几乎所有的地方都可能外部需要扩展或者覆写。对使用者来说,组件提供相应的功能和扩展机制就只能等着开发者更新。因为现在的前端即使同样的框架,不同的组件开发者往往也有不同的打包工具,要自己修改源码去得到想要的成本太高。

二、仍然存在大量的视觉还原等耗时工作。业界从视觉稿生成前端的工具远没达到好用的程度。

三、组件的状态和应用的状态需要由框架提供统一的解决方案。这样才有可能构建构建更好的调试、测试工具。

为了解决这些问题,和流行的框架相比,Axii 的主要特点如下:

  • reactive data + jsx + function 的写法来开发组件。没有 template,但实现了精确更新来提供更好的性能。相比 template,function 更灵活、认知负担更小。
  • 组件自动支持受控、非受控两种模式,自动支持回调、回调内阻止默认行为。不需要开发者单独处理这些逻辑。
  • 可通过 feature based 的方式开发组件,可实现动态扩展或者覆写组件,使用成本低。对开发者来说通过框架就能获得极大的可扩展性,维护成本更低。
  • 全新的样式系统。拆分 layout 和 非layout 样式,可快速得到页面骨架。将 design pattern 代码化,不必再逐个属性地还原视觉,可以通过 pattern 计算得到。样式逻辑可与代码交互逻辑分离。

除了框架本身外,我也完善了周边设施:

  • 组件库。覆盖了常见的原子组件。
  • Axii-x6。x6 的 Axii 版本,前文中的 ER 图和流程图使用。
  • Axii-mdx。mdx 的 Axii 支持,Axii 官网使用。

更多的能力不在此赘述,参见 Axii 官方网站:

未来或许会有一篇文章单独讨论前端的领域问题。

渐进推进的产物

正如前面的 ER 图、流程图所示,我们并不需要一开始建立完整的功能描述,也不需要发明语言,而是可以先将一部分业务逻辑用数据结构表示,独立成文件,给与特定的后缀名。然后再为其提供合适的编辑器。

在制作编辑器的时候进一步想到,只要将编辑器在线化,通过 openapi 和仓库链接,一个非常简单的,可以让产品设计师直接参与进来的协作平台就出来!产品设计师编辑完直接又保存成相应的数据结构提交到仓库中。配合上 CI 工具设置可以实现产品设计师自己发布。推进过程中,我们只要逐步提供更多的数据结构和编辑器便逐步完善架构。

这个方案的实施成本极低,在我已上线的平台中,甚至允许业务开发者自己开发编辑器,自己定义数据结构。即使一开始不提供完整的数据结构,只是将“需求文档”也管理到代码仓库中,将其中的一些简单系统变量,例如列表每页应该有多少项、默认时间是什么时候等解析出来,直接由代码使用,也能极大地减轻工程师的低价值工作,让文档活起来,从而提高系统的可维护性。

可用来编辑任何格式
可以编辑图片

同时对于运行时的代码一开始也没有必要使用“生成”的形式,直接能运行更便于前期调试和稳定框架。但等研发力量成熟之后还是应该实现”生成“运行时代码。一是我们可以继续在生成的代码上覆写来满足极限的性能等要求,二是对于”覆写“代码多少的管理可以作为一种评价”描述抽象是否契合“的指标,但需要覆写的太多时,我们就知道该要重新设计抽象了。我们不可能找到完美的抽象,所以才要主动去管理起抽象变化的过程,不能纯粹寄希望与架构师考虑的深远程度。

第二部分 对工程质量、项目管理产生的意义

提升效能的最大意义并不是降低工程师的工作量,而是释放创造产品的生产力。这个架构的关键之处在于,以可运行的功能描述作为中间交付物,更好地分离了产品设计师和工程师的职能:

一方面增强了产品设计师的表达能力,使其可能在无工程师介入的情况下创造可用的产品。

另一方面降低了工程师翻译逻辑等的低价值任务,使其有更多去抽象总结技术能提供的能力,进一步提升可供产品表达能力。

这两方面会互相促进,形成飞轮。除了直观的对研发的提深,接下来我们继续看看其对质量、项目管理的重要价值。

对于软件质量的价值(可维护性)

在质量相关的工作中,我看到的大公司这种流水线式生产的质量核心问题是”理解原有逻辑的成本太高”。在对故障的统计分析结果中看到, 90% 以上的故障都是变更时理解出错产生的。从变更时的研发流程来看原因很简单,工程师:

  • 先要需要理解原来原来业务逻辑。可能没有文档,可能原来的业务方换人了,可能理解有误。
  • 再要理解原来的实现。可能代码可读性很差,可能其中有些性能之类的隐晦考虑。

这个链路中两次理解可能出错的几率太大了,特别是对于流水线式的、可能会经常换人的研发方式。

文档缺失或者腐化严重是造成业务理解错误的主要原因,深入到研发过程去看为什么产品设计不好好写文档:

  • 很多产品设计师觉得文档是一次性的,特别是互联网的业务初期可能只是尝试,可能很快又会下线,写文档太麻烦,不如直接口述给工程师。即使写了,慢慢也发现自然语言描述不易于长期迭代。不好写,写了很快没用,所以不想写。
  • 同时对工程师来说,如果文档写得不好,阅读起来反而产生更多障碍,不如口头实时交流,有问题当面问效率更高。

很多公司都把质量管控工作的重心放在了 code review、人工的流程管控上。这些工作应该做,但解决不了核心问题。首先对于 code review 来说,现在的 web 应用研发方式中有大量的“使用框架的翻译型工作”,严格去检查都相当于自己再写了,耗时并且价值极低。应该要做的是对其中”特异的“关键设计的 design review,这应该发生在写代码之前。其次,流水线上的流程管控形同虚设,就像 code review 一样,上线时的主管审批其实很难事无巨细去检查正确性。既然不能验证正确性,还怎么管控?

将”可运行起来的功能描述“作为产品和工程师的中间交接物可以解决核心问题。对产品设计来说必须保持其正确性才能运行起来,对工程师来说工作回归到有价值的能力研发上,让 code review 也变得有意义起来。

更深一层思考会发现,这样发展对保持和促进产品设计能力也有重要意义。如果仍然长期保持交接物模糊、人工传递逻辑的状态,产品设计的能力也会慢慢腐化掉:

我们构建的产品终究只是思维的产物,只是为了完成特定目的,并没有客观存在的“规律”,有的只是一时的总结。随着产品越来越复杂,任何之前总结的规律都可能被打破。可怕的是,我们常认为工程师“提前考虑到了,使得发生变化时开发顺畅“是个优点,但实际上对整个系统来说是个巨大的问题。

一是通常情况考虑得越多,抽象越复杂。发展过程中用到了是好事。如果没有用到呢?其维护成本如何计算?特别是如果业务的变化偏离了预计,提前考虑反而成了新的负担,这如何衡量?

二是很多时候业务所提出的所谓”特殊情况“其实是可能是因为业务逻辑上存在漏洞或者本来就有矛盾导致的。特殊情况越多,可能出问题的可能越大。工程师的”宽容“,反而掩盖了这种隐患,实际上得到的也是错误的抽象。

所以功能描述、包括对未来变化的预测必须回到产品设计师的职责之内,同时有”明确可衡量"的交付物作为检验的指标,才有可能综合地让研发质量上一个台阶。

对于项目管理的价值

项目管理是效能评估管理的基础。我在项管工作中发现,效能问题远不只是从研发速度这个角度解这么简单,特别是公司越大流水线越长,问题越复杂。

我们从不同角色的不同需求来分析。角色可分为三种:

  • 顶层管理者。资源投入者,项目最终受益者。
  • 过程管理者。通常是产品经理,团队规模较小时,通常顶层管理者也是过程管理者。
  • 实施者。团队的产品设计师,研发工程师等。

在很多传统的项目管理工具中,顶层管理者的需求其实被忽略了。我们认为他关注进度,但其实他并不关注进度,他关注的是”进度风险“。他站在的角度是”资本投入“的角度,他需要清晰看见的是”成本“、”收益“、”风险“。同时我还发现越来越多的管理者需要工具承载”预期“,以帮助其不断通过”预期“和”收益“来修正自己的认知,也同时作为工具来评判自己管理团队成员的认知正确程度。更进一步,顶层管理者是期待看到”成本“和”收益“细节的,期待能通过对细节的分析调节来降低成本提高收益。但现有的大部分基于”图文任务列表“的管理工具并不能满足其需求,一般只能看到项目级别投入了多少人,花了多少时间。至于项目中有多少人是做”基础设施“的,基础设施又支撑了多少场景,产生了多少收益,目前的工具难以回答。

对过程管理者来,他的需求是管理真正的进度,需要向上正确汇报。但软件越复杂,越难正确描述进度,可能任务列表看起来就只有一个任务了,但却是最难的一个花费了最长的时间。同时大部分管理软件也没有将软件中的依赖关系等内在逻辑表达出来,过程管理没有依据去提前预知风险等问题。在实际工作中发现很多过程管理者逐渐沦为了人肉沟通汇总的工具。

最后看实施者的需求。他们需要的其实“协同工具”。来看一个典型的流水线中的流程和需要协作的角色:

在大公司经常听到这样的问题:“看起来每个人都很忙,但是忙到什么事情中去了?为什么一线说白天开会,晚上才能写代码?”其罪魁祸首就是协作中的“非研发任务”负担:

  • 理解产品
    • 整个产品流程是怎样的?有图吗?找谁问?
  • 理解团队
    • 我和谁合作?这个问题找谁?
  • 理解迭代上下文
    • 迭代中为什么有的功能没做,有的做了?
    • 当时为什么是这样设计的?临时方案?
    • 新的迭代修改会不会改出 bug?
  • 理解研发任务
    • 测试提的这个 bug 是哪个页面上的?
    • 这个配置的数值为什么是这样?

如果工程师还要并行处理多个项目,那么上述过程就还要重复 N 次,怎么还有时间写代码?

虽然看待问题的角度不同,但问题背后的原因其实是同一个——“产品与实施者之间的可定性衡量的协作交付物的缺失”。举个例子来说明,交互设计师和前端研发同为实施者需要协作,他们之间一般是以视觉稿作为明确交付物的。有了这个交付物,我们就可以从管理者角度回答“是不是设计师出视觉稿太晚了所以导致延期?”、“这个页面的 XXX 交互改善了转化率,是谁做的?花了多长时间做的?”。

如果我们能将产品功能描述作为产品与研发中间的必须交付物,并以此为中心建立管理体系,那么项目管理、效能将得到重大提升。

一是从实施中协作的角度,可以实现所有人工作就像在看一份图纸的效果,我们可以把“实施者”、“进度”、“缺陷”等等信息都全部关联到结构化的页面、功能描述上。

二是从进度管理的角度,可以清晰地看见关联、瓶颈,预知风险。

三是从顶层投资的角度,可以以产品功能描述为中心关联到产生的受益和投入的研发,真正展示出成本和收益的细节。

(出于工作的要求,我暂时不能展示更多的设计图来说明。或许未来会有一篇文章剔除公司的信息来进一步说明。)

当然,不一定是要以文中所述的“产品功能描述”作为中间交付物,只要达到“可定性衡量的交付物”即可。只不过我们架构中的“产品功能描述”可以用“是否能正确运行”作为“定性衡量的标准”,可以在研发上直接提升速度释放产品设计的生产力,这应该是最佳的选择。

后记

这篇文章也可以看做是 240 计划研发部分的总结。回望这段经历,其中最有价值的有两个:

一、我即是设计者,也是实现者,既不想做低价值工作,又要考虑实现成本的这个视角。这个视角自然地就把职能不清晰、低效重复等问题放大了。其中既有需要具体技术支持解决的问题,也有需要改变研发模式的问题。有了这些实际的问题作为思考的线索,一切都清晰了起来。

二、从一个人研发,到思考怎么邀请他人协作、怎么管理,到结合之前带团队和做项管工作的实际经验,给了我更全面的视角去考虑最优解。越来越多的时候感受到我们一点也不缺做好工具的能力,缺的永远是正确的方向

其中我也收获了很多同行者的观点,希望这篇文章能有所回赠。

路还长,但很有希望。

编辑于 2022-09-23 15:15