如何避免在次优设计中无休止地迭代?


10

因此,可能像许多人一样,我经常发现自己对设计问题感到头疼,例如,有些设计模式/方法似乎可以直观地解决问题并具有预期的好处。通常会有一些警告,如果没有某种工作就很难实施模式/方法,而这周围的工作否定了模式/方法的好处。我可以很轻松地结束许多模式/方法的迭代,因为可以预见的是,几乎所有的模式/方法在现实世界中都存在一些非常重要的警告,在这些情况下,根本没有一个简单的解决方案。


例:

我将给您一个假设的示例,大致基于我最近遇到的一个真实示例。假设我想在继承上使用合成,因为过去继承层次结构阻碍了代码的可伸缩性。我可能会重构代码,但随后发现在某些上下文中,超类/基类只是需要在子类上调用功能,尽管尝试避免这样做。

下一个最好的方法似乎是实现一半委托/观察者模式和一半组合模式,以便超类可以委托行为,或者子类可以观察超类事件。然后,该类的可伸缩性和可维护性较差,因为它不清楚应如何扩展,而且扩展现有的侦听器/代理也很棘手。同样,信息也隐藏得不好,因为人们开始需要了解实现以了解如何扩展超类(除非您非常广泛地使用注释)。

因此,在此之后,我可能会选择完全使用观察者或委托人来避免因混淆大量方法而带来的弊端。但是,这有其自身的问题。例如,我可能会发现我最终需要观察者或代表来应对越来越多的行为,直到我几乎需要针对每种行为的观察者/代表。一种选择可能是只为所有行为提供一个大的侦听器/代理,但是实现类最终会带有许多空方法等。

然后,我可以尝试另一种方法,但是与此同时存在很多问题。然后是下一个,然后是下一个,依此类推。


当每种方法似乎都存在其他问题时,这种迭代过程将非常困难,并导致某种设计决策瘫痪。无论使用哪种设计模式或方法,都很难接受代码最终同样会带来问题。如果我最终陷入这种情况,是否意味着问题本身需要重新考虑?别人遇到这种情况时会做什么?

编辑: 似乎有一些我想清除的问题的解释:

  • 我将OOP完全排除在问题之外,因为事实证明它实际上并不是特定于OOP的,而且很容易误解我在传递OOP时所做的一些评论。
  • 有些人声称我应该采取迭代的方法并尝试不同的模式,或者当它停止工作时我应该放弃一个模式。这是我首先要参考的过程。我认为这个例子很清楚,但是我可以更清楚一点,所以我编辑了问题。

3
不要“订阅面向对象的哲学”。这是一种工具,而不是重大的人生决定。在有帮助时使用它,在无用时不要使用它。
user253751 '18

如果您不停地思考某些模式,或者尝试使用C编写一些中等复杂的项目,那么体验没有这些模式可以做的事情会很有趣。
user253751 '18

2
哦,C有模式。它们只是非常不同的模式。:)
candied_orange

我已在编辑中清理了大部分此类误解
Jonathan

这些问题经常出现很长时间。通常不会发生这种情况。最有可能的是,您对问题使用了错误的编程范例,或者您的设计在错误的位置混合了范例。
Dunk

Answers:


8

当我做出这样艰难的决定时,我通常会问自己三个问题:

  1. 所有可用解决方案的优缺点是什么?

  2. 有没有解决方案,我还没有考虑?

  3. 最重要的是:
    我到底有什么要求?不是纸上的要求,而是真正的基本要求?
    我是否可以以某种方式重新构造问题/调整其要求,以便可以提供简单,直接的解决方案?
    我可以用不同的方式表示我的数据,以便它可以提供一种简单,直接的解决方案吗?

    这是关于感知问题的基本原理的问题。事实证明,您实际上是在尝试解决错误的问题。您的问题可能有特定的要求,因此与一般情况相比,该解决方案要简单得多。质疑您对问题的表述!

我发现在继续之前认真思考所有三个问题非常重要。散散步,步调您的办公室,尽一切努力仔细考虑这些问题的答案。但是,回答这些问题应该不会花费不适当的时间。正确的时间可能从15分钟到大约一周的时间不等,这取决于您已经找到的解决方案的弊端及其对整体的影响。

这种方法的价值在于,有时您会发现出奇的好解决方案。优雅的解决方案。解决方案非常值得您花时间来回答这三个问题。如果您只是立即输入下一个迭代,那么您将找不到那些解决方案。

当然,有时似乎没有好的解决方案。在这种情况下,您会被困在对问题一的回答中,好是最坏的。在这种情况下,花时间回答这些问题的价值在于您可以避免必然会失败的迭代。只要确保您在适当的时间内恢复编码即可。


我可能会将此标记为答案,这对我来说最有意义,而且似乎对重新评估问题本身已经达成共识。
乔纳森

我也喜欢您将其分解为几个步骤的过程,因此,存在一些评估情况的宽松程序。
乔纳森

OP,我看到您陷入了代码解决方案的困境,所以,是的,“您也可能标记此答案”。我们编写的最好的代码是在对需求,派生的需求,类图,类交互等进行了非常仔细的分析之后。谢天谢地,我们抵制了廉价座位(阅读管理和同事)的所有烦恼: “在设计上花了太多时间”,“赶快去编码!”,“类太多了!”等等。一旦我们掌握了这一点,编码就是一种乐趣-不仅仅是乐趣。客观地讲,这是整个项目中最好的代码。
–radarbob

13

首先是-模式是有用的抽象,而不是最终的全部设计,更不用说OO设计了。

其次,现代OO足够聪明,可以知道并非所有事物都是对象。有时使用简单的旧函数,甚至使用某些命令式脚本将为某些问题提供更好的解决方案。

现在到东西的肉:

当每种方法似乎都遇到其他问题时,这将变得非常困难。

为什么?当您有许多类似的选择时,您的决定应该会更容易!选择“错误”选项不会让您损失太多。实际上,代码不是固定的。尝试一下,看看是否很好。反复进行

不管使用哪种设计模式或方法,都很难接受代码最终会带来很多问题。

坚韧的坚果。困难的问题-实际的数学困难的问题就很难解决。很难。对于他们来说,实际上没有好的解决方案。事实证明,简单的问题并不是很有价值。

但是要小心。我经常看到人们对没有好的选择感到沮丧,因为他们坚持以某种方式看待问题,或者他们以对眼前的问题不自然的方式削减了责任。“没有好的选择”可能会散发出一种气味,说明您的方法存在根本上的错误。

由于有时“干净”的代码不再是一种选择,因此这会失去动力。

完美是善的敌人。使某些东西起作用,然后对其进行重构。

是否有任何设计方法可以帮助最小化此问题?在这种情况下应该做什么,是否存在公认的惯例?

如前所述,迭代开发通常可以最大程度地减少此问题。一旦工作正常,您就会对问题更加熟悉,从而可以更好地解决问题。您需要查看和评估实际的代码,而不是一些看起来不太正确的抽象设计。


只是以为我应该说我已经清除了编辑中的一些误解。我通常会采用“使其正常运行”和重构的方法。但是,根据我的经验,这常常导致很多技术债务。例如,如果我只是使某件事起作用,但是我自己或其他人在此之上构建,则其中某些事情可能具有不可避免的依赖关系,因此还需要进行大量重构。当然,您不能总是总是事先知道这一点。但是,如果您已经知道该方法存在缺陷,是否应该在建立代码之前先使其工作,然后迭代重构?
乔纳森

@Jonathan-所有设计都需要权衡取舍。是的,如果设计有缺陷并且您有明确的改进方法,则应立即进行迭代。如果没有明显的改善,请停止他妈的。
Telastyn

“他们以一种对眼前的问题不自然的方式削减了他们的责任。没有什么好选择可以闻到您的方法存在根本上的错误。” 我敢打赌这个。一些开发人员从未遇到过OP描述的问题,而其他开发人员似乎经常这样做。通常,由原始开发人员提出的解决方案并不容易,因为他们已经在设计问题的方式上使设计产生了偏差。有时,找到非常明显的解决方案的唯一方法是从设计要求开始。
Dunk

8

您描述的情况看起来像是一种自下而上的方法。您拿起一片叶子,尝试对其进行修复,以找出其已连接到一个分支,该分支本身也已连接到另一个分支等。

这就像试图从轮胎开始制造汽车。

您需要退后一步,并放眼全局。那片叶子在整体设计中的位置如何?这仍然是最新且正确的吗?

如果您从头开始设计和实现模块,该模块的外观如何?您当前的实现离该“理想”有多远。

这样一来,您就可以全面了解要做什么。(或者,如果您认为工作量太大,那么问题出在哪里)。


4

您的示例描述了一种情况,这种情况通常发生在较大的遗留代码片段中,以及当您尝试进行“太大”的重构时。

对于这种情况,我可以提供的最佳建议是:

  • 不要试图一次“大爆炸”实现自己的主要目标

  • 了解如何以较小的步骤改进代码!

当然,这写起来容易做起来难,那么如何在现实中实现呢?好吧,您需要实践和经验,这在很大程度上取决于具体情况,并且没有一成不变的规则说“做这个或那个”适合每种情况。但是,让我使用您的假设示例。“继承之上的合成”不是一个全有或全无的设计目标,它是通过几个小步骤就可以实现的理想选择。

假设您注意到“继承构成”是解决该问题的正确工具。必须有一些迹象表明这是一个明智的目标,否则您将不会选择此目标。因此,让我们假设超类中有很多功能,这些功能只是从子类中“调用”的,因此,此功能是不留在该超类中的候选方法。

如果您发现无法立即从子类中删除超类,则可以首先将超类重构为较小的组件,以封装上述功能。从挂得最低的果实开始,首先提取一些更简单的组件,这将使您的超类不再那么复杂。超类得到的越小,其他重构就越容易。使用子类和超类中的这些组件。

如果幸运的话,在整个过程中,超类中的其余代码将变得如此简单,以至于您可以将超类从子类中删除,而不会产生任何其他问题。或者,您会注意到保留超类不再是问题,因为您已经将足够的代码提取到了想要在不继承的情况下重用的组件中。

如果您不确定从哪里开始,因为您不知道重构是否会很简单,那么有时最好的方法就是进行一些临时重构

当然,您的实际情况可能会更复杂。因此,学习,收集经验并耐心等待,正确的做法需要花费数年时间。我可以在这里推荐两本书,也许您会觉得有帮助:

  • Fowler的重构:描述了非常小的重构的完整目录。

  • Feathers 有效处理遗留代码:为如何处理大量设计不良的代码并在较小的步骤中使其更具可测试性提供了出色的建议


1
这也非常有帮助。小规模的逐步重构或临时构建是一个不错的主意,因为它随后可以逐步完成,并且可以与其他工作一起完成,而不会过多地妨碍它。希望我可以批准多个答案!
乔纳森

1

有时,最好的两个设计原则是KISS *和YAGNI **。不必将所有已知的设计模式都塞进只需要打印“ Hello world!”的程序中。

 * Keep It Simple, Stupid
 ** You Ain't Gonna Need It

在问题更新后进行编辑(并在一定程度上反映Pieter B所说的内容):

有时,您会及早做出架构决定,从而导致您尝试进行特定设计时会导致各种丑陋。不幸的是,到那时,“正确”的解决方案是退后一步,弄清楚您是如何到达那个位置的。如果您还看不到答案,请继续后退,直到找到答案为止。

但是,如果要做到这一点的工作不成比例,则需要采取务实的决定,才提出解决问题的最丑陋的解决方法。


我已经在编辑中清除了其中的一些问题,但总的来说,我指的是那些没有可用的简单解决方案的问题。
乔纳森

很好,是的,关于退后并重新评估问题似乎已达成共识。这是一个好主意。
乔纳森

1

在这种情况下,我要做的第一件事就是停止。我切换到另一个问题,并进行一段时间的处理。也许一个小时,也许一天,也许更多。这并不总是一种选择,但是我的潜意识会在事情上起作用,而我的意识大脑会做些更有生产力的事情。最终,我重新审视了它,然后重试。

我要做的另一件事是问一个比我聪明的人。这可以采取以下方式:在Stack Exchange上提问,在Web上阅读有关该主题的文章或询问在该领域有更多经验的同事。通常,我认为正确的方法对于我尝试做的事情是完全错误的。我误解了问题的某些方面,它实际上与我认为的模式不符。发生这种情况时,让别人说:“您知道,这看起来更像是……”可能会很有帮助。

与上述相关的是通过自白进行设计或调试。您去一位同事说:“我要告诉您我遇到的问题,然后我将向您解释我所遇到的解决方案。您指出每种方法中的问题并提出其他方法。” 正如我正在解释的那样,通常在对方甚至还没有讲话之前,我就开始意识到,一条似乎与他人平等的道路实际上比我原先认为的更好或更糟。最终的对话可能会强化这种看法,或者指出我没有想到的新事物。

TL; DR:请稍事休息,不要强迫,请寻求帮助。

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.