为了提高生产率而愚蠢吗?


112

我已经花了很多时间阅读有关“好的设计”,“设计模式”等的不同书籍。我是SOLID方法的忠实拥护者,每当我需要编写简单的代码时,我都会考虑未来。因此,如果要实现新功能或错误修复,只需添加以下三行代码即可:

if(xxx) {
    doSomething();
}

这并不意味着我会这样做。如果我觉得这段代码在不久的将来可能会变得更大,那么我会考虑添加抽象,将此功能移到其他地方等等。我追求的目标是保持平均复杂度与更改前相同。

我相信,从代码的角度来看,这是一个好主意-我的代码永远不够长,而且很容易理解不同实体的含义,例如类,方法以及类与对象之间的关系。

问题是,这花费了太多时间,而且我经常觉得如果我按原样实现该功能会更好。它仅是“三行代码”与“新接口+两个用于实现该接口的类”。

从产品的角度(当我们谈论结果时),我所做的事情是毫无意义的。我知道,如果我们要开发下一个版本,那么拥有良好的代码真的很棒。但另一方面,您可能花费了使代码“良好”的时间来实现一些有用的功能。

我经常对结果感到不满意-仅能执行A的好代码要比能执行A,B,C和D的不好的代码差。

这种方法是否可能为软件项目带来正的净收益,还是浪费时间?


141
我会看到您的SOLID,然后会和雅格尼(YAGNI)和亲吻(KISS)一起拉西
jk。

16
景气,爆头。
罗伯特·S。

16
“过度工程”的问题有时是需求捕获过程无法正常运行的体现。如果您在“假设”的情况下苦苦挣扎,然后在没有利益相关者/客户互动的情况下自己回答这些问题,那么您将可以预测未来。在将更多复杂性引入代码中之前,也许可以花一些精力并重新投入到对需求的更好理解中?
Angelo

4
也许只有我一个人,但我会认为“愚蠢”使我的客户/雇主花钱购买他们不想要/想要的东西:S
Thomas Bonini 2011年

2
将来在实际需要时添加功能几乎从来不会变得如此困难。您现在做不到任何事情。
Hardwareguy

Answers:


153

只能执行A的好代码比可以执行A,B,C,D的坏代码差。

这对我来说就像是投机性的概括。在不知道(或至少合理确定)您的客户将需要功能B,C和D的情况下,您不必要地使您的设计过于复杂。从长远来看,更复杂的代码很难理解和维护。仅通过有用的额外功能才能证明额外的复杂性。但是,我们通常很难预测未来。我们认为将来可能需要的大多数功能在现实生活中永远不会被要求。

所以:

只能执行A的好代码(但是简单而干净地做一件事)比可以执行A,B,C,D的坏代码(将来可能需要其中的一些代码)要好。


87
+1“我们通常在预测未来非常糟糕”的用户很可能会要求E :)
米哈尔皮亚斯科夫斯基

1
+1无法同意。我最近在一家公司完成了一个工作学期,我认为这是我学到的最重要的一课。
Wipqozn 2011年

4
@Michał,这是一个预测吗?;-)
彼得Török

10
即使他们要求您提供D,他们的思维模式也可能会有所不同,并且会要求一种稍微不同的D,您的抽象不支持这种D ...
Chris Burt-Brown

“如果一个问题没有被完全理解,那么最好根本不提供解决方案”或“除非实现者没有它不能完成一个真实的应用程序,否则不要添加新的功能。” 在这里适用。通过en.wikipedia.org/wiki/X_Window_System#Principles
nwahmaet 2012年

87

轶事时间:

我有两个开发人员为我工作,他们倾向于以这种方式进行过度设计。

对于其中一个人来说,这基本上使他的生产力停了下来,尤其是在启动一个新项目时。最特别的是,如果该项目从本质上来说是相当简单的。最终,我们需要的是一个现在可以运行的软件。这太糟糕了,我不得不让他离开。

另一个容易过度设计的开发人员通过极高的生产力弥补了这一缺陷。这样,尽管有所有多余的代码,他的交付速度仍然比大多数人快。但是,既然他继续前进,我经常会因为需要修改(完全不必要)抽象层等而添加功能所需的额外工作量而感到烦恼。

因此,道理是,过度设计会吃掉多余的时间,而这些时间本可以花在做有用的事情上。不仅是您自己的时间,而且还包括那些必须使用您的代码的人的时间。

所以不要这样

您希望您的代码尽可能简单(而不是更简单)。处理“ maybes”并没有使事情变得更简单,如果您猜错了,您将使代码变得更加复杂,而没有任何实际收获。


10
+1越简单越好,但是再简单不过了。
朱利安

33
“设计师知道他已经实现了完美,不是没有余量可以添加,而是没有余量可以添加。” Antoine de Saint-Exupery
Arkh 2011年

避免像瘟疫一样重复,您不会出错。
凯文(Kevin)

@arkh ...我要使用相同的引用:P
Michael Brown

6
我这样说:每一行代码都有很高的成本。因此,通过最小化代码来最小化成本。删除不必要的代码比编写新代码具有更高的生产率(甚至可能更高)。
克里斯托弗·约翰逊

26

SOLID和KISS / YAGNI原理是相反的。有人会告诉您,如果不能将doSomething()证明是调用它正在执行的类的工作的组成部分,则应该将其放在松散耦合和注入的另一类中。另一个人会告诉您,如果这是一个使用doSomething的地方,那么即使将其提取到方法中也可能是过大了。

这就是使优秀的程序员值得花在金子上的东西。“适当的”结构是逐案的基础,需要了解当前的代码库,程序的未来路径以及程序背后的业务需求。

我喜欢遵循这种简单的三步法。

  • 第一步,使其工作。
  • 在第二遍,使其干净。
  • 在第三遍,将其设为实心。

基本上,这就是将KISS与SOLID混合的方式。当您第一次编写一行代码时,就您所知,这将是一次性的。它只是必须工作,而且没人在乎,所以不要花哨。第二次将光标移到该行代码中时,您已经证明了最初的假设。在重新访问此代码时,您可能会对其进行扩展或从其他地方插入。现在,您应该清理一下;提取常用花絮的方法,减少或消除复制/粘贴编码,添加一些注释等,等等。第三次回到该代码时,它现在是程序执行路径的主要交叉点。现在,您应该将其视为程序的核心部分,并在可行的情况下应用SOLID规则。

示例:您需要在控制台中编写一行简单的文本。第一次发生这种情况,Console.WriteLine()很好。在新的要求还要求将同一行写入输出日志之后,您可以返回此代码。在这个简单的示例中,可能没有很多重复的“复制/粘贴”代码(或者也许有),但是您仍然可以进行一些基本的清理,也许提取一两个方法以避免将IO操作内联到更深的业务逻辑中。然后,当客户端希望将相同的文本输出到监视服务器的命名管道时,您将返回。现在,此输出例程相当重要。您正在以三种方式广播相同的文本。这是可以从复合模式中受益的算法的教科书示例;用Write()方法开发一个简单的IWriter接口,实现该接口以创建ConsoleWriter,FileWriter和NamedPipeWriter类,并再创建一次“ MultiWriter”复合类,然后对您的类公开IWriter依赖项,并使用三个实际的writer设置MultiWriter复合,然后将其插入。现在,它非常坚固。从现在开始,每当需求要求输出应该到新的地方时,您只需创建一个新的IWriter并将其插入MultiWriter即可,而无需接触任何现有的工作代码。


同意,但是通常一旦您走过第一步,就再也不会回到第二或第三步了,因为现在该功能是“实时”的,并且还有更多的功能即将发布,因此您无法返回并修复该问题。第一个功能。你会发现所有你能做的就是第一步与每一个功能,然后你留下了沼泽。
韦恩·莫利纳

2
@Wayne M-在瀑布式SDLC或短时间场景中,肯定会发生这种情况。在这种情况下,最重要的是按照截止日期之前的原始规范完成工作,而不是代码的可维护性。如果您想评估源代码的质量,则只需在计划中花费时间进行重构。就像在其他任何涉及书面信息生产的工作中,如果您重视内容的质量,则可以花时间进行校对和编辑。
KeithS 2011年

您的开头语具有误导性-您说他们反对,然后描述将它们一起使用的最佳方法。我不知道我应该否赞成糟糕的行话还是赞成好的建议。
肖恩·麦克米伦

1
我认为我并不矛盾。它们是近极相反。宗教对一个或另一个的坚持必然意味着对另一个的拒绝。但是,这并不意味着它们是互斥的。您不必选择两种方法都必须100%遵守。这就是其余答案的重点。当涉及到每个原则的结论中的得失时,您如何平衡?
KeithS 2011年

真是个不错的过程:可行,干净,可靠。我想知道这样做的必然结果是“在没有首先破解原型的情况下不要尝试和设计任何东西”。
史蒂夫·本内特

17

只能执行A的好代码比可以执行A,B,C,D的坏代码差。

1)拥有仅执行应做的事情的代码。

如果我觉得这段代码在不久的将来可能会变得更大,那么我会考虑添加抽象,将此功能移到其他地方等等。

2)如果您计划代码执行A,B,C和D,则客户最终会要求您提供E。

您的代码应该执行应该做的事情,您现在不应该考虑将来的实现,因为您将永远不会继续更改代码,更重要的是,您将过度设计代码。由于当前的功能,您应该在需要代码时立即对其进行重构,而不要为尚未完成的工作做好准备,除非您当然将其计划为项目体系结构的一部分。

我建议您读一本好书:The Pragmatic Programmer。它会打开您的胸怀,并教您应该做什么和不应该做什么。

同样,Code Complete是一个巨大的资源,其中充满了每个开发人员(不仅是程序员)都应该阅读的有用信息。


12

我很需要写一段简单的代码,我思考未来

也许这就是问题所在。

在早期阶段,您不知道最终产品是什么。或者,如果您拥有它,那就错了。当然。就像一个14岁的男孩,几天前在Programmers.SE上问他是否必须为自己的未来职业在Web应用程序之间进行选择,我不记得还有什么:很明显,几年后,他喜欢会改变,他会对其他领域感兴趣,等等。

如果为了编写三行代码而创建一个新接口和两个类,则说明您过度设计了。您将获得一个难以维护且难以阅读的代码,因为对于每行有用的代码,您都有两行不需要的代码。不计算XML文档,单元测试等。

想一想:如果我想知道功能是如何在您的代码中实现的,那么让我更容易阅读二十行代码,还是不得不打开几十个半空类来更快,更容易,并且界面以找出哪个使用哪个,它们之间如何关联等等?

请记住:更大的代码库意味着更多的维护。当可以避免时,不要编写更多代码。

您的方法在其他方面也有害:

  • 如果您需要删除某个功能,那么,要浪费时间了解数十个类之间的依赖关系,是否更容易找出使用特定二十行方法的位置?

  • 调试时,进行较小的堆栈跟踪难道不是很容易,还是您更喜欢阅读几十行以弄清究竟是什么被调用?

总而言之,这似乎类似于过早的优化。您正在尝试解决问题,甚至不确定一开始是否存在问题以及它在哪里。当使用产品的版本1时,实现您现在需要实现的功能;不要考虑您希望在两年内在版本14中实现的功能。


“数十个半空类”的另一个缺点是,没有足够的材料来理解这些类的优点。
gnasher729

5

编写很多(可能)永远不会使用的代码是发布P45的一种很好的方法。您没有水晶球,也不知道开发的最终方向,因此花时间在这些功能上只会花钱而没有回报。


3

试图预测未来代码的需求往往会导致不必要的过度设计(我目前正在努力摆脱的习惯)。我要说的就是三行。当需要出现时(而不是之前),进行重构。这样一来,您的代码始终可以完成所需的工作,而不会过于复杂,并且可以通过重构自然地发展出良好的结构。


3

我经常说编码就像部队的明暗侧一样-“明暗”侧需要更多的努力,但会产生更大的结果。“阴暗”的一面是快速而容易的,可立即带来更多好处,但会破坏您的发展。一旦您走上了黑暗的道路,它将永远统治您的命运。

我无时无刻不在遇到这个问题,就像我曾经做过的每件事一样,就像我无法逃脱的诅咒。公司文化始终是阴暗的一面,快速的破解/修复以推出新功能,而我对重构和编写代码的要求和呼声充耳不闻,如果这不能导致我终止“尝试改变事物”(我没有开过几次笑话,因为我想介绍设计模式并摆脱快速的攻击)。

可悲的事实是,愚蠢/黑暗的一面经常是您必须踩踏的方式,您只需要确保轻踩即可。我已经缓慢而悲哀地意识到,那些了解正确编写软件方式(即遵循SOLID,使用设计模式,遵循SoC等)的程序员比笨拙的会写一条if语句来修复错误的笨拙的程序员要少得多,并且当出现更多错误时,只需在该语句上添加内容,而不用考虑“也许有更好的方法”,然后将代码重构为可适当扩展。


3
if是很多更容易维护比IAbstractBugFixerIAbstractBugFixerFactory。如果再加上IF,if就该重构了。在架构阶段,设计模式和SOLID非常重要,但在产品已经运行并且以所有团队成员都同意的一种风格编写产品时,则不重要。
编码器

@Coder尽量不要假设体系结构随时都不能更改。它可以并且可以。
里奇·克拉克森

1
韦恩·M,我很同情您的工作情况。留在原力。:)
珍妮弗·S

3

意识到可能发生的事情(未来)永远不会坏。思考什么是做某事的更好方法是使您做好工作的一部分。最困难的部分是确定花费的时间:还款比率是否合理。我们都看到过这样的情况,人们会“轻松地”停止立即流血(和/或大喊大叫),随着这些加总,您将获得令人困惑的代码。我们中的许多人还经历了过高的抽象,一旦原始编码器继续前进,这就是一个谜,这也会产生混乱的代码。

我会看您的情况并提出以下问题:

  1. 这些代码对任务至关重要吗?如果我重新编码,它会变得更加稳定吗?用手术的话来说,这是重构生命的挽救,还是仅仅是选择性的和美观的?

  2. 我是否正在考虑要在6个月内替换的重构代码?

  3. 我愿意花很多时间花在重构上的时间来记录设计和对未来开发人员的推理吗?

  4. 关于我添加新功能的优雅设计,这是用户要求每周更改的代码,还是我今年第一次接触?

有时候,YAGNI和KISS会赢得胜利,但是在某些日子里,根本的改变会让您摆脱疯狂的下降。只要您不仅对自己想要的东西,而且对他人必须做的事情进行评估以保持您的工作,就可以更好地确定哪种情况。哦,别忘了写下您所做的事情以及原因。这样不仅可以保存跟随您的人,还可以保存您自己,以防您稍后不得不返回。


3

在Stroustrups第二版“ C ++编程语言”中,(我没有可用的页面)我阅读了

不要自发地添加代码。

我遵循了建议。当然,需要权衡取舍,而且您必须找到一个平衡点,但是比起大的意大利面烂摊子,较短的碎片更易于测试。

我经常体验到,在将一个案例与两个案例区分开的同时,如果您将2个案例视为n个案例,则会为许多新的可能性打开一扇大门,而这是您可能没有想到的。

但是随后您不得不问YAGNI问题:值得吗?真的有用吗?有经验的人意味着您很少会犯错,而作为初学者,您常常会犯错。

您应该足够关键,以识别模式,并检测是否由于过多的抽象而难以维护代码,还是因为一切都已解决就难以维护。

解决方案不是这个或那个,而是要考虑一下。:)


2

“仅能执行A的好的代码比能执行A,B,C和D的不好的代码更糟糕。”

这在产品开发中可能有意义。但是大多数IT专业人员都是在“项目”中工作,而不是在产品开发中工作。

在“ IT项目”中,如果您编写了一个好的组件网络,它将在项目的整个生命周期内平稳运行-可能运行不超过5或10年,否则业务场景可能已过时并且新的项目/ ERP产品可能已经替换了它。在这5/10年的寿命中,除非您的代码中有缺陷,否则没人会注意到它的存在,而您最好的想法的优点也不会被忽略!(除非您擅长大声吹打自己的小号!)

没有多少人有机会编写'Windows Ctl + Alt + Del'程序,而很少有人有机会无法意识到其代码的未来潜力!


1

许多关于精益和/或敏捷开发的书籍将有助于加强这种方法:立即做必要的事情。如果您知道要构建框架,请添加抽象。否则,在您需要之前不要添加复杂性。我推荐精益软件开发,它将介绍许多其他可以大大提高生产率的概念。


1

人们如何谈论正确的做事方式是很有趣的。同时,编程任务仍然非常艰巨,并且没有为编写大型复杂系统提供好的解决方案。

也许有一天,我们程序员将最终弄清楚如何编写复杂的软件。在此之前,我建议您始终先从“愚蠢”的原型实现开始,然后再花足够的时间进行重构,以便您的大学可以遵循您的代码。


1
在大多数情况下,您永远不会有一些特殊的时间来进行重构-这可能是所有这些问题的主要原因“我们必须重新实现才能实现它;-)您要么从一开始就以正确的方式进行操作,否则您总是会用错误的方式做。
Andrey Agibalov 2011年

@ loki2302:请记住,编写新代码总是更容易。对愚蠢的代码进行原型制作时,您的速度将是原来的两倍,在此之后,您的生产率将在大约一半的时间内下降到零。所以到最后你还是会一样快程序员试图设计的正确方法..
AareP

1

看到过早的泛化设计根本无法满足后来出现的实际需求,我为我设计了一条规则:

对于假设的要求,仅编写假设的代码。

即:建议您考虑以后可能发生的更改。但是,仅使用这些见识为代码选择一种设计,如果确实提出了这些要求,就可以轻松地对其进行更改和重构。在这种情况下,您甚至可能会想编写一些代码(假设代码),但不要编写任何实际代码!


0

我觉得心态,这将有助于你是要始终为争取具体解决编码问题,而不是抽象的解决方案。仅当抽象实际上有助于简化代码库时才应添加抽象(例如,当它们允许您使代码库更小时)。

许多程序员发现,干掉代码后,抽象几乎是自己出现的。设计模式和最佳实践可帮助您找到这样做的机会,但这些目标本身并不值得追求。


0

我认为过度设计通常是由于编写代码的不安全感所致。所有抽象的原则和模式都应视为可以帮助您的工具。经常发生的事情是将它们视为必须遵守的标准。

我相信,与公理相比,程序员总是可以更好地决定如何抽象。

其余的已经由KeithS说过


我认为获取代码编写自我安全性的一种方法是在Linux系统上以root身份进行编码。如果键入willy-nilly,boom,则只需重新映像VM。真正快速地传授各种良好习惯。确保您的盒子位于真实的互联网中,并确保您查看日志。(ssh,http)那些也很有趣!
Christopher Mahan

我的版本是:忽略您不了解的原理。如果您决定使用它们,请不要比锻炼更认真地对待它们。
sabof 2011年

0

问问自己,好的设计的优点是什么:

  • 更容易理解
  • 维护更轻松
  • 随身携带
  • 长期保持有用
  • 易于添加新功能

现在,问问自己,添加所有这些抽象层是否真的增加了上述任何要点。如果不是,则说明您执行WRONG

如果您可以通过添加如下三行代码来添加新功能:

if (condition()) {
  doSomething()
}

然后,这样做。这表明您以前的设计很好并且很容易适应。仅当您的类开始增长到一定程度时,才可以使用重构来拆分函数并可能提取新的类。

我的经验法则是,应该尽可能简化地实现新功能,只有在某些事情需要大手笔掌握的情况下(例如,实施需要花费超过1天或半天的时间),才可以添加粗略的全局设计。从那里开始,仅在代码大小增加时才添加抽象层。然后,您就可以重构!一段时间后,当您需要再设计一点或走一条捷径时,对您来说甚至很自然。重用时,某些代码可以使用某些清理的另一个指示。每次添加新功能或在新位置调用旧代码时,都是时候查看旧代码并查看是否可以对其进行一些改进,然后再重新添加。这样,热代码将逐渐变得更干净,而无关紧要的部分将慢慢腐烂,从而不会占用您的宝贵时间。

如果您像这样工作,您将永远不会过度设计任何东西。当您想添加新的东西,或者想要留下比您喜欢的丑陋的新代码时,可能需要一些纪律才能回到旧代码,但是您将朝着高效的方向努力,而不是过度设计。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.