通常,我使用私有方法来封装可在类中的多个位置重用的功能。但是有时我有一个大型的公共方法,可以将其分解为更小的步骤,每个步骤都使用自己的私有方法。这会使公共方法更短一些,但是我担心强迫任何读取该方法的人跳到不同的私有方法会损害可读性。

对此有共识吗?拥有较长的公共方法,还是将其分解成较小的块(即使每个块都不可重用)会更好吗?

评论

可能的重复方法是否可以将长函数和方法拆分为较小的函数,即使它们不会被其他任何东西调用也可以吗?

所有答案均基于意见。原始作者始终认为他们的馄饨代码更具可读性;随后的编辑者将其称为馄饨。

我建议您阅读清洁代码。它推荐这种风格,并有很多示例。它还提供了解决“跳跃”问题的解决方案。

我认为这取决于您的团队以及所包含的代码行。

STRUTS的整个框架不是基于一次性使用的Getter / Setter方法,而所有方法都是一次性使用的方法吗?

#1 楼

不,这不是一个不好的风格。实际上,这是一个非常好的样式。

仅仅因为可重用性,就不需要存在私有功能。当然,这是创建它们的一个好理由,但是还有另一个原因:分解。请考虑一个功能太多的函数。它的长度为一百行,因此无法进行推理。它调用其他应具有描述性名称的函数。主要功能读起来就像一本书:先执行A,然后执行B,然后执行C,等等。它调用的功能只能在一个地方调用,但是现在它们变小了。任何特定功能都必须与其他功能沙盒化:它们具有不同的作用域。

将大问题分解为较小的问题时,即使那些较小的问题(功能)仅使用/解决一次,您也可以获得以下好处:


可读性。没有人可以阅读单片函数并完全理解它的功能。您可以继续对自己撒谎,也可以将其拆分成有意义的小块。
参考位置。现在变得不可能声明和使用变量,然后将其保留,并在100行之后再次使用。这些功能具有不同的范围。
测试。虽然只需要对班级中的公共成员进行单元测试,但也可能需要对某些私人成员进行测试。如果长功能的关键部分可能会从测试中受益,则不可能不将其提取到单独的功能中而对其进行独立测试。
模块化。现在您有了私有函数,无论是仅在此处使用还是可重复使用的函数,都可以将它们中的一个或多个提取到单独的类中。上一点,由于需要公共接口,因此单独的类也可能更易于测试。

将大代码分成更易于理解和测试的较小部分的想法是Bob叔叔的书Clean Code的重点。在撰写此答案时,该书已有9年的历史了,但今天和那时一样重要。

评论


不要忘记保持一致的抽象水平和良好的名称,以确保在方法中偷看不会使您感到惊讶。如果整个对象都隐藏在其中,请不要感到惊讶。

–candied_orange
17-6-5 17:43



有时拆分方法可能会很好,但保持联机状态也可以带来好处。例如,如果可以在代码块的内部或外部合理地进行边界检查,则将该代码块保持在行内即可轻松查看是否一次,多次或根本不进行边界检查。将代码块放入其自己的子例程中会使这些事情变得更加难以确定。

–超级猫
17年5月5日在16:29

“可读性”项目符号可以标记为“抽象”。这是关于抽象的。 “一口大小的块”只做一件事情,一件低级的事情,当您阅读或逐步使用公共方法时,您实际上并不关心这些细节。通过将其抽象为功能,您可以单步执行并继续关注大型事物方案/公共方法在更高层次上所做的工作。

–马修·金登(Mathieu Guindon)
17年5月5日在16:32

国际海事组织对“测试”项目符号表示怀疑。如果您的私人成员想接受测试,那么他们可能属于公共成员,属于自己的班级。当然,提取类/接口的第一步是提取方法。

–马修·金登(Mathieu Guindon)
17年5月5日在16:35

完全不考虑实际功能的情况下,仅通过行号,代码块和变量范围拆分功能是一个可怕的想法,我认为更好的替代方法是将其留在一个功能中。您应该根据功能拆分功能。请参阅DaveGauer的答案。您在回答中稍有暗示(提及名称和问题),但是我不禁觉得,鉴于此问题的重要性和相关性,这方面需要更加集中。

– Bernhard Barker
17年6月6日在12:52



#2 楼

这可能是个好主意!

我确实遇到了将较长的线性动作序列拆分为单独的函数的问题,纯粹是为了减少代码库中的平均函数长度: >现在,您实际上已经添加了源代码行,并大大降低了总可读性。特别是如果您现在在每个函数之间传递大量参数以跟踪状态。还可以!

但是,如果您设法将一个或多个行提取到一个服务于单个明确目的的纯函数中(即使仅被调用一次),则可以提高可读性: br />
function step1(){
  // ...
  step2(zarb, foo, biz);
}

function step2(zarb, foo, biz){
  // ...
  step3(zarb, foo, biz, gleep);
}

function step3(zarb, foo, biz, gleep){
  // ...
}


在现实世界中,这可能并不容易,但是如果您考虑足够长的时间,通常可以嘲笑一些纯功能。

当您拥有带有漂亮动词名称的函数,并且当您的父函数调用它们时,您会发现自己处在正确的轨道上,而整个内容实际上就像一段散文。
然后几周后回来添加更多功能时,您会发现实际上可以重用这些功能之一,然后,狂喜!多么奇妙的光芒四射的喜悦!

评论


我已经听到了很多年的论点,但它在现实世界中从未奏效。我专门讲一两个行函数,这些函数只能从一个地方调用。虽然原始作者始终认为它“更具可读性”,但随后的编辑者通常将其称为馄饨代码。一般来说,这取决于通话深度。

–弗兰克·希勒曼
17年5月5日下午17:00

@FrankHileman坦白说,我从未见过任何开发人员赞美其前任的代码。

– T. Sar
17年5月5日在17:13

@TSar以前的开发人员是所有问题的根源!

–弗兰克·希勒曼
17年6月6日在1:03

@TSar当我是以前的开发人员时,我什至从未称赞过以前的开发人员。

–IllusiveBrian
17年6月6日在13:48

分解的好处是可以编写:foo = compose(reglorbulate,deglorbFramb,getFrambulation); ;)

–Warbo
17年6月6日在14:04

#3 楼

答案是高度取决于情况。 @Snowman涵盖了拆分大型公共职能的积极意义,但记住您也应注意的一点也很重要,因为您应该正确地关注它。效果会使代码难以阅读且相当脆弱。当这些私有功能相互依赖时,尤其如此。避免具有紧密耦合的功能。
抽象是泄漏的。假装如何执行操作或如何存储数据无关紧要,但是在某些情况下,可以做到这一点,并且识别它们很重要。
语义和上下文很重要。如果您不能清楚地捕捉到功能名称的作用,则可以再次降低可读性并增加公共功能的脆弱性。当您开始创建具有大量输入和输出参数的私有函数时,尤其如此。从public函数中,您可以看到它调用了什么私有函数,而从private函数中,您看不到公共函数调用了什么。这可能会导致私有功能中的“错误修复”,从而破坏了公共功能。这并不意味着仍然不清楚其他人,但是可以很容易地说,在编写本文时必须在bar()之前调用foo()是很有意义的。您的公共职能可能会限制每个私有职能的可能输入。如果未正确捕获这些输入假设(很难记录所有假设),则某人可能会不正确地重用您的私有函数之一,从而将错误引入代码库中。耦合,不是绝对长度。

评论


忽略可读性,让我们看一下错误报告方面:如果用户报告MainMethod中发生了错误(很容易得到,通常包括符号,并且您应该有符号解引用文件),它为2k行(从不包含行号)在错误报告中),这是一种NRE,可能发生在其中的1k行中。但是,如果他们告诉我这发生在SomeSubMethodA中,并且发生在8行中,并且例外是NRE,那么我可能会发现并修复该问题足够容易。

– Der Kommissar
17年6月6日在16:23

#4 楼

恕我直言,提取代码块的价值纯粹是为了打破复杂性,这通常与以下两者之间的复杂性差异有关:代码的功能,包括如何处理极端情况,以及代码本身。

如果代码比说明复杂得多,则用函数调用可以使周围的代码更容易理解。另一方面,某些概念可以用计算机语言比用人类语言更可读地表达。例如,我认为w=x+y+z;w=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);更具可读性。

随着大型函数的分解,子函数的复杂性与其描述之间的差异将越来越小,并且进一步细分的优势将会降低。如果事情分裂到描述比代码复杂的程度,进一步的分裂会使代码更糟。

评论


您的函数名称具有误导性。 w = x + y + z上没有任何东西可以指示任何类型的溢出控制,而方法名称本身似乎暗示着其他情况。例如,函数的更好名称是“ addAll(x,y,z)”,甚至只是“ Sum(x,y,z)”。

– T. Sar
17-6-5 17:11



既然谈论的是私有方法,那么这个问题就不可能指的是C。无论如何,这不是我的意思-您可以调用相同的函数“ sum”,而无需处理方法签名上的假设。按照您的逻辑,例如,任何递归方法都应称为“ DoThisAssumingStackDoesntOverflow”。 “ FindSubstringAssumingStartingPointIsInsideBounds”也是如此。

– T. Sar
17-6-5 17:27



换句话说,基本假设,无论它们是什么(两个数字都不会溢出,堆栈不会溢出,索引正确传递)不应该出现在方法名称上。当然,如果需要的话,这些东西应该被记录下来,但是不要在方法签名上记录下来。

– T. Sar
17-6-5在18:29



@TSar:我对这个名称有些不满意,但是关键是(切换到平均,因为这是一个更好的示例)在上下文中看到“(x + y + z + 1)/ 3”的程序员比那些正在查看int average3(int n1,int n2,int n3)的定义或调用位置的人要好得多,这才知道这是否是计算给定调用代码的平均值的合适方法。拆分“平均”功能意味着,想要查看它是否适合需求的人将不得不在两个地方查看。

–超级猫
17-6-5在21:22



如果我们以C#为例,您将只有一个“ Average”方法,可以接受任何数量的参数。实际上,在c#中,它可以处理任何数字集合-而且我相信它比将粗糙的数学代码处理起来更容易理解。

– T. Sar
17年5月5日在22:48

#5 楼

这是一个平衡的挑战。

Finer更好

private方法有效地为所包含的代码提供了一个名称,有时还提供了有意义的签名(除非其参数的一半是完全临时的带有

给名称提供代码构造通常是好的,只要名称对调用者暗示有意义的约定,并且私有方法的约定准确地对应于名称所暗示的含义。

通过迫使自己考虑代码较小部分的有意义的契约,即使在进行任何开发测试之前,初始开发人员也可以发现一些错误并毫不费力地避免它们。这仅在开发人员力求简洁命名(听起来简单但准确)并且愿意适应私有方法的边界以便甚至可以进行简洁命名时才起作用。 ,因为附加的命名有助于使代码自记录。


压缩较小的代码段
高级方法有时会变成简短的调用序列,每个调用都可以重要的东西,并有明确的名字-伴随着薄薄的一般,最外层的错误处理。对于必须迅速成为模块整体结构专家的任何人来说,这种方法都可以成为宝贵的培训资源。过度给小块代码命名,并最终得到太多,太小的私有方法?当然。以下一种或多种症状表明方法太小而无法使用:


太多的重载不能真正代表相同基本逻辑的替代签名,而是一个固定的调用堆栈。
同义词仅用于重复引用相同的概念(“有趣的命名”)
许多敷衍性的装饰,例如XxxInternalDoXxx,尤其是在没有引入统一方案的情况下。

#6 楼

与其他人所说的相反,我认为长的公共方法是一种设计气味,不能通过分解为私有方法来纠正。可以分解成较小的步骤


如果是这种情况,那么我认为每个步骤都应该是自己的头等公民,每个人都应负有单一责任。在面向对象的范例中,我建议为每个步骤创建一个接口和一个实现,以使每个步骤都有一个易于识别的职责,并且可以以明确职责的方式来命名。这使您可以对(以前)大型的公共方法以及每个单独的步骤彼此进行单元测试。它们也都应记录在案。

为什么不分解为私有方法?原因如下:



紧密耦合和可测试性。通过减小公共方法的大小,可以提高其可读性,但是所有代码仍然紧密地耦合在一起。您可以对单个私有方法进行单元测试(使用测试框架的高级功能),但是不能轻易地独立于私有方法来测试公共方法。这违反了单元测试的原则。
类的大小和复杂性。您降低了方法的复杂度,但增加了类的复杂度。公共方法更易于阅读,但现在类却更难以阅读,因为它具有更多定义其行为的函数。我的首选是小型单一职责类,所以长方法表示该类做得太多。
不能轻易重用。通常情况下,作为代码主体成熟的可重用性很有用。如果您的步骤采用私有方法,则必须先以某种方式提取它们,然后才能在其他任何地方重用它们。此外,当需要在其他位置执行某个步骤时,它可能会鼓励复制粘贴。
以这种方式进行拆分可能是任意的。我认为,拆分长的公共方法不会像将职责分解为类那样花很多时间去思考或设计。每个类都必须使用适当的名称,文档和测试来证明其合理性,而私有方法并不需要太多考虑。
隐藏了这个问题。因此,您已决定将公共方法拆分为小型私有方法。现在没有问题!您可以通过添加越来越多的私有方法来不断添加越来越多的步骤!相反,我认为这是一个主要问题。它建立了一种增加类复杂性的模式,随后将进行后续的错误修复和功能实现。


不久,您的私有方法将变得越来越多,必须将它们拆分。但我担心强迫任何读取该方法的人跳到不同的私有方法会损害可读性/>

这是我最近与一位同事争论的话题。他认为,将模块的整个行为包含在相同的文件/方法中可以提高可读性。我同意这些代码在一起时更易于遵循,但是随着复杂性的增加,代码也变得不那么容易推理。随着系统的发展,对整个模块作为一个整体进行推理变得十分棘手。当您将复杂的逻辑分解为几个类,每个类具有单个职责时,则推理每个部分就变得容易得多。

评论


我将不同意。过多或过早的抽象会导致不必要的复杂代码。私有方法可能是可能需要进行接口和抽象的路径的第一步,但是您的方法建议在可能永远不需要访问的地方徘徊。

– WillC
17年6月8日在17:51

我知道您来自哪里,但是我认为代码闻起来就像一个OP询问的那样,这表明它已经准备好进行更好​​的设计。在您遇到问题之前,过早的抽象将在设计这些接口。

–塞缪尔
17年6月8日在19:48

我同意这种方法。实际上,当您遵循TDD时,这是自然而然的过程。另一个人说这是过早的抽象,但是我发现在一个单独的类中(在接口的后面)快速模拟所需的功能要比在私有方法中实际实现以便通过测试容易得多。

–永恒21
17年11月17日在12:41