软件开发领域素来有10倍开发者的说法,但对于有理想的开发人员来说,在提升开发、架构能力的基础上,训练自己站在产品的角度考虑问题,将产品打造得更为符合客户和运维的需求,可能比10倍效能的个人开发者能够产生的价值更大。原文: How to Outperform a 10x Developer[1]
"10倍开发者"这一说法起源于Jeff Foster[2]写的一篇文章[3],他在文章中对比了超强代码能力和专注于产品的价值[4]。
站在DevOps(覆盖了从开发到运维的全方位协作)的角度来看,专注于产品要胜过编码技能,因为有时候不写新代码是对产品最好的决定。
本文提供了具体例子,能够更好理解生产系统如何改变软件开发人员的角色,并显著改善设计系统和编写代码的方式。
当质量不够时
当然,高质量的代码和对技能的磨练仍然非常重要。软件开发人员应该遵守良好的实践[5],例如自动静态检查、代码审查、单元测试以及许多其他方法。尽管如此,快速编写高质量代码只是将系统投入生产并保持其活力所需的所有任务的一小部分。
从这个意义上说,组织不应该花费太多资源来寻找和培养10倍开发者。首先,他们可能不是真正的10倍开发者[6]。其次,持续的软件开发必须包括支撑工具,如设计文档和运维程序,这些文档必须与使用它们的人一起合作不断被审查和更新。我们可以简化协调活动,但基本的人际交互和工作流几乎不会以10倍的速度发生。
9月22日更新: Hajime Vukelic[7]在这篇文章中添加了一条评论,指出我的说法听起来像是说优秀的个人表现是不可能或不受欢迎的。我仍然认为这在DevOps实践中是困难的,但他深思熟虑的文章我认为是对这一主题的极好的补充,文章题为"What’s a high-differential developer[8]"。
你希望开发人员能够达到更高的水平(比如1.2倍开发者?),以便其他团队成员能够理解和吸收更高效的技术和行为。
第一课 — 优秀需要花27倍甚至更多的时间
我指的"时间"不是指"几十年经验"(当然这也有帮助),而是指做伟大工作所需的实际时间。
即使是最有才华的开发人员,也会对从原型发展到可持续的生产系统所需的时间做出糟糕的估计。我必须强调"可持续的"这个词,即随叫随到的轮班工人也可以产出成果,[即使不懂代码的人也可以解决问题](http://sourcepatch.blogspot.com/2021/09/asking-wrong-question-should-developers.html?view=magazine "Should (only "即使不懂代码的人也可以解决问题") developers be on-call?")。
我最喜欢的估算产品特性总成本的方法是创建工作原型,然后将原型所花费的时间乘以27。这是对的,因为如果我需要两天时间来开发一个原型,那么整个团队可能需要将近3个月(2x27=54个工作日)的共同努力才能将其投入生产。
9月22日更新: 用户Liquid Analytics[9]在评论区提出了一个很好的观点,即27倍乘数可能成为有意义的进展的阻碍因素。我应该提到,这个想法是使用现代"test in production"方法来部署原型,比如利用暗启动[10]或特性开关[11]进行基于主干的开发[12]。
为什么是27倍?
Frederick Brooks Jr.在《人月神话[13]》中解释了第一个9倍,这是他以书籍形式发表的关于软件开发的一系列具有里程碑意义的文章。
第一个3倍乘数是从可运行的代码(书中使用术语"program",这是当时的特点)到可靠的应用程序(书中使用术语"program product")的成本。
第二个3倍乘数来自将可靠的应用程序转换为系统("system product")所需的额外测试。
Brooks介绍了创建交付给客户进行内部部署的软件的各个方面(这是当时IBM业务模型的特点),因此排除了与运营方面相关的成本,为此我添加了最后一个3倍因子(这是基于我在运维工程方面的经验,有可能你的乘数会有所不同),以将系统转变为可运维的基于云的服务。
最后一项乘数包括下列开发活动:
-
测量系统的可观测性 -
根据系统设计编制运维规则 -
持续部署流水线的开发和维护 -
与其他云提供商服务的集成测试
结合所有乘数(3x3x3),我们将原型投入生产的成本增加了27倍。
重要原因: 虽然经验丰富的开发人员可能不会低估新功能的规模,但任何超出2倍或3倍的因素[14]都肯定会在整个团队中产生难以处理的意外,更重要的是,会浪费我们验证新功能所需的时间,从而给运维团队带来噩梦。
第二课 — 运维是一项工程而不是艺术
无论如何,艺术都有其位置,系统的某些角落可能看起来即聪明又精致,但最终形式服从功能[15],生产系统的功能是运行满足需求和成本目标的软件。
本节将介绍在运维中获得的经验教训,以及如何影响设计和编码活动,这部分工作占了27倍倍数的一部分。
这一节没有涵盖代码开发之外的所有范围,从而避免文章过于冗长。
更多的组件,更多的成本,检查每个人的预算。 在设计和架构时需要考虑运维成本是我在运维期间学到的最重要一课。系统中的任何新组件都会改变运维该系统的成本结构,因此问问自己,该组件是否减少了比增加的成本更多的成本,并准备好放弃在系统中添加一些看起来非常有趣的东西。
毕竟,在同一个世界里,首席财务官们(正确的)告诉你,增加收入比削减开支更重要[16],开发人员将其诠释为将一个50毫秒的SQL查询抽象为微服务包装的一系列分页RESTful调用(真实故事)。
我并不是说要采取相反的方式来改变,但这是一个需要找到合作方式的领域,或者至少要与运维团队进行协商。
全局状态和健康检查点。 如果发生停机事件,没人能够在缺乏系统状态的情况下做任何事情,特别是在具有数百个组件的微服务架构中。
生产系统需要总体运行状况检查点,该点聚集了来自各种依赖项的运行状况。其思想是运维人员可以快速评估(1)系统的哪些部分不起作用(2)哪些依赖项不起作用。后面会提到"为什么"需要这样的检查。
我在关于Kubernetes容器的readiness和liveness的文章[17]中列出了关于设计健康检查点的建议,那篇文章中的大多数建议也适用于非Kubernetes系统。
从读者的角度记录错误。 一旦运维人员意识到系统不完全健康,下一步就是了解是什么原因,以及如何修复。
有些事情应该会发生,但却没有发生。从阅读日志消息的人的角度来看,最有用的日志消息遵循如下模板:
[ERROR|WARNING|INFO]: [Component X] attempted to [take action Y], which returned [response Z].
这种格式乍一看似乎很明显,看起来像常规的主谓宾结构,但最重要的是术语:
-
" Component X"是否存在于系统文档中? -
" action Y"对读者来说容易理解吗? -
" response Z"是否在故障排除手册的某个地方提到?
我读过很多错误消息,很感激有人花时间把它们添加到代码中,但我对其中引用的只对代码作者有意义的文件名和库调用犹豫不决。这些内部引用应该放在专用的跟踪文件中,或者添加"debug"前缀标记,以便于过滤。
最终,每条日志消息都必须导致一个明确的解析步骤,该步骤不涉及联系消息的作者或阅读源代码。
编写系统文档: 编写文档迫使我们将对系统的理解构建为一种新的媒介。除了必须告诉人们如何安装、监控、保护或排除系统故障之外,该活动还为系统带来了许多好处。
写作行为要求作者对系统中难以解释的方面进行思考。
通常,这种困难可能表明潜在的设计缺陷,例如难以解释所有组件的正确安装顺序。其他时候,这种困难可能是由于产品的"待开发"区域需要为读者提供一大堆伪代码指令("…然后点击这里,键入这个,等待几秒钟,然后一个面板将弹出,找到一个名为…的按钮")
如果处理得当,文档还可以作为合作者的聚集点,这些合作者可能愿意贡献他们的知识,但没有时间去弄清楚如何做到这一点。这些类型的贡献对于整个生态系统(开发人员和用户)来说是巨大的生产力提升,如果不加以记录的话,往往会丢失在团组和私人对话中。
时间框调用远程组件。 在分布式系统中,我们比依赖远程代理更了解如何快速可靠的响应。尽管如此,我们经常在客户端库和实用程序中使用默认的超时设置,而不加考虑,因为我们认为开发人员已经找到了适用于每个人的神奇设置。
这是一个常见疏忽,因为库和实用程序都带有合理的默认值,系统采用了最新的设计和运维技术,以获得最大的可用性。对系统结果的错误信任一直持续到其中一个系统发生故障,导致组件无条件等待TCP超时达2小时[18]。
总是在代码中寻找远程调用,并确保知道它们的限制,以及代码如何处理这些限制:
-
最大连接响应时间 -
最大请求响应时间 -
最大重试次数
对任何系统来说,为远程调用创建有效的重试策略都是一项受欢迎的改进。尽管如此,仍有可能掩盖对迫在眉睫的问题的可见性,例如掩盖响应时间的持续恶化,直到它们最终超过最大限制,这将我引向下一点。
遥测、可观察性和分布式跟踪: 处理停机比查看系统状态和日志条目要复杂得多。除了处理中断之外,还有更多的操作,例如主动查看系统对内部度量、跟踪和日志条目的遥测。
许多平台已经在源代码中使用最少的指令[19]生成了大量遥测数据。然而,仍然需要将大量特定于遥测的代码与源代码混合在一起,尤其对于度量(metrics)来说。
另外,确保你(和其他开发人员)能够定期访问系统操作中使用的遥测框架的本地设置。许多框架支持在工作站或免费试用的基于云的帐户中本地执行,从本地环境无缝过渡到远程环境的统一设置需要开发、验证、文档编制和维护。创造这样的环境非常有趣,但也需要花费时间和金钱。
再怎么强调也不为过,即使是最有经验和自信的开发人员,在查看他们的代码遥测时,也总会学到一些新的或令人惊讶的东西。
更进一步,与运维团队合作,确保与运维团队共享数据,特别关注数据匿名化和访问等方面。
关注队列系统。 编写队列系统很有趣,设计架构图是令人兴奋(和诱人的),但是大多数人严重错误的判断了在系统中添加队列模式的成本。
我知道队列系统有一些合适用例,比如大量事务(例如每天有数百万条消息),其中调用组件不能等待响应,而只关心事务最终在合理的时间内得到处理。
但是,如果不处理这些用例,可能需要认真考虑是否需要将异步消息处理包含到系统中。这种通信模式增加的成本贯穿消息生产者"A"和消息消费者"B"之间的整个业务事务。以下是关于这种额外复杂性的几个例子:
-
系统管理员用于处理队列大小超过某些限制的运维流程。 -
处理过期消息的额外设计和代码。 -
处理过期后发送到死信队列的消息的操作过程。 -
扩展系统以管理死信队列。 -
处理跨消息传递的业务事务的 分布式跟踪 [20]。
第三课 — 大杀四方:编码、构建、支持、运维
虽然长期职业生涯可能会逐渐教会你DevOps实践的不同领域,但你可以通过有意的在开发、集成、支持和运维之间轮换来加快成长。我们的想法是学习事物是如何工作的,以及如何构建在工程周期的每个重要领域都能很好工作的软件。
一旦你了解了这些领域的人员和工作流程,就更容易在核心专业知识之外做出贡献,减少技术和社交摩擦。
处于职业生涯中期和高级的开发人员可能不倾向于改变工作角色,但经验使他们能够更快学习。几个星期的临时轮岗、共享项目,甚至是处理客户支持和事件报告都可以起到同样的作用。
案例研究1(构建)
在之前项目中,我们有一个团队主要负责演进和维护构建系统。在本地工作站上构建(编译和打包)整个代码库大约需要两分钟。相比之下,在构建周期中,相同的操作花费了看似永恒的15分钟。
构建团队之外的一名开发人员花了一个下午的时间将各种日志条目添加到构建脚本中,以隔离问题,将可能的原因缩小到构建将已编译二进制文件写入磁盘的步骤。在构建系统中,磁盘写操作似乎比在本地工作站中花费的时间要长几个数量级(是的,构建机器有SDD存储:-)
构建团队分析了这些发现,并尝试了不同的替代方案(其中一些方案超出了原始开发人员的Unix技能集),最终的解决方案是增加虚拟机上的内存分配,并将每个构建的临时目录移动到内存文件系统(tmpfs[21])。
案例研究2(运维)
我曾经持续几个月管理整个组织(几百人)的PagerDuty升级策略和警报规则。有时,由于假期安排,当收到紧急消息时,需要越过我的直接处理范围触发警报。
在按下按钮之前,我总是会问几个问题,比如:
-
"这些产生新警报的组件是什么时候部署的?" -
"我在描述中没有看到剧本链接,运维团队知道去哪里找吗?"
在不涉及偶尔混淆的令人困惑的答案的情况下,这个(兼职)任务教会了我宝贵的经验,包括管理系统中警报类型总数的重要性,向生产系统添加新组件的成本效益,以及让运维团队参与架构决策的绝对必要性。
第四课 — 有意识的学习:在每一项任务中学习
在这一点上,你需要知道如何确保时间来编写用于生产的代码,以及需要在代码中包含的内容。
这是一个很长的清单,尽管有["1万小时"规则](https://www.edsurge.com/news/2020-05-05-researcher-behind-10-000-hour-rule-says-good-teaching-matters-not-just-practice ""1万小时"规则" ""1万小时"规则"),但你不会想要等上几十年才能自然而然获得这些技能。
我们总是在执行任务的过程中学习一些东西,但刻意学习[22]意味着不仅要完成工作,还要弄清楚为什么某些事情会起作用,以及如何改进。
搜索网络可能会给你某个特定问题的精确答案,人们可以通过这种方式学到很多东西,但通过有意识的学习,我们想要的是超越现成的解决方案:
-
浏览。 如果解决方案涉及框架的模式化解决方案,例如Terraform的一组特定资源,请返回 浏览这些资源的完整定义 [23],并可能浏览来自同一提供者的邻近资源。 -
如果解决方案涉及具有特定参数的实用程序,请返回实用程序手册研究这些参数,并浏览其他参数。我们的想法不是记住它们,而是 在脑海中索引它们,特别是对于" awk [24]"这样的工具,其手册可以是一整本书。 -
解释你学到了什么。对于源代码,请拿出" 橡皮鸭 [25]",并向一个无生命的物体解释源代码。对于概念,你甚至可以不去找别人,而是使用 费曼技巧 [26],假装你是在向一个孩子介绍这个概念。 -
写作关于主题的文章。写作是一种更有意识的学习形式,帮助你巩固和扩展对一个概念(可能是写一篇文章)、相关概念(使用技术论文之类的东西)或整个领域(例如写一本书)的知识。写作远远超越了学习,可能更有助于在头脑中组织相邻的知识。如果你决定从事这项令人兴奋的活动,一定要阅读海因里希·哈特曼(Heinrich Hartmann)的《 为工程师写作 [27]》。
"阅读产品指南?谁有时间读?"
我意识到这是一个用谷歌搜索"如何让这个错误信息消失"的时代,这在时间紧迫的时候很有意义。不过,你从这些捷径中学不到多少东西。
在这里,我必须引用史提芬·金的话,他是有史以来最成功的小说作家之一,他给有抱负的作家[28]提供了这样一条严厉的爱的建议:
"如果你没有时间阅读,你就没有时间(或工具)写作。就是这么简单。"
如果你没有从时间表中抽出时间来探索项目中使用的技术,并对其进行扩展,你就会限制自己只学习足够完成当前任务的技术。当你周围的人认为你只能胜任同样的任务时,这种动力就会自我强化。用不了多久,这些假设就会被证明是正确的。
根据不同情况,"挤出"时间或多或少具有挑战性,但意识到这一点是一个起点。有时你可能会不断加倍承担重复性(但有价值)的任务,因为这样更舒服。在其他时候,组织可能会对某人比其他人更有效的做重复的工作感到满意。无论情况如何,首选需要认识到你无法从每项任务中学习新东西。
结论
DevOps实践涵盖了许多不同且广泛的流程,使得人们专注于特定领域,如软件开发、持续交付或运维。
有时,在人们有机会探索其他领域之前,兴趣和目标就会缩小,所以要抵制在职业生涯早期专攻的冲动和压力。作为组织,在员工可能长期呆在一个职位上所带来的生产力与日本式的年度轮换方法[29](也许不是固定的时间表)的好处之间取得平衡。
在职业生涯早期,与管理团队一起在编码、交付和运维方面进行"实习"。这种全面的经验是非常珍贵和罕见的,无论是作为程序员、系统架构师、用户体验设计师、技术客户经理、基础设施工程师,或任何其他你可能喜欢的角色,都将帮助你在任何你选择贡献的领域倍增你的潜力。
愿意从不同角度理解是什么让产品变得更好,并将这些经验教训融入日常生活中,这是一种超级力量,远远超过了一个实际的10倍开发人员在利基领域所能聚集的力量。
归根结底,这些教训来自个人经验,每条道路都是不同的,欢迎提供反馈。
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。
微信公众号:DeepNoMind
参考资料
How to Outperform a 10x Developer: https://betterprogramming.pub/how-to-outperform-a-10x-developer-fa1132807934
[2]Jeff Foster's Medium: https://medium.com/@jeff-foster
[3]The origins of the 10x developer: https://medium.com/ingeniouslysimple/the-origins-of-the-10x-developer-2e0177ecef60
[4]Project vs. Product Thinking: https://medium.com/ingeniouslysimple/project-vs-product-thinking-b4971163f3dd
[5]30 best practices for software development and testing: https://opensource.com/article/17/5/30-best-practices-software-development-and-testing
[6]The 10x Programmer Myth: https://www.simplethread.com/the-10x-programmer-myth
[7]Hajime Vukelic's Medium: https://medium.com/@hayavuk
[8]What’s a high-differential developer: https://medium.com/@hayavuk/whats-a-high-differential-developer-2fc42e2ee6a3
[9]Liquid Analytics' Medium: https://medium.com/@liquidanalytics
[10]暗启动: https://www.split.io/glossary/dark-launch
[11]特性开关: https://www.atlassian.com/continuous-delivery/principles/feature-flags
[12]基于主干的开发: https://trunkbaseddevelopment.com
[13]人月神话: https://archive.org/details/MythicalManMonth
[14]2x: Simple rule for software development time estimation: https://fibery.io/blog/software-development-time-estimation
[15]形式服从功能: https://en.wikipedia.org/wiki/Form_follows_function
[16]增加收入比削减开支更重要: https://twitter.com/KurtisHanni/status/1560986912613072899
[17]The ART and Science of probing a Kubernetes Container: https://dnastacio.medium.com/the-art-and-science-of-probing-a-kubernetes-container-db1f16539080
[18]TCP man page: https://man7.org/linux/man-pages/man7/tcp.7.html
[19]OpenTelemetry Instrumentation: https://opentelemetry.io/docs/instrumentation
[20]分布式跟踪: https://microservices.io/patterns/observability/distributed-tracing.html
[21]tmpfs: https://en.wikipedia.org/wiki/Tmpfs
[22]What is intentional learning & what does it mean for the global reskilling emergency?: https://learntechasia.com/intentional-learning-global-reskilling-emergency
[23]浏览这些资源的完整定义: https://registry.terraform.io/browse/providers
[24]awk: https://www.gnu.org/software/gawk/manual/gawk.html
[25]橡皮鸭: https://en.wikipedia.org/wiki/Rubber_duck_debugging
[26]费曼技巧: https://en.wikipedia.org/wiki/Learning_by_teaching
[27]为工程师写作: https://www.heinrichhartmann.com/posts/writing
[28]On Writing: A Memoir of the Craft: https://stephenking.com/works/nonfiction/on-writing-a-memoir-of-the-craft.html
[29]Jinji Ido: the notorious system of annual rotations: https://wa-shoku.info/jinji-ido-the-notorious-system-of-annual-rotations
本文由 mdnice 多平台发布