您在公司的生产环境中见过的最邪恶或最危险的代码片段是什么?我从未遇到过我认为是故意恶意和邪恶的生产代码,所以我很想知道其他人发现了什么。我见过的最危险的代码是一个存储过程,距离我们的核心生产数据库服务器有两个链接服务器。该存储过程接受任何 NVARCHAR(8000) 参数并通过双跳 sp_executeSQL 命令在目标生产服务器上执行该参数。也就是说,sp_executeSQL 命令执行了另一个 sp_executeSQL 命令,以便跳转两个链接的服务器。哦,链接服务器帐户在目标生产服务器上具有系统管理员权限。 1 我写过我以前在这里和这里工作过的一个应用程序。简单来说,我公司从印度继承了13万行垃圾。该应用程序是用 C# 编写的;这是一个柜员应用程序,当你去银行时,柜员在柜台后面使用的那种软件。该应用程序每天崩溃 40-50 次,而且根本无法重构为可运行的代码。我的公司不得不在 12 个月的时间里重新编写整个应用程序。为什么这个应用程序是邪恶的?因为看到源代码就足以让一个理智的人发疯,一个理智的疯子。用于编写此应用程序的扭曲逻辑可能只是受到洛夫克拉夫特式噩梦的启发。此应用程序的独特功能包括: 在 130,000 行代码中,整个应用程序包含 5 个类(不包括表单文件)。所有这些都是公共静态类。一个类称为 Globals.cs,其中包含 1000 和 1000 和 1000 个公共静态变量,用于保存应用程序的整个状态。这五个类总共包含 20,000 行代码,其余代码嵌入在表单中。你一定想知道,程序员是如何在没有任何类的情况下编写如此大的应用程序的?他们用什么来表示他们的数据对象?事实证明,程序员仅仅通过组合 ArrayLists、HashTables 和 DataTables 就成功地重新发明了我们都学到的关于 OOP 的一半概念。我们看到了很多这样的:
请记住,上面的数据结构都不是强类型的,因此您必须将列表中的任何神秘对象转换为正确的类型。仅使用 ArrayLists、HashTables 和 DataTables 就可以创建什么样的复杂的、类似于 Rube Goldberg 的数据结构,真是令人惊奇。要分享如何使用上面详述的对象模型的示例,请考虑 Accounts:原始程序员为帐户的每个可理解的属性创建了一个单独的 HashTable:一个名为 hstAcctExists、hstAcctNeedsOverride、hstAcctFirstName 的 HashTable。所有这些哈希表的键都是“|”分隔的字符串。可以想到的键包括“123456|DDA”、“24100|SVG”、“100|LNS”等。由于整个应用程序的状态很容易从全局变量中访问,程序员发现没有必要将参数传递给方法。我会说 90% 的方法采用 0 参数。在少数这样做的情况下,为了方便起见,所有参数都作为字符串传递,而不管字符串代表什么。没有副作用的功能不存在。每个方法都修改了 Globals 类中的 1 个或多个变量。并非所有的副作用都有意义;例如,其中一种表单验证方法有一个神秘的副作用,即为存储 Globals.lngAcctNum 的任何帐户计算贷款的超额和短额付款。尽管有很多形式,但只有一种形式可以统治它们:frmMain.cs,其中包含高达 20,000 行的代码。 frmMain 做了什么?一切。它查帐、打印收据、发放现金,它什么都做。有时其他形式需要调用 frmMain 上的方法。与其将表单中的代码分解为一个单独的类,为什么不直接调用代码:尽管它已经创建了一个不可见的表单来执行业务逻辑,但您认为表单如何知道要查找哪个帐户?这很简单:表单可以访问 Globals.lngAcctNum 和 Globals.strAcctType。 (谁不喜欢匈牙利符号?)
代码重用是 ctrl-c、ctrl-v 的同义词。我发现 200 行方法在 20 个表单中复制/粘贴。该应用程序有一个奇怪的线程模型,我喜欢称之为线程和计时器模型:每个生成线程的表单都有一个计时器。产生的每个线程都会启动一个有 200 毫秒延迟的计时器;一旦计时器启动,它会检查线程是否设置了一些魔法布尔值,然后它会中止线程。产生的 ThreadAbortException 被吞了。您可能认为这种模式只会看到一次,但我至少在 10 个不同的地方发现了它。说到线程,关键字“锁”从未出现在应用程序中。线程无需锁定即可自由操作全局状态。应用程序中的每个方法都包含一个 try/catch 块。每个异常都被记录并吞下。一些天才发现您可以将多个表单控件连接到同一个事件处理程序。程序员是怎么处理的? private void OperationButton_Click(object sender, EventArgs e){ Button btn = (Button)sender; if (blnModeIsAddMc) { AddMcOperationKeyPress(btn); } else { string strToBeAppendedLater = string.Empty; if (btn.Name != "btnBS") { UpdateText(); } if (txtEdit.Text.Trim() != "Error") { SaveFormState(); } switch (btn.Name) { case "btnC": ResetValues();休息; case "btnCE": txtEdit.Text = "0";休息; case "btnBS": if (!blnStartedNew) { string EditText = txtEdit.Text.Substring(0, txtEdit.Text.Length - 1); DisplayValue((EditText == string.Empty) ? "0" : EditText); } 休息; case "btnPercent": blnAfterOp = true; if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, false); decCurrValue = decResultValue * decCurrValue / intFormatFactor; DisplayValue(GetValueString(decCurrValue)); AddToTape(GetValueString(decCurrValue), string.Empty, true, false); strToBeAppendedLater = GetValueString(decResultValue).PadLeft(20) + strOpPressed.PadRight(3); if (arrLstTapeHist.Count == 0) { arrLstTapeHist.Add(strToBeAppendedLater); blnEqualOccurred = false; blnStartedNew = true; } 休息; case "btnAdd": case "btnSubtract": case "btnMultiply": case "btnDivide": blnAfterOp = true; if (txtEdit.Text.Trim() == "Error") { btnC.PerformClick();返回; } if (blnNumPressed || blnEqualOccurred) { if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { if (Operation()) { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, true); DisplayValue(GetValueString(decResultValue)); } else { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, true); DisplayValue("错误"); strOpPressed = btn.Text; blnEqualOccurred = 假; blnNumPressed = 假; } } else { strOpPressed = btn.Text; AddToTape(GetValueString(0), (string)btn.Text, false, false); } if (txtEdit.Text.Trim() == "Error") { AddToTape("Error", string.Empty, true, true); btnC.PerformClick(); txtEdit.Text = "错误"; } 休息; case "btnEqual": blnAfterOp = false; if (strOpPressed != string.Empty || strPrevOp != string.Empty) { if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { if (OperationEqual()) { DisplayValue(GetValueString(decResultValue)); } else { DisplayValue("错误"); } if (!blnEqualOccurred) { strPrevOp = strOpPressed; decHistValue = decCurrValue; blnNumPressed = 假; blnEqualOccurred = 真; strOpPressed = string.Empty; } } 休息; case "btnSign": GetValueDecimal(txtEdit.Text, out decCurrValue); DisplayValue(GetValueString(-1 * decCurrValue));休息; } }}
strDrCr = chkCredits.Checked && chkDebits.Checked ? string.Empty : chkDebits.Checked ? “D”:chkCredits.Checked ? “C”:“N”; if (strDefaultVals == strNowVals && (dsTranHist == null ? true : dsTranHist.Tables.Count == 0 ? true : dsTranHist.Tables[0].Rows.Count == 0 ? true : false))演示了 StringBuilder 的典型误用。请注意程序员如何在循环中连接字符串,然后将结果字符串附加到 StringBuilder: private string CreateGridString(){ string strTemp = string.Empty; StringBuilder strBuild = new StringBuilder(); foreach (DataGridViewRow dgrRow in dgvAcctHist.Rows) { strTemp = ((DataRowView)dgrRow.DataBoundItem)["Hst_chknum"].ToString().PadLeft(8, ' '); strTemp += " "; strTemp += Convert.ToDateTime(((DataRowView)dgrRow.DataBoundItem)["Hst_trandt"]).ToString("MM/dd/yyyy"); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_DrAmount"].ToString().PadLeft(15, ' '); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_CrAmount"].ToString().PadLeft(15, ' '); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_trancd"].ToString().PadLeft(4, ' '); strTemp += " "; strTemp += GetDescriptionString(((DataRowView)dgrRow.DataBoundItem)["Hst_desc"].ToString(), 30, 62); strBuild.AppendLine(strTemp); } strCreateGridString = strBuild.ToString(); return strCreateGridString;//strBuild.ToString();} 表上不存在主键、索引或外键约束,几乎所有字段都是 varchar(50) 类型,并且 100% 的字段可以为空。有趣的是,位域不用于存储布尔数据;相反,使用了 char(1) 字段,字符“Y”和“N”分别用于表示真和假。 ALTER PROCEDURE [dbo].[Get_TransHist] ( @TellerID int = null, @CashDrawer int = null, @AcctNum bigint = null, @StartDate datetime = null, @EndDate datetime = null, @StartTranAmt decimal(18,2) = null , @EndTranAmt decimal(18,2) = null, @TranCode int = null, @TranType int = null )AS 声明@WhereCond Varchar(1000) 声明@strQuery Varchar(2000) Set @WhereCond = ' ' Set @strQuery = ' ' 如果不是@TellerID 为null Set @WhereCond = @WhereCond + ' AND TT.TellerID = ' + Cast(@TellerID as varchar) 如果不是@CashDrawer 为null Set @WhereCond = @WhereCond + ' AND TT.CDId = ' + Cast(@CashDrawer as varchar) 如果不是 @AcctNum 为 null Set @WhereCond = @WhereCond + ' AND TT.AcctNbr = ' + Cast(@AcctNum as varchar) 如果不是 @StartDate 为 null Set @WhereCond = @WhereCond + ' AND Convert(varchar,TT.PostDate,121) >= ''' + Convert(varchar,@StartDate,121) + '''' 如果不是 @EndDate 为 null Set @WhereCond = @WhereCond + ' AND Convert(varchar,TT .PostDate,121) <= ''' + Convert(varchar,@EndDate,121) + '''' 如果不是@TranCode为 null Set @WhereCond = @WhereCond + ' AND TT.TranCode = ' + Cast(@TranCode as varchar) 如果不是 @EndTranAmt 为 null Set @WhereCond = @WhereCond + ' AND TT.TranAmt <= ' + Cast(@EndTranAmt as varchar) 如果不是 @StartTranAmt 为 null Set @WhereCond = @WhereCond + ' AND TT.TranAmt >= ' + Cast(@StartTranAmt as varchar) 如果不是 (@TranType is null or @TranType = -1) Set @WhereCond = @WhereCond + ' AND TT.DocType = ' + Cast(@TranType as varchar) -- 根据过滤器获取柜员交易记录 Set @strQuery = 'SELECT TT.TranAmt as [Transaction Amount], TT.TranCode as [Transaction Code], RTrim(LTrim(TT.TranDesc)) 作为[交易描述], TT.AcctNbr 作为[账号], TT.TranID 作为[交易编号], Convert(varchar,TT.ActivityDateTime,101) 作为[活动日期] ], Convert(varchar,TT.EffDate,101) 为[生效日期], Convert(varchar,TT.PostDate,101) 为[发布日期], Convert(varchar,TT.ActivityDateTime,108) 为[时间], TT .BatchID, TT.ItemID, isnull(TT.DocumentID, 0) as DocumentID, TT。 TellerName, TT.CDId, TT.ChkNbr, RTrim(LTrim(DT.DocTypeDescr)) as DocTypeDescr, (CASE WHEN TT.TranMode = ''F'' THEN ''Offline'' ELSE ''Online'' END) TranMode, DispensedYN FROM TellerTrans TT WITH (NOLOCK) LEFT OUTER JOIN DocumentTypes DT WITH (NOLOCK) on DocType = DocumentType WHERE IsNull(TT.DeletedYN, 0) = 0 ' + @WhereCond + ' Order By BatchId, TranID, ItemID' Exec (@strQuery) ) 尽管如此,这个 130,000 行应用程序的最大问题是:没有单元测试。
10 令人沮丧的是,在某个地方,一些编写代码的程序员认为他们做得很好,并在他的简历上炫耀。 “不熟练而且不知道它”——塞尔吉奥·阿科斯塔,嘿,你写的这段代码,我认为它很好,如果你认为自己做得更好,你应该尝试——贝斯卡 一些管理历史的淡化版本最终出现在 DailyWTF 上今天:thedailywtf.com/Articles/eTeller-Horror.aspx – Juliet 4 LOL – 这就像用透明信封分发工资单、银行对账单等。 :-) – 克里斯蒂安·海特·约奇。我曾经在商业网络应用程序的数据库中发现了看起来很好散列的密码。原来它们只是存储在 VARBINARY 列中的纯文本,因此您乍一看无法分辨。 - 马特·吉布森说来悲哀,这基本上在我继承了一个项目的密码加密。尽管他们旧的自定义编写的 base64 编码函数做错了,所以我想这有什么好处 ;-) – Allbite 在一个接受信用卡付款的系统中,我们过去常常存储完整的信用卡号码以及姓名、到期日期等。
事实证明这是非法的,鉴于我们当时正在为司法部编写程序,这具有讽刺意味。 5 有谁知道亚马逊是如何解决这个问题的?或者如果您要求用户许可是否合法? – Davy Landman @Davy – 加密。如果它是加密的并且只能在需要知道的情况下访问,则存储是合法的。有很多关于强度、保留、DMZ 等的规则,请参阅此处 pcisecuritystandards.org/security_standards/pci_dss.shtml – Luke Schafer 5 好消息是有一个 FIXME,因此 IDE 可以将您引导至该行。 – Josh Lee @Chadworthington:如果是故意的,评论应该是 /* DON'T FIXME! */ ;P – David 在构建商业版本时,这种东西不是被编译器优化了吗? – Attila Kun 在这种情况下,编译器没有“优化”循环;它会“优化”什么?此外,“故意破坏”是一个确定的可能性。 “FIXME”可能是为了否认。 – 杜尔高拱门
(在我意识到它们不是同一个变量之前,我真的花了一个小时试图弄清楚它是如何工作的)包含 50 个文件,每个文件包含 50 个文件,并且内容以有条件和不可预测的方式在所有 50 个文件中线性/程序地执行.现在考虑 $x 包含来自您的 URL 的值( register globals magic ),因此在您的代码中没有任何地方很明显您使用的是哪个变量,因为它全部由 url 决定。现在考虑当该变量的内容可以是网站用户指定的 url 时会发生什么。是的,这对您来说可能没有意义,但它会创建一个名为该 url 的变量,即:除了不能直接访问之外,您必须通过上面的双 $ 技术使用它。此外,当用户可以在 URL 上指定一个变量来指示要包含哪个文件时,有一些讨厌的技巧,例如“evilcode.php”打印其代码明文,并且 Php 没有得到适当的保护,php 只会被淘汰,下载 evilcode.php,并以 Web 服务器的用户身份执行它。
网络服务器会给它所有的权限等等,允许 shell 调用,下载任意二进制文件并运行它们等等,直到最终你想知道为什么你的盒子用完了磁盘空间,一个目录有 8GB 的盗版电影意大利语配音,通过机器人在 IRC 上共享。我很庆幸我在运行攻击的脚本决定做一些非常危险的事情之前发现了暴行,比如从或多或少不安全的数据库中获取极其机密的信息:| (我可以用那个代码库在 6 个月的时间里每天娱乐 Dailywtf,我没有骗你。很遗憾我在我逃脱了那个代码之后发现了 Dailywtf) 3 “我很庆幸我在脚本决定之前发现了暴行收获数据库:|"你怎么知道的?对于所有密集的海豚,它可能已经在没有人注意到的情况下这样做了...... – Piskvor 离开了建筑物 它可能已经这样做了,但数据库日志并没有表明它做了多少。 – Kent Fredric 在主项目头文件中,来自一位老手 COBOL 程序员,他莫名其妙地用 C 编写了一个编译器:“所以如果你忘记声明你的循环变量,你就不会得到编译器错误。”
0 3 只要您使用正确的工具来构建包 (WiX),这还不错。然而,VS 编辑器和 InstallShield 是邪恶的——erikkallen 这篇文章如何编写不可维护的代码涵盖了一些人类已知的最出色的技术。我最喜欢的一些是: 购买一本婴儿命名书,您永远不会对可变名称感到茫然。 Fred 是一个美妙的名字,而且很容易打字。如果您正在寻找易于输入的变量名称,如果您使用 DSK 键盘输入,请尝试使用 adsf 或 aoeu。如果您必须使用描述性变量和函数名称,请拼错它们。通过在某些函数和变量名称中拼错并在其他中正确拼写(例如 SetPintleOpening SetPintalClosing),我们有效地否定了 grep 或 IDE 搜索技术的使用。它工作得非常好。通过在不同的剧院/剧院拼写 tory 或 tori 来添加国际风味。在命名函数和变量时,大量使用抽象词,如 it、everything、data、handle、stuff、do、routine、perform 和数字,例如routineX48、PerformDataFunction、DoIt、HandleStuff 和 do_args_method。将单词中间的音节的第一个字母随机大写。例如 ComputeRasterHistoGram()。
使用小写 l 表示长常量。例如,10l 更可能被误认为是 101,而不是 10l。禁止任何明显消除 uvw wW gq9 2z 5s il17|!j oO08 `'" ;,. m nn rn {[()]} 歧义的字体。要有创意。只要范围规则允许,重复使用现有的不相关变量名称。类似地,使用相同的临时变量用于两个不相关的目的(声称是为了节省堆栈槽)。对于一个恶魔变体,将变量变形,例如,在很长的方法顶部为变量赋值,然后在中间的某处更改以微妙的方式了解变量的含义,例如将其从基于 0 的坐标转换为基于 1 的坐标。一定不要记录这种含义的变化。在变量或方法名称中使用缩写时,用同一个词的几种变体,甚至偶尔拼写出来。这有助于打败那些使用文本搜索只理解程序的某些方面的懒惰的流浪汉。考虑将变体拼写作为策略的变体,例如混合国际颜色, 带有美国色彩和花花公子的酷乐rz。如果您完整地拼出姓名,则每个姓名只有一种可能的拼写方式。这些对于维护程序员来说太容易记住了。因为有很多不同的方式来缩写一个词,通过缩写,你可以有几个不同的变量,它们都具有相同的明显目的。作为额外的好处,维护程序员甚至可能不会注意到它们是单独的变量。使用诸如 LancelotsFavouriteColour 之类的常量名称而不是蓝色,并将其指定为 $0204FB 的十六进制值。颜色看起来不错......