简易二元性





5.00/5 (5投票s)
一篇关于“轻松”并非总是好事可能的煽动性文章。
背景
不久前,我开始写一篇关于一个乌托邦世界的文章,在那里计算机程序没有 bug(至少在生产环境中没有),以及我们如何实现这一点。
我从未真正认为那篇文章值得发表,但这个想法从未离开我的脑海,所以我重新思考了整个主题,我想到的是,轻松是所有罪恶的根源。好吧,不是全部,但大部分。
所以,我决定写下来。至少我认为这篇文章会比之前的文章更具焦点。
轻松的利弊
即使这里的一些论点可能适用于编程和计算机世界之外,在本文中,我只打算讨论轻松对程序的影响,或者更确切地说,对最终编写代码的程序员的影响。
那么,让我们先来看看证明轻松是件好事(这是最自然的想法)的论点。
- 开发人员可以专注于真正的问题(通常是业务逻辑),而不是在复杂的算法上浪费时间;
- 易读的代码意味着“任何人”(考虑到开发者)阅读它都能理解;
- 易于编写的代码意味着经理可以用更小的预算完成他们的项目,无论是因为他们可以雇用技能较低的开发人员,还是因为 bug 会更少;
- 轻松通常意味着更快,在任何领域中抢先一步通常会带来竞争优势。
有了这些好论点,还有什么会出错呢?
我的初步答案是:很多事情。让我们看一些例子。
- 随着轻松而来的是懒惰。优秀的开发人员可以生产更多,但大多数开发人员只会生产相同数量,因为他们只会理解完成工作所需的最低限度;
- 并非所有东西都能以相同的比例变得更容易,因此,以前能够解决难题的开发人员现在只能解决中等问题,他们不再能够解决那些仍然可能出现的难题;
- 由于“容易”,更多的人会开始做这件事。在这种情况下,许多不喜欢编程且对此不感兴趣的人能够编写低质量的软件,并认为自己做得很好。之后,他们(或雇佣这些人的人)将花费更多的钱来“优化”一些一开始就出错的东西;
- “简单且错误”可能会变得如此出名,以至于你,这位优秀的开发者,可能被迫将事情做得“像那个东西一样简单”,即使你知道所有可能随之而来的问题。
还没有被说服吗?
让我们看一个例子:SQL 注入。
今天,众所周知,我们永远不应该直接连接字符串来为 SQL 提供参数。也就是说,而不是写
"SELECT SomeField FROM SomeTable WHERE SomeOtherField=" + stringVariableThatContainsAnUserValue
我们应该使用参数占位符。类似
"SELECT SomeField FROM SomeTable WHERE SomeOtherField=@SomeOtherField"
然后我们用用户的值填充那个“@SomeOtherField”。
但是,尤其是在旧的数据访问库(包括 ADO.NET)中,填充此类参数并不容易。一个可以用单行命令(如直接执行)执行的命令变成了一个多语句命令,它
- 需要创建一个命令对象;
- 需要填充命令文本(即带有占位符的 SQL);
- 需要创建参数对象(“填充”参数的对象)或以某种方式找到它们;
- 需要填充这些参数;
- 可能需要指明何时参数为空以及它们的类型应为何;
- 执行命令;
- 也许会删除/关闭该命令对象。
这更难,不是吗?
但是,通过执行更难的版本,我们不会有任何 SQL 注入的风险。任何在正常连接中会导致 SQL 注入的字符串都将简单地传递给参数,可能因为期望值为 int 而字符串值为无效字符串而抛出异常,或者在数据库中插入一个奇怪的值,但绝对不会遭受 SQL 注入的攻击。
那么,如果 SQL 数据库根本不接受 SQL 语句中的直接值,可以节省多少钱?
我不是在质疑事情发生的顺序(也许在 SQL 中编写值是第一种做事方式,后来创建参数是为了避免解析文本……我真的不知道),但是这种值解析仍然在 SQL 数据库中进行(并且预计如此),并且它造成了大量的损失,仅仅是因为存在这种参数占位符的“简单替代方法”,许多新开发者(甚至是一些老但懒惰的开发者)会使用它,因为这只需要写更少的代码,并且在正常测试中,它是有效的。
想要更多例子吗?
好的,我现在看起来可能像个 SQL 仇恨者,但我还是会谈论 SQL。你觉得像
- 级联删除?
- 不带 WHERE 子句的更新或删除?
- 甚至更好,把这些与自动提交结合起来呢?
如果你是 SQL 专家,你可能认为任何不带 WHERE
的更新或删除操作都是愚蠢的,但这甚至会发生在有经验的开发人员和数据库管理员身上。
为了弥补这一点,一些编辑器试图通过自动提交来帮助我们节省编写提交语句的时间。但是,考虑到可能产生的问题,这值得吗?
那么,为什么不强制执行这些呢?如果我们真的需要更新或删除所有记录,我们可以总是使用“WHERE ALL”、“WHERE TRUE”甚至奇怪但功能性的条件,如“WHERE 0=0”。
好吧。我不指望 SQL 现在会纠正这种行为(这就是为什么我最初考虑乌托邦世界),但这些都是值得思考的例子。
垃圾回收
我一直在过度批评 SQL。那么,垃圾回收呢?
我个人很喜欢它。它通过移除大量的 try/finally 子句来执行删除,极大地简化了程序,并消除了许多复杂性,尤其是对于循环引用或添加到我们无法控制的列表中的对象。
那么,什么可能出错呢?
还记得我之前谈到的程序员的懒惰吗?好吧,它更进一步,许多新开发者根本不明白管理内存为什么重要。
如果问题仅限于新程序员,他们最终学会了如何处理内存,那还不算太糟,但即使有垃圾回收,我们仍然应该注意内存,无论是通过使用 Dispose()
来立即释放非托管资源,还是通过取消注册事件(这是最难发现的 bug 之一,因为当应用程序很小时通常不会影响它们,但在非常繁忙的服务器上会成为一个问题),而垃圾回收的轻松导致了一种错觉,即开发人员根本不应该关心内存。
更糟糕的是,我听不止一位 .NET 架构师说,我们永远不应该处置对象,.NET 应该负责内存,而不是我们(显然他们读到了我们不应该调用 GC.Collect()
,并且认为 Dispose()
是不好的)。但让我印象深刻的是,我在巴西和加拿大都听到了这种“信息”(起初我以为这只是一个巴西问题,可能与一些错误的翻译有关,但当我在加拿大听到同样的信息被用作开发指南,并且听到不得不 Dispose()
可处置对象是纠正与未释放资源相关的问题的不可接受的更改时,我无语了)。
即使你从未听说过这种说法,让我印象深刻的是,当我开始编程时,知道如何处理指针和如何释放内存是能够编写计算机程序最基本概念之一。今天,似乎谈论释放内存或使用单个指针是一个仅限于疯狂天才的过于高级的话题,因为即使是“资深”开发人员也不知道如何做。
越难越好吗?
阅读“轻松可能不好”可能会让人觉得我是在说代码越难越好。
嗯,我不是说轻松的事情完全不好。我只是说它可能会带来问题。
如果我们只有汇编语言,我敢肯定大型应用程序几乎不存在,许多优秀的开发者在尝试使用计算机之前就会简单地放弃。所以,不,我不是说越难越好。我也不是说拥有轻松的东西是坏事。我所说的是,太轻松可能会带来不好的后果。
事实上,我可以把它比作一场游戏。当我们玩游戏时,我们期望第一个关卡是容易的,然后是越来越难的关卡。如果所有关卡都像最后一个关卡一样难,大多数人会直接放弃游戏(但那时我们可能会看到一些疯狂或非常熟练的玩家会继续玩并从中获得乐趣)。所以,在我看来,编程语言和开发工具应该被这样看待。在学习了“基本”(我不是指 BASIC 语言)之后,开发人员不应该止步于此,至少应该理解最先进的概念,即使他们继续使用更轻松的替代方法。
此外,在开发组件和库时,重要的是要记住,一些“简单的调用”(如没有参数的删除、默认方法参数以及许多代码生成器)可能在某个时刻有所帮助,并且可能带来比它们实际简化得多的问题。
结论或“我为什么写这个?”
我认为这是一篇反思性的文章。我不指望存在了十年的解决方案会一夜之间改变,或者那些开发了这些“轻松解决方案”并最终带来问题的公司会停止这样做。
但是,如果你处于决策位置,请记住,当事情过于轻松时,质量不会真正提高。而且,如果更轻松意味着更容易做坏事,那么最好是做一些稍微困难或啰嗦的事情,并在问题开始咬你之前就将其扼杀在萌芽状态。