你必须维持的最不健全的计划是什么?

时间:2008-10-18 10:03:36

标签: anti-patterns

我经常被要求对由真正的火箭外科医生建造的系统进行维护工作。这有很多错误,很难知道从哪里开始。

不,等等,我将从一开始就开始:在项目的早期阶段,设计师被告知系统需要扩展,他会读到可扩展性问题的根源是应用程序和数据库服务器,所以他确保最小化此流量。怎么样?通过将所有应用程序逻辑放在SQL Server存储过程中。

严重。大量的应用程序由HTML前端制定XML消息。当中间层接收XML消息时,它使用文档元素的标记名作为它应调用的存储过程的名称,并调用SP,将整个XML消息作为参数传递给它。它接收SP返回的XML消息并将其直接返回到前端。 应用程序层中没有其他逻辑。

中间层中的一些代码来验证针对模式库的传入XML消息。但是在确定1)之后我删除了它,只有少数消息具有相应的模式,2)消息实际上并不符合这些模式,3)在验证消息后,如果遇到任何错误,该方法将其丢弃。 “这款保险丝盒可以节省时间 - 它来自工厂预装的便士!”

我之前看到过做错事的软件。很多。我写了很多。但我从来没有见过任何喜欢这种狡猾的决心做错事,在每一个可能的转弯,这都体现在这个系统的设计和编程中。 / p> 好吧,至少他跟他所知道的一样,对吧?嗯。显然,他所知道的是Access。他并没有真正理解 Access。或数据库。

以下是此代码中的常见模式:

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

好的,好的。您也不信任查询优化器。但是这个怎么样? (最初,我打算在What's the best comment in source code you have ever encountered?发表这篇文章,但我意识到除了这一条评论之外还有更多内容要写,而且事情已经失控。)在许多实用程序结束时存储过程,您将看到如下所示的代码:

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

是的,那段代码完全是你不能让自己相信它正在做的事情,以免你被激怒。如果变量包含NULL,则他通过将其值更改为-9999来警告调用者。以下是这个数字的常用方法:

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

真。

有关此系统的另一个方面,请参阅日常网站上标题为I Think I'll Call Them "Transactions"的文章。我没有做任何这个。我发誓。

当我在这个系统上工作时,我常常想起沃尔夫冈·泡利对一名学生的着名回应:“这是不对的。这甚至都没有错。”

这不是真正有史以来最糟糕的节目。这绝对是我在整个30年(yikes)职业生涯中所做过的最糟糕的一次。但我还没有看到一切。你看到了什么?

14 个答案:

答案 0 :(得分:41)

我曾尝试编写MP3解码器。它不起作用。

答案 1 :(得分: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 hackery保持兼容。

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

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

答案 2 :(得分:12)

  

你必须维持的最不健全的计划是什么?

我写过的所有内容!

严重。我阅读的博客越多,收听播客,关注这样的网站,我每天学的越多。每天我基本上都意识到我昨天所写的一切在某些方面都是错误的。我觉得那些在我职业生涯早期保留着我所写的东西的可怜的小傻瓜。

答案 3 :(得分:6)

我刚刚开始的那个。

  1. 无源控制。
  2. 所有来源均已实时修改。为了阻止错误,有像db-access.php.070821这样的备份文件乱丢源树。
  3. 代码异常脆弱 - 错误检查的方式很少,如果确实没有回落。

答案 4 :(得分:5)

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

然后我读了Roedy Green's essay并笑了很多,直到我意识到我发现它很有趣的原因是我从我维护的代码中认识到了大部分的例子。 (这篇文章在多年的添加中变得有点臃肿,但它仍然值得一看。)

答案 5 :(得分:2)

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

然而,最糟糕的是,在我出生之前(我出生于1967年)维持着COBOL程序,其独有的流量控制方法是GOTO。这是一个绝对的混乱,无法遵循。对变量类型的微小更改可能需要数天才能解决。没有自动化测试,也没有保存手动测试计划,因此每次更改都需要写出新的手动测试计划,然后详尽地说明,然后输入代码。

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

答案 6 :(得分:2)

在朗讯的研究生院,我获得了一个编译器和口译员来维护,用PL / I编写。正在编译的语言描述了一组复杂的完整性约束,并且解释器针对大型数据集运行这些约束,该数据集稍后将形成控制4ESS开关的初始数据库。 4ESS过去是,现在仍然是用于长距离语音通信的电路交换机。

代码是混乱的。有一个分支标签称为“NORTH40”。我问原始开发者它的意思。

“这就是范围检查完成的地方,你知道,检查确保每个字段都有正确的值。”

“但为什么'NORTH40'?”

“你知道,'家,在范围内的家。'”

“啊?”

原来'NORTH40'意味着一个农场的北面40英亩,在他的城市繁殖的心灵与牧场有着模糊的联系。

另一个模块有两个名为TORY和DIREC的并行数组,它们是并行更新的,因此显然是误导性地尝试对包含数据对的单个数组进行建模。我无法弄清楚这些名字并问开发商。原来他们本来应该一起阅读:“直接”。大。

那些必须编写完整性约束的穷人没有用户手册来指导他们,只是口头传统和手写笔记。更糟糕的是,编译器没有进行语法检查,并且通常他们最终指定了一个静默映射到错误逻辑的约束,通常会产生非常糟糕的后果。

更糟的是。当我深入研究解释器的内容时​​,我发现它是围绕一个巨大的排序过程构建的。排序之前是一个输​​入过程,它从原始数据和完整性约束生成排序输入数据。因此,如果您有一个包含5,000个主干定义的表,并且每个主干记录有三个必须在整个输入数据集中唯一的字段值,则输入过程将创建3 * 5,000 = 15,000个排序输入记录,每个记录都是原始的数据记录以完整性约束号为前缀,以及要排序的字段值的副本。它没有进行三次5,000记录排序,而是进行了一次15,000记录排序。当你考虑到数百个表内和表间完整性约束以及一些非常大的表时,你就会遇到组合噩梦。

我能够重构一下,记录语言,并使用可理解的错误消息添加语法检查,但几个月后转移到新组。

答案 7 :(得分:1)

我正在维护我们在Intranet中使用的调度Web应用程序。当我被问到是否可以从我认为的调度程序中删除代理时,请确定原因。 当我查看源代码时,我发现这个代理当天的每个小时都是单独编码的。那一周的每一天都是如此。每个地区的每个代理人都是如此。大约5个地区的每个地区也是如此。 持有asp代码的Html fies。

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

但是这个号码很快让我的经理相信我们需要一个新的调度应用程序。

答案 8 :(得分:1)

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

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

答案 9 :(得分:1)

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

ON ERROR RESUME

JMM

答案 10 :(得分:0)

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

每个功能都会复制并粘贴到每个ASP页面中。因此,没有任何功能定义或任何...每天我都被他们的环境瘫痪,因为我首先要远程服务器,绕过DMZ。之后我必须远程进入生产服务器,我必须在那里进行更改。

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

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

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