当前位置: 首页 > 科技观察

码农孙育聪:《人,技术与流程》

时间:2023-03-17 23:23:57 科技观察

先自我介绍一下。我于2007年加入谷歌,在山景城总部担任谷歌SRE。我今年早些时候回到中国加入Coding。在谷歌,我参与了两个项目。第一个是Youtube,包括Videotranscoding,streaming等,Google的存储量很大,每个月有1PB的存储量。存储和转码后,我们也做了GolbalCDN的峰值,最大达到10TB。我们在全球有100,000个节点,每台机器都以24核满负荷运行。然后我离开了Youtube团队,加入了GoogleCloudPlatform团队。我们做的主要工作是管理谷歌在全球的机器,大约有100万台机器。我离开谷歌之前做的是OmegaProject,一个管理谷歌整个云平台的任务调度和协作的集群管理系统。很多人可能会说“随意”,因为这些都是中国不存在的网站。(笑)从Google回来担任Coding的CTO也是我人生中的一个很大改变。最近在知乎上看到一个很好的问题《离开大公司去小公司做CTO是什么体验》,摘录了一个很好的回答:《以CTO的名义招聘、培训、鼓励程序员》、拉网线、检查机房、装系统,都是CTO要做的事;讨论方案、推方案、定方案、确认进度、拖进度、安抚程序员、挨骂听老板的,安抚老板也是CTO的职责。”Coding不包括在内,我的工作也包括Coding,很悲哀。(笑)所以我把这个问题归类为:什么是CTO:首先,他是公司里的鼓励者(其实我作为鼓励者的形象不是很合适)第二,他可能是网络管理员,我确实做过,接网线,架设服务器,第三,可能是擦鞋垫,程序员对我很不满意,老板对我也很不满意。那么我们把这三个角色作为对应:第一是研发人员的管理,包括如何招聘合适的人,如何让这些人更好的合作;第二是研发技术和研发环境的管理,就是有了这些人,大家怎么才能更好的协作,更有效率的发展;第三是研发流程的管理,如何让公司的机器更加灵活顺畅。那么在这里我提出了三个要素,人、技术和流程,这是一个公司研发体系中不可或缺的关键要素。那我们先说说人。初创公司需要全栈工程师。这个全栈工程师长什么样?总的来说,他就像一个英雄,踏着白马,手持着金枪,什么问题都能解决,能单挑上百人。但是有时候我们招不到全栈工程师,就招一些全栈工程师,这也是我们最近发明的一个词汇。这个人就是他无所不能。写完网站再写iOS,最后写Android。找到这样的人也很难,更难的是把这些招募来的人员合理安排在一起,组织他们合理配合。要解释Coding在研发人员管理上的演变,我先说说Coding的服务架构。这是我们去年5月份的服务架构(第一版上线)。它非常简单,只有一个核心程序。经过一年多的演变,现在变成了这个样子。我特意把字写得很小,因为不想让大家看清楚,其实错综复杂,不一定正确。一个简单的架构是如何一步步演变成一个复杂的?在这里我想介绍一下康威定律,即“任何设计系统的组织,最终的设计都等同于组织内部和组织之间的沟通结构”。回忆Coding以前的沟通方式,老大说“我们要创建一个新的功能”,大家开始拆功能,前端怎么改,谁来做,后端要不要改,谁来做去做吧,还有service层,DB层,测试,部署,每个人做不同的事情,每个人只做一件事,每个人都在管道上拧螺丝。这样会发生什么?前端程序员在等待后端程序员,什么时候连接接口才开始工作,后端程序员在等待数据库,我什么时候开始工作之前添加这个字段,测试和部署是甚至更多的等待。每次开项目进度会,老板都会问功能做到什么程度了。前端说后端没写接口,后端说数据库还没定型。这种情况经常发生。那么,今天的编码采用了一种组织方法。我们称之为全栈全栈。这个full-stack的意思和普通意义上的full-stack不太一样。我们指的是产品和功能上的全栈。它是如何实现的?其实,对于任何一个合格的程序员来说,语言都不是他的瓶颈,也不应该是他的限制条件。我们更希望的是我们公司有更多这样的全栈工程师。当我们在做任何一个功能的时候,我都会告诉这个人,你负责这个功能从头到尾的实现。需要改前端就改前端,需要改后端就改后端。一开始可能很难做到这一点。后端工程师对前端不是很熟悉,前端工程师对后端也不是很熟悉。然后我们使用一些其他的方法来克服它。这个过程迫使组织内的知识共享。很多大公司的工程师可能只关心这一小块。比如前端工程师只写前端的东西,我不管后端。理由是我不敢动,大家也不让我动。在小公司里,大家一定要了解公司使用的各种技术,大家一起努力降低系统的复杂度。全员全栈是我们公司的战略方向。我们希望每个人都有主人翁意识。研发环境和研发工具的调整,接下来我们讲一下我们是如何通过技术手段来辅助的。技术手段在一个研发环境中有三个要素:第一是如何管理和运行代码;二是操作,前后端代码操作复杂,如何调整。第三点就是写完之后怎么部署运维。开发环境和生产环境经常脱节。为了把代码讲好,我先给大家介绍一下在谷歌做码农是怎样一种体验。谷歌最厉害的地方在于它的研发体系经过了10多年的积累和锤炼,现在已经是一个非常高效的体系。那么这个“不存在”的公司是如何管理自己的代码的呢?第一点,整个公司只有一个仓库,只有一个版本控制系统。这件事说起来容易,但要真正做到却很难。很多人觉得这个东西好像是可行的,但是他仔细一想,难道我们都用SVN吗?显然不是,那SVN没用,Git也没用,怎么可能只有一个仓库呢?只有一个仓库的问题是所有的程序代码都在一个目录下。如果你有一个特别大的硬盘,你把整个公司都拿下来了,那一个目录下就是很多个子目录,可能是几十个子目录,整个公司代码可能有几百G。如何同步、管理和运营是谷歌独有的秘方。这有什么好处?第一,你可以看到任何人的代码,你可以看到服务是如何实现的,为什么会出现异常,出现什么错误;第二,它带来了更高效的复用方式,比如我自己写的一个小程序。我的一个Gmail程序使用了一段bigtable代码,所以我可以在代码中引用它。反正大家都在同一个目录下,编译环境也一样。有了这个repo之后,还有一套编译系统,这个编译系统可以一键编译任何程序,我跑一个Gmail也是一行程序,一个命令就是编译程序build加上Gmail的路径到compile,编译Go、Python、C++都是一个命令,你不需要关心底层是如何实现的,运行即可。至此,部署就完成了,开发就很轻松了。也就是说,如果我需要用到一个服务来写业务代码,那么我可以很方便的启动这个服务,也可以对这个服务做一些小的改动,然后重新运行。一切都很容易。这里说多了大家会说:道理我们都懂,但是怎么去实现呢?那么Coding是如何组织我们的代码的呢?因为我们没有谷歌的中心化代码服务,为了调整我们的代码结构,让大家可以复用和引用彼此的代码,而且因为我们自己提供Git服务,所以我们也使用Git仓库,但是我们每个项目都是一个独立的仓库。比如我们的前端代码放在一个仓库,后端代码放在一个仓库,每个服务放在不同的仓库。这种方法产生了一个问题,如何同步?我们使用Google的开源项目Androidrepo。你在做Android开发的时候可能用过。这个回购是什么意思?也就是他定义了一个Workspace,它有固定的格式/结构,然后你用repo工具同步,这个repo应该去哪个commit,那个repo应该去哪个commit,整个公司用同一个Workspace这样就保证了大家看到的代码是一致的。定义这种代码结构的好处是:第一,我们有代码阅读的功能。比如我们的Coding代码阅读可以基于这个Workspace,可以看到各个项目的代码和相互引用的状态;二是质量分析。您可以在此工作区上进行质量分析。这个工作区有一个repo.sh,这是一个命令。比较重要的是有一个default.xml就是这个命令的配置文件。然后你可以打开Workspace并运行reposync,它会自动将每个组件更新到最新版本。我们也很鄙视xml但是没办法,这个程序这么写,其实很简单,主要定义了很多Project,这个Project可能是代码仓库的路径映射到本地路径,它有一个更高级的功能,它可以同时同步多个项目,即当你输入repo.shsync-g=4时,你会打开4个线程进行同步。实际上,这很容易。有了这个东西,就打开了这个局面,也为大家下一步开发开发环境做了铺垫。有了工具,我们真正想要的开发环境是什么?我对Google的感觉是,我们想要一个统一的、编码的、可复制的、可复制的开发环境。第一,每当有新同事来公司,都是自己带电脑,或者用公司的电脑。他使用的工具不同,环境不同。公司的代码怎么跑在自己的机器上?其实,这是一件更难的事情。大家的解决办法可能就是写文档,但这是一件非常痛苦和浪费时间的事情。第二个是如果你的开发环境不能被复制和重现,你的自动化测试怎么做?总不能说是有人手动配置,运行,今天挂了,明天改吧?那么如何实现统一的开发环境呢?我们的方法是定义一个通用接口。我觉得这个编译系统的内部实现无所谓,随便什么都可以,但是它的接口比实现重要一百倍。大家可以想一想,如果用同样的方法来构建和运行每一个程序、每一个组件,这是一种怎样的体验?我们定义了一个build.sh和package.sh。这个构建意味着我使用Java、Python或Ruby。我定义了build.sh。它最终可以产生一个可以构建这个东西的结果。对我来说,我不关心下面的程序是怎么写的,但我只需要构建它。我更改了一行代码,它可以构建新的东西。这就是我们现在所做的。Google最近开源了bazel,这是一个用Java编写的编译工具。其实就是build命令,后面是两个反斜杠代表整个Workspace的根,然后coding就是一个project,server就是这个project的target。有了这个东西,其实你在做任何一个项目的时候,考虑的都是它的逻辑层次,而不是物理层次。逻辑上的分层是我要搭建一个编码服务器,所以里面可能会引用到这个编码服务器。是否安装了其他第三方库、头文件、Ruby程序或Java程序都没有关系。我只想说我可以建立一个编码服务器。这个bazel更好的地方在于它可以自动处理递归依赖,这是你的规则。取决于另一个规则。有了这个编译,我们还需要一个可重现、可复用的开发环境,那么这个开发环境我们要怎么做呢?我们使用Vagrant和docker。Vagrant是VM的管理工具。它可以生成一个新的VM,由代码定义,然后在VM中将每个服务作为docker服务运行。Vagrant其实做了三件事:第一件事是从指定的地方下载BaseBox,BaseBox是我们自己制作的,比如一个Ubuntu镜像加上一些本地依赖。二是它支持脚本定义,你可以运行一个shell脚本来定制,然后选择一个所谓的Provider。这个提供者是一个你可以在本地使用的提供者,例如,连接到许多云提供者。完成这两个操作后,他会生成一个VM,你可以一键ssh进去,它会自动配置你的所有东西。这是一个属于你的开发环境。那么你有了这个东西之后,整个公司就可以有一个一致的开发环境,因为它是一个VM,在任何一台机器上都是一样的运行方式,所有的依赖库都可以放到里面,所以最后的结果就是,我们有一个几千兆字节的镜像放在我们的内网,每次有新同事来,我们让他安装一个Vagrant或VirtualBox,然后他输入命令,自动下载镜像,启动它,然后放整个Coding项目在他的机器上一键启动运行。为了实现这个可重用的开发环境,我们也实现了所谓的一键式操作。一键式操作是另一个层次的发展。它不关心这个东西是如何建造的。我只关心启动这项服务。比如我在Coding开发环境中需要哪些服务?然后我们使用一个自写脚本来安排Docker。在这个配置文件中定义了一些作业,每个作业都有一些镜像(运行程序版本、环境变量等)。其实很多时候我们使用gorun命令可以一键启动很多服务和镜像,实现我们刚才说的统一编码、可重现、可复现的开发环境。技术工具是我最怀念谷歌的地方,因为这些工具带来很多好处:首先,他鼓励公司内部的协作。每个人在写程序的时候,首先不会想到自己创造一个小东西。我们经常去看看公司里别人做了什么,是怎么实现的,能不能引用,能不能用,能不能提取常用的类库;其次,可以让新人快速上手,我们新同事也好,老同事也好。更多的场景是老同事换项目。他们以前可能是用户,但他们可以瞬间成为开发者。开发环境的无缝切换无形中降低了很多公司的运营成本。第三点是它使自动化成为可能。刚才我们安装了Vagrant,执行了一条命令就搞定了。这个东西也可以实现成一个自动化的东西。例如,每次我们发送CodeReview时,我们都可以在后台自动启动一个新的VM并下载所有内容。运行测试,最后给出变更对错、影响等结果,都可以自动化。我认为这是一个关键点。有了这个环境之后,你就有了质量分析和工具,然后才能进行,并且可以连接更多的生产力工具。最后说说流程管理。作为CTO,我们的小梦想是不断交付更好的软件。老板说,作为老黄牛,要一直往前跑,也能按时完成任务。公司内部有很多实现方式:第一个是CodeReview,一个混合review的流程工具:第一点,这个CodeReview绝对是一个脑抽检测器,当大家都有脑抽的时候,review期就是一个缓冲期,让你想想,你真的要这么做吗?让别人为您检查对您有好处。第二个是CodeReview是分享知识的好方法。当我自己写这个功能的时候,我把代码给别人看,然后另一个人就明白了。以后他可能来做你的工作,你可以转去做他的工作。鼓励公司内部的知识共享很重要。三是创意的产生。大家在分享知识的时候很容易在另一个地方发现这个问题。我就是这样解决的。为什么不这样解决呢?有没有更好的方法来解决它?CodeReview这样做是为了促进沟通。那么CodeReview不好的有哪些方面呢:第一是程序员鄙视链,就是老程序员鄙视新程序员,说你写的太烂,我懒得看。这是一种很不好的行为,我们一定要避免这种事情。我们提出了一些程序要求。例如,每次复习都必须通读整篇文档。不能说我读了一行,发现它太糟糕了,无法阅读。你改正后我会读的。这是不允许的。的。其次,我们谈论所有权,这意味着当您进行代码审查时,谁拥有代码。我们讨论谁编写代码以及谁负责代码审查。如果你写rm-rf,你将对此负责。看完后,我觉得你写的很有道理,但我没有看到错误,这是你的问题而不是审稿人的问题。这是我们在Google和Coding中实践过的经验。第三点是拥抱变化。我可能已经由一名员工编写了一个程序。他觉得我写的程序真的很好。不要改变它。我只是无法理解。所以他非常抗拒任何改变,这也是错误的。无论是在Google还是Coding,我们坚持的原则之一是,如果我们有业务案例,您可以更改代码。你只能把这段代码写的更好,不能让他改。这是关键。你写的代码是公司的,那公司怎么用代码做更好的事情呢?你得说清楚你的道理,为什么要改这段代码,改了有什么好处,把这个东西变成技术讨论。与其说是责任,不如说是对权威的讨论。所以我觉得这几点是让CodeReview变得更好的关键点。接下来,让我们谈谈ReleaseSchedule问题。因为创业公司不同于大公司,每个公司都有自己的发布时间表。过去,我们基本上靠喊叫。今天我们要上线,所以所有员工都要上线。如果我们今天要发布,那就发布吧。要是今天骂老板我上不了线,老板想了想,算了,明天上线吧,不是很严重。我们内部进行的改革,就是要把短跑变成长跑。我们创业一年多了,总不能一直冲刺吧。我们需要成为一个长跑者,能够持续交付高质量的软件,而不是每天上线后加班加点到半夜改进。方法很简单,就是一周钉两次释放。我说服了老板,每周两次就够了,三次就更多了。谷歌每月只发布一次,重大项目每六个月才发布一次。我们的初创公司能做到这一点已经很不错了。每周发布两次是什么概念?周一要上staging测试环境,周二release,周三再去staging,周四再release,留给大家周五写程序修bug。其实一周发布两次也很频繁,但是为了保证我们现阶段的转化率,我们采用了这种方式。把这个东西摆在桌面上有什么好处?他让大家计划我的活动是在下周二还是下周四。当你和项目经理吵架时,这是有道理的。他说,看,你是星期二。还是周四,程序员说周二可能跑不出来,那就周四来吧,把这次冲刺改成长跑。接下来,我们要区分FeatureTeam和InfrastructureTeam。InfrastructureTeam也是我们用来谷歌的一个术语。他是什么意思?也就是说,虽然所有员工都在做业务逻辑,但我们必须抽出一定的时间来推动技术演进。你总不能说我天天上来就是写代码,复制粘贴,搞得大家一头雾水。我们在内部提出了三点:第一点叫CodingOne,就是我们公司所有的项目,所有的技术,所有的服务都力求以同样的方式运作。例如,使用相同的Java版本。这是一件困难的事情。我想很多公司做不到。也很难用同一个第三方库,所以库不一样,东西是一样的。这样大家的代码看起来都差不多,其实是不一样的,所以CodingOne就是为了解决这个问题,就是Java版本,第三方库的版本,第三方库的类型,编译方法、运行环境和启动方法。应该是一样的。这样会减少很多程序员之间的内耗,提高大家的效率。第二个是编码二。我们刚开始创业的时候,大家都认为我们都用一台机器,什么都跑在上面。如果这东西坏了,一切都会失败。大家觉得每次都更新太危险了。让我们在半夜做吧。十二点还不够半夜。我们三四点钟做吧。因为你这个东西没有备份,所以没有灰度。我们的名字是编码二。以前是从零到一的过程,现在我们是从一到二。从一到二就是说这个东西你可以有多个,这个东西挂了之后你还可以继续上传。当您发布本程序时,您可以先为自己发布,使用后再发送给其他人。用户,这才是负责任的方式。第三点:CodingCI是我想说的最后一点。最后,我们的最终目标是所谓的PushOnGreen,就是在你提交代码后的一分钟内,只要所有的测试都跑完了,就会立即上线。生产环境,你能不能想想你自己公司能不能做这个?如果不是为什么?您的程序员编写了代码。我们相信程序员写的代码是善意的。您已经通过CodeReview,业务流程没有问题。为什么他不能直接上生产环境呢?PushOnGreen是测试您的研发系统是否可以做到这一点的终极测试。如果你能做到这一点,你就是一个很好的研发系统。这就是我跟大家分享的三点,人、技术、流程。我认为这是一家公司的齿轮组合。可以说,公司就是一台大机器。如果机器经过润滑,齿轮会转动得更快。好吧,绑定得更紧,这就是我们想要追求的。谢谢你们。