你觉得过度概括是什么意思?

时间:2010-07-14 02:37:12

标签: c++ templates generic-programming

花了一些时间在Haskell和其他函数式语言中玩游戏,我开始意识到设计的简单性来源于描述一般问题。虽然模板编程的许多方面可能远非简单,但有些用法很常见,我认为它们不会妨碍清晰度(尤其是函数模板)。我发现模板通常可以简化当前设计,同时自动添加一些未来阻力。为什么他们的功能应该降级为图书馆作家?

另一方面,有些人似乎避免像瘟疫这样的模板。十年前,当泛型类型的概念对于大多数编程社区而言是陌生的时候,我能理解这一点。但是现在所有流行的静态类型OO语言都支持这种或那种形式的泛型。增加的熟悉程度似乎需要调整保守态度。

最近我向我表达了一种这样的保守态度:

  

你永远不应该做出比必要更通用的东西 - 软件开发的基本规则。

我很遗憾地看到这种说法如此轻蔑,好像应该是不言自明的。就个人而言,我发现它远非不言自明,对于像Haskell这样的语言,除非你另有说明,否则一切都是通用的。话虽如此,我想我理解这种观点的来源。

在我的脑海里,我确实有类似的规则在喋喋不休。现在它处于最前沿,我意识到我总是根据整体架构来解释它。例如,如果您有一个类,则不希望使用您可能有一天使用的大量功能加载它。如果你只需要一个具体的版本,就不要费心去做接口(虽然可模拟性可能是对这个版本的反驳)。这样的事情......

然而,我不做的是在微观层面上应用这一原则。如果我有一个小实用功能,没有理由依赖任何特定类型,我会制作一个模板。

那你觉得怎么样?您认为过度概括是什么?根据具体情况,此规则是否具有不同的适用性?你是否同意这是一条规则?

7 个答案:

答案 0 :(得分:12)

过度概括让我发疯。我不怕模板(无处可去),我喜欢一般的解决方案。但我也喜欢解决客户支付的问题。如果这是一个为期一周的项目,为什么我现在要资助一个月的盛会,这不仅会通过明显可能的未来变化(如新税收)继续发挥作用,还可能通过发现新的卫星或火星生命来实现?

将其带回模板,客户端要求一些功能,包括编写一个带字符串和数字的函数。你给我一个模板化的解决方案,它采用任何两种类型,并为我的具体情况做正确的事情,并在其他情况下做出可能或不正确的事情(由于没有要求),我将不会感激。我将被告知,除了支付你之外,我必须付钱给别人来测试它,有人记录它,以及有人在未来的约束下工作,如果一个更普遍的情况应该发生的话。

当然,并非所有概括都超过概括。一切都应该尽可能简单,但并不简单。尽可能一般,但不一般。尽可能经过测试,但没有经过测试。等等。另外,“预测可能会发生什么变化并将其封装起来。”所有这些规则都很简单,但并不容易。这就是为什么智慧在开发人员和管理他们的人中很重要。

答案 1 :(得分:10)

如果你能在同一时间做到这一点,并且代码至少同样清晰,那么概括总是比专业化更好。

有一个原则,XP人跟随叫YAGNI - 你不会需要它。

wiki有这样说:

  

即使你完全,完全确定以后需要一个功能,现在也不要实现它。通常,它会变成a)你毕竟不需要它,或者b)你实际需要的东西与你之前需要的东西完全不同。

     

这并不意味着您应该避免在代码中建立灵活性。这意味着你不应该根据认为你以后可能需要的东西来过度使用某些东西。

答案 2 :(得分:6)

太通用了吗?我必须承认我是泛型编程的粉丝(作为一个原则),我真的很喜欢Haskell和Go在那里使用的想法。

然而,在使用C ++进行编程时,您可以通过两种方式实现类似的目标:

  • 通用编程:通过模板的方式,即使编译时存在问题,依赖于实现等等。
  • 面向对象编程:它的祖先在某种程度上,它将问题放在对象本身(类/结构)而不是函数上......

现在,何时使用?这肯定是个难题。大多数时候它不仅仅是一种直觉,而且我当然也看到了滥用它们。

根据经验,我会说功能/类越小,其目标越基本,就越容易概括。举个例子,我在大多数宠物项目和工作中随身携带一个工具箱。大多数函数/类都是通用的......有点像Boost;)

// No container implements this, it's easy... but better write it only once!
template <class Container, class Pred>
void erase_if(Container& c, Pred p)
{
  c.erase(std::remove_if(c.begin(), c.end(), p), c.end());
}

// Same as STL algo, but with precondition validation in debug mode
template <class Container, class Iterator = typename Container::iterator>
Iterator lower_bound(Container& c, typename Container::value_type const& v)
{
  ASSERT(is_sorted(c));
  return std::lower_bound(c.begin(), c.end(), v);
}

另一方面,你越接近业务特定的工作,你最不可能是通用的。

这就是为什么我自己也很欣赏最小努力的原则。当我想到一个班级或方法时,我先退一步思考一下:

  • 它更通用是否有意义?
  • 费用是多少?

根据anwsers,我调整了通用度,我努力避免过早锁定,即我避免使用非通用的方法,因为使用它不会花费太多稍微更通用一点。

示例:

void Foo::print() { std::cout << /* some stuff */ << '\n'; }

// VS

std::ostream& operator<<(std::ostream& out, Foo const& foo)
{
  return out << /* some stuff */ << '\n';
}

它不仅更通用(我可以指定输出的位置),它也更具惯用性。

答案 3 :(得分:3)

当你浪费时间概括它时,有些东西过于笼统。如果您将来要使用通用功能,那么您可能不会浪费时间。这真的很简单[在我看来]。

需要注意的一点是,如果使软件更加混乱,那么使软件概括化并不一定是一种改进。经常需要权衡。

答案 4 :(得分:2)

我认为你应该考虑编程的两个基本原则:KISS(保持简单和直接)和DRY(不要重复自己)。大部分时间我从第一个开始:以最简单和最简单的方式实现所需的功能。通常它足够了,因为它已经可以满足我的要求。在这种情况下,它仍然很简单(而不是通用)。

当我需要类似的第二个(或最多三个)时间时,我会尝试根据具体的现实生活示例来概括问题(功能,类,设计等等) - &gt;我不太可能只为自己做泛化。 下一个类似的问题:如果它适合当前的画面优雅,很好,我可以很容易地解决它。如果没有,我会检查当前的解决方案是否可以进一步推广(不要太复杂/不那么优雅)。

我认为你应该做类似的事情,即使你事先知道你需要一个通用的解决方案:采取一些具体的例子,并根据它们进行概括。否则,在你有一个“好的”通用解决方案的情况下,很容易陷入死胡同,但它无法解决真正的问题。

但是,可能会有一些例外情况 a)当一般解决方案几乎完全相同的努力和复杂性。示例:使用泛型编写Queue实现并不比为字符串执行相同操作复杂得多 b)如果以一般方式解决问题更容易,而且解决方案也更容易理解。它不会经常发生,我现在无法想出一个简单的,现实生活中的例子:-(。但即使在这种情况下,以前拥有/分析具体的例子是必须的IMO,因为它只能确认你走在正确的轨道上。

可以说经验可以克服具体问题的先决条件,但我认为在这种情况下,经验意味着您已经看到并考虑过具体的,类似的问题和解决方案。

如果你有时间,你可以看看Structure and Interpretation of Computer Programs。它有很多有趣的东西,关于如何在通用性和复杂性之间找到适当的平衡,以及如何将复杂性保持在最低限度,这是你的问题所必需的。

当然,各种敏捷流程也推荐类似的东西:在需要时从简单的重构开始。

答案 5 :(得分:2)

对我而言,如果需要在任何进一步的步骤中打破抽象,那么过于概括。我住在项目中的例子:

Object saveOrUpdate(Object object)

此方法过于通用,因为它是在3层体系结构中提供给客户端的,因此您必须在没有上下文的情况下检查服务器上保存的对象。

答案 6 :(得分:0)

微软过度概括有两个例子:
1.)CObject(MFC)
2.)对象(.Net)

它们都被用来“实现”c ++中的泛型,这是大多数人都没有使用的。事实上,每个人都使用这些(CObject / Object)〜

对参数进行了类型检查