反对注释的论据

Gan*_*alf 52 java annotations coding-style

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

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

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

mat*_*t b 20

实际上我认为你的直觉中的不良感觉更多地与注释混合配置与代码有关.

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

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

  • 注释也不错.在我看来,JPA对它们的使用是非常棒的.但是有一个时间和地点适合一切,如果你有超过两行的价值,你可能做错了什么. (8认同)
  • 在我的工作中,我发现我发现程序员通常认为"更好"的程序员真的不喜欢注释 - 但其他人都是关于他们的.既然这些人像我一样思考,可能就是为什么我认为他们是"更好"的程序员. (4认同)
  • 我想每个人都认为像他们一样认为是"更好"的人:) (4认同)

Tho*_*ung 12

也许你有一个遍及代码的冗余注释问题.使用注释,可以替换冗余注释,并且您的注释至少是DRY.

来自Spring博客:

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

@MyService
public class RewardsService {
…
}
Run Code Online (Sandbox Code Playgroud)

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

  • 为什么在没有编译的情况下,您有兴趣更改事务设置,当然,您肯定必须通过QA构建/部署/测试周期运行此类更改? (7认同)
  • 我想我更喜欢这样. (4认同)
  • XML:要查看的多个位置以获得完整的图片.理想情况下,解决手头问题所需要知道的一切都适合一页.如果您不需要配置,则只会增加复杂性.AOP:AOP非常强大.另一方面,它增加了复杂性(附加概念,新语言,复杂的运行时行为).如果用AOP替换@Transaction代理,你就会用另一种更复杂的技术取代相当复杂的技术. (2认同)

Sri*_*bat 8

现在已经是 2018 年了,这一点仍然具有现实意义。

我对注释的最大问题是您不知道注释在做什么。您正在切断一些调用者代码并将其隐藏在与被调用者断开连接的某个地方。

引入注释是为了使语言更具声明性,而不是程序性。但是,如果您将大部分功能转移到注释中,那么您实际上是将代码切换为另一种语言(而且不是一种很好的语言)。编译时检查很少。本文提出了同样的观点:https://blog.softwaremill.com/the-case-against-annotations-4b2fb170ed67

“将一切都转移到配置中,这样人们就不必学习如何编码”的整个启发式已经失去了控制。工程经理没有思考。

例外情况:

  • 联合单元
  • JAX-RS


Yis*_*hai 7

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

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

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

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

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

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

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

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

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

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

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

  • 我更大的问题甚至不在配置方面,而是使用@Controller之类的东西,而不是使用一个名为Controller的接口/抽象类,然后对其进行扩展/实现,这确实给我带来了糟糕的设计印象。如果您要忽略OO语言,为什么还要使用它呢? (2认同)

Uri*_*Uri 6

我个人觉得注释已经接管了太多,并且已经从它们最初的和超级有用的目的(例如,指示被覆盖的方法之类的小事)变成了这个疯狂的元编程工具。我认为 JAva 机制不够健壮,无法处理每个方法之前的这些批注集群。例如,这些天我正在与 JUnit 注释作斗争,因为它们以我不喜欢的方式限制了我

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

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

  • 使 XML 配置文件不完全痛苦的唯一一件事是 IDE 的支持。如果没有 IDE 支持、没有类型安全、没有重构等,以及所有这些字符串,那将只是一场噩梦。不,真的,我不认为 XML 是理想的。但是你是对的,注释也不理想,至少不是所有的。 (2认同)

lla*_*bda 5

像许多事情一样,有利有弊。在我看来,一些注解是好的,尽管有时感觉有一种过度使用注解的倾向,而一个普通的旧函数调用方法可能更胜一筹,总的来说,这可能会无意中增加认知负荷,因为它们增加了“做事”的方法。

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

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

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