你永远不应该做的事情 —— 危险的重构

注:机翻,未校对。20多年过去了,作者并没有继续写第二部分。


Things You Should Never Do, Part I 你永远不应该做的事情,第一部分

Netscape 6.0 is finally going into its first public beta. There never was aversion 5.0. The last major release, version 4.0, was released almostthree years ago. Three years is anawfullylong time in the Internet world. During this time, Netscape sat by, helplessly, as their market share plummeted.
Netscape 6.0 终于进入了第一个公开测试版。从未有过 5.0 版。最后一个主要版本是 4.0 版,大约在三年前发布。在互联网世界中,三年是一段非常长的时间。在这段时间里,网景无助地坐视着他们的市场份额直线下降。

It’s a bit smarmy of me to criticize them for waiting so long between releases. They didn’t do iton purpose, now, did they?
批评他们在发布之间等待了这么长时间,这对我来说有点不屑一顾。他们不是故意的,现在,是吗?

Well, yes. They did. They did it by making thesingle worst strategic mistakethat any software company can make:
是的。他们做到了。他们通过犯任何软件公司都可能犯的最糟糕的战略错误来做到这一点:

They decided to rewrite the code from scratch.
他们决定从头开始重写代码。

Netscape wasn’t the first company to make this mistake. Borland made the samemistake when they bought Arago and tried to make it into dBase forWindows, a doomed project that took so long that Microsoft Access atetheir lunch, then they made it again in rewriting Quattro Pro fromscratch and astonishing people with how few features it had. Microsoftalmost made the same mistake, trying to rewrite Word for Windows fromscratch in a doomed project called Pyramid which was shut down, thrownaway, and swept under the rug. Lucky for Microsoft, they had neverstopped working on the old code base, so they had something to ship,making it merely a financial disaster, not a strategic one.
Netscape 并不是第一家犯这个错误的公司。Borland 在收购 Arago 并试图将其制作成 dBase for Windows 时犯了同样的错误,这是一个注定要失败的项目,花了很长时间,以至于 Microsoft Access 吃了他们的午餐,然后他们再次从头开始重写 Quattro Pro,并以其很少的功能让人们感到惊讶。Microsoft 几乎犯了同样的错误,试图在一个名为 Pyramid 的注定失败的项目中从头开始重写 Word for Windows,该项目被关闭、扔掉并被扫地出门。幸运的是,Microsoft 他们从未停止过在旧代码库上的工作,所以他们有一些东西要发布,这使得它只是一场财务灾难,而不是战略灾难。

We’re programmers. Programmers are, in their hearts, architects, and thefirst thing they want to do when they get to a site is to bulldoze theplace flat and build something grand. We’re not excited by incrementalrenovation: tinkering, improving, planting flower beds.
我们是程序员。在他们心中,程序员是建筑师,当他们到达一个现场时,他们想做的第一件事就是把这个地方推平,建造一些宏伟的东西。我们对渐进式翻新并不感到兴奋:修补、改进、种植花坛。

There’s a subtle reason that programmers always want to throw away the code and start over. The reason is that they think the old code is a mess. Andhere is the interesting observation:*they are probably wrong.*The reason that they think the old code is a mess is because of a cardinal, fundamental law of programming:
程序员总是想扔掉代码并重新开始,这是有微妙原因的。原因是他们认为旧代码一团糟。这里有一个有趣的观察结果:*他们可能错了。*他们之所以认为旧代码一团糟,是因为编程的一个基本定律:

It’s harder to read code than to write it.
阅读代码比编写代码更难。

This is why code reuse is so hard. This is why everybody on your team has adifferent function they like to use for splitting strings into arrays of strings. They write their own function because it’s easier and more fun than figuring out how the old function works.
这就是为什么代码重用如此困难的原因。这就是为什么团队中的每个人都有不同的函数,他们喜欢使用不同的函数将字符串拆分为字符串数组。他们编写自己的函数,因为这比弄清楚旧函数的工作原理更容易、更有趣。

As a corollary of this axiom, you can ask almost any programmer todayabout the code they are working on. “It’s a big hairy mess,” they willtell you. “I’d like nothing better than to throw it out and start over.”
作为这个公理的推论,你今天几乎可以向任何程序员询问他们正在处理的代码。“这是一个毛茸茸的大烂摊子,” 他们会告诉你。“我只想把它扔掉,重新开始。”

Why is it a mess?
为什么会一团糟?

“Well,” they say, “look at this function. It is two pages long! None of thisstuff belongs in there! I don’t know what half of these API calls arefor.”
“好吧,” 他们说,“看看这个功能。它有两页长!这些东西都不属于那里!我不知道这些 API 调用的一半是干什么用的。

Before Borland’s new spreadsheet for Windows shipped, Philippe Kahn, thecolorful founder of Borland, was quoted a lot in the press braggingabout how Quattro Pro would be much better than Microsoft Excel, because it was written from scratch. All new source code! As if source coderusted.
在 Borland 的新 Windows 电子表格发布之前,Borland 的创始人 Philippe Kahn 经常被媒体引用,吹嘘 Quattro Pro 如何比 Microsoft Excel 好得多,因为它是从头开始编写的。全新源代码!好像源代码生锈了

The idea that new code is better than old is patently absurd. Old code has beenused. It has beentested.Lotsof bugs have been found, and they’ve beenfixed. There’s nothing wrong with it. It doesn’t acquire bugs just by sittingaround on your hard drive. Au contraire, baby! Is software supposed tobe like an old Dodge Dart, that rusts just sitting in the garage? Issoftware like a teddy bear that’s kind of gross if it’s not made out ofall new material?
认为新代码比旧代码更好的想法显然是荒谬的。已使用旧代码。它已经过测试。已经发现了许多错误,并且已经修复了它们。这没有错。它不会仅仅通过坐在硬盘上来获取错误。Au contraire,宝贝!软件是否应该像一辆旧的道奇飞镖一样,只是坐在车库里生锈?软件就像泰迪熊一样,如果不是用所有新材料制成的,那就有点恶心了吗?

Back to that two page function. Yes, I know, it’s just a simple function todisplay a window, but it has grown little hairs and stuff on it andnobody knows why. Well, I’ll tell you why: those are bug fixes. One ofthem fixes that bug that Nancy had when she tried to install the thingon a computer that didn’t have Internet Explorer. Another one fixes that bug that occurs in low memory conditions. Another one fixes that bugthat occurred when the file is on a floppy disk and the user yanks outthe disk in the middle. That LoadLibrary call is ugly but it makes thecode work on old versions of Windows 95.
回到那两页功能。是的,我知道,这只是一个显示窗口的简单功能,但它在上面长出了小毛发和东西,没有人知道为什么。好吧,我会告诉你为什么:这些是错误修复。其中一个修复了 Nancy 尝试在没有 Internet Explorer 的计算机上安装该东西时遇到的错误。另一个修复了在内存不足条件下发生的错误。另一个修复了当文件位于软盘上并且用户在中间拉出磁盘时发生的错误。该 LoadLibrary 调用很丑陋,但它使代码在旧版本的 Windows 95 上工作。

Each of these bugs took weeks of real-world usage before they were found.The programmer might have spent a couple of days reproducing the bug inthe lab and fixing it. If it’s like a lot of bugs, the fix might be oneline of code, or it might even be a couple of characters, but a lot ofwork and time went into those two characters.
这些错误中的每一个都需要数周的实际使用才能被发现。程序员可能花了几天时间在实验室中重现错误并修复它。如果它像很多错误一样,修复可能是一行代码,甚至可能是几个字符,但这两个字符投入了大量的工作和时间。

When you throw away code and start from scratch, you are throwing away allthat knowledge. All those collected bug fixes. Years of programmingwork.
当你扔掉代码并从头开始时,你就是在抛弃所有的知识。所有这些收集的错误修复。多年的编程工作。

You are throwing away your market leadership. You are giving a gift of twoor three years to your competitors, and believe me, that is alongtime in software years.
你正在抛弃你的市场领导地位。你给你的竞争对手送了两三年的礼物,相信我,这在软件年代是很长的时间。

You are putting yourself in an extremely dangerous position where you willbe shipping an old version of the code for several years, completelyunable to make any strategic changes or react to new features that themarket demands, because you don’t have shippable code. You might as well just close for business for the duration.
你把自己置于一个极其危险的境地,你将发布一个旧版本的代码好几年,完全无法做出任何战略改变或对市场要求的新功能做出反应,因为你没有可交付的代码。您还不如在这段时间内关闭业务。

You are wasting an outlandish amount of money writing code that already exists.
你浪费了一大笔钱来编写已经存在的代码。

Is there an alternative? The consensus seems to be that the old Netscape code base wasreallybad. Well, it might have been bad, but, you know what? It worked pretty darn well on an awful lot of real world computer systems.
有其他选择吗?人们的共识似乎是,旧的 Netscape 代码库真的很糟糕。好吧,这可能很糟糕,但是,你知道吗?它在很多现实世界的计算机系统上运行得非常好。

When programmers say that their code is a holy mess (as they always do), there are three kinds of things that are wrong with it.
当程序员说他们的代码是一团糟时(就像他们一直做的那样),有三种事情是错误的。

First, there are architectural problems. The code is not factored correctly.The networking code is popping up its own dialog boxes from the middleof nowhere; this should have been handled in the UI code. These problems can be solved, one at a time, by carefully moving code, refactoring,changing interfaces. They can be done by one programmer workingcarefully and checking in his changes all at once, so that nobody elseis disrupted. Even fairly major architectural changes can be donewithoutthrowing away the code. On the Juno project we spentseveral months rearchitecting at one point: just moving things around,cleaning them up, creating base classes that made sense, and creatingsharp interfaces between the modules. But we did it carefully, with ourexisting code base, and we didn’t introduce new bugs or throw awayworking code.
首先,存在架构问题。代码分解不正确。网络代码不知从何而来地弹出了自己的对话框;这应该在 UI 代码中处理。这些问题可以通过仔细移动代码、重构、更改接口来一次解决一个。它们可以由一个程序员仔细工作并一次检查他的更改来完成,这样就不会打扰其他人。即使是相当重大的架构更改也可以在不丢弃代码的情况下完成。在 Juno 项目中,我们花了几个月的时间重新构建:只是移动东西,清理它们,创建有意义的基类,并在模块之间创建清晰的接口。但是我们用现有的代码库小心翼翼地做了这件事,我们没有引入新的错误或丢弃工作代码。

Asecond reason programmers think that their code is a mess is that it isinefficient. The rendering code in Netscape was rumored to be slow. Butthis only affects a small part of the project, which you can optimize or even rewrite. You don’t have to rewrite the whole thing. Whenoptimizing for speed, 1% of the work gets you 99% of the bang.
程序员认为他们的代码一团糟的第二个原因是它效率低下。有传言说 Netscape 中的渲染代码很慢。但这只会影响项目的一小部分,您可以对其进行优化甚至重写。你不必重写整个事情。在优化速度时,1% 的工作可以让您获得 99% 的收益。

Third, the code may be doggone ugly. One project I worked on actually had adata type called a FuckedString. Another project had started out usingthe convention of starting member variables with an underscore, butlater switched to the more standard “m_”. So half the functions startedwith “” and half with “m
第三,代码可能很丑陋。我参与的一个项目实际上有一种叫做 FuckedString 的数据类型。另一个项目开始使用带有下划线的起始成员变量的约定,但后来切换到更标准的 “m
”。所以一半的函数以 “” 开头,一半以 “m” 开头,看起来很丑。坦率地说,这是你在 Emacs 中使用宏在五分钟内解决的问题,而不是从头开始。

It’s important to remember that when you start from scratch there isabsolutely no reasonto believe that you are going to do a better job than you did the first time. First of all, you probably don’t even have the same programmingteam that worked on version one, so you don’t actually have “moreexperience”. You’re just going to make most of the old mistakes again,and introduce some new problems that weren’t in the original version.
重要的是要记住,当你从头开始时,绝对没有理由相信你会比第一次做得更好。首先,你甚至可能没有与第一版相同的编程团队,所以你实际上没有 “更多的经验”。你只会再次犯大部分旧错误,并引入一些原始版本中没有的新问题。

The old mantra build one to throw away is dangerous when applied to large scale commercial applications. Ifyou are writing code experimentally, you may want to rip up the function you wrote last week when you think of a better algorithm. That’s fine.You may want to refactor a class to make it easier to use. That’s fine,too. But throwing away the whole program is a dangerous folly, and ifNetscape actually had some adult supervision with software industryexperience, they might not have shot themselves in the foot so badly.
老的座右铭“先建造一个然后抛弃”在大型商业应用中是危险的。如果你是在实验性编写代码,可能会在下周想到一个更好的算法时废弃掉上周编写的函数。这没问题。你可能想重构一个类以使其更易于使用。这也没问题。但是,抛弃整个程序是危险的愚蠢行为,如果 Netscape 拥有软件行业经验的成熟监督,他们可能不会如此严重地搬起石头砸自己的脚。


via:


不要轻易重头再来,可能的话,尽量优化而非重构 ——《 Things You Should Never Do, Part I》 读后感

[感想]

工程师总是喜欢另起一套东西,一套自己熟悉的东西。对于过往的代码,往往会因为可读性差而决定放弃使用他们,更宁愿重头再来重写一份代码。

其实很能理解。代码在长久的维护过程中,慢慢腐坏变得越来越丑陋是司空见惯的事,对于这样的代码,团队的老成员都会头皮发麻,团队新手进来就更难以溶入了。非常多的工程师在代码维护成本高达至不堪忍受时,都会想要放弃原代码从头再来。

还真是。我在接手站长天下代码,要将其改成淘宝外店时,也是觉得 “非重构不可,我宁可从头再来”。接手 web 聊天室时,我、纯华、葛岩都非常想要重构,想要一份自己能够 “掌控” 的代码,而不是基于别人的代码进行维护。

代码为什么会变得越来越丑陋呢?前期构思不充分固然是可能的原因之一,但更大的可能性是因为有太多意料外的需求和 bug。这不是前期构思不充分的错,前期不可能预测至所有的问题,只要主体架构搭得合理,前期的构思就算是成功的,小的细节问题是一定会存在的。这些细节问题只能在开发过程中发现,别无他法。每发现一个这种问题,就必须打一次补丁,补丁多了,代码就变得可读性越来越差。一直到那个无法再忍受的临界值达到为止,代码会越来越腐坏。然后终于要开始重构了,重头再来。

就像 Joel Spolsky 说的,重构的成本是非常大的。如果重构的团队还是之前那支团队,那还相对较好,如果团队是支新的团队,那效果未必好。你无法保证你的代码不会犯过去团队相同的错误,你在选择重构时,丢弃了过往的累赘,但也丢弃了过往的经验。尽管过去的代码十分丑陋,但他们确确实实可以正常地在运转。如果要重构,你能保证你的代码不会像过去团队的代码一样,慢慢变丑陋吗?你面临同样的风险,而且,过去团队已经付出的时间成本,你还要再次付出一遍。

没有人愿意写出难看的代码,你们团队的实力也不一定会比过去团队更高明。在项目初期,大家的愿望一定都是好的,代码的组织结构也一定是满意的。在长期的维护过程中,意料外的事件是不可避免的。重要的不是重构,重要的是如何防止代码腐坏!这是最治本的,永远别说 “稍后再来修改它”,因为 “稍后” 等于 “永远”,代码就是在一次次这样的 “稍后” 中腐坏的。马丁说得对,永远要保证 “签出” 的代码比 “签入” 时更干净、更整洁。永远保证你的代码是高品质的输入,不要留任何坏的东西在代码库里,它会像破窗户原理一样,让你的代码越来越破,永远别打破那第一扇窗户。

如果代码已经很乱了,怎么办?重写吗?如果你的团队还是之前那支团队,你们已经有第一版的 “经验” 在了,我觉得是可以去重构的,这个风险在可控范围。但如果之前版本并非由你们来做,那么不要去重构,而是去读他们的代码,再难读也要去读,然后再去优化他们,而非重构。

也就是说,如果项目要交接给其他团队,不要寄希望于日后的新团队的重构,那成本实在太大。应该在交接的时候,交接清楚,我知道如果开发周期长,这个交接的过程并不容易,但即使如此,这个成本也是最小的,交接的过程不妨给长一些,保证质量。


via:


篇外

为什么祖传代码被称为「屎山」?

说一个亲身经历的一座「屎山」,曾入职一家成立 15 年的软件公司,我当时应聘的是中级程序员,但在入职几个月后,我的岗级和薪资调整到了高级程序员,这并不是因为我在这几个月中技术水平跨越式提升,而是因为这三个月中发生了以下事情:

  • 前任组员一号和我完成交接之后跑路了!
  • 前任组长和新招来的组长交接之后跑路了!
  • 前任组员二号和新招来的初级程序员交接之后跑路了!
  • 新任组长和我交接之后跑路了!
  • 新人组员(女)在工位掩面痛哭之后,换组了!(捂着脸掉眼泪不发出声音的那种哭)

组内人手严重不足,我白天解决生产 bug,晚上写新需求!

这是一座年轻的「屎山」,我是第三批接手者,历时几个月后我成了项目组中,资历最老的员工!实习生和初级程序员写出来的 bug 和低级错误我就忍了,都是从菜鸟过来的,勉强可以理解。

但是框架因为 “高程”、“架构组”、“大手子” 等人的填填补补,已经到了严重影响用户体验的程度!!!

For example!当时项目的工作流很奇葩,不论出现什么错误,都会统一提示 “发生未知错误”。哪怕我照着 “公司祖传框架使用手册”,在配置中填写「核算系统接口调用失败」、「当前时间不允许操作」等提示信息,客户用的时候还是统一提示 “发生未知错误”!

起初因为运维人员每天都在帮客户解决这种问题,客户倒是没有多大的怨气。某一天,因为很复杂的原因,客户为了此事大发雷霆,我被要求解决这个问题。

在一顿忙碌之后,问题定位到了一个公司自己封装的 jar 包,反编译后发现里面的逻辑有问题。我就联系外地的架构组,让他们给我一个新的 jar 包,第二天我收到了回复:“这个框架很早就重构了,公司新框架不兼容老框架,使用老框架的项目都交给项目组自己维护了,你们项目组的框架应该是 xxx 在维护。”

xxx 是一个很陌生的名字,几番打听之后才知道,xxx 是我们组的第一任组长,离职两年多了!我只能在 svn 上继续摸索,愣是没有找到 jar 包的源码。几经波折之后才知道,svn 之前是几个外包厂商共用的,后来因为外包厂商多了,就给每个厂商重新配置了一个 svn,迁移的时候这个 jar 包的源码因为没有厂商认领,就被丢到了公用的 svn 上。

然而故事并没有结束,从公用 svn 找到的源码,和我通过反编译出来的代码,很多地方对不上!源码里的注释在我眼中都变成了「年轻人,千万不要动这坨屎!」

最后我只能在工作流外面,又封装了一套组件,专门用于代替工作流提示信息,并且留下了一行注释「如果你不幸看到了这行注释,不要怪我,我也不想的!」


via:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值