24

I periodically am called upon to do maintenance work on a system that was built by a real rocket surgeon. There's so much wrong with it that it's hard to know where to start.

No, wait, I'll start at the beginning: in the early days of the project, the designer was told that the system would need to scale, and he'd read that a source of scalability problems was traffic between the application and database servers, so he made sure to minimize this traffic. How? By putting all of the application logic in SQL Server stored procedures.

Seriously. The great bulk of the application functions by the HTML front end formulating XML messages. When the middle tier receives an XML message, it uses the document element's tag name as the name of the stored procedure it should call, and calls the SP, passing it the entire XML message as a parameter. It takes the XML message that the SP returns and returns it directly back to the front end. There is no other logic in the application tier.

(There was some code in the middle tier to validate the incoming XML messages against a library of schemas. But I removed it, after ascertaining that 1) only a small handful of messages had corresponding schemas, 2) the messages didn't actually conform to these schemas, and 3) after validating the messages, if any errors were encountered, the method discarded them. "This fuse box is a real time-saver - it comes from the factory with pennies pre-installed!")

I've seen software that does the wrong thing before. Lots of it. I've written quite a bit. But I've never seen anything like the steely-eyed determination to do the wrong thing, at every possible turn, that's embodied in the design and programming of this system.

Well, at least he went with what he knew, right? Um. Apparently, what he knew was Access. And he didn't really understand Access. Or databases.

Here's a common pattern in this code:

SELECT @TestCodeID FROM TestCode WHERE TestCode = @TestCode

SELECT @CountryID FROM Country WHERE CountryAbbr = @CountryAbbr

SELECT Invoice.*, TestCode.*, Country.*
   FROM Invoice
   JOIN TestCode ON Invoice.TestCodeID = TestCode.ID
   JOIN Country ON Invoice.CountryID = Country.ID
   WHERE Invoice.TestCodeID = @TestCodeID AND Invoice.CountryID = @CountryID

Okay, fine. You don't trust the query optimizer either. But how about this? (Originally, I was going to post this in What's the best comment in source code you have ever encountered? but I realized that there was so much more to write about than just this one comment, and things just got out of hand.) At the end of many of the utility stored procedures, you'll see code that looks like the following:

-- Fix NULLs
SET @TargetValue = ISNULL(@TargetValue, -9999)

Yes, that code is doing exactly what you can't allow yourself to believe it's doing lest you be driven mad. If the variable contains a NULL, he's alerting the caller by changing its value to -9999. Here's how this number is commonly used:

-- Get target value
EXEC ap_GetTargetValue @Param1, @Param2, OUTPUT @TargetValue
-- Check target value for NULL value
IF @TargetValue = -9999
    ...

Really.

For another dimension of this system, see the article on thedailywtf.com entitled I Think I'll Call Them "Transactions". I'm not making any of this up. I swear.

I'm often reminded, when I work on this system, of Wolfgang Pauli's famous response to a student: "That isn't right. It isn't even wrong."

This can't really be the very worst program ever. It's definitely the worst one I've worked on in my entire 30-year (yikes) career. But I haven't seen everything. What have you seen?

4

14 回答 14

42

I once tried to write an MP3 decoder. It didn't work.

于 2008-10-18T10:33:32.787 回答
19

我维护了 ExtUtils::MakeMaker。MakeMaker 肯定不是我必须维护的最糟糕的代码。这实际上是一个工程奇迹。然而,正是在这种独特的编码恐怖中,最关键的代码也是最可怕的。

MakeMaker 是大多数 Perl 模块的安装程序。当您运行“Makefile.PL”时,您正在调用 MakeMaker。如果 MakeMaker 中断,Perl 就会中断。Perl 可以在所有东西上运行,因此 MakeMaker 必须在所有东西上运行。当我说一切时,我的意思是一切。每个奇异的 Unix 变体。Windows 95 上。和VMS。是的,VMS。

MakeMaker 是做什么的?Makefile.PL 是一个 Perl 程序,它编写包含 shell 命令的 Makefile,这些命令通常运行 Perl,以构建和安装 Perl 模块。让我重复一遍:它编写 shell 命令来运行 Perl。Perl,替代 shell 脚本的语言。

哦,它还可以编译和链接C代码。它还可以将 Perl 模块静态链接到 perl。哦,它可以管理 RCS 结帐。哦,还有你的发行版的 tarball 和 zip 文件。并做所有其他与安装模块相关的事情。

它必须以便携、向后兼容的方式完成所有这些工作。它必须处理...中的变体和错误

  • make(GNU make、BSD make、nmake、dmake、mms、mmk 等等)
  • Perl
  • 文件系统(如果你认为这没什么大不了的,试试 VMS)
  • C 编译器和链接器

它绝对不会失败,并且必须保持 100% 向后兼容。

哦,它几乎没有真正的扩展 API,所以它必须与人们为了扩展它而必须做的临时 Makefile 保持兼容。

为什么它会做这一切?15 年前,当 Perl 只在 Unix 上运行时,这似乎是个好主意。当你可以只使用 make 时,为什么还要编写一个完整的构建系统?Perl 是一种文本处理语言;我们将使用它来编写 Makefile!

幸运的是,有一个替代品Module::Build,我寄希望于它会迅速杀死 MakeMaker。但是它的吸收速度很慢,而且社区非常抵制这种变化,所以我坚持维护 MakeMaker。

于 2008-10-20T00:56:44.230 回答
12

您必须维护的最不健全的程序是什么?

我曾经写过的一切!

严重地。我阅读博客、收听播客和关注此类网站的次数越多,我每天学到的就越多。每天我基本上都意识到我昨天写的所有东西在某种程度上都是错误的。我很同情那些维护我在职业生涯早期写的东西的可怜虫。

于 2008-10-20T02:04:21.380 回答
7

The one I have just started on.

  1. No Source control.
  2. All source is edited live. To stop mistakes, there are backup files like db-access.php.070821 littering the source tree.
  3. The code is exceptionally brittle - there is very little in the way of error checking and absolutely no fall back if it does.
于 2008-10-18T10:44:16.043 回答
5

我曾经不得不维护一个遗留的 C 应用程序,该应用程序以前是由一些失去编程意愿(可能还活着)的程序员编写和维护的。它有太多的 WTF 可提,但我记得有一个布尔函数,它在各种特殊情况下会返回 TRUE+1、TRUE+2 等。

然后我读了Roedy Green 的文章,笑了很多,直到我意识到我觉得这很有趣的原因是我从我维护的代码中认出了大部分示例。(那篇文章经过多年的补充变得有点臃肿,但仍然值得一看。)

于 2008-10-22T10:04:30.660 回答
2

我曾经是一名 COBOL 程序员(不寒而栗)。我们所有的代码都属于“不健全”的类别。在 COBOL 中,您没有命名空间,所有变量都是全局变量,并且文件名和其他资源有很多强制重复。要调用过程,您需要设置全局变量,调用过程,然后检查这些全局变量(或其他可能被设置的变量)的内容。

然而,最糟糕的是维护一个在我出生(我出生于 1967 年)之前编写的 COBOL 程序,其唯一的流控制方法是 GOTO。这绝对是一团糟,无法遵循。对变量类型的微小更改可能需要几天时间才能完成。没有自动化测试,手动测试计划也从未保存,因此每次更改都需要写出新的手动测试计划,详尽地遵循,并与代码一起上交。

具有讽刺意味的是,这正是 COBOL 如此成功的原因。COBOL 通常由作业控制语言 (JCL) 执行。由于 COBOL 很弱,程序不会做很多事情,所以 JCL 会分配一些磁盘空间(通常到柱面级别),并执行一个小的 COBOL 程序来读取数据,然后只写出您需要的数据。然后 JCL 可能会调用一个排序程序来对结果文件进行排序。然后将调用另一个 COBOL 程序来读取已排序的文件并汇总数据,并可能重新提取所需的结果。并且可能会再次使用 JCL 将文件移动到其他位置,然后调用另一个 COBOL 程序来读取结果并将它们存储在数据库中,等等。每个 COBOL 程序往往只做一件事,并创建了 Unix 管道模型的原始版本——这一切都是因为 COBOL 太难维护或做任何复杂的事情。我们有松散的耦合和紧密的内聚(在程序之间,而不是在它们之间),因为几乎不可能以任何其他方式编写 COBOL。

于 2008-10-22T09:07:25.237 回答
2

Right out of grad school at Lucent I was given a compiler and interpreter to maintain, written in PL/I. The language being compiled described a complex set of integrity constraints, and the interpreter ran those constraints against a large data set that would later be formed into the initial database controlling the 4ESS switch. The 4ESS was, and still is, a circuit-switch for long distance voice traffic.

The code was a jumble. There was a label to branch to called "NORTH40". I asked the original developer what it meant.

"That's where the range checks are done, you know, the checks to make sure each field has a correct value."

"But why 'NORTH40'?"

"You know, 'Home, home on the range.'"

"Huh?"

Turned out 'NORTH40' meant a farm's north 40 acres, which in his city-bred mind had an obscure connection to a cattle ranch.

Another module had two parallel arrays called TORY and DIREC, which were updated in parallel and so were an obviously misguided attempt to model a single array containing pairs of data. I couldn't figure out the names and asked the developer. Turned out they were meant to be read together: "direc-tory". Great.

The poor guys that had to write the integrity constraints had no user manual to guide them, just an oral tradition plus hand-written notes. Worse, the compiler did no syntax-checking, and very often they wound up specifying a constraint that was mapped quietly into the wrong logic, often with very bad consequences.

Worse was to come. As I delved into the bowels of the interpreter, I discovered it was built around a giant sort process. Before the sort was an input process that generated the sort input data from the raw data and the integrity constraints. So if you had a table with 5,000 trunk definitions in it, and each trunk record had three field values that had to be unique across the whole input data set, the input process would create 3 * 5,000 = 15,000 sort input records, each a raw data record prefixed by the integrity constraint number and a copy of the field value to be sorted. Instead of doing three 5,000 record sorts, it did one 15,000 record sort. By the time you factored in hundreds of intra- and inter-table integrity constraints, and some very large tables, you had a combinatorial nightmare.

I was able to refactor a bit, document the language, and add syntax checking with comprehensible error messages, but a few months later jumped at a transfer to a new group.

于 2009-06-01T01:43:45.200 回答
1

我正在维护我们在 Intranet 中使用的调度 Web 应用程序。当我被问到是否可以从调度程序中删除代理时,我想,当然可以。当我查看源代码时,我发现这个代理一天中的每个小时都是单独编码的。他一周中的每一天都是如此。该地区每个代理商的每周都是如此。大约 5 个区域中的每个区域也是如此。Html 文件中到处都是 asp 代码。

有一天,我花了一些时间来猜测这些不同的文件中有多少行代码,我估计大约有 300000。30 万行代码,一次手写然后复制粘贴代码。

但这个数字很快让我的经理相信,我们很快就需要一个新的日程安排应用程序。

于 2008-10-20T01:13:31.450 回答
1

一个 PHP/MySQL 驱动的在线联系人管理系统,其中联系人表没有自然键。有许多数据库字段实例包含分隔字符串形式的复合数据,随后需要由应用程序代码解析。

HTML和逻辑交织在一起,几乎没有使用任何功能,而是将代码剪切并粘贴到数十个源代码文件中。数据未经过清理,因此带有(例如)嵌入垂直选项卡的字段导致 Ajax 调用返回的 XML 出现故障,最重要的是,文件包含数十个(如果不是数百个)由右大括号紧跟分号组成的空语句:“};”

于 2008-11-22T02:59:57.617 回答
1

我曾经做过一个用 BASIC 编写的 CAD 应用程序,公司的政策是每个程序都必须以以下语句开头:

错误恢复

jMM

于 2009-07-13T17:45:04.570 回答
0

CAD/CAM 几何处理语言的解释器(P1 = 10,10; P2 = 20,20; L1 = P1,P2; - 那种东西),用 Microsoft BASIC Professional Development System (PDS) 编写,长度最短变量名(它很快就用完了单个字母,所以转到双字母。PP,PQ​​,PR,有人吗?)。而且,公平地说,一些评论。用意大利语。

有趣的是,它确实有效,并且我能够为其添加一些功能,但这就像业余牙科 - 很痛苦,当然不推荐......

于 2009-01-10T09:27:57.117 回答
0

为雇用开发人员以维护他们以前雇用的特定公司维护 ASP 应用程序......所有这些应用程序都没有记录,也没有任何评论。

每个功能都被复制并粘贴到每个 ASP 页面中。所以没有定义任何功能或任何功能......每天我都被他们的环境所困扰,因为我必须首先远程到服务器,绕过DMZ。之后,我必须远程连接到必须进行更改的生产服务器。

于 2008-10-22T09:16:39.057 回答
0

我曾经写过的任何东西,最初都应该是一个快速的原型,最终会停留一段时间。我的问题领域本质上需要大量一次性原型设计。对于这些原型,有时违反每个最佳实践和良好风格的规则是合理的,如果原型最终值得保留,只需完成它并稍后清理。然而,有时这些原型最终很难正常工作,但最终会成为守护者。在这些情况下,我通常会无限期地推迟重构/重写这个东西,因为我担心我永远不会让它再次工作。进一步削弱我的动力的是,我的老板是一位根本不会编程的领域专家。

于 2008-11-22T02:31:17.253 回答
0

我曾经被要求帮助追踪 EDIF 阅读器中的周期性崩溃。几乎立刻我就开始头疼。原作者似乎觉得Yacc会因为空格而惩罚他,而他的 Yacc 语法是一个密集的、难以阅读的混乱。我花了几个小时对其进行格式化,为出现的缺失终端添加规则,构建声明以避免堆栈增长,瞧,崩溃消失了。

所以请记住,每次等待 Yacc 处理语法时,生成的解析器都会运行数千次。不要对空白便宜!

于 2008-10-24T19:25:34.553 回答