2015年5月2日

程式設計=廉價工作?

不知多久以來,一直存在的一種狀況:程式設計是廉價的工作。
會有這樣子的心情跟感受,有三:
  • 有人說,程式設計是很無趣的 ("好像"不是很難、不是很有價值的工作)。
  • 技術門檻越來越低,從業的人越來越多;而且做出來的軟體系統,品質並不高。(原因可能不只是"人"的部份而已)
  • 經歷過,而且很長的一段時間被當做廉價勞工。
很多年前在網路上看到(而已經找不到)的一份(好像是M$做的)資料;指出,(不良的)系統調教的方向與範疇,70% 來自所做的應用系統,20% 需要檢視作業系統,而 10% 則可能看看主機硬體是否有調整或升級的必要。個人在這個行業多年的觀察,70% 之語應該指的是歐美的情形,但對照於國內來看,那算是太客氣了。

7、8 年前接觸到的 Java 應用系統,一個是自己主導的、一個是有參與的、一個是維運而沒有權力介入的,還有現在維運的系統;都看到了一個很平常的問題:系統慢、很吃記憶體...等。而這些系統,甚至與其他見過的 80% 的應用系統,都有一個(很典型的)共同點:處理(關聯式)資料庫的資料。

除了自己主導做的系統很認真的設計外,餘者多讓我覺得:難怪很多人認為程式設計是廉價的。因為,連基本的資料庫處理方法都做的不好。

在這裡想指出兩個觀點:
  • 資料連線的建立是耗時、不經濟的
  • 除非是跨資料庫,否則少有同時多個資料庫連線的需要
白話的意思是:除非有情境需要特殊設計外,否則一般應用程式當下只會用到一個資料庫連線資源 (connection object)。

話說回來,這個年代所做的已不是十多年前的 standalone program,而多半是 web application。使用的是 App Server 來做為程式的執行環境。這些 App Server 執行環境存在的主要目的,其中一個就是資源管理(/控),而且資料庫連線資源是大宗。程式基本上是向 DataSource (也就是 connection pool) 取得資料庫連線資源 (connection object),基於並受限於 pool 的管制以免資料庫 (DB server) 受到太大的網路連線需求衝擊。

然而,除非你買的是百萬元級 App Server(例如 WebLogic);否則,pool 中的資源並沒受制於 API 的規定而受特別控制;一旦,程式要求 connection 時,pool manager 就會試著產生一個 connection 給程式用:
// DataSource.getConnection()
Connection conn = someDataSource.getConnection();
// ...
return conn;

但抱歉的是,用的 App Server 不是百萬元起跳,程式也不只是一個 class 、一個 data table 來源,就可以把前端 (client side) 的作業需求處理完畢。一旦,一群 class、一堆 data table 的處理,各 class 程式中需要 connection 時,透過上面的 API 取用;那麼,一個作業 (one thread) 可能會用到很多 connection objects,而同時間不同的使用者作業(multi-thread),將勢必造成 connection 不足 (leak) 的現象。

索性,O.O. 的世界有個重要的觀念:封裝。我們可以做一個 helper class,封裝 get connection 的需求:
// ...
Connection conn = ConnectionHelper.getConnection(dataSourceName);
// ...

在其中有兩件事情要處理:
  1. 以 dataSourceName 向 context 取得 DataSource。
  2. 向 DataSource 取得 Connection。

第 1 點的做法:

  • 使用 Servlet 的 load-on-startup 定義,對整個系統會用到的 DataSource 進行登記,以便爾後繼續取用。
  • 若改用 Spring framework 的話,則 bean object 除了不為 lazy initialized 外,利用 init-method 來進行 DataSource 登記,而這個 DataSource 則用 JndiObjectFactoryBean 以 JNDI name 取得並注入 (inject) 即可。
  • 若不是這兩個方法,而想自己做 singleton object(甚至用 static class) 的話,要注意的是:不要每次都呼叫/調用 new InitialContext() 及 lookup()。因為...它不經濟而且有點慢;現況來說,太常見到這類程式臨時在這裡採用這種方式的設計,做的只是上面的事。是浪費記憶體、[笨的可以] 的方法。可以做的是改用 static holder 技巧,該 holder 則是用 static inner class 在 class loading 初始化階段產生,用來 hold 住 DataSource 物件。
而這裡要做的是,以 dataSourceName 來對應以上面各方法取得的 DataSource 而已。

第 2 點的做法:

  • return connection object 前,用 ThreadLocal 留住向 DataSource 取得的 connection object,不用每次都向 DataSource 要,逕自 ThreadLocal 物件取得即可,直到 thread 結束。
  • thread,這裡多半指的是 HTTP thread。即然使用 ThreadLocal 物件來 "hold" 住及釋放資源,那麼必須找出 transaction 作業的起點,來進行取得與釋放動作;一般來說,實作的方法多半是 Servlet filter 或 Spring framework 的 OncePerRequestFilter,要注意的是該 filter "通常"是第一個(/最後一個)。
如果有必要的話,花一點力氣去偵測取得的 connection object 是否沒做最後的 close(),幫忙做一下 close(),以免 connection leak 發生;必竟用的不見得是百萬元級的 App Server 啊。

以上出現很多名詞與技巧,若不清楚、不懂、不知道,那表示這些東西一點兒都不廉價。
別再說:我也做過軟體、這不算什麼、[(一點也不難)找個會寫 Java 的人來做]...這種 "廉價" 的想法。 <除非他真的懂 Java>

ps. 這裡尚未探討到 XA / 2PC 的議題

2014年9月7日

另類、精彩的 ”one shop stop“ 活動

視野寬闊的機場、豪邁的烤肉、抗議的民團、坐在旁邊抓人的便衣(維安人員)、地景月免、精彩好聽的打擊樂表演、嗨翻的西班牙樂團+雲端魔幻搖滾秀。
帶孩子一次體驗(而且是第一次),What an amazing night!




2014年8月25日

遊江、照夕 、港平安


2014年8月23日

風輕、雲淡、山芙蓉

2014年8月15日

Arduino - LED 7 段數字顯示器

LED 7 段數字顯示器數數控制。

材料
LED 共陰極7段顯示器  x 1
Resistor 330Ω  x7

製作
< 參考「Arduino互動設 計入門2」- p7-5 ~ p7-7 >
port D0 (RX) 與上傳程式的USB共用之故, 我沒用上。



電路圖如下:


程式同上篇一樣使用 DDRx/PORTx 的技巧;如下:

const byte LED_SET = 0b11111110
const byte LEDs[10] = {
  0b11110110, // 0
  0b11000000, // 1
  0b01101110, // 2
  0b11101010, // 3
  0b11011000, // 4
  0b10111010, // 5
  0b10111110, // 6
  0b11100000, // 7
  0b11111110, // 8
  0b11111000  // 9
};

byte index = 0;

void setup() {
  DDRD = LED_SET;
}

void loop() {
  index %= 10;
  PORTD = LEDs[index++];
  delay(1000);
}

各接腳定訂如下:
pin D0 :(不用)
pin D1 :底部橫線
pin D2 :左下直線
pin D3 :左上直線
pin D4 :中間橫線
pin D5 :頂部橫線
pin D6 :右上直線
pin D7 :右下直線

右下角dp點沒做控制;程式技巧使用到 mod 運算,使得索引 index 只由 0 ~ 9 不會超過 10。
完成!

2014年8月12日

Arduino 第二課 - 跑馬燈

繼自學第一課: LED switch 用開關控制 LED (或者其他元件)後, 接著進入較複雜的(程式)控制: 跑馬燈。

材料
LED x3 (以上)
Resistor 330Ω x3 (搭配LED數量)

製作
< 參考「Arduino互動設 計入門2」- p4-19 ~ p4-21 >
紅色線: 高電位/電源輸出
黑色線: 低電位/電源回流

分兩種接法: 左圖為源流(source current) 與 右圖潛流(sink current)


電路圖如下:

程式如下:
const byte LEDs[] = {4 ,7, 10};
const byte LEDs_SIZE = sizeof(LEDs);

const byte INIT_VALUE = LOW; // LOW/HIGH when using source-/sink-current circuit
byte count = 0;

void setup() {
  for (byte i=0; i< LEDs_SIZE; i++) {
    pinMode(LEDs[i], OUTPUT);
  }
}

void loop() {
  for (byte i=0; i< LEDs_SIZE; i++) {
    digitalWrite(LEDs[i], INIT_VALUE);
  }
  
  count = count % 3;
  digitalWrite(LEDs[count], ! INIT_VALUE);
  count++;
  
  delay(200);
}

上述, 使用了廻圈(loop)與陣列(array)的寫法; 源流接法時, 將
const byte INIT_VALUE = LOW;

潛流接法時, 將
const byte INIT_VALUE = HIGH;

另外, 可使用 DDRx (Data Direction Register)來控制 pin 的輸出入, 並使用 shift 的程式技巧來達成:

<以DDRD/PortD為例>

// for sink-current circuit
const byte INIT_VALUE = 0b00001000;
const byte LED_SHIFT = 2;
byte count = 0;

void setup() {
  DDRD = 0b10101000; // OFF for pull-down circuit
}

void loop() {
  count = count % 3;

  if (count == 0) {
    PORTD = INIT_VALUE;
  } else {
    PORTD <<= LED_SHIFT;
  }

  count++;
  delay(200);
}

上述為潛流接法的寫作, 源流的寫法得花點時間想想。

ps.
上述二例寫法與「Arduino互動設 計入門2」- p4-28, p4-31有點不同

2014年8月10日

Arduino 第一課 - LED switch

去天瓏買書時, 老闆慫恿我買 Arduino 的書來看, [年紀這麼大了, 學還來得及嗎?] 我這樣子問, 他說:[玩玩嘛!]. 於是真得玩了起來.

會有這樣子的想法, 是多年前 Make: 正夯(現在已發行《Make》雜誌國際中文版), 印象所及是曾看一篇網頁說明了用紅外線指示筆+光碟機做出了一個雷射筆(槍), 不久後新聞報導了大陸的網購網站竟然開始賣這樣子危險的東西; 另外, 還有 GPS 尋寶遊戲的導航製作. 試想, 我自己是否依樣畫葫蘆呢?!

而 Arduino 對我所學非專科的人來說, 多半只是看看而已; 直到一年多前開始察覺已經出現很多網站刊出很多有趣的東西之後, 我才順著天瓏老闆的話, 試著玩玩, 看是否如同以前的中推會同事一般, 做出個藍芽搖控玩具呢?!

但自學的第一課是什麼呢? 買材料! 而買材料的第一步是找一間電子材料行隨意逛逛. 看了玲瑯滿目各式規格看不懂的電子料件, 足足花了三個小時才買到了一些要學習的基本材料; 當然, 這樣子的結果是有先做功課的; 不然, 第一時間, 大概會因為眼花潦亂, 嚇到後打退堂鼓的.

即然身為 software developer 是沒在怕的, 先自我催眠: 只不過是組裝電路後把程式寫入即可. 於是乎產出了第一個電路及程式: LED switch

材料
Switch x 1 (微動開關)
LED x1
Resistor 330Ω (for LED)
Resistor 10KΩ (for Switch)

製作
< 參考「Arduino互動設 計入門2」- p4-8, p4-12, p4-14 >
紅色線: 高電位/電源
黑色線: 低電位/接地
白色線: 輸出 or 輸入
要注意上拉(pull-up)電阻與下拉(pull-down)電阻的選用與接法不同:


圖左為下拉電阻接法, 右圖為上拉電阻接法; 電路圖如下:

程式如下:
const byte SWITCH = 10;
const byte LED = 16;

//boolean lastState;

void setup() {

//  lastState = LOW;
  
  pinMode(SWITCH, INPUT);
  pinMode(LED, OUTPUT);
  pinMode(LED_2, OUTPUT);
  
  digitalWrite(LED_2, LOW);
}

void loop() {
    boolean click = false;
    boolean currentState = digitalRead(SWITCH); // pull-down circuit, use NOT when using pull-up circuit

    if (currentState != lastState) {
      delay(20);
      currentState = digitalRead(SWITCH); // pull-down circuit, use NOT when using pull-up circuit
      
      if (currentState != lastState) {
        click = true;
        lastState = currentState;
      }
    }
    
    if (click) {
      digitalWrite(LED, currentState );
    }
}

程式以下拉電阻接法寫的, 若為上拉電阻接法, 只要將其中
    ... = digitalRead(SWITCH);

改為
    ... = digitalRead(! SWITCH);

即可.

done!

ps.
「Arduino互動設 計入門2」- p4-16 的程式寫法會嚇到初學者的

2014年4月19日

Upgrade subversion 第二彈

自從 upgrade 至 Mavericks 後, 大多不太敢將開發工具也一起 upgrade, 必竟編譯環境並非像這些軟件更新的這麼快。

以個人的工作平台為例:
Mavericks  +  GGTS v3.5.0(Eclipse v3.8.2)  +  Xcode v5.1  +  JDK 1.7.0_51

對於更新 JavaHL native lib 以整合 subversion eclipse plug-in v1.7.x,過去雖有經驗,但仍無法使用 Homebrew 順利安裝,發生的問題有二:
  1. 安裝 subversion v1.7.10 時, 無法取得 serf
  2. 安裝 subversion17 (v1.7.14) 時, 無法載入 zlib
不免要花上一些時間來解決。而這狀況有點像是綜合了前兩篇所提到的問題:
但,索性有過這樣子的經驗後,找到了解決的方法:
  1. 解開 subversion17 安裝時留下來的壓縮檔
  2. 手動進行編譯
  3. 產生 java.library.path 路徑內的 lib 連結
做法如下:
cd /tmp
cp /Library/Caches/Homebrew/subversion17-1.7.14.tar.bz2 .
open subversion17-1.7.14.tar.bz2
cd /tmp/subversion-1.7.14
確認 java --version 為 1.7.0
make clean
./configure --prefix=/usr/local/Cellar/subversion17/1.7.14 --with-apr=/usr/bin --with-zlib=/usr/local --with-sqlite=/usr/local/opt/sqlite --with-serf=/usr/local/opt/serf --disable-mod-activation --disable-nls --without-apache-libexecdir --without-berkeley-db --enable-javahl --without-jikes
修改 Makefile:
...
JAVAC_FLAGS = -target 1.7 -source 1.7
...
再進行安裝:
make install-javahl
make install
cd /Library/Java/Extensions
sudo ln -sf /usr/local/Cellar/subversion17/1.7.14/lib/libsvnjavahl-1.dylib
sudo ln -sf /usr/local/Cellar/subversion17/1.7.14/lib/libsvnjavahl-1.jnilib
打開 GGTS, 設定 [Team]/[SVN]/[SVN interface]:

that's it.

2014年4月7日

IPO

IT 領域中, input --> process --> output 是基本結構

社會層面上, "法(條)" 是 "法治化" 的結果;
就邏輯上而言, 法治化是 process , 法(條)是 output

那 input 是什麼呢?!

若從 IT 技術上來看, Garbage in 就會 garbage out

換句話說, 對於目前政經問題(服貿協議法治化/太陽花學運), 我們更該注意 input 到法治化過程中的內涵

2014年1月23日

BeanUtilsBean 的小小應用 2

在利用 BeanUtilsBean 的一點小技巧來解決部份 property 不做 copyProperties() 的需要之後。
如果處理 BigDecimal 的 property 賦值, 而其值為 null 時, 會出現...
Exception in thread "main" org.apache.commons.beanutils.ConversionException: No value specified
at org.apache.commons.beanutils.converters.BigDecimalConverter.convert(BigDecimalConverter.java:...)
...


解決問題前, 先從 BeanUtils 取得 BeanUtilsBean.getInstance() 來處理 copyProperties() 的 source 中看到:

    private static final ContextClassLoaderLocal beansByClassLoader = new ContextClassLoaderLocal() {

        protected Object initialValue() {
            return new BeanUtilsBean();
        }
    };

實際是使用了預設的 BeanUtilsBean() constructor:

    public BeanUtilsBean() {
        this(new ConvertUtilsBean(), new PropertyUtilsBean());
    }

    public BeanUtilsBean(ConvertUtilsBean convertUtilsBean, PropertyUtilsBean propertyUtilsBean) {
        this.log = LogFactory.getLog(BeanUtils.class);
        this.convertUtilsBean = convertUtilsBean;
        this.propertyUtilsBean = propertyUtilsBean;
    }
明顯的看到它使用了預設的 ConvertUtilsBean() constructor, 並在 deregister() method 中建構各類型的 converter 以便爾後的 properties 賦值處理:

    public ConvertUtilsBean() {
        this.converters.setFast(false);
        deregister();
        this.converters.setFast(true);
    }
    ...
    public void deregister() {
        ...
        register(BigDecimal.class, new BigDecimalConverter());
        ...
    }
    ...
但, 當中 BigDecimalConverter() constructor 是不預設賦值的:
    public BigDecimalConverter() {
        this.defaultValue = null;
        this.useDefault = false;
    }
另一 constructor 則是:
    public BigDecimalConverter(Object defaultValue) {
        this.defaultValue = defaultValue;
        this.useDefault = true;
    }
也就是說, 可以使用另一組 BigDecimalConverter() constructor 來避免上述的 exception 發生:
    BeanUtilsBean defaultNullValueBeanUtils = new BeanUtilsBean(new ConvertUtilsBean() {

        @Override
        public void deregister() {
            super.deregister();
            // 這裡還是預設了 null 為其值
            register(new org.apache.commons.beanutils.converters.BigDecimalConverter(null), BigDecimal.class);
            ...
        }
    }, new PropertyUtilsBean());
    ...
    defaultNullValueBeanUtils.copyProperties(dest, orig);
    ...
done!

2014年1月17日

BeanUtilsBean 的小小應用

Apache Commons BeanUtils 發展有十多年, 累積了很多很棒的功能; 它在當時推動 JavaBean 風潮的時空背景下, 算是主要的推手之一。主要的精神在於讓 class 像 data type 一般, 借由 naming convention 慣例, 使用 attributes(或稱 properties) 的 setter/getter methods 來存取 information, 保有了物件導向理論中封裝特性的彈性與好處。雖說另有一派(人)不同意此一看法,但仍有其存在的必要。

最常見的是, 兩個物件有幾乎相仿的 setter/getter methods 情形下進行 copy;在很早的 COBOL、FoxBase ... 年代, 這些 language 在宣告上或語法上已有支援, 而 4GL 年代也有相同、類似的支援方式:
        LET recordA. * = recordB.*
唯獨當年剛從 internet 中展露頭角的 Java 還沒有"方便"的方式, 尤其是當兩個物件有幾十、甚至上百個 attributes 時, programmer 一定會恨死如:
    ...
    beanA.setAttr1(beanB.getAttr1());
    beanA.setAttr2(beanB.getAttr2());
    beanA.setAttr3(beanB.getAttr3());
    ...
    beanA.setAttr99(beanB.getAttr99());
    ...
這種佔篇幅、又容易打(或複製)錯字而不易察覺的笨挫寫法。

此時, programmer 一定會很感激 BeanUtils 的存在; 它的其中一個最基本的功能:
BeanUtils.copyProperties(destBean, origBean);
它會取得 origBean 中所有 public getXXX() 之類的 methods 回傳值, 找到 destBean 中相對應的 setXXX() methods, 接著叫(調)用並賦值給它。(當然,  origBean 的 getter 回傳型別與對應 destBean 的 setter 參數型別要相容)

當有十幾、廿個 attributes 要賦值, 但僅少數幾個則否; 那要怎樣進行呢?

可從 BeanUtils 的 source code 看來, 它主要是由 BeanUtilsBean class 的 method 進行 copy 功能:
public void copyProperties(Object dest, Object orig)
        throws IllegalAccessException, InvocationTargetException {
        // ...
        copyProperty(dest, name, value);
        // ...
}
所以, 只要製訂這 class 新的 subclass, 並控制 copyProperty() 的處理, 如下所示:
@Override
public void copyProperty(Object bean, String name, Object value)
        throws IllegalAccessException, InvocationTargetException {

        if (!excludeNames.contains(name)) {
                super.copyProperty(bean, name, value);
        }
}
而在其中, 只要先紀錄 excludeNames 即可。

但, 如果沒有重用(re-use)的情形, 定義新的 class 實在會顯得 Java 語言的繁瑣; 索性, 可利用 anonymous inner class 寫法來做就好:
        // ...
        new BeanUtilsBean() {
                private List〈string〉 excludeNames = new ArrayList〈string〉(0);

                BeanUtilsBean setExcludeNames(String[] excludeNames) {
                        if (excludeNames != null && excludeNames.length > 0) {
                                this.excludeNames = Arrays.asList(excludeNames);
                        }
                        return this;
                }

                @Override
                public void copyProperty(Object bean, String name, Object value)
                        throws IllegalAccessException, InvocationTargetException {
                        if (!excludeNames.contains(name)) {
                                super.copyProperty(bean, name, value);
                        }
                }
        }.setExcludeNames(new String[] {"someAttr"})
        .copyProperties(destBean, origBean);
        // ...
如此直接叫(調)用即可。
而其中 setExcludeNames() 回傳 this 的方法, 可以免除自定 class 及其新的 constructor 來紀錄 excludeNames 的麻煩。

2014年1月12日

Grails Scaffolding

Grails 的 Scaffolding 功能自開天闢地以來就有了, 也是介紹入門時的介紹重點之一。

上篇 GORM custom id 另解 就是由 scaffolding plugin 產生出來後所進行的調整; 如果多數 controller 變更情形如同上篇, 那多半會考慮變更 template files, 在產生(generate-all)時就大致已調整完成, 而稍做修改即可。

變更修改前, 須先產生預設的 template files 在專案目錄 src/templates(/scaffolding) 下:
grails> install-templates
接著再以 editor 直修改它, 存檔後會在下次產生另一組 controller/views 時生效。

其實這小撇步早在多年前另一篇 GORM 物件於 update method 後立即顯示資料 也用過。

當時 Grails 的版本及 IDE 工具比較沒有整合的很好, 多半是 console 下打指令來進行開發 (而 IDE 則用來 debug)。目前的版本 v2.3 早已提供 interactive mode, 可利 TAB 鍵進行快速提示與執行; 另外 GGTS IDE 也提供了很好的整合, 以 generate-all 指令為例, 有兩種方式:
(1) 原先提供的 context menu :
  • 在 project 上按右鍵, 選擇 [Grails Tools]
  • 選擇適合的功能 (多半以 [Open Grails Command Prompt])
  • 當然, 最快的方式是按下快捷鍵 [Cmd]+[Opt]+[Shift]+G

(2) [New] function:
  • 直接在 domain class 上按右鍵, 選 [New]
  • 選擇 [Generate Controller and Views], 並輸入 domain class 即可
以上

2013年12月31日

GORM custom id 另解

在前篇 使用 Grails 開發應用系統之感 提到 "框"不"框"住的問題, 其中提到不同版本 Grails 的 domain class 用到 transients 的技巧來避免使用 (Hibernate) object id 做為資料庫的 PK。該算是自己一時失查 (此查非彼"察") 呢? 還是才疏學淺?! 就在無意間瀏覧 Blog 時, 看到一篇 GORM custom id mapping (作者: Ilya Sterin) 的技巧才算找到我的答案。

因此, 依樣畫葫蘆做了一個 domain class:
class Employee {
    String id
    String code // 員工編號
    String name // 員工姓名
 
    static constraints = {
        id maxSize: 3
        code maxSize: 3, nullable: false, blank: false, unique: true
        name maxSize: 20
    }

    static mapping = {
        id generator: 'assigned', name: 'code'
    }

    String getId() {
        this.id ?: getCode()
    }
 
    void setCode(code) {
        this.code = code?.toUpperCase() // 大寫
    }
}

在典型的 CRUD controller 中如果有提供  code 的欄位變更, 因設計了 code 來代替 id 欄位, 而 id 負責查詢而 code 負責輸入的情形下, 需要特別處理 save() 與 update() 兩個 methods :
  1. save() 中...
    • unique validation 需要自行處理, 而非讓它發生 org.springframework.dao.DataIntegrityViolationException
  2. update() 中...
    • 須先行以 params.code 查詢是否輸了已經存在的資料了
    • 變更 code 欄位值之前, 先刪掉"舊"的資料 (它不像下 SQL 那般直接 update key value 就好, 參考資訊: Hibernate, alter identifier/primary key )
如下所示:
    @Transactional
    def save(Employee employeeInstance) {
        // ... (略) ...

        try {
            employeeInstance.save flush:true

        } catch (DataIntegrityViolationException dive) {
            employeeInstance.errors.rejectValue('code', 'default.not.unique.message' ,['code', message(code: 'employee.label'), employeeInstance.code] as Object[], message(code: 'default.not.unique.message'))
            employeeInstance.code = params?.id // 還原
            respond employeeInstance.errors, view:'create'
            return
        }

        request.withFormat {
        // ... (略) ...
    }
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    def beforeUpdate(employeeInstance) {
        if (Employee.findByCode(params?.code?.toUpperCase())) {
            employeeInstance.errors.rejectValue('code', 'default.not.unique.message' ,['code', message(code: 'employee.label'), employeeInstance.code] as Object[], message(code: 'default.not.unique.message'))
            employeeInstance.code = params?.id // 還原
            throw new ValidationException(null, employeeInstance.errors)
        }
    }
    @Transactional
    def update(Employee employeeInstance) {
        // ... (略) ...

        if (params?.id != params?.code?.toUpperCase()) {
            try {
                 this.&beforeUpdate(employeeInstance)
            } catch(e) {
                employeeInstance.discard()
            } finally {
                if (employeeInstance.hasErrors()) {
                    respond employeeInstance.errors, view:'edit'
                    return
                }
            }
            // 先刪後增
            employeeInstance.delete()
            employeeInstance.discard()
            employeeInstance.id = null
        }
        employeeInstance.save flush:true // 功能變為 insert 而不是 update 了

        request.withFormat {
        // ... (略) ...
    }
要注意的是, 這是 v2.3.x 的作法: save() 與 update() methods 都冠上了 @Transactional, 而早期 transaction 處理則是交由 service 來做。所以, 為避免先查詢是否已存在的資料時, 發生
org.hibernate.HibernateException: identifier of an instance of ... was altered from ... to ...
因此將它獨立為另一 method: beforeUpdate(), 並宣告為 transaction "NOT_SUPPORTED"。

完成。

ps. 花了不少時間!

2013年12月30日

Java實戰兩三事

多年前的 presentation:

使用 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

2013年8月19日

Upgrade subversion 至 v1.7.11

繼上篇解決 OSX 10.6 中安裝 SVN 與 Git 的 zlib 問題;為 upgrade subversion 至 v1.7.x 好讓 GGTSSubclipse v1.8 開發工具所使用,進行下列指令的處置:
cd /usr/local/Library/Formula/
brew update
brew versions subversion
git checkout 0060dc3 /usr/local/Library/Formula/subversion.rb
卻發生無法安裝情形:
處理的方法是改安裝 v1.7.11:
brew tap homebrew/versions
brew install --universal --java subversion17
這樣子只要再檢查 /Library/Java/Extensions 下有沒有相關的 symbolic link:
隨即開啟 GGTS 設定: [Preferences/Team/SVN] 下的 [SVN interface] 至 "JavaHL (JNI) 1.7.11 (r1503888)" 即可。