2013年12月30日

使用 Grails 開發應用系統之感

ihower 的一篇 FAQ 開頭中提到 [Rails 發明人... : Rails Is Not For Beginners], 不知這自問自答式的內容是否也同意了 Rails 發明人的說法?! 但, 卻又像傳教士般的引導閱者進入 RoR 的世界之中?!

lyhcode 的 [程式設計師小心別被框架給「框」住了...] 一篇也提到 [... 如果一位「資深」的 Java 程式設計師,在過去 3-5 年間在專案中應用這些技術,可是不深入瞭解這些框架解決了甚麼問題、用什麼方法實作、底層如何運作以及如何擴充或調校,就很難有真正屬於自己的 Know-how。...], 文中意在說明"用過"(而非"適當運用")一些 framework 時存在的風險。

當個人近幾年發展的一些小案專使用 Grails 時, 也意識到了一點點以上的狀況: 需要溝通並傳達一些基礎與概念, 讓其他同仁放下排斥的意念並願意花時間來接受, 同時也須避免被它給"框"住了。

譬如: Grails 1.1.x 時代, GORM 並不支援 enum, 如果要使用 inList constraints 並活用在 GSP viewer 中, 確實要花一點技巧才能達到; 雖然在 v1.2.1 以後就支援了, 但當下專案驗收卻是不會等人的。

又如: legacy RDB 與 GORM 結合, 其實談的不是 ORM 而是 R-->O mapping; 因為資料庫是 DBA 在管的, 不是 AP 開發人員想怎樣就怎樣, 恣意使用了 object ID 而挷架了 DB。因此, adopt 欄位時定義了諸如下列的 domain class:
class Department {
    String id // 不使用內定的 long type
    String code
//    ...
    String description
 
    static transients = ['code']

//  ...

    static constraints = {
        id(maxSize: 3)
        code(nullable: false, blank: false, size: 2..3, unique: true)
//        ...
    }
 
    static mapping = {
        id generator: 'assigned'

        columns {
            id column: 'code' // code 即 id
//            ...
        }
    }

//    ...

// 以 code 代替 id
    void setCode(String code) {
        this.id = code.toUpperCase() // 轉 upper case
    }

    String getCode() {
        this.id
    }
}

但, 在到了 v2.3.x 之後卻不能正常運作了, 因為 code 欄位並不是真正的欄位 (尚不知是 Hibernate 版本的問題, 還是 Grails 版本的問題); 如果真的無法增加 id 欄位而只能修改的話, 應該會如下:
class Department {
// ...(略)...
 
    static constraints = {
        id nullable: false, unique: true, maxSize: 3
        code blank: false, size: 2..3
//        ...
    }

    static mapping = {
        id column: 'code', generator: 'assigned'
//        ...
    }

 
//    ...(略)...
 
    String getId() {
        this.id ?: '' // 防止 null value 造成 retrieving 問題
    }

    void setCode(String code) {
        setId(this.code = code?.toUpperCase())
    }
}
除了喪失了對 code 欄位進行 unique constraint 作用, 不料輸入相同重複的 code(即id) 之後, 即使在 controller 中加了 rejectValue() 控制:
def save(Department departmentInstance) {
//    ...

    try {
        departmentInstance.save flush:true
    } catch (e) {
        if (ExceptionUtils.getRootCause(e) instanceof NonUniqueObjectException) {
            departmentInstance.errors.rejectValue('code', 'default.not.unique.message',
                ['code', Department.class.name, departmentInstance.code] as Object[], '')
            respond departmentInstance.errors, view:'create'
        }
        return
    }
//    ...
}
仍無法阻止資料的存檔( create失敗 卻變成 update成功 )!
一時之間找不到解法, 腦袋中頓時出現 "框" 住的感覺。

不過, 正如過去長官的提點: 做事情要有中心思想。如果, 不該"重新造輪子"、"站在巨人的肩膀上"... 角度思考來使用 Grails, 理應快速開發客戶所需的應用系統。畢竟它背後有 SpringSouce(spring.io) 及眾多的 user group 在支撐。

2013年10月31日

用 unoconv 轉出我的 .xlsx 資料

典型的應用程式開發多會處理資料庫(RDB)。
有很多情形是版本會依不同的開發環境不同而資料庫也有所不同,例如:
  • Dev. App sever --> Dev. DB server
  • UAT App server --> UAT DB server
對於 Dev. DB server 上的資料,無論是測試案例或設定資料會不斷、不斷的異動;或者像 UAT DB server 上的資料會從不同的測試 phase 往前或往後進、退版時而所有異動。

App 前後版本的異同有很多開發工具可用而能看出差異,但對於要觀察或審視因此而調整的差異資料,要如何比對出來呢?

這個問題對於精熟資料庫及工具的 DBA 來說,也許不是什麼大問題;要模擬前後不同的(資料庫中)測試案例或設定資料,無論是觀察或資料補遺,免不了要進行資料兩相比對,這種情形可能來說,但對於應用程開發人員來說會是不小的困擾。

以個人而言,以  Unix 為作業平台使然,會第一時間聯想到很多 command 來操作文字型態的資料差異,例如:diff。再者,很多資料庫相關的工具有很多匯出的功能,例如 SQL Developer 能匯出 .csv 檔案。如此來說,可以用 diff 指令比對不同時點或不同資料庫同一 Table 資料的匯出檔,即可分辨其差異。

但,個人也遇過有些開發或資料管理人員喜歡使用 Excel 來進行資料統整;若遇上要用此來做為資料比對的對像,又要如何呢?

直觀上,就是使用 Excel 來轉存 .csv 檔案即可。

但幾種情形可能不見得適用:
  • 沒有 license 供安裝 Excel
  • 要大量、批次比對 (這裡指的對像是 Tables)
  • 非 Unix / Linux 平台使用者 (意指 command line 型式)
想到的方法是 OpenOffice/LibreOffice,以 command line 方式匯出,供 diff 指令來進行比對,然後以 shell script 型式存在,就可以進行批次、自動化作業。(當然,也有人會用 VBS 對 Excel 調用 API 來做,再用排程來進行此一作業)

使用 OpenOffice/LibreOffice 時可借助 unoconv (python code) 來做,可省去很多寫 script 的時間。做法:
  • 安裝 OpenOffice/LibreOffice (通常 python 是預裝好的,若否也要安裝)
  • 設定 PATH,確定能執行到 python、OpenOffice/LibreOffice、unoconv
  • 分別匯出欲比對的兩個 .xlsl 檔案成 .csv 檔案
    unoconv -f csv myTableVer1.xlsx
    unoconv -f csv myTableVer2.xlsx
    
  • 儲存比對的結果
    diff myTableVer1.csv myTableVer2.csv > myTable.diff
    cat myTable.diff | colordiff | less -R
that's it.

2013年10月27日

Upgrade 我的 Snow Leopard 到 Mavericks

使用 OSX 10.6 有一段時間了,對於 software development 來說開始有些不方便了;像是 XCode 工具的版本限制以及 OpenJDK 7 安裝問題等。自從 Apple 公司發表新款 iPhone 5S/C 與 iPad Air 同時,突然佛心來著,OSX 10.9 提供免費升級:
當然,對個人來說簡直提天掉下來的禮物,於是開始著手安裝。
(1) 程序上總是先求保險,得準備第 2 顆硬碟進行資料備份:
(2) 設定第 2 顆硬碟開機,測試並確認資料有抄寫:
(3) 無誤後再設回原開機磁碟,重開機後開始下載安裝:
安裝完畢後,便是檢查所有常用應用程式的相容問題;多數情形下使用系統更新作業或程式換版就可以解決了。不過,最麻煩的是得下載幾個重量級的更新,例如:XCode;而且原系統中的 JDK 6 會被移除,須自行下載與安裝 Java for OS X 2013-005

整個升級的過程最需的是體力與細心,因為備份時的資料整理與枯等,安裝後的目錄、檔案的移置、缺漏確認等,所需的時間很長不消十數小時。萬一不小心,大多資料的遺失可是會損失慘重。

而初步的使用心得,有 3 個小地方待適應:
(1) mouse / pad 的 scrolling 手勢方向與 v10.6 相反
(2) [swipe between pages] 手勢的功能,因不熟悉而常將正在瀏覧的頁面切到上一頁或下一頁
(3) 輸入法 hint 與過去的方式有點不同,尤其是標點符點的字鍵有更動,會一時之間找不到

that's it!

ps. 另外,中文字體看起怪怪、醜醜的

2013年10月15日

Multiple assignment in Groovy

在 Groovy 的說明資料中提到, 物件可以進行 multiple assignment。從其範例可看出 values 是 List 型別的: ...The syntax works for arrays as well as lists, as well as methods that return either of these...。

在 HA.KI blog 的範例 中最後一個也舉出了使用 regular expression 的技巧:
def money  = '12 Euro'
def regexp = /(\d+) (\w+)/
def (exp, amount, currency) = (money =~ regexp)[0]

assert '12' == amount
assert 'Euro' == currency

不過, 對於 List 的表述方式:
listObject[index]
// 或
listObject.get(index) // 保守寫法: listObject?.get(index)
是存在當掉的風險的, 因為 index 有可能超過索引範圍。
以上例來說, 只要 money 的值不是依  '數值 幣別'  呈現就會發生。 (例如 'Euro  12')

所以, 可以改善的技巧是用 inject() [參考上篇Groovy 的 inject() method 應用] 來呈現 List 物件即可:
def (exp, amount, currency) = (money =~ regexp)?.inject([]) { res, itm ->
    res += itm
}
ps. 參考資訊 http://rosettacode.org/wiki/Return_multiple_values

2013年10月5日

GVM 與 GroovyServ

執行 .groovy code 不外乎要設定 PATH 與 CLASSPATH,以找到 interpreter(groovy)、compiler(groovyc) 與相關的 library;它往往需要在啟動的 shell 或 user profile 來加以處理。但,以系統維運的角度來說,則需要"安裝",以便整合至 environment 之中,維持系統固定、一致版本的 interpreter 與 compiler。以 ubuntu 系統為例:
sudo apt-add-repository ppa:groovy-dev/groovy
sudo apt-get update
sudo apt-get install groovy

groovy -version

Groovy Version: 1.8.6 JVM: ...
使得 groovy script 有如 shell script 一般,只要在 script 的第一行指出 interpreter 即可:
#!/usr/bin/bash
# this is a bash shell script

echo "1 + 2 = $((1+2))"

# ...
#!/usr/bin/env groovy
// this is a groovy script

println "1 + 2 = ${1+2}"

// ...

另一種安裝的選擇:GVM;它有如 linux 的 alternatives 來管理不同版本的 package 路徑一般 (alternatives 可用來安裝多版本 JDK),安裝後並指定 groovy 的版本。使得 groovy script 的第一行寫法保持一致、不受影響。

話說 language ,不免會被拿來與過去熟知的做比較;Java 也曾被"提過"很慢,但隨時代的演進這已是不再拿來作文章的話題了。近年來 JVM 上熱門的 dynamic language:Groovy ;也一樣引起話題:慢。

也就是說 script 會因功能的增加而 source code 越來越長,也造成編譯與執行的時間相對變長;這往往會造成 developer 的困擾。這種在編譯時期一樣有這"症頭"的還有 Scala;不過,人家它有 fsc;那 Groovy 呢?

還好出現了 GroovyServ。

以抓取某熱門森林遊樂區的訂房首頁的應用為例:
僅使用 groovy interpreter,執行的時間:
real 0m6.180s
user 0m4.875s
sys  0m0.271s
而搭配 GroovyServ 使用,在執行一次以後則:
real 0m1.445s
user 0m0.018s
sys  0m0.040s
看的出它的"效果"驚人!這對於 developer 的工作有相當大的幫助。並且, GVM 工具也支援安裝 GroovyServ,維護上相當方便。

ps.
停用 GroovyServ 的指令:
groovyserver -k