是否存在过多的私有功能/方法?


63

我了解文件齐全的代码的重要性。但是我也了解自我记录代码的重要性。直观地读取特定功能越容易,我们在软件维护期间就可以进行得越快。

话虽如此,我喜欢将大型功能分离为其他较小的功能。但是我这样做的程度是,一个班级最多可以有五个班级,以服务于一种公共方法。现在将五个私有方法乘以五个公共方法,您会得到大约二十五个隐藏方法,这些隐藏方法可能只会被那些公共方法调用一次。

当然,现在可以更轻松地阅读这些公共方法,但是我不禁认为,拥有太多函数是不明智的做法。

[编辑]

人们一直在问我为什么认为过多的功能是不好的做法。

简单的答案:这是一种直觉。

我的信念一点都没有任何小时的软件工程经验作为后盾。只是不确定性给了我一个“作家的障碍”,但对于程序员而言。

过去,我只编写个人项目。就在最近,我开始进行基于团队的项目。现在,我想确保其他人可以阅读和理解我的代码。

我不确定会提高可读性。一方面,我正在考虑将一个较大的功能分成名称可理解的其他较小的功能。但是我还有另一面说这是多余的。

因此,我要对此进行启发以选择正确的路径。

[编辑]

下面,我包括我怎么两个版本可以解决我的问题。第一个解决方案是分离大块代码。第二个不同的东西。

第一版:

public static int Main()
{
    // Displays the menu.
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

第二版:

public static int Main()
{
    DisplayMenu();
}

private static void DisplayMenu()
{
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

在上面的示例中,后者调用了一个函数,该函数将在程序的整个运行时仅使用一次。

注意:上面的代码是通用的,但与我的问题具有相同的性质。

现在,这是我的问题:哪个?我选择第一个还是第二个?


1
“具有太多功能是不正确的做法。”?为什么?请更新您的问题,以解释为什么这对您不利。
S.Lott

我建议阅读“类内聚性”的概念-鲍勃·马丁(Bob Martin)在“清洁代码”中对此进行了讨论。
JᴀʏMᴇᴇ

除了其他答案外,还有split和name,但让我们都记住,随着类的增长,其成员变量变得越来越像全局变量。改善问题的一种方法(除了更好的解决方案,例如重构;-之外)是,如果可能的话,将那些小的私有方法分成独立的函数(因为它们不需要访问很多状态)。一些语言允许类外函数,另一些则具有静态类。这样,您可以孤立地了解该功能。
Pablo H

如果有人能告诉我为什么“这个答案没有用”,我将不胜感激。一个人总是可以学习。:-)
Pablo H

@PabloH您提供的答案可以很好地观察和洞察OP,但是实际上并不能回答所提出的问题。我将其移至评论字段。
maple_shaft

Answers:


36

现在将五个私有方法乘以五个公共方法,您会得到大约二十五个隐藏方法,这些隐藏方法可能只会被那些公共方法调用一次。

这就是所谓的封装,它在更高级别上创建了控件抽象。这是一件好事。

这意味着,任何人阅读你的代码,当他们得到的startTheEngine()代码中的方法,可以忽略所有的较低级别的细节,比如openIgnitionModule()turnDistributorMotor()sendSparksToSparkPlugs()injectFuelIntoCylinders()activateStarterSolenoid(),和所有的其他复杂的,小的,必须按顺序运行功能促进的更大,更抽象的功能startTheEngine()

除非您在代码中处理的问题直接涉及那些组件之一,否则代码维护者可以继续进行下去,而无需考虑沙盒化,封装的功能。

这还具有使代码更易于测试的附加优点。例如,我可以编写一个测试用例turnAlternatorMotor(int revolutionRate)并测试其功能,使其完全独立于其他系统。如果该功能存在问题,并且输出不是我期望的,那么我知道问题出在哪里。

未分解为组件的代码很难测试。突然之间,您的维护人员只是在看将要抽象的东西,而不能深入研究可测量的组件。

我的建议是随着代码的扩展,易于维护以及可以在未来几年使用和更新代码,继续做您正在做的事情。


3
因此,您认为使逻辑可用于仅在一个位置调用的整个类是一个很好的“封装”吗?
史蒂文·杰里斯

9
@Steven Jeuris:只要将这些功能设为私有,我认为它没有问题;实际上,正如该答案所述,我发现它更易于阅读。理解仅调用一系列低级私有功能(当然,它们已适当命名)的高级公共功能要容易得多。当然,所有这些代码都可以放在高级函数本身中,但是那样一来,您将需要花费更长的时间来理解代码,因为您必须在同一位置查看更多代码。
gablin 2011年

2
@gablin:仍然可以从整个类中访问私有函数。在这篇(显然有争议的)文章中,我讨论了当功能过多时,如何失去“全局可读性”。实际上,除了功能应该只做一件事之外,还有一个共同的原则,那就是您不应拥有太多功能。仅通过简短分割即可获得“本地可读性”,这也可以通过简单的注释和“代码段”来实现。好的,代码应该是自记录的,但是注释不是过时的!
史蒂文·杰里斯

1
@Steven-什么更容易理解? myString.replaceAll(pattern, originalData);还是我在代码中粘贴了整个replaceAll方法,包括每次需要使用String的功能时都表示基本字符串对象的支持char数组?
jmort253

7
@Steven Jeuris:您的意思是正确的。如果一个类具有太多的功能(公共或私有),则可能整个都在尝试做太多事情。在这种情况下,最好将其拆分为几个较小的类,就像将太大的函数拆分为多个较小的函数一样。
gablin

22

是的,没有。基本原则是:一种方法只能做一件事而只能做一件事

将您的方法“分解”为较小的方法是正确的。再者,那些方法应该最佳地具有足够的通用性,不仅可以服务于“大”方法,而且可以在其他地方重用。在某些情况下,方法将遵循简单的树结构,例如调用InitializePlugins,InitializeInterface等的Initialize方法。

如果您有一个非常大的方法/类,通常表明它已经做了很多事情,并且您需要进行一些重构以分解“斑点”。Perphaps在抽象之下的另一个类中隐藏了一些复杂性,并且确实使用了依赖注入


1
很高兴看到不是每个人都盲目遵循“一件事”规则!; p不错的答案!
史蒂芬·杰里斯

16
我经常将大型方法分解为较小的方法,即使它们只会使用较大的方法。对其进行划分的原因在于,它变得更易于处理和理解,而不是仅仅拥有一大堆代码(可能会自我注释,也可能不会自我注释)。
gablin'2

1
在您确实无法避免使用大型方法的某些情况下,这也可以。例如,如果您有一个Initialize方法,则可以将其称为InitializeSystems,InitializePlugins等。人们应始终不断检查自己是否不需要重构
Homde 2011年

1
每个方法(或函数)的关键在于它必须具有清晰的概念界面,即“合同”。如果您不能确定这一点,那将会很麻烦,并且您的分解因数不正确。(明确记录合同可能是一件好事–或使用埃菲尔风格的工具来执行该合同,但它并不像最初存在时那样重要。)
Donal Fellows

3
@gablin:要考虑的另一个好处:当我将其分解为较小的功能时,不要以为“它们仅能提供其他功能”,而可以认为“它们现在仅能提供其他功能”。在很多情况下,重构大型方法在以后编写其他方法时为我节省了很多时间,因为较小的函数更通用且可重用。
GSto 2011年

17

我认为一般来说,最好是过多的函数而不是过多的函数。我在实践中看到的该规则的两个例外是DRYYAGNI原则。如果您有很多几乎完全相同的功能,则应将它们组合在一起并使用参数以避免重复您自己。如果“以防万一”创建了它们并且没有被使用,它们也可能具有太多的功能。我发现拥有仅在增加可读性和可维护性的情况下才使用一次的功能绝对没有错。


10

jmort253的出色答案激发了我继续回答“是的,但是”的答案。

拥有许多小型私有方法并不是一件坏事,但是要注意让您在此处提出问题的那种feeling琐感觉:)。如果到达要编写仅专注于私有方法的测试(通过直接调用它们或通过设置以某种方式调用一个场景以便可以测试结果的方案)的方式,那么您应该退后一步。

您可能拥有的是一个新类,它拥有自己的更加侧重于公共的界面,试图逃脱。到那时,您可能要考虑提取一个类以封装现有类功能中当前存在于私有方法中的那一部分。现在,您的原始类可以将新类用作依赖项,并且您可以直接针对该类编写更具针对性的测试。


我认为这是一个很好的答案,并且是代码肯定可以发展的方向。最佳做法是使用限制性最强的访问修饰符,该修饰符可为您提供所需的功能。如果未在类外使用这些方法,则私有方法最有意义。如果将这些方法公开或将它们移到另一个类使其更有意义,以便可以在其他地方使用它们,则也可以这样做。+1
jmort253

8

我参加过的几乎每个OO项目都因类太大,方法太长而遭受严重破坏。

当我需要注释来解释下一部分代码时,请将该部分提取到单独的方法中。从长远来看,这会使代码更短。这些小方法比您最初期望的要重用更多。您开始看到机会,将一堆机会收集到一个单独的班级中。很快,您就在更高层次上思考问题,而整个过程就快得多了。新功能更易于编写,因为您可以建立在通过这些小方法创建的小类上。


7

对于我来说,具有5个公共方法和25个私有方法的类似乎并不大。只要确保您的类具有明确定义的职责,并且不必过多担心方法的数量即可。

也就是说,那些私有方法应专注于整个任务的一个特定方面,而不是以“ doSomethingPart1”,“ doSomethingPart2”的方式进行。如果这些私有方法只是任意划分的长篇故事的一部分,而在整个上下文中却没有任何意义,那么您将一无所获。


+1表示“如果这些私有方法只是随意分割的长篇小说的一部分,而在整个上下文中它们都是毫无意义的,您将一无所获。”
Mahdi 2014年

4

摘自R. Martin的《清洁法》(第176页):

甚至对于消除重复,代码表达性和SRP等根本性的概念也可能被认为太过分了。为了使我们的类和方法变小,我们可能会创建太多微小的类和方法。因此,此规则[最小化类和方法的数量]建议我们也保持函数和类计数低。高等级的方法有时是无意义的教条主义的结果​​。


3

一些选项:

  1. 没关系。只需命名私有方法以及命名公共方法即可。并命名您的公共方法。

  2. 将其中的一些辅助方法抽象为辅助类或辅助模块。并确保这些助手类/模块命名正确,并且本身就是可靠的抽象。


1

我不认为“私有方法太多”的问题就在于它可能是不良代码体系结构的征兆。

这就是我的想法...如果架构设计合理,那么执行X的代码通常应该放在哪里。对此有几个例外是可以接受的-但这些例外必须是真正的例外,否则需要重构架构以将其作为标准来处理。

如果问题在于打开您的类时,其中充满了私有方法,这些方法会将较大的块分解为较小的代码块,那么这可能是缺少体系结构的征兆。代替类和体系结构,这部分代码已在一个类中以过程方式实现。

在此处找到平衡取决于项目及其要求。与之相反的说法是,初级程序员可以用50行代码(需要一个架构师50个类)来组合解决方案。


0

是否存在过多的私有功能/方法?

是。

在Python中,“私有”(由C ++,Java和C#使用)的概念实际上并不存在。

有一个“私有排序”的命名约定,仅此而已。

私有可能导致难以测试的代码。通过简单地关闭代码,它也可以破坏“开放-封闭原则”。

因此,对于使用Python的人来说,私有函数毫无价值。只要公开所有内容并完成即可。


1
如何打破开放封闭原则?无论是否将公共方法拆分为较小的私有方法,该代码都可以扩展进行扩展,也可以封闭进行修改。
byxor
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.