我的老板要求我停止编写小的函数,并在同一循环中完成所有操作


209

我已经读过罗伯特·C·马丁(Robert C. Martin)的一本名为《清洁代码》的书。在本书中,我看到了许多清理代码的方法,例如编写小的函数,仔细选择名称等。这似乎是迄今为止我所读过的最有趣的有关干净代码的书。但是,今天我的老板不喜欢我看完本书后写代码的方式。

他的论据是

  • 编写小函数很痛苦,因为它迫使您进入每个小函数以查看代码在做什么。
  • 即使主循环超过300行,也可以将所有内容放入主大循环中,以使其读取速度更快。
  • 仅在必须复制代码时编写小型函数。
  • 不要使用注释名称编写函数,而是将复杂的代码行(3-4行)放在注释上方;同样,您可以直接修改失败的代码

这与我读过的所有内容背道而驰。您通常如何编写代码?一个主要的大循环,没有小的功能?

我使用的语言主要是Javascript。由于我删除了所有小的明确命名的函数并将所有内容放入一个大循环中,因此我现在真的很难阅读。但是,我的老板喜欢这种方式。

一个例子是:

// The way I would write it
if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

// The way he would write it
// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

例如,在我读过的书中,注释被认为是无法编写干净的代码,因为如果您编写小的函数,它们就会过时,并且经常导致未更新的注释(您修改代码而不是注释)。但是,我要做的是删除注释,并使用注释的名称编写一个函数。

好吧,我想要一些建议,哪种方法/做法更适合编写简洁的代码?



4
phoneNumber = headers.resourceId?:DEV_PHONE_NUMBER;
约书亚

10
确认您想就位,管理层告诉您如何进行工作,而不是需要解决的问题。
康斯坦丁·彼得鲁赫诺夫

8
@rjmunro除非您真的喜欢您的工作,否则请记住,开发人员少于工作。因此引用马丁·福勒的话:“如果您不能更改组织,请更改组织!” 我建议老板应该告诉我,老板告诉我如何编码。
Niels van Reijmersdal

10
永远不要isApplicationInProduction()功能!您必须具有测试,并且如果您的代码与生产时的行为有所不同,则测试无用。就像有意在生产中使用未经测试/未发现的代码:这没有任何意义。
RonanPaixão16年

Answers:


215

首先以代码示例为例。您赞成:

if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

而您的老板将其写为:

// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

我认为,两者都有问题。在阅读您的代码时,我立即想到“可以用if三元表达式替换它”。然后,我读了您老板的代码,然后想:“为什么他用注释替换了您的功能?”。

我建议最佳代码介于两者之间:

phoneNumber = isApplicationInProduction(headers) ? headers.resourceId : DEV_PHONE_NUMBER;

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

这为您提供了两全其美的优势:简化的测试表达式,并将注释替换为可测试的代码。

关于老板对代码设计的看法:

编写小型函数很痛苦,因为它迫使您进入每个小型函数以查看代码在做什么。

如果函数命名正确,则不是这种情况。 isApplicationInProduction是不言而喻的,因此不必检查代码以查看其作用。实际上,事实恰恰相反:检查代码所显示的意图要比函数名称所揭示的要少(这就是您的老板必须诉诸注释的原因)。

将所有内容放入一个主要的大循环中,即使该主循环超过300行,读取速度也更快

扫描可能会更快,但是要真正“读取”代码,您需要能够有效地执行它。使用小的函数很容易,而使用100行的方法确实很难。

如果必须复制代码,则仅编写小函数

我不同意。如您的代码示例所示,名称小巧的函数可提高代码的可读性,并且应在例如您对功能的“方式”不感兴趣的情况下使用。

不要使用注释名称编写函数,而是将复杂的代码行(3-4行)与注释一起放在上面。这样,您可以直接修改失败的代码

假设它确实很严重,我真的不明白这背后的原因。我希望看到The Expert Beginner Twitter帐户以模仿形式编写的内容。注释有一个根本缺陷:注释没有被编译/解释,因此不能进行单元测试。代码被修改,注释独自留下,您最终不知道哪个是正确的。

编写自文档代码很困难,有时还需要补充文档(甚至以注释的形式)。但是,“鲍勃叔叔”的观点认为注释是编码失败,这种说法经常成立。

让您的老板阅读“清洁代码”书,并努力使代码的可读性降低,以使他满意。但最终,如果您不能说服他进行更改,则必须要么排队,要么找一个可以更好地编码的新老板。


26
小类函数是easilly单元测试
Mawg

13
答曰@ ExpertBeginner1:“我厌倦了看到吨的小方法遍布在我们的代码的地方,所以从这里走,有一个最低的所有方法15 LOC。”
格雷格·培根

34
“注释有一个根本的缺陷:它们不能被编译/解释,因此不能进行单元测试”在这里扮演魔鬼的提倡者,如果用“函数名”替换“注释”,这也是正确的。
mattecapu

11
@mattecapu,我将接受您的倡导,并将其加倍返回给您。任何旧的垃圾开发人员都可以在注释中随意使用,以描述一段代码的作用。用功能名称简洁​​地描述那段代码需要熟练的沟通者。最好的开发人员是熟练的沟通者,因为编写代码主要是与其他开发人员进行沟通,而与编译器则是次要问题。好吧,一个好的开发人员将使用命名函数,并且不加注释;糟糕的开发人员将他们的糟糕技能隐藏在使用评论的借口后面。
大卫·阿诺

4
@DavidArno所有函数都有前置条件和后置条件,问题是是否记录它们。如果我的函数采用的参数是以英尺为单位的距离,则必须以英尺而不是公里为单位。这是前提条件。
约根·福

223

还有其他问题

两种代码都不是好方法,因为两者基本上都是通过调试测试用例使代码膨胀。如果您出于任何原因要测试更多东西怎么办?

phoneNumber = DEV_PHONE_NUMBER_WHICH_CAUSED_PROBLEMS_FOR_CUSTOMERS;

要么

phoneNumber = DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY;

您是否要添加更多分支?

一个重要的问题是您基本上重复了部分代码,因此您实际上并没有在测试真实的代码。您编写调试代码来测试调试代码,而不是生产代码。这就像部分创建并行代码库。

您正在与老板争论如何更聪明地编写错误的代码。相反,您应该解决代码本身的固有问题。

依赖注入

这是代码的外观:

phoneNumber = headers.resourceId;

这里没有分支,因为这里的逻辑没有任何分支。该程序应从标题中提取电话号码。期。

如果您想拥有DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY一个结果,则应将其放入中headers.resourceId。一种方法是简单地headers为测试用例注入一个不同的对象(很抱歉,如果这不是正确的代码,我的JavaScript技能会有点生锈):

function foo(headers){
    phoneNumber = headers.resourceId;
}

// Creating the test case
foo({resourceId: DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY});

假定这headers是从服务器收到的响应的一部分:理想情况下,您有一台完整的测试服务器,该服务器可以提供headers各种用于测试目的的测试服务器。这样,您可以按原样测试实际的生产代码,而不是一半的重复代码可能会或可能不会像生产代码那样工作。


11
我确实考虑过用自己的答案来解决这个话题,但是感觉已经足够长了。因此为您+1 :)
David Arno

5
@DavidArno我打算将其添加为您的答案的注释,因为当我初次阅读该问题时,该问题仍处于锁定状态,令我惊讶的是它再次被打开,因此将其添加为答案。也许应该补充一点,有许多用于进行自动化测试的框架/工具。尤其是在JS中,似乎每天都有新的版本出现。但是可能很难将其卖给老板。
空值

56
@DavidArno也许您应该将答案分解为较小的答案。;)
krillgar 2016年

2
@ user949300使用按位或不是明智的;)
curiousdannii

1
@curiousdannii是的,注意到编辑太迟了……
user949300

59

对此没有“正确”或“错误”的答案。但是,基于36年的设计和开发软件系统的专业经验,我将发表自己的看法。

  1. 没有“自我记录代码”之类的东西。为什么?因为那种说法完全是主观的。
  2. 评论永远不会失败。什么一个失败在于不能在所有的理解代码没有评论。
  3. 一个代码块中的300条不间断的代码行是维护的噩梦,极易出错。这样的障碍强烈表明不良的设计和计划。

直接说说您提供的示例...将isApplicationInProduction()其放入自己的例程是一件很聪明的事情。今天,该测试仅是对“标头”的检查,可以在三元(?:)运算符中进行处理。明天,测试可能会更加复杂。此外,“ headers.resourceId”与应用程序的“生产状态”没有明确关系;我认为需要对这种状态的测试与基础数据脱钩。子程序将执行此操作,而三元程序则不会。另外,为什么 resourceId是对“生产中”的测试有用的评论。

注意不要太过“小而清晰的函数”。例行程序应该比“仅仅代码”更多地封装一个想法。我支持amon的建议,phoneNumber = getPhoneNumber(headers)并补充说getPhoneNumber()应该进行“生产状态”测试isApplicationInProduction()


25
评论之类的东西并不是失败。但是,注释几乎完全是它们应该解释的代码,或者只是方法/类/等之前的空注释块。肯定是失败的。
jpmc26 2016年

3
比起任何英语描述的功能以及处理和不处理的极端情况,可能有更小的代码和更易于阅读的代码。此外,如果将某个函数提取到其自己的方法中,则读取该函数的人将不知道其调用者正在处理或未处理哪些极端情况,并且除非该函数的名称非常冗长,否则检查调用者的某些人可能不知道哪个角落案例由功能处理。
超级猫

7
评论绝不是本质上的失败。注释可能是失败的,当注释不正确时也是如此。可以在多个级别上检测到错误的代码,包括在黑盒模式下的错误行为。只有认识到描述了两个模型并且其中一个是不正确的,才可以由人类理解在白盒模式下检测到错误的注释。
廷博

7
@Timbo您的意思是,“ ... 至少其中之一是不正确的。” ;)
jpmc26 2016年

5
@immibis如果你无法理解什么代码确实没有注释的,那么代码很可能不够清晰。注释的主要目的是阐明代码为什么要执行其所执行的操作。编码员向未来的维护者解释了他的设计。该代码永远无法提供这种解释,因此注释可以填补这些理解上的空白。
格雷厄姆

47

“实体绝不能成倍增加。”

- 奥卡姆剃刀

代码必须尽可能简单。错误喜欢隐藏在复杂性之间,因为它们很难发现。那么,什么使代码简单?

小单元(文件,函数,类)是个好主意。小型单位易于理解,因为您一次需要了解的东西较少。正常的人一次只能玩7个概念。但是大小不仅仅是用代码行来衡量。我可以通过“遍历”代码(选择短的变量名,使用“聪明的”快捷方式,将尽可能多的代码粉碎到一行上)来编写尽可能少的代码,但是最终结果并不简单。试图理解这样的代码更像是逆向工程,而不是阅读。

缩短功能的一种方法是提取各种辅助功能。当它提取出一个独立的复杂性时,这可能是一个好主意。孤立地讲,这种复杂性比嵌入无关的问题中要容易得多(管理和测试!)。

但是每个函数调用都有一定的认知负担:我不仅要了解当前代码的代码,还必须了解它如何与外部代码交互。我认为可以说,提取的函数比提取的函数引入了更多的复杂性。这就是您的老板所说的“ 小功能”的痛苦,因为它迫使您进入每个小功能以查看代码的作用。

有时,即使无聊的功能只有数百行,它们也容易理解。这通常会在初始化和配置代码中发生,例如,在没有拖放编辑器的情况下手动创建GUI时。您可以合理地提取任何独立的复杂性。但是,如果格式易读并且有一些注释,那么跟踪正在发生的事情确实不难。

还有许多其他复杂性指标:作用域中的变量数量应尽可能小。这并不意味着我们应该避免变量。这意味着我们应该将每个变量限制在需要的最小范围内。如果我们从不更改变量所包含的值,那么变量也会变得更加简单。

一个非常重要的指标是圈复杂度(McCabe复杂度)。它通过一段代码来测量独立路径的数量。每个条件,该数字都呈指数增长。每个条件循环都使路径数量加倍。有证据表明,得分超过10分太复杂了。这意味着可能得分为5的非常长的函数可能比得分为25的非常短且密集的函数更好。我们可以通过将控制流提取到单独的函数中来降低复杂度。

您的条件是一个可以完全提取的复杂性示例:

function bigFatFunction(...) {
  ...
  phoneNumber = getPhoneNumber(headers);
  ...
}

...

function getPhoneNumber(headers) {
  return headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;
}

这仍然非常有用。我不确定这是否会大大降低复杂度,因为这种条件不是非常有条件的。在生产中,它将始终走相同的道路。


复杂性永远不会消失。它只能随机播放。许多小事情比几件大事简单吗?这在很大程度上取决于情况。通常,有些组合感觉很不错。发现不同复杂性因素之间的折衷需要直觉和经验,还需要一点运气。

知道如何编写非常小的函数和非常简单的函数是一项有用的技能,因为在不知道替代方案的情况下您无法做出选择。盲目遵循规则或最佳实践,而不考虑如何将它们应用于当前情况,将导致最好的平均结果,最坏的情况是导致货运狂热的编程

那就是我不同意你老板的地方。他的论点不是无效的,但是“清洁代码”书也不是错误的。遵循老板的指导可能会更好,但是您正在考虑这些问题并试图找到更好的方法这一事实非常有希望。随着经验的积累,您会发现为代码找到合适的因式会更容易。

(注意:此答案部分基于Jimmy Hoffa The Whiteboard的Rationalable Code博客文章中的思想,该博客文章对使代码变得简单的内容提供了高级观点。)


我是将军,我很喜欢您的回复。我同意,但是对mcabes圈复杂度度量提出了质疑。从我所看到的情况来看,它并不能真正代表复杂性。
罗伯特·巴伦

27

罗伯特·马丁(Robert Martin)的编程风格是两极分化的。您会发现许多程序员,甚至是经验丰富的程序员,他们都会找到许多借口,以说明为什么“太多”的拆分太多,以及为什么将功能保持更大一点是“更好的方法”。但是,大多数这些“论据”通常是不愿意改变旧习惯和学习新知识的一种表达。

不要听他们的话!

只要您可以通过将一段代码重构为具有表达性名称的单独函数来保存注释,就可以这样做-这很可能会改善您的代码。鲍勃·马丁(Bob Martin)在其干净的代码本中没有做那么多的事情,但是我过去看到的导致维护问题的绝大多数代码包含了太大的功能,而不是太小的功能。因此,您应该尝试使用自描述名称编写较小的函数。

自动重构工具使提取方法变得容易,简单和安全。而且,请不要以为那些建议使用300行以上的代码编写功能的人会认真对待-这些人绝对没有资格告诉您应该如何编码。


53
“别听他们的话!” :由于OP 的老板要求停止拆分代码,因此OP应该避免您的建议。即使老板不愿改变他的旧习惯。还要注意,如先前的答案所强调,OP的代码和他老板的代码均写得不好,并且您(有意或无意)在答案中都没有提及。
Arseni Mourzenko '16

10
@ArseniMourzenko:并不是我们每个人都必须在老板面前屈服。我希望OP的年龄足够大,可以知道他什么时候必须做正确的事,或者什么时候他必须做老板说的话。是的,我并没有故意进入示例的细节,已经有足够的其他答案在讨论这些细节了。
布朗

8
@DocBrown同意。300课对整个班级都是有问题的。300行功能是淫秽的。
JimmyJames

30
我看过很多类,它们的长度超过300行,都是非常好的类。Java非常冗长,如果没有那么多代码,您几乎无法在类中做任何有意义的事情。因此,“一个类中的代码行数”本身并不是一个有意义的指标,甚至超过了我们认为SLOC对于程序员生产力而言是有意义的指标。
罗伯特·哈维

9
另外,我已经看到Bob叔叔的明智建议被误解和滥用,以至于我怀疑它是否对经验丰富的程序员有用。
罗伯特·哈维

23

在您的情况下:您需要一个电话号码。显而易见,如何获得电话号码,然后编写明显的代码。还是不清楚如何获取电话号码,然后为它编写方法。

在您的情况下,如何获取电话号码并不明显,因此您需要为其编写方法。实现不是很明显,但是这就是为什么要将其放入单独的方法中,因此只需要处理一次。注释将很有用,因为实现并不明显。

“ isApplicationInProduction”方法是毫无意义的。从您的getPhonenumber方法调用它并不会使实现变得更加明显,而只会使人们更难理解发生了什么。

不要编写小的函数。编写具有明确定义的目的并满足该明确目的的函数。

PS。我一点都不喜欢这种实现。它假定没有电话号码意味着它是开发版本。因此,如果生产中不存在电话号码,则您不仅不处理它,还替换一个随机电话号码。想象一下,您有10,000个客户,而17个没有电话号码,并且您在生产中遇到麻烦。应该直接检查您是在生产中还是在开发中,而不应从其他任何方面检查。


1
“不要编写小的函数。编写具有明确定义的目的并满足那个明确定义的目的的函数。” 是分割代码的正确标准。如果一个函数执行太多(如多个)完全不同的函数,则将其拆分。 单一责任原则是指导原则。
罗伯特·布里斯托

16

即使忽略了两种实现都不是那么好的事实,我也将注意到,这至少在抽象出单次使用琐碎函数的水平上本质上是一个品味问题。

在大多数情况下,行数不是有用的指标。

300行(甚至3000行)完全平凡的纯顺序代码(安装程序或类似的东西)很少会出现问题(但可能会更好地自动生成或作为数据表之类的东西),而100行嵌套循环又有很多复杂的问题您可能会在高斯消除法或矩阵求逆法中找到退出条件和数学运算,否则可能很难遵循。

对我而言,除非声明该事物所需的代码量比构成实现的代码量小得多,否则我不会编写单一使用函数(除非我有理由说要轻松进行故障注入)。有一个条件很少符合这个要求。

现在,我来自一个小型的核心嵌入式世界,在这里我们还必须考虑诸如堆栈深度和调用/返回开销之类的问题(这再次与这里所提倡的那种微小功能不符),这可能会使我的设计产生偏差决定,但是如果我在代码审查中看到该原始功能,它将得到旧版本的usenet响应。

品味是很难讲授的设计,只有真正的经验才能带来,我不确定是否可以将其简化为关于函数长度的规则,甚至圈复杂性也有其度量标准的局限性(有时候事情很复杂,但是您可以解决)。
这并不是说干净的代码不会讨论某些好东西,它确实可以讨论这些事情,而应该考虑本地定制和现有代码库的功能。

在我看来,这个特定的例子似乎是琐碎的细节,我会更关注更高层次的东西,因为这对轻松理解和调试系统的能力更为重要。


1
我坚决同意- 我需要将其包装成一个函数才能使用非常复杂的单行代码...我当然不会包装三元/默认值行。我已经包装了一个内衬,但是通常这是10个管道来解析某些内容的shell脚本,如果不运行它,代码将变得难以理解。
TemporalWolf's

15

不要将所有内容都放在一个大循环中,但是也不要经常这样做:

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

大循环的问题在于,当跨过许多屏幕时,很难真正看到其整体结构。因此,请尝试取出大块,理想情况下是具有单个职责并且可重复使用的块。

上面微小功能的问题在于,尽管原子性和模块性通常都很好,但是这可能太过分了。如果您不打算重复使用上述功能,则会损害代码的可读性和可维护性。要深入研究细节,您必须跳到函数而不是能够内联读取细节,并且函数调用所占用的空间几乎不会少于细节本身。

显然,在做得太多的方法与做得太少的方法之间可以找到一种平衡。除非要从一个以上的地方调用它,否则我绝不会分解上面提到的一个小函数,即使那样,我也会三思而后行,因为就引入新逻辑和实现而言,该函数并不那么重要。这样几乎不能保证有它自己的存在。


2
我知道,一个布尔布尔值的内衬很容易阅读,但仅靠它真的只能解释“正在发生什么”。我仍然编写包装简单三元表达式的函数,因为该函数的名称有助于说明“为什么”执行此条件检查的原因。当新手(或您自己在6个月内)需要了解业务逻辑时,这特别有用。
AJ X.

14

看来您真正想要的是:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER

对于任何阅读它的人来说,这应该是不言自明的:设置phoneNumber为,resourceId如果可用,则默认为,否则为默认值DEV_PHONE_NUMBER

如果您确实只想在生产环境中设置该变量,则应该使用其他更规范的应用程序范围内的实用程序方法(不需要参数)来确定从何处运行。读取这些信息的标题没有任何意义。


它的作用是自我解释的(有点猜测您使用的是哪种语言),但是发生的事情一点也不明显。显然,开发人员假定phoneNumber存储在生产版本中的“ resourceId”下,而resourceId不存在于开发版本中,并且他想在开发版本中使用DEV_PHONE_NUMBER,这意味着电话号码存储在一个奇怪的位置。标题,这意味着如果生产版本中缺少电话号码,事情将会变得很糟糕
gnasher729

14

让我直言不讳:在我看来,您的环境(语言/框架/类设计等)并不真正适合“干净”的代码。您将所有可能的事情混合在几行代码中,这些代码本来应该并不太紧密。知道一个功能resourceId==undef意味着您不在生产中,在非生产系统中正在使用默认电话号码,resourceId保存在某些“标题”中等等,这意味着什么功能?我假设headers是HTTP标头,因此您甚至可以将最终用户的决定权留给最终用户?

将其中的一部分分解为函数将不会对您解决该潜在问题有多大帮助。

要寻找的一些关键字:

  • 去耦
  • 凝聚
  • 依赖注入

您可以通过零代码行,通过转移代码的职责并使用现代框架(对于您的环境/编程语言可能存在或不存在)来实现所需的代码(在其他上下文中)。

根据您的描述(“主功能”中的300行代码),即使是单词“功能”(而不是方法)也使我认为您要实现的目标没有意义。在那种老式的编程环境中(即基本的命令式编程,结构很少,当然没有有意义的类,没有像MVC这样的类框架模式),做任何事情实际上没有多大意义。没有根本性的改变,您将永远无法摆脱困境。至少您的老板似乎允许您创建函数来避免代码重复,这是一个很好的第一步!

我知道代码的类型以及您所描述的程序员的类型都很好。坦率地说,如果是同事,我的建议会有所不同。但是,因为它是您的老板,所以您为此奋斗是没有用的。如果您仅部分地“做您的事”,而您的老板(可能还有其他人)像以前那样继续工作,不仅老板会否决您,而且添加代码确实会导致更糟糕的代码。如果您适应他们的编程风格(当然,仅在使用此特定代码库时),并尝试在这种情况下尽力而为,那么对最终结果可能会更好。


1
我100%同意这里有一些隐式组件应该分开,但是在不了解更多语言/框架的情况下,很难知道OO方法是否有意义。从纯函数式(例如Haskell)到纯命令式(例如C),解耦和单一职责原则都是重要的。如果老板允许,第一步是将主函数转换为调度程序函数( (例如大纲或目录),它们将以声明性方式(描述策略,而不是算法)阅读,并将工作分配给其他功能。
David Leppik '16

JavaScript是原型,具有一流的功能。它本质上是面向对象的,但不是经典意义上的,因此您的假设可能不正确。在YouTube上观看克罗福德影片的提示时间……
Kevin_Kinsey

13

“干净”是编写代码的目标之一。这不是唯一的目标。另一个值得的目标是同居。非正式地讲,共地域性意味着试图理解您的代码的人们无需四处走动即可查看您在做什么。使用命名良好的函数而不是三元表达式似乎是一件好事,但是取决于您拥有多少个此类函数以及它们位于何处,这种做法可能会变得很麻烦。我无法告诉您您是否已经越过界限,只能说如果人们在抱怨,您应该倾听,特别是如果这些人对您的就业状况有发言权的话。


2
“……除了要说人们在抱怨外,你应该倾听,尤其是那些人对你的就业状况有发言权的时候。” IMO这真是个坏建议。除非您是一个严重贫穷的开发人员,需要欣赏您能获得的任何工作,否则请始终遵循“如果您不能换工作,就换工作”的原则。永远不要迷恋一家公司;他们比您更需要他们,因此,如果他们没有提供您想要的东西,那就走到更好的地方。
David Arno

4
在我的职业生涯中,我已经移动了一些。我认为我从未有过与老板就如何编码达成100%的共识的工作。我们是具有自己背景和哲学的人。所以我个人不会因为一些我不喜欢的编码标准而辞职。(经理们似乎喜欢弯弯曲曲的命名约定,如果把这些留给我自己的设备,则与我编写代码的方式特别相反。)但是,您是对的,一个体面的程序员不必为继续工作而担心太多。
user1172763 '16

6

通常,使用小功能是一个好习惯。但是理想情况下,我相信引入函数应该要么将大的逻辑块分开,要么通过将其干掉来减小代码的整体大小。您提供的示例都使代码更长,并且需要更多的时间供开发人员阅读,而简短的选择并不能说明该"resourceId"值仅在生产中存在。像这样的简单事情在尝试使用它时既容易忘记又会令人困惑,特别是如果您还不熟悉代码库的话。

我不会说您绝对应该使用三元,与我一起工作的某些人更喜欢稍长的时间if () {...} else {...},这主要是个人选择。我倾向于使用“一行完成一件事”,但是我基本上会坚持使用代码库通常使用的任何方法。

当使用三进制时,如果逻辑检查使行太长或太复杂,则考虑创建一个命名良好的变量来保存该值。

// NOTE "resourceId" not present in dev build, use test data
let isProduction = 'resourceId' in headers;
let phoneNumber = isProduction ? headers.resourceId : DEV_PHONE_NUMBER;

我还想说,如果代码库扩展到300个行函数,那么它确实需要细分。但我建议您使用稍宽的笔触。


5

您提供的代码示例,您的老板是正确的。在这种情况下,最好使用一条清晰的界线。

通常,将复杂的逻辑分解成较小的部分对于提高可读性,代码维护以及子类具有不同行为(即使只是很小)的可能性来说更佳。

不要忽略这些缺点:函数开销,晦涩(函数不执行注释和函数名所暗示的意思),复杂的意大利面条逻辑,潜在的失效函数(一次创建是出于不再调用的目的)。


1
“函数开销”:由编译器决定。“模糊不清”:OP尚未表明这是检查该属性的唯一还是最佳方法;您也不能肯定。《复杂的意大利面条逻辑》:在哪里?“失效功能的潜力”:这种失效代码分析是垂手可得的成果,而缺乏它的开发工具链还不成熟。
Rhymoid

答案更多地集中在优点上,我也只想指出缺点。调用sum(a,b)之类的函数总是比“ a + b”更昂贵(除非该函数由编译器内联)。其余的缺点表明,过于复杂会导致其自身的一系列问题。错误的代码就是错误的代码,仅仅因为它被分解成较小的字节(或保存在300行循环中)并不意味着它更容易被吞噬。
Phil M

2

我可以想到至少两个赞成长函数的论点:

  • 这意味着每行都有很多上下文。一种形式化的方式:绘制代码的控制流程图。在函数入口和函数出口之间的顶点(〜=线)处,您知道所有传入的边。函数越长,存在的此类顶点就越多。

  • 许多小的功能意味着有一个更大,更复杂的调用图。在随机函数中选择一条随机行,然后回答问题“在哪条上下文中执行此行?” 调用图越大,越复杂,就越难,因为您必须查看该图中的更多顶点。

还有反对长功能的争论-想到单元可测试性。在一个和另一个之间进行选择时,请使用您的经验。

注意:我并不是说您的老板是对的,只是说他的观点可能并不完全没有价值。


我认为我的观点是好的优化参数不是函数长度。我认为从下面的角度来看,更有用的一种想法:在其他条件相同的情况下,最好能够从代码中读取业务逻辑和实现的高级描述。(如果可以找到相关的代码位,则始终可以阅读低级实现的详细信息。)


评论David Arno的答案

编写小型函数很痛苦,因为它迫使您进入每个小型函数以查看代码在做什么。

如果函数命名正确,则不是这种情况。isApplicationInProduction是不言而喻的,不必检查代码以查看其作用。实际上,事实恰恰相反:检查代码所显示的意图要比函数名称所揭示的要少(这就是您的老板必须诉诸注释的原因)。

该名称表明了返回值的含义,但是它并没有说明执行代码的效果(=代码的作用)。名称(仅)传达有关意图的信息,代码传达有关行为的信息(有时可以从中推断出意图的各个部分)。

有时您需要一个,有时则需要另一个,因此这种观察不会创建一个普遍适用的单方面决策规则。

将所有内容放入一个主要的大循环中,即使该主循环超过300行,读取速度也更快

扫描可能会更快,但是要真正“读取”代码,您需要能够有效地执行它。使用小的函数很容易,而使用100行的方法确实很难。

我同意您必须在脑海中执行它。如果您在一个大型功能中拥有500行功能,而在许多小型功能中具有500行功能,那么我不清楚为什么这样做会更容易。

假设有500行高度副作用的直线的极端情况,您想知道效果A是在效果B之前还是之后发生。在大功能情况下,使用Page Up / Down来定位两行,然后进行比较行号。在小功能的情况下,您必须记住效果在调用树中的何处发生,并且如果您忘记了,则必须花费大量的时间来重新发现此树的结构。

当遍历支持功能的调用树时,您还面临着确定何时从业务逻辑转到实现细节的挑战。我声称没有证据*,调用图越简单,进行区分就越容易。

(*)至少我对此很诚实;-)

再一次,我认为这两种方法都有优点和缺点。

如果必须复制代码,则仅编写小函数

我不同意。如您的代码示例所示,名称小巧的函数可提高代码的可读性,并且应在[例如]您对功能的“方式”不感兴趣的情况下使用。

您是否对“如何”或“什么”感兴趣,取决于您正在阅读代码的目的(例如,获得一般性想法与跟踪错误)。编写程序时,您无法读取代码的目的,并且您很有可能出于不同的目的而读取代码。不同的决策将针对不同的目的进行优化。

就是说,这可能是我最不同意老板观点的一部分。

不要使用注释名称编写函数,而是将复杂的代码行(3-4行)与注释一起放在上面。这样,您可以直接修改失败的代码

假设它确实很严重,我真的不明白这背后的原因。[...]注释有一个根本缺陷:注释未经编译/解释,因此无法进行单元测试。代码被修改,注释独自留下,您最终不知道哪个是正确的。

编译器仅比较名称是否相等,它们从不给您MisleadingNameError。另外,由于多个调用站点可能会按名称调用给定的功能,因此更改名称有时会更加艰巨且容易出错。评论没有这个问题。但是,这有点投机。为了真正解决这个问题,人们可能需要有关程序员是否更可能更新误导性注释和误导性名称的数据,而我没有。


-1

我认为,您所需功能的正确代码是:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER;

或者,如果您想将其拆分为一个函数,可能类似于:

phoneNumber = getPhoneNumber(headers);

function getPhoneNumber(headers) {
  return headers.resourceId || DEV_PHONE_NUMBER
}

但是我认为“生产中”的概念存在一个更根本的问题。函数的问题isApplicationInProduction在于,这是系统中唯一影响“生产”的地方,而且您总是可以依靠有无resourceId标头来告诉您,这似乎很奇怪。应该有一种isApplicationInProduction或多种getEnvironment直接检查环境的方法。该代码应如下所示:

function isApplicationInProduction() {
  process.env.NODE_ENV === 'production';
}

然后,您可以通过以下方式获取电话号码:

phoneNumber = isApplicationInProduction() ? headers.resourceId : DEV_PHONE_NUMBER;

-2

只是对其中两个要点进行评论

  • 编写小函数很痛苦,因为它迫使您进入每个小函数以查看代码在做什么。
  • 即使主循环超过300行,也可以将所有内容放入主大循环中,以使其读取速度更快。

许多编辑器(例如IntelliJ)将使您仅通过按住Ctrl键单击用法即可跳到一个函数/类。另外,您通常不需要知道函数的实现细节即可读取代码,从而使代码读取更快。

我建议你告诉老板。他会喜欢您的倡导并将其视为领导。保持礼貌。

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.