哪种流行的“最佳实践”并不总是最好的,为什么?[关闭]


100

“最佳实践”在我们行业中无处不在。一对“编码的最佳实践”谷歌搜索变成了近150万个结果。这个主意似乎给很多人带来了安慰。只需按照说明进行操作,一切都会很好。

当我读到一种最佳实践时(例如,最近我刚刚阅读了“ 清洁代码”中的几本),我感到很紧张。这是否意味着我应该始终使用这种做法?有附带条件吗?在某些情况下可能不是一个好习惯吗?在我进一步了解该问题之前,如何确定?

清洁代码中提到的几种做法并不适合我,但老实说,我不确定这是否是因为它们可能不好,或者这仅仅是我个人的偏见。我确实知道,技术行业中的许多杰出人士似乎都认为没有最佳实践,因此至少我那令人烦恼的疑问使我成为了一个好公司。

我已经阅读的最佳实践的数量太多了,无法在此处列出或询问个别问题,因此我想将其表述为一个一般性问题:

在某些情况下,哪些通常被称为“最佳实践”的编码实践可能不是最佳选择,甚至是有害的?这些情况是什么?为什么使这种做法不佳?

我希望听到有关具体示例和经验的信息。


8
您有哪些不同意的做法?
塞尔吉奥·阿科斯塔

任何人都可以写一本书,而我不必同意-就是这么简单。
Job

我喜欢史蒂夫·麦康奈尔(Steve McConnell)的书《代码完成》(Code Complete),一件事是他用确凿的证据和研究来支持他的所有技巧。只是说
JW01

5
@沃尔特:这已经开放了几个月,绝对是建设性的,为什么要关闭它?
2011年

2
好像在这里提到我的名字一样,我想加入一下:我相信这里的答案是有价值的,但是这个问题可以改写成一定程度的民意测验,而不会使任何答案无效。示例标题:“哪些流行的“最佳做法”有时可能有害,何时/为什么?
2011年

Answers:


125

我想你已经把这句话打在了头上

我讨厌把事情看成是面子,而不是批判地考虑

当它没有解释为什么存在时,我几乎忽略了所有最佳实践

雷蒙德陈说得最好在这篇文章中,他说:

好的建议带有基本原理,因此您可以分辨出什么时候变为坏的建议。如果您不理解为什么应该做某事,那么您就陷入了对货物崇拜编程的陷阱,即使不再需要甚至变得有害时,您也将继续这样做。


4
精彩的报价。
David Thornley 2010年

Raymond Chen引用的下一段可能描述的是匈牙利符号!我看到的大多数公司都没有充分的理由来解释它。
克雷格

3
但是,我希望人们不要以此为借口不去了解最佳实践背后的原因。;)不幸的是,我见过开发人员有这种态度。
Vetle

3
理由是好的。研究更好。
乔恩·普迪

7
绝对正确,我之前也说过。也许任何“标准”文档中的第一个标准都应写为:“为了共享知识并在以后提供废除标准所需的信息,所有标准都应包括其存在的原因。”
Scott Whitlock 2010年

95

最好将其放入环中:

Premature optimization is the root of all evil.

不,这不对。

完整报价:

“我们应该忘记效率低下的问题,大约有97%的时间是这样:过早的优化是万恶之源。但是,我们不应该在这3%的临界水平上放弃机会。”

这意味着您可以在整个设计过程中利用特定的战略性能增强功能。 这意味着您将使用与性能目标一致的数据结构和算法。这意味着您了解影响性能的设计注意事项。但这也意味着您这样做时不会轻率地进行优化,这样做只会给您带来可观的收益,但会牺牲可维护性。

需要对应用程序进行良好的架构设计,以免在您向它们施加少量负载然后最终重写它们时,它们不会在最后崩溃。缩写词的危险在于,开发人员经常以它为借口,根本不考虑性能,直到最后对它做任何事情都为时已晚。最好从一开始就建立良好的性能,前提是您不关注细节。

假设您正在嵌入式系统上构建实时应用程序。您选择Python作为编程语言,因为“过早的优化是万恶之源”。现在,我对Python没有任何了解,但这一种解释语言。如果处理能力有限,并且需要实时或近乎实时地完成一定数量的工作,并且您选择的语言需要比您的工作具有更大处理能力的语言,那么您就很费力。现在必须从一种精通的语言开始。


4
+1为强否,不是。
斯蒂芬

21
但是,如果您已经意识到某个特定的优化在关键的3%之内,那么您是否还为时过早?
约翰

7
@罗伯特:那么与“过早的优化是万恶之源”这一说法有何不同?
约翰

8
优化高级设计和技术决策(例如语言选择)永远不会为时过早。但是,通常只有在您基本上完成了设计之后,效率低下才会变得明显,这就是为什么弗雷德·布鲁克斯(Fred Brooks)表示大多数团队无论是否打算都编写一个废弃版本。原型的另一个论点。
Dominique McDonnell

8
@Robert,对Knuth的报价进行了过早的优化……

94

每个函数/方法返回一个。


7
我打算把这个。我爱我一些早期的回报声明。
卡森·迈尔斯

4
绝对!人们设计一些非常有趣的程序流程来避免过早return声明。深度嵌套的控制结构或连续检查。当if return可以真正简化此问题时,这确实会使方法肿。
snmcdonald 2010年

4
如果您需要一个函数的多个返回值(除了警卫之外),则您的函数可能太长了。
EricSchaefer 2010年

18
除非要在多个位置出现,否则没有return关键字是没有意义的。早点回来,经常回来。它只会进一步简化您的代码。如果人们能够理解中断/继续语句的工作方式,为什么还要为收益而挣扎?
Evan Plaice

7
我认为这是一个过时的最佳实践。我认为这不是现代的最佳做法。
Skilldrick 2011年

87

不要重新发明轮子是一种被广泛滥用的教条。它的想法是,如果存在合适的解决方案,请使用它而不是创建自己的解决方案。除了节省精力外,现有解决方案可能比最初设想的要更好地实施(无错误,高效,经过测试)。到现在为止还挺好。

问题是几乎没有100%合适的解决方案。可能存在80%合适的解决方案,使用它可能很好。但是60%合适吗?40%?您在哪里划界线?如果不划清界限,最终可能会在项目中合并一个blo肿的库,因为您正在使用其10%的功能-只是因为您想避免“重新发明轮子”。

如果您确实发明了轮子,那么您将获得所需的一切。您还将学习如何制作轮子。边做边学不应被低估。最后,定制轮可能比现成的通用轮更好。


3
我曾经发生过这种情况。我构建了自己的ajax网格组件,因为当时没有人可以做我想要的,但是后来用Ext JS网格替换了它。我从一开始就假设将替换显示层,这很有帮助。
Joeri Sebrechts 2010年

20
同意 如果没有人重新发明轮子,那么我们所有人都将使用木制轮胎驾驶汽车。
维利博士的学徒

6
当我将Boost添加到C ++项目时,我总是感觉像是10%的示例。我总是直接需要不到10%的东西,但是我需要的功能当然是导入其他模块,这些模块导入其他模块...
Roman Starkov 2010年

3
+1:就在本周,我重新发明了轮子(例如,用适合我们需求且模块化的东西来替换我们使用的肿但流行的jquery插件),并带来了巨大的性能提升。此外,有些人的工作实际上就是重塑车轮:以米其林为例,他们从事研发以改进轮胎。
wildpeaks 2011年

2
@博士 聪明地,这些轮子没有被重新发明,而是被重构了!

78

“对所有内容进行单元测试。”

我听说它经常说所有代码都应该具有单元测试,这一点我不同意。对方法进行测试时,必须对该方法的输出或结构进行任何更改两次(在代码中一次,在测试中一次)。

因此,我认为单元测试应该与代码的结构稳定性成比例。如果我是从下往上编写分层系统,则我的数据访问层将测试wazoo。我的业务逻辑层将经过很好的测试,我的表示层将进行一些测试,而我的视图几乎没有测试。


7
我怀疑“一切都进行单元测试”已经成为陈词滥调,就像“过早的优化”报价一样。我通常同意您的比例,并且看到了许多开发人员为模拟应用程序层对象而付出巨大努力的例子,这些努力可能最好花在接受测试上。
罗伯特·哈维

36
如果方法结构的更改导致测试更改,则可能是测试做错了。单元测试不应验证实现,而只能验证结果。
亚当李尔

7
@Anna Lear:我认为他是在谈论进行设计/结构更改(重构)。由于设计还不够成熟,因此当您找到更好的设计方法时,您可能必须以这种方式修改很多测试。我同意,当您的测试人员技能更高时,您可能会更容易注意到在哪里进行测试是个坏主意(由于这个原因和其他原因),但是如果设计不是很成熟,您仍然很可能会在测试中进行一些测试。方式。
n1ckp 2010年

13
我认为这也是为什么“先做测试”的想法行不通的原因。首先要进行测试,您必须具有设计权。但是拥有正确的设计要求您尝试一些事情,看看它们如何工作,以便您可以对其进行改进。因此,您在设计之前就无法真正进行测试,而正确进行设计需要您进行编码并查看其工作方式。除非您有一些真正的超级建筑师,否则我不会真正看到这个想法如何工作。
n1ckp 2010年

13
@ n1ck TDD实际上不是设计练习,而是设计练习。这个想法是,您通过测试来改进设计(因为这样可以快速地为您的东西公开一个合理的API),而不是使测试适合现有的设计(这可能是不好的/不足的)。因此,您不必先拥有设计权就可以进行测试。
亚当李尔

57

始终对接口进行编程。

有时只有一个实现。如果我们将提取接口的过程推迟到看到需要接口的时间,我们通常会发现它是不必要的。


4
同意,当需要接口(例如,可以使用的稳定API)时,可以对接口进行编程。
罗伯特·哈维

45
在我的阅读中,该规则与语言构造的接口无关。这意味着您在调用类的方法时不应对类的内部工作作任何假设,而应仅依赖于API合同。
ZsoltTörök2010年

2
好的,这是一个有趣的问题-我主要是.NET开发人员,因此对我来说,我的界面看起来像IBusinessManager或IServiceContract。对我来说,这非常容易导航(而且我通常将我的界面保留在另一个名称空间(甚至另一个项目)中)。当我使用Java时,实际上发现了这一点令人困惑(通常我看到的接口实现都带有.impl后缀-接口没有描述)。那么这可能是代码标准问题吗?当然,Java中的接口会使代码看起来很混乱-乍一看,它们看上去与普通类完全相同。
沃森

5
@沃森:一个代价是,每当我在Eclipse中的一个方法调用上击中F3(“跳转到声明”)时,我都会跳到该接口,而不是一个实现。然后,我必须控制-T,向下箭头,返回执行。它还会阻止一些自动重构-例如,您不能在接口定义中内联方法。
汤姆·安德森

4
@汤姆:好吧,先生,我很乐意让您参与这场Eclipse vs. Intellij的战争-但是我有出色的道德准则,这使我无法与有明显障碍的人进行身体对抗。繁荣。我并不是说Eclipse不好,而是说如果轴心国使用它来建造或设计他们的战争机器,第二次世界大战现在将被称为“两日混战”。严重的是,我发现它缺乏一些现成的IDE(Intellij / VS + ReSharper)中的润色。我发现自己不止一次与之抗争-这是太多了。
沃森

46

不要使用任何开放源代码(或.NET开发人员使用的非Microsoft)

如果Microsoft不开发它-我们不在这里使用它。想要使用ORM-EF,想要使用IOC-Unity,想要记录-企业记录应用程序块。有这么多更好的库存在-但是我总是被困在开发世界的美元菜单中。每当我听到“ Microsoft最佳做法”时,我都会发誓我认为《麦当劳的营养指南》。当然,如果跟随他们,您可能会生存,但您也会营养不良和超重。

  • 请注意,这可能不是您的最佳做法,但这是我在几乎所有工作领域都遵循的常见做法。

13
听起来太可怕了... =(我可能是太上对方,不过,我避免M $尽可能。
Lizzan

那不应该是那样的。应该选择图书馆的价值,而不仅仅是考虑谁创造了它。例如,我喜欢EF,但是我对Enterprise Library经验很差,并且发现了更好的验证和日志记录工具,例如FluentValidation,log4net和Elmah。
Matteo Mosca

4
您不会因为购买IBM ^ wMicrosoft而被解雇的
Christopher Mahan

17
也有镜像版本,即从不使用任何Microsoft或从不使用任何您需要付费的东西。
理查德·加兹登

5
我很幸运地在一个组织中工作,这不是一个普遍存在的教条,但是在我们采用了商业解决方案的地方,肯定会有很多痛苦。当商业解决方案的某些部分无法正常工作时,就会出现问题。当它是开源时,您可以查看源代码(最终文档)并找出问题所在。使用封闭源代码时,您必须为获得对技术支持知识的特权而付出的特权,而技术支持知识对您所做产品的了解甚至更少。那就是唯一可用的“修复”。
SingleNegationElimination

40

面向对象

有一个假设,就是因为代码是“面向对象的”,所以它非常神奇。因此人们继续将功能压缩到类和方法中,仅是面向对象的。


7
我无法想象在不利用对象定向提供的组织优势的情况下构建任何规模的软件系统。
罗伯特·哈维

18
罗伯特 Unix不是面向对象的,并且可以肯定是合格的软件系统。它似乎也很受欢迎(例如Mac OSX,iPhone,Android手机等)
Christopher Mahan,2010年

7
我的意思是我认为我们应该使用最合适的方法。我已经看到人们使用繁琐且没有多大意义的方法和类,只是因为“它是面向对象的”。那是货物崇拜。
LennyProgrammers

8
没有银弹。在没有面向对象的情况下,函数式编程(Haskell)非常成功。归根结底,您拥有多种工具,为手头的任务选择最佳的分类是您的工作。
Matthieu M.

9
有趣的是,除了使用类,多态性之类的东西外,大多数面向对象的代码实际上都是过程代码。
奥利弗·韦勒

35

所有代码都应加注释。

不,不应该。有时您会有明显的代码,例如,在设置器做一些特别的事情之前,它们不应被注释。另外,为什么我要这样评论:

/** hey you, if didn't get, it's logger. */
private static Logger logger = LoggerFactory.getLogger(MyClass.class);

13
所有代码都应该可以理解。注释是其中的主要工具,但远非唯一。
Trevel,2011年

绝对地,代码应该是可以理解的。但是没有唯一的理由写一个注释,该注释不会为方法名称添加任何内容。如果您写的话,/** sets rank. */ void setRank(int rank) { this.rank = rank; }我认为评论是愚蠢的。为什么要写?
弗拉基米尔·伊万诺夫

2
生成的文档。这就是/** */格式的用途,而不是/* */格式注释。或.NET,它将是///
Berin Loritsch 2011年

10
使用}//end if}//end for}//end while是浪费的评论我一生中遇到的最好的例子。我已经看过很多次了,开头括号不超过2行。恕我直言,如果您需要这些注释,那么您的代码就需要重构...或者您需要付20美元并购买一个IDE / Text编辑器,以突出显示匹配的花括号。
scunliffe

7
代码说“如何”。评论需要说“为什么”。

32

方法论,尤其是混乱。当我听到大人使用“ Scrum Master”一词时,我不能保持直面。我非常厌倦听到开发人员抗议方法论X的某些方面对他们的公司不起作用,仅由Guru So-and-Su告诉他们,它行不通的原因是实际上他们不是真正的从业者X的方法论。“加倍努力,我的Padawan学习者必须加倍努力!”

敏捷方法中有很多智慧-其中很多--但是它们经常被堆满很多肥料,以至于我无法抗拒呕吐反射。从Wikipedia的Scrum页面获取以下内容

Scrum中定义了许多角色。根据它们在开发过程中参与的性质,所有角色都分为猪和鸡两个不同的组。

真?猪和鸡,你说呢?迷人!迫不及待想向我的老板推荐这个...


有趣。我在一定程度上同意。在最后一部分中:称呼您想要的东西,它们就是助记符,仅此而已。
史蒂文·埃弗斯

12
+1 ...您是对的,很难认真对待。<大声的声音> *我是SCRUMMASTER * </ voice>
GrandmasterB,2010年

2
和寓言。它使我想起教堂的讲道,或自助大师(和喜剧演员)闻名的各种轶事:“带我的朋友史蒂夫。史蒂夫一直在与妻子谢里尔争论。他们的婚姻真正处于危险境地。然后,有一天……”这些说教的纱线不会在其他领域打扰我,但我讨厌看到它们在工程科学中激增。
evadeflow,2010年

2
Scrum忍者呢?
Berin Loritsch 2011年

1
我不同意“猪和鸡”的比较……它直接面对敏捷宣言。即“基于合同谈判的客户协作”。在项目成功方面,客户与项目团队一样被授予(如果不是更多的话)。称某些角色为“猪”而其他角色为“鸡”,则树立了“我们与他们”的思想,即恕我直言是成功项目的最大障碍。
迈克尔·布朗

25

对象关系映射... http://en.wikipedia.org/wiki/Object-relational_mapping

我既不想从我的数据中抽象出来,也不想失去那种精确的控制和优化。我在这些系统上的经验非常差...这些抽象层生成的查询甚至比我从离岸外包中看到的还要糟糕。


19
过早的优化是万恶之源。与无法维护的代码相比,慢速代码在现实生活中仅是极少出现的问题。使用ORM,然后仅在需要时切入抽象。
Fishtoaster

28
ORM是80-20工具。他们的目的是要处理80%的CRUD,这使得在一段时间后编写所有无尽的管道代码变得很烦。剩下的20%可以通过更“常规”的方式完成,例如使用存储过程和编写普通的SQL查询。
罗伯特·哈维

18
@Fishtoaster:您的意思不是说:“我们应该忘记效率低下的问题,大约有97%的时间是这样:过早的优化是万恶之源。但是,我们不应该在这3%的临界水平上放弃机会。”
罗伯特·哈维

5
@Robert Harcey:我没有使用直接引号是有原因的。我认为大多数程序员都过于关注效率问题,这是他们中很少真正需要解决的问题。诚然,在某些领域比其他领域更重要,但是可维护性和可扩展性到处都是问题。另一个修改过的语录:“使它起作用,使其可维护,使其易于阅读,使其可扩展,使其可测试,然后,如果有时间,事实证明您需要它,请使其快速。”
Fishtoaster

12
@克雷格:你怎么不承认你的陈述中的讽刺?需要一年的时间来学习如何从ORM中获得良好的性能是反对 ORM的一个很好的论据,对“控制” SQL生成和注入存储过程的需求也是如此。如果您有足够的知识,那么您就有知识可以完全绕过ORM。
尼古拉斯·奈特

22

将函数名称写成英语句子:

Draw_Foo()
Write_Foo()
Create_Foo()

等等,这看起来不错,但是在学习API时会很痛苦。搜索“以Foo开头的所有内容”的索引有多容易?

Foo_Draw()
Foo_Write()
Foo_Create()

等等


2
大约和在TM的功能列表中键入Foo一样容易。
乔什·K

68
听起来像是你真正希望它是Foo.Draw()Foo.Write()并且Foo.Create(),这样你就可以做Foo.methods.sortFoo.methods.grep([what I seek]).sort
Inaimathi

7
以“ Get”开头是另一个示例。
JeffO 2010年

1
我来自Objective-C世界,在做Java时(我的另一生)大大错过了冗长的方法名称和中缀表示法。自从代码完成开始工作以来,我也没有发现额外的键入问题。

2
@Scott惠特洛克:不认为过时了一些.NET开发人员的,IIRC,VS 2008并没有这样做。不过2010年确实如此,这真是太棒了。
史蒂文·埃弗斯

22

MVC-我经常发现,将许多Web设计问题纳入MVC方法中,更多的是使框架(导轨等)满意,而不是简单或结构。MVC是“建筑宇航员”的最爱,他们似乎过于重视简单的支架。嗯

基于类的面向对象-在我看来,它鼓励了可变状态的复杂结构。多年来,我发现基于类的面向对象的唯一引人注目的案例是构成任何面向对象的书的第一章的老套“形状->矩形->正方形”示例


4
我已经在ASP.NET和ASP.NET MVC中完成了Web开发,尽管MVC似乎很简陋,但由于很多原因,我还是更喜欢ASP.NET,原因有很多:简单性,可维护性和对标记的超精细控制。一切都有它的位置,尽管看起来有些重复,但维护起来却是一种快乐。它是完全可定制的,因此,如果您不喜欢开箱即用的行为,则可以对其进行更改。
罗伯特·哈维

1
就OO而言,有好的方法也有坏的方法。继承被高估了,并且在现实世界中使用的程度比大多数人认为的要少。即使在面向对象的世界中,目前也存在着一种趋向于更加实用,不变的开发风格的趋势。
罗伯特·哈维

1
+1代表提及MVC。虽然MVC的概念(将数据层逻辑,表示逻辑和背景逻辑分隔开)是一个好主意,但是将它们物理地分隔成一个复杂的文件夹层次结构却很杂乱,其中包含了包含代码段的文件。我将整个现象归咎于PHP缺乏名称空间支持,而新手开发人员将数十年的技术赞誉为“最新事物”。很简单,为数据库访问器,GUI和后台逻辑创建名称空间(以及需要的子名称空间)。
Evan Plaice 2010年

3
对于OO,直到您的项目发展到管理复杂性变得重要的规模时,您才真正看到它的好处。尽可能遵循单一责任原则,并且如果您的代码可以公开访问(例如,.dll),请在任何可能的地方隐藏类/方法/属性的内部实现,以使代码更安全并简化API。
Evan Plaice 2010年

1
我个人认为shape-> rectangle-> square示例是反对 oop 的最优雅的论据之一。例如,Square(10).Draw()足以Rectangle(10, 10).Draw()。所以我想这意味着Square是矩形的子类。但是这mySquare.setWidthHeight(5,10)是胡说八道(即,它不符合Liskov替换原理),正方形虽然矩形可以,但不能具有不同的高度和宽度,这意味着矩形是正方形的子类。在其他情况下,这称为“圆形,椭圆问题”
SingleNegationElimination11年

22

雅尼

您将不需要它

当我不得不在现有代码库上实现功能时,这种方法花费了我数小时的时间,而如果仔细计划,这些功能将事先包含在这些代码中。

我的想法经常由于YAGNI而被拒绝,并且在大多数情况下,后来有人不得不为该决定付费。

(当然,可以说设计良好的代码库也可以在以后添加功能,但实际情况有所不同)


15
我同意YAGNI,但我明白您的意思。YAGNI的重点是与想要从头到尾计划所有细节的人员打交道。尽管十分之九,但它却被用作借用工程师代码或完全跳过计划的借口。
杰森·贝克

12
P.Floyd,@ Jason Baker:+1完全正确。俗话在这里适用:“实验室中的几个月可以节省图书馆的时间”
Steven Evers 2010年

规范通常会(而且大多数情况下应该)保留大部分实现,并且某些接口是开放的。规范中没有直接列出的所有内容,而是实现规范所需的所有内容,无论是实现决策,用户可见的界面还是其他任何内容,也都是规范的间接要求。如果某个功能不在规范中,并且未在规范中隐含,那么您就不需要它。这怎么会令人困惑?
SingleNegationElimination

1
@TokenMacGuy的关键方面是规范部分所隐含的内容。那是意见分歧很大的地方。
肖恩·帕特里克·弗洛伊德

20

对于SQL

  1. 不要使用触发器
  2. 始终将表格隐藏在视图后面

为了:

  1. 它们是拥有它的功能。您有一个表的多个更新路径,还是需要100%审核?

  2. 只是为什么 如果要重构以维护合同,我会这样做,但当我读完该文档后,便不会更改视图以匹配任何表更改

编辑:

数字3:使用EXISTS避免*。尝试1/0。有用。列列表未按照SQL标准进行评估。第191页


3
#2是最佳做法?
霍根


1
@Hogan:仅镜像基本表的视图不会增加直接使用基本表的安全性。如果您加入一个securityuser表,或者掩盖了某些列,那么就足够公平了。但是从表或视图中选择每一列:没有区别。就个人而言,我还是使用存储过程。
gbn

3
@Hogan:我对SQL Server有所了解:-) stackoverflow.com/users/27535/gbn 我的意思是,如果视图是SELECT * FROM TABLE
gbn

1
@gbn:我同意。那里没有区别。我想我们可能会说同样的话。我想我最初的评论(“#2是最佳实践吗?”)更多地是基于我的个人经验,即视图(如触发器)经常被误用而非正确使用。因此,这种最佳做法只会导致滥用,而不会导致滥用。如果认为这是一种最佳做法,那么您100%正确,那是不好的做法。
霍根2010年

20

主要是设计模式。它们被过度使用和利用不足。


13
+1我仍然看不到设计模式是美观还是优雅的解决方案。它们是解决语言缺陷的方法,仅此而已。
奥利弗·韦勒

2
只是将其视为使用语言本身消除语言气味的一种尝试。
FilipDupanović2011年

15

单一责任原则

(“每个班级应该只有一个责任;换句话说,每个班级应该只有一个,并且只有一个改变的理由”)

我不同意。我认为一个方法只应该有一个改变的理由,并且一个类中的所有方法应该彼此之间具有逻辑关系,但是该类本身实际上可以几件事(相关的事情)。

以我的经验,这个原则常常过于热心地应用,并且您最终会遇到许多微小的单方法类。我工作过的两家敏捷商店都做到了。

想象一下,如果.Net API的创建者具有这种思维方式: 我们将拥有ListSorter,ListReverser和ListSearcher类,而不是List.Sort(),List.Reverse(),List.Find()等!

我不再分享SRP (从理论上讲它并不可怕),而是分享一些我漫长的轶事经历:


在我工作的地方,我写了一个非常简单的最大流量求解器,它由五个类组成:一个节点,一个图,一个图创建器,一个图求解器,以及一个使用图创建器/求解器来求解一个类的类。实际问题。没有一个特别复杂或很长(求解器最长的〜150行)。但是,确定类具有太多的“职责”,所以我的同事着手重构代码。完成后,我的5个班级已扩展到25个班级,它们的总代码行是原来的三倍多。代码的流程不再明显,新的单元测试的目的也不再明显。我现在很难弄清楚自己的代码做了什么。


在同一地方,几乎每个类都只有一个方法(它只有“责任”)。遵循程序中的流程几乎是不可能的,并且大多数单元测试包括测试该类另一个类中调用代码,这两个目的对我来说都是一个谜。从字面上讲,有数百个班级应该只有几十个班级。每个类仅执行一个“操作”,但是即使使用“ AdminUserCreationAttemptorFactory”之类的命名约定,也很难分辨出类之间的关系。


在另一个地方(也具有类应该只有一种方法的心态),我们正在尝试优化一种方法,该方法在特定操作中会占用95%的时间。经过(有点愚蠢的)优化之后,我将注意力转向了为什么它被称为“ bajillion次”。它在一个类的循环中被调用...其方法在另一个类的循环中被调用..该方法也在一个循环中被调用..

总而言之,有五个级别的循环(严重地分布在13个类中)。仅仅通过查看它就无法确定任何一个类实际上正在执行的操作-您必须绘制一个心理图,说明它调用了什么方法以及那些方法调用了什么方法,依此类推。如果将所有方法都归纳为一种方法,那么问题方法将嵌套在五个显而易见的循环级别中,那么大约只有70行。

我要求将这13个类重构为一个类的请求被拒绝。


6
听起来有人在该工作中得了“发烧”或在这种情况下是“发烧”。列表类不违反SRP。它的所有功能都有一个目的,即操纵对象的集合。在班上只有一个功能听起来对我来说太过分了。SRP背后的原理是,一个代码单元(无论是方法,类还是库)都应具有单一职责,可以简明扼要地表述。
Michael Brown

3
我开始从人们那里发现这种疯狂,他们发现不可能编写纯粹的纯函数式代码。过多的教育使世界上的每个问题都可以从一本模式书中解决。关于实用主义的想法还不够。像您一样,我已经看到了一些基于类的OO代码,它们是如此可怕,以至于完全不可能遵循它。而且它巨大而肿。
quick_now 2011年

3
在这里第二条评论。许多“原则”被过度应用。很多事情都是好主意,有时恰恰相反是合适的。好的程序员知道什么时候打破规则。因为这些规则不是“规则”,所以它们是“大多数时候的良好做法的陈述,除非它是一个愚蠢的想法”。
quick_now 2011年

“想象一下,.Net API的创建者是否有这种想法:我们将拥有ListSorter,ListReverser和ListSearcher类,而不是List.Sort(),List.Reverse(),List.Find()等。 !”。这正是在C ++中完成的工作,它很棒。该算法与数据结构分离,所以如果我写我自己的容器,这一切与标准库工作的算法只是工作与我的新的容器。它在.Net领域中必须是可怕的,为要排序的每个新容器编写一个新的排序功能。
Mankarse 2011年

14

既然您提到了“干净代码”,尽管它包含一些好主意,但我认为它对将所有方法重构为子方法以及将那些方法重构为子子方法等的痴迷程度实在太过分了。您应该使用二十行(据说是好命名的)一线而不是十行方法。显然有人认为它很干净,但对我来说,它似乎比原始版本差很多。

另外,替换简单的基本事物,例如

0 == memberArray.length

在类中调用该类自己的方法,例如

isEmpty()

是一个可疑的“改进”恕我直言。另外:区别在于第一次检查完全按照它的意思进行:检查数组长度是否为0。好的,isEmpty()也可以检查数组长度。但是也可以这样实现:

return null != memberArray ? 0 == memberArray.length : true;

也就是说,它包含一个隐式的空检查!对于公共方法来说,这可能是好的行为-如果数组为null,则某些东西肯定为空-但是当我们谈论类内部时,这不是很好。虽然必须封装到外部,但类内部必须准确知道类中发生的事情。您不能从自身封装类显式胜于隐式。


这并不是说打破冗长的方法或进行逻辑比较是不好的。当然可以,但是在什么程度上做(甜蜜点在哪里)显然是口味的问题。分解一个长方法会导致更多方法,而这并非免费。如果您一眼就能看到所有内容都在一个方法中,那么您就必须四处浏览源代码才能查看实际情况。

我什至可以说,在某些情况下,单行方法太短了,不值得成为一种方法。


6
我很少将此视为问题。通常,这是因为我通常在一种方法中看到的太多而不是很少。但是,根据一些研究,方法的极低复杂度也比中等程度的低复杂度具有更高的错误率。enerjy.com/blog/?p=198
MIA 2010年

是的,这绝对只是Clean Code中的问题。正如您所说,在现实生活中方法往往太长。但是看到那条曲线很有趣!确实,应该使事情尽可能简单,但不要简单。
Joonas Pulakka 2010年

1
我发现您的第二个示例更具可读性,并且如果要公开它,则必须使用该格式(或类似的东西,例如类本身的Length属性)。
罗伯特·哈维

@Robert Harvey:第二个示例是一个很好的公共方法,但是从类本身内部调用它是有问题的,因为在您看它是如何实现之前,您并不确切知道它的作用。例如,它可以检查是否为空。请参阅上面的内容。
乔纳斯·普拉卡

@乔纳斯:足够公平。
罗伯特·哈维

13

“对评论持开放态度”

评论绝对是一件好事,但太多的评论同样是不好的,甚至还不够不够。为什么?好吧,如果看到太多不必要的注释,人们倾向于将注释排除掉。我并不是说您可以拥有完全自我记录的代码,但是最好使用需要注释的代码来进行解释。


1
自我记录代码绝对不错。虽然,我喜欢在注释旁边加上简单的计算(以表示返回类型或返回值是什么)。但是,如果您的注释需要比代码本身更多的单词,那么可能是时候重写代码了。
sova,2010年

6
我必须同意sova提出这一建议的方式。干净的代码胜于注释。
riwalk

4
您仍然需要其中的“为什么”!

我宁愿评论解释原因。这意味着当我查看代码意图时,我不得不减少对代码意图的逆向工程。
quick_now 2011年

12

GoTo被认为有害

如果要实现状态机,则GOTO语句比“结构化编程”方法更有意义(可读性和高效的代码)。当我在一份新工作中编写的第一段代码不仅包含一个goto语句,而且包含多个goto语句时,这确实使某些同事感到担忧。幸运的是,他们足够聪明,可以意识到这实际上是此特定情况下的最佳解决方案。

任何不允许对其规则进行明智且有文件证明的例外的“最佳实践”都是令人恐惧的。


16
我正在进行九年的编程,没有一个goto语句(如您所述,包括多个状态机)。将您的想法扩展到新的想法。
riwalk

3
@Mathieu M.-同意-将GOTO与结构化控制语句混合是不明智的。(这是纯C,这是不是一个问题。
MZB

1
@ Stargazer2-使用简单的FSM,它取决于是否将状态放入变量中并将其用作索引来调用过程(与计算出的GOTO是否相同?)是否比使用程序计数器提供了更清晰/更快的代码作为FSM状态。我并不是在大多数情况下都主张将其作为最佳解决方案,而在某些情况下只是将其作为最佳解决方案。将您的想法扩展到其他方法。
MZB

5
@MZB,您是否同意函数调用也只是计算得出的GOTO?同样,for / while / if / else / switch构造也适用。语言设计人员出于某种原因将直接更改抽象到程序计数器。不要使用goto。
riwalk

3
直接实现状态机可能是一种反模式。在没有字面表示状态和过渡的情况下,有很多方法可以拥有状态机。例如,import re
SingleNegationElimination 2011年

12

我们为使代码可测试而做出的牺牲

我花了很多时间来使我的代码可测试,但是我不假装如果选择的话就不会。但是,我经常听到人们推崇这些是“最佳实践”的想法。这些做法包括(以.Net语言编写,但也适用于其他语言)

  • 每个类创建一个接口。 这使要处理的类(文件)数量加倍,并重复了代码。是的,接口编程很好,但这就是公共/私有说明符的目的。
  • 在启动时未实例化的每个类都需要一个工厂。 显然,new MyClass()这比编写工厂要简单得多,但是现在不能单独测试创建工厂的方法。如果不是因为这个事实,我只会使我现在做的工厂班级数量的1/20减少。
  • 公开每个班级,这完全没有在班级上使用访问说明符的目的。但是,非公共类不能从其他项目访问(并因此进行测试),因此唯一的选择是将所有测试代码移至同一项目(并随最终产品一起发布)。
  • 依赖注入。显然不得不放弃每一个我用一个字段和构造函数参数的其他类的不仅仅是创建它们,当我需要他们显著更多的工作; 但是我不能再单独测试该类了。
  • 单一责任原则让我头疼不已,现在我将其转移到自己的答案上

那么我们该怎么做才能解决此问题?我们需要对语言架构进行彻底的改变:

  • 我们需要模拟类的能力
  • 我们需要能够从另一个项目测试内部类的私有方法的功能(这似乎是一个安全漏洞,但是如果被测者被迫命名其测试器类,我认为不会有问题)
  • 依赖注入(或服务位置)以及与我们现有工厂模式等效的东西必须成为语言的核心部分。

简而言之,我们需要一种从头开始设计的语言,并且要考虑到可测试性


我猜您从未听说过TypeMock吗?它允许模拟类,私有,静态(工厂)等任何东西。
Allon Guralnek,2011年

@Allon:我有,但是它远非免费,这对于大多数人来说不是一个选择。
BlueRaja-Danny Pflughoeft

如果必须编写许多工厂类,那么您在做错什么。智能DI库(例如,C#的Autofac)可以将Func <T>,Lazy <T>,Delegates等用于工厂,而无需自己编写任何样板。
gix11年

10

将应用程序划分为多个层;数据层,业务层,UI层

我不喜欢这样做的主要原因是,大多数遵循此方法的地方都使用非常脆弱的框架来完成它。IE UI层是手工编码的,用于处理业务层对象;业务层对象是手工编码的,用于处理业务规则和数据库;数据库是SQL,已经相当脆弱,并且由不喜欢更改的“ DBA”组管理。

为什么这样不好?最常见的增强请求可能是“我需要屏幕X上具有Y的字段”。砰! 您只是具有一个影响每个单独层的新功能,并且如果您与不同的程序员将各个层分开,那么对于一个非常简单的更改,它就成为涉及多个人员和团队的大问题。

另外,我不知道我参加过多少次类似这样的争论。“名称字段的最大长度限制为30,这是UI层,业务层还是数据层问题?” 有一百个论点,没有正确的答案。答案是相同的,它影响所有层,您不想使UI变得笨拙,而不必遍历所有层,并在数据库上失败,只是为了让用户发现他的输入太长。如果更改它,它将影响所有图层等。

“层”也容易泄漏。如果任何层在物理上都被进程/机器边界(IE Web UI和业务后端逻辑)隔开,则规则将重复,以使一切正常运行。也就是说,即使是“业务规则”,某些业务逻辑也会出现在UI中,因为用户需要UI能够响应。

如果所使用的框架或所使用的体系结构能够抵抗较小的更改和泄漏(即基于元数据)并在所有层中进行动态调整,则可以减轻痛苦。但是,对于大多数框架而言,这是一个极大的痛苦,需要对UI进行更改,对业务层进行更改以及对数据库进行更改,而对于每个小的更改,都会导致比该技术所产生的工作量更大,帮助更少。

我可能会为此受到抨击,但事实是这样的:)


+1,听起来像是我最后就业的最细微之处!我原则上尊重分层应用程序,但是在没有意义时,太多的人将其视为灵丹妙药。大多数业务软件的业务逻辑量极少,其功能相对简单。这会使分层业务逻辑成为样板代码的噩梦。很多时候,数据查询和业务逻辑之间的界限可能会变得模糊,因为查询就是业务逻辑。
maple_shaft

……此外,大多数商店绝对无法识别UI逻辑或Presentation逻辑。因为他们不了解典型的CRUD应用程序中几乎没有多少业务逻辑,所以当他们的大多数逻辑作为表示逻辑位于表示层时,他们会觉得自己一定在做错事。它被错误地标识为业务逻辑,然后人们将其推到服务器以进行另一个服务器调用。瘦客户端可以并且应该具有表示逻辑,例如。(如果在dropDown3中选择了option1,则隐藏textField2)。
maple_shaft


7

用户故事/用例/角色

 

当您为一个您不熟悉的行业编程时,我了解这些需求,但是我认为,如果完全实施它们,它们将变得过于公司化,浪费时间。


7

80个字符/行的限制是愚蠢的

我知道需要做出一些折衷来适应GUI端最慢的运行程序的速度(屏幕分辨率限制等),但是,为什么该规则适用于代码格式?

参见...有一个叫做水平滚动条的小发明,创建该滚动条是为了管理最右边像素边界之外的虚拟屏幕空间。为什么设法创建了出色的生产力增强工具(例如语法突出显示和自动完成)的开发人员为什么不使用它?

当然,* nix恐龙虔诚地使用了CLI编辑器,这些编辑器遵循其VT220终端的陈旧旧限制,但是为什么我们其他人都遵循相同的标准?

我说,拧紧80个字符的限制。如果恐龙足够强大,可以整天破解emacs / vim,为什么它不能创建自动包装行的扩展或为其CLI IDE提供水平滚动功能的扩展?

1920x1080像素监视器最终将成为标准,全世界的开发人员仍然生活在相同的限制下,除了他们为什么这样做外,与他们的工作无关,长者刚开始编程时就告诉他们这样做。

80个字符数的限制不是最佳实践,但是对于极少数程序员来说,这是一个小众实践,应该这样对待。

编辑:

可以理解,许多开发人员不喜欢水平滚动条,因为它需要鼠标手势,所以...为什么不为使用现代显示的我们这些人增加列宽限制(大于80)。

当800x600的计算机显示器成为大多数用户的标准时,Web开发人员增加了他们的网站宽度以容纳大多数用户...为什么开发人员不能这样做。


1
用___ @Orbling Nice GWB逻辑是邪恶的角度。因此,您讨厌hScroll,您是否有任何合理的理由将col-width限制为80个字符?为什么不160或256?我想我们都可以假设大多数开发人员已经淘汰了他们的VT220终端,并用puTTY替换了它们,因此他们无论如何都可以通过编程方式扩展宽度。
Evan Plaice

4
我更喜欢我们坚持80个字符的限制。您给我更多的水平空间后,我将尝试与其他文件并排打开其他文件。我讨厌必须滚动四种方式。另外,我经常注意到我被迫使用80个字符的上限编写更具可读性的代码。
FilipDupanović2011年

2
您将如何打印?

5
抱歉-必须对此表示不同意见。我真的很讨厌长长的线-它需要更多的眼睛移动,它需要鼠标手势滚动,在行尾很难看到微弱的晃动小东西。在大约99%的情况下,有一些干净的方法可以使内容跨几行(更短)运行,这更加清晰易读。80个字符可能是任意的,并且“因为在打孔卡时代就是这样”,但由于上述原因,在大多数情况下,它仍然是一个合理的内部工作框架。
quick_now 2011年

3
缩进2个空格。并使用带有自动缩进跟踪器的编辑器。我已经这样做了好几年了,没什么大不了的。(具有正确模式的Emacs在这里
有所

5

测量,测量,测量

很好,可以进行衡量,但是为了隔离性能错误,可以进行衡量以及逐步消除。这是我使用的方法。

我一直在努力寻找“智慧”的来源。有人拿着一个足够高的肥皂盒说了这句话,现在它可以旅行了。


5

我的老师要求我以小写字母(例如)开头所有标识符(不包括常量)myVariable

我知道这似乎是一件小事,但是许多编程语言都要求变量以大写字母开头。我重视一致性,因此我的习惯是,所有内容都以大写字母开头。


9
我有一位需要camelCase的老师,因为他坚持这是人们在现实世界中使用的东西...同时,我在工作时在两个不同的小组中进行编程-两个小组都坚持使用under_scores。重要的是您的小组使用什么。他本可以将自己定义为首席程序员,但在我的书中,一切都会好起来的-我们遵循他的惯例-但他始终将自己的观点视为“现实世界中的工作方式”,就好像没有其他有效的方法一样。方式。
xnine

@xnine我在该网站上没有足够的代表来对您的评论进行评分,因此,我将以同意的评论进行
2010年

5
驼峰式大小写(首字母小写)和帕斯卡大小写(每一个单词的首字母大写)是很常见的约定。在大多数语言中,camelCase用于私有/内部变量,而PascalCase用于类,方法,属性,名称空间,公共变量。习惯于为可能使用不同命名方案的项目做好准备并不是一个坏习惯。
Evan Plaice

2
仅供参考。某些语言根据变量的首字母是否大写来推断含义。在这种语言中,如果第一个字母为大写字母,则将变量视为常量,任何对其进行更改的尝试都会引发错误。
Berin Loritsch 2011年

1
Go中,公共方法和类中的成员以大写字母开头;带有小写字母的私人字母。
乔纳森·勒夫勒

5

使用单例

当您仅应具有某个实例时。我不同意更多。切勿使用单例,只分配一次,并根据需要传递指针/对象/引用。绝对没有理由不这样做。


2
那是一堆负面因素,使我对您对单身人士的实际立场感到困惑。
Paul Butcher

1
@Paul Butcher:我讨厌单身人士,永远不要使用它

1
@rwong:就我个人而言,我认为没有任何理由是合法的。只需将其编写为普通课程即可。确实,除了懒惰之外,没有任何理由使用单身人士来提倡不良习惯或设计。

6
谁说使用Singeltons是最佳做法?
菲尔·曼德

2
单例确实有其位置,尤其是在启动时分配并填充了操作结构的情况下,然后基本上在整个程序运行时才变为只读。在那种情况下,它只是成为指针另一侧的缓存。
蒂姆·波斯特

5

使用unsigned int作为迭代器

他们什么时候会知道使用signed int更安全,更不易出错。为什么数组索引只能是正数,为什么每个人都高兴地忽略4-5是4294967295的事实,这是如此重要?


好吧,现在我很好奇-为什么这么说?我有点傻-您可以提供一些代码示例来备份您的语句吗?
保罗·内森

4
@Paul Nathan:就越野车而言,这是一个示例:for(unsigned int i = 0; i <10; i ++){int crash_here = my_array [max(i-1,0)];}
AareP

@AareP:可以肯定的是-我假设您引用的一个事实是,当unsigned int 0减少了时1,您实际上最终会获得unsigned int可能存储的最大正值吗?
亚当·佩恩特

1
@Adam Paynter:是的。对于c ++程序员来说,这似乎很正常,但是让我们面对现实吧,“ unsigned int”是正数的一种不好的“实现”。
AareP 2010年

在小型嵌入式计算机上不是一个好主意-经常使用无符号int会生成更小,更快的代码。取决于编译器和处理器。
quick_now 2011年

4

方法不应超过单个屏幕

我完全同意单一责任原则,但是为什么人们会认为它是指“一个功能/方法在最佳逻辑粒度上最多只能承担一个责任”?

这个想法很简单。一种功能/方法应完成一项任务。如果该功能/方法的一部分可以在其他地方使用,请将其切成自己的功能/方法。如果可以在项目的其他地方使用它,请将其移到其自己的类或实用程序类中,并使其在内部可访问。

如果一个类包含27个在代码中仅被调用一次的辅助方法,那将是愚蠢的,浪费的空间,不必要的复杂性增加以及大量的时间消耗。对于想看重重构代码但又不会产生太多结果的人来说,这听起来似乎是个好规则。

这是我的规则

编写函数/方法来完成某件事

如果您发现自己要复制/粘贴一些代码,请问自己是否为该代码创建函数/方法会更好。如果一个函数/方法在另一个函数/方法中仅被调用一次,那么将它放在首位真的有一点意义(将来会被更频繁地调用)。在调试期间添加更多的函数/方法跳转是否有价值(即,添加的跳转是否使调试更容易或更难)?

我完全同意,需要检查大于200行的函数/方法,但是某些函数只能在多行中完成一项任务,并且不包含可以在项目的其余部分中抽象/使用的有用部分。

我是从API开发人员的角度看的……如果一个新用户要查看您的代码的类图,那么在整个项目的整个范围内,该图的多少部分才有意义,而在整个项目中将单独存在多少?项目内部其他部分的助手。

如果要在两个程序员之间进行选择:第一个倾向于编写尝试做太多事情的函数/方法;第二个将每个函数/方法的每个部分分解到最细粒度的层次;我会选择第一手放下手。第一个将完成更多的工作(即编写更多的应用程序内容),其代码将更易于调试(由于调试期间函数/方法的跳入/跳出次数减少),并且他将花费更少的时间进行忙碌的工作以完善方法代码看起来比完善代码的工作方式还要好。

限制不必要的抽象,不要污染自动完成功能。


这个。我曾经将一个长函数重构为几个,只是意识到几乎所有的函数都需要原始代码的几乎所有参数。参数处理非常痛苦,以至于回到旧代码会更容易。
l0b0 2011年

3
我认为对此有一个反论点,那就是将大型方法的各个部分重构为单独的调用可以使大型方法更易于阅读。即使该方法仅被调用一次。
杰里米·海勒

1
@Jeremy怎么样?与仅在一行代码段中描述一行代码的顶部放置一行注释相比,抽象出一段代码并将其放在自己的方法中如何使代码更具可读性?假设该代码块在该代码段中仅使用一次。难道真的很难的,大多数程序员分解代码的工作部件,而他们阅读和/或放置几个单行注释,提醒他们它做什么,如果他们不能?
伊万·普赖斯

4
@Evan:有效地将代码段放入函数中可以为其命名,希望可以很好地解释该代码段的作用。现在,无论调用哪段代码,您都可以看到名称来解释代码的作用,而不必分析和理解算法本身。如果做得好,这可以极大地简化阅读和理解代码的过程。
2011年

1
+1,如果可以的话,我会付出更多。单个函数中有1000行代码的C代码块没有任何问题(例如,带有大型switch()的解析器),其目的是明确而简单的。删除所有小片段并称呼它们只会使事情变得更难以理解。当然,这也有局限性。明智的判断就是一切。
quick_now 2011年
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.