10 月 15 日,七牛云主办的「Go+ Together!Go+ 1.0 发布会暨 Go+ 开发者基金会启动仪式」在上海隆重召开。
本次大会中,CCF 杰出工程师奖获得者林昊以《开发者如何提升写代码的硬实力》为题,结合自身的成长发展路径,分享了开发者提升个人代码硬实力的方法与经验。
林昊认为深刻掌握语言 API 是开发者的基本功,一名进阶的开发者则需要能够写出鲁棒性好的代码。高阶开发者,则需要合理选择语言,深刻理解语言运行机制。
以下为林昊主题演讲的内容整理:
大家好,我是林昊。
作为一个工程师,我特别佩服许式伟开发 Go+ 语言。所有的工程师应该都能感受到做一门新的语言有多么的难,尤其是一家中国公司做一门新的语言,更是无比的困难。因此我非常佩服许式伟,也非常感谢邀请我来发布会上做分享。
我回顾了一下自己作为一个工程师这么多年,整个写代码的能力是怎么样逐步提升起来的,还挺有意思的。
主要有基本功、进阶、高阶三个阶段,这三个阶段会相对比较明显,是我自己的一个经历。最后我想讲一点选择语言的问题,因为一帮程序员凑在一起,打开聊天的最简单方式是讨论哪个语言最好,基本就可以聊一个通宵了。
基本功:深刻掌握语言 API
- 为什么要掌握语言 API
第一点,对于所有程序员来讲,学习任何语言,最重要的是基本功是深刻掌握语言的 API。
这句话说起来非常容易,但回顾自己写代码的历程,我觉得还挺难的。比如说怎么样充分发挥语言的能力,因为每个语言在不同场景的能力是不一样的。每门语言都有自己的 API,像编写通信程序的程序员,语言可能会提供各种 I/O 的能力,比如 Blocking I/O、Non-Blocking I/O、Asynchronbous I/O等;写高并发程序的程序员,会有很多高并发的包。
很多人觉得我看一下教程就学会了,但实际不是这样的。我最早学的是 Java,没有写高并发程序以及通信程序之前,我觉得这个东西太简单了,我也懂怎么用 Java 写一个 I/O 程序、网络通信程序,或者并发量很大的程序,照着书本,照着 Demo写一遍,然后上线,应该不会有任何问题。后来在阿里,我开始接触并发量上亿或者更高的并发量。我在阿里上线的第二个程序,大概一天的访问量大概过亿,上去之后系统就崩了。只有到这个阶段你才会明白,学的所有 API,里面非常重要的一点是,你有没有理解 每一个 API 背后的不同到底是什么。肯定要掌握这个语言有哪些 API 的构成。这里最重要的就是实战,真正去动手,而不是光看一篇文章和一本书,不管那本书写得多好,应该都没有太大的作用。
第二,掌握语言 API ,对于在写代码的过程中避免造成一些非常常见的故障,是非常有帮助的。对 API 大家都会有自己的默认理解,你以为它是这么做的,其实它可能不是这么做的。
最常见的故障是对资源超出预期的消化。我们写代码,背后肯定会消耗一些资源,关键是消耗的资源到底有多少,有些时候可能是不知道的。比如说在数据结构里面,很多的数据结构都是自增长的,创建了一个 list,随着塞入 list 的元素越来越多,可能会不断地增长,一开始你会觉得没什么,有时候不知道什么原因就消耗了非常多的资源。故障都是这样,你不知道它为什么。
这种故障我们看得太多了,我们甚至给 JDK 官方说以后的数据语言能不能限制大小,后来发现原来就是一小部分公司的诉求,大部分公司是没有这样的诉求,但这种故障在我们这儿出现过不知道多少次。如果不是非常清楚 API 的话,在一个大型系统里面出故障几乎就是必然的事情。
- 如何掌握语言 API
学好一个语言的 API ,最重要是翻阅一下 API 的实现代码,以及场景实战。
刚刚其实也讲到,不光去翻阅一段代码,不要仅仅是去看一个文档,现在多数的语言其实都是开源的,可以看到这个语言是怎么实现的,用的 API 最好能够翻看一下,这个 API 从代码上是怎么做的,会帮助你理解 API 的实现。
第二,场景实战。我自己以前学 Java,什么包都学过,真正用的时候发现什么都不懂,就感觉没有看过文档和书一样。等到要写的时候,我又把书拿出来再读一遍是学得比较快的。但不是所有的人都有那个机会去触碰非常复杂的 I/O 或者高并发程序,不是所有人都有实战的机会,不是完全没有办法。
以写通讯程序为例。有很多非常顶级的、开源的通讯程序框架,比如 Java 的通讯框架有 Netty,Go 有 GRPC 等等。它们都写得非常好。最好的学习方法是,基于语言最原生的 API 自己写一个,不看别人的代码自己写一个出来,再对比一下你写的跟开源的、顶级框架的代码有什么区别。我想应该是有些区别的,你会发现自己哪个地方理解得不太到位。
进阶:写鲁棒性好的代码
第二个阶段是进阶,对程序员来讲就是一个比较高的挑战,怎么写鲁棒性非常好的代码。
为什么很多职业程序员会认为用 Scratch 只是在写一个玩具而已,因为那样的程序放在生产级里面,鲁棒性非常糟糕,很难适应多种环境,很容易出问题。我们写一个玩具型的系统非常简单,人人都可以是程序员,写生产级的系统是需要职业程序员来写的。
- 被误用导致故障
所谓鲁棒性好,我举几个例子。写代码的时候,最常见、最典型的例子是被误用,我们以前出过很多次、非常常见的故障。
我相信大家写代码的时候,经常会写个代码做批量处理,传一个列表或数组出来,做一些批量处理,这是非常常见的 API。很多工程师会在 API 上写一个文档,写明建议不要传超过一千个数组的元素进来,但其实代码里面没做任何的边界处理,没有控制到底传多少。以前我们出过一次非常严重的故障,就是“误用”造成的,当时我们大概处理了一个多小时。现代的软件工程其实是由众多程序员共同完成的,一家公司可能有上万个程序员甚至更多,没有办法保障每个人写出来的代码质量都非常高。
在我们内部,这种情况在故障定责上会定在提供 API 的这一方。你提供了一个 API ,只在文档上写了不要传太多的数据进来。出现故障后,不能说别人误用了一下你的 API 这个系统就挂了,原因是他没有仔细看文档。这个理由我们是不会认的。
确定边界是鲁棒性好的代码的基本特征。大家去看一个职业程序员写的代码,如果是正常程序的代码是非常容易写的,但是好的代码大量都在处理边界和异常情况。看代码的时候,可能会觉得那些代码都没什么用,删掉也没关系,但在很多情况下会起到最关键的作用。
误用这种故障大家会觉得不常见,其实有一个最经典的误用 API 的案例:1999 年美国发射的火星气候探测者号,降落的时候,在空中爆炸了。原因是这个程序是由两个团队写的,一个是美国的团队,另外一个是别的国家的团队。两个团队在 API 上在传入的参数的单位理解不同,一边认为是牛顿,另外一边认为是另外一个单位,两边都没有核对过。双方都没有去看调 API 的文档,双方看到 API 的参数名就开始猜,应该是这个意思。后来在火星气候探测者号降落的时候,就按照这个参数去调位置,导致降落太快,直接就爆炸了,大概损失了几个亿美金。像卫星这种最大的问题是发射窗口,不仅损失了钱,还损失了大量的时间。
这种故障很典型,大家都觉得不会犯,但我 review 过很多代码,觉得这是非常常见的。
- 运行环境等改变造成故障
我们在内部最强的一个感受:如果这个代码只运行一天难度是不大的,随便写写就好了;如果要在生产环境连续运行一年,并且从来不重启,很多代码是会挂的。
以前我们内部开玩笑,比如说做活动、大促前最好把所有的系统都重启一遍,这是最保险的,重启是最大的绝招。如果不重启,可能真的会出点问题。很多程序,比如以前我们有人做 cache,觉得这家公司的业务量不可能增长那么大,所有东西都可以放在缓存里面,没想到公司成长很快,很多东西塞不到 cache 里面,直到有一天系统全部崩掉。这种东西临时改是来不及了,这种故障时间通常都会非常长。
- 鲁棒性是业余、职业、优秀程序员的最大分水岭
我有很长时间没有写代码,但仍然 review 过很多代码,我自己还是认为代码的鲁棒性写得如何,几乎就是业余程序员、职业程序员、优秀程序员最大的分水岭。
更优秀的程序员能够关注到更多的防范。比如说用 Java 写通讯框架,Netty 是垄断者,几乎所有人都是基于 Netty 去写通讯程序。如果大家稍微翻 Netty 的代码,会发现里面处理了非常多种可能想都想不出来的异常,包括 Java 语言在某个情况下会出现一个问题, Netty 在那里写了一段代码,就是为了屏蔽掉语音上的 bug。我觉得这个挑战还是非常大的。
我觉得要做好鲁棒性,最常见、最简单的有以下几点。
第一,做边界处理。我觉得这是最简单的,但很多人觉得没有必要做,很多人的代码肯定是不做边界处理,认为某些现象不可能发生。问题通常就是你认为绝对不会发生的地方就发生了,有很多种原因造成,不用纠结这个原因是什么。对不符合设计预期的,都可以抛异常。
第二,对响应时间要求非常高的程序,对批量处理的程序,通常来讲写代码时候的处理逻辑、方式会很不一样。对于响应时间非常高的程序而言,宁可去抛错误,也不要去做过多的脱慢,或者把资源耗尽。以前看过很多的并发程序,有一堆任务要扔进来,要去做并发的处理,有些人觉得如果现存存码满了,最好扔进一个队列里面等一下,不要处理不了,但是很有可能导致很多请求堆在队列里面,导致响应时间拖长之后,把整个线程全部耗尽。这个是非常常见。我觉得在写这种程序的时候,要稍微看场景决定。
第三,深度理解所使用的 API ,甚至语言的运行机制,确保控制异常。我们内部以前在所有核心系统,包括运维的同学,其实运维的同学是很吓人的,因为运维以前很多的操作都是在生产环境直接敲命令行。不是所有的人都知道敲的那行命令会发生什么,敲下去了后,整个系统就全挂了。用 API 也一样,如果不是非常清楚 API 是怎么实现的,直接去用,出故障其实很正常。语言也一样,如果你不知道语言是怎么运转的,很多时候会误用或者造成一些问题。
第四,真正要把代码的鲁棒性写好,更好的训练方式是多参与。只要是人写的代码出点问题都很正常,每家公司都会碰到很多的问题、故障,大家可以多参与这个过程。我自己觉得以前在异常部分代码能力提升关键之处,以前阿里内部有一个民间组织叫“消防队”,专门负责灭火。以前最早进入消防队的人处理了太多故障,看过太多代码导致的问题,等到他们自己写代码的时候就会稍微注意一点,这是一个很好的过程。当然现在应该很多公司都有官方团队处理故障。只有看多了以后,才有可能把代码的鲁棒性写好,真正在一开始什么都没有看到过的情况下要写好鲁棒性,难度是非常大的。
我认为很容易通过看代码看出一个程序员的水平。现在很多程序员的水平被 PPT 误导,PPT 里面画非常好看的架构图,架构图里一堆的框,最后拼的是谁画的框好看。但是我们说一个真正好的程序员,只要看几段代码是能看出他的水平,这个是非常明显的。
高阶:深刻理解语言运行机制
更高阶的程序员,一定会理解自己所用的那门语言背后是怎么运行的。每一门语言背后的实现过程都有很大的差别。
以 Java 举例,对 Java 程序员来讲,非常清楚 JMF 怎么运行一段代码,对Java来说启动的时候有解释运行、C1编译、C2 编译等。很多 Java 程序,刚启动的时候整个系统的响应时间是比较慢的,后面用着用着响应速度就比较快了。很大的原因就是因为 Java 在运行的过程中不断收集信息做 C2 高级编译,才能让整个程序运转得越快。在活动类型的时候,比如说做一个促销活动,觉得活动之前重启一下比较安全,这个时候量没有打过来,系统运行是比较慢的。像阿里前几年双 11 一个非常重要的创新是让代码全部热起来,在活动开始前先跑很多很多遍。
第二,内存管理机制。现在几乎所有高级语言都是自动内存管理,你必须要知道自动内存管理的机制是什么。所有的自动内存管理很难做到不暂停你的应用,对你的业务肯定会有影响。
第三,线程和 OS 线程对应关系。
下一个话题是关于如何学习语言的运行机制。我自己觉得,怎么去学好每一门语言背后整体运行的机制,对高阶的程序员来讲非常重要。
第一,读一两本语言基础原理的书。直接看语言的代码估计很难看懂,语言基本的原理应该都差不多,不管是什么语言,基本要做的几件事都是一样的。
第二,翻看语言背后实现的源码。这个就需要找不同感兴趣的地方去看,比如对内存管理很感兴趣,可以翻看一下内存管理相应的代码是什么。掌握一定的技巧是非常重要的,以前我们很多人学 JMF 的时候,打开庞大的代码库,你不知道从哪看,如果没有入口的话,你没有办法顺藤摸瓜。
第三,多做代码的实现。即使是语言级的源码,很多人觉得是大牛写的代码,有些时候也会看到里面有一堆很搞笑的代码,会发现以前无法解释的神奇的事情,就是这个原因造成的。
合理选择语言
最后要讲的一点,怎么样去合理选择一门语言。这个跟代码实力没关系,但是对很多做技术选型的人非常重要。
我认为,脱离公司背景、脱离业务背景去讨论语言的好坏,其实没有什么好争论的,因为没有任何意义,每门语言在任何场景下会有相对的优劣性。
淘宝最早是用 PHP 写的,为什么从 PHP 换成了 Java,不是因为 Java 比 PHP 好。最最大的原因是阿里除了淘宝以外有另外一家公司做 B2B,当时 B2B 的主体工程师是 Java 工程师。淘宝那个时候业务发展非常快,需要很多的人一起写业务。如果没有办法用 B2B 的工程师,纯粹招聘新人的话,会导致开发效率不够高。最简单的方法是让整个公司的程序员都能够被使用起来。让一个程序员换一门语言,难度还是非常大的。大量使用来自 B2B 公司的 Java 工程师,这才是淘宝从 PHP 换到 Java 的原因。
Twitter 最早用 Scala,学习门槛比较高,想换成 Java,最后也没有换成。Facebook 最初用 PHP,后来也想换 Java,也是被程序员吐槽的。
我觉得不用做纯语言的争论。举个例子,C 通常用来做基础型软件,当你写一个非常重要的存储软件和内存软件的时候,最好能自己控制内存的管理,而不是交给语言去控制,这样可以更加的精准。所以 C 很少用来做业务型的场景,更多是基础性软件。C 对很多程序员来讲要求还是更高一些。我们开玩笑说,C 语言写出来的代码通常只会看到两种状况,一种是特别牛的,一种就是特别差的。
Java 在运行时的优化,还有自动内存管理等等,对于写业务来讲,相对是比较友好的。Java 的程序一般来讲不会写得太烂,多数人可以写到六七十分,但想写到 90分的高分,可能难度就非常大了,因为需要非常清楚语言怎么运转。比如,写一个对内存管理友好的 Java 程序,对程序员的要求就比较高了。
今天发布会所讲的 Go+,也是有清晰场景定位的语言。
对所有程序员来讲,最重要的生产工具,第一个是语言本身,第二个是 IDE。语言本身会直接决定某些场景的效率,我觉得语言是一个很重要的工具。
这差不多是我想讲的内容,谢谢大家!