函数可以太短吗?


125

每当我发现自己多次编写相同的逻辑时,我通常会将其粘贴在一个函数中,因此在我的应用程序中只有一个地方需要维护该逻辑。副作用是我有时会遇到一两个函数,例如:

function conditionMet(){
   return x == condition;
}

要么

function runCallback(callback){
   if($.isFunction(callback))
     callback();
}

这是偷懒还是不好的做法?我之所以只问是因为这会导致大量的函数调用需要非常小的逻辑。


3
不,不要太短。在C#中,我会将“满足条件”作为一个属性,这很优雅,并且会考虑使用Assert.AreEqual<int>(expected, actual, message, arg1, arg2, arg3, ...);。第二个也可以。我可能会包含一个可选的bool标志,该标志指示是否抛出异常/等。万一回调不是函数。
工作

38
是的,仅在一种情况下:function myFunction(){}
惊吓

5
def yes(): return 'yes'
Dietbuddha 2011年

3
@Spooks-我在这里劈毛,但空函数至少对适配器类有效,例如download.oracle.com/javase/6/docs/api/java/awt/event/…中的 MouseMotionAdapter 。当然,这只是解决语言限制的一种方法。
Aleksi Yrttiaho 2011年

3
@dietbuddha:要求刚刚更改-现在下周的演示必须支持两种语言。写下“ def yes()”的家伙感到非常高兴,然后将其更改为“ def yes(:(IsFrench()?'Oui':'Yes')“)
Andrew Shepherd

Answers:


167

呵呵,布朗先生,如果我能说服我遇到的所有开发人员将其功能保持在这种程度之内,请相信我,软件世界将是一个更好的地方!

1)您的代码可读性提高了十倍。

2)由于易于阅读,因此很容易弄清楚代码的过程。

3)干-不要重复自己-您对此非常满意!

4)可测试。与我们经常看到的那200条线方法相比,微小的功能要容易测试一百万倍。

哦,不必担心性能方面的“功能跳跃”。“发布”版本和编译器优化为我们很好地解决了这一问题,并且性能是系统设计中其他地方的99%。

这很懒吗?-恰恰相反!

这是不好的做法吗?- 绝对不。比起太常见的tar球或“上帝对象”,最好采用这种制造方法。

跟上我的工匠,继续努力;)


40
您不是在回答问题,而是在宣讲在这里质疑更极端应用的规则。

4
我很少会发现只有1次失败的局限性。但是,对于这个答案,+ 1似乎并不公平。
Bevan

4
+5 ...哦只能做+1,哦!为了支持MeshMan,我建议以下由Robert C. Martin Clean代码撰写的书。这本书确实在改变我现在编程的方式。
Eric-Karl

10
非常讨人喜欢的答案,但我不同意其中的大多数观点:1)极小的函数会导致总体上产生更多的代码,因为每个函数都必须进行所有的检查。这可能会降低可读性,尤其是对于一个非常大的项目。另外,取决于语言。2)完全取决于点1,因此与问题无关。3)什么?runCallback重复四次“回调”,并且三遍引用同一变量以运行单个函数。4)当然,无法测试200多种线的方法,但是从这一方法到一条线还有很长的路要走。
l0b0 2011年

10
-1:最可怕的是此答案的投票数。

64

我要说的是,重构方法太短了:

  • 它复制了原始操作,除使其成为方法外,没有其他目的:

例如:

boolean isNotValue() {
   return !isValue();
}

要么...

  • 该代码仅使用一次,其意图一目了然。

例如:

void showDialog() {
    Dialog singleUseDialog = new ModalDialog();
    configureDialog(singleUseDialog);
    singleUseDialog.show();
}

void configureDialog(Dialog singleUseDialog) {
    singleUseDialog.setDimensions(400, 300);
}

这可能是有效的模式,但在此示例中,我只是内联了configureDialog()方法,除非我打算覆盖它或在其他地方重用此代码。


7
在第二种情况下,分隔单行方法可以使调用方法的抽象级别保持一致。如果使用此推理,则提供的示例会出现在栅栏上:确切的像素宽度和高度是否过多,无法显示对话框?
2011年

2
“ isNotValue()”方法的名称很错误,应该对其进行重构以命名实际的域问题。

4
@Aleksi:我不同意;在此示例中,详细信息已隐藏在showDialog()方法中;无需对其进行双重重构。该示例旨在显示一个孤立的用例。如果在不同情况下需要使用不同对话框配置的模式,则在模式建立后返回并重构是有意义的。
RMorrisey 2011年

1
@Thorbjorn:当您回答一个业务逻辑不清晰的业务领域问题时,我可以看到一个案例。我只是指出,在某些情况下,它是过大的。
RMorrisey 2011年

3
也许这很挑剔,但是我认为在您的示例中问题不是功能太短,而是它们没有增加描述性的价值。
keppla 2011年

59

函数可以太短吗?一般没有

实际上,唯一的方法可以确保:

  1. 您已经找到设计中的所有类
  2. 您的功能仅做一件事。

是使您的功能尽可能小。或者,换句话说,从函数中提取函数,直到无法提取为止。我称此为“提取直至删除”。

对此进行解释:函数是具有通过变量进行通信的功能块的作用域。类也是具有通过变量进行通信的功能块的作用域。因此,长函数始终可以用小方法替换为一个或多个类。

同样,一个足以使您从中提取另一个函数的函数在定义上做的不只是一件事情。因此,如果可以从另一个函数中提取一个函数,则应该提取该函数。

一些人担心这会导致功能的扩散。他们是对的。它会。这实际上是一件好事。很好,因为函数有名称。如果您在选择好名字时很小心,则这些功能可以充当指示其他人通过您的代码的路标。的确,在命名空间中,在命名类中的命名函数是确保读者不会迷路的最​​佳方法之一。

在cleancoders.com上的Clean Code第三章中有很多关于此的内容


5
鲍伯叔叔!很高兴在这里见到您。不出所料,很棒的建议。我以前从未听过“抽精直到您放手”一词,并且可以肯定的是星期一在办公室使用此词;)。
Martin Blore

1
您愿意阐述您的第一点吗?以身作则解释会很棒。
丹尼尔·卡普兰

53

哇,这些答案中的大多数都不是很有帮助。

不能编写任何以其定义为身份的函数。也就是说,如果函数名称只是用英语写出的函数的代码块,则不要将其写为函数。

考虑一下您的函数conditionMet和另一个函数addOne(请原谅我生锈的JavaScript):

function conditionMet() { return x == condition; }

function addOne(x) { return x + 1; }

conditionMet是正确的概念定义;addOne重言式conditionMet之所以conditionMet有用,是因为您仅通过说“满足条件” 就不知道会发生什么,但是addOne如果您用英语阅读它,就会明白为什么很愚蠢:

"For the condition to be met is for x to equal condition" <-- explanatory! meaningful! useful!

"To add one to x is to take x and add one." <-- wtf!

为了爱任何可能仍然圣洁的事物,请不要编写重言式功能!

(并且出于同样的原因,不要在每一行代码中都写注释!)


一些更复杂的语言构造可能不熟悉或难以阅读,这使它成为一个有用的技巧。

3
同意,究竟要把什么确切地定义为一个函数,很大程度上取决于它所使用的语言。如果您使用的是VHDL之类的语言,实际上定义了在一个语言上添加一个语言的含义,那么addOne之类的功能将非常有用。二进制或数论级别。我想认为,没有一种语言具有如此神秘的含义,即使是经验丰富的程序员也很难阅读(也许是brainf#ck),但是如果是这种情况,由于该功能实际上是英语的翻译,因此同样的想法仍然有效。该语言的名称-因此名称与定义不同。
宫坂丽

1
我在谈论Haskell标准库中的事物身份功能 -我认为您不能再获得更多的“重言式” :)
hugomg

1
@missingno Bah,这是作弊:D
宫坂丽(Rei Miyasaka)

5
如果您根据功能的用途而不是内容来命名功能,它们将立即变得愚蠢。因此,您的示例可能是例如“是” function nametoolong() { return name.size > 15; }或“ function successorIndex(x) { return x + 1; } 所以”。因此,您的函数的问题实际上只是它们的名称不好。
汉斯·彼得·斯托尔2012年

14

我想说的是,如果您认为可以通过添加注释来改善某些代码的意图,则可以将代码提取到自己的方法中,而不是添加注释。无论代码多么小。

因此,例如,如果您的代码看起来像这样:

if x == 1 { ... } // is pixel on?

使它看起来像这样:

if pixelOn() { ... }

function pixelOn() { return x == 1; }

换句话说,这与方法的长度无关,而与自记录代码有关。


5
我尊敬的同事指出,您也可以写信if x == PIXEL_ON。使用常数可以像使用方法一样具有描述性,并且简短一些。
Tom Anderson

7

我认为这正是您想要做的。现在,该功能可能只有一两行,但随着时间的流逝它可能会增长。还有更多的函数调用,使您可以阅读函数调用并了解其中发生的情况。这使您的代码非常干(不要重复自己),这更易于维护。


4
那么,当您可以证明自己需要而不是现在只是当自重时,将其分解出来又有什么问题呢?
dsimcha 2011年

函数不会自行增长。恕我直言,例子很糟糕。conditionMet是一个太通用的名称,并且不带任何参数,因此它测试某些状态?(x == xCondition)在大多数语言和情况下都表现为'xConditition'。
用户未知,

是的,这就是为什么您希望代码中的开销超过75%?单线书写为3行实际上是300%。
编码员

5

我同意我所看到的所有其他帖子。这是很好的风格。

这种小的方法的开销可能为零,因为优化程序可以优化调用并内联代码。像这样的简单代码可以使优化器尽其所能。

为了清晰和简单起见,应编写代码。我试图将方法限制为以下两个角色之一:决策;或执行工作。这可能会生成一种方法。我做的越好,代码越好。

像这样的代码倾向于具有高内聚性和低耦合性,这是良好的编码实践。

编辑:关于方法名称的说明。使用方法名称,该名称指示什么方法不执行该操作。我发现verb_noun(_modifier)是一个很好的命名方案。这将提供类似于Find_Customer_ByName的名称,而不是Select_Customer_Using_NameIdx。修改方法时,第二种情况容易变得不正确。在第一种情况下,您可以换出整个Customer数据库实施。


+1表示内联,imo在性能和短函数方面是主要考虑因素。
Garet Claborn

4

将一行代码重构为一个函数似乎是多余的。可能会有例外情况,例如ver loooooong / comples line或expessions,但是除非我知道该功能将来会增长,否则我不会这样做。

您的第一个示例暗示了使用全局变量(可能会或可能不会提及代码中的其他问题),我将对其进行进一步的重构,并将这两个变量作为参数:

function conditionMet(x, condition){
   return x == condition;
}
....
conditionMet(1,(3-2));
conditionMet("abc","abc");

conditionMet例子可能如果条件是长和重复,例如有用:

function conditionMet(x, someObject){
   return x == ((someObject.valA + someObject.valB - 15.4) / /*...whole bunch of other stuff...*/);
}

1
他的第一个例子并没有暗示全局变量。如果有的话,这是高度内聚的类中的内聚方法,其中大多数变量都在对象上。
Martin Blore

7
conditionMet函数只是一个冗长的==运算符。这可能毫无用处。

1
我绝对不使用全局变量。@MeshMan,这就是示例的确切位置……类上的方法。
马克·布朗

1
@Mark Brown:@MeshMan:好的,我想代码示例太含糊了,无法确定...
FrustratedWithFormsDesigner

我在conditionMet (x, relation condition) {这里闻到一个气味,您在此处传递x,'=='和关系。然后,您无需重复代码“ <”,“>”,“ <=”,“!=”等。另一方面,大多数语言都在操作符(x ==条件)执行这些操作时内置了这些结构。原子语句的功能还差一步。
用户未知,

3

考虑一下:

一个简单的碰撞检测功能:

bool collide(OBJ a, OBJ b)
{
    return(pow(a.x - b.x, 2) + pow(a.y - b.y, 2) <= pow(a.radius + b.radius, 2));
}

如果您一直在代码中编写“简单”的衬纸,最终可能会犯错。另外,一遍又一遍地写它真的很折磨。


如果我们在这里处理C,我们可能会滥用#define;)
科尔·约翰逊

如果您的尺寸或距离变得很大,您甚至可能希望将其替换为较慢但防溢出的伪造。显然,这一次容易得多。
马特·克劳斯

2

不,这很少有问题。现在,如果有人觉得任何功能都不能超过一行代码(如果只是那么简单),那将是一个问题,并且在某种程度上是懒惰的,因为他们没有考虑什么是合适的。


我不会在代码行中计算它,而是在表达式中计算它。“(x == condition)”是原子的,因此,如果多次使用它并且可能会更改(例如function isAdult () = { age >= 18; }但肯定不是),我只会将其放入方法/函数中isAtLeast18
用户未知,

2

我会说它们太短了,但这是我的主观意见。

因为:

  • 如果仅使用一次或两次,就没有理由创建一个函数。跳到防御吸吮。特别是使用速度惊人的VS和C ++代码。
  • 类概述。当您有成千上万的小功能时,它使我生气。当我可以查看类定义并快速查看它的功能而不是SetXToOne,SetYToVector3,MultiplyNumbers和100个setters / getter时,我感到很满意。
  • 在大多数项目中,这些助手在一个重构阶段或两个重构阶段后变得无用,然后您执行“搜索全部”->删除操作以删除过时的代码,通常占代码的25%以上。

长的函数是不好的,但是短于3行并且仅执行1件事的函数同样是不好的恕我直言。

因此,我想只在以下情况下编写小型函数:

  • 3行以上的代码
  • 是否有初级开发人员可能会错过的东西(不知道)
  • 进行额外验证
  • 已使用或将使用至少3倍
  • 简化常用界面
  • 在下一次重构期间不会成为自重
  • 有一些特殊的含义,例如模板专门化或其他
  • 做一些隔离工作-const引用,影响可变参数,是否进行私有成员检索

我敢打赌,下一个开发人员(高级)将比记住所有SetXToOne函数有更好的事情要做。因此,无论哪种方式,它们很快都会变成自重。


1

我不喜欢这个例子。1,因为第i个通用名称。

conditionMet似乎不是通用的,所以代表特定条件吗?喜欢

isAdult () = { 
  age >= 18 
}

这样很好 这是语义上的差异,而

isAtLeast18 () { age >= 18; } 

对我不好。

也许它经常被使用,并且可以在以后进行更改:

getPreferredIcecream () { return List ("banana", "choclate", "vanilla", "walnut") }

也可以 多次使用它,如果需要的话,您只需要更改一个位置即可-也许明​​天就可以使用生奶油了。

isXYZ (Foo foo) { foo.x > 15 && foo.y < foo.x * 2 }

不是原子的,应该给您一个不错的测试机会。

当然,如果您需要传递一个函数,则传递您喜欢的任何东西,然后编写看上去很傻的函数。

但是总的来说,我看到的函数太多了,而太短了。

最后一句话:有些函数看起来很恰当,因为它们写得太冗长了:

function lessThan (a, b) {
  if (a < b) return true else return false; 
}

如果您看到它与

return (a < b); 

你不会有任何问题

localLessThan = (a < b); 

代替

localLessThan = lessThan (a, b); 

0

没有代码就没有代码!

保持简单,不要使事情复杂化。

这不是偷懒,它正在完成您的工作!


0

是的,可以使用短代码功能。如前面提到的答案,在使用“ getters”,“ setters”,“ accesors”的方法时非常常见。

有时,那些短的“ accesors”函数是虚拟的,因为当在子类中重写这些函数时,它们将具有更多的代码。

如果您不希望函数这么短,那么在许多函数(全局或方法)中,我通常使用“结果”变量(pascal样式)而不是直接返回,这在使用调试器时非常有用。

function int CalculateSomething() {
  int Result = -1;

   // more code, maybe, maybe not

  return Result;
}

0

太短永远都不是问题。函数短的一些原因是:

可重用性

例如,如果您有一个函数(如set方法),则可以断言它以确保参数有效。必须在设置了变量的所有位置进行此检查。

可维护性

您可能会使用一些您认为将来可能会改变的声明。例如,您现在在列中显示一个符号,但稍后可能会更改为其他符号(甚至发出哔声)。

均匀度

您正在使用例如外观模式,而函数唯一要做的就是将函数准确地传递给另一个函数而无需更改参数。


0

给代码段命名时,本质上是给它命名。这可能是由于多种原因造成的,但是关键是要给它起一个有意义的名称,该名称可以添加到程序中。诸如“ addOneToCounter”之类的名称不会添加任何内容,但是conditionMet()会添加任何内容。

如果您需要一个规则来确定代码片段太短或太长,请考虑需要多长时间才能找到代码片段的有意义的名称。如果您不能在合理的时间内完成,则该代码段的大小不合适。


0

不,但是可能太简洁了。

切记:代码只写一次,但是读过很多遍。

不要为编译器编写代码。为将来必须维护您的代码的开发人员编写它。


-1

是的,亲爱的,功能可能会越来越小,它的好坏取决于您使用的语言/框架。

在我看来,我主要从事前端技术方面的工作,小型函数通常用作辅助函数,当您使用小型过滤器并在整个应用程序中使用相同的逻辑时,您必然会像很多人一样使用它们。如果您的应用程序具有太多的通用逻辑,那么将会有大量的小功能。

但是,在没有通用逻辑的应用程序中,您不一定会做一些小功能,但是您可以将代码分成几个部分,以便于管理和理解。

通常,将巨大的代码分解为小函数是一种很好的方法。在现代框架和语言中,您一定会这样做,例如

data => initScroll(data)

是ES 2017 JavaScript和Typescript中的匿名函数

getMarketSegments() {
 this.marketService.getAllSegments(this.project.id)
  .subscribe(data => this.segments = data, error => console.log(error.toString()));
}

在上面的代码中,您可以看到3个函数声明和2个函数调用,这是Angular 4中带有Typescript的简单服务调用。您可以将其视为您的要求

([] 0)
([x] 1)
([x y] 2)

以上是Clojure语言中的3个匿名函数

(def hello (fn [] "Hello world"))

上一个是Clojure中的功能声明

因此,是的,FUNCTIONS可以更小,但是如果您具有以下功能,则它的好坏:

incrementNumber(numb) { return ++numb; }

好吧,这样做不是一个好习惯,但是如果您在Angular Framework中像在Angular Framework中那样在HTML标记中使用此函数,并且如果不支持Angular HTML模板中的Increment或Decrement,那将是解决方案。我。

让我们再举一个例子

insertInArray(array, newKey) {
 if (!array.includes(newKey)) {
   array.push(newKey);
 }
}

上面的示例是在Angular HTML模板中播放数组时必须使用的示例。因此有时您必须创建一些小函数


-2

我不同意这个时候给出的答案。

最近,我找到了一个同事,该同事编写所有的类成员,如下所示:

 void setProperty(int value){ mValue=value; }
 int getProperty() const { return (mValue); }

在零星的情况下,这可能是一个很好的代码,但是如果您定义了许多具有许多属性的类,那么肯定不是。我不想这样说,不得编写上述方法。但是,如果您最终将大多数代码编写为真实逻辑的“包装器”,则可能有问题。

程序员可能会错过总体逻辑,担心将来的更改以及重构代码。

接口的定义是因为有实际需要;实际上,如果您需要设置并获得一个简单的属性(没有太多逻辑,这会使成员更短),则应该可以实现。但是,如果可以用不同的方式来分析实际需求,为什么不定义一个更直观的界面呢?

要真正回答这个问题,当然不是,原因非常明显:应根据需要定义方法/属性/警告。即使是虚的虚函数也有理由存在。


6
添加访问器/更改器方法是有充分理由的,您可以在“担心将来...重构”下进行暗示。但是您也很对,它不会为代码增加价值。既不减少大小(本地或全局),也不增加可读性。在我看来,这说明Java和JavaBeans约定中的语言设计都很差。这是C#(许多方面)成功改进语言以解决自动属性语法这两个问题的一个领域。作为Java程序员,我同意您的基本结构式类的原始风格。
ANM

1
Getter / setter函数很好,即使它们现在所做的只是读取或写入变量。实际上,您可以想象一个场景,稍后您决定每次字段更改时在屏幕上打印一个值,或者检查该值是否有效。(也许它是分数的分母,您需要检查以确保它不为零。)
Miyasaka Rei

2
尽管有有效的用法,但getter和setter却很糟糕。如果有必要使用它们,我认为这是该语言的失败。
卡森·迈尔斯,

@Rei:为什么外部类需要检查分母的值?这应该通过Fraction类的divid()函数完成,或者Fraction类应提供isDivisible()来检查空分母。需要使用吸气剂/吸气剂通常是一种代码气味,说明您的班级耦合度高和/或凝聚力低(即不好)。
Lie Ryan

@李什么?我从来没有说过应该使用外部类来检查分母。我的意思是,外部类可能会偶然将分母设置为0。在设置程序中进行有效性检查的目的是鼓励更早发现使用者代码中的任何错误。提供一个isDivisible()函数可能无法调用也无法达到该目的。到底需要吸气剂/设定剂如何表明存在高耦合?
宫阪丽
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.