Java與groovy混編 - 一種兼顧接口清晰和實現敏捷的開發方式

phpde1 9年前發布 | 25K 次閱讀 Java Groovy Java開發

原文鏈接: http://pfmiles.github.io/blog/java-groovy-mixed/

  1. 有大量平均水平左右的“工人”可被選擇、參與進來 —— 這意味著好招人
  2. 有成熟的、大量的程序庫可供選擇 ——
    這意味著大多數項目都是既有程序庫的拼裝,標準化程度高而定制化場景少
  3. 開發工具、測試工具、問題排查工具完善,成熟 ——
    基本上沒有團隊愿意在時間緊、任務重的項目情況下去做沒有把握的、基礎開發工具類的技術試探
  4. 有面向對象特性, 適合大型項目開發 ——
    無數大型項目已向世人述說,“面向對象”是開發大型軟件的優秀代碼組織結構
  5. 能適應大型團隊、多人協作開發 ——
    代碼需要簡單易懂,起碼在接口、api層面是這樣

—— 這是我所理解的“工業化開發編程語言”的概念

很顯然, java就是種典型的“工業語言”, 非常流行,很多企業靠它賺錢,很實際;但java也是常年被人黑,光是對其開發效率的詬病就已經足夠多,不過java始終屹立不倒;

這樣的局面其實無所謂高興還是擔憂,理性的程序員有很多種,其中一種是向“錢”看的 —— 我寫java代碼,就是因為工作需要而已,能幫助我的組織搞定業務,做出項目,這很好;當有人說java語言不好的時候,理性的程序員不會陷入宗教式的語 言戰爭之中,他會思考這些人說的是否有道理;如果真的發現整個java平臺大勢已去,他會毫不猶豫地扭頭就走,不過直到目前為止,還沒有這種跡象出現;

那么,從這些無數次的口水之爭中,我們能否從別人的“戰場”上發現一些有用的東西, 來改進我們的開發方式,從而使得java這種已經成為一個“平臺”的東西走得更遠,賺更多的錢呢?答案是“有的”,感謝那些參與口水戰爭的、各種陣營的年 輕程序員們,有了你們,java speaker們才有了更多的思考;

我就只談一個最實際的問題:

java被吐槽的這些年, 就開發效率這一點而言,到底有哪些東西是值得借鑒的?

也就是說,到底是哪些主要特性直接導致了某些其它語言在語法上相對于java的優越感?

豐富的literal定義

在groovy中定義map和list的慣用方式:

def list = [a, 2 ,3]
def map = [a:0, b:1]

而java呢?只能先new一個list或map,再一個個add或put進去; 上面這種literal(字面量)形式的寫法便捷得多;

而javascript在這方面做得更絕, 我們都用過json,而json其實就是literal形式的object

極端情況下,一門編程語言里的所有數據類型,包括”內建”的和用戶自定義的,統統可以寫成literal形式;在這種情形下,其實這種語言連額外的對象序列化、反序列化機制都不需要了 —— 數據的序列化形式就是代碼本身, “代碼”和“數據”在形式上被統一了

java對這方面幾乎沒有任何支持,對于提高編碼效率來講,這是值得學習的一點, 起碼“內建”數據結構需要literal寫法支持

first-class function & higher-order function & function literal(lambda)

無論是js, 還是python/ruby,或是groovy,都可以將函數作為另一個函數的參數傳入,以便后者根據執行情況判斷是否要調用前者

或者能夠將一個函數作為另一個函數的返回值返回,以便后續再對其進行調用

這種高階函數特性,就不要再說java的匿名內部類“能夠”實現了, 如果認為匿名內部類已經”夠用”了的話,其實就已經與現在的話題“開發效率”相悖了

高階函數顯然是一種值得借鑒的特性,它會讓你少寫很多很多無聊的“包裝”代碼;

還有就是匿名函數(lambda)了我不喜歡lambda、lambda地稱呼這個東西,我更喜歡把它叫做“匿名函數”或者“函數字面量(literal)”, 因為它跟數學上的lambda演算還是有本質區別,叫”lambda”有誤導的危險

函數字面量的意思就是說,你可以在任何地方,甚至另一個函數體的調用實參或內部,隨時隨地地定義另一個新的函數這種定義函數的形式,除了“這個函數我只想在這里用一次,所以沒必要給它起個名字”這種理由之外,還有一個更重要的理由就是“閉包”了

所謂閉包,其實也是一個函數,但是在這個函數被定義時,其內部所出現的所有”自由變量(即未出現在該函數的參數列表中的變量)”已被當前外層上 下文給確定下來了(lexical), 這時候,這個函數擁有的東西不僅僅是一套代碼邏輯,還帶有被確定下來的、包含那些“自由變量”的一個上下文, 這樣這個函數就成為了一個閉包

那么閉包這種東西有什么好呢?其實如果懶散而鉆牛角尖地想,閉包的所有能力,是嚴格地小于等于一個普通的java對象的,也就是說,凡是可以用 一個閉包實現的功能,就一定可以通過傳入一個對象來實現,但反過來卻不行 —— 因為閉包只有一套函數邏輯,而對象可以有很多套,其次很多語言實現的閉包其內部上下文不可變但對象內部屬性可變

既然這樣,java還要閉包這種東西來干嘛?其實這就又陷入了”匿名內部類可以實現高階函數”的困境里了 —— 如果我在需要一個閉包的時候,都可以通過定義一個接口再傳入一個對象來實現的話,這根本就跟今天的話題“開發效率”背道而馳了

顯然,java是需要閉包的

強大而復雜的靜態類型系統

這和開發效率有關么?編程語言不是越“動態”,開發效率越高么?還需要強大而復雜的靜態類型系統么?

試想一下這種api定義:

def eat(foo) {
    ...
}

這里面你認識的東西可能只有’吃’了, 你知道foo是什么么?你知道它想吃什么么?吃完后要不要產出點什么東西? —— 你什么都不知道這種api極易調用出錯,這就好比我去買飯,問你想吃什么你說“隨便”,但買回肯德基你卻說你實際想吃的是麥當勞一樣

可能你還會反駁說,不是還有文檔么?你把文檔寫好點不就行了么? —— 不要逼我再提“匿名內部類”的例子,如果給每個函數寫上復雜詳盡的文檔是個好辦法,那就顯然 —— again, 與“開發效率”背道而馳了

那么,靜態類型系統,這里顯然就該用上了

靜態類型系統在多人協作開發、甚至團隊、組織間協作開發是非常有意義的;

擁有靜態類型系統的編程語言通常都有強大的、帶語法提示功能的IDE,這很正常,因為靜態類型語言的語法提示功能好做;

只要把別人的庫拿過來,導入IDE,各種函數簽名只需掃一眼 —— 很多情況下根本不需要仔細看文檔 —— 就已經知道這個函數是干嘛用的了, 合作效率成倍提升;

而且,作為”api”,作為“模塊邊界”,作為與其它程序員合作的“門面”, 函數簽名上能將參數和返回值類型“卡”得越緊越好 —— 這樣別人不用猜你這個函數需要傳入什么類型,甚至他在IDE里一“點”,這里就給自動填上了 :)

要做到“卡得緊”,光有靜態類型系統還不夠,這個系統還需強大, 試想一下這個例子:

/**
 * 我只吃香蕉和豬肉,請勿投食其它物品
 */
public void eat(List<Object> list) {
  for(Object o: list) {
    if(o instanceof Banana){
      ... // eating banana
    } else if(o instanceof Pork) {
      ... // eating pork
    } else {
      throw new RuntimeException("System err.");
    }
  }
}

這段純java代碼已經是“定義精確”的靜態類型了

但如果沒有上面那行注釋,你很可能會被System err.無數次

而這行注釋之所以是必需的,完全是因為我找不到一個比List<Object>更好的表達“香蕉或豬肉”的形式, 這種情形足以讓人開始想念haskell的either monad

在“強大而復雜的類型系統”這一點上,jvm平臺上令人矚目的當屬scala了,可惜java沒有,這是值得借鑒的

不過這一點的“借鑒”還需java的compiler team發力,我等也只是說說(按照java保守的改進速度,估計HM類型系統是指望不上了)

動態類型系統,duck-typing

剛說完靜態類型,現在又來說動態類型系統合適么?

然而這與節操無關,我想表達的是,只要是有助于“開發效率”的,都能夠借鑒,這是一個理性的java speaker的基本素質

我們在開發項目的時候,大量的編碼發生在“函數”或“方法”的內部 —— 這就好比你在屋子里、在家里宅著一樣, 是不是應該少一些拘束,多一些直截了當?在這種情形下,動態類型系統要不要太爽? ——

Void visitAssert(AssertTree node, Void arg1) {
  def ahooks = this.hooks[VisitAssertHook.class]
  ahooks.each {it.beforeVisitCondition(node, errMsgs, this.ctx, resolveRowAndCol, setError)}
  scan((Tree)node.getCondition(), arg1);
  ahooks.each {it.afterVisitConditionAndBeforeDetail(node, errMsgs, this.ctx, resolveRowAndCol, setError)}
  scan((Tree)node.getDetail(), arg1);
  ahooks.each {it.afterVisitDetail(node, errMsgs, this.ctx, resolveRowAndCol, setError)}
  return null;
}

你知道ahooks是什么類型么?你不知道但我(我是編碼的人)知道你知道ahooks身上有些什么方法可以調么?你同樣不知道但我知道

你不知道沒關系,只要我知道就行了,因為現在是我在寫這段代碼;這段代碼寫完以后,我只會把Void visitAssert(AssertTree node, Void arg1)這個類型明確的方法簽名提供給你調用,我并不會給你看函數體里面的那坨東西,因此你知不知道上面這些真的沒關系

方法內部滿是def, 不用書寫繁復的List<Map<String, List<Map<Banana, Foo>>>>這種反人類反社會標語, 每個對象我知道它們身上能“點”出些什么來,我只管“點”,跑起來之后invokedynamic會為我搞定一切

動態類型系統 —— 這就是方法內部實現應該有的樣子哪怕你的方法內部實現就是一坨shi,你也希望這坨shi能盡可能小只一點,這樣看起來更清爽是吧?

不要說我太分裂,我要笑你看不穿 —— 靜態類型和動態類型既然都有好處,那么他們能放在一起么?

能的,這里就需要點明這篇文章的政治目的了: “java與groovy混編”

而且,目前來看,jvm平臺上,只有它二者的結合,才能完成動態靜態混編的任務

曾經我發出過這樣一段感嘆:


公共api、對外接口聲明、應用程序邊界…這些對外的“臉面”部分代碼,如果擁有scala般強大的類型系統…就好了;而私有代 碼、內部實現、各種內部算法、邏輯,如果擁有groovy般的動態、簡單的類型系統…就好了;綜上,如果有門語言,在接口和實現層面分別持有上述特性,就 好了

這種“理想”中的語言或許某天我有空了會考慮實現一個

而現在,雖說不是scala,但我終于想要在java和groovy身上來試驗一把這種開發方式了

這里我坦白一下為什么沒用scala,原因很簡單,我在技術選型方面是勢利的,scala還不被大多數平均水平的java開發人員(參見”工業化開發編程語言”定義第一條)接受,這直接導致項目的推進會遇到困難

而相對來講,我暫且相信大多數java開發人員都還算愿意跨出groovy這一小步,當然這還需要時間證明

好了,下面還剩下一點點無關痛癢的牢騷 ——

元編程能力

macro, eval, 編譯過程切入, 甚至method missing機制,這些都算“元編程”

元編程能力的強弱直接決定了使用這種語言創作“內部DSL”的能力java在元編程方面的能力,幾乎為0

這是值得借鑒的

與groovy的混編,順便也能把groovy的元編程也帶進來

各種奇巧的語法糖

語法糖,關起門來吃最美味,這也是一種使得“方法內部實現更敏捷”的附加手段

網上隨便下載一份groovy的cheat sheet, 都會列舉groovy的那些寫代碼方面的奇技淫巧

這些奇技淫巧,在各種腳本語言之間其實都大同小異, 因為他們本來就是抄來抄去的

結合方法內部的動態類型環境,這一定會進一步縮小方法內部實現代碼的體積

java & groovy混編:一種最“勢利”的折衷

我不去討論什么語言才是The True Heir of Java, 那會使這篇文章變成一封戰書,我只關心如何更好地利用現有開發資源完成項目,高效地幫組織實現利益

所以說java和groovy的混編是一種最“勢利”的折衷,我不想強迫平均水平的開發人員去學習一種完全不同的語言,短期內不會對項目有任何好處,真正想去學的人他自己會找時間去學

而groovy,說它是java++也不為過,因為java代碼直接就可以被groovy編譯, groovy完全兼容java語法, 對一般java開發人員來說,這真是太親切了

這里我要提一下我對“java和groovy混編”的一個個人性質的小嘗試 —— kan-java項目

kan-java這個小工具,凡是用戶在編碼使用過程中能“碰”到的類和接口,全部都由java定義, 這確保用戶拿到的東西都有精確的類型定義

凡是對上述接口的實現,都以groovy代碼的形式存在

這貫徹了”接口靜態類型,內部實現動態類型”的宗旨, 或者說“凡是要提供給另外一個人看、調用的地方(接口或接口類),使用java,否則就用groovy”

當然了,單元測試也完全由groovy代碼實現

將kan-java的jar包引入到項目中使用時,就跟使用其它任何純java實現的jar包一樣 —— 接口清晰,參數類型明確,返回類型明確, 你不會也沒有必要知道開發人員在具體實現的時候,使用動態語言爽過一把

對于java和groovy的混編,項目的pom.xml如何配置,除了可以參考kan-java的配置外,還可以參考這個gist: https://gist.github.com/pfmiles/2f2ab77f06d48384f113 , 里面舉例了兩種配置方式,各有特色

具體的效果,還需要真正地去實際項目中體會另外,kan-java也是一個有趣的工具,這個工具所實現的功能我也是從未見到java世界內有其它地方討論過的,它可以輔助java做“內部DSL”,有場景的可以一試

 本文由用戶 phpde1 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!