这两段录播发布于 2023 年,技术实现、个人经历与观点均为当时情况。
在线文档看起来仍是一个网页,但让多人顺畅地编辑同一份内容,需要解决哪些普通业务页面不常面对的问题?谢文清把它拆成两条主线:编辑器怎样理解和呈现输入,协同系统怎样在并发修改之后得到一致结果。
他曾在不同规模的公司与创业团队工作,经历后端和前端开发,当时在飞书文档团队。这次分享也直接使用在线文档展开,呼应团队通过文档开会、讨论和分享的工作习惯。约 69 分钟技术讲解之后,还有约 24 分钟访谈,继续讨论 AI、个人成长与团队文化。
让文字可以编辑,只是第一步
输入框不等于富文本编辑器
input 和 textarea 能接收输入,却很难直接承担丰富的段落、格式、表格和复杂交互。谢文清先介绍浏览器提供的可编辑能力:designMode 可以让文档可编辑,而 contenteditable 可以作用于指定区域。
区域控制很重要。在线文档还有导航、评论、工具栏等内容,并不是整个页面都应该允许用户修改。早期实现可以通过独立文档或 iframe 划出编辑范围,也可以把可编辑属性放在需要处理的元素上。
再结合浏览器的编辑命令,可以快速实现加粗、斜体等基础功能。这样确实能够搭起一个简单编辑器,但当产品要求一致的结构、丰富能力与长期可扩展性时,浏览器默认行为就不再足够。
在他给出的分级里,这种 contenteditable 加 document.execCommand 的组合属于 L0。它适合解释编辑器的起点:浏览器替开发者完成输入、选区和样式修改,开发者很快就能看到结果。但产品越依赖这些默认动作,后续要纠正和兼容的行为也越多。
同一个动作,可能生成不同的 DOM
谢文清用回车和删除回车的例子说明问题。看起来相同的操作,在不同浏览器和版本中,可能产生不同的 div、br 或文本节点组合。用户认为自己已经回到最初状态,底层结构却未必完全恢复。
加粗也类似:视觉效果接近,不代表输出标签和结构相同。编辑器如果完全依赖这些结果,就需要不断处理兼容性差异;更复杂的功能还可能超出已有命令的能力范围。
讲解里有三个连续的回车案例。空的 div 中按回车,不同浏览器可能生成不同的 div 与 br 组合;已有一行文字时按回车,有的浏览器保留前半段文本节点、把后半段包进新元素,有的则分别生成两个块;再删除这个回车,界面恢复为一行,DOM 却不一定回到输入前。若文档直接以这些结构为准,撤销、保存和协同都要承受差异。类似地,对同一段文字执行加粗,浏览器可能使用 b 或 strong;两者呈现接近,数据却不同。
因此,难点不是有没有一个 API 可以让文字变粗,而是怎样建立可以稳定理解、修改和保存的文档结构。
从浏览器行为,走向自己的数据模型
拆开输入、模型和渲染
分享把编辑器演进概括为 L0、L1、L2 三个阶段,用来讨论控制范围的变化,并非要求所有产品沿同一条路线升级。
第一个转变,是减少对浏览器编辑命令的依赖。编辑器继续利用部分输入、光标与选区能力,但把用户动作转换成自己的数据模型,再根据模型渲染。这样,文档的含义不再只由某次浏览器生成的 HTML 决定。
具体过程是先拦截键盘、鼠标等事件,判断用户想做的编辑动作,再修改模型,最后渲染出相应界面。浏览器仍可提供光标与选区,而编辑器不再把 execCommand 的输出当作内容的唯一真相。输入、模型、渲染各有职责后,换一种呈现方式也不必重新定义文档本身。
他将系统拆成输入、数据模型和自定义渲染三个部分。输入可以有不同程度的受控方式;模型可以采用字符串、数组或树;渲染则可以结合 DOM、SVG、Canvas 等技术。选哪一种,要看产品需要控制什么,以及愿意承担多少实现复杂度。
文档模型,是理解不同编辑器的重要入口
他随后比较了几类有代表性的设计。Changeset 用变更描述文档的演进,配合文本及属性信息表达内容;Quill 的 Delta 以操作序列描述插入、删除、保留和格式。变更不仅能表示一次编辑,也能从初始状态逐步构造完整文档。
Draft.js 采用分层组织的编辑状态与内容结构;Slate 则用节点树表达编辑器、元素和文本之间的关系。平坦结构与树形结构各有表达方式,也会影响后续修改和渲染如何组织。
他没有把这些产品只列为名词。Changeset 的文本及属性集合可以表达一段内容和样式怎样变化;Delta 把一次修改组织成按顺序执行的操作,比如保留已有文字、插入新文字、删除一段内容。Draft.js 区分编辑状态、内容状态、块与实体;Slate 用嵌套节点承载文档结构。比较这些表示方式,是为了看清同一个“插入文字”在不同模型中作用于什么数据,以及后续协同算法需要转换的究竟是什么。
谢文清还解释,成熟产品中同时出现多种模型,不一定是随意堆叠,可能与历史演进和兼容既有实现有关。理解当前接口时,也需要知道它从哪里来。
更强的控制,也意味着自己承担更多基础能力
下一阶段,编辑器进一步自行管理布局、光标和选区,以满足一致体验与高级功能。这里的“减少依赖”并不等于彻底停止使用浏览器输入能力:实现中仍可能保留隐藏的可编辑元素来接收输入,再自行决定怎样显示。
控制范围扩大之后,复杂度也转移到自己手里:哪里换行、光标如何移动、拖选怎样跨越块、不同浏览器和输入法怎样保持一致,都要由编辑器处理。因此 L2 并不天然比 L1 更适合每个产品。分享用这条演进线解释的是需求与代价:需要稳定数据时先收回结构控制权,需要更一致的排版与交互时再考虑收回更多浏览器能力。
分享也提到从 DOM 走向 Canvas 的产品实践,但没有把渲染技术直接当作阶段划分的唯一标准。核心仍是哪些行为由浏览器决定,哪些由编辑器自己的模型与规则决定。
多人协同,需要同时解决传输和冲突
普通富文本编辑器主要处理一个使用者的输入。在线协作文档则要让不同用户及时收到彼此的操作,并在同时修改时处理冲突。谢文清把它概括为数据同步与冲突处理,两者缺一不可。
实时通道要有故障时的行为
通信层需要支持双向、实时的消息交换,还要维持稳定。多接入点、心跳和断线重连,是分享中提到的基础措施。如果长连接无法工作,也需要考虑通过其他请求方式兜底,尽量保持可用,只是实时性可能下降。
连接本身也有失效路径。客户端可能被分配到不同接入服务,网络切换可能让已有连接消失,服务端则需要知道一条看似存在的连接是否还活着。心跳用来发现异常,重连恢复订阅与同步;长连接受限时,短请求能提供降级路径。谢文清并未把“用了 WebSocket”视为实时协同已经完成,因为它只解决消息怎么走,不能决定并发消息怎么合并。
这些机制的价值不仅在正常演示里可见,更在网络变化、连接中断或服务异常时体现出来。用户正在写的内容,不能因为底层通道暂时变化就完全失去处理路径。
房间与通道,是资源组织与业务隔离的问题
通信层还要支持不同业务复用。分享介绍了协同实体与其内部通道的分层:一个文档或协作空间可以作为较大的组织单元,内部再承载头像、权限或其他消息类型。
为什么不把每个小功能都拆成独立实体?他的解释是,推送和资源管理本身也有成本。适当聚合与隔离,需要一起设计;他用进程与线程的关系帮助听众理解这种分层意图。
在他的例子里,文档可以作为协同实体,进入它的用户带着文档标识订阅相关消息;实体内部再区分更细的通道,例如在线成员、权限变化和编辑操作。推送按较大的实体组织,业务却可以按通道处理。若把每一种细小状态都建成独立实体,连接、订阅和推送的开销会快速增加;若全部揉进一条没有区分的消息流,权限与业务逻辑又难以维护。这是资源粒度的权衡。
收到同样的操作,仍可能得到不同文档
分享用一个简单例子进入冲突问题:两位用户看到相同的初始内容,都在同一位置插入字符,各自在本地立即显示结果。若服务端只是把操作原样转发,另一人的插入到达之后,两边可能得到不同的字符顺序。
他把初始内容写作 12:A 在末尾插入 A,B 在同一位置插入 B。两人的界面马上分别显示 12A 和 12B,这符合输入要即时响应的预期。但随后若各自按原坐标直接套用远端插入,A 可能得到 12BA,B 可能得到 12AB。消息都送到了,文档仍未收敛。真正需要共享的,是对这两个并发动作的顺序和位置的共同判断。
删除、格式修改和文档块也会遇到类似问题。冲突的关键不在消息有没有送达,而在并发操作基于怎样的状态、以什么规则执行。
谢文清把它联系到分布式系统的一致性问题。对于编辑体验,本地输入应及时响应;暂时离线或网络延迟又是现实条件。因此,系统可以允许短时间看到不同状态,但需要在操作传播和处理完成后收敛,而不是要求每次击键都等待所有人同步完成。
他借分布式系统中一致性与可用性的取舍解释为什么不能简单等待全员确认:文档写作通常需要在本地立刻显现输入,网络分区时也不能让编辑区域彻底停摆。他把最终一致性视为更贴近这种体验的目标,同时承认用户会短暂看到不同版本。这里是对协同编辑需求的分析,并非凭一个 CAP 标签就能决定具体协议。
给编辑加锁,或者像文件合并那样让用户不断手动处理冲突,都不太符合实时协作文档的预期。分享因此转向 OT 与 CRDT 两类方法,解释它们怎样把冲突处理纳入算法与数据设计。
OT:先理解操作,再理解转换
三类基础操作可以组合成复杂编辑
在讲解的文本模型中,一次编辑可以拆成插入、删除和保留。保留表示跳过已有的一段内容,继续在后面处理;替换则可以拆成删除与插入。
例如从 123 得到 12A3,可以先保留前两个字符、插入 A,再保留剩余字符。删除也不是传一份删完后的全文,而是在操作里说明删掉哪一段。把复杂编辑拆成少数原子动作,才有可能列出并发动作相遇时的规则;但这些动作作用的位置仍依赖它们所基于的文档版本。
这样的操作序列描述了怎样从一个文档版本走到下一个版本。它与只传递最终文本不同:系统保留了修改动作,才有条件分析并发修改之间的关系。
转换的目标,是让不同路径到达相同结果
假设 A 与 B 都基于同一个文档版本 S。先执行 A 后,原来的 B 可能需要变成 B′;先执行 B 后,A 也可能需要变成 A′。转换希望满足下面的关系:
apply(apply(S, A), B′) = apply(apply(S, B), A′)
谢文清用菱形图说明这一点:起点相同,两条路径经过各自的操作与转换,最终汇合。公式是要满足的目标,真正的工作仍在转换函数怎样处理各种组合。
他随后看 ot.js 的转换分支:两边都保留、两边都删除、一个插入而另一个保留等情形,可以按各自的规则前进;同位置的两个插入则必须有稳定的优先顺序。操作类型只有三类,配对就已有多种情形;一旦对象、数组、块和表格加入,转换函数要覆盖的组合迅速变多。公式给出了正确性的检验方向,却不会自动生成这些分支。
同时插入时,还需要确定的处理顺序
在源码讲解中,他把三种基础操作的组合拆开,观察转换函数的分支。某些分支可以合并处理,但不能因此忽略顺序:当两边同时在同一位置插入时,采用哪一方优先的约定,会影响最终结果。
只让每个客户端按照“先自己的,再别人的”处理,未必能形成相同结果。这里引出他重点分析的客户端—服务端模型:不仅需要转换函数,还需要围绕版本、操作次序和确认消息建立完整协议。
他的演示先尝试只在两端转换:A 和 B 都从相同起点插入,在各自设备上“我的插入先发生”,于是即使两边都调用转换函数,只要参数顺序与同位置插入的优先规则不一致,仍会分叉。由此他采用中心服务端为已接受的操作确立顺序,再让客户端对齐这个顺序。这个例子说明的是该套协议如何取得一致上下文;并不意味着 OT 的每一种设计都只能使用一个中心节点。
服务端与客户端,要以一致的上下文处理操作
服务端维护操作历史与版本
客户端提交操作时携带版本信息。服务端根据它所依据的版本,判断当前操作是否需要与之后已经确认的操作进行转换,再将处理后的操作纳入历史。
如果操作基于服务端当前版本,就可以进入历史队列,版本向前推进。如果客户端仍基于较早版本,服务端先取出从该版本以来已确认的操作,逐个把新操作转换到最新状态,再入队。这个历史并不是仅供审计的日志,而是后到操作得以重新定位的上下文。转换后的结果及其版本,才是其他客户端应当接收的内容。
来源客户端收到确认,其他客户端收到广播。广播出去的可能已经是转换后的操作,因此不能把服务端理解为不参与判断的纯转发通道。
客户端必须区分“已同步”和“还在等待”
谢文清以 ot.js 的三个状态展开:已同步、等待确认,以及等待确认时又产生了本地缓冲。用户不会因为一个操作还在路上就停止输入,因此后续编辑需要保存下来。
处于已同步状态时,收到远端操作可以直接应用;如果本地还有待确认操作,就需要处理它与远端操作的关系;如果还有缓冲,又要把后续编辑纳入转换。收到自己的确认,也不意味着把同一修改再执行一次,而是推进状态并按需发送缓冲内容。这些状态和转换路径可以在 ot.js 客户端源码中对照。
比如第一笔本地编辑刚发出,客户端已在界面上应用它,却仍处于等待确认;这时继续敲字,第二笔不能假装自己已和服务端同步,于是先进入缓冲。若在此期间收到别人的编辑,就需要让远端操作与已发送操作、必要时与缓冲操作相互转换。收到确认后,客户端可以把缓冲的修改发出去,再继续等待。区分这些状态,既避免本地输入阻塞,也避免把同一次操作执行两遍。
演示把消息顺序与本地状态放在一起看
现场用两个客户端和一个服务端演示并发编辑。先到达的操作被接受并传播;后到达的操作根据已有历史转换。另一端收到远端操作时,还要结合自己是否正在等待确认,决定如何应用和更新状态。
可以把演示读成一条消息链:A、B 都基于版本 0 本地修改并发出操作;服务端先收到 A,接受它,给 A 确认,并把它广播给 B。此时 B 的本地画面已经包含 B,所以不能机械套用 A,而要把 A 与尚未确认的 B 转换。服务端随后收到仍标着版本 0 的 B,发现历史里已有 A,就把 B 转换成基于新版本的 B′,接受后广播。A 收到 B′,B 处理 A 与自己的确认,最终两边采用相同顺序。若消息抵达顺序改变,协议仍需要把每一步放到对应的版本与状态中处理。
这段演示的价值,是把公式里的 A、B、A′、B′放回实际消息过程。某个函数单独满足一种代数关系,还不足以说明整个系统正确;版本判断、确认、缓冲与传播也必须遵循相应的约定。
从文本走向文档块,操作种类会增加
分享比较了 ot.js、Etherpad 的协同实现、Delta 与 JSON 类型的 OT。它们的模型和接口名称有所不同,但都需要表达操作、处理并发并与客户端状态配合。
JSON 类型的操作不仅面对一串文字,还要表达路径及其上的对象、数组或数值变化。文本子类型可以进一步使用专门的规则,文档块则由更通用的结构组织。谢文清借此解释复杂文档为什么可能组合多种实现,而不是只使用一个文本操作函数。
以 JSON OT 为例,一项修改先指出路径,再说明该路径上做什么动作;字符串、对象与数组操作有不同类型。块文档可以用结构操作移动或修改一个块,块内的文本又可交给更细的文本算法。讲解提到飞书当时的方案也会结合不同层次的机制处理结构与行内冲突。这里没有给出足够细节复原其内部实现,但足以说明“整篇文档只用一个字符串位置”无法覆盖所有编辑对象。
随着操作类型增加,需要考虑的组合也会增长。表格、块结构与更复杂业务会提高转换规则的实现和验证成本;在他分析的中心化模型里,服务端的计算与网络也影响并发规模。具体人数上限属于产品和部署条件,不能仅凭使用 OT 就推出一个统一数字。
CRDT:通过数据与合并规则处理并发
合并规则要面对重复和乱序
进入 CRDT 部分,谢文清把关注点转到数据类型的设计。网络可能重复发送消息,消息抵达次序也不一定等于发送次序;幂等、交换和结合等性质,有助于理解某些合并规则为什么能应对这些情况。
他用计数与增减量的例子说明,发送完整结果与发送变化量会带来不同问题。即使加法可以交换顺序,重复应用同一增量仍可能出错,因此还需要明确识别和处理重复。不同 CRDT 类型的前提各不相同,不能只列出几条数学性质,就跳过完整的实现条件和正确性分析。
例子从三个节点都看到库存 100 开始:一个节点增加 10,一个减少 10,第三个不操作。若网络里只传播各自最后的 110、90 和 100,第三个节点无法仅凭这些值判断应采用哪个结果。若传播的是加 10、减 10 两个动作,不论先后都可回到 100;但同一动作因重试而执行两次又会出错。演讲者因此提到用标识或时间信息识别重复。这个例子展示了为什么要改变共享信息的表示方式,但不能把普通加减操作本身等同于完整的计数器 CRDT。
YATA 的讲解关注位置标识与确定顺序
随后,分享介绍面向协同文本的 YATA 思路。字符或插入项不只携带内容,还需要标识与相邻关系。不同用户在接近位置插入时,系统根据约定的顺序规则处理,避免单纯依赖某个瞬间的数组下标。
他以双向链表解释其位置关系,并介绍由用户标识和本地递增计数组成的唯一标识。插入项引用左右相邻项,删除先留下逻辑标记;这样即使不同设备没有同步看到同一个数组下标,也能描述“插在这些已有项之间”的意图。图示里,一个新项指定的左右端点之间已存在其他项,冲突不再是简单地把字符塞进某个数字索引,而是要按统一规则排列这些候选插入。
逻辑删除也保留了供协同处理所需的信息,之后再考虑回收。谢文清结合图示讨论插入位置、冲突集合和排序规则,同时说明现场没有展开全部证明。这里的重点,是理解位置与标识为什么需要进入模型,而不是把简短演示当成已经覆盖所有边界的实现教程。
他特别强调“意图保留”:既然新项说要插在左右端点之间,最终排序就不能跑到这个范围之外。冲突项之间还需有确定的全序,所有副本按同一规则排序,才可能在不同到达顺序下得到同一个文本。他引用论文的条件与伪代码,但坦言自己没有深入推导全部证明,也没有在现场展开实现细节。这个限定比直接宣称某个排序规则绝对正确更重要。
后写优先,同样需要定义怎样比较先后
他还提到 Last Write Wins 一类处理方式,以后来的写入覆盖之前的值。这样的规则看起来直观,但系统仍需要定义可比较的顺序,并处理相同时间或不同节点视角下的差异。
他把它放在对象字段写入等场景中讨论:当两次写入互相冲突,系统可以按预先约定的时间及身份信息决定保留谁的值。这个结果容易解释,却可能舍弃较早写入的内容,因此是否适合某个字段仍取决于产品语义;而时间戳的产生与比较也需要统一规则。
这也呼应前面 OT 的讨论:仅仅知道策略名称,不足以说明并发系统会得到什么结果;需要进一步看标识、时间和消息处理怎样配合。
技术选择,还要看内存、部署与产品要求
在对比环节,谢文清更关注当时产品中的现实成本。更丰富的标识和历史信息会占用空间,逻辑删除与回收需要设计;复杂文档又对内存、响应和体验提出要求。
他的具体担忧以 YATA 这类文本模型为例:若每个字符都是带标识和链接关系的节点,被删除的字符也暂时留作逻辑删除,那么长文档的内存开销与回收就必须认真处理。相对地,他展示的 Changeset、Delta 等表示方式更平坦,某些文本操作更紧凑。不过这是针对所比较实现的成本观察,不能推出所有 CRDT 的内存一定高于所有 OT 实现。
他对“CRDT 是否一定代表协同未来”保留疑问,也表达了企业数据管理方面的顾虑。这是他的场景判断,不能进一步概括为 CRDT 天然不适合文档,或者只能在没有服务器的网络中使用。Yjs 官方文档就提供了不同网络与持久化方式的组合;安全和权限还取决于具体部署。
他还问了一个更贴近企业产品的问题:去中心化是否本身就是在线文档必须追求的价值?如果文档需要明确的权限、审计与数据边界,数据在各设备间如何分布、何时清理,都会影响方案选择。这是他基于当时团队场景提出的疑问,并且明确把“CRDT 是否更适合未来”留作开放讨论。
分享结束时,他强调编辑与协同也只是在线文档复杂性的一部分。理解每种设计为何出现、怎样处理真实约束,比只记住某个算法名称更有价值。
AI 会怎样进入前端研发流程
访谈的第一个问题回到当时迅速受到关注的 ChatGPT。谢文清从研发流程与软件架构两个维度分析,没有把它仅仅看成代码补全工具。
在需求阶段,AI 可能帮助解释和拆解需求,甚至将其整理为更一致的行为用例。他以 BDD 的沟通方式为例,认为产品、研发与测试要共享同一种描述,本身有学习成本;工具有机会降低这种转换成本。
他的推理从一条常见交付链展开:提出需求、设计、技术方案、开发、测试、发布。团队多年希望用行为驱动开发把需求拆为可供产品、研发、测试共同理解的用例,但每个角色都要学习新的描述方式,落地并不轻松。若 AI 能把自然语言需求整理成这些用例,再由各方检查,就可能降低转换成本。这里的关键不是让模型自动决定需求,而是减少跨角色沟通时反复翻译同一件事。
设计、开发、测试与发布,也都有可以尝试的辅助环节。现场提到的产品和能力属于 2023 年语境,更重要的是这套观察方式:沿着实际交付流程寻找问题,而不是先假设某一个工具能包办所有工作。
在设计环节,他借当时的图像生成工具讨论辅助出图的可能;开发环节则以 Copilot 和 ChatGPT 生成代码为例;测试环节想到生成用例,尤其是团队对测试用例理解不一、很难长期落实自动化时。发布也可能有辅助空间。他反复使用“可能”“理想情况”来限定这些判断,尤其承认生成代码是否能减少低级错误,仍需具体检验。
工程师需要更清楚地定义边界与接口
他把当时的 AI 定位为副驾驶。工程师描述场景、目标和约束,让工具参与实现,再检查结果。工作由直接写代码,扩展为问题拆解、明确表达、生成和评审。
他的流程示意是从技术设计先抽象出逻辑,再写成工具能理解的提示,让工具生成代码,最后由人做代码审查。比起直接把“写代码”交出去,他更在意工程师能否说清问题、算法、模块边界和验收条件。模块之间的协议、输入与输出若尚未想清,提示写得再流畅也无法补上架构判断。
模块之间的关系、输入输出与协议,在这里尤其重要。只有自己先想清楚,才更容易把任务交给工具。AI 是否减少低级错误仍要实际验证,但这种工作方式会让抽象、架构与评审能力变得更值得重视。
“提示层”是设想,完整交付仍然需要人负责
谢文清还设想,未来软件架构中可能出现需要持续维护的提示层,让一部分生成行为围绕稳定的意图描述组织。主持人从组织成本和人员分工出发,讨论工具可能怎样改变工作。
主持人追问的是更现实的用人问题:如果模型调用比雇用开发者便宜,工程师会不会被替代?谢文清没有接受“把事情全部交给 AI”这个推论。他认为当时更可信的角色,是有人负责拆解、使用、检查这个工具;能力要求可能向抽象、表达与评审移动。主持人又把这个想法连到低代码平台和流水线式交付,双方都把它当成大胆设想,没有声称已形成稳定的新岗位或流程。
两人都强调学习使用新工具,同时也承认把全部工作无条件交给 AI 并不现实。他们还提到低代码与流程化工作的结合空间。这些属于当时的探索和判断,不能倒写成已经在团队全面落地的架构。
前端成长:持续学习、技术积累与业务理解
深度和广度,需要不同的投入方式
谢文清把持续学习放在最前面。无论处于什么级别、是否转向管理,都需要维持理解新问题的能力。
技术深度意味着在某个方向形成自己的分析;广度则帮助看见边界条件、关联模块和其他领域的经验。他建议对知识进行分类,区分需要深入研究的主线与需要建立基本认识的方向,避免把“知识很多”变成对所有主题平均用力。
访谈中他把深度放到招聘场景里解释:一个候选人是否真的研究透某个方向,可能成为录用与否的关键。广度则是另一种判断力——同一个技术方案,有人会考虑到跨模块影响、业务限制与边界条件,有人只看到局部实现。知识分类是为了分配精力:选一条主线做深,其他方向建立足够的认识,而不是被不断出现的新技术牵着平均投入。
业务不是技术成长的对立面
面对每天做业务需求、似乎没有时间提升技术的感受,他提醒,深入理解业务本身就是能力。相同需求由不同工程师处理,架构、边界、质量与后续维护可能差别很大。
他的成长方式,是从业务中发现值得深入的技术问题,再把它做透。站在团队或产品目标上看,技术是否解决了真实问题,也是评价工作的关键。
他回忆自己从小公司、创业团队到大公司工作的过程,也是在一个个业务里剥离出技术问题,再继续深入。因此“每天做需求”不等于没有技术成长;如果只把架构项目当作高级工作,可能看不见需求背后的领域规则和实现差异。主持人举移动端团队的例子呼应:新人觉得基础架构比画界面有价值,但同一个业务交给初级工程师或架构师完成,结果可能完全不同,差别就在如何理解需求并处理质量与边界。
主持人随后补充自己的管理经历:工作重心改变之后,仍会学习语言、国际化协作和管理知识;即使不再每天写代码,也希望保留学习技术与解决问题的能力。两人都反对把管理理解为不再需要技术判断,也不赞成因为业务表面上像是在写界面,就低估其中的工程差异。
团队文化,要落实到日常怎样做事
最后的问题是“字节范”。谢文清将它理解为共同做事的方式,并按六个方面介绍自己的感受:始终创业、多元兼容、坦诚清晰、求真务实、敢为极致和共同成长。名称可在字节跳动公开文化说明中核对。
他所理解的创业心态,既包含投入,也包含承认尝试可能失败;坦诚清晰则体现在表达真实想法、就事论事和较直接的沟通。对他印象较深的,是可以带着问题或想法,与不同层级的同事交流。
他把“始终创业”放进内部项目:投入去做,也允许先尝试、验证并承认失败。“多元兼容”与不同背景的协作相连。“坦诚清晰”是他感受尤其强的一项:有问题可以直接找同事讨论,也可以越过层级与负责的人沟通,减少只为迎合上级而表达的顾虑。他同时说,这种文化要真正落实并不容易。
高标准也不只是把需求上线,而是考虑质量、边界以及更好的实现。共同成长则再次回到持续学习,希望人在完成工作时也积累长期能力。
谈到“敢为极致”,他区分“做了”与“做好了”:同样一个需求,有人交付上线就结束,有人还会关注质量、产品没有列出的边界情况和长期实现。求真务实则要求基于事实判断。主持人觉得这种做事方式对技术人员有吸引力,谢文清随即补充,公开倡导是一回事,不同团队、不同人的实际体验仍可能不一样。
他最后保留了一个区别:文化倡导与每个人、每个团队的实际落实之间可能有距离,个人感受不能代表所有工作场景。访谈因此没有停留在口号,而是回到具体的表达、协作和交付行为。
