防御样板?


14

对我来说,样板代码显然是不好的。但是我遇到了一个开发人员,他在减少样板方面表现出了抵制。我意识到,随着时间的流逝,我对这种厌恶情绪还没有形成,经过深思熟虑的论点。

为了使我能够形成一个令人信服的理由,即主张减少样板,有哪些反对意见?换句话说,支持样板的论点是什么(如果有)?

(我的意思是我通常认为是样板程序,但是一个很好的例子是Java中的getter和setter方法。)


7
反对重复代码的争论(假设样板已复制/粘贴):stackoverflow.com/a/2490897/1583
2012年

1
@Oded:是的。但是您误解了问题。:)他正在尝试查看关于样板代码的内容。我的猜测是他对不利情况非常了解。
史蒂文·杰里斯

3
@StevenJeuris-我完美地阅读了这个问题。这就是为什么我没有发布答案。我只想补充论点的另一面。正好如此,OP下次将“经过一段时间以来,我已经形成了一种很容易形成的,经过深思熟虑的论点”;)
Oded

2
样板在美学上可能令人愉悦:en.wikipedia.org/wiki/This_Is_the_House_That_Jack_Built
SK-logic

几个很好的答案和意见相辅相成...很难选择要接受的一个。
2012年

Answers:


15

要记住的一件事是,通常通过删除不必要的上下文来使代码变小。 如果编译器可以解决问题,那么该参数就可以了,无需显式地将其写出。

如果只有编译器打算读取它,那就太好了。但是请记住,“应该编写程序供人们阅读,而只能由机器执行。” (具有讽刺意味的是,这句话来自一本专门针对普通人类阅读的所有语言中最难的一种的教科书,这在很大程度上是因为它过于简洁。)

在您编写时,对您而言可能看起来像是无聊的,重复的样板,对于那些一年(或五年)后又必须维护您的代码的人来说,这可能是有价值的上下文。

特别是WRT Java示例,我同意这是一个糟糕的样板示例,因为它可以用更短更易读且更灵活的东西代替:属性。但这并不意味着所有语言的所有样板语法元素都像Java和C ++的getter和setter一样浪费。


7
当然,这种说法是双向的。只是有大量的样板代码可以安抚编译器,并且不能帮助人们理解-为了与getter / setter呆在一起,必须完全阅读这几十行内容,以确保“那些只是不做getter和setter” ,而不是为每个属性读取一条短线,而只是简单地声明它是一个属性及其类型。

6
我认为样板人类读者也有害。通过强制编写重复的,无意义的文本,我们使其他人难以理解代码的相关部分。如果有帮助的话,那么按照定义,它不是样板。
Andres F.

1
@乔治:相反,这不仅仅是我的看法;这是曾经尝试研究它的绝大多数人的意见。当“难于阅读”本质上是一种意见问题时,这种意见被广泛分享这一事实就使它成为事实。
梅森惠勒2014年

1
@梅森·惠勒:人们对编程语言的看法通常受其过去经验的影响。学会在Scheme中编程的人发现C或Pascal笨拙且难以阅读。另一方面,绝大多数人学会了使用主流语言进行编程。
乔治

2
我在那里的时候更多的上下文是通过去除样板从程序员隐藏的唯一手段,人权必须保持的所有的东西更大的心理地图发生了舆论的幕后看不见是一个更大的损害,当涉及到调试比在创作时节省一点视觉空间。
Patrick Hughes

7

支持样板代码的一个论据是,如果在一个地方进行更改,则只会影响代码的一种流程。这必须与一个事实相平衡,即,实际上,您实际上希望所做的更改会影响使用该代码的每一段代码。但是我看到了支持该论点的罕见例子。

假设您有一段代码说

public ForTheBar(Foo foo)
{
    Bar bar = foo.bar();
    return bar.BeFooed();
}

在您的代码中大约2个地方使用了它。

有一天,有人走过来说:“好吧,仅在这条路上,我们希望您先将酒吧放宽,然后再将其放倒。”

而且您认为“这很简单”。

public ForTheBar(Foo foo, bool shouldIGrommit)
{
    Bar bar = foo.bar();

    if (shouldIGrommit)
    {
        bar.BeGrommitted();
    }

    return bar.BeFooed();
}

然后,您的用户添加了一些新功能,您认为它与FooTheBar非常适合。然后,您忠实地问他们,是否应该在Foo之前​​将其隐去,然后他们说“不,这次不行”。

因此,您只需调用上面的方法。

但是随后您的用户说:“好,等等,在第三种情况下,我们希望您在致电BeFooed之前先涂鸦一下吧台。”

没问题,您认为,我可以做到。

public ForTheBar(Foo foo, bool shouldIGrommit, bool shouldIDoodle)
{
    Bar bar = foo.bar();

    if (shouldIGrommit)
    {
        bar.BeGrommitted();
    }

    if (shouldIDoodle)
    {
        bar.BeDoodled();
    }

    return bar.BeFooed();
}

突然,您的代码变得越来越少。也许您应该已经接受了重复的两行代码。到现在为止,您将拥有三段代码,每段2-3行,并且看起来不再重复。

综上所述,我将用“这是不常见的情况来反驳,当它发生时,您可以进行重构”。

我最近听到的另一个论点是样板代码有时可以帮助您浏览代码。我们正在讨论的示例是删除大量样板映射代码并将其替换为AutoMapper的示例。现在,有人争辩说,因为所有内容都是基于约定的,所以您不能对IDE说“该属性集在哪里”,并且希望它知道。

我见过人们争论有关IoC容器的类似问题。

并不是说我同意他们的观点,但这仍然是一个公平的论点。


2
我希望您回答的第二部分。; p +1
Steven Jeuris 2012年

只是意识到我的示例与您的示例非常相似...猜猜,即使这不是常识,它似乎
也会

6

效率的演变

您从此开始:

<p>
    <label for="field">My field</label>
    <input type="text" id="field">
</p>

那么您就可以摆脱所有烦人的样板并将其放在函数中:

  1. createFieldHtml( id, label )

    很好,我节省了很多行!

  2. createFieldHtml( id, label, defaultValue )

    是的,我也需要一个默认值,很容易添加。

  3. createFieldHtml( id, label, defaultValue, type )

    很酷,我现在也可以将其用于复选框

  4. createFieldHtml( id, label, defaultValue, type, labelFirst )

    UX设计人员说标签必须在复选框之后。

  5. createFieldHtml( id, label, defaultValue, type, labelFirst, isDate )

    现在可以在需要时呈现日期选择器。嗯..参数有点失控了

  6. createFieldHtml( id, label, defaultValue, type, labelFirst, isDate, containerCssClasses )

    在这种情况下,我需要添加CSS类

  7. createFieldHtml( id, label, defaultValue, type, labelFirst, isDate, containerCssClasses, fieldCssClasses, disabled, clearAfter, helpText, uploadPath )

    aaaaaaaaaaaaaaaaaaaaaaa

捍卫样板

我很难说出来,因为这确实是我最近才注意到的事情,所以我列出一个清单:

  1. 在我看来,似乎有些担心重复的行会延伸一些。如果只有几行,那可能根本没问题。有些事物本质上是“几乎重复的”(例如上面的示例)。从长远来看,我认为在那里进行优化的机会很小。
  2. 人们喜欢将功能封装在某个地方。如果您客观地看,似乎只是在“隐藏混乱”,请多加注意!可能是时候准备一些好的旧样板了
  3. 当您拥有越来越强大的功能时;取决于输入,它需要许多不同的执行路径,最终几乎没有作用-这可能是样板时间!
  4. 当您在另一个抽象层之上添加一个抽象层时,只是为了使您的代码更短(不打算更改底层),请花一些时间!
  5. 当您的函数需要接受如此多的参数时,您确实需要有命名参数-也许是样板时间。

我最近经常问自己一个问题是:
我可以在不做任何更改的情况下将其复制并粘贴到另一个项目中吗?如果是,则可以封装或放入库中;如果否,则是样板时间。

这与通常的样板是复制和粘贴代码的观念大相径庭。对我而言,样板是关于复制和粘贴的,但始终必须对其进行一些微调。


更新:我刚刚碰到一篇文章,上面给出了我的示例的实际名称:“过于干燥的反模式”。

该函数获取更多参数,并具有越来越复杂的内部逻辑来控制不同情况下的行为。太干的功能很容易发现。它们具有许多复杂的if-then逻辑,试图解决各种用法。[...]而且,如果代码很小并且执行离散功能,那么重复代码并不总是一件坏事。

这是一篇简短而有趣的文章,您可以在这里找到该文章:过于干燥的反模式


1
“当您在另一个抽象层之上添加一个抽象层,但只是为了使您的代码更短时”,是的,只有在可能重用的情况下,才应添加抽象层。
史蒂文·杰里斯

4
+1。不要重复自己,但也不要笨拙地避免几乎重复自己。
朱莉娅·海沃德

4

鄙视样板代码,但是能够删除样板代码并不总是意味着这是最好的方法。

WPF框架具有依赖项属性,其中包含大量的样板代码。在我的业余时间里,我研究了一种解决方案该解决方案极大地减少了需要编写的代码量。一年多以后,我仍在对该解决方案进行改进,并且仍然需要扩展其功能或修复错误。

问题是什么?为了学习新知识和探索替代解决方案,这很棒,但这可能不是最佳的商业决策。

WPF框架已有详细记录。它正确地记录了如何编写样板代码。尝试删除此样板代码是一个不错的练习,虽然绝对值得一试,但是要达到与msdn提供的相同的“抛光度”需要很长时间,而我们并非总是如此。


但是,我仍然对目前的结果感到满意,并很乐意在业余时间项目中使用它。:)
史蒂芬·杰里斯

3
每次我接触WPF和那些依赖项属性时,我总是希望C#像C ++的宏一样简单。当然,宏会被错误地使用,但是它们可以消除很多重复。下次我开始希望这些宏时,我将不得不看一下您的AOP框架:)
DXM 2012年

@DXM如果这样做,并且它崩溃得很惨,请不要忘记怪我并发布错误。; p
史蒂文·杰里斯

代码片段对依赖项属性非常有效。
Codism

@Codism:好的,这一个解决方案,但我也鄙视那些解决方案。:)
史蒂文·杰里斯

1

样板的问题在于它违反了DRY。本质上,当您编写样板时,您将在多个类中重复相同的代码(或非常相似的代码)。当需要更改该代码时,并不能完全确定开发人员会记住所有重复代码的地方。这会导致使用旧API或旧方法的错误。

如果将样板重构为公共库或父类,则只需在API更改时在一个位置更改代码。更重要的是,当发生意外更改时,代码会停在一个位置,并让您确切知道必须修复的内容才能使所有功能再次正常工作。这比一种更改导致数十个甚至数百个类的失败的方案要好得多。


2
这是怎样一个说法赞成的样板
克里斯·韦瑟林

0

我将采取不同的策略。开发的一致性是软件设计的最重要功能之一,它是使应用程序可扩展和可维护的关键工具,但是在跨多个站点,不同语言和不同时区管理团队时可能很难实现。

如果实现了一致性,那么“一旦您看过代码,您就已经看到了所有代码”,一致性将使代码更易于访问,维护和重构的成本要便宜得多,但最重要的是,易于扩展。当您编写一个需要一些样板的库以及库的强大功能时,您还获得了开发人员的支持:

  • 首先要了解功能的序言(样板),大多数关键类和访问点通常会作为样板的一部分。这为开发人员提供了进入文档的起点
  • 您对开发人员的期望将变得显而易见,例如,如果您将跟踪对象设置为序言的一部分,则开发人员将知道在哪里记录异常和信息
  • 当您迫使开发人员完成实例化类的过程时,会产生隐含的理解,他们可以推断出访问其余库所需的技术,并有机会向您介绍在整个库中使用的所有约定
  • 易于验证,当需要样板代码时,它通常可以非常容易地使用异常和保护子句进行自我验证,从而防止使用者在已经犯了错误的流程时陷入困境
  • 需要样板代码的库的配置很容易,因为已经有一个明显的实现点可以针对单个执行路径自定义功能。如果使用Factory或Command模式增强了所需的样板代码,则此功能特别有用

当您的库的使用者对实例化有所了解时,他们可以轻松地创建私有/扩展方法,父类甚至模板来实现样板代码。


-3

样板代码的唯一真正问题是,当您发现其中的错误时,必须修复使用它的所有地方,而不是重复使用的地方。

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.