区分优秀程序员和卓越程序员的 10 个开发者习惯
区分优秀程序员和卓越程序员的 10 个开发者习惯
区分优秀程序员和卓越程序员的 10 个开发者习惯
每个开发者的职业生涯中都会有那么一个时刻,他们会意识到仅仅编写出能运行的代码是不够的。每个人经历这个时刻的方式都不尽相同。也许你正盯着六个月前提交的拉取请求,对当时自己做出的决定感到懊恼不已。也许你凌晨两点还在调试生产环境的问题,周围堆满了能量饮料罐,百思不得其解,为什么这么简单的问题会演变成如此灾难性的后果。又或许你正在和一位编程高手结对编程,他处理问题毫不费力——几分钟就能解决你可能需要花费数小时才能解决的问题——而你却不禁疑惑,自己和他之间究竟存在什么差距。
我从事专业编程工作十余年,可以肯定地告诉你:优秀程序员和卓越程序员之间的区别,与掌握更多算法或记住更多语法无关。这与是否毕业于名牌大学或在FAANG公司工作也无关。真正的区别在于那些不易察觉的地方——日常习惯、无数次重复的微小决策,以及默默无闻地完成那些不为人知的琐碎工作的自律。
这无关乎天赋。我见过才华横溢的开发者因为只依赖过人的智力而最终失败。我也见过平庸的程序员通过刻意练习和习惯养成,蜕变为杰出的人才。伟大的人并非天生,而是后天培养的,一个习惯,一个习惯。
接下来要介绍的并非效率提升技巧或快捷键,而是随着时间推移不断积累的深层次、根本性的习惯。无论你今天编写的是 Python 微服务,还是十五年后的量子计算算法,这些习惯都至关重要。其中一些习惯可能会让你感到挑战,一些习惯可能会让你觉得有悖常理,但所有这些习惯都需要你付出努力才能养成。
但如果你坚持下去,你不仅会成为一名更优秀的程序员,还会成为其他团队梦寐以求的那种开发者——那种会被拉去解决最棘手问题、塑造工程开发方式的人。
我们开始吧。
习惯一:他们阅读代码的时间远远多于编写代码的时间。
当我指导初级开发人员时,我经常问他们:“你花多少时间阅读别人的代码,又花多少时间编写自己的代码?”答案几乎总是相同的:不多。也许他们在遇到问题时会快速浏览一下文档或库的源代码,但真正有意识地深入阅读代码?很少。
这是区分优秀开发者和卓越开发者的第一个习惯:卓越的开发者都是如饥似渴的代码阅读者。
不妨这样想。如果你想成为一位伟大的小说家,你不会整天埋头写作。你会广泛阅读,进行批判性、分析性的阅读。你会研究海明威如何构建句子,厄休拉·勒奎恩如何构建世界,托妮·莫里森如何运用语言唤起情感。编程也是如此。软件工程的技艺既需要实践,也需要观察。
但正是这一点让这个习惯如此强大:阅读代码能教会你一些单靠编写代码永远无法学到的东西。当你编写代码时,你会被困在自己的思维模式、固有思维模式和固有偏见中。你会自然而然地选择你已经知道的解决方案。而阅读他人的代码则能让你接触到不同的思维方式、不同的问题解决方法以及不同的抽象层次。
我记得第一次通读 Redux(这个流行的状态管理库)源代码时的情景。当时我的 JavaScript 水平属于中级,虽然熟悉一些 JavaScript,但算不上高级。让我印象深刻的不仅仅是代码的运行方式,更是它的简洁性。Redux 的核心实现只有几百行代码。开发者们将一个复杂的问题(管理应用程序状态)提炼到了最本质的层面。阅读这段代码彻底改变了我对软件设计的看法。我意识到,复杂性并非值得称道之处,简洁才是。
优秀的开发者将阅读代码作为一种日常习惯。他们不会等待特定的理由才深入研究代码库。他们这样做是因为他们好奇,因为他们渴望学习,因为他们知道,那些文件中蕴藏着别人花费数年时间才学到的经验教训。
以下是培养这种习惯的实际方法:
安排专门的阅读时间。就像你会安排时间做编程项目一样,也要安排时间阅读代码。可以先从每周两次,每次 30 分钟开始。选择一个你经常使用的库或框架,通读它的源代码。不要略读——要逐行阅读。遇到不理解的地方,不要急于跳过。停下来,查阅资料,弄明白。
带着目的去阅读,不要被动地阅读。阅读时要不断提问:他们为什么要这样组织代码?他们用这种抽象方式解决了什么问题?如果是我,我会怎么做?有哪些模式可以借鉴?是什么让这段代码易于理解或难以理解?
阅读不同领域和不同语言的代码。如果你是 Web 开发人员,就去读嵌入式系统代码;如果你用 Python,就去读 Rust。其中的模式和原则往往超越了具体的技术。我曾将从 Erlang 的 OTP 框架中学到的知识应用到 Node.js 微服务的架构设计中,尽管这两种语言截然不同。容错和监督树的基本原理是普遍适用的。
加入代码阅读俱乐部运动吧!一些开发团队已经成立了“代码阅读俱乐部”,开发者们定期聚会,共同阅读和讨论一些有趣的代码库。如果你的团队还没有这样的俱乐部,那就赶紧创建一个吧!选择一个备受好评的开源项目,大家一起深入研究。这些讨论会的成果弥足珍贵——你会听到不同的人如何解读同一段代码,他们注意到了什么,以及他们重视什么。
学习大师们的作品。有些程序员的代码值得专门研究。例如,John Carmack 的游戏引擎代码、Rich Hickey 的 Clojure、Linus Torvalds 的 Git 以及 DHH 的 Rails。这些代码并非完美无缺(世上没有完美),但它们凝聚了数千小时的精雕细琢和深入思考。阅读他们的作品就像跟随一位技艺精湛的工匠学习。
这种习惯带来的改变虽然微妙,却意义深远。你会开始培养对代码质量的直觉,更快地识别出各种模式,并建立一个可以随时调用的解决方案库。当你遇到新问题时,不会立刻上网搜索,而是会想起:“哦,这和 React 处理 reconciliation 的方式很相似”或者“这是我在那个 Python 库里看到的策略模式”。
我采访过数百位开发者,通常只需几个技术问题就能判断出对方是否认真阅读代码。他们会引用自己研究过的实现方案,比较不同库中的实现方式,他们的观点是基于对各种方案的实际考察,而不仅仅是 Stack Overflow 上的答案。
单靠阅读代码并不能让你成为一名优秀的开发者,但它是基础。其他一切都建立在这个基础之上。因为如果你从未见过优秀的代码是什么样子,你就无法编写出优秀的代码。
习惯二:他们会深入研究每个决定背后的“为什么”
优秀的程序员实现功能,而卓越的程序员则理解他们所构建产品的业务背景、用户需求以及系统性影响。
这听起来似乎显而易见,但却是最常被忽视的习惯之一,尤其是在那些自诩技术精湛的开发者中。我曾与一些才华横溢的工程师共事,他们能够实现任何算法、优化任何查询、构建任何系统——但他们却把需求奉为圭臬,从不质疑自己被要求构建的东西是否真的是正确的解决方案。
这里有一个故事完美地说明了这一点。几年前,我参与开发一个金融科技平台,我们收到一个功能请求,要求添加“待处理交易”功能。产品经理希望用户能够看到已授权但尚未结算的交易。这听起来很简单。
一个优秀的开发人员会接受这个需求并加以实现。在数据库中创建一个新的状态字段,添加一些用户界面组件,编写业务逻辑。搞定。发布吧。
但我们的一位高级工程师采取了不同的做法。她安排了一次与产品经理的会议,并问道:“用户为什么需要查看待处理的交易?他们试图解决什么问题?”
原来用户抱怨的是账户余额显示不正确——他们购物后,余额却不会立即更新。他们并非真的想查看待处理交易,而是对可用余额感到困惑。真正的解决方案并非显示待处理交易,而是显示两个余额:当前余额和可用余额,后者会考虑待处理的授权。
这看似微小的区别,却彻底改变了实现方式。我们没有为待处理交易构建一个全新的用户界面(这会增加用户的认知负担),而是优化了现有的余额显示。这个方案更简洁,更符合用户需求,而且实现时间也缩短了一半。
这就是在实践中投资于“为什么”的意义所在。
优秀的开发者会将每一个功能请求、每一份错误报告、每一个技术决策都视为理解更深层次背景的契机。他们不会只问“需要构建什么?”,而是会问:
- 这项技术旨在解决什么问题?不是技术问题,而是人的问题。哪些人会受到影响?他们正在经历怎样的痛苦?
- 有哪些限制因素?是因为监管规定的最后期限而紧急?是因为竞争压力?还是因为重要客户威胁要流失?了解紧急程度有助于您做出更明智的权衡决策。
- 二阶效应是什么?这将如何改变用户行为?这将如何影响系统的复杂性?我们将承担哪些维护负担?
- 这是正确的解决方案吗?有时候,最好的代码就是没有代码。我们能否通过更好的用户体验来解决这个问题?通过配置而不是编程?通过找出问题的根源而不是仅仅治标不治本?
我曾经花了三个小时参加一个缓存层技术设计评审,这个方案本来可以解决我们的性能问题。提出这个方案的工程师做得非常出色——详细的基准测试、稳健的架构、清晰的迁移计划。但随后有人问:“我们为什么会遇到这些性能问题呢?”
我们深入调查后发现,根本原因在于一个优化不佳的查询语句,它导致了数百万次不必要的数据库调用。我们原本打算构建一个缓存系统来绕过这个问题,而实际上只需两行 SQL 优化语句就能解决。弄清“为什么”之后,我们避免了数周不必要的工作。
养成这种习惯需要勇气,尤其是在职业生涯初期。质疑需求、反驳产品经理或资深工程师、提出既定方案可能并非最优,这些都让人感觉冒险。但我学到的是:人们尊重那些能够批判性地思考自己所开发产品的开发者。他们需要的是能够及早发现问题、参与产品思考、并将软件开发视为解决问题而非仅仅完成任务的合作伙伴。
如何养成这个习惯:
把“为什么?”作为你的首要问题。在开始任何重要的工作之前,确保你能清楚地阐述这项工作的重要性。如果你做不到,说明你对问题的理解还不够透彻。安排时间与提出这项工作的人——产品经理、其他工程师、客户支持人员——沟通,并不断提问,直到彻底弄清问题的来龙去脉。
研究你所处的领域。如果你在开发医疗保健软件,那就去了解医疗保健行业。阅读 HIPAA 相关内容。了解医院的运作方式。如果可以,尽量与医生交流。你对这个领域了解得越透彻,就越能更好地评估技术方案是否真正解决了实际问题。我见过一些开发人员把领域知识当作背景噪音,他们的代码也印证了这一点——技术上很精湛,但却与实际业务运作方式脱节。
参与用户调研。观看用户测试。阅读支持工单。加入客户电话会议。没有什么比亲眼目睹真实用户在使用你的软件时遇到的困难更有效的了。这会从根本上改变你对产品开发的思考方式。只要观看一次用户测试,你以后就不会再写晦涩难懂的错误信息了。
培养系统思维。你做的每一个改动都会对系统产生连锁反应。看似无害的功能添加可能会增加数据库负载、使部署流程复杂化,或者产生新的极端情况,从而破坏现有功能。优秀的开发者在编写代码之前,会在脑海中构建这些连锁反应的模型。他们以系统而非孤立的功能进行思考。
记录“为什么”,而不仅仅是“做了什么”。编写代码注释时,不要解释代码的功能(阅读代码后应该很清楚),而是要解释它存在的意义。为什么选择这种方法而不是其他方法?是什么限制或要求促使你做出这个决定?未来的你和维护者都会感激你的。
说实话,这种习惯确实很累人。照着指示去做,在心理上确实更轻松。但关键在于——优秀的开发者并非因为选择了捷径才变得优秀,而是因为他们勇于承担结果的责任,而不仅仅是产出。他们明白,自己的工作不是编写代码,而是解决问题。而你无法解决你不理解的问题。
养成这种习惯的开发者会成为值得信赖的顾问。他们会被邀请参加规划会议,影响产品方向,并成为团队的倍增器,因为他们能及早发现问题,避免造成迭代周期浪费和用户失望。
理解“为什么”能让你从代码编写者蜕变为工程师。而这种转变至关重要。
习惯三:他们把调试当作科学,而不是猜谜游戏
晚上11点,生产系统宕机了。客户怨声载道。经理每隔十分钟就要求汇报最新情况。压力巨大,你的第一反应是赶紧想办法解决问题——重启服务器、回滚上次部署、调整配置——总之,想尽一切办法解决问题。
这是优秀开发者和卓越开发者之间最显著的区别所在。
优秀的开发者靠猜测。他们依赖直觉、过往经验和希望。他们会在未完全理解问题的情况下进行修改,把调试当作打地鼠游戏。有时他们运气好,偶然发现解决方案。但更多时候,他们找不到,几个小时的功夫就此白白浪费在挫败感中。
优秀的开发者将调试视为严谨的科学过程。他们提出假设,收集数据,进行实验,并系统地排除各种可能性,直到找到问题的根源。即使耐心看似不可能,他们也能保持耐心。即使一片混乱,他们也能条理清晰地解决问题。
让我来跟你说说我遇到过的最严重的生产环境漏洞。我们的电商平台开始随机丢单——不是所有订单都丢,只是一部分。大概有2-3%的交易在支付端完成了,但数据库里却没有生成任何订单记录。公司收入因此大幅下降。漏洞每拖延一个小时,公司就要损失数千美元。
“赶紧修复”的压力巨大。最简单的做法是凭直觉就开始部署补丁。但我们的首席工程师却反其道而行之:她让所有人退后一步,遵循一套结构化的调试流程。
首先,要重现问题。这看似显而易见,但很多开发人员都会忽略这一步,尤其是在压力之下。她搭建了一个测试环境,并用大量的测试事务进行测试,直到我们能够稳定地重现订单下降的问题。这一步至关重要——这意味着我们可以在不影响生产环境的情况下验证各种假设。
其次,收集数据。这些被丢弃的订单有什么共同点?我们提取了日志,追踪了请求在每个系统组件中的流转,分析了时间节点,检查了用户代理,仔细审查了支付网关的响应。我们当时并非急于寻找答案,而是要构建问题的完整图景。
第三,形成假设。基于数据,我们生成了一个可能原因列表,并按可能性排序:数据库连接超时、订单创建逻辑中的竞争条件、支付网关 webhook 故障、API 速率限制、网络分区、Redis 缓存中的状态损坏。
第四,系统地进行测试。我们逐一测试每个假设,从最有可能的假设开始。对于每个测试,我们都明确定义了什么样的结果才能证实或证伪该理论。不靠猜测,也不搞“试试看会发生什么”这种随意的做法。每一个实验都是经过深思熟虑的。
经过四个小时的细致调查,我们终于找到了问题所在:一个竞态条件,即并发的支付 Webhook 可能导致支付状态被标记为成功,但订单创建交易却被回滚。该漏洞仅在高负载且满足特定时间条件时才会出现——因此具有间歇性。
关键在于:我们原本可能要花二十个小时胡乱摸索,随意修改,在修复旧 bug 的同时又制造出新的 bug。但系统性的调试方法却让我们在四分之一的时间内找到了根本原因。更重要的是,我们正确地修复了问题,并且确信问题已经彻底解决。
将调试视为一种有条不紊的实践,而不是混乱的故障排除,这种习惯或许是软件工程中最被低估的技能。
优秀开发者是如何调试的:
他们克制住急于寻找解决方案的冲动。当你发现错误时,你的大脑会立刻想要修复它。要克服这种本能。首先要花时间去理解问题。我有一个个人原则:花在理解一个bug上的时间至少是你预计修复它所需时间的两倍。这个原则让我节省了无数个小时,避免了仅仅追查症状而忽略根本原因。
他们明确地运用了科学方法。写下你的假设。写下哪些证据可以证实或反驳它。进行实验。记录结果。如果需要,就转向下一个假设。我甚至专门写了一个调试日志,记录复杂 bug 的整个过程。这能让我保持严谨,避免因为忘记之前已经尝试过而重复验证同一个理论。
他们能把问题缩小。优秀的调试员精通二分查找法。如果一个 bug 存在于 1000 行代码的某个地方,他们会注释掉 500 行,看看 bug 是否仍然存在。然后是 250 行。再然后是 125 行。他们系统地缩小问题范围,直到 bug 无处可藏。
他们对工具的理解非常透彻。调试器、性能分析器、日志分析器、网络检查器、数据库查询分析器——优秀的开发人员会投入时间精通这些工具。他们可以设置条件断点、分析内存转储、追踪系统调用、解读火焰图。这些工具极大地提升了他们的效率。我曾亲眼目睹一些资深开发人员在几分钟内就调试出了其他人束手无策数天的问题,原因仅仅是他们懂得如何高效地使用性能分析器。
他们将调试功能融入代码中。优秀的开发者编写的代码易于调试。他们在关键决策点添加有意义的日志语句。他们从一开始就将可观测性融入系统——指标、追踪、结构化日志。他们深知,一个 bug 生命周期中 80% 的时间都花在了理解 bug 的来龙去脉上;让这个过程更轻松,绝对是值得投入的时间。
他们先重现问题,然后修复,最后验证。永远不要修复无法重现的 bug——那只是在猜测。一旦能够重现问题,就修复它。然后验证修复后的 bug 在最初出现问题的条件下是否真的有效。太多开发者忽略了验证这一步骤,最终发布的修复程序实际上并没有解决任何问题。
他们会深入挖掘问题的根本原因。当你发现一个 bug 时,连续问自己五遍“为什么会发生这种情况?”。每个答案都会让你更深入地了解问题所在。“服务器崩溃了。”为什么?“内存不足。”为什么?“内存泄漏。”为什么?“对象没有被垃圾回收。”为什么?“事件监听器没有被移除。”为什么?“组件卸载时没有进行清理。”现在你找到了问题的根本原因,而不仅仅是症状。
我曾与一些开发人员共事,他们似乎拥有某种超乎常人的能力,总能找到 bug。刚入行时,我以为他们只是更聪明或更有经验。现在我明白了真相:他们只是将一套系统化的方法内化于心。他们相信流程,而非直觉。
这种习惯还有着深远的心理益处。调试不再令人倍感压力,而变成了一种智力上的享受。当出现 bug 时,你不再感到无助,而是充满信心——你拥有流程、方法和解决之道。bug 或许很复杂,但你知道如何应对。
优秀的开发人员在遇到问题时不会惊慌失措是有原因的。他们训练自己将每个 bug 都视为一个有解的谜题,而不是一场危机。他们知道,系统性的调查最终总能解决问题。这种自信正是源于这种习惯。
还有一点很妙:当你用科学的方法调试时,你不仅能更快地修复 bug,还能从每个 bug 中学到更多。每个 bug 都成为你了解系统、了解边界情况以及了解自身思维模式的一课。只会猜测和检查的调试人员什么也学不到。而科学的调试人员则在解决每一个问题的过程中积累对系统的深刻理解。
下次遇到 bug 时,不要急于修改代码。深呼吸。打开笔记本。写下你已知的信息。提出假设。验证假设。让科学方法指导你。
你会惊讶于自己效率的提升幅度。
习惯四:他们首先为人写作,其次才为机器写作。
一个令人不快的真相是:你作为开发人员的大部分职业生涯并非花费在编写新代码上,而是花费在阅读、理解和修改现有代码上——这些代码可能是其他人编写的,也可能是过去的自己编写的,而过去的自己实际上可能就是其他人。
然而,当我审阅优秀开发者的代码时,我总是发现同样的错误:他们追求的是巧妙或简洁,而不是清晰易懂。他们编写的代码虽然精妙复杂,但却需要高度集中注意力才能理解。他们把编译器或解释器当作了主要读者。
优秀的开发者会颠倒这种优先顺序。他们首先编写供人使用的代码,其次才是供机器使用的代码。
这听起来或许是一句老生常谈,但它代表了一种根本性的思维转变,会影响你编写的每一行代码。让我来解释一下我的意思。
以下是我在生产代码库中找到的一段代码片段:
def p(x): return sum(1 for i in range(2, int(x**0.5)+1) if x%i==0)==0 and x>1
你能看出这个函数是做什么的吗?如果你熟悉算法,你可能会认出它是一个质数检测器。它运行完美,机器执行起来也毫无问题。但对于阅读这段代码的人来说呢?这却是一个需要解开的谜题。
以下是优秀开发者编写相同函数的方式:
def is_prime(number):
"""
Returns True if the number is prime, False otherwise.
A prime number is only divisible by 1 and itself.
We only need to check divisibility up to the square root of the number
because if n = a*b, one of those factors must be <= sqrt(n).
"""
if number <= 1:
return False
if number == 2:
return True
# Check if number is divisible by any integer from 2 to sqrt(number)
for potential_divisor in range(2, int(number ** 0.5) + 1):
if number % potential_divisor == 0:
return False
return True
第二个版本更长,也更冗长。机器并不在意——两个版本的时间复杂度都是 O(√n)。但对人类来说,差别却天壤之别。第二个版本具有自文档性,即使是初级开发人员也能理解。即使六个月后你已经忘记自己写过它,你仍然能够理解。它的意图非常清晰。
这种习惯——即写作时力求让读者理解——体现在很多方面:
命名要体现意图。temp像`a`、 ` b`、data` c`这样的变量名毫无意义。优秀的开发者会选择能够编码含义的变量名:` a` 、 `b`、`c`。没错,这些名称更长。没关系。多出的几个字符是值得的。代码你只写一次,但要读几十遍。objresultunprocessed_orderscustomer_email_addresssuccessfully_authenticated_user
我记得曾经审查过一段代码,有人给一个变量起了个名字x2。我不得不追溯 50 行逻辑代码才弄明白它代表的是“XML 转 JSON 转换器”。他们为了省下 18 个字符,却让未来的读者浪费了几分钟的认知时间。这简直太不划算了。
功能单一的函数和方法。当一个函数试图完成多项任务时,命名、测试和理解都会变得困难。优秀的开发者会将功能提取到命名清晰的函数中,即使这看起来有点“杀鸡用牛刀”。他们明白,一系列命名良好的函数调用通常比原始实现更能清晰地传达意图。
策略性注释。很多开发者都忽略了一个关键点:优秀的开发者不会注释代码的功能,而是会注释代码为什么这样做。如果你的代码需要注释来解释其功能,那么代码本身就不够清晰。但是,解释某些决策背后的原因的注释呢?这些注释才是真正的宝藏。
“为什么”之类的评论可能会解释:
- “我们之所以使用算法 X 而不是显而易见的算法 Y,是因为对于我们的数据模式,算法 Y 的时间复杂度为 O(n²)”。
- “这个奇怪的超时值是通过使用外部 API 进行大量测试得出的——较小的值会导致间歇性故障。”
- “我们故意不处理极端情况 X,因为考虑到迁移 Y 所强制执行的数据库限制,这是不可能的。”
这些注释保留了原本会丢失的上下文信息。它们可以防止未来的开发者“优化”你精心选择的方法,或者删除他们认为不必要的代码。
代码结构应与思维模型相呼应。优秀的开发者会按照人类自然思考领域的方式来组织代码。如果你正在构建一个电子商务系统,你的代码结构应该反映订单、客户、支付和库存等概念,而不是像管理器、处理程序和处理器这样的通用抽象概念。
我曾经参与过一个代码库的开发,其中包含 `<string> DataManager` DataHandler、` DataProcessor<string>`、`<string>` 和 ` DataController<string>`。这些名称都无法准确表达它们的实际功能。当我们重构为 `<string>` OrderValidator、 `<string>`PaymentProcessor和 `<string>`之后InventoryTracker,代码库突然变得易于浏览。新团队成员可以轻松找到所需内容。代码结构也符合他们对业务的理解。
一致的模式。人类天生擅长模式匹配。当你的代码库遵循一致的模式时,开发者就能轻松地将知识从一个部分迁移到另一个部分。如果每个模块的实现方式都不同,那么每次上下文切换都需要重新学习。优秀的开发者重视一致性,即使他们个人可能更倾向于不同的方法。
合适的抽象层次。这一点看似细微,实则至关重要。优秀的开发者会谨慎地避免在同一个函数中混用不同的抽象层次。如果你正在编写高层业务逻辑,就不应该突然深入到底层字符串操作的细节。应该将这些细节提取到一个命名清晰的辅助函数中。保持代码的每一层在概念层次上保持一致。
以下是一个混合抽象层次的例子:
function processOrder(order) {
// High-level business logic
validateOrder(order);
// Suddenly low-level string manipulation
const cleanEmail = order.email.trim().toLowerCase().replace(/\s+/g, '');
// Back to high-level
chargeCustomer(order);
sendConfirmation(order);
}
更好的:
function processOrder(order) {
validateOrder(order);
const normalizedOrder = normalizeOrderData(order);
chargeCustomer(normalizedOrder);
sendConfirmation(normalizedOrder);
}
现在,该函数读起来就像一系列业务步骤,而不是业务逻辑和实现细节的混合体。
养成这种习惯需要自律,因为为机器编写代码通常比为人类编写代码容易。机器比较宽容——它不在乎你的变量名是 ` xa` 还是 `b` customer_lifetime_value_in_cents。但人类却非常在意。
我见过一些才华横溢的开发者因为这个习惯而自毁前程。他们编写的代码简洁精炼,展现了对语言特性的精通。但之后,他们却要在代码审查中花费数小时解释代码的功能,因为其他人根本看不懂。他们优化的方向错了。
有一句名言常被归功于多位编程界泰斗:“任何傻瓜都能写出计算机能理解的代码,而优秀的程序员则能写出人类能理解的代码。” 这句话的智慧会随着经验的积累而愈发凸显。
当你养成以人为本的写作习惯时,奇妙的事情就会发生:你的代码将变得易于维护。团队协作速度更快,因为代码更容易理解。新开发人员的入职培训只需几天而不是几周。bug 减少,因为代码的意图清晰明了。技术债务积累速度也更慢,因为未来的修改无需费力地去研究晦涩难懂的逻辑。
在代码审查中,我总能通过一个特点识别出优秀的开发者:我很少需要问“这段代码是做什么的?”代码本身就能告诉我答案。我可能会问一些关于权衡取舍、性能影响、替代方案的问题——但我从不担心他们理解不了代码的基本逻辑。
写代码时,要像对待一个有暴力倾向、知道你住址的变态杀手一样。六个月后,维护你代码的人就是你自己,到时候你会感谢自己当初的清晰思路。
习惯五:他们将限制视为创造力的催化剂
当我还是初级开发人员时,我把各种限制条件都看作是需要克服或绕过的问题。时间有限?令人沮丧。与旧系统兼容性?令人恼火。内存限制?限制了开发。我当时认为我的工作就是克服这些限制,实现“正确”的解决方案。
优秀的开发者看待限制的方式截然不同。他们欣然接受限制,充分利用限制。他们明白,限制并不会限制创造力——反而能集中和引导创造力,并常常能创造出比拥有无限资源时更优秀的解决方案。
这是区分优秀和卓越最违反直觉的习惯之一,需要数年时间才能内化。
让我分享一个让我对此有更深刻理解的故事。当时我在一家创业公司工作,开发一款面向新兴市场的移动应用。我们的目标用户使用的是低端安卓设备,网络连接不稳定,只有2G网络,而且流量套餐有限。我们最初的想法是把这些限制当作障碍——开发一个精简版的“轻量级”产品,功能有所简化和妥协。
然后,我们的技术主管说了一句话,改变了我的看法:“这些不是限制,而是我们的设计参数。它们告诉我们,在这种情况下,卓越是什么样子。”
我们彻底改变了方法。我们不再问“如何在有限的环境下实现所有功能?”,而是问“在这些参数条件下,我们能创造出的最佳体验是什么?”
我们从零开始设计,始终坚持离线优先原则。我们大幅压缩图像,并尽可能使用 SVG 格式。我们实现了增量更新,使应用能够在网络连接不稳定的情况下自动更新。我们采用了智能缓存和预测性预取。我们让每一字节都发挥最大效用。
结果如何?即使在网络连接极差的情况下,这款应用也能流畅运行,响应迅速。其体验甚至优于许多面向高端市场的应用,因为我们不得不深入思考性能和效率。那些专为高带宽、高性能设备设计的西方竞争对手,在这个市场上根本无法与之匹敌。他们的应用臃肿、运行缓慢且消耗大量数据。
这些限制并没有阻碍我们,反而使我们变得更强。
这条原则远不止于技术限制。想想时间限制吧。优秀的开发者会把紧迫的截止日期视为压力,而卓越的开发者则将其视为清晰目标的契机。当你拥有无限的时间时,你可以探索每一种可能的解决方案,无休止地重构,无限地打磨代码。听起来很棒,对吧?但无限的时间往往会带来更糟糕的结果,因为没有任何东西能迫使你分清轻重缓急,去识别真正重要的事项,去做出艰难的取舍。
我曾目睹一些项目因为截止日期宽松而漫无目的地拖延数月,不断添加新功能,重构代码,却始终无法真正发布。我也见过一些团队,他们被要求在两周内交付最小可行产品(MVP),最终却打造出了目标明确、范围清晰、真正解决用户问题的产品。时间限制迫使他们厘清哪些才是真正重要的。
或者考虑一下团队的限制。也许你是一个小型团队中唯一的后端开发人员。优秀的开发人员会觉得压力巨大——责任太重,维护工作太多。而卓越的开发人员则会将其视为塑造整个后端架构、做出一致的决策、积累深厚专业知识的机会。独自工作的限制迫使你编写极易维护的代码,因为维护工作将由你一人承担。
或者,也可能是遗留系统的限制。你正在集成一个已有 15 年历史、文档糟糕的 SOAP API。优秀的开发人员会抱怨,而卓越的开发人员则会将其视为构建一个清晰抽象层的机会,从而将代码库的其他部分与这种复杂性隔离开来。遗留系统的限制迫使你认真思考边界和接口。
以下是培养这种习惯的方法:
重新组织语言。不要再说“由于限制条件 Y,我们不能做 X”,而要说“在限制条件 Y 下,我们能设计出的最佳解决方案是什么?”这种语言上的转变会带来思维上的转变。你的思维方式会从以问题为中心转变为以解决方案为中心。
研究历史案例。Twitter最初的 140 个字符限制并非漏洞,而是塑造平台特性的一种限制。为超级任天堂开发游戏的开发者仅凭 32 KB 的内存就创作出了杰作。他们没有无限的资源,但正是这些限制激发了惊人的创造力和效率。阿波罗计划的制导计算机的计算能力甚至不如现代计算器,但它却将人类送上了月球。研究这些案例中限制是如何推动创新的。
人为地设定限制。这听起来很疯狂,但确实有效。如果你正在开发一个 Web 应用,不妨挑战一下自己:如果它不能使用 JavaScript 呢?如果它的打包大小必须小于 50KB 呢?如果它必须在 30 美元的安卓手机上运行呢?这些人为的限制会迫使你质疑既有假设,并探索不同的方法。你最终可能不会按照这些限制发布应用,但这种练习会让你成为一名更优秀的开发者。
接受“宁可差也好”的理念。有时候,一个虽然无法处理所有极端情况的简单方案,反而比一个能够处理所有情况的复杂方案更好。限制条件迫使你明确地做出这种权衡。UNIX 的设计理念——小型程序,每个程序做好一件事——正是源于内存和存储空间的极端限制。这些限制条件反而催生了比无限资源下更好的设计原则。
寻找限制的馈赠。每一种限制都在试图告诉你一些事情。内存限制提醒你要考虑效率。时间限制提醒你要关注影响力。遗留系统限制提醒你要设计简洁的界面。预算限制提醒你要使用成熟的技术,而不是一味追求新奇。那么,限制究竟在教你什么呢?
我见过很多开发者浪费大量精力去对抗限制,而不是顺应限制。他们会花几周时间设计绕过数据库查询限制的方案,而不是重构数据模型以适应限制。他们会为了绕过框架的设计而增加复杂性,而不是接受框架的理念。
优秀的开发者会选择值得挑战的目标。有时,限制条件确实存在缺陷,应该予以质疑。但更多时候,限制条件代表着复杂系统中的现实权衡,在限制条件下工作比对抗限制更能取得理想的结果。
这种习惯也能塑造性格。接受限制需要谦逊——承认你不可能拥有一切,权衡取舍是真实存在的,完美是无法企及的。它需要创造力——在限制范围内找到巧妙的解决方案。它需要专注——区分什么是必需的,什么是锦上添花的。
现代开发领域似乎总是追求更多:更多的工具、更多的框架、更多的库、更多的功能、更强的可扩展性。但一些最具影响力的软件却是在严格的限制下构建的。Redis 最初是为了解决一个对性能要求极高的特定问题而开发的。Unix 系统是为内存占用极小的机器设计的。Web 本身的设计初衷就是为了在不可靠的网络上运行,并且对客户端的能力要求极低。
当你接受限制条件时,你便不再与现实对抗,而是开始与之合作。你会成为一个务实的解决问题者,而不是一个理想主义的完美主义者。你会交付解决方案,而不是无休止地追求最优解。
这就是美妙的悖论:接受限制,往往反而能超越限制。限制迫使你培养的自律和创造力,反而能带来更优的解决方案,而不是更差的。例如,针对 2G 网络优化的应用,在 5G 网络下也能流畅运行。由单人开发者编写的、旨在易于维护的代码,在团队壮大后依然易于维护。受时间限制而精简的功能集,最终却恰好满足了用户的需求。
限制并非你的敌人。它们是你的老师,你的焦点,也是你激发创造性解决方案的催化剂。学会拥抱它们。
习惯六:在注意力分散的时代,他们培养深度专注力
现代开发者的环境就像一台精心设计的干扰机器。Slack 的提示音、电子邮件通知、没完没了的会议、“快速提问”,以及社交媒体和新闻推送的诱惑——所有这些都在合谋将你的注意力分割成无数碎片。
优秀的开发者在这样的环境下工作。他们不断切换任务,同时处理多个线程,并将快速响应视为一种美德。他们以忙碌为荣。
优秀的开发者会构筑专注的堡垒。他们深知,自己最宝贵的资产并非对框架或算法的掌握,而是能够长时间深度专注于复杂问题的能力。他们将不受干扰的时间视为不可或缺的资源,比任何云计算额度都更加珍贵。
这不仅仅是个人偏好,更是基于我们工作性质的必然要求。编程并非机械地敲击代码行,而是一种构建和解决问题的过程,很大程度上是在脑海中进行的。你需要构建复杂的系统、数据流和逻辑的心理模型。这些模型非常脆弱,一次小小的干扰就可能摧毁数小时的心血,迫使你从头开始重建。
我在职业生涯早期就为此付出了惨痛的代价。我一直以“永远在线”为傲。我同时打开八个不同的通讯软件,秒回消息,整天在编码、代码审查和技术支持工单之间来回切换。到了下午三点,我已经筋疲力尽,但工作效率却很一般。我投入了大量时间,却没有拿出最佳水平。
一切都在我与一位名叫大卫的高级工程师合作一周后发生了改变。大卫的工作方式很神秘,他会以两小时为一个工作时段。在这两个时段里,他会关闭所有通知,除了IDE和终端之外关闭所有应用程序,然后戴上耳机。起初,我以为他有点孤僻。但后来我看到了他的成果。在两个小时的专注时间内,他往往能完成我一整天分心工作才能完成的工作。而且质量更高——更少的bug,更简洁的设计,更周全的边界情况处理。他不仅速度更快,而且工作质量也达到了另一个层次。
大卫让我明白,专注力是一种需要培养的技能,而不是与生俱来的特质。而且,它或许是你所能培养的最有价值的技能。
以下是优秀开发者如何保持和培养高度专注力的方法:
他们会严格安排专注时间,绝不掉以轻心。他们会在日历上预留出几个小时的时间段,并像对待与CEO的会议一样认真对待这些与自己的约定。在这段时间里,他们实际上处于离线状态。有些公司甚至会制定诸如“无会议星期三”或“专注早晨”之类的政策,但优秀的开发者无论公司政策如何,都会自觉地遵循这些原则。
他们精通工具,但不会过度依赖。优秀的开发者使用“勿扰模式”、网站拦截器和全屏IDE等工具,并非为了提高效率,而是为了刻意避免干扰。他们的目标不是找到完美的应用程序,而是创造一个能够进行深度工作的环境。他们明白,工具本身并不重要,重要的是工作的目的。
他们奉行单任务处理。多任务处理其实是个误区,尤其是在编程领域。我们所谓的多任务处理,实际上是快速切换上下文,而每次切换都会消耗认知资源。优秀的开发者会训练自己专注于一件事,直到它自然而然地结束。他们可能会在手边放一个“干扰清单”——一个用来记录突发想法或待办事项的记事本——这样他们就能在不中断当前任务的情况下注意到这些想法。
他们勇敢地捍卫自己的专注。这是最难的部分。这需要对好心的同事说“不”,与管理者划清界限,并抵制随时待命的文化压力。优秀的开发人员懂得清晰礼貌地沟通这些界限:“我现在正在深度工作,但我可以在下午3点帮您。” 如果沟通方式始终如一,大多数通情达理的人都会尊重这一点。
他们深知切换工作内容的成本。每一次中断不仅耗时,还会耗费重新投入到最初问题中的时间。一个 30 秒的 Slack 问题就可能轻易中断 15 分钟的高效工作。优秀的开发者会让团队成员清楚地看到这种成本,从而帮助营造一种尊重深度工作的文化。
他们会根据自身的精力水平来安排一天的工作。专注力是一种有限的资源。优秀的开发者知道自己何时思维最敏捷——对许多人来说,是早晨——他们会竭尽全力把这段时间留给最需要投入精力的工作。会议、行政事务和代码审查都被安排在精力较低的时段。他们不会把精力最充沛的时间浪费在低价值、浅薄的工作上。
他们乐于接受无聊。这听起来很奇怪,但却至关重要。在感到沮丧或思维停滞时,人们的第一反应往往是拿起手机——从推特或电子邮件中寻求多巴胺的刺激。优秀的开发者会抵制这种冲动。他们会专注于问题本身,必要时甚至会凝视窗外,让潜意识发挥作用。一些最精妙的解决方案并非源于疯狂的敲击,而是在静默的思考中诞生的。
这种习惯的好处远不止提高效率。深度专注是精通的源泉。正是在这些不受干扰的专注时刻,你会遇到真正棘手的问题,这些问题会迫使你成长。你会培养出系统性调试的耐心,洞察精妙架构的清晰思路,以及克服复杂难题的毅力——这些难题足以让心不在焉的人束手无策。
此外,专注力会增强。就像肌肉一样,你的专注力会随着练习而增强。一开始可能难以集中注意力30分钟,但随着时间的推移,就能稳定地进行两个小时的深度工作。
在一个崇尚浅薄响应的世界里,选择深度专注似乎与主流文化背道而驰。但正是这种选择,将合格的开发者与卓越的开发者区分开来。那些能够经常进入心流状态的开发者,才能交付复杂的功能,解决最棘手的bug,并创造出近乎神奇的高质量作品。
你最宝贵的代码将在专注状态下编写。务必誓死捍卫这种状态。
习惯七:他们奉行策略性懒惰
如果你觉得“懒惰”是一种恶习而不是美德,那就大错特错了。优秀的开发者往往都很勤奋——他们会投入大量时间进行手动测试、重复配置和使用蛮力解决问题。他们认为付出就等于价值。
优秀的开发者懂得策略性地偷懒。他们乐于花一个小时自动化一个手动只需五分钟就能完成的任务,这并非因为这样能立即节省时间,而是因为他们极其厌恶重复劳动,愿意前期投入以彻底消除这种弊端。他们不断寻找能够以最小的持续投入获得最大产出的杠杆、捷径和抽象方法。
这条原则通常被认为是 Perl 语言的创始人 Larry Wall 提出的,也是程序员的三大美德之一(另外两个是急躁和傲慢)。策略性懒惰并非逃避工作,而是通过自动化枯燥乏味的工作来大幅提高效率,从而将精力集中在真正有挑战性、有趣的问题上。
我曾亲眼目睹过一个典型的例子,我的一位DevOps工程师就完美地诠释了这一点。我们的部署流程包含一个15步的检查清单,大约需要30分钟,而且需要高度集中注意力。任何一个步骤出错都可能导致生产环境宕机。我们大多数人都把它当作工作中必不可少但又繁琐的部分。
然而,她却无法忍受。两天之内,她编写了一套脚本,实现了整个流程的自动化。最初的投入相当可观——大概花了16个小时。但之后,部署只需2分钟,而且零失误。一个月后,她就为整个团队收回了时间成本。一年下来,她节省了数百小时,并避免了无数潜在的故障。这就是所谓的“战略性偷懒”。
这种习惯主要体现在以下几个方面:
他们孜孜不倦地进行自动化。如果某件事需要重复执行两次以上,他们就会编写脚本。环境搭建、数据库迁移、构建流程、测试例程——所有这些都是自动化的理想对象。他们不仅考虑节省时间,还考虑减少认知负担和避免错误。
他们构建工具和抽象层。优秀的开发者不仅仅解决眼前的问题,他们解决的是一类问题。当他们发现代码中存在重复模式时,他们不会简单地复制粘贴并稍作修改——而是会提取函数、创建库或构建框架。他们宁愿花时间设计简洁的 API,也不愿第十次编写相同的样板代码。
他们是委派任务的高手——把任务委派给计算机。他们不断问自己:“这其中哪些部分可以由计算机处理?” 代码检查、格式设置、依赖更新、性能监控——优秀的开发人员会手动完成这些任务,而卓越的开发人员则会将它们委派给自动化系统。这解放了他们的精力,让他们可以专注于真正需要人类智慧的任务。
他们追求的是长期的简洁性,而非短期的速度。这种“策略性偷懒”的开发者深知,最容易编写的代码往往最难维护。因此,他们会在前期投入更多时间,打造一个简洁清晰、便于日后修改的设计。他们对未来的工作比较“懒惰”,所以现在就把最难的部分都考虑进去。
他们会充分利用现有解决方案。策略性地偷懒的开发者不会在 Auth0 存在的情况下自行构建身份验证系统,也不会在已有结构化日志库的情况下编写自定义日志框架。他们倾向于使用经过实战检验的解决方案,而不是重复造轮子。他们的目标是解决业务问题,而不是自己编写每一行代码。
如何培养策略性懒惰:
培养对重复性工作的厌恶感。留意那些你反复做的任务。它们是否让你感到厌烦?如果是,那就说明你需要自动化了。从小处着手——比如用 shell 脚本搭建项目,或者用宏生成样板代码。消除重复性烦恼带来的满足感会让人上瘾,并激励你进一步自动化。
问自己一个“懒人问题”。在开始任何任务之前,问问自己:“有没有更简单的方法?”“我以后还要再做一遍吗?”“我能让电脑帮我完成那些枯燥的部分吗?”这种简单的元认知能力能够区分习惯性懒惰和策略性懒惰。
投资你的工具链。花时间学习 IDE 的快捷键、精通 shell 或配置代码检查工具并非浪费时间——而是会产生复利效应。花几个小时学习 Vim 的手势操作或 VS Code 的多光标编辑,一年下来可以节省你数天的打字时间。
先构建,再利用。构建自动化流程或抽象层时,要考虑如何使其可重用。一个仅适用于单个项目的脚本固然不错;但一个可以跨多个项目使用的工具则更胜一筹。为你的工具编写文档——未来的你会感谢自己当初的明智之举。
策略性偷懒的妙处在于它惠及所有人,而不仅仅是你。你编写的部署脚本对整个团队都有帮助。精心设计的抽象层让代码库更易于所有人协作。自动化测试套件则能为所有未来的开发人员预防 bug。
这种习惯能让你从一个只会写代码的程序员变成一个效率倍增器。你不再只是代码的生产者,而是能够构建更高效、更有价值的系统的开发者。你不仅能埋头苦干,还能让整个团队的工作更轻松、更高效。
归根结底,这才是值得培养的那种懒惰。
习惯八:他们与生产环境保持反馈循环
优秀的开发人员编写代码、运行测试,然后将其部署到生产环境。他们相信,只要测试通过且构建成功,他们的工作就完成了。他们把生产环境看作一个遥远而略带神秘的地方,是运维团队需要操心的。
优秀的开发者与生产环境保持着密切且持续的联系。他们不会发布代码后就置之不理;他们会密切关注代码的运行状态,并跟随其进入实际应用场景。他们把生产环境视为检验代码行为、性能和价值的最终标准,而不是最终目的地。
这种习惯决定了理论正确性和实际应用之间的区别。你的代码可以通过所有测试,满足所有要求,在代码审查中看起来也很漂亮,但如果它在生产环境中崩溃,这一切都毫无意义。优秀的开发者明白,生产环境是他们的假设与现实碰撞的地方,而现实总是会胜出。
我职业生涯早期的一次灾难性性能下降就让我吸取了教训。当时我们开发了一个新功能,其中包含一个复杂的数据库查询。这个查询语句很优雅,使用了所有正确的连接(JOIN),并且通过了所有的单元测试和集成测试。我们的测试数据库只有几百行合成数据,查询速度非常快。
我们在周五下午发布了程序。到了周六早上,数据库就崩溃了。在生产环境中,面对数百万行真实数据,那条看似“优雅”的查询竟然执行了全表扫描。它超时了,导致表被锁定,整个应用程序瘫痪。我们整个周末都在惊慌失措地回滚并编写修复程序。
我们团队里一位优秀的开发人员玛丽亚对此事非常在意。倒不是因为是她写的那条错误的查询语句(她并没有),而是因为她认为这是系统性的问题。“我们不能只测试代码是否有效,”她说,“我们必须测试它在实际环境下是否有效。”
从那天起,她就成了我们生产反馈循环的守护者。
以下是保持紧密生产反馈循环在实践中的样子:
他们会监控一切。优秀的开发者不仅记录错误,还会衡量所有重要的指标,例如响应时间、吞吐量、错误率、业务指标、缓存命中率和数据库查询性能。他们从一开始就将可观测性——指标、日志和追踪——融入到代码中。他们深知,看不到的问题就无法修复。
他们像老鹰一样紧盯部署情况。代码发布后,他们不会立刻着手处理下一个问题。他们会密切关注部署指标,监控错误率,查看性能仪表盘。他们甚至会花几分钟时间观察真实用户的会话,了解功能的实际使用情况。这种即时反馈让他们能够及时发现那些绕过测试的回归问题。
他们奉行“谁开发谁运维”的理念。这种源自亚马逊的理念意味着开发者要对生产环境中的代码负责。他们需要随时待命,确保功能正常运行。一旦出现问题,他们会被通知。这听起来或许有些苛刻,但却是你能想象到的最有效的反馈机制。没有什么比知道如果你不编写健壮、容错的代码,凌晨三点你的电话就会响起更能激励你编写出健壮且容错性强的代码了。
他们非常重视功能开关的使用。优秀的开发者不会一次性发布所有功能。他们会将新功能封装在功能开关中,然后逐步推出——先面向内部用户,然后是 1% 的客户,再是 10%,以此类推。这样一来,他们就能在最小范围内获得真实用户的反馈。如果出现问题,他们只需单击一下即可关闭该功能,而无需进行完全回滚。
他们分析生产数据来做决策。我们应该优化这个查询吗?一个优秀的开发人员可能会凭感觉判断。而一个卓越的开发人员会查看生产指标,了解查询的调用频率、平均延迟和 p95 延迟是多少,以及它对用户体验的影响。他们让生产数据指导他们的优先级排序。
他们会坦然面对并从事故中吸取教训。当生产环境出现问题时,优秀的开发人员不会互相指责。他们会主导并参与客观的事故分析。他们会深入挖掘问题的根源,而不仅仅是表面症状。更重要的是,他们专注于系统性的解决方案,以防止此类问题再次发生,而不仅仅是修补眼前的漏洞。
如何养成这个习惯:
从一开始就让你的应用可观测。在编写业务逻辑之前,先设置好结构化日志、指标收集和分布式追踪。之后再添加这些功能会困难得多。从简单的入手——即使只是记录关键业务事件和性能边界,也是向前迈出的一大步。
创建个人仪表盘。构建一个仪表盘,用于显示你负责的功能的运行状况。让它成为你每天早上醒来第一件事,也是每次部署前最后一件事。养成这个习惯,可以增强你对代码实际运行状况的掌控感和认同感。
主动要求轮值值班。如果你的团队有轮值值班制度,就加入;如果没有,就提议设立。被寻呼机吵醒,发现自己写的代码出了问题,这种经历会让你终生难忘。它将彻底改变你对错误处理、日志记录和系统设计的看法。
练习“生产环境调试”。下次生产环境出现问题时,即使问题不在你的代码中,也要询问是否可以跟随调试人员学习。观察他们如何使用日志、指标和跟踪信息来精确定位问题。这项技能只能通过实践来学习。
小规模、高频率地发布。部署频率越高,每次变更的幅度就越小,系统变更与系统行为变更之间的关联性也就越容易建立。频繁部署可以降低对生产环境的恐惧,使其变得熟悉且易于管理。
维护这种反馈循环的作用远不止于预防 bug——它还能形成一个完整的学习闭环。你基于假设编写代码,而生产环境会告诉你哪些假设是错误的。也许用户使用你的功能的方式是你从未预料到的。也许你认为的“极端情况”实际上很常见。也许你假设的性能特征在实际负载下完全不同。
这种持续学习才能将优秀的程序员培养成卓越的工程师。你不再闭门造车地设计系统,而是开始基于对系统在实际运行环境中表现的深刻而直观的理解来构建它们。
生产环境是你所能遇到的最严格、最诚实的代码审查员。听从它的。
习惯九:他们有意识地而非偶然地将学习放在首位
技术领域瞬息万变,犹如一条奔腾的河流。新的框架、语言、工具和范式层出不穷,迅速风靡一时,却又往往在短短几年内销声匿迹。优秀的开发者如同在这条河流中奋力游弋,努力保持清醒。他们的学习方式是被动的——工作需要才去学习新的 JavaScript 框架,Hacker News 上突然冒出的博客文章让他们匆匆浏览,遇到具体问题才去看教程。他们的学习是临时性的,受制于眼前的需求和生态系统中最具影响力的声音。
优秀的开发者并非随波逐流,而是建造一艘船,规划航线。他们深知,在特定技术生命周期仅有数年的领域,唯一可持续的优势在于深入高效地学习。他们并非被动地学习,而是主动学习。他们的学习是一项系统性的、持续性的投资,以对基本原理的清晰理解和长远目标为指导,而非受制于技术潮流的瞬息万变。
这可以说是所有习惯中最重要的一种,因为它是其他所有习惯的基础。它是成长的引擎。
我亲眼见证了这种习惯的力量,它体现在我团队中两位背景和才能都相似的开发人员身上。我们姑且称他们为 Alex 和 Ben。
Alex是个典型的被动学习者。他聪明能干。当团队决定采用一个新的状态管理库时,他立刻投入其中。他只学习了完成任务所需的基本知识。他搜索特定的错误信息,从现有代码中复制模式,最终熟练掌握了相关功能。他的知识面很广,但深度却很浅——他掌握了一系列针对特定问题的解决方案,却缺乏统一的思维模式。
本采取了不同的方法。面对同样的新库,他并没有仅仅阅读“入门指南”,而是花了一个周末的时间,用它搭建了一个临时项目。之后,他又从头到尾阅读了官方文档。他还观看了库创建者的演讲,以了解该库背后的设计理念——它真正旨在解决哪些问题,以及它做出了哪些权衡。他不仅学会了如何使用它,还了解了它为何如此设计,以及它在什么情况下是合适的工具,在什么情况下是错误的工具。
六个月后,情况发生了惊人的变化。Alex 可以使用库完成任务,但他编写的代码经常违背库的核心原则,导致出现一些不易察觉的错误和性能问题。每当遇到新问题时,他往往束手无策。
另一方面,本已经成了团队的专家。他能预见问题,并能设计出巧妙的解决方案,充分利用库的优势。他还能用浅显易懂的方式向初级员工解释库的概念,让他们牢记于心。他不仅是工具的用户,更是它的专家。
亚历克斯是偶然学会的,本是刻意学习的。
优秀的开发者是这样安排他们的刻意学习的:
他们学习的是基础知识,而非花样。优秀的开发者深知,尽管 JavaScript 框架层出不穷(还记得 jQuery、AngularJS 和 Backbone.js 吗?),但 Web 的底层基础——DOM、事件循环、HTTP 和浏览器渲染——却始终如一。他们投入时间理解计算机科学的基础知识:数据结构、算法、网络、操作系统和设计模式。这些永恒的原则使他们能够快速而深入地评估和学习任何新的技术“花样”。如果你已经理解了声明式 UI、虚拟 DOM 概念和单向数据流的原理,学习 React 就变得轻而易举。你不是在死记硬背 API,而是在理解更深层次概念的体现。
他们会维护一个“学习待办清单”。就像他们有待开发的功能清单一样,他们也会维护一个个人学习清单,列出需要学习的概念、需要探索的技术和需要阅读的书籍。这并非空泛的“我总有一天会学Go语言”,而是一份具体的清单:“阅读《设计数据密集型应用程序》”、“构建一个简单的Rust命令行工具来理解内存安全”、“完成Coursera上的‘开发者网络编程’课程”。这使得学习从抽象的意图转化为可管理、可执行的项目。
他们会专门安排“学习时间”,并且竭力保护它。他们不会把学习浪费在一天疲惫的会议和编码工作之后剩下的零碎时间里,而是会把它安排进日程。我认识的许多优秀开发者每周都会抽出一个下午,或者每天早上上班前抽出几个小时,专门用于学习。这段时间对他们来说弥足珍贵,不是用来查看邮件或处理紧急事务的,而是用来进行深入、不受干扰的学习和练习的。
他们通过实践学习,而非仅仅被动接受。被动接受——阅读、观看视频——仅仅是第一步。优秀的开发者通过实践来内化知识。他们不会仅仅阅读关于新数据库的资料;他们会安装数据库,导入数据集,并运行查询。他们不会仅仅观看关于新架构的教程;他们会构建一个实现该架构的演示项目。这种实践能够建立起理论无法企及的强大而持久的神经通路。他们明白,真正的精通不仅体现在大脑中,更体现在指尖的实践中。
他们直奔源头。当一款新工具出现时,被动学习者会阅读一篇“十分钟入门”的博客文章。而主动学习者则会直接查阅原始资料:官方文档、原始研究论文(如果有的话)或开发者的演讲。他们明白,二手资料往往过于简化、带有主观色彩或已经过时。真相,以及其中所有微妙的复杂性,通常都隐藏在源头之中。阅读 React 官方文档或 Dan Abramov 的博客文章,与阅读某个不知名博客上的“React 使用技巧”列表,是截然不同的学习方式。
他们教自己学到的东西。有意识的学习者深知,检验理解程度的最终标准是能否将概念解释给他人。他们会撰写博客文章,为团队准备自带午餐,参与文档编写,或者简单地向同事解释自己学到的知识。整理思路以便进行教学的过程,迫使他们正视自身理解上的不足,并巩固已有的知识。这是学习循环的最后一步。
他们会明智地筛选信息来源。数字世界充斥着大量低质量、重复且常常错误的信息。优秀的开发者会严格筛选自己的信息。他们不会试图阅读所有内容。他们会甄选出少数几个值得信赖、高质量的信息来源——特定的博客、期刊、播客或个人——然后忽略其他信息。他们更注重深度而非广度,更注重质量而非数量。
如何培养这种习惯:
每季度进行一次“技能盘点”。每三个月,认真盘点一下自己的技能。哪些技能在增强?哪些技能正在过时?你需要了解哪些新兴趋势?根据这次盘点,更新你的学习计划。这将使你的职业发展从被动的过程转变为由你掌控的主动过程。
遵循“20%法则”。拿出固定比例的时间——哪怕一开始只有5%——去学习那些与你当前工作没有直接关系的知识。这样可以避免技术过时,也能让你意外发现更高效的工作方式和新的机遇。
制定一份“个人学习计划”。如果你想成为分布式系统专家,你需要学习哪些内容?学习顺序又该如何安排?一个有意识的学习者会像制定大学课程一样,为自己制定学习计划。他们可能会先从教科书入手,然后阅读经典论文,最后完成一个项目。这种结构化的方法比漫无目的地探索要有效得多。
找到一个学习小组。独自学习很难。找一两个和你拥有共同成长型思维的同事。组织读书会、学习小组或“技术深度研讨”活动。这种社交互动能让你保持自律,而讨论则能加深你的理解。
这种习惯带来的回报是无法估量的。它决定了开发人员的价值是会在职业生涯的第五年达到顶峰,还是会随着时间的推移而不断提升。它决定了你是任由就业市场摆布,还是成为各公司争相抢夺的对象。
刻意学习是职业生涯的终极资本。在这个瞬息万变的世界里,学会如何学习,并有目的地、有策略地学习,绝非锦上添花。它是预测长期成功的最佳指标。它就像一台静默而持续的引擎,将优秀的程序员培养成卓越的程序员,再将卓越的程序员打造成真正的行业大师。
习惯10:他们建立并培养自己的工程判断力
你可以精通所有技术技能,编写完美无瑕的代码,以科学的精准度进行调试,并构建简洁优雅的系统。你可以拥有百科全书般的算法知识,并与生产环境保持密切联系。但是,如果没有最后也是最难得的习惯,你永远无法跨越鸿沟,从一名优秀的技术人员蜕变为一名真正伟大的工程师。
最后一个习惯是培养工程判断力。
工程判断力是你所有技术决策中默默无闻的伙伴。它就像一个内在的指南针,在你面对需求、文档和最佳实践等信息匮乏时为你指引方向。它蕴含着丰富的智慧,告诉你何时应该遵循规则,更重要的是,何时应该打破规则。正是工程判断力,区分了技术上正确的解决方案和真正明智的解决方案。
优秀的开发者在遇到问题时会问:“技术上最优的解决方案是什么?”他们会找到最高效的算法、最具扩展性的架构和最简洁的代码结构。他们追求的是技术上的完美。
优秀的开发者会提出一系列更复杂的问题:“对于这个团队、这个业务环境、这个时间节点来说,什么才是最合适的解决方案?” 他们会权衡技术上的理想与错综复杂的现实,例如截止日期、团队技能、业务目标以及长期维护等。他们明白,最佳的技术方案有时也可能是最糟糕的工程决策。
我并非从成功中领悟到这一点,而是从一次至今仍让我耿耿于怀的失败中。职业生涯初期,我受命开发一项新的报表功能。当时的旧系统是由大量嵌入PHP的SQL查询语句组成的混乱体系,运行速度慢、难以维护,而且修改起来极其麻烦。
我看到了大展身手的机会。我设计了一个基于事件溯源的精美架构,并采用了 CQRS 模式。它的技术堪称完美。它拥有无限的可扩展性,提供完善的审计追踪,并支持复杂的历史查询。这正是你在软件架构书籍中才会读到的那种系统。我为此感到无比自豪。
这也是一次灾难性的失败。
这个项目耗时是预估的三倍。代码库的复杂度极高,只有我一个人能看懂。我最终离开公司后,团队花了几个月的时间才勉强维护好它,最终不得不以一种更简单、更粗糙的方式重写了整个功能。我当初“技术上最优”的方案,实际上是一场工程灾难。它既不符合团队的技能水平,也不符合业务对速度的需求,更不利于代码库的长期健康发展。
我具备技术技能,但在工程判断力方面却不及格。
工程判断力是将所有其他习惯综合起来,形成一种专业智慧。它体现在以下几个方面:
他们深谙“足够好”的范畴。优秀的开发者明白,并非系统的每个部分都需要做到完美无瑕。一次性营销活动的原型无需像核心身份验证服务那样强大。内部管理工具比面向客户的API更能容忍技术债务。他们会做出有意识的、深思熟虑的权衡。他们会问:“为了成功解决问题,同时又不造成不可接受的未来风险,最低质量要求是什么?” 这并非懒惰,而是对资源的战略性分配。
他们目光长远。一位判断力敏锐的开发者能够预见决策的二阶和三阶后果。他们不仅关注眼前的功能实现,更会思考它将如何限制未来的变更,可能引入哪些新的 bug 类型,以及它将如何影响系统的概念完整性。在选择库时,他们不仅评估其功能,还会评估其维护状况、升级路径、社区健康状况以及架构理念。他们着眼于长远,而其他人甚至都看不到这一点。
他们兼顾理想主义与务实主义。他们对代码质量有着强烈的信念,但又不会固执己见。在规划会议上,他们会热情地力主构建清晰的架构,但如果业务环境需要更快捷、更“粗糙”的解决方案,他们也能坦然接受并执行务实的选择。他们会记录所做的权衡取舍和产生的技术债务,创建工单以便日后处理,然后继续前进。他们明白,软件的存在是为了服务于业务,而不是反过来。
他们要在不确定性中做出决策。需求模糊不清,时间紧迫,信息不完整。这就是软件开发的现实。优秀的开发人员会因此停滞不前,要求获得更多确定性、更多规范和更多时间。而卓越的开发人员则会运用判断力,在现有信息的基础上做出最佳决策。他们会识别核心风险,做出合理的假设,并制定行动方案。他们深知,拖延决策的代价往往比做出略微错误的决策更大。
他们区分症状和病因。初级开发人员处理的是症状:“页面加载缓慢,我们添加缓存吧。”优秀的开发人员会发现病因:“页面加载缓慢是因为 N+1 查询问题,我们修复查询吧。”而拥有敏锐判断力的卓越开发人员会质疑病因本身是否也是症状:“为什么这个页面要进行这么多查询?我们的数据模型有问题吗?这个功能是不是想做太多事情?我们是否应该预先计算这些数据?”他们以更高的抽象层次进行工作,解决的是问题类别,而不是个别实例。
如何培养工程判断力(因为它无法教授,只能培养)
判断力不是一种可以从书本上学到的技能。它是一种隐性知识,需要通过经验、反思和特定的实践慢慢积累。
寻求多元化的经验。判断力本质上是一种大规模的模式匹配。你见过的模式越多,你的判断力就越强。去一家以速度为重的初创公司工作,去一家以稳定性为首要考量的大型企业工作。尝试前端、后端和基础设施的开发。每种环境都会教会你一套不同的价值观和权衡取舍。只在单一环境中工作的开发者,其判断力会非常狭隘,甚至存在安全隐患。
对自己的决策进行回顾。这是最有效的实践方法。不要在项目结束后或决策做出后就匆匆结束。安排一次个人回顾。拿出笔记本,问问自己:
“我当时做出的关键技术决策是什么?”
“我当时的理由是什么?”
“这些决策的最终结果如何?比预期更好还是更糟?”
“我错过了什么?如果可以重新来过,我会怎么做?”
这种自我反思的过程,正是将经验转化为智慧的方式。
找到一位“尤达大师”。找到一位你非常敬重的高级工程师——一位似乎拥有超凡判断力、总能做出正确决定的人。仔细观察他/她。当他/她做出看似违反直觉的决定时,请他/她解释背后的逻辑。不仅是技术原因,还要包括背景、人性以及商业因素。他/她所展现的这些细微之处,正是判断力的基石。
练习阐述“为什么”。当你提出建议时,要强迫自己不仅解释你认为应该做什么,还要解释为什么。列出你考虑过的权衡取舍。解释你否决的方案以及原因。阐述你的理由会迫使你审视其合理性,并暴露逻辑中的缺陷。它还能让其他人参与到你的思考过程中,从而挑战并完善你的判断。
拥抱“可逆性”启发式原则。当面临艰难抉择时,问问自己:“这个操作的可逆性如何?”对于代码库而言,采用新的编程语言几乎是不可逆的。添加复杂的微服务架构也很难撤销。选择云服务提供商会带来锁定效应。这些都是需要高度判断力的决策。另一方面,重构模块、更改 API 端点或尝试新的库通常很容易撤销。优秀的开发者对于不可逆的决策会更加严谨,要求更高的确定性,而对于可逆的决策,他们的行动则更加迅速。
培养分寸感。这或许是判断力中最微妙的方面。它意味着要明白,花两天时间优化一个每天只运行一次的函数是浪费时间,但花两天时间优化一个每秒调用一万次的函数却至关重要。它意味着要明白,结账流程中 10% 的性能下降是紧急情况,而“关于我们”页面中 10% 的性能下降则无关紧要。这种分寸感使他们能够将精力集中在真正重要的事情上。
十大习惯的叠加效应
单独来看,这些习惯都能让你成为更优秀的开发者。但它们真正的力量并非简单的叠加,而是相乘式的。它们会产生复合效应。
广泛阅读代码(习惯 1)构建了你的思维库,为你的工程判断提供依据(习惯 10)。理解“为什么”(习惯 2)使你能够做出策略性惰性(习惯 5)和合理判断(习惯 10)所需的务实权衡。培养深度专注力(习惯 6)能够让你进行刻意学习(习惯 9),从而避免做出幼稚的决定。将调试视为一门科学(习惯 3)并与生产环境保持反馈循环(习惯 8)提供了原始数据,你的判断力会将这些数据综合起来,最终形成智慧。
这不是一份需要完成的清单,而是一个需要逐步构建的系统,一种需要逐步形成的身份认同。你不可能在一周或一年内掌握这些,这需要毕生的积累。
先从一个开始。选择一个你现在最能产生共鸣的习惯,一个你觉得既必要又遥不可及的习惯。有意识地练习一个月。然后再增加一个。
从优秀程序员到卓越程序员的道路并非直线,而是一个螺旋式上升的过程。在你的职业生涯中,你会反复运用这些习惯,每一次都会对它们有更深刻的理解,每一次都会将它们更充分地融入到你的实践中。
目标并非职位头衔或薪水,而是成为那种不仅编写代码,更能解决问题的开发者。那种不仅构建功能,更能构建健壮、易维护且真正令人愉悦的系统开发者。那种能让每个代码库、每个团队、每个组织都比他们加入时更好的开发者。
这就是工作,这就是技艺。而这一切都始于此刻的决定:不仅要做到优秀,更要开始刻意追求卓越,并为此投入终身努力。
文章来源:https://dev.to/thebitforge/10-developer-habits-that-separate-good-programmers-from-great-ones-293n
