首頁  >  文章  >  Java  >  Java中萬惡的註解

Java中萬惡的註解

黄舟
黄舟原創
2017-02-20 10:09:341454瀏覽

本文由碼農網 – 孫騰浩原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃!

當Java 1.5引入註解,企業開發者對簡化EJB和其他企業產品開發抱持很大期望。可以看同一時期的一篇文章用EJB 3.0簡化企業Java開發。

然而從那時起,Java企業使用註解出現一些無法預料的後果和副作用,有些甚至到今天都沒有被注意到。幸運的是,並非所有的副作用都沒有被注意到,來看一些例子,在StackOverflow標題為“Why Java Annotations?”有很多有價值的評論,“Are Annotations Bad?”這篇文章有很棒的觀點,還有“Magics Is Evil”,“Annotations…Good, Bad or Worse?”。

Java中萬惡的註解

並非所有的註解都相同

儘管上面許多討論都包含有價值的觀點,但並不是所有註解都是相同的。

這裡有兩類註解,差別在於他們是否在運行期影響程式。首先,說一下無害的一類,它們並不會在運行期間對程式碼產生任何影響;另一種是有害的一類,它們會修改運行期行為。無害的註解包括@Deprecated, @Override, @SuppressWarnings, 等等。有害的註解包括@Entity, @Table, @PostConstruct, @ApplicationScoped,等等。

在無害的註解中存在一小部分註解,它們非常實用。有一些提供在編譯期間(靜態檢查)捕獲錯誤或提供安全保障。一些實用的註解包括:@Override, @NonNull/@Nullable 來自(Checker Framework), 等等。

為什麼有害的註解不好?

我們定義了一些有害的註解,為什麼要避免使用它們?

想像一個標準的Java Data類別擁有@PostConstruct方法。這個註解表示所標註的方法應該在物件創建好之後被呼叫。這個功能並不是由JVM處理,所以Date類別隱含取得未知的框架和容器,而自身語意並沒有做任何事。如果這些程式碼沒有運行在任何容器中,而只是運行在JVM中呢?這個註解大大降低了這個類別的重用性。另外對於任何使用Date的地方進行單元測試就變成了噩夢,因為你必須確保每次都正確綁定post-construction,要模擬一個相容的容器。這就有點可笑了,一個Date類別需要一個容器來運行,但這確實是有害的註解對類別、方法和參數的影響。

無可否認,業務邏輯往往複雜,需要更多依賴和關係,而不僅僅是一個簡單的Date類別。然而沒有理由在一個類別中明確或隱式地添加不必要的依賴或約束,有害的註解就是:依賴和約束。

企業陷阱

不幸的是有害的聲明在Java Enterprise 5大規模合法化。為了修正早期企業API的易用性問題,註解用來隱藏系統中冗餘的和難用的部分。新的JEE 5被稱讚為”輕量級”和”簡單”,表面上看起來是這樣。但是一個微小的,同時也是至關重要的誤用蔓延開來。

@Stateless
public class DocumentRepository {
   public Document getDocument(String title) {
      ...
   }
   ...
}


如果想要取得一個Stateless EJB,「只需要」在類別上宣告@Stateless註解。確實,編寫這個類別只需要只一點動作,但是請注意這個類別中有害的註解綁定了幾百頁的說明文檔,而且只能在百萬位元組的應用伺服器(Application Server)上運行。這又怎麼能稱的上是」輕量級」呢。所以,這個註解只是真正需要寫的Java程式碼的佔位符而已,程式碼仍需要以某種形式存在。現在只不過是隱藏在註解之下。

不幸的是,這種變通方案稱為一種模式,現在有害的註解廣泛分佈:JPA, CDI, Common Annotations, JAXB 等等。

有害的註解有時會出現在錯誤的地點

因為註解通常作為開發環境,有時有害的註解被當作單一職責原則(Single Responsibility Principle)或關注點分離(Separation of Concerns)的最佳實踐。

讓我們來考慮一下下面這個CDI範例:

@ApplicationScoped
public class DocumentFormatter {
   ...
}


上面的註解描述這個類別應該是一個CDI Bean,意味著它應該只能由CDI實例化,並確保每個應用程式中只有一個實例。

這些資訊並不屬於這個類別。這個服務在功能上(無論什麼方式)並不會對它在目前應用中的作用產生影響。這裡有兩個明顯的關注點。

一個JPA的簡單例子:

@Entity
@Table("PERSON")
public class Person {
   ...
}


问题在于这种类往往是”领域对象(domain objects)”,它们直接将领域模型持久化。更糟的是,数据传送对象(DTO)用来在对象之间传送数据,使得整个构造变得脆弱,因为对象间耦合过于紧密。不管怎样,这是一种错误的方式。

所有的这些附加的功能和(或)信息应该从这些类中分离出来,但是它们却悄悄混在一起,因为它们”只不过”是注解。

有害的注解有时蔓延

注解有时会传染其他对象。回顾上面那个CDI Bean。每个使用它的对象,每个依赖它的对象现在都拥有一个CDI注解,否则依赖关系树就不会构建成功。

@Entity注解也一样。因为对象之间的关系,其他对象也通过注解持久化,很快所有的持久化对象都会有这个注解。我们无法使用原生的第三方对象(除非序列化或包装它们),我们无法使用其他持久化机制(比如用NoSQL DB存放对象)。

这些注解使得这些对象无法复用。它们只能在一个严格的、受控制的、不透明的环境中使用,不能和任何东西整合。

有什么替代品?

是XML吗?当然不是,至少对于上面的例子来说不是。

Spring框架使用配置来管理对象,因此可以用XML当做配置文件。然而,是否某个依赖需要在运行期改变,而不通过重新编译?如果不需要,那么很难说配置应该用另一门语言来表示,尤其重构困难、测试困难、管理需要特殊工具。

真正的替代品当然是好的Java代码,正确封装并解耦的。是的,用代码来管理对象,尽管有时被当做样板(boilerplate),但并不算糟糕。它带来一些好处,比如让代码可读、可调试、可重构。只有那些长片的、复杂的、冗余的样板是糟糕的,比如“关于EJB 2.0”。但是解决方案并不是摆脱所有的样板或用另一种语言隐藏样板,而是简单干净的架构,直接而不多余的信息,简单并合适的方式来面向对象。

这也适用于JPA、Spring和其他东西。误用注解来表示功能会发生Stcakoverflow上这个问题“Arguments Against Annotations”,为什么不用已有的工具呢:比如Java语言本身和编译器,来解决这类问题,面向对象和软件最佳实践。

总结

如果注解在代码运行期加上了额外功能和约束,那它是有害的。这很糟糕,因为它隐藏了类或方法的切面,使之难懂、难复用、难重构、难测试。

不幸的是Java Enterprise不理睬Java开发者社区中发对注解的声音。所以企业级Java和其他”官方”框架更不可能重视这类问题。

至少我们可以持续关注有害的注解,如果可能尽量避免使用,编写新的框架和软件替换掉注解,不会出现有害注解所带来的问题。

 以上就是Java中万恶的注解 的内容,更多相关内容请关注PHP中文网(www.php.cn)!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn