主持人说明
此问题已经有十七个答案。在发布新答案之前,请阅读现有答案,并确保您的观点没有得到足够的掩盖。


我一直在遵循罗伯特·马丁(Robert Martin)的“干净的代码”这本书,特别是适用于我使用的软件类型的书以及对我有意义的书(我不遵循它作为教条)。

我注意到,但是,我编写的“干净”代码比没有遵循某些实践的代码更多。导致这种情况的具体做法是:


封装条件

因此而不是

>我可以编写一个像这样的小型方法

private Boolean isEmailValid(String email){...}



用另一个私有方法替换内联注释,以便该方法名描述自身而不是使用对其进行内联注释
一个类只应该有一个更改的理由

以及其他一些理由。关键是,可能有30行的方法最终成为一个类,这是因为微小的方法可以替换注释并封装条件等。当您意识到自己有这么多的方法时,它对将所有功能归为一类,而实际上它本来应该是一种方法。

我知道任何极端的做法都是有害的。

我正在寻找的一个具体问题是:

这是编写干净代码的可接受的副产品吗?如果是这样,我可以使用哪些论据来证明已编写了更多LOC这一事实呢?

组织并没有特别在意更多的LOC,但是更多的LOC可能会导致非常大的类(同样,出于可读性考虑,可以用长方法替换这些方法,而不必使用一堆一次性使用的辅助函数)。

当您看到一个足够大的课程时,会给人一种印象,那就是该课程很忙,并且其职责已经结束。因此,您最终可能会创建更多的类来实现其他功能。结果就是很多类,都借助于许多小的辅助方法来“做一件事”。

这是特定的问题...这些类可以是一个单一的类,仍然可以实现“一件事”,而无需使用许多小的方法。它可能是单个类,可能带有3或4个方法以及一些注释。

评论

如果您的组织仅将LOC用作代码基础的度量标准,那么证明干净的代码是没有希望的。

如果将可维护性作为您的目标,则LOC并不是判断的最佳指标-它是其中之一,但是要考虑的不仅仅是简单地保持简短。
这不是一个答案,而是要指出的一点:关于编写代码的整个子社区,使用的行/符号尽可能少。 codegolf.stackexchange.com人们可以争辩说,那里的大多数答案都不如他们想象的那么可读。

了解每一个最佳实践背后的原因,而不仅仅是规则自己。遵守规则是没有理由的。每个规则都有其自己的原因。
顺便说一句,并使用您的示例,有时将事情推到方法上会让您想到:“也许有一个库函数可以做到这一点”。例如,要验证电子邮件地址,可以创建一个System.Net.Mail.MailAddress来为您验证它。然后,您可以(希望)信任该库的作者以使其正确无误。这意味着您的代码库将具有更多的抽象,并减小其大小。

#1 楼


...我们是一个很小的团队,支持相对较大且未记录的代码库(我们继承了),因此一些开发人员/经理认为编写更少的代码来完成工作具有价值,因此我们需要维护的代码更少


这些人正确地识别出一些东西:他们希望代码更易于维护。尽管他们犯了错,但是他们假设代码越少,维护起来就越容易。到目前为止,获得易于更改的代码的最简单方法是对其进行全套自动化测试,如果您的更改不成功,该测试将失败。测试是代码,因此编写这些测试将扩大您的代码库。

其次,为了弄清楚需要更改的内容,您的代码既要易于阅读又要易于推理。非常简洁的代码,只是为了减少行数而缩小了大小,很难读取。显然有一个折衷的选择,因为更长的代码将需要更长的时间才能读取。但是,如果要更快地理解它,那是值得的。如果没有提供这种好处,那么冗长就不再是一种好处。但是,如果更长的代码提高了可读性,那么这又是一件好事。

评论


“到目前为止,获得易于更改的代码的最简单的方法是对其进行全套自动化测试,如果您所做的更改是一个重大突破,它将失败。”这是不正确的。对于每个行为更改,测试都需要额外的工作,因为测试也需要更改,这是设计使然,许多人认为更改更安全,但也必然使更改更难。

–杰克·艾德利(Jack Aidley)
19 Mar 18 '19 at 12:48

可以,但是与那些您无法诊断和修复测试所阻止的错误的时间相比,维护这些测试所花费的时间相形见war。

– MetaFight
19 Mar 18 '19 at 12:50

@JackAidley,必须与代码一起更改测试可能会带来更多工作,但前提是只有忽略了未经测试的代码更改会带来的难以发现的错误,而这些错误通常要等到发货后才能找到。后者仅提供较少工作的幻觉。

– David Arno
19年3月18日在13:00

@JackAidley,我完全不同意你的看法。测试使代码更易于更改。我将承认,设计得太糟糕的代码过于紧密地耦合在一起,因而很难与测试紧密耦合,这是很难改变的,但是结构良好,经过良好测试的代码在我的经验中很容易改变。

– David Arno
19 Mar 18 '19 at 13:29

@JackAidley您可以进行很多重构,而无需更改任何API或接口。这意味着您可以在修改代码时发疯,而不必在单元测试或功能测试中更改任何一行。也就是说,如果您的测试没有测试特定的实现。

–埃里克·杜米尼尔(Eric Duminil)
19年3月18日在13:53

#2 楼

是的,这是可以接受的副产品,其理由是它现在已经结构化,因此您不必在大多数时间都阅读大多数代码。您不是在每次更改时都读取30行功能,而是在读取5行功能以获取整体流程,如果更改涉及该区域,则可能需要几个辅助功能。如果您将新的“额外”类称为EmailValidator,并且您知道问题不在于电子邮件验证,则可以完全跳过阅读它。整个程序的行数。一个EmailValidator可以在所有地方使用。一些执行电子邮件验证但与数据库访问代码捆绑在一起的代码行无法重用。

然后考虑如果需要更改电子邮件验证规则,该怎么办?您是否愿意:一个已知的位置;或很多位置,可能会丢失一些?

评论


更好的答案就是疲倦的“单元测试解决了您所有的问题”

–德克·波尔(Dirk Boer)
19 Mar 19 '19 at 13:36



这个答案击中了Bob叔叔和朋友们似乎总是想念的一个关键点-仅当您不必阅读所有小的方法来弄清楚代码在做什么时,重构为小的方法才有用。创建一个单独的类来验证电子邮件地址是明智的。将代码迭代<_maxIterations引入到名为ShouldContinueToIterate的方法中是很愚蠢的。

– BJ Myers
19 Mar 20 '19在4:52



@DavidArno:“有用”!=“解决了所有问题”

–克里斯蒂安·哈克(Christian Hackl)
19年3月20日在12:17

@DavidArno:当有人抱怨暗示单元测试“解决了您的所有问题”时,他们显然意味着暗示单元测试解决了或至少有助于解决软件工程中几乎所有问题的人。我认为没有人会指责有人建议通过单元测试来结束战争,贫困和疾病。换一种说法是,(正确地)批评(不仅是对这个问题,而且对整个SE而言)许多答案中的单元测试都被极端高估了。

–克里斯蒂安·哈克(Christian Hackl)
19年3月21日在6:31

嗨,@ DavidArno,我的评论显然是个夸张而不是稻草人;)对我来说,这是这样的:我问如何固定汽车,宗教人士过来告诉我,我应该过少犯罪的生活。从理论上讲,有些事情值得讨论,但这并不能真正帮助我在修理汽车方面取得更好的进步。

–德克·波尔(Dirk Boer)
19年3月21日在9:25



#3 楼

比尔·盖茨的著名说法是:“用代码行来衡量编程进度就像按重量来衡量飞机制造进度。”这并不是说一个程序应该争取更多或更少的代码行,但这并不是最终创建一个正常运行的程序至关重要。可以记住,最终添加额外的代码行的原因是,从理论上讲,这种方式更具可读性。

关于特定更改是否可读性不同的观点可能存在分歧,但是我不相信认为对程序进行更改不会错,因为您认为这样做可以使程序更具可读性。例如,制作isEmailValid可能被认为是多余的和不必要的,特别是如果定义它的类恰好一次调用它。但是,我宁愿看到一个条件中的isEmailValid而不是一连串ANDed条件,因为我必须确定每个条件检查的内容以及为什么要检查它。

遇到麻烦的地方是创建时一种isEmailValid方法,该方法具有副作用或检查电子邮件以外的其他内容,因为这比简单地全部写出来要糟糕。更糟糕的是,因为它会误导您,并且我可能会因此而错过一个错误。您应该经常问自己,如果进行更改,是否更容易阅读,如果是这种情况,那就这样做!

评论


飞机重量是一个重要指标。在设计过程中,预期重量会受到密切监控。不是进步的标志,而是制约。监控代码行表明,越多越好,而在飞机设计中,越轻越好。因此,我认为盖茨先生可以为他的观点选择一个更好的例证。

– jos
19 Mar 18 '19 at 14:05

与特定团队OP一起工作的@jos,看来被认为“更好”的LOC更少。比尔·盖茨(Bill Gates)提出的观点是,LOC与进度没有任何有意义的联系,就像在飞机制造中,重量与进度没有任何意义一样。在建的飞机可能相对较快地达到了其最终重量的95%,但是这只是一个空壳,没有控制系统,还没有完成95%。在软件中也是如此,如果程序有10万行代码,并不意味着每1000行提供1%的功能。

– Mindor先生
19年3月18日在14:51

进度监视是一项艰巨的任务,不是吗?可怜的经理。

– jos
19年3月18日在15:45

@jos:在代码中,如果其他所有条件都相同,则最好为同一功能使用更少的行。

– RemcoGerlich
19年3月20日在12:49

@jos请仔细阅读。盖茨没有透露重量是否对飞机本身很重要。他说,重量是衡量飞机制造进度的可怕指标。毕竟,通过这种方法,一旦将整个船体抛在了地上,就可以基本完成了,因为这大概占整个飞机重量的9%。

– Voo
19年3月21日在9:04

#4 楼


,因此一些开发人员/经理认为编写更少的代码来完成工作具有价值,因此我们需要维护的代码更少


这是对实际目标视而不见的问题。

重要的是减少开发时间。这是按时间(或等效的工作量)而不是代码行来衡量的。尽管这在理论上是正确的,但汽车的市场价值并不由它拥有或没有多少螺丝来定义。最重要的是,汽车必须具有高性能,安全性和易于维护的特点。

其余的答案都是清洁代码如何导致时间节省的示例。


记录

获取没有记录的应用程序(A)。现在创建应用程序B,它与应用程序A相同,但带有日志记录。 B总是会有更多的代码行,因此您需要编写更多的代码。
对于应用程序A,开发人员将被困在阅读代码的过程中,并且必须不断重现问题并逐步遍历代码以查找问题的根源。这意味着开发人员必须在每个使用的层中从执行的开始到结束进行测试,并且需要观察每个使用的逻辑。
也许他很幸运能够立即找到它,但是也许答案

对于应用程序B,假设日志记录完美,开发人员将观察日志,可以立即识别出故障组件,并且现在知道要查找的位置。
br />
可以节省几分钟,几小时或几天。取决于代码库的大小和复杂性。


回归

使用完全不适合DRY的应用程序A。以应用程序B(它是DRY)为例,但是由于附加的抽象,最终需要更多行。对于应用程序B,开发人员根据更改请求更改(唯一,共享)逻辑。

对于应用程序A,开发人员必须在他记得使用该逻辑的情况下更改此逻辑的所有实例。 br />

如果他设法记住所有实例,那么他仍然必须多次实施相同的更改。
如果他不记得所有实例,那么您现在处理与自身矛盾的不一致的代码库。如果开发人员忘记了很少使用的代码段,则该错误可能直到很长一段时间才对最终用户显而易见。那时,最终用户是否会确定问题的根源?即使是这样,开发人员也可能不记得所做的更改,因此必须找出如何更改此被遗忘的逻辑。也许那时开发人员甚至不在公司工作,然后其他人现在必须从头开始解决所有问题。

这会导致大量的时间浪费。不仅在开发中,而且还在寻找和发现错误中。应用程序可能会以开发人员无法轻易理解的方式开始出现异常行为。这将导致冗长的调试会话。


Developer互换性

Developer创建的应用程序A。代码虽然不干净也不可读,但它的工作原理类似于魅力并已投入生产。毫不奇怪,也没有文档。

由于假期,开发人员A缺席了一个月。紧急更改请求已提交。开发人员迫不及待要再等三个星期。

开发人员B必须执行此更改。现在,他需要阅读整个代码库,了解一切工作原理,工作原理以及要完成的工作。这需要花很多时间,但是可以说他可以在三个星期内完成。

同时,应用程序B(由开发人员B创建)遇到了紧急情况。开发人员B被占用,但开发人员C可用,即使他不知道代码库也是如此。


如果我们让B在A上工作,而让C在B上工作,那么我们有两个开发人员不知道他们在做什么,并且
如果我们把B从A上拉开,让他去做B,现在我们把C放在A上,那么开发人员B的所有工作(或其中很大一部分)可能最终被丢弃。

Dev A休假回来,发现B不理解该代码,因此实施得很差。这不是B的错,因为他使用了所有可用资源,所以源代码只是不够可读。现在A是否需要花费时间来修复代码的可读性?


所有这些问题,以及更多的这些问题,最终导致浪费时间。是的,从短期来看,干净的代码现在需要付出更多的努力,但是当需要解决不可避免的错误/更改时,它将最终在将来分红。

管理层需要了解一项简短的任务现在将为您节省一些将来的繁重任务。无法计划就是计划失败。


如果是这样,我可以用什么论据来证明已经编写了更多LOC呢?


我的解释是向管理层询问他们希望使用什么:具有100KLOC代码库的应用程序可以在三个月内开发,或者具有50KLOC代码库的应用程序可以在六个月内开发。他们显然会选择较短的开发时间,因为管理层并不关心KLOC。专注于KLOC的经理在进行微观管理的同时,不了解他们要管理的内容。

#5 楼

我认为您在应用“干净的代码”实践时应格外小心,以免导致整体复杂性增加。过早的重构是许多不好的事情的根源。

将条件提取到函数会导致从中提取条件的地方简化代码,但会导致整体复杂性更高,因为您现在拥有一个函数从程序的更多点可见。您会在现在可以看到此新功能的所有其他功能上增加一点复杂性负担。

我并不是说您不应该提取条件,而只是在需要时应该仔细考虑。


如果要专门测试电子邮件验证逻辑。然后,您需要将该逻辑提取到一个单独的函数-甚至可能是类。
如果在代码的多个位置使用了相同的逻辑,则显然必须将其提取到单个函数。
如果逻辑显然是单独的责任,例如电子邮件验证发生在排序算法的中间。电子邮件验证的更改将独立于排序算法,因此它们应位于单独的类中。

在上述所有方面,这是提取不仅仅是“干净代码”的原因。此外,您可能甚至不会怀疑这样做是否正确。

如果有疑问,我想总是选择最简单,最直接的代码。

评论


我必须同意,在维护和代码审查方面,将每种条件转换为验证方法都会带来更多不必要的复杂性。现在,您必须在代码中来回切换,以确保您的条件方法正确。当您在相同条件下具有不同条件时会发生什么?现在,您可能会遇到一个命名梦night,其中包含几种小型方法,这些方法只会被调用一次,并且外观几乎相同。

–pboss3010
19年3月19日在11:44

最好的答案就在这里。特别是(在第三段中)观察到的复杂性不仅是整个代码整体的一个属性,而且还同时存在于多个抽象级别上并且有所不同。

–克里斯蒂安·哈克(Christian Hackl)
19年3月19日在12:17

我认为,表达这种情况的一种方法是,通常,只有在该条件有一个有意义的,不混淆的名称时,才应该提取条件。这是必要但不充分的条件。

– JimmyJames
19 Mar 19 '19在15:22

Re“ ...因为您现在有了一个可以在程序中更多点看到的函数”:在Pascal中,可以有局部函数-“ ...每个过程或函数可以具有自己的goto标签,常量声明,类型,变量以及其他过程和功能,...”

– Peter Mortensen
19-3-20在1:51



@PeterMortensen:在C#和JavaScript中也可以。太好了!但是重点仍然是,与内联代码片段相比,在更大的范围内可以看到一个函数,甚至是一个局部函数。

–雅克B
19 Mar 20 '19 6:42



#6 楼

我要指出的是,这没有天生的错误:

if(contact.email != null && contact.email.contains('@')


至少假设它已经使用过一次。问题很容易:

private Boolean isEmailValid(String email){
   return email != null && email.contains('@');
}


我要注意的一些事情:


为什么它是私有的?它看起来像一个潜在有用的存根。
我不会个人命名该方法IsValidEmail,可能不是ContainsAtSign或LooksVaguelyLikeEmailAddress,因为它几乎不进行任何实际验证,这也许很好,也许不是预期的。
它被使用了一次以上吗?如果不是团队中的特殊问题,那可能就不是我想要的东西了。

另一方面,我看到方法可以执行以下操作:

if (contact.email != null && contact.email.contains('@')) { ... }
else if (contact.email != null && contact.email.contains('@') && contact.email.contains("@mydomain.com")) { //headquarters email }
else if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") ) { //internal contract teams }


该示例显然不是DRY。

或者仅是最后一条语句就可以给出另一个示例:


目标应该是使代码更具可读性:

if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") )


另一种情况:

if (LooksSortaLikeAnEmail(contact.Email)) { ... }
else if (LooksLikeFromHeadquarters(contact.Email)) { ... }
else if (LooksLikeInternalEmail(contact.Email)) { ... }


如果这符合您的业务逻辑并且未被重用,则这里没有问题。

但是当有人问“为什么保存'@'时,这是不对的!”然后您决定添加某种形式的实际验证,然后将其提取!

当您还需要考虑总统的第二个电子邮件帐户Pr3 $ sid3nt @ h0m3!@ mydomain.com并决定全力以赴并支持RFC 2822时,您会感到高兴。

关于可读性:

public void SaveContact(Contact contact){
   if (contact.email != null && contact.email.contains('@'))
   {
       contacts.Add(contact);
       contacts.Save();
   }
}


如果您的代码如此清晰,则无需在此处添加注释。实际上,您不需要注释来说明代码在大多数时间在做什么,而是在说它为什么这样做:

// If there is an email property and it contains an @ sign then process
if (contact.email != null && contact.email.contains('@'))


对我来说,无论是if语句上方的注释还是小方法内的注释,都是很古怪的。我什至可能会反驳在另一种方法内使用有用的注释进行有用的相反操作,因为现在您必须导航到另一种方法,以了解其如何以及为什么执行其工作。这些东西;重点关注文本的构建原则(DRY,SOLID,KISS)。

// The UI passes '@' by default, the DBA's made this column non-nullable but 
// marketing is currently more concerned with other fields and '@' default is OK
if (contact.email != null && contact.email.contains('@'))


评论


对我来说,无论是if语句上方的注释还是小方法内的注释,都是很古怪的。这是一个“稻草打断了骆驼的背”的问题。您是对的,这件事并不难被完全理解。但是,如果您有一个大型方法(例如大型导入),其中包含许多这样的小型评估,则将它们封装在可读的方法名称中(IsUserActive,GetAverageIncome,MustBeDeleted等),将在读取代码时得到显着改进。该示例的问题在于,它仅观察到一根稻草,而不观察到破坏骆驼后背的整个捆。

–更
19 Mar 19 '19在8:38

@Flater,我希望这是读者从中获得的精神。

– AthomSfere
19 Mar 19 '19 at 12:02

这种“封装”是一种反模式,答案实际上证明了这一点。我们回来阅读代码是出于调试和扩展代码的目的。在两种情况下,了解代码的实际作用都是至关重要的。如果(contact.email!= null && contact.email.contains('@'))有问题,则代码块开始。如果if为false,则else if行都可以为true。这在LooksSortaLikeAnEmail块中根本不可见。包含一行代码的函数并不比注释解释一行的工作原理好多少。

– Quirk
19 Mar 19 '19在13:06



充其量,间接的另一层模糊了实际的机制,使调试更加困难。最糟糕的是,函数名称已变成谎言,就像注释变成谎言一样-内容已更新,但名称未更新。一般而言,这并不是针对封装的攻击,但是这种特殊用法是“企业”软件工程的一个伟大的现代问题的征兆-抽象的层和层将相关的逻辑掩盖起来。

– Quirk
19年3月19日在13:10

@quirk我认为您同意我的总体观点?有了胶水,您将面临一个完全不同的问题。在查看新的团队代码时,我实际上使用了代码映射。我见过的一些大型方法即使在mvc模式级别也调用了一系列大型方法,这是非常可怕的。

– AthomSfere
19 Mar 19 '19在13:13



#7 楼

考虑到您当前具有的“有效的电子邮件”条件将接受非常无效的电子邮件地址“ @”,我认为您有充分的理由抽象出EmailValidator类。更好的是,使用一个经过良好测试的良好库来验证电子邮件地址。

代码行作为度量标准是没有意义的。软件工程中的重要问题不是:


代码是否太多?
代码是否太少?

重要问题是:


应用程序整体设计是否正确?
代码是否正确实施?
代码是否可维护?
代码可测试吗? ?
代码是否经过了适当的测试?我问自己“我能不能更简洁地写这个?”,但出于可读性,可维护性和效率的目的,而不仅仅是长度。

您的问题实际上使我回想起我写的一些长布尔值,意识到我可能应该写一个或多个实用方法。

#8 楼

干净的代码是一本非常好的书,非常值得一读,但是它并不是这类事情的最终权威。

将代码分解为逻辑函数通常是一个好主意,但是很少有程序员这样做。 Martin的作用-在某些时候,将所有内容转换为函数会得到越来越少的回报,而当所有代码都成小块时,它就会变得难以遵循。新功能是简单地使用中间变量:

boolean isEmailValid = (contact.email != null && contact.emails.contains('@');

if (isEmailValid) {
...


这有助于使代码易于遵循,而不必在文件中四处跳转。 br />另一个问题是,“清洁代码”现在已经很老了。许多软件工程朝功能编程的方向发展,而Martin则竭尽全力向事物添加状态并创建对象。我怀疑如果他今天写的话,他会写完全不同的书。

评论


有些人担心条件附近的多余代码行(我一点也不在意),但也许可以在您的回答中解决。

– Peter Mortensen
19年3月19日在11:11

#9 楼

一方面,它们是正确的-更少的代码会更好。
盖特引用了另一个答案,我更喜欢:


“如果调试是去除软件错误的过程,则进行编程
– Edsger Dijkstra

“调试时,新手插入纠正性代码;专家会删除有缺陷的代码。”
– Richard Pattis

最便宜,最快和最可靠的组件就是那些不存在的组件。 -Gordon Bell


总之,您拥有的代码越少,出错的机会就越少。如果不需要某些东西,则将其剪切掉。如果存在过于复杂的代码,则将其简化,直到剩下所有实际的功能元素为止。功能,并且只需要最少的功能。它没有说明它的表达方式。

您尝试通过使用干净的代码所进行的操作与上述内容不符。您要添加到LOC中,但不添加未使用的功能。

最终目标是拥有可读的代码,而没有多余的额外功能。这两个原则不应该相互抵触。该代码的功能部分是底盘,发动机,车轮……是什么使汽车行驶。分解方式更像是悬架,动力转向等,它使操作变得更容易。
您希望机械师在执行工作的同时尽可能简单,以最大程度地减少出现问题的机会,但这并不能阻止您拥有漂亮的座位。

#10 楼

现有答案中有很多智慧,但是我想补充一个因素:语言。

某些语言比其他语言需要更多的代码才能达到相同的效果。特别是,尽管Java(我怀疑是问题所在的语言)非常著名,并且通常非常扎实,清晰和直接,但一些更现代的语言却更加简洁和富于表现力。

例如,在Java中,只需花费50行即可编写一个具有三个属性的新类,每个属性都具有一个getter和setter以及一个或多个构造函数,而您可以在一行Kotlin *或Scala中完全相同。 (如果您还希望使用合适的equals()hashCode()toString()方法,则可以节省更多的钱。)

结果是在Java中,额外的工作意味着您更有可能重用不包含该对象的通用对象。确实适合,将属性压缩到现有对象中,或单独传递一堆“裸”属性;而使用简洁,富于表现力的语言,则您更有可能编写更好的代码。处理代码行不是对前一种代码的不好衡量,但是与第二种代码无关。)

因此,正确完成工作的``成本''取决于语言。也许一种好的语言的标志就是不会让您在做得好和做简单之间做出选择!非常值得一看,恕我直言。)

#11 楼

假设您当前正在使用类Contact。您正在编写另一种验证电子邮件地址的方法,这一事实证明了Contact类未处理单一职责。


进一步证明您的代码是ContactEmail类的融合,这是您将无法轻松测试电子邮件验证代码。要使用正确的值以一种大方法获得电子邮件验证代码,将需要大量的操作。请参见下面的方法。您的验证代码,您只需用测试数据简单地调用Email.Validation()

#12 楼

已经发现,LOC的降低与缺陷的减少相关,仅此而已。假设任何时候降低LOC,就减少了缺陷的可能性,本质上是陷入相信关联等于因果关系的陷阱。降低的LOC是良好的开发实践的结果,而不是使代码变得更好的原因。那些编写更多代码来执行相同操作的人。这些熟练的开发人员为减少代码行而要做的是使用/创建抽象以及可重用的解决方案,以解决常见问题。他们不用花时间计算代码行数,也不必花时间在这里或那里打断代码。他们编写的代码通常比必要的更为冗长,而只编写很少的代码。

让我举一个例子。我不得不处理有关时间段的逻辑,它们如何重叠,它们是否相邻以及它们之间存在什么差距。当我第一次开始解决这些问题时,我会有无处不在的代码块来进行计算。最终,我建立了类来表示计算重叠,补码等的时间段和运算。这立即删除了大量代码,并将它们转变为几个方法调用。但是这些类本身根本不是写得很简洁。

简单地指出:如果您试图通过在此处或此处以更简洁的方式剪切一行代码来减少LOC,则您做错了。这就像通过减少所吃的蔬菜来减肥一样。编写易于理解,维护和调试的代码,并通过重用和抽象来减少LOC。

#13 楼

您已经确定了一个有效的折衷方法

,因此这里确实存在一个折衷方法,它是整个抽象所固有的。每当有人尝试将N行代码放入其自己的函数中以对其进行命名和隔离时,它们同时使读取调用站点变得更加容易(通过引用一个名称,而不是引用该名称背后的所有细节,)以及更复杂(您现在的意思已经纠缠在代码库的两个不同部分中)。 “容易”与“困难”相反,但它不是“简单”的同义词,而与“复杂”相反。两者不是相反的,抽象总是增加复杂性以插入某种形式或另一种形式。

当业务需求中的某些变化使抽象开始泄漏时,我们可以直接看到增加的复杂性。 。也许某些新逻辑会在预先提取的代码中间最自然地消失,例如,如果抽象的代码遍历了某些树,而您确实想在您访问时收集(或采取行动)某种信息遍历树。同时,如果您已对该代码进行了抽象,则可能会有其他调用站点,并且将所需的逻辑添加到方法的中间可能会破坏其他调用站点。看到,只要我们更改一行代码,我们只需要查看该行代码的直接上下文即可;当我们更改方法时,我们必须Cmd-F我们的整个源代码寻找由于更改该方法的约定而可能损坏的任何内容,或者希望测试能够为我们找到破绽。贪婪算法在这种情况下可能会失败

复杂性也使代码在某种意义上可读性较差,而不是更多。在之前的工作中,我处理了非常仔细且精确地构造为多层的HTTP API,每个端点均由控制器指定,该控制器验证传入消息的形状,然后将其交给某个“业务逻辑层”管理器,然后向“数据层”提出一些请求,该“数据层”负责对某个“数据访问对象”层进行多次查询,该层负责创建多个实际上可以回答您问题的SQL委托。我可以说的第一件事是,大约90%的代码是复制粘贴样板,换句话说,它是无操作。因此,在许多情况下,读取任何给定的代码通过都是非常“容易的”,因为“哦,这个管理员只是将请求转发到该数据访问对象。”但是事实是,您必须在所有这些不同的层之间跳转,并通读一堆样板来确定是否针对此特定情况进行了自定义,这意味着您需要进行大量的上下文切换和查找文件,并试图跟踪您不应该跟踪的信息,“在这一层上称为X,在另一层上称为X',然后在另一层上称为X”。

我想当我离开时,这个简单的CRUD API处于这样一个阶段:如果您以每页30行的价格打印它,那么它将在一个书架上占用10至20个五百页教科书:这是整个重复代码的百科全书。在基本复杂性方面,我不确定其中是否存在一半的基本复杂性教科书。我们可能只有5-6个数据库图来处理它。对其进行任何细微的更改都是一项艰巨的任务,学习这是一项艰巨的任务,添加新功能非常痛苦,以至于我们实际上拥有可用于添加新功能的样板模板文件。

因此,我亲眼看到了如何使每个部分都非常易读和明显,从而使整个部分变得非常不可读和不明显。这意味着贪婪算法可能会失败。您知道贪婪算法,是吗? “我将尽一切可能在本地改善局势,然后相信我会发现自己处于全球改善的情况。”这通常是一次美丽的尝试,但在复杂的环境中也可能会错过。例如,在制造过程中,您可能会尝试提高复杂制造过程中每个特定步骤的效率-进行更大的批量处理,对地板上的人大喊大叫,他们似乎无所事事而忙于其他事情-最佳实践:使用DRY和长度进行调用

(注意:本节标题有些像个玩笑;我经常告诉我的朋友,当有人说“我们应该做X,因为最佳实践这样说”时,他们有90%的时间不在谈论诸如SQL注入或密码哈希之类的东西,或者诸如此类的单方面最佳实践。可以将陈述翻译成90%的时间为“我们应该做X,因为我这样说。”就像他们可能从某些企业那里写了一些博客文章,用X而不是X'做得更好,但通常不能保证您的业​​务类似于该业务,并且通常还有其他一些业务其他公司在X'方面做得更好而不是X的文章。因此,请不要过于重视标题。)

我建议的依据是杰克·迪德里奇(Jack Diederich)的一次演讲,名为“停止写作课堂”(youtube.com)。他在讲话中提出了几点要点:例如,当一个类只有两个公共方法,而其中一个是构造函数/初始化程序时,您可以知道它实际上只是一个函数。但在一种情况下,他正在谈论他如何将字符串替换为假设的库,因为“ Muffin”声明了自己的类“ MuffinHash”,该类是Python内置dict类型的子类。该实现完全是空的,有人曾想过:“以后我们可能需要向Python字典添加自定义功能,以防万一,现在就引入一个抽象。”

他的反抗很简单, “如果需要的话,我们以后总是可以这样做。”插入一些可能会让我们将来开心的小东西。我们预期未来的需求。 “如果流量比我们预期的大100倍,则该方法将无法扩展,因此我们必须将前期投资投入到这种更难扩展的方法中。”非常可疑。

如果我们认真对待该建议,那么我们需要确定何时“以后”到来。可能最明显的事情是出于样式原因而确定事物长度的上限。而且我认为,剩下的最佳建议是将DRY(不要重复一遍)与这些有关线长的启发式方法结合使用,以弥补SOLID原理中的漏洞。基于30行是文本的“页面”并与散文进行类比的启发式方法,


当您要复制粘贴功能时,将其重构为功能/方法。就像偶尔有复制粘贴的正当理由,但您应该始终对此感到不舒服。真正的作者不会使您在整个叙述中重复阅读一个大而冗长的句子50次,除非他们真的想突出一个主题。
理想情况下,功能/方法应该是“段落”。大多数函数的长度应为半页左右,或1-15行代码,并且仅应允许将函数的10%分配为一页半,45行或更多。一旦您拥有120余行代码和注释,就需要将其分解为多个部分。大多数文件的长度应少于或等于12页,因此应包含360行代码和注释。也许只允许文件的10%到50页长,或1500行代码和注释。
理想情况下,大多数代码应缩进函数的基线或一层深度。根据对Linux源代码树的一些启发,如果您对它有信仰,则可能只有10%的代码在基准范围内缩进2级或更多,少于5%缩进3级或更多。尤其是这意味着需要“包装”其他问题的事物,例如大try / catch中的错误处理,应该从实际逻辑中拉出来。我对照当前的Linux源代码树对这些统计数据进行了测试,以找到这些近似百分比,但是在文学类比中它们也可以说是合理的。