本文来自微信公众号:InfoQ (ID:infoqchina),作者:李冬梅,审校:刘燕,原文标题:《指导了上百万程序员,〈代码大全〉之父和你聊聊软件开发素养|独家探访“编程圣经”背后故事》,题图来自:视觉中国

史蒂夫·迈克康奈尔(Steve McConnell)被公认为软件开发社区中的首要作者和发言人之一。他是 Construx Software 公司的首席软件工程师。他所编著的图书包括曾被《软件开发》杂志授予优异产品震撼大奖的《代码大全》和《快速软件开发》,以及《软件项目生存指南》和《专业软件开发》等等。


(资料图)

他撰写的无论是《代码大全》还是《快速软件开发》,主要聚焦的内容都是敏捷开发、高效开发,许多开发者在阅读过两本书后均表示 Steve 撰写的书实用性很高,是历久弥新、永不褪色的经典之作。

从第一版《代码大全》到最新出版的《卓有成效的敏捷》,20 多年来,Steve 一直倡导敏捷开发、高效开发。不久前,InfoQ 有幸采访到了 Steve,与他聊到了创作《代码大全》的过程以及他对敏捷开发、编程复杂性、代码可读性等时下热门话题的理解。

Steve 称,在最初写作时,他并没有想过写一本篇幅如此宏大、知识点覆盖如此全面的书籍。据他回忆,为了完成《代码大全》,他曾翻阅 80 多本书,但在翻阅书籍的过程中却发现并没有一本书能详实关于软件开发实践的细节,于是 Steve 决定自己写一本这样的书来填补空白。

在书中,Steve 特别强调了要管理软件的复杂性,Steve 认为大型编程项目极具挑战,要管理成千上万人写的代码,如果不对复杂性做出限制,那么项目的每个步骤都可能崩溃失控。

提及敏捷开发时,Steve 认为不能把敏捷单独列出,也不把它当成独立于编程之外的东西,它只是一种普通的编程方式,很大程度上已经成了良好实践的统称,不必过于纠结是否是敏捷。

除了敏捷之外,另一种好的编程方式是写出可读性强的代码,Steve 表示,“能跑起来的代码不一定是好代码”,代码是为人服务的,真正的好代码得能随时间推移而持续成功,这样才能供他人处理和扩展而且可以更正,还要看他人能不能读懂代码内容。

以下为 InfoQ 与《代码大全》作者的独家访谈实录,经编辑。

耗时三年半,写完900多页《代码大全》

InfoQ:您最近都在忙什么?在关注哪些技术呢?

Steve:我最近在研究领导力方面的内容,我觉得很多软件从业者,在职业生涯后期总会进入一种模式——会更多地开始扮演领导角色,我本人也是如此。就技术发展和技术趋势而言,在第一版《代码大全》出版以来,我觉得这些年间最让人印象深刻的变化就是程序员们往往将大部分时间花在一种技术上或者说只关注一种编程语言。

当然,人们也会学习其他工具,但多数人确实可能每天都在用 C 或者 C++,如果再回溯得更古早些时候,也可能是 Fortran。到我完成第二版《代码大全》时,人们将大部分时间花在同一种技术上的情况也仍然非常普遍。

即使是现在程序员们的工作技术栈,也总有种例行公事的感觉,他们只是在象征性地使用多种语言或者是把低级语言跟脚本语言混合使用。在《代码大全》的序言部分,我说我认为程序员职业生涯中的分水岭,就是开始学习第二语言,但时至今日我发现使用两种语言才是普遍现象。这已经成为他们日常业务的一部分,甚至不只两种语言,我不知道该不该称其为趋势,这种情况在最近这几年已经屡见不鲜了。

InfoQ:您当初创作《代码大全》的初衷是什么?第一版耗时多久完成的?中间发生过哪些令您印象深刻的事?

Steve:这本书大概花了我三年半的时间才完成,差不多 3500 个小时的全身心投入。我最初只打算写篇杂志投稿,只想写点编程风格和编程技巧之类的短文,于是我特意为此收集了一些资料。在写第一版那会儿还没有互联网,所以不能靠搜索引擎来找到我想要的答案,而是得去大学、去学术图书馆里找资料。我花了很长的时间在巨大的馆廊里跑来跑去,翻阅各种杂志,还得查看那些实体卡片目录,然后再到相应的书架上找书。

我住的地方离华盛顿大学很近,所以我就去了那边校区里至少三处不同的图书馆查阅资料。当时,我以为肯定已经有人写了关于软件开发实践的书籍,只是我没听说过罢了。

当时,我大概翻阅了 80 多种不同的书籍和文章,随着资料查得越来越多,研究越来越深入,我发现市面上与软件编程实践相关的书籍并不多,甚至没有一本书是专门讨论软件编程实践主题的,这让我十分震惊。而在我翻阅众多书籍的过程中,我积累了许多关于这方面的知识,所以我决定自己写一本这样的书来填补这一空白。

在这三年半期间,我用了大概一年半的时间收集资料、试写部分章节,之后花了一年时间全职写书,又用了近一年时间参与编辑。

在我心里,我深知软件开发实践内容的重要性,但当我写这么一本书的时候,我发现很难向他人解释书的内容,他们理解不了我在说什么、做什么。我甚至专门找了位朋友帮我从头到尾审阅手稿,我觉得这样边写边给他看,理解起来应该很简单。让我感到意外的是,他并不能完全理解我在写些什么,这也让我意识到,很多对我自己显而易见的问题对其他人来说很难理解,所以我写这本书时尽量让内容详实、易懂。

在写书的三年多时间里,让我印象最深刻的就是为手稿和大纲收集同行评审意见。我跟出版商签过合同 所以得先提供一份大纲,我就试写了几个章节交给五六个朋友审阅,他们的意见非常重要,我最终提交给出版商的素材至少是评审之前的两倍。而在真正开始写手稿时,内容接受了约 12 个人的审阅,那时还没有互联网,我就把纸质版手稿分章节寄给他们,收到的意见反馈同样是手写信件或电邮副本,而最终出版的第一版《代码大全》的篇幅也从最初设想的 250 到 350 页激增至 900 多页。

回顾整段经历,我体会到了同行评审的价值,时至今日,这些手写信件和电邮对我来说都弥足珍贵。

InfoQ:这本书取得的成就是否满足了您的预期?

Steve:在 1992 年向出版商提交第一版手稿时,我从没想过 25 年后会在采访中谈写书的感受,太奇妙了!

创作初期,我给自己定的目标没有那么高,如果当时就想到要写 900 多页,可能就会头大了。总之,虽然最终耗费了很大的工作量,但最终《代码大全》的反馈很好,它的销量远超我的想象。当时我觉得最好的情况大概能卖 10 万册,但在被翻译成不同语言和版本之后,它卖出了超 100 万册,这比我最乐观的估计都要多得多。

编程语言没有好坏之分

InfoQ:您在《代码大全》书中曾提到,“与使用低级语言的程序员相比,使用高级语言的程序员可以获得更高的生产力和质量。与汇编语言和 C 等低级语言相比,C++、Java、Smalltalk 和 Visual Basic 等语言在提高生产率、可靠性、简单性和可理解性方面被认为提高了 5 到 15 倍”。但据 TIOBE 2022 年 10 月编程语言排名来看,Python、Java、C 成为了 TIOBE 榜单的前三甲,还是有很多人愿意使用 C 语言,对此现象您怎么看?

Steve:在我当初发表这条评论,或者引用程序员使用高级语言时效率更好的结果时,谈论的主体其实都是通用语言,而实际上绝大多数语言也都属于通用语言。所以我想要表达的是只要任务能用通用语言来完成,那使用高级通用语言要比使用低级通用语言的效率高。但之后多年当中,语言本身的专业化倾向越来越显著,语言在特定用途上的表现也更好了,换言之,某些语言确实不再像过去那么“通用”了。

C 语言之所以还那么受欢迎,是因为一些从事硬件设备或其他类型的低级编程的程序员们确实还是很喜欢使用它。对于这类特定用途的编程来说,C 仍是个好工具。需要强调的是,我提到的高级编程语言可以提高效率这一概念的核心是为合适的工作选择合适的语言,而不是单纯地评价哪种语言是好或是坏。

我现在的观点是,如果某种语言对于任务来说太高级了,那就得额外花时间搞清很多实现细节,这就跟设计目的错位了。同样地,如果选的语言对任务来说太低级,那就得额外做大量编码才能完成工作,所以语言的优劣只体现为更适合哪种工作场景,而不是说一种语言天生就比另一种语言好。

InfoQ:第二版《代码大全》和第一版最大的区别是什么?

Steve:第二版《代码大全》确实跟第一版那会儿有不同。在写第一版时,确实花了很长时间来打磨内容。面向对象编程其实更像是研究课题,而非具体实现,那时候敏捷理念还没出现。到我写第二版的时候,面向对象编程已经成为实用议题,而且在商业实践中得到了广泛应用,其普及度之高,甚至火出了圈,成为了一种可供人们参考的编程方式。

另一方面,在编写第二版时,敏捷的概念仍然很新,所以编写第二版时的一大挑战,就是决定要在多大程度上把敏捷设置成独立议题,或者说把相关良好实践整合到常规工作当中。

最终我决定不把敏捷单独列出,也不把它当成独立于编程之外的东西,我只谈它的好处,视它为一种普通的编程方式,我觉得这个决定还挺明智的,我认为敏捷很大程度上已经成了良好实践的统称,这些实践普遍有效而且包含或成功或失败的文化迭代,这就是第一版与第二版之间发生的大变化。

具体来讲,编程语言及其使用方式都与以往有了很大不同,我认为第二版中使用的编程语言到现在仍未过时,而且如今人们在用的语言数量则出现了爆炸式增长,这是实实在在的转变。

软件构建的核心就是管理复杂度

InfoQ:您身上有很多标签,比如技术作家、演讲者、企业家等,这些角色中您最喜欢哪一个,为什么?

Steve:我自己其实一直在程序员和作家这两个角色间反复横跳。从一线程序员到全职作家,单这个过渡期就有一年。在写了一阵子书之后,我感觉自己爱写书开始多于写代码。但到后面我又开始厌倦了,我迫切想要回去编程,当时我甚至感觉自己以后再不会写书了。

在又编了几年程之后,我觉得编程挺好,却也怀念写作,所以又开心地投入到第二本书中,边写边觉得写书才最快乐。但第二版写到了一定阶段,我又想起了编程的好,最终我意识到自己其实不太定性,喜欢反复横跳。

但我也从编程中学到了很多写编程书所用的知识,如果我仅仅是一名作家,那可能永远没机会验证自己写的东西能不能真正跑起来。所以不能说我单纯喜欢写作或者编程,这两者对我来说是相辅相成,相互成就的。

InfoQ:您书中传递出来两个很重要的观点:

1. 软件构建的核心就是管理复杂度;

2. 以程序员为本。

先来说说第一个,软件构建的核心就是管理复杂度,为什么会这样认为,能举个具体的例子吗?写代码时该如何管理好复杂度,您有什么建议吗?

Steve:没错,我在书里谈了不少如何管理复杂性,因为我觉得只要了解编程的基础知识,那小型编程项目其实都不困难。但大型编程项目却极具挑战,部分原因在于人员管理问题以及如何协调大量人员,这是一项极其困难的脑力工作。

实际上,软件开发是极少数既需要协调人量人力又不具备实体的工程技术项目,建筑项目也很复杂而且涉及大量参与者,但他们面对的是有形的实体,所以能以物理形式进行跟踪。同样,桥梁隧道之类的工程项目也是如此,现场参与者虽然成百上千,但各个步骤或者施工环节确有切实的线索可抓。但在软件中很多工作无形无相,所以除了我们自己的想象力外再无跟踪事态的可靠线索。

所以大型软件项目因复杂性而失控的可能性远超人类从事过的几乎任何其他项目,其根源就是软件项目的无形性,而且大型软件项目很快就会超出个人的理解力上限,没人能理解项目中的所有代码,毕竟这是成百上千人的成果。

所以面对这些规模更大的项目,如果不对复杂性做出限制,那么项目的每个步骤都可能崩溃失控。以往,无数大型项目都在砸下巨量资金后失败了,原因就是其过于复杂,已经无人能理解究竟发生了什么,就如同过于庞大的巨兽被自身重量所压垮。

具体来讲,复杂性其实贯穿于软件开发的各个阶段,复杂性在编码过程中,因为如果底层代码的质量不好,超大规模系统也可能就由此崩溃。所以必须立足底层,立足细节抓代码质量,关注每个语句、例程和类,步步为营,以此为基础才能扩大规模,同时继续保持代码质量,即在设计和架构层级控制复杂性,对复杂性控制要广泛而深入地体现在软件开发的各个阶段。

“能跑起来的代码不一定是好代码”

InfoQ:您书中传递出来的第二个观点是以程序员为本,您书中提到的“以程序员为本”,即代码是为了人而写的,不是为了机器,所以代码要以程序员为本,为程序员服务,那么该如何提高代码的可读性呢?

Steve:很多科班出身的程序员尤其是很多初入职场的新手一直有个误区,他们认为能跑得起来的代码就是好代码。但专业级别的商业编程有新的要求,代码不能只跑通一次。学校作业能跑通一次就算合格,但商业代码得能稳定运行多年,工作五年、十年、二十年,还得易于理解,这样移交后,其他人也能处理后续,继续移交,并仍然能保持稳定。第四、五、六个人都能接手,这明显跟大学教育里的要求完全不同了。

能跑通的代码并不可靠,必须做得更好,否则你就不算是专业的程序员,真正的好代码得能随时间推移而持续成功,这样才能供他人处理和扩展而且可以更正,还要看他人能不能读懂代码内容。

很多程序员认为自己的主要工作是跟计算机沟通,编写计算机能懂的代码就行。确实,但也不完全对,还有另一部分,就是通过代码跟其他人沟通,这是一种非常重要的心理转变。

有时候,甚至富有经验的程序员也会犯错,误以为代码能跑就万事大吉。在我看来,代码能跑通只是万里长征的第一步,往上还有很多级,拾级而上才能让代码具有可理解性,可供他人接手处理。

是否敏捷并不重要,重要的是能解决问题

InfoQ:在您的书中,您大部分内容都聚焦在敏捷开发上,但似乎敏捷开发并不适合所有企业,您认为什么样的团队适合敏捷?

Steve:关于敏捷开发,我写了一本新书《More Effective Agile》。“Effective(有效)这个词我是认真考量过的,我觉得关于敏捷开发有不少认知误区,好像开发只有敏捷或者不敏捷之分。事实上,不能这样简单粗暴地定义敏捷开发。

现在,敏捷概念下集中了大量实践。二十多年来,我们一直在讨论实践、讨论敏捷,讨论能不能支持敏捷原则和价值主张,但我觉得这些讨论并没有抓住重点,重点应该是真正用软件和开发来达成某些目标,某些敏捷案例能够支持业务目标,而某些则不能,所以与其硬去评判某个项目是否敏捷,倒不如考虑该向项目引入哪些元素来支持业务目标。

如果我们开发的是移动应用,而且该应用的更新需求比较频繁,那也许可以每周更新一次,有些组织的软件每天可能更新几百次,对于这类组织,敏捷就很有意义。但如果所开发的是嵌入式固件软件,比如是那些需要安装在飞机、航天器或者船舶上的设备,而且设备本身的更新频率不会太高,我觉得这类软件就不适合敏捷。

可靠性跟高质量是两个不同的目标,而敏捷并不普遍适用。当然,也有一些办法能把敏捷实践元素引入到安装到飞机上的软件中,这时候它们的意义就不在于敏捷而只是种被证明能提高质量的有效办法。到底是否是敏捷并不重要,重要的是这些实践能否帮助项目达成目标。

InfoQ:“试图通过增加测试量来提高软件质量,就像试图通过更频繁地称重自己来减肥一样。你在上秤前吃的东西决定了你的体重,而你使用的软件开发技术决定了测试会发现多少错误”,但不经过足够的测试,如何判定软件的质量呢?您认为软件测试的量多少为最佳,是否有个标准?

Steve:我并不是说测试不重要,测试当然很重要,我想表达的是,按当时的软件开发方式,人们更倾向于先编写大量代码后再统一做测试,那时候的测试属于事后环节。

在之后的几年中,开发模式发生了很大变化,测试开始更多融入到开发流程当中。所以现在来看那个比喻已经不适用了,更准确的说法应该是:我刚吃了一根胡萝卜,就跑到秤上称一下看看有什么变化,之后再吃一根胡萝卜,再称一下,这么做对于结果来说毫无影响。吃点东西就去称体重的做法是荒谬的,但写点代码就去测确实是正确的软件测试思路,更理想的思路甚至希望在编写代码前先编写测试。

我在《代码大全》第一版里分享过一个故事,故事说有个女人每天起床都会上秤,只要发现超重,她就只吃胡萝卜和芹菜直到恢复到她理想的体重状态,因为她每天都坚持这么做,所以她从不会超重太多,因为每次发现超重后她就会立刻纠正,这基本就是软件测试的意义。只要测试够频繁就不会遇到太多麻烦。

编写代码的最快方法就是提高代码质量

InfoQ:您提到过,“复制和粘贴是一种设计错误”,但其实很多代码是通用的,我们如何能“既保证代码的原创性又不去重复造轮子”呢?这中间的尺度该如何把握?

Steve:“复制和粘贴是一种设计错误”其实不是我提出来的,是其他开发伙伴提出来的,我很喜欢这句话。这话的意思是,如果你正在编写代码而且在复制和粘贴代码片段,那你就错过了深入剖析代码的机会。当然,复制和粘贴也有其必要性,我自己有时候也会复制和粘贴,但我想强调的是希望大家在复制粘贴时能思考下到底要不要深入琢磨问题中的共性,这背后是不是有需要关注的规律?

技术发展至今,开发者们变得更加聪明,开发效率也更高效,他们已经习惯把经常调用的模块打包起来以便随时复用,这和简单的复制粘贴还是不完全一样的。我们现在的全球基础设施令人赞叹,它的根基就是大规模的软件复制,这是很正常的现象,没什么不对。

InfoQ:复制粘贴能提高一些编程效率,但如果想要在更短时间内写出更高质量的代码,程序员应该培养哪些技能或编程习惯?

Steve:长久以来的经验表明,编写代码的最快方法就是提高代码质量。1970 年,IBM 的一项经典研究发现,被要求尽快完成工作的编程团队最容易写出有缺陷的代码,而且交付速度也最慢;被要求保证质量的团队不光代码质量好,交付也更及时。

所以这个问题的答案就很明确了:要重视代码质量,质量就是结果。对于那些需要超级可靠的软件,比如飞机的飞行控制软件或者嵌入心脏起搏器或医疗设备的软件,那就得通过更长的时间和额外的步骤才能保证这样的质量水平。但对于常规的商业编程、应用编程而言,最有效的办法就是尽量保证初始代码的高质量,这样就不用花太多时间调试和重写。

很多程序员是花半个小时写代码然后花四个小时做调试,这明显大有问题,如果程序员最初能花一个小时甚至更多时间对代码“精雕细琢”,那也许后期的调试只需要半个小时且整体效果更好。

80% 的情况下没必要重构代码

InfoQ:《代码大全》这本书主张开发者应跳过“先做再修正”(code and fix) 和“始于大设计”(Big Design Up Front)的瀑布模型,近年来这个模型有哪些变化吗?您想通过这个模型向外传递的价值点是什么?

Steve:我写这本书绝不是让大家跳过瀑布模型,或者让人们以特定某种方式做事,比如预先做整体设计,这本书想让人们摆脱纯编码和纯修复,把编程变成一件有趣的事。我认为有更多系统性的方法能帮助大家缩短代码编写时间,但这种方法不会是僵化的,人们不该耗费大量时间调试代码、纠正代码和修复错误代码,我写这本书的目标是帮助大家以一种能保证代码质量的方式来编程,享受编程带来的乐趣。

在敏捷方面,特别是在 20 年前首次开始讨论敏捷时,我提出“提前做整体设计”其实是要受抨击的。当时人们认为预先做整体设计不值得提倡,我自己对这个问题的看法也在逐渐发生变化。在我早期的职业生涯,特别是在写初版《代码大全》时,我确实觉得预先做整体设计是对的,那时这个理念并不盛行,大家做项目时只是在尽量做预估,在投身于细节之前至少先想想要干什么。

但多年来我意识到,不同开发者所掌握的技能组合确实差异很大,部分开发者更擅长以抽象方式设计之后再实现这些设计代码,也有些开发者更擅长编写代码然后边写边学,边写边改。这两种方式或许最终都能得令人满意的结果,而这两类开发者都无法照抄照搬对方的工作模式。

技术项目能否取得成功的重点在于人的思维是如何跃动的,所以如果某个程序员的思维是从抽象到具象的演绎,而另一个人的思维是从具象到抽象的归纳,那绝不能把两人的思维模式强行互换。

略显遗憾的是,过去二三十年间,编程行业一直想强迫每个人以相同的思维方式工作,这是注定要失败的。

InfoQ:您曾提到,“将时间花在提供 80% 收益的 20% 的重构上”,这句话我们该怎样理解?

Steve:说起重构,我曾在网上看到过一个帖子,说的是程序员的代码能跑,就不用改,根本就不需要去碰它。但出于种种原因,它还是会出 bug,所以最后还是进行了重构。

但在我看来,重构其实没什么必要。因为就算不改,代码也能运行良好,这样的代码实际就如同黑盒,没必要打开盒子换里面的东西,因为没有改进的潜在价值,所以没有动它的必要,相反,重构还可能搞出新的错误。

所以我说的二八定律其实是说,绝大多数更迭往往只涉及代码库中的很小一部分,只要将重构集中在实际变更的那部分代码上就可以了,所以在更改代码时,大家应该把更多精力放在提高代码质量上,而不是单纯为了改而改,重构应该有明确理由,随便选段代码来改没什么意思。

编程年限增加,开发者越要重视质量设计

InfoQ:您认为不同阶段的开发者该培养哪些技能?

Steve:程序员的从业生涯肯定会经历几个阶段,在生涯早期需要努力跟编程语言或工具作斗争。现在的情况跟我写《代码大全》时已经完全不同了,那时候,程序员在学校和公司里都在鼓捣语言和工具,而现在很多程序员从青少年时就起步了,很多孩子在 10 岁甚至更小的时候就在接受系统的编程教育,所以他们早早过了前面的阶段。

过了编程的早期阶段,我觉得下个阶段应该是提高代码质量,特别是重视质量设计。人们意识到哪怕代码再好,如果质量设计不足,那写好的代码只能废弃掉。再往后,程序员们会进入新的阶段,他们开始意识到编码是一种团队行为,靠个人编写的代码是有极限的,依靠团队编写出优质的代码才是关键。这就回到了我之前提到的观点,编程中的沟通不只是跟计算机沟通,更多是跟其他人沟通。所以程序员们终将意识到编程不是一切、设计也不是一切,团队合作同样重要。

有个观点叫康威定律,认为软件系统的结构反映的是构建该系统的人类组织的结构,只要能重视这一点,人们就能在组织内良好协作,这种良好的协作特性也将反映在软件当中。我也见到过那些因为团队内部协作不足导致软件开发失败的例子,这样的团队中的成员们只关注开发任务,成员之间不做沟通,那软件是不可能成功的。所以我认为软件最终能否顺利交付最关键的部分在于人。

本文来自微信公众号:InfoQ (ID:infoqchina),作者:李冬梅,审校:刘燕

推荐内容