软件随想录(More Joel on Software)
程序员部落酋长Joel谈软件

https://book.douban.com/subject/4163938/

这是很重要的一点,比尔盖茨对技术的了解令人惊叹,他理解可变数据类型,COM对象,IDispatch接口以及Automation与虚表有何不同。他明白这种不同可能会导致双重接口(dual interface)。因此,他担心日期函数并非心血来潮。如果他信任那个干事的人,他就不会干涉软件。但是,你不要糊弄他,哪怕是一分钟,因为他也是一个程序员,一个真正的现实的程序员。不懂编程的人管理软件公司,就好像不懂冲浪的人硬要去冲浪。

不管怎么说,这些都是陈规陋习。所以为了留住最好的程序员,投资银行有两个策略:一个是给程序员发一吨的钞票,另一个是给予程序员完全的自由,允许他们使用自己想学的任何最新热门编程语言,不断的一遍又一遍重写每件东西。想把整个交易程序用Lisp语言重写?随你的便。帮我再拿一个该死的汉堡包过来。一些程序员固执与他们正在使用的编程语言,但是,大多数程序员很高兴有机会使用令人激动的新技术,现在的热门大概是Python语言或者是Ruby on Rails,三年前是C#,再以前是Java。在这里我不是让你不要用最好的工具完成工作,我也不是让您每两年就用热门语言重写一遍程序,我只是在说,如果你能找到办法让程序员有接触新的语言框架和技术的经历,那么他们会感到更开心一些。即使也不敢为了学习的目的,用一门新语言重写核心程序,那有没有可能重启你们使用的内部工具,或者其他不关键的应用程序呢?


我再告诉你一件事。1900年的时候,拉丁语和希腊语都是大学里的必修课,原因不是因为它们有什么特别的作用,而是因为他们有点被看成是受过高等教育的人士的标志。在某种程度上我的观点同拉丁语支持者的观点没有不同:“(拉丁语)训练你的思维,锻炼你的记忆,分析拉丁语的语法结构是思考能力的最佳练习,是真正对智力的挑战,能够很好的培养逻辑能力。”以上是出自Scott Barker之口。今天我找不到一所大学,还把拉丁语作为必修课。指针和递归不正向计算机科学中的拉丁语和希腊语吗?

除了上面那些直接就能想到的重要性,指针和递归的真正价值在于,那种你在学习它们的过程中所得到的思想深度,以及你因为害怕在这些课程中被淘汰所产生的心理抗压能力,它们都是在建造大型系统中的过程中必不可少的。指针和递归要求一定水平的推理能力,抽象思考能力,以及最重要的,在若干个不同的抽象层次上,同时审视同一个问题的能力。因此,是否真正理解指针和递归与是否是一个优秀程序员直接相关。

OOP不构成对智力的太大挑战,吓不跑一年级新生。据说如果你没学好OOP,你的程序依然可以运行,只是维护起来有点困难。但是如果你没学好指针,你的程序就会输出一行段错误信息,而且你对什么地方出错了毫不知情,然后你只好停下来,深吸一口气,真正开始努力在两个不同的抽象层次上同时思考你的程序是如何运行的。


有一件事我是非常确定的,那就是管理层获得的技术问题的信息是所有人中最少的。经理绝不应该试图对一些问题做决定。当我还在微软公司的时候,应用软件部门的负责人是Mike Maples,有些程序员在技术问题上争执不下时就去找他做评判。他会玩一会儿杂耍,两只手同时抛接三个球,讲一个笑话,再告诉他的人立即离开他的办公室,自己去解决他们那些该死的问题,不要来找他,因为实事求是里说,他是最没有资格做技术决策的人。我认为,管理非常聪明,高度适任的人就是这样,没有其他办法。但是,Juno公司的经理层把自己看得像美国总统一样,一个个都是决策家。如果你企图决定一切,那么需要做决定的事情实在太多了。

尽管CS115课程不是为计算机系学生开设的,也无助于拿到学位,但是这门课在我毕业以后却成了对我最有用的课程,因为他要求学生写大量关于技术内容的文章,我因此都让他训练。能不能清晰地写出技术内容的文章决定了,你是一个口齿不清的程序员还是一个领袖。我注意到,微软公司中那些真正优秀的程序经理都是具有优秀写作能力的人。微软公司的副总裁Steve Sinofsky曾经写过一份电子邮件《康奈尔大学已经开始使用互联网》(Cornell is Wired)。就只靠这封雄辩的电子邮件,微软公司的战略发生了180度大转弯。那些决定游戏规则的人都是善于写作的人。为什么C语言是最流行的语言,原因就是创始人Brian Kernighan and Dennis Ritchie写了一本伟大的书《C程序设计语言》。

下面就是叫针对计算机专业学生的7条免费建议:

  1. 毕业前练习好写作
  2. 毕业前学好C语言
  3. 毕业前学好微观经济学
  4. 不要因为枯燥就不选修非计算机专业的课程
  5. 选修有大量编程实践的课程
  6. 别担心所有工作都被印度人抢走
  7. 找一份好的暑期实习工作

说到这里就引出了我职业生涯中的一个重要发现。周而复始的,你会注意到,当程序员遇到遇到问题的时候,他们会把问题重新定义,使得这些问题可以用算法来解决。这样一来,问题转换成他们可以解决的形式,但实际上,那些问题是一种“琐碎”的问题,也就是说程序员解决的只是问题的某种外在形式,而并没有真解决真正的问题,原因是这些问题非常难,不是表面的算法可以概括的,下面我就给你举个例子。

有一种说法,你们以后会经常听到,那就是软件工程或多或少正在遭遇质量危机。我本人是不同意这种说法的,同生活中的其他商品相比,大多数人用到的大多数软件质量好的出奇。但是这种说法指的不是这个意思,它所声称的“质量危机”涉及许多观念和研究,目标就是如何才能生产出更高质量的软件。从这个角度上看,计算机界可以分为技术派(geek)和务实派(suits)两大类。

技术方案想要把质量问题用软件自动处理。为了这个目的,他们发明了单元测试(unit testing),测试驱动开发方法(test-driven development),自动测试(automated testing),动态逻辑等。目的只有一个,就是“证明”程序中没有错误。务实派并不真的关心质量有没有问题,只要有人愿意出钱购买软件,他们才不想关心代码中有没有错误。当然,在技术派和务实派的大战中,务实派是获胜的一方,因为他们控制了公司的预算。老实说,我不觉得这是一件很糟糕的事。务实派认识到,消灭软件代码中的错误是一个边际报酬递减的事件。一旦软件的质量达到了一定的水准,能够用来解决特定的问题,那么就会有用户从这个软件中获益,用户也会因此愿意出钱购买。

同时,务实派对于“质量”有一个更广义的定义。你尽管大胆的想象,这个定义完全符合利益原则。所谓软件的“质量”,就是看它能为大家带来多少奖金,奖金越多,也就表明软件的质量越高。出人意料的是,“质量”的这种定义有深得多的内涵,远不止于写出没有错误的代码。举例来说,这个定义很看重为软件添加更多的功能,使得它能为更多的人解决更多的问题,而技术派很可能会嘲讽这种类型的软件为膨胀件(bloatware)。这个定义还要看重将软件做得更美观,一个好看的软件就是比一个难看的软件销量更好。这个定义还看重程序是否能使用户感到很愉快。基本上这个定义就是让用户自己来表达什么是软件的质量,让用户自己来决定某一个程序是否符合他们的需要。

现在回过头来再看技术派,他们只是对狭义的技术方面的质量感兴趣。他们只关注从代码中能看的出来的东西,而不关心用户将会怎么判断。他们是程序员,所以会想让生活中每一件事情都自动完成,因此很自然的,他们也想自动完成QA过程,这就是单元测试的来历。不要误解我,单元测试本身并不坏,当你运行完所有的单元测试,就可以机械实例“证明”一个程序是“正确”的。但是,这样做的不利之处是,任何不能被自动测试的东西,就会被排斥在质量的定义之外,变成与软件质量无关。即使我们明知用户更喜欢看上去很漂亮的软件,但是因为没有办法自动评估一个软件看上去到底有多漂亮,所以软件的美观就被排斥在自动化运维过程之外。

老式的微软公司测试员要做很多事情,包括检查自己是否协调和清晰,检查对话框中控制按钮的位置是否合理和对齐,检查用户操作时屏幕是否闪烁,观察用户界面的流程,考虑软件是否容易上手,判断用词是否合适,评估软件的表现,检查所有错误信息的拼写和语法。他们花大量的时间去确保软件的不同组成部分中用户界面都保持一致,因为协调的用户界面会让用户更容易上手。

所有这些事情,没有一件可以用程序自动完成。微软公司向自动测试靠拢的政策导致的后果之一就是,windows vista发布出来的时候,这个产品是极端不协调的,很多细节处都没有处理好,许许多多非常明显的问题没有解决,最终产品就上市了。这些问题中,没有一个是自动化脚本进行软件测试时定义的“错误”,但是他们中的任何一个都会让用户产生一种种的看法,即windows vista不如windows xp. 在这里,技术派的质量定义压倒了务实派的定义。我完全相信,在微软内部,现在所有的自动化脚本都显示软件100%合格,但是这又有什么用呢?此刻差不多每一个技术评论家都建议用户,只要有可能,就继续使用windows xp。他们没有人写过这样一个自动化测试,就是去测试window vista有没有给用户提供一个令人信服的从windows xp升级的理由。


为了发现可以改进的地方,你必须有一个定势思维,始终如一地用批判的眼光看世界。随便找一样东西,如果你看不出它的缺点,那么你的思维转型还没有成功。当你成功的时候,你身边亲密的人都会被你逼的发疯,你的家人恨不得杀了你。当你步行上班的时候看到一个司机漫不经心的开车,你几乎用了所有的意志力才勉强忍不住冲上去告诉那个司机,他这样开车差点要了旁边坐在轮椅上的那个可怜小孩的命。

当你改变了一个又一个这样的小细节后,当你磨光,定型,擦亮,修饰你的产品的每一个边角后,就会有神奇的事情发生。厘米变成分米,分米变成了米,米变成了千米。你最后拿出来的是一件真正优秀的产品。它第一眼就让人觉得震撼,出类拔萃,工作起来完全符合直觉。就算100万个用户中有一个用户突然某天要用到一个他100万次使用中才用到一次的罕见功能,他发现了这个功能不仅能用,而且还很美。你的软件中,即便是看门人的小屋都铺着大理石的地板,配有实心的橡木门和桃花心木的壁板。

就在这个时候,你意识到这是一个优秀的软件。


Excel工作表中有两种类型。一种的日期元年是1900年1月1日,这种格式中包含一个闰年的错误,这是为了与Lotus123兼容而故意留下的。另一种的日期元年是1904年1月1日。Excel同时支持这两种日期类型的文件,原因就是Excel的第一版本是为Mac系统开发的,当时直接使用了操作系统的日期元年,这样做比较容易。但是windows系统上的Excel必须能够输入Lotus123文件,后者是使用1900年1月1日作为日期元年的。这足以让人欲哭无泪。回顾整个历史,程序员无时无刻不想做出正确的事情,但是有时候你不得不向现实屈服。


很有可能的是,不管别人雇佣你干什么,你都会遇到某些很不顺心的麻烦事。就算你不用跟干面粉或面包屑打交道,只要你在刀片厂工作,回家的时候,你的手指上可能就都是小伤口。如果你为VMWare工作,你的噩梦就是视频游戏所依赖的高端显卡总是出错。如果你的工作是开发windows操作系统,你的噩梦就是代码中小小的变化,就会引起几百万的旧程序和老硬件停止工作。它们就是工作中让你不顺心的麻烦事。

实情却是,如果你为麻烦事找到了解决方法,市场就会向你支付报酬。解决轻而易举的事情是拿不到钱的。

目前,许许多多样可爱的创业公司都有一个共同点,那就是他们所有的产品就是一个小小的站点,背后的技术就是一些如Ruby-on-Rails和Ajax,不解决任何“麻烦事”,而且别人很容易做出复制品。这类公司中相当一部分,给人的感觉就是不实在和空洞,因为他们没有解决实际中迫切需要解决的任何困难问题。只有等到他们解决了困难问题,他们对用户才是有用的,用户只向解决困难问题的公司付钱。

编写一个设计优雅易于使用的应用程序,同解决“麻烦事”有相仿的效果。虽然看到成品的时候,你会觉得不难做,但是实际上是很难的。就好比你在看精彩的芭蕾舞演出,你觉得演员跳起来很轻松,实际上换了你就会困难无比。Jason和他的37signals致力于做出优秀的设计,他们得到了回报。优秀的设计似乎是最容易复制的东西,但是做起来却又不那么容易。你看看微软试图仿效iPad的例子,就会明白这一点。做出优秀的设计本身就是一件“麻烦事”,实际上能够提供牢固的令人震惊的竞争优势。

说实话,Jason做出目前成果的一个可能原因,就是他在设计方面有很高的天赋,所以他选择解决领域的“麻烦事”,因为看上去这对他来说不难做。同样地,多年以来,我一直是一个windows程序员,所以用C++从头写出一个FogBuzz的windows安装程序,去与所有那些烦人的com组件打交道,看上去对我来说也不难做。


如果你不搞一个日程规划,程序员就会首先将容易的有趣的功能做出来。然后,他们剩下的时间就不够了,你别无选择,只好推迟日程来开发有用的重要的功能。如果你确实搞了一个日常规划,那么甚至在你开始工作之前,你就会意识到你必须砍掉一些东西,因此你砍掉了容易的或者是有趣的功能,全部精力投入开发用于有用的或重要的功能。正是这种迫使你砍掉某些功能的压力,使得你做出了一个更强大更优秀的产品。它包括了更好的功能组合,而且能够在较早的日期完成。

当Excel5的开发工作接近尾声的时候,我和同事Eric Michaelman开始着手编写Excel6的设计规格说明书。我们坐下来,详细审阅从Excel5的日程规划中被刷下来,准备放进Excel6的功能清单,猜猜结果怎么样?这份功能清单比你能想到的最糟糕的清单还要糟糕,上面没有一个功能是值得开发的。我想他们之中的每一个功能都从来没有过开发价值。为了赶上日程,我们砍掉了这些功能,现在看起来这是我们做过的最有价值的一件事情。如果我们当时没有这样做,那么Excel5的开发时间会延长一倍,然后做出来的产品中包含了50%的无用功能,并且未来我们还不得不维护这些功能,直到Excel生命的最后一天,都要让当前版本向后兼容这些功能。


由于内存的价格直线下降,CPU的速度每年都在翻番,所以作为一个程序员你就面临选择。你可以花6个月,用汇编语言重写程序的内循环,你也可以休假6个月,一支摇滚乐队当鼓手。不管哪一种选择,6个月后你的程序都会运行得更快。实际情况是,没有程序员喜欢用汇编语言编程。

有一些程序员将大量的精力投入优化工作,要将程序变得更紧凑,更快速。某一天,他们醒来后将会发现,这种努力基本上是白忙一场。如果你喜欢用经济学家的口吻夸夸其谈,那么你至少可以说,这种努力“不会带来长期的竞争优势”。


有大量例子可以说明,只要你把相关内容放在一起,就能改善代码的质量。大多数编程规范都有类似的规则:

  1. 尽量将函数写得简短。
  2. 变量声明的位置距离使用的位置越近越好。
  3. 不要使用宏去创建你的编程语言
  4. 不要使用goto。
  5. 不要让扩号与对应组合之间的距离超过一个显示屏。

所有这些规则都有一个共同点,就是让与一行代码相关的所有信息尽可能的靠拢,缩短它们之间的物理距离。这将大大增加你的机会,让你一眼就能看出程序内部是怎么回事。

西蒙尼版的匈牙利命名法中,每一个变量的开头都是一个小写字母组成的标签,表明变量中包含了什么种类(kind)的数据。在微软公司内部,最初被叫做应用型匈牙利命名法(Apps Hungarian),因为它是在应用程序部门(Applications Division)中使用的,也就是在Word和Excel身上。类型转换函数名字都是TypeFromType类型,而不是传统TypeToType形式,目的是确保每个函数都以返回值的类型作为前缀。应用型匈牙利命名法的作用是极其巨大的,在C语言盛行的时代尤其如此,因为C语言的编译器几乎不提供任何类型识别。

我知道大家都会认定,我的编程水平很低下,理解不了异常处理的真正含义,完全不知道如果全身心拥抱异常处理,我的生活质量就会出现全方位的提升。但是我要对你们说,实在太遗憾了,写出真正可靠代码的方法,就是使用了那些考虑了人类常见缺陷的简单工具,而不是使用那种含有隐藏的副作用,生产有瑕疵的抽象层,假设程序员绝对不会出错的复杂工具。


我甚至不喜欢“软件个体户”这个词,它的原意是微型独立软件供应商(micro independent software vendor)。其中的独立软件供应商是微软公司发明的一个新词,指“微软公司以外的软件公司”,或者说得更明白一些,就是指“由于某种原因,我们还没来得及并购或消灭的软件公司,可能因为他们从事的都是一些花里胡哨的生意(比如布置婚礼仪式上的桌子),所以像我们这样的高级公司就没必要自降身价去跟他们争这些小生意,就让他们逍遥自在去吧。但是你们这些公司一定要记得使用.NET!”

与这个词在一起的还有另外一个微软公司发明的词,叫做“历史遗留下来的”(legacy),用来指所有那些不是微软公司开发的软件。比如谈到Google的时候,他们就说这是“历史遗留下来”的搜索系统,好像都在暗示Google只不过是“一家古老,蹩脚的搜索引擎,之所以还有人用,完全是因为历史原因,用户迟早不可避免地会投入MSN的怀抱。”反正就是诸如此类的意思。


通过这件事,我学到了软件开发中重要的一课。那就是,对你最重要最关键的部分,你一定要用使用更原始的工具。举例来说,假定你正在编写一个很酷的3D射击游戏(就像同样在那个时期出现的雷神之锤)你排第一位的卖点,就是最酷的3D图像。为了达到这个目的,你就不能使用任何你能找到的3D库,你一定要写一个你自己的3D库,因为这是达到你目的的基础。那些使用诸如DirectX这样现成的3D库的公司,只是因为他们的卖点不是软件的3D表现(也许他们有一个很好的故事)。

一旦我认识到了这一点,我就决定不再信任其他人写出的糟糕的应用程序服务器,而要自己重新用C++和Netscape Server的底层API写一个。因为我想通了,这样的话假定某个地方出错了,那就是我自己的代码出错了,我能加以处理,并且最终能解决问题!

当我坐下来构建一个软件系统时,我必须作出决定到底使用哪些工具。一个好的软件架构师只会使用那些“可以被信任”或“可以被维护”的工具。一种工具可以被信任,并不意味着它是由像IBM那样可以被信任的大公司制造,而是意味着你百分百确定它会正常运作。比如我觉得现在的大多数windows程序员对visual c++都很信任,另一方面,他们可能不怎么信任MFC,但是MFC提供源码,因此当你发现异步套接字(async socket library)写得糟糕透顶时,你不信任它,但是你至少可以维护它。所以把你的职业生涯赌在MFC的前途上,也不会有太大问题。

你可以把你的职业生涯赌在Oracle DBMS上,因为它有效运行,大家都知道它。你也可以把职业生涯赌在BerkleyDB上,因为就算它把一切都搞糟了,你也能查看源代码,解决问题。但是,你可能就不能把职业生涯赌在那些既不开源也不知名的工具上。你可以把它们用来测试,但是它们不是值得押上你的未来的工具。


我认为将iPad的成功归因于简化的功能是不正确的。如果你相信这种错误的观点,你就会相信在其他产品上也要减少功能,才能让产品变得更成功。根据我6年来创办公司的经验,我可以告诉你,在我做过的所有事情中,最能增加收入的就是推出一个带有更多功能的新版本。这一招最管用。一个新功能的软件新版本,对我们财务报表的影响是绝对无法否认的,它就好像地心吸力一样,能够把钱吸过来。我们试过Google的在线广告系统,也试过各种业务推广分成计划(affiliate scheme),或者看到新闻媒体上出现介绍说FogBugz的文章,它们对财务报表的影响几乎很难被察觉。但是如果推出新版本,其中包含了新功能,我们就会看到公司的收入出现了迅猛的,确凿的,重大的持续的增长。

不习惯把“这是我的错”这句话说出口是很自然的。人的天性就是这样。但是,这几个字能够使得发怒的顾客变得高兴,所以你必须学会这样说,必须让你嘴里发出来的声音听起来,就像是你真这样想一样。这就开始练习吧,每天早上淋浴的时候,把“这是我的错”重复100遍,直到你忘了它的意思,只记住了一串音节为止,这样你就能在需要的时候张口就来。

作为对比,再来看看BEA公司软件公司。它属于大公司,高价软件那一类。仅仅因为它的软件价格如此之高,几乎无人有使用它的产品的经验。高校的毕业生创办互联网公司,不会选择BEA的技术,因为他们读书的时候根本用不起BEA的产品,所以对它一无所知。许许多多其他优秀的技术,就是因为价格太高,而注定了黯然退场的命运。苹果公司的WebObjects,始终无法被应用程序服务器采用,就是因为它高达5万美元的价格。谁在乎它有多优秀啊?根本没人使用。

在实践中往往存在一种强烈的倾向,那就是你会假设,针对大规模人群的实验,应该像高中实验室里的化学实验那样准确和可以重复。但是,每一个做过社会实践的人都知道,每次实验得到的结果差异得的惊人,而且不可重复。如果你想要对得到的结果充满自信,那么唯一的方法就是千万不要把同样的实验做两次。