很多年前,当我还是一名计算机专业的大四学生时,整天上网浏览各类招聘信息,想找到一个合适的程序员实习岗位。除了实习岗位外,我偶尔也会点进一些“高级工程师”的招聘帖里。现在回想起那些帖子,抛开让人眼花缭乱的技术名词,我印象最深的就是常出现在第一行的岗位年限要求:“本职位要求 工作经验 5 年以上”。

作为一只一天班都没上过的小菜鸟,这些年限要求在我眼里简直长到夸张。不过,望洋兴叹之余,我有时也会在心中暗暗憧憬一下:“五年工作经验的程序员,那该多厉害啊?写代码对于他们来说,是不是像吃饭一样简单?”时光荏苒,一晃十几年过去了。如今回头一望,自己也成了一名有着 14 年工作经验的光荣打工人。在软件开发行业摸爬滚打这些年后,我发现很多事情,与我在大四时所想象的大不相同。

随着经验增长,编程并不会变简单太多,“像吃饭一样简单”只出现在梦里。给许多“大项目”写代码不光没意思,还很危险,远不如在 LeetCode 上做一道算法题有趣。只从技术角度思考问题,成不了好程序员,有些东西远比技术更重要。细想起来,这类关于编程的感触还有许多。我整理了其中 8 条,写成了这篇文章。如果其中某些观点引起了你的共鸣,我会非常高兴。

更多编程干货:

一、写代码很简单,但写好代码很难

编程曾经是一项门槛很高的专业技能。从前,一个普通人想学编程,最常见的做法就是通过教材和书本学习。不过大部分编程专业书,十分艰深晦涩,对于初学者来说很不友好。因此不少人在尝到编程的乐趣前,就早早地半途而废。

但如今,学编程正在变得越来越容易。学习不再像以前那样,只能硬啃书本,而是多了许多新途径。观看教学视频、参加 Codecademy 的交互式课程,甚至直接在 CodeCombat 通过玩游戏来学编程,每个人都能找到适合自己的学习方式。

“妈,我真没在玩游戏,我在学编程呢!你看屏幕右边!”此外,编程语言也在变得越来越易用。经典的 C 和 Java 不再是大多数初学者的首选,许多更简单、更易上手的动态类型语言如今大受欢迎,与之相关的 IDE 等工具也变得越来越完善。这些因素进一步降低了编程的学习门槛。总而言之,编程早已褪去了它的神秘面纱,从只有少数人才能掌握的神秘技能,变成了一门人人皆可学习的普通手艺。

但更低的学习门槛,更友好的编程语言,并不意味着人人都能写出一手好代码。如果你已经工作,参与过一些项目,那我很想问你一个问题:”你日常接触的这些项目的代码质量如何?是好代码多,还是烂代码多?”

不知你会怎么回答,我先来说说我的答案。首先,好代码在现实中的出现概率仍然很小。2010 年,我跳槽到了一家总部位于北京五道口的大型互联网公司。加入这家公司前,我只在十人规模的小公司待过,因此,我对新公司在各方面都有着很高的期待,尤其是软件质量方面。当时,我心里想的大概是这样:“这可是支撑了有着千万用户量的产品的大项目,代码质量跟之前那些比,肯定有质的飞跃吧!”

等到在新公司工作了一周后,我才发现自己实在是错得离谱。所谓“大”项目的代码质量同我的预期相去甚远。打开 IDE,数百行的函数和神秘的数字字面量比比皆是,开发任何一个小需求都难如登天。后来,在待过更多公司,接触了更多软件项目后,我总结出一个道理:不论公司多大、项目多牛,在实际工作中遇见好代码,仍然是小概率事件。

那么,到底怎样的代码才算是好代码呢?在这方面,Martin Fowler 有一句话常被大家引用:“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” “任何傻瓜都能写出计算机能理解的代码。优秀程序员写人类能理解的代码。”我认为它可以作为评价好代码的原点:好代码一定是可读、易读,且容易理解的。写出好代码的第一原则,就是把人类读者放在第一位。

除了可读性以外,评价代码好坏还有许多其他维度:贴合编程语言:是否使用了当前编程语言的推荐写法?语言特性和语法糖,使用程度是否恰到好处?易于修改:代码设计是否考虑了未来的需求变更,当变化发生时,代码是否容易随之修改?API 设计合理:API 设计是否合理,易于使用?好的 API 在简单场景下使用方便,在高级场景下又可以随需求扩展。性能够用:代码性能是否满足当前业务需求,同时为未来保留了一定提升空间?避免过度设计:代码是否存在过度设计、过早优化的毛病?

综上所述,虽然我们不能保证每个人都能写出高质量的代码,但是了解好代码的特征和评价标准可以帮助我们提高自己的编码水平。在实际工作中,我们应该努力追求高质量的代码,以提高整个团队的工作效果和产品质量。

总的来说,对于任何层级的程序员来说,好代码都不是唾手可得的。要写出高质量的代码,需要在许多维度上反复权衡、精心设计,最后再进行持续打磨。既然如此,如果想尽快掌握编写代码这门技能,是否有捷径呢?

3. 编写高质量代码的捷径

在许多层面上,编程和写作非常相似[3]。二者都是使用文本和符号来表达思想,只是方式略有不同。谈到写作,我想问一个关于作家的问题:“你听说过不读书的作家吗?你有没有听到过某位作家说,他从来不读其他人的作品,只读自己的东西?”。我猜答案应该是否定的吧。

如果你去查阅相关资料,你会发现许多职业作家的日常生活,就是阅读和写作两件事在不断循环。他们每天会花大量时间阅读各类文字,然后再进行写作。同样是“文字工作者”,程序员们却很少重视阅读。但要想快速提升编程能力,阅读正是不可或缺的重要一环。除了日常工作接触到的项目以外,我们应该更多地阅读那些经典软件项目,从中学习 API 设计、模块架构和代码编写的技巧。

不仅要阅读代码和技术文档,最好定期读一些计算机方面的专业书籍,保持阅读的习惯。在这方面,我认为 Jeff Atwood 在 15 年前撰写的文章 “Programmers Don't Read Books -- But You Should(都说程序员不读书——但你应该读)”[4],如今读来仍不过时。

提升编程能力的捷径,就藏在“阅读 -> 编程”这个无尽循环里。那么“一个好的程序员应该做什么?”

二、编程的精髓是“创造”

在程序员的日常工作中,有很多事情会让人充满成就感,甚至情不自禁地感叹“编程真美好”。比方说,修复了一个极难定位的 Bug,用新算法将代码性能提升了一倍等。但在所有的这类事情当中,没有任何一件能与“亲手创造出一件东西”相比。

当你在编程时,创造新事物的机会实际上随处可见。因为并非只有发布一个新软件才称得上是“创造”。写一个可复用的工具函数、设计一套清晰的数据模型等都可以归入“创造”的范畴。

作为程序员,保持对“创造”的热情至关重要。因为它可以帮助我们:更高效地学习:学习一门新技术的最高效方式就是用它开发一个真实项目,在创造的过程中学习,效果最佳。

机会邂逅了不起的东西:许多改变世界的开源软件,最初都是作者纯粹出于兴趣所创造,比如 Linus Torvalds 和 Linux,Guido van Rossum 和 Python。1989 年的圣诞假期,荷兰人 Guido van Rossum 敲下了 Python 语言的最初几行代码,Python 最初仅被期望作为 ABC 语言的继承者,但后来“吞噬”了全世界。

虽然“创造”好处多多,程序员们也有大把机会去做,但许多人常常缺少一种身为“创造者”的觉悟。就像那个广为流传的小故事所说:一位哲学家询问正在砌砖的工人,有人清楚地知道自己是在建造一座大教堂,有人却认为自己只是在砌砖。很多程序员正是“只见砖块,不见教堂”。

将自己定位成创造者后,看待事物的方式就会发生天翻地覆的变化。举个例子,同样是给 API 增加报错提示文字,创造者们就能跳出“快速完成需求就好”的思维陷阱,向前一步,追问自己一些更重要的问题:“我想为用户创造什么样的产品体验?怎样的报错文字,更能帮助我达成该目标?”就像任何一个有用的编程模式一样,“创造者思维”也能成为你的职业生涯的一道巨大推进力。因此,现在就试着问自己一个问题吧——“我的下一份创造会是什么?”

三、打造高效试错的环境至关重要

我曾参与开发过一个互联网产品,它设计精美,功能丰富,每天都有大量用户使用。但就是这么一个从市场角度看颇为成功的产品,工程质量却非常糟糕。如果你打开它的后端项目,把所有目录翻个底朝天,都找不到任何一行单元测试代码,其他自动化测试流程也是无从谈起。而业务逻辑偏偏又十分复杂,最后,项目代码间的意料耦合多如牛毛,开发一个新特性,很容易把旧功能给搞挂。

“在忙啥呢?”“试着修复我之前修一个问题时搞出来的问题,那问题是我之前解决另一个问题搞出来的,而那个问题又是我......”因此,项目每次发布时,开发和产品同学全都得严阵以待,氛围十分紧张。整个发布过程也很刺激,紧急回滚时有发生。一个人在这样的环境中工作,技术成长抛开不谈,心理素质肯定能得到极大锻炼。

编程原本是一件充满乐趣的工作,但为这样的项目编程,乐趣根本无从谈起。究竟是什么夺走了编程的乐趣?首先是理想的编程体验≈“刷题”,其次是高效的团队协作和沟通。为了提高编程效率和质量,我们需要不断优化工具链、提升编码规范、加强代码审查等。同时,我们也需要关注自己的心理健康,保持良好的心态和乐观的心情。只有这样,我们才能在这个充满挑战和机遇的行业中不断成长和进步。

以下是重构后的内容,我尽量保留了原文的意思和结构:

LeetCode[5]是一个著名的编程学习网站,提供了许多覆盖各个难度的编程题,大部分与算法相关。用户可以选择自己感兴趣的题目,在浏览器上编写代码(支持十几种编程语言)并执行。如果通过了全部的测试用例,则算作解答成功。在LeetCode上做题很像在玩游戏,富有挑战性,同时也很有趣。整个做题过程完美展现了一种理想化的编程体验,包括关注点分离、快速获得精准反馈和零成本试错。

当然,全世界的软件千差万别,开发起来不可能都像在LeetCode上刷题一样轻松愉快。但这并不意味着我们不应该努力改善自己身处的编程环境,哪怕只有一点点。要通过改善环境来提升编程体验,可用的理念和工具包括模块化思想、设计原则、自动化测试、缩短反馈回路和微服务架构等。关注编程环境,刻意创造出允许高效试错的“代码乐园”,让工作像刷题一样轻松愉快。这是经验丰富的程序员能为自身团队做出的最好贡献之一。

此外,在代码质量上精益求精是好事,但也要注意别掉进完美主义的陷阱。因为编程不是艺术创作,不鼓励人们无限度地追求极致。作家大可花上数年打磨一本传世之作,但程序员在代码上钻牛角尖就很有问题。

你好,你想了解什么内容呢?

微服务架构是近年来备受关注的热门话题,但在讨论它时,很多人往往只关注技术本身,而忽略了微服务架构与人之间的关系。关键在于将大单体拆分为独立的微服务后,不同模块间的边界变得更加清晰,这使得许多小组织各自维护独立的微服务,相较于与数百人的团队共同维护一个大单体,拥有更高的运作效率。然而,如果忽视了特定的组织规模(也就是“人”)作为前提,那么空谈微服务的各种技术优势和那些花活,就纯属本末倒置。

作为程序员,我们天然被那些瑰丽的架构图和独具匠心的代码细节所吸引。但是,千万不要对软件开发中的另一个重要因素——“人”视而不见。有时候,我们需要转换一下看事情的角度(从“技术”转向“人”),这样对我们大有裨益。

六、求知若渴是好事,但也要注意方法。如今,人人都在说“终身学习”,而程序员是一个尤其需要终身学习的职业。因为计算机技术的迭代更新非常快,某个三年前流行的框架或编程语言很可能一个月前已经过时。在如此快速变化的环境中,程序员们需要学习的东西非常多,涵盖各个层面。以我比较熟悉的后端领域为例,一位合格的后端工程师至少需要掌握以下这些:一种或多种后端编程语言、MySQL等关系数据库、Redis等常见存储组件、设计模式、用户体验、软件工程、编译原理、操作系统、网络基础、分布式系统等。

虽然要学很多,但据我观察,大部分程序员其实都挺爱学习(至少不排斥),因此心态不是问题。然而,有时候光有“求知若渴”的心态并不够,学习时我们尤其需要关注“性价比”。为了提高学习效果,我们可以关注学习性价比。下面这张图展示了学习成效和投入之间的关系:

学习成效与投入关系图

横轴为学习投入,纵轴为学习成效

从图中可以看到,在学习的初级阶段,投入较少时,所获得的成效增长飞快。但当成效超过某个阈值后,之后再想继续提升,所需要的学习投入就会呈指数级增长。因此,在学习任何一项新事物时,我建议你先在脑海中想清楚一个问题:“我应该在上图中的哪个位置停下来?”,而不是闷头猛学。这样可以确保我们在有限的时间和精力内,取得最大化的学习效果。

在知识的海洋中,有些东西需要我们持续学习、不断精进,而有些东西,蜻蜓点水般学到一些皮毛就足够了。因此,准确判断并分配自己有限的学习精力显得尤为重要,甚至比努力学习本身更为关键。

当我们确定了学习目标后,接下来便是寻找合适的学习资料。我曾有一次失败的经历,当时我对产品交互设计产生了浓厚兴趣,认为自己应该在这方面有所精进。于是,我挑选了一本领域内非常经典的专业书《About Face 4: 交互设计精髓》[6],并满怀信心地认为自己的交互设计能力可以迅速提升。然而,当我尝试阅读这本书时,发现自己连第一章都无法顺利读完——正如那句老话所说:“隔行如隔山”。

从这次失败中,我总结出了一点经验:学习某项新技能时,我们最好选择那些更易读、更适合“门外汉”的学习资料,而不是盲目追求最经典、最权威的资料。在我看来,以下几本书非常适合门外汉学习使用,性价比极高:《写给大家看的设计书》[7](设计相关)、《点石成金》[8](Web 用户体验相关)以及《鸟哥的 Linux 私房菜》[9](Linux 系统相关)。

或许每个人都希望成为一个博学的人,无所不知,无所不晓。然而,我们所能分配的时间和精力总是有限的,我们不需要也不可能在所有领域都成为专家。因此,在学习过程中,我们需要明确自己的重点和方向。

此外,尽早开始写单元测试对于程序员来说是非常有益的。我一直非常喜欢编写单元测试,认为它对我的编程生涯产生了极大的影响。如果以“开始写单元测试”作为分界线,我的职业生涯似乎分为了两段,后面那段远远比前面那段精彩得多。编写单元测试的好处有很多,比如它可以驱动你改进代码设计、作为代码的一种文档等。完善的单元测试还是构建高效犯错环境的关键。我已经撰写过多篇关于单元测试的文章,如《有关单元测试的 5 个建议》[10]、《游戏“蔚蓝山”教我的编程道理》[11]。因此,在这里我不打算重复这些内容。总之,如果你从未尝试过编写单元测试或未重视测试,我建议你从明天开始尝试。

通常情况下,我不会对我的代码进行测试。但如果需要进行测试的话,我会在生产环境中进行。

最后,我们来谈谈程序员最大的敌人是什么?在很多程序员的故事里,产品经理经常扮演反派角色。他们口中的项目需求总是变来变去,每天都有新的想法出现,让程序员疲于应对。实际上,程序员最大的敌人并不是产品经理或其他人,而是自己的心态和行为模式。只有克服这些障碍,才能更好地发挥自己的潜力,成为一名优秀的程序员。

在我们的日常生活中,客户的需求可能会经常发生变化。为了应对这种情况,我们决定在下次发布前将这些需求“冻结”。然而,在这个过程中,产品经理常常被戏谑为程序员们的敌人,因为他们不断地修改需求。尽管如此,我认为产品经理并非真正的敌人。事实上,软件开发本身就是为了适应不断变化的需求而设计的,因此开发软件和修建房子有很大的不同。如果没有需求的变化,那么软件就失去了它的价值。

那么,程序员们最大的敌人究竟是什么呢?首先,复杂度是最大的敌人。失控的复杂度会导致项目变得难以管理和维护。为了降低复杂度,我们需要关注以下几个方面:

1. 控制新功能的增加:过多的新功能会增加代码量,从而导致更高的复杂度。

2. 优化对高可用性的需求:通过合理的技术选型和模块划分,降低对高可用性的需求。

3. 提高性能:通过缓存、性能优化等手段,提高代码的执行效率。

4. 及时进行重构:避免因项目排期紧张而导致的重构推迟,及时解决技术债务问题。

5. 重视自动化测试:编写单元测试和关注测试过程,有助于提高代码质量和降低复杂度。

当项目的复杂度增长到一定程度时,可能会出现一个“大坑”,使得团队难以对其进行修改和优化。因此,降低复杂度对于项目的成功至关重要。为了减缓复杂度的增长,我们需要关注以下几点:

1. 精通当前编程语言和工具,编写高质量的代码。

2. 使用合适的设计模式和编程模式,提高代码的可读性和可维护性。

3. 对重复代码零容忍,抽象出通用的库和框架。

4. 适当运用整洁架构、领域驱动设计等思想,提高代码的整体质量。

5. 编写详尽的文档和注释,便于其他团队成员理解和使用代码。

6. 编写规范有效的单元测试,确保代码的稳定性和可靠性。

7. 将变动的部分与不变的部分进行分离,降低整体的复杂度。

总之,要成为优秀的程序员,关键在于编写更好的代码。只有这样,我们才能在面对不断变化的需求时,保持高效的工作状态,为项目的成功贡献自己的力量。

020年,我在小组内做了一次分享,当时我的PPT标题是《编程十年后的十个感触》。在将这份资料分享给公司内部网络后,一位同事看到了PPT,他觉得仅仅看PPT还不够过瘾,希望我能将这些内容扩展成一篇文章。我回复他说没问题。如今已经过去了3年,我终于实现了当初的承诺。

当年准备分享材料时,我在制作完整个PPT后,最后一页却不知道该放些什么内容。于是灵机一动,我设计了一个纯白色的背景,中间用大黑字写道:“十年很短,编程很难”。如今,第二个十年也已经过去一半多,而这句话的前半部分似乎对我依然适用——时间过得很快,我在编程方面还有很长的路要走,所以我会继续努力。

如果你对这篇文章感兴趣,欢迎关注作者的微信公众号「piglei」,以获取更多关于编程和个人成长的内容。