【问题标题】:What do you feel is over-generalization?你觉得什么是过度概括?
【发布时间】:2011-03-15 15:44:00
【问题描述】:

在使用了一些 Haskell 和其他函数式语言之后,我开始欣赏通过笼统地描述问题所带来的设计简单性。虽然模板编程的许多方面可能远非简单,但有些用途很常见,我不认为它们是清晰的障碍(尤其是函数模板)。我发现模板通常可以简化当前设计,同时自动添加一些未来阻力。为什么要将它们的功能委托给库编写者?

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

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

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

老实说,看到这种说法如此不屑一顾,好像它应该是不言而喻的,我真的很惊讶。就我个人而言,我发现它远非不言而喻,除非你另有说明,否则像 Haskell 这样的语言都是通用的。话虽如此,我想我明白这种观点的来源。

在我的脑海里,我确实有类似的规则在喋喋不休。现在它处于最前沿,我意识到我总是从整体架构的角度来解释它。例如,如果您有一个类,您不想在其中加载大量您可能有一天会使用的功能。如果您只需要一个具体版本,请不要费心制作接口(尽管可模拟性可能与此相反)。诸如此类...

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

那你怎么看,所以?你认为什么是过度概括?这条规则是否根据上下文有不同的适用性?你甚至同意这是一条规则吗?

【问题讨论】:

  • 你以编码为生吗?由 Haskell 提供支持的 Web 服务器如何为您工作?记住:“聪明并把事情做好”。 joelonsoftware.com/items/2007/06/05.html
  • @James McNellis:谢谢。我不想指责或看起来我只是想挖掘旧的论点。我真的很好奇。
  • 这是交易 - 你不再是大学生了。我相信,如果真正需要,您始终可以将特定功能转换为通用功能。现在,你是一个忙碌的成年人,工作时薪 50 美元左右。仅在需要时进行概括不是很有意义吗?时间就是金钱。
  • @Hamish:谢谢你的屈尊。非常感谢。如果你真的对我的回答感兴趣:首先,你需要了解“微妙”和“细微差别”。我的问题如何让你觉得我在说“你想什么时候随意制作模板”?我的问题中真的没有暗示,也许,我会花时间考虑泛化行为的开销吗? (你一做我就改变态度)
  • 好问题。我希望你能在一周前问我这个问题,然后我才以我的客户为代价进行我的小型概括盛会。当我不按时交货时,他会很窒息,我对此感到非常难过。哎呀。

标签: c++ templates generic-programming


【解决方案1】:

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

把这个带回模板,客户要求一些能力,包括你编写一个接受字符串和数字的函数。你给了我一个模板化的解决方案,它采用任何两种类型,并为我的特定情况做正确的事情,以及在其余情况下可能正确或可能不正确的事情(由于没有要求),我将不胜感激。我会被打勾,除了付钱给你,我还必须付钱给别人测试它,有人记录它,如果将来发生更一般的情况,有人在你的限制范围内工作。

当然,并不是所有的概括都是过度概括。一切都应该尽可能简单,但不能更简单。必要时通用,但不再通用。尽我们所能承受的测试,但不再测试。等等。此外,“预测可能发生的变化并将其封装起来”。所有这些规则都很简单,但并不容易。这就是为什么智慧对开发人员和管理他们的人很重要。

【讨论】:

  • 这也是一个很好的视角。如果您的角色是为客户生成特定代码,那么您肯定会希望按照规范进行操作。我想我主要将其应用于您对设计本身具有更多独立性的情况。
【解决方案2】:

如果你能同时做到,而且代码至少一样清晰,泛化总是比专业化好。

XP 的人遵循一个叫做 YAGNI 的原则——你不需要它。

wiki 有话要说:

即使您完全、完全、完全确定以后需要某个功能,也不要现在就实施。通常,结果要么是 a) 你根本不需要它,要么 b) 你实际需要的与你之前预见的完全不同。

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

【讨论】:

  • 如果这不是主观的,我会接受这个作为答案。第一句话击中了我的头。
  • #@&%.我只是花了一周的时间概括了一些我真的不应该做的事情。难怪我什么都没做。
【解决方案3】:

太笼统了?我必须承认我是泛型编程的粉丝(作为原则),我真的很喜欢 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);
}

另一方面,您越接近特定业务的工作,您就越不可能成为普通人。

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

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

根据回答者,我调整通用程度,并且努力避免过早锁定,即当使用稍微通用一点。

例子:

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

// VS

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

它不仅更通用(我可以指定输出位置),而且更惯用。

【讨论】:

  • 大多数时候只是一种直觉:同意。
【解决方案4】:

当您浪费时间概括某件事时,它就被过度概括了。如果您将来要使用通用功能,那么您可能不会浪费时间。 [在我看来]真的很简单。

需要注意的一点是,如果使软件变得更加混乱,那么使您的软件通用化并不一定是一种改进。经常需要权衡取舍。

【讨论】:

  • 阿门第二位
【解决方案5】:

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

当我第二次(或最多第三次)需要类似的东西时,我会尝试根据具体的现实生活示例来概括问题(功能、类、设计等) -> 不太可能我只是为自己做概括。 下一个类似的问题:如果它优雅地适合当前图片,很好,我可以轻松解决它。如果没有,我检查当前的解决方案是否可以进一步推广(不要让它太复杂/不太优雅)。

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

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

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

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

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

【讨论】:

    【解决方案6】:

    对我来说,过度概括是,如果需要在任何进一步的步骤中打破抽象。项目中的示例,我住在:

    Object saveOrUpdate(Object object)
    

    这个方法太通用了,因为它是在一个 3-Tier-Architecture 中提供给客户端的,所以你必须在没有上下文的情况下检查服务器上保存的对象。

    【讨论】:

      【解决方案7】:

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

      它们都用于在 c++ 中“实现”大多数人不使用的泛型。事实上,每个人都对使用这些(CObject/Object)给出的参数进行类型检查〜

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-09-12
        • 1970-01-01
        • 2010-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多