Java中萬惡的註解
本文由碼農網 – 孫騰浩原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃!
當Java 1.5引入註解,企業開發者對簡化EJB和其他企業產品開發抱有很大期望。可以看一看同一時期的一篇文章用EJB 3.0簡化企業Java開發。
然而從那時起,Java企業使用註解出現一些無法預料的後果和副作用,一些甚至到今天都沒有被注意到。幸運的是,並非所有的副作用都沒有被注意到,來看一些例子,在StackOverflow標題為“Why Java Annotations?”有很多有價值的評論,“Are Annotations Bad?”這篇文章有很棒的觀點,還有“Magics Is Evil”,“Annotations…Good, Bad or Worse?”。
並非所有的註解都相同
儘管上面許多討論都包含有價值的觀點,但並不是所有註解都是相同的。
這裡有兩類註解,區別在於他們是否在執行期影響程式。首先,說一下無害的一類,它們並不會在執行期對程式碼產生任何影響;另一種是有害的一類,它們會修改執行期行為。無害的註解包括@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和其他”官方”框架更不可能重視這類問題。
至少我們可以持續關注有害的註解,如果可能儘量避免使用,編寫新的框架和軟體替換掉註解,不會出現有害註解所帶來的問題。
譯文連結:http://www.codeceo.com/article/java-evil-annotations.html
英文原文:Evil Annotations
翻譯作者:碼農網 – 孫騰浩
[ 轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]
相關文章
- Java中的註解-自定義註解Java
- Java中的註解-自定義註解處理器Java
- 關於Java中的@Deprecated註解Java
- JAVA中的註解可以繼承嗎?Java繼承
- java中如何自定義註解Java
- 【轉】java中註解的使用與例項Java
- Java註解的使用Java
- 萬惡的前端記憶體洩漏及萬善的解決方案前端記憶體
- Java 註解及其在 Android 中的應用JavaAndroid
- java註解Java
- 註解 javaJava
- Java註解詳解Java
- Java 註解詳解Java
- JAVA-註解(2)-自定義註解及反射註解Java反射
- 註解專題(一)Java元註解,內建註解Java
- Java註解解析-搭建自己的註解處理器(CLASS註解使用篇)Java
- JAVA 註解 AnnontationJava
- Java註解AnnotatonJava
- Java反射-註解Java反射
- Java™ 教程(註解)Java
- Java —— 註解(Annotation)Java
- Java(5)註解Java
- java註解,反射Java反射
- Java Annotation 註解Java
- Java--註解Java
- Java 註解(Annotation)Java
- java中SpringBoot定時器註解JavaSpring Boot定時器
- Java註解詳解「註解專案實戰」Java
- Java中的幾種註釋Java
- Java註解(Annotation)詳解Java
- Java註解與反射的使用Java反射
- 深入理解 Java 註解 [元註解(一)]Java
- 註解式專案開發!詳細解析Java中各個註解的作用和使用方式Java
- Java 8 註解探秘Java
- Java 註解完全解析Java
- 自定義JAVA註解Java
- Java 註解全面解析Java
- Java反射與註解Java反射