来自编程“老者”们的须时刻谨记的七大教训金典
在HBO电视剧集《硅谷》第一季第六集中,一家初创企业的创始人Richard陷入困境,并向一位看起来只有十三、四岁的男孩求助。
这位少年天才瞄了Richard一眼并说道:“我本以为你会更年轻些。你今年有多大了,25岁?”
“26岁,”Richard如实回答。
“我的妈呀。”
没错,软件行业向来崇尚年轻化。如果大家已经拥有了自己的家庭,那么在编程领域已经算是个老年人了。而如果大家已经年过廿五甚至已过而立,那么各位未来的技术之路可能只会一路走低。
唉,自以为是的家伙并不总能完美地解决技术难题。尽管他们的大脑当中塞满了关于最新、最流行的各类架构、框架以及堆栈的技术细节,但他们并不具备对 软件在本质层面为何能够确切起效或者发生故障的实际认知。这些经验只会在我们连续数周遭遇怪异甚至莫名其妙的错误之后慢慢积累并建立起来。
正如《硅谷》的观众们心满意足地看着第一季第六集结尾那位少年天才最终搞砸了一切,我们这些编程界的“老者”们也同样乐于欣赏那群视我们为“过时之人”、但却由于不听前辈劝告而陷入困难的后起之秀们的沮丧表情。
本着共享精神的考量、或者也算是给年轻人的一点教训,在这里我们总结出了一些远不是凭借聪明的头脑加上几周的实践就能掌握的宝贵经验。顺带一提,其中很多财富只有那些需要靠两位十六进制数字才能写出自己年纪的老者们方可掌握。
内存很重要
就在不久之前,我们计算机设备上的内存容量还在以MB为计量单位而非GB。当我组装起自己的第一台计算机(一台Sol-20)时,其内存甚至只有可 怜的KB级别。这台设备的主板上接有约64块内存芯片,每一块大约配备18个插针。我记不清楚具体数目了,但我可以肯定每个接点都是由自己亲手焊接完成。 当焊接任务结束后,我还得重新处理那些无法通过内存测试的插针。
如果大家像我一样经历过那段内存是金的往昔岁月,就会意识到这一点点资源有多么宝贵。如今的年轻人们则不会那么严谨,而更倾向于“差不多得了”的态 度打理内存资源。他们会把指针摇来晃去,从不清理自己的数据结构,这一切都是因为内存成本如今已经非常低廉。他们只需要点击一个按钮,就能为自己的云实例 添加16GB内存容量。如果每个人都能如此轻松地从Amazon手中租到配备244GB巨量内存的实例,鬼才会在编程当中认真考虑内存的分配问题。
然而垃圾收集机制的工作效果总会有局限,正如家长不可能无限度地为小朋友们打扫房间。大家可以分配规模庞大的堆,但最终我们仍然需要对内存加以清 理。如果各位习惯了任意挥霍资源并在内存当中如流感一般来回穿梭,垃圾收集机制很可能出现体积膨胀的状况——并最终塞满看似充裕的224GB空间。
除此之外,虚拟内存的兴盛同样带来值得重视的隐患。如果我们的计算机由于内存不足而转向利用磁盘进行数据交换,那么软件的运行速度将发生成百倍甚至 上千倍的速度递减。虚拟内存从理论层面讲确实大有可为,但在实际效果角度看却太过缓慢。程序员们需要清醒地意识到,内存资源在物质极大丰富的今天仍然非常 珍贵。如果缺乏这种科学的观念,那么原本在开发阶段运行速度理想的软件很可能在投付实践之后遭遇速度下滑。换言之,大家的工作成果根本无法实现规模化拓 展。近年以来,可扩展能力已经成为一切技术方案的必要前提。因此请大家注意,在软件或者服务遭遇瓶颈之前打理好内存资源。
计算机网络速度缓慢
负责市场营销的员工们一直将云服务包装成类似于计算业务领域的万灵药,在这里数据总能够顺畅无阻地往来迁移。如果大家希望在云端保存自己的数据,他 们还准备好了能够提供永久存储、备份以及其它各类功能的简单Web服务产品——总而言之一句话,事情交给服务供应商、您就放心吧。
在万事拜托这方面、营销人员的宣传内容的确属实,但还有一点他们没提——客户需要等,长久地、不懈地等。进入与传出计算机的全部流量都需要耗费时间。相较于CPU与本地磁盘驱动器之间的传输速度,计算机网络一直扮演着缓慢小乌龟的角色。
编程界的前辈们可谓“生在新中国,长在红旗下”,在那艰苦的岁月里互联网还根本连雏形都没有。FidoNet会以对话方式将我们的数据路由至与可能 接近目的地的其它计算机处。要想跨越国境线,大家的数据可能走得比人还慢——花费几天时间穿越无数吱吱作响的调制解调器。这种痛苦的经历告诉他们,正确的 处理方式应该是尽可能多地以本地方式处理计算任务,并最大程度保证远方的Web服务只需要处理规模较小的最终结果。今天的程序员们很可能无法体会这些由老 一辈无产阶级开发者们从实践中辛苦积累而来的教训,事实上云存储给出的承诺并不可靠,而且直到最后几毫秒内才可以放心将任务交给云服务。
编译器中存在漏洞
当我们遭遇故障之时,真正导致问题发生的往往并不是我们编写出的代码本身。我们也许忘记了对某些项目进行初始化,或者没能及时检查某个null指针。无论实际原因是什么,每一位程序员都明白当软件出现故障时,责任必须由我们自己承诺——句号。
事实上,最令人头痛的并不是我们自己的编程失误。有时候责任源自编译器或者解释器。尽管目前的编译器与解释器在稳定性方面相对可靠,但其距离完美仍 有很长一段道路要走。必须承认,无数技术人员耗费大量心血才让今天的编译器与解释器拥有当下的稳定性水平,但将这种稳定性认定为理所当然仍然不够明智。
需要提醒大家的是,编译器与解释器同样有可能发生故障,我们也应当在遭遇问题时将针对二者的调试工作考虑入其中。如果大家并不清楚编译器为什么出现 问题,那么追寻答案的过程很可能耗时数天甚至是数周。早在很久之前,程序员们前辈们就意识到有时候最理想的问题调试途径并不是测试自己的代码成果,而是将 注意力集中在工具身上。如果大家习惯性地认为编译器本身不会出问题,而且不假思索地把责任归咎于代码的渲染计算过程,那么往往耗费数天甚至数月也无法从工 作中找到根本不存在的问题根源。年轻人啊,相信你们很快就能在实践中成长起来。
速度对于用户而言极为重要
很久以前,我曾经听说IBM公司就可用性议题开展过一次调研,并发现人们的意识会在响应时间超过100毫秒之后出现波动。这一结论到底是真是假?我曾经就此求证于搜索引擎,但互联网挂掉了……而且我之后也忘记了再试一次。
经历了IBM大型机上那古董级绿屏应用时代的朋友们肯定知道,IBM公司将100毫秒这一导致人脑意识涣散的时间分水岭设为响应速度阈值。有鉴于 此,他们在I/O线路方面投入了大量精力。在销售其大型机产品时,蓝色巨人的工作人员们会以详尽的规格列表指明设备当中的I/O通道数量,这种作法与汽车 制造商介绍自家发动机参数时如出一辙。诚然,这些设备也会如现代产品一样发生崩溃,但当它们处于正常运转状态时,这些数据总能通过预设通道顺畅地流向终端 用户。
我曾亲眼目睹过不少一位编程人员绞尽脑汁地调整其由于大量JavaScript库以及数量总量流向浏览器而导致崩溃的AJAX重量级项目。他们往往 抱怨称,将其陷入泥潭的缓慢创新成果与作为替代对象的陈旧绿屏终端相比较并不公平。但企业中的其它部门却应该为此而庆幸。毕竟如今我们迎来了更美观的图形 显示效果并在应用程序中包含更多色彩表现。毫无疑问,CSS让一切变得更酷、更漂亮,令用户不满的仅仅是其缓慢的响应速度。
真正的Web永远不可能像办公网络那样迅捷
现代网站往往像是一只用时间垒砌而成的小猪。其往往需要数秒钟时间将MB级别的数据从JavaScript库当中交付给浏览器。接下来,浏览器需要 将这些多层MB数据推向JIT编译器。如果我们能够将世界范围内全部jQuery重新编译带来的时耗加以累积,其总长很可能达到数万甚至上百万年。
乐于使用基于浏览器的各类工具的程序员们往往会犯下一类常见错误——以无处不在的方式肆意滥用AJAX。这一切在办公环境的演示过程中都能顺利完 成,毕竟在这类条件下服务器本身就位于桌子后面的柜子上。有时候“服务器”也会运行在本地主机当中。当然,文件能够在弹指一挥间到达指定位置,运行的整个 过程都非常顺畅、甚至老板在屋角进行测试时也能应对自如。
不过当用户身处DSL连接环境下或者需要通过一座已然过载的信号塔以蜂窝网络进行路由时,结果又会如何?他们需要耗费大量时间等等库内信息的交付。如果无法在数毫秒当中顺利抵达,他们往往会愤而在TMZ上发表文章大发牢骚。
算法的复杂程度至关重要
在某个项目当中,我遇到了与《硅谷》居住中Richard面临的同一个难题,而我也与他一样、把求助的目光投向了一位尚未到饮酒年龄但已经对 Greasemonkey的前世今生极为熟悉的朋友身上。他对我的代码进行了重写,并把结果发还给我。在阅读了各项变更之后,我意识到他仅仅是将代码内容 变得更加精致,但同时也把算法的复杂程度由0(n)提升到0(n^2)。他坚持将数据放在列表当中以实现匹配,这么做看起来确实很漂亮、但却会随着n值的 提升而导致代码运行速度越来越慢。
算法复杂性议题在高校的计算机科学课程当中已经得到了详尽的解读。然而,很多出身科班、凭借着聪明才智在周末自学掌握Ruby或者 CoffeeScript的年轻人们根本没能深入领会其精神。复杂性分析可能看似一种极为深奥的理论性事务,但其会随着项目规模的变化而展现出完全不同的 面貌。当n值较小时,一切都能够轻松实现。特别是在内存容量充裕时,代码将以令人满意的速度得以执行,这时即使糟糕的算法也能够在测试中得以通过。然而当 用户倍增再倍增时,那些包含有0(n^2)甚至0(n^3)的算法会让使用者陷入无尽等待的噩梦。
当我询问这位天才少年,能否把匹配流程转化为一条二次算法时,他挠了挠头。他根本不知道我在说些什么。后来,我利用一套散列表取代了他的清单,一切也就此回归顺畅。不过时至今日,我想他一定已经老到到能够理解这个话题的程度了。
库有可能糟糕透顶
那些编写库的技术人员并不总是关注我们这些普通使用者的利益与诉求。他们确实在努力帮忙,但其关注重点往往集中在为整个世界作出贡献身上——而非我 们日常面临的小小难题。他们最终打造出的往往是一把能够解决多种不同版本问题的瑞士军刀,而非针对当前问题作出深度优化的解决方案。库项目的工程技术与编 码水平毋庸置疑,但运行速度却可能不堪恭维。
如果大家不在这方面多加注意,那么库本身很可能把我们的代码成果拖进速度缓慢的泥潭,而各位对此甚至一无所觉。我曾经请一位年轻的程序员帮我调整代码成果,因为我写了十行代码来从字符串中提取字符。
“我可以用一条正则表达式与一行代码完成同样的任务,”他自信地表示。“从十行到一行,这就是看得见、摸得着的改进。”但他并没有意识到,他这一行 代码在每一次进行正则表达式调用时,都需要经历解析与重新解析的过程。他单纯认为自己编写的是一行代码,而我这是十行代码,因此他比我水平高到不知道哪里 去了。
库与API在适当运用的前提下能够发挥巨大的作用。但如果以内部循环的方式加以使用,那么他们完全可能对速度产生破坏性的影响——而当事人往往还完全摸不着头脑。
原文标题:7 timeless lessons of programming ‘graybeards’
核子可乐译
来自:http://developer.51cto.com/art/201503/468420_all.htm