2012-10-14 35 views
12

当你在开发时,你什么时候可以确定你的应用程序中是否有很多不必要的类?你应该有多少班级有一定的限制?有太多课程吗?

+7

肯定是 - 这可能是任何OOP项目中唯一的,最困难的部分:定义最少的一组类以完成工作。以不必要的预计未来自由度(又称不需要/未使用的灵活性)来过度设计/使项目复杂化非常容易。 – kfmfe04

回答

17

真的没有“太多班级”这样的事情。 “可以成为一个问题是”太多的班级做同样的事情。“

如果您觉得您的代码库中有太多的类,那么审计这个问题的好方法就是添加一些新的需求。任何迫使您对代码进行一些更改的事情。 (当然,在源代码管理的单独分支中)。进行这些更改有多难?相对简单的改变是否需要修改吨级和吨级的类?如果是这种情况,那么你很可能会有太多的问题,但问题不在于数字本身。

这在很多情况下主要是个人喜好的问题。代码重用和代码解耦之间经常存在折衷。通过分离出每一个可能的问题,并且有许多小班,你会从其他方面中解脱出来。然而,你经常发现在这种情况下你必须重复代码,因为很多代码可能会做“同样的事情”,但原因稍有不同。另一方面,如果你坚持不要在代码中重复任何事情,那么当你得到更少的类时,你通常也会得到更多的耦合,因为单个类对于任何需要相似功能的代码都有多重责任。

大多数情况下的底线是抗拒变化。耦合与重用是人们可以长期争论的问题,但软件僵化是争论变成实际努力(金钱)的地方。测试对代码进行更改有多困难。然后尝试重新安排您的课程/逻辑,以便您认为会更加接受变化并再次进行测试。是否有显着的改善?

+0

我相信你正在做的一个主要观点是展开。这是你展开类的地方,暴露了很多细节,而不是批量做事。这个想法是,如果千分之一需要不同的东西,看起来很容易改变其锅炉板方法,而不会影响其他任何东西。问题是你可以通过简单的if语句更容易地实现这一点。在OOP中还有一些方法可以定义一个叶子,并将其分支返回到树干。你很少需要从树干上向外展开整个树来达到目的。 – jgmjgm

1

这一切都取决于你的项目。它取决于你的要求。
类别必须是最小的,因为没有不需要的类别必须是最大的,因为它们都包含单独的属性。

+0

你的答案看起来很含糊,谨慎地阐述它呢? – user962206

+0

“它们都包含单独的属性。”这一点有点失落。 – jgmjgm

5

通常很多类意味着你很可能已经非常普遍地解决了你的问题。这通常是很好的,因为这意味着当你最终需要时,你希望有一个更容易改变行为的时间。

在开发较小的项目时,有时最好更具体一点(即不那么一般)来更快地实现,这可能导致较少的类,但可能在需要出现时更难以改变。

只要这些类是有序的,并有一个明确的目的,它应该不是一个问题,有很多类。

如果问题的类型是紧密耦合的,或者某些类的责任没有很好地定义,那么问题会是怎样。有关耦合的更多信息,请参阅。

下面的评论中提到了可能发生的另一个问题。如果你的很多类有相似的代码,你就有一个复制问题。这通常会导致系统中的可维护性下降,因为如果需要对重复代码进行更改,则必须多次进行更改。这通常通过继承来解决。

+2

我同意。但是,您可能会遇到许多只能重复使用的类,因为继承不正确。有测量代码重复的工具。如果你有很多低代码重复类,我同意这将是一个好兆头。 – Dan

+1

丹正在谈论的事情叫做代码复制。这通常是非广义代码的症状,也是复制粘贴代码不是好主意的原因之一。代码的结构元素当然可以被复制和改变。如果你发现你的自我复制代码,或许你应该扼制你为什么要复制它。重复的代码需要大约两倍的时间来维护,原因很明显。 –

1

一个应用程序可以全部在一个代码文件中,或者每个雾化函数可以在它自己的文件中,唯一受影响的是可维护的。可维护性可能意味着您自己的代码导航能力,也可能意味着他人如何理解代码,或者是否可以构建新版本。

我不认为有任何通用的指导方针总是适用于此,它取决于很多事情。例如,在JavaScript中编写代码时,与使用C#或C++编写代码相比,通常使用更少(和更大)的文件,其中包含更多不相关的功能。

如果您使用的是Visual Studio 2012,那么http://msdn.microsoft.com/en-us/library/bb385910.aspxhttp://msdn.microsoft.com/en-us/library/dd264939.aspx有关于代码度量和代码分析如何工作的信息。

这是基于我自己的应用程序从Visual Studio 2012中的代码度量的报告的一个示例,值在http://msdn.microsoft.com/en-us/library/bb385914.aspx解释。

Project: <<Projectname>> 
Configuration: Debug 
Scope: Project 
Assembly: <<Path>> 
Maintainability Index: 84 
Cyclomatic Complexity: 479 
Depth of Inheritance: 8 
Class Coupling: 189 
Lines of Code: 903 
1

我认为这取决于哪个区域有大量的类。如果有很多包含公共业务逻辑的静态类,则这会被认为是不好的,因为静态类只能用于常用的辅助方法。静态类不应该包含通用的业务逻辑。

如果为不同的层存在不同的类来保存基本相同的数据。这将被认为是不好的,因为DTO类不应该跨层复制。

但是,如果在对需求进行适当分解之后创建了类,那么我认为拥有大量类是很好的。

+0

这与业务逻辑或帮助程序没有区别。决定者是,如果你需要事例给你的程序没有。业务逻辑希望被隔离,但是否需要实例化则纯粹是程序性问题,而不是代码关联的问题。如果所有事情都做得适当,如果实际需要出现,将程序性业务逻辑转换成多态就没有什么大不了的。如果您的语言或方法会使得难以更改所有依赖于该逻辑的代码,则会出现例外情况。 – jgmjgm

2

正如许多人都认为,“这取决于......”

通常它取决于你的方法,你的目标和你的团队成员的喜好和能力的结合。如果你对单元测试非常严格,你可能会得到很多小的普通类和依赖注入。这也意味着单个团队成员很难从所有非常非常通用的部分看到您正在构建的具体整体。

就我个人而言,我更喜欢根据构建在两个层面上的API来考虑:通用,独立部分的较低层和使用几个立面,导向器等的较高层来呈现具体和有用的东西其他编码器。这很像iOS库IMHO的设计。

3

在我正在研究的项目中,我绝对认为我们使用了太多的类 - 或者至少是太多的对象/实例。

我们基于PHP/MySQL构建了一个CMS,其中数据库中的每个记录和字段都以PHP中的对象表示。这可能导致成千上万的实例同时发生,并且我们不断遇到性能问题/内存不足等问题。

这当然不是其他编程语言或其他要求中的问题,但在我看来,表现也是需要考虑的。

+0

虽然这与操作问题无关。正如你承认它太多(因此可能相当小)的类而不是实例。 – RecursiveExceptionException

+0

看到人们从数据库中获取一个关联数据,将其转换为表示该表格的对象,逐个设置每个属性,然后在将项目发送给Javascript时做相反处理是非常常见的。这是一种常见的反模式。您应该尽可能避免篡改或更改数据。如果没有理由不这样做。我经常将成千上万的对象和数万行代码转换为简单的json_encode。问题是大多数开发者都是年轻人,刚刚从学术界崭露头角,并且被纯粹的面向对象淹没,因此很难提出这种情况。 – jgmjgm

3

Kent Beck回答你的问题。 Jeff Langr在“Clean Code敏捷软件工匠手册”一书中讨论了Kent Beck指定的四条设计规则。

(按重要性排序)

  1. 运行所有测试
  2. 不含重复
  3. 表示程序员
  4. 的意图最小化的类和方法

肯特建议采用实用的方法来k等级和方法都很低。他给出了教条式规则的例子,比如所有的类都应该有接口。通常是的,但有时候在某些情况下可能不需要。但是,这个规则是简单设计四条规则中最低优先级的。

(注意,这是肯特·贝克认为,与其说是我的!)

+1

运行所有的测试应该意味着它的工作(通过所有测试第二次)。他意味着确定某些工作的很多方法中的一种(自动化测试),只需确定应该确定的内容,而不是具体如何。尽管原则上它是正确的,但数字3是非常主观的。与3号机有潜在冲突,因为它可能导致膨胀。但是#1和#4应该防范这一点。 – jgmjgm

0

哲学:

有这样的事太多了的东西。你可以有太多和太少的课程。有人喜欢假装更多是免费的,因为他们可以使用搜索等工具作为制造过度混乱,难以导航和大型搜索空间的借口。从长远来看,你总会发现可衡量的赤字。

我不太确定你可以有多少班级有上限。无论从技术角度讲,你都可以无限地找到方法来无限地望远镜添加类。如果你不能有太多的课程,但是你可以无限增加它们,那么你就不会因为你想要的课程数量而完成你的课程,因此你可以有太多的课程。

大量的IDE使得创建大量类并将它们与诸如样板生成,自动完成以及总是复制副本之类的东西串起来变得非常容易。许多工具降低了创建本质上通常无用的代码的成本,但并未降低膨胀成本。即使有佣工,未使用代码的代码总是会比臃肿的代价便宜(只能减少税收,而不能消除税收)。如果你不关心它,它最终会成为一个问题。即使你有代码扫描,罚款和替换文件等内容,那么十倍以上仍然是十倍以上。这意味着改变十倍,错误增加十倍,每行消耗的工作量的十分之一。

许多人陷入了陷阱,认为他们通过增加更多的类来降低复杂性。事实上,它们往往只是打破复杂性,将事物从它们相关的事物中移开,并以间接方式增加复杂层次。线性代码变为

非线性不必要的(这是一个段落太多的例子,虽然公平一个更好的例子可能是一个段落或每个句子太多,当你的段落成为句子,不再有两个独立的东西,这可能是两个段落的证明,当句子停止与段落不同的事情时)。

检测:

简单的方式来看待,这是如果你有路径A(单节点/一个函数/类/等),但它分解成A-> B,你实际上并没有获得任何东西。你只拿了一张纸,撕成两半,放在两个信封中,然后贴到目的地。如果事实证明,你确实需要一个多边的节点,那么你会获得一些东西。例如,这将是A-> B,A-> C。您可以使用图形分析来嗅出太多的对象。如果你有很长的链表或者很多小链表(或者甚至有一些小链表),那么你可能会说你有太多的类。并非所有形式的对象过度都很容易被检测到。由于班级太多,维护变得过于复杂,因为您最终支持一定程度的灵活性,而且只有一小部分只用于您的模型。这意味着你的许多代码实际上并不对应需要完成的工作。仅凭这一点就很难保持,因为该守则的目标是主观的而不是客观的,因此它可能是任意的。

您可以取代码库并减少类的数量,直到您只有实际需要的类为止。这意味着只有那些需要可移植性(传递数据),差异应该如何表现(传递方法)以及合理分离(像持久性,表示等独立处理主要概念)以及重复数据删除所需的需求。如果不能很好地进行设计,许多程序员会反其道而行之,编写代码并仅根据需要将其分开以满足特定用途。

虽然可衡量,但没有一个准确的衡量太多的班级,也没有一个完美的标志。只有提示。需要的最小类数与最大值之间的较大比例是一个提示。什么是大?我认为100次肯定是可疑的,10次相当可疑,5次有轻微怀疑。虽然这可以根据许多参数进行更改。

一个奇怪的措施是gzip你的代码。压缩比越好,膨胀的可能性就越大。这不是一个完美的措施,因为它需要参考点。降低压缩率的某些方法也可能是徒劳的,编码到一个特定的数字天真是永远不会工作。

你可以知道你是否有很多类(或接口),如果他们让你做的工作不能真正帮助你达到你的最终目标,或者如果他们放慢速度比放慢速度更慢向上。但这可能是主观的。如果有人做了太多的课程,那意味着他们将不得不改变他们的习惯,这意味着通常需要更好的编码方法的入门费。在项目开始时,这很难检测到,因为添加代码通常非常便宜。现在还没有什么依赖于它,分层很浅等等。至少在几个月甚至一年的时间内,这个项目的成本才会变得更加明显,这个项目膨胀,组织糟糕等等。直到一个项目几乎陷入僵局,人们才会注意到。许多人不知道,如果需要一年需要一年或六个月。很少有比较的重点。

如果你看看你的课程,你可以选择一些东西。有多少代码是面向对象的,多少是FO(面向对象与面向功能)?

功能面向意味着代码实际上做了一些事情,并直接有助于最终结果。这包括你必要的代码。它可能会由除分配和铸造以外的操作员组成。通常有条件的声明,分支,读取数据,选择行动方案并采取适当的行动方式,例如数据生成,突变或针对IO的读取/存储。

面向对象意味着简单地使用类表示概念。这段代码几乎可以将程序代码转换为声明性语言。如果你的大部分课程仅仅是帮助重型检查,表示等,那么你可能会有太多的东西。标志是类和方法,其名称的一部分可能是变量允许你缩小这些类。这是一个非常强烈的迹象,如果这些类的大部分都是拳击。简单地分配变量,但不做其他任何事情。这实际上是一个过度结构的案例,通常缺乏重复数据删除,动态编码或抽象。

在显而易见的情况下,如果一个类从来没有使用过,那么它就是一个类太多(如果它是死代码的话)。像相同但名称不同的类也是一个好兆头。

原因:

这可以通过一些事情来驱动从使它很容易创建和连接东西放在一起(这往往会打破,当你抽象机制旁白和做事动态令人鼓舞的好习惯避免取代但打破了IDE)。我常常因为试图完美地表现一切而陷入困境,但OO实际上不够灵活,无法做到这一点,而且它通常变成YAGNI。如前所述,一个常见的问题是缺少抽象,通常会有变量展开到顶级语言结构中(它也链接到设计中,直接将所有内容展示给IDE)。这不仅可能背叛缺乏动态编码,而且可能会使用预处理器或类似处理器。这看起来像什么基本上是一棵树,所有的叶子定义。尽可能避免为每一个可能的叶子定义一个类。树木扩张到极致的迹象可能就是你有什么样的原型使用类的地方。这也可能是更极端的事情的标志。所有类的展开笛卡尔积。在这种情况下,当两个人之间通常没有实际的差异时,你不会简单地为一个腿获得一个班级,而是一个CatLeg,DogLeg等的班级。有些人可以通过类型检查的极端主义来做到这一点,以阻止某人将DogLeg放在CatLeg上。这是一个令人讨厌和普遍的反模式。

太多类别的最大驱动力之一是试图遵守云中的标准,这些标准并不适用于您的情况。在这种情况下,您不是针对您的问题编程的。你正在编程以回应其他人的问题。

这是很常见的东西,如SOLID。了解和理解诸如SOLID之类的原理,能够应用它们是非常重要的,但知道何时不应用它们也很重要。

当有大型库的OOP语言被教授时,这个原则被大量使用。如果你正在制作一个你希望分发给世界的OOP库,那么可能有数百万人会遇到各种各样的用例,那么你希望遵循OOP原则,这会导致大量的分解成接口和类,使它们可以以不同的方式使用,因此一个功能块没有很高的机会拉入另一个可能不需要的功能。你必须考虑到,在这个世界上,你不想创建人们可能不得不分叉的图书馆。人们不希望这样做,因为他们然后成为他们正在重复使用的代码的维护者,否则这些代码会损失总体拥有成本。

这些原则也增加了很多开销,如果你的代码库有一个有限的用户范围,完全在你的控制之下等等,那么你可能有太多的代码,如果你这样做“的方式你应该“为分布式代码。即使你的代码是分布式的,但有时它可能太过于极端,无法事先满足所有的用例,有时你需要计算出最有可能需要的东西,然后根据需要改变其他东西。对于一个小型图书馆,你可以负担得起大量额外工作。对于一个庞大的代码库,你必须在哪里开销最有可能为自己付出代价。

反例:

在一个理想的世界里,您可以根据您的燃眉之急代码minimalistically和唯一。有一种偏见,认为这种方法的优越性是因为过度不自我暴露。缺点是。如果你太少,它会直接出现。这在DRY中很常见。添加一个函数后添加另一个函数。您复制并粘贴第一个,然后更改下半部分。这两个函数的上半部分是重复代码,它们立即自我揭示它们需要重复数据删除。你通过创建第三个函数来完成。你知道这不是一个功能太多,因为你有一个客观的可证明的理由去创造它。编写代码供其他人使用时,这种方法变得更加困难。对别人来说,我不一定是指任何人。我的意思是那些没有直接访问代码库的人,通常是陌生人。从本质上讲,人们在需要时无法轻松/快速/便宜地分解代码。如果你不满足这样的观众,那么你不必担心过早地破坏你的代码。

我最近在线上使用的图书馆数量太少。它包含一个具有多重职责的单一课程。它需要一个文件句柄(作为基本类型)来写入,然后根据调用的方法(例如addImage,addText等)自动输出适合于它正在生成的流的HTTP头。

在理想的世界中,这个类别不应该对输出做出假设。用户可能希望输出到文件系统,内存,TCP流等。它只需要提供一个简单写入方法的接口或使用标准库中的接口。在我的情况下,我只需要通过字符串连接输出,但为了实现这一点,我不得不打开一个伪文件映射到内存(这通常不可能,但语言允许它作为黑客)。

我已经有过这个问题多次使用从所有来源的随机库。在某些情况下,分离应该适用,有时不适用。如果有疑问,因为你保证最终了解它,所以太少仍然太多。我倾向于观察,如果你添加任何你不确定的事情,你将会陷入巨大的膨胀领域。如果你这样做,你可能会做两次,然后成为一种习惯。