反对注释的论据

时间:2009-11-04 18:02:47

标签: java coding-style annotations

我的团队正在迁移到Spring 3.0,有些人想要开始将所有内容都移到Annotations中。当我看到一个类似于以下方法的类时,我的内心感觉非常糟糕(代码味道?):(只是一个例子 - 并非所有真正的注释)

@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}

我只是落后于时代,还是这对所有人来说都是一个可怕的想法?而不是使用诸如继承和多态的OO概念,现在通过约定或通过注释来实现一切。我只是不喜欢它。必须重新编译所有代码来改变IMO配置的东西似乎是错误的。但它似乎是一切(特别是春天)的方式。我应该“克服它”还是应该回过头来尽量保持我们的代码免费注释?

12 个答案:

答案 0 :(得分:20)

实际上我认为你的直觉中的不良感觉更多地与注释混合,例如混合配置与代码。

我个人觉得和你一样,我宁愿在代码库本身之外和外部Spring中保留配置(例如事务定义,路径元素,控制器应该映射到的URL等等)。 XML上下文文件。

我认为,这里的正确方法归结为意见以及您更喜欢哪种方法 - 我预测社区的一半会同意注释方法,而另一半会同意外部配置方法。

答案 1 :(得分:12)

也许你的代码中存在冗余注释的问题。使用 meta-annotations 可以替换冗余注释,并且您的注释至少是DRY。

来自Spring博客:

@Service
@Scope("request")
@Transactional(rollbackFor=Exception.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
}

@MyService
public class RewardsService {
…
}

由于Java发展得如此缓慢,人们会将更多语言中缺少的功能放入注释中。这可能是某种形式的扩展这是一件好事,这是一件坏事,因为大多数注释都是解决方法并增加了复杂性。

答案 2 :(得分:7)

我最初也对注释持怀疑态度,但看到它们在使用中,它们可能是一件好事。它们也可能被过度使用。

关于注释要记住的主要事情是它们是静态的。它们无法在运行时更改。任何其他配置方法(xml,代码中的自我描述,无论如何)都不会受此影响。我已经看到这里的人在Spring上遇到了关于注入测试配置的测试环境以及必须下载到XML以完成它的问题。

XML不是多态的,继承的或其他任何东西,所以在这个意义上它不是倒退。

注释的优点是它可以为您的配置提供更多的静态检查,并且可以避免XML配置中的大量冗长和协调困难(基本上保持DRY)。

就像XML一样,Annotations可能会被过度使用。重点是平衡每个人的需求和优势。注释,在某种程度上,它们为您提供了较少的详细和DRYer代码,是一种可以利用的工具。

编辑:关于替换接口或抽象类的注释的注释,我认为在框架边界处可以是合理的在一个旨在被数百个(如果不是数千个)使用的框架中具有接口或基类的项目可以真正压缩事物(特别是基类,但如果你能用注释做到这一点,你没有理由不能用常规接口来做。

考虑JUnit4。之前,您必须扩展具有设置和拆除方法的基类。对于我的观点,如果它们是在接口上或基类中并不重要。现在我有一个完全独立的项目,它有自己的继承层次结构,他们都必须遵守这个方法。首先,他们不能拥有自己的冲突方法名称(在测试框架中没什么大不了的,但是你明白我的观点)。其次,你必须完全调用超级链,因为所有方法都必须耦合。

现在使用JUnit4,您可以在层次结构中的不同类中使用不同的@Before方法,它们可以彼此独立。没有注释,没有同样的干燥方法来实现这一点。

从JUnit的开发者的角度来看,这是一场灾难。拥有一个可以调用setUp和teardown的已定义类型会好得多。但是为了框架开发人员的方便,框架不存在,它的存在是为了方便框架用户。

如果您的代码不需要关心类型,那么所有这些都适用(也就是说,在您的示例中,无论如何都不会真正使用Controller类型)。然后你甚至可以说实现框架的界面比放置注释更容易泄漏。

但是,如果您要编写代码以在自己的项目中读取该注释,请远程运行。

答案 3 :(得分:4)

我个人觉得注释占用了太多,并且从他们原始的和超有用的目的(例如,指示被覆盖的方法之类的小事)中被炸毁到这个疯狂的元编程工具中。我不认为JAva机制足够强大,可以处理每种方法之前的这些注释集群。 例如,我现在正在与JUnit注释作斗争,因为它们以我不喜欢的方式限制我

话虽如此,根据我的经验,基于XML的配置也不是很好。所以引用南方公园,你要选择一个巨大的冲洗和一个三明治三明治。

我认为您必须做出的主要决定是,您是否更熟悉弹簧配置的非本地化(即,维护两个文件而不是一个),以及您是否使用受益于该文件的工具或IDE插件注释。另一个重要问题是使用或维护代码的开发人员是否真正理解注释。

答案 4 :(得分:3)

检查类似问题的这些答案

What are the Pros/Cons of Annotations (non-compiler) compared to xml config files

Xml configuration versus Annotation based configuration

基本上归结为:同时使用两者。他们两个都有用例。不要将注释用于那些应该保持可配置而不重新编译所有东西的东西(特别是你的用户应该能够配置的东西,而不需要你重新编译所有东西)

答案 5 :(得分:3)

我认为如果将它们与度量一起使用,注释就会很好。像@WebService这样的注释在部署和运行时做了很多工作,但它们不会干扰类。 @Cachexxx或@Transactional通过创建代理和大量工件明显干扰,但我认为它们已受到控制。

使用带有注释和CDI的Hibernate或JPA时,事情开始变得混乱。注释增长很多。

IMO @Service和@Repository是应用程序代码中Spring的干扰。它们使您的应用程序依赖于Spring,仅供Spring使用。

Spring Data Graph的案例是另一个故事。例如,@ NodeEntity在构建时向类添加方法以保存域对象。除非你有Eclipse和Spring插件,否则你会出错,因为源代码中不存在这些方法。

对象附近的配置有其好处,但也有一个配置点。注释在测量方面很好,但它们并不适用于所有内容,而且当注释行与源代码行一样多时,它们确实很糟糕。

我认为春天的道路是错的;主要是因为在某些情况下没有其他方法可以做这些有趣的事情。就像Spring想要进行xtreme编码一样,同时他们将开发人员锁定在Spring框架中。可能Java语言需要另一种方法来做一些事情。

答案 6 :(得分:3)

必须谨慎使用注释。他们对一些人有益,但对所有人都不是。至少xml配置方法将配置保存在一个文件(或多个)中,而不是遍布整个地方。这将引入(我喜欢称之为)糟糕的代码组织。如果配置分布在数百个文件中,您将永远不会看到配置的完整图片。

答案 7 :(得分:2)

我认为这在某种程度上取决于你何时开始编程。我个人认为他们很可怕。主要是因为他们有一些准“意义”,你不会理解,除非你碰巧知道有问题的注释。因此,它们本身就形成了一种新的编程语言,使您远离POJO。与(比如)普通的旧OO代码相比。第二个原因 - 它们可以阻止编译器为您完成工作。如果我有一个庞大的代码库并且想要重构某些东西或者重命名某些东西,那么理想情况下我喜欢编译器抛出所有需要改变的东西,或者尽可能地。注释应该是那样的。注释。不是代码行为的核心。它们最初设计为在编译时可选地省略,告诉您需要知道的所有内容。

是的,我知道XML配置会以同样的方式受到影响。这不会让情况变得更糟,同样糟糕。至少我可以假装忽略它 - 它并没有在每一个方法或参数声明中盯着我。

鉴于选择,我实际上更喜欢可怕的旧J2EE远程/家庭界面等(最初受到Spring人员的批评)至少可以让我了解最新情况,而无需研究@CoolAidFrameworkThingy及其缺陷。 框架人员的一个问题是,他们需要将您与他们的框架联系起来,以使整个企业在财务上可行。这与设计框架很不一致(即,它可以作为独立的,并且可以从代码中删除)。

不幸的是,注释很时髦。所以你很难阻止你的团队使用它们,除非你进入代码审查/标准等(也不合时宜!)

我读到Stroustup从C ++中留下了注释,因为他担心这些注释会被误用。有时事情往往是错误的方向几十年,但你可以希望事情会及时到来。

答案 8 :(得分:2)

像许多事情一样,有利有弊。在我看来,一些注释是好的,虽然有时感觉有一种过度使用注释的倾向,当一个普通的旧函数调用方法可能是优越的,并且作为一个整体,这可能无意中增加认知负荷,因为它们增加了如何“做事。”

让我解释一下。例如,我很高兴您提到了@Transactional注释。大多数Spring开发人员可能会了解并使用@Transactional。但是,有多少开发人员知道@Transactional实际上是如何运作的?他们是否会在不使用@Transactional注释的情况下知道如何创建和管理事务?使用@Transactional可以让我在大多数情况下更容易使用事务,但在特殊情况下,当我需要对事务进行更细粒度的控制时,它会隐藏这些细节。所以在某种程度上它是一把双刃剑。

另一个例子是Spring配置类中的@Profile。在一般情况下,它可以更容易地指定您希望加载Spring组件的配置文件。但是,如果您需要比仅指定要加载组件的配置文件列表更强大的逻辑,则必须获得环境对象自己并编写一个函数来执行此操作。同样,大多数Spring开发人员可能熟悉@Profile,但其副作用是他们对其工作方式的细节不太熟悉,例如Environment.acceptsProfiles(String ... profiles)函数。 / p>

最后,当注释不起作用时,可能更难理解为什么,你不能只在注释上放置一个断点。 (例如,如果您忘记了配置上的@EnableTransactionManagement,会发生什么?)您必须找到注释处理器并对其进行调试。使用函数调用方法,您当然可以在函数中添加断点。

答案 9 :(得分:1)

注释通常会引入不属于此类依赖项的依赖项。

我有一个类巧合,它具有类似于RDBMS模式中表的属性的属性。该类是在考虑此映射的情况下创建的。类和表之间显然存在关系,但我很高兴保持该类免于声明该关系的任何元数据。这个类在完全不同的系统中引用表及其列是否正确?我当然不反对将两者联系起来的外部元数据,并使每个元数据都无法理解另一个。我获得了什么?并不是说源代码中的元数据提供了类型安全性或映射一致性。任何可以分析JPA注释的验证工具都可以很好地分析hibernate映射文件。注释没有帮助。

在一份合同中,我创建了一个maven模块,其中包含来自现有包的接口实现包。不幸的是,这个新软件包是单片构建中​​的众多目录之一;我把它视为与其他代码分开的东西。尽管如此,该团队正在使用类路径扫描,因此我必须使用注释才能将我的组件连接到系统中。在这里,我不希望集中配置;我只想要外部配置。 XML配置并不完美,因为它将依赖性布线与组件实例化混为一谈。鉴于Rod Johnson不相信基于组件的开发,这是公平的。尽管如此,我再次感到注释并没有帮助我。

让我们将这与不打扰我的事情进行对比:TestNG和JUnit测试。我在这里使用注释,因为我知道我正在使用TestNG或JUnit来编写此测试。如果我为另一个替换一个,我理解我将不得不执行一个代价高昂的转换,这个转换会偏离重写测试。

无论出于何种原因,我接受TestNG,JUnit,QUnit,unittest和NUnit拥有我的测试类。在任何情况下,JPA或Hibernate都不会拥有碰巧映射到表的那些域类。在任何情况下,Spring都不会拥有我的服务。我控制我的逻辑和物理包装,以隔离依赖于其中任何一个的单元。我想确保远离一个人不会因为它留下的所有依赖性而使我瘫痪。说再见总比离开容易。在某些时候,离开是必要的。

答案 10 :(得分:1)

现在是2018年,这一点仍然有意义。

关于注释的最大问题是您不知道注释在做什么。您正在切断一些代码并将其隐藏在其他地方。

引入了注释,以使该语言更具声明性和编程性。但是,如果您将大部分功能移至注释,则实际上是在将代码切换为另一种语言(这不是一种很好的语言)。几乎没有编译时检查。本文提出了相同的观点:https://blog.softwaremill.com/the-case-against-annotations-4b2fb170ed67

“将所有内容移至配置,从而使人们不必学习如何编码”的整个试探法已失去控制。工程经理没有思考。

例外:

  • JUnit
  • JAX-RS

答案 11 :(得分:0)

注释在我的经验中很糟糕:

  • 无法在注释中强制使用类型安全性
  • 序列化问题
  • 交叉编译(例如javascript)可能是个问题。
  • 需要注释的库/框架从外部库中排除未注释的类。
  • 不可替代或互换
  • 您的项目最终会严重依赖需要注释的系统

如果Java具有类似“方法文字”的内容,则可以在相应的注释类中对一个类进行注释。 如下所示: 以javax.persistence和以下带注释的类为例:

@Entity
class Person
{
    @Column
    private String firstname;
    public String getFirstname() { return firstname; }
    public void setFirstname(String value) { firstname = value; }

    @Column
    private String surname;
    public String getSurname() { return surname; }
    public void setSurname(String value) { surname = value; }

}

我建议使用一个映射类,而不是注释:

class PersonEntity extends Entity<Person> {
    @Override
    public Class<Person> getEntityClass() { return Person.class;}

    @Override
    public Collection<PersistentProperty> getPersistentProperties() {
         LinkedList<PersistentProperty> result = new LinkedList<>();
         result.add(new PersistentProperty<Person>(Person#getFirstname, Person#setFirstname);
         result.add(new PersistentProperty<Person>(Person#getSurname, Person#setSurname);
         return result;
    }
}

此伪Java代码中的虚构“#”符号表示方法文字,当在给定类的实例上调用该方法文字时,将调用该实例的对应委托(从Java 8开始用“ ::”签名)。 “ PersistentProperty”类应该能够强制方法文字引用给定的通用参数,在本例中为Person类。

这样,您所拥有的好处比注释所能提供的要多(例如将您的“注释”类子类化),并且您没有上述缺点。 您也可以使用更多特定于域的方法。 唯一的预先注释已解决了这一问题,即通过注释,您可以快速查看是否忘记了包含属性/方法。但这也可以通过Java中更好的元数据支持来更简洁,更正确地处理(例如,在Protocolbuffer中考虑诸如required / optional之类的东西)