2007年7月6日 星期五

不用切換到中文輸入法就能打中文

今天發現一個好東西,就是所謂的網頁輸入法。

如果你是使用「非」注音輸入法的網兄,常常出門在外時,會遇到所用的電腦沒有安裝你慣用輸入法的情形,這時黃金手指幾乎會等於廢功,就算加藤鷹上身也無濟於事。

這症狀尤以使用「無蝦米」的同好為最,由於無蝦米是版權軟體,沒有內建在Windows中,所以出門遇到沒有無蝦米輸入法的電腦,就只能硬著頭皮打一分鐘只有一個字的注音…

不過近年來拜AJAX技術的庇佑,有了網頁輸入法這玩意,你只需要打開瀏覽器,連線到網頁輸入法的網址,就可以快快樂樂的使用你慣用的輸入法打中文,即使你正在使用一些中文支援度不是很好的Unix也一樣。

不過稍有不方便的是,打完的字要手動複製,然後再貼到你所要用的地方喔!

這個Wiki中有各種中文網上輸入法的連結
ChineseInformationProcessing

這個連結是我愛用的無蝦米網上輸入法
http://liu.twbbs.org/hliu/


2007年7月5日 星期四

不用切換到中文輸入法就能打中文

今天發現一個好東西,就是所謂的網頁輸入法。

如果你是使用「非」注音輸入法的網兄,常常出門在外時,會遇到所用的電腦沒有安裝你慣用輸入法的情形,這時黃金手指幾乎會等於廢功,就算加藤鷹上身也無濟於事。

這症狀尤以使用「無蝦米」的同好為最,由於無蝦米是版權軟體,沒有內建在Windows中,所以出門遇到沒有無蝦米輸入法的電腦,就只能硬著頭皮打一分鐘只有一個字的注音…

不過近年來拜AJAX技術的庇佑,有了網頁輸入法這玩意,你只需要打開瀏覽器,連線到網頁輸入法的網址,就可以快快樂樂的使用你慣用的輸入法打中文,即使你正在使用一些中文支援度不是很好的Unix也一樣。

不過稍有不方便的是,打完的字要手動複製,然後再貼到你所要用的地方喔!

這個Wiki中有各種中文網上輸入法的連結
ChineseInformationProcessing <--感謝網友提醒這個連結已經失效

這個連結是我愛用的無蝦米網上輸入法
http://liu.twbbs.org/hliu/

2007年7月2日 星期一

使用Adobe Contribute CS3編輯Blog

這個世界上的部落格服務提供者已經多得我數不清了,不管那一家,大多提供了不錯的文章編輯器,Blogger算是當中的佼佼者,但不管怎麼好用,比起傳統的網頁編輯器(如FrontPage, Dreamweaver, Nemo, Nvu...),就是少了那麼點流暢和方便的感覺。

今天我要來介紹個好工具,那就是Adobe Contribute CS3,它有兩大功能:管理網站和部落格,是的,你可以利用它來寫新文章,甚至發行、管理你部落格的文章。

輕鬆的點選建立新文章,然後在十分方便的所視即所得的環境下編輯,讓部落格文章的寫作更添加了暢快的感覺。接著,便來看看如何使用Contribute CS3來發表部落格文章吧:

1.在歡迎畫面上點選Blog Connection,然後選擇 「Blogs」,hosts則選「Blogger」,輸入你的帳號密碼。

2. 在Toolbar上點選「New ...」 ,建立一個「Blank Blog Entry」。

3.在Contribute提供的編輯器,以所視即所得的方式輕鬆的編輯你的文章。當你編輯完成時,只需要點選「Publish」,便可發行到你的部落格了。

以上,是不是很簡單呢?

你可以到www.adobe.com去抓Contribute的試用版喔

2007年6月4日 星期一

JRuby 1.0.0RC3 announced!!

最近工作忙,很久都沒時間玩玩Ruby了,剛剛突然發現JRuby 1.0.0RC3出了…

=================================
People are

encouraged to try out this release to help us find any remaining showstopper issues.  We

have spent a lot of time over the last month squashing compatibility bugs and we have

confidence that applications 'will just work' (tm)*.

==================================
看來還是有些潛在的相容性問題,不過我倒是蠻期待有一天能把可愛的Rails Web App Deploy到Tomcat或Weblogic上的,也許這天不遠了吧~

2007年4月24日 星期二

Synchronized造成效能低落嗎? 試試樂觀同步化吧!!

前陣子調效一個Web Application的效能,在貢獻了許多咖啡因到腦袋後,我找到了原因-- Synchronized

是的,在某支Servlet中的某函式使用了Synchronized關鍵字,造成系統在此排隊執行,因而效能低落,於是立刻招開小組會議,但幾經測試及討論後,卻發現此區塊一定得同步化,否則發生Race Condition時,會造成資料錯誤…

我效能調效的工作遇到了瓶頸…Orz

...
...


幾經思量,我決定將此函式改造成使用樂觀同步化技術!!

什麼是樂觀同步化?
使用Java寫作的程式,通常在可能會發生同時存取共用資料的情形時,使用Synchronized關鍵字,以確保同一時間只會有一個Thread執行這段程式,但這種作法就是所謂的悲觀同步化,問題解決了,但代價可能是會有多個Thread在排隊等候,造成效能低落。

樂觀同步化則樂觀的認為同時存取的情形很少發生,萬一發生的話,再補救就好了。

說明看起來很簡單,但使用較早期版本的JDK要實作可不簡單,幸運的是,JDK從1.5之後引進了Atomic類別庫,讓你可以方便的實作樂觀同步化。

Race Condition
在開始之前,先來看看什麼是「同時存取共用資料」,這個問題學名叫「Race Condition」,就是指共用的資料,幾乎同時間被許多不同的Thread存取,造成資料不一致的問題。

讓我們來看個例子:
有個定貨系統,使用了三個執行緒在處理同一個產品的訂單,訂單在產生之前必須先更新產品的資料。

產品物件的定義如下:




public class MyProduct {
private String productId;
private double price;
private String description;

public MyProduct(String productId, double price, String description) {
this.productId = productId;
this.price = price;
this.description = description;
}

public String toString() {
return productId +
"(NT$ " + price +
", " + description + ")";
}

public boolean equals(Object anObject) {
MyProduct newProduct = (MyProduct) anObject;
return productId.equals(newProduct.getProductId()) &&
price == newProduct.getPrice() &&
description.equals(newProduct.getDescription());
}

public void createOrderTO() {
// 用一段sleep來模擬產生訂單需要執行的時間
try {
long rnd = (long)(java.lang.Math.random() * 100);
Thread.sleep(rnd);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}

public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
}



處理訂單的執行緒程式如下:

public class ProductProcessor implements Runnable {

public ProductProcessor(int id) {
processorId = id;
}

private int processorId;

public int getProcessorId() {
return processorId;
}

private MyProduct product;
public void setProduct(MyProduct product) {
this.product = product;
}

/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
// 每個thread對此產品產生5個訂單
for (int i = 0; i < 5; i++) {
// 更新產品資料
product.setPrice(processorId * 10);
product.setDescription(
"modified by processor: " + processorId
);

// 跟據product產生訂單物件(Order Transfer Object)
product.createOrderTO();

// 列印訂單
System.out.println("Processor " + processorId +
" Create Order for: " + product.toString());
}

}

public static void main(String args[]) {
// 產生一個product
MyProduct prod = new MyProduct("my_product_01", 9999999, "this is my product!");

ProductProcessor p1 = new ProductProcessor(1);
ProductProcessor p2 = new ProductProcessor(2);
ProductProcessor p3 = new ProductProcessor(3);

// 讓三個thread都一起存取同一個Product
p1.setProduct(prod);
p2.setProduct(prod);
p3.setProduct(prod);

Thread t1 = new Thread(p1);
Thread t2 = new Thread(p2);
Thread t3 = new Thread(p3);

t1.start();
t2.start();
t3.start();
}


}


執行結果如下:


Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
Processor 2 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
Processor 3 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 1 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
Processor 3 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
Processor 1 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
Processor 2 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
Processor 1 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 3 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
Processor 1 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
Processor 2 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
Processor 1 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 3 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)



從結果可以發現許多衝突,例如第2行Processor 2產生的訂單,其說明卻註記說是Processor 3更新的。
這種衝突的情現就叫Race Condition。

悲觀同步化
要如何解決Race condition呢? 最簡單的方法就是使用Synchronized區塊了:
在有可能發生Race Condition的地方,使用Synchronize {}把這些程式碼都包起來,就可以解決問題,像下面這樣:

// use synchronized block to solve race condition
synchronized (product) {
// 更新產品資料
product.setPrice(processorId * 10);
product.setDescription(
"modified by processor: " + processorId
);

// 跟據product產生訂單物件(Order Transfer Object)
product.createOrderTO();

// 列印訂單
System.out.println("Processor " + processorId +
" Create Order for: " + product.toString());
}


再執行一次,我們可以得到完美的結果:


Processor 1 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
Processor 1 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
...
...



這個技巧就是悲觀同步化,Java保證synchronized區塊同時只會有一個執行緒進入存取product物件,因此Race Condition不會再發生,但有時這種解法會造成效能低落。


樂觀同步化
在大多數情況下,其實Race Condition是很少發生的;當衝突很少發生,卻使用悲觀管控,就好像是「交通部長要求交通警察限制同一時間只能有一台車上高速公路一樣」,可以想見交流道口會塞的又臭又長。

所以樂觀同步化就派上用場了,基本原理很簡單:「如果發生衝突,就重做」!

JDK 1.5之後提供了AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference…等類別讓你可以不使用lock卻能解決Race Condition。

這些類別的特色是裡面的方法都是Atomic的,也就是所有的方法都可以視作單一的一種操作,不會受其它Thread的影響。例如:

AtomicInteger ival = new AtomicInteger(1);
ival.set(5); // 這個操作是Atomic的

// 如果ival==5的話就把ival的值改成10 (這個操作也是Atomic的)
ival.compareAndSet(5, 10);


(注:你可能覺得ival.set(5)和宣告成一般的int然後執行ival=5;有什麼不同,但其實用一般的int,其操作還是沒有Atomic,這遷涉到byte code的內容和register的載入方式,以後有機會再聊聊。)


使用AtomicReference
思考一下前述發生問題的程式,如果以下三個步驟:

// 更新產品資料
...
// 跟據product產生訂單物件(Order Transfer Object)
...
// 列印訂單
...


可以結合成一個Atomic的動作,那不就不需要再使用synchronized關鍵字了嗎?


問題是,要怎麼做呢? 我們可以使用AtomicReference將MyProduct包裝一下,像這樣:


import java.util.concurrent.atomic.AtomicReference;

public class MyAtomicProduct{
private AtomicReference product;

public MyAtomicProduct(String productId, double price, String description) {
product = new AtomicReference
( new MyProduct(productId, price, description) );
}

public void updateProductAndMakeOrder(int processorId,
double price,
String description) {
MyProduct origVal, newVal;
String origProductId;

// 這是一個無窮迴圈,會一直重做到沒有發生Race Condition為止
while (true) {
origVal = product.get();
origProductId = origVal.getProductId();

// 記住使用AtomicReference在設定值的時候一定要指派一個新的
newVal = new MyProduct(origProductId, price, description);

// 跟據product產生訂單物件(Order Transfer Object)
newVal.createOrderTO();

// compareAndSet會確保product的值和原來(origVal)一樣時
// ,才會將product的值更新,否則就不做事且傳回false
// (如果不一樣表示過程中有其他的thread進來,也就是Race Condition
// 當發生Race Condition時,迴圈就會重做)
if (product.compareAndSet(origVal, newVal)) {
System.out.println("Processor " + processorId +
" Create Order for: " + newVal.toString());
break; // 產生的訂單是正確的,所以結束迴圈
} else {
// Debug用,在此特別將發生Race Condition而重做的情形印出來
System.out.println("** [Redo] because of Race Condition! **");
}

}
}
}



然後把void main中產生product的那行改成如下

MyAtomicProduct prod = new MyAtomicProduct("my_product_01", 9999999, "this is my product!");



執行結果如下:


Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
** [Redo] because of Race Condition! **
** [Redo] because of Race Condition! **
Processor 1 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
Processor 1 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
** [Redo] because of Race Condition! **
** [Redo] because of Race Condition! **
Processor 1 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
** [Redo] because of Race Condition! **
** [Redo] because of Race Condition! **
Processor 1 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
** [Redo] because of Race Condition! **
Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
** [Redo] because of Race Condition! **
** [Redo] because of Race Condition! **
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
** [Redo] because of Race Condition! **
** [Redo] because of Race Condition! **
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
** [Redo] because of Race Condition! **
** [Redo] because of Race Condition! **
Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
** [Redo] because of Race Condition! **
** [Redo] because of Race Condition! **
Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
** [Redo] because of Race Condition! **
** [Redo] because of Race Condition! **
Processor 1 Create Order for: my_product_01(NT$ 10.0, modified by processor: 1)
** [Redo] because of Race Condition! **
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)



可以發現當Race Condition發生時,迴圈會重做以確保資料正確,在衝突不常發生的情形下,使用樂觀同步化是有可能解決效能低落的狀況的,不過如果Race Condition常常發生,則值不值得就見人見智了,更何況實作樂觀同步化會使你的系統更複雜了。

2007年4月21日 星期六

使用MetaProgramming 動態定義類別

使用MetaProgramming寫程式的確和以往有很多不一樣的地方,例如,我可以在程式中讀取資料庫中的表格名稱和欄位名稱,然後根據表格的Schema動態產生一個類別定義,並且利用此類別產生Instance,像下面這樣:(注:為求簡化範例,資料庫存取的部份用模擬的)


class MetaProgrammingTest

# class method for creating a new class
def self.create_class(class_name, attr_names)
# use class name to create a class
klass = Object.const_set(class_name,Class.new)

klass.class_eval do
attr_accessor *attr_names

define_method(:initialize) do |*values|
attr_names.each_with_index do |name,i|
instance_variable_set("@"+name, values[i])
end
end

define_method(:to_s) do
str = "[#{self.class}:"
attr_names.each {|name| str << " #{name}=#{self.send(name)}" }
str + "]"
end

end

# return the class
klass
end

end


class TestDBSchemaReader
def self.getTableNameFromDB
# 下列程式可以改成從資料庫取得Table Name
return "Person"
end

def self.getAttributeNamesFromDB(table_name)
# 下列程式可以改成從資料庫取得Table的所有欄位名稱
return ["name", "age"]
end
end

table_name = TestDBSchemaReader.getTableNameFromDB

attr_names = TestDBSchemaReader.getAttributeNamesFromDB(table_name)

# 動態產生一個新的類別
myCls = MetaProgrammingTest.create_class(table_name, attr_names)

# 動態產生一個新的類別的Instance
vo = myCls.new("richard", 15)

puts "class name=" + vo.class.name
puts "instance: vo.name = " + vo.name + ", vo.age=" + vo.age.to_s



執行結果像這樣

>ruby meta_programming_test.rb
class name=Person
instance: vo.name = richard, vo.age=15
>Exit code: 0


類別"Person"被動態的定義了,而且還使用此類別建立了Instance,蠻容易的,不是嗎?

更厲害的是,動態定義的類別還可以被繼承,像下列這段,我在程式後方又加上了一個GoodPerson類別,繼承自動態定義出來的Person類別:

class MetaProgrammingTest

# class method for creating a new class
def self.create_class(class_name, attr_names)




#更神奇的是,動態定義的類別還可以被繼承
class GoodPerson < Person
def hello
print "我是#{@name},我今年#{@age},我被發好人卡了!\n"
end
end

gp = GoodPerson.new("steve", 31)


執行結果

>ruby meta_programming_test.rb
class name=Person
instance: vo.name = richard, vo.age=15
我是steve,我今年31,我被發好人卡了!
>Exit code: 0


更進一步的,我還可以在這個GoodPerson類別中,動態加入新method:

...
...

procBlock = proc { |x| puts "the " + x + "is a nerd!" }

GoodPerson.class_eval do
define_method(:showNerd, procBlock)
end

gp.showNerd "jonny"



執行結果

>ruby meta_programming_test.rb
class name=Person
instance: vo.name = richard, vo.age=15
我是steve,我今年31,我被發好人卡了!
the jonnyis a nerd!
>Exit code: 0

2007年4月19日 星期四

結合Meta Programming的OR Mapping - Active Record

像Ruby這種動態語言有個一般靜態語言較難達成的功能--Meta Programming。

它並不是什麼新技術,早在70年代的SmallTalk就有內建這玩意了,不過它的好處倒是在Ruby on Rails出現後,才紅了起來。

什麼是Meta Programming?
簡單說就是類別的定義可在執行期自由自在的修改…
可以在執行期對動態現存的類別新增、修改成員函式,也可以新增、修改成員變數...
舉例來說:
Date是一個Ruby提供的類別,而你需要在程式中判斷所指定的日期是不是假日,在一般靜態語言中你可能會新增一個全域函式,或是在你自己的Helper類別中加入一個函式,如下:



boolean isHoliday(Date theDay);



然後使用時是像這樣:

Date someDay = ...;
...
...
if (isHoliday(someDay)) {
....
}
...


嗯…較不直覺,不是嗎?
看看下列程式,如果可以這樣用呢?

if (someDay.isHoliday?()) {
...
}


是不是方便多了!!

如果你還不太了解的話,可以參考一下我之前寫的這篇Meta Programming簡介


當然Meta Programming的作用不止於此,它可以做出許多令人驚艷的應用,在Ruby on Rails中的Active Record就是使用此技術的殺手級應用之一。

Active Record是結合Meta Programming的OR Mapping框架,就像Java中的Hibernate一樣,但比它更好,在如此肯定的說他好之前,我們來回想一下Hibernate:

在資料庫中有一個Table: User,內容是使用者的資料,Schema如下:

create table products (
id int not null,
name varchar(100) not null,
age number,
primary key (id)
);


我希望使用物件的方式存取使用者的資料,使用Java+Hibernate時,我有以下幾個步驟:
1. 建立User.hbm.xml,做為OR Mapping的定義:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="richard.User" table="userinfo">

<id name="id" column="id" type="java.lang.Integer">
<generator class="assigned"/>
</id>

<property name="name" column="name" type="java.lang.String"/>

<property name="age" column="age" type="java.lang.Integer"/>

</class>

</hibernate-mapping>


2. 為物件中的欄位建立相對應的Getter和Setter:

package richard;

public class User {
private Integer id;
private String name;
private Integer age;

// 必須要有一個預設的建構方法
// 以使得Hibernate可以使用Constructor.newInstance()建立物件
public User() {
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}



3. 在Client端程式中如下列方法使用:

// Configuration 負責管理 Hibernate 配置訊息
Configuration config = new Configuration().configure();
// 根據 config 建立 SessionFactory
// SessionFactory 將用於建立 Session
SessionFactory sessionFactory = config.buildSessionFactory();

// 新增持久化的物件
User user = new User();
user.setId(new Integer(4));
user.setName("caterpillar");
user.setAge(new Integer(30));

// 開啟Session,相當於開啟JDBC的Connection
Session session = sessionFactory.openSession();
// Transaction表示一組會話操作
Transaction tx= session.beginTransaction();
// 將物件映射至資料庫表格中儲存
session.save(user);

// delete
User user2 = new User();
session.load(user2, 2);
session.delete(user2);

tx.commit();

session.close();
sessionFactory.close();




嗯!Hibernate還是不失為在Java上一個相當優良的OR Mapping工具,使用起來是很簡單易懂的,但還是有一些不方便的地方:

1. 必須定義XML檔,當Table很多,欄位也愈來愈多時會很難維護。
2. 必須為物件依照要操作的欄位定義Getter和Setter方法,同樣的,資料愈多愈難管。
3. 當物件之間有關聯時,會更難維護。
4. 每次設計有變更時,一連串要更新的地方都讓我想要離職。

這些都是目前OR Mapping難解的部分,很幸運的,如果你使用Ruby,現在就有個Active Record解決了這些問題:

Active Record是怎麼做的?

1. 你需要一個User類別,長這樣:

class User < ActiveRecord::Base
end


2. 使用方法如下:

# 可以用Active Record 新增一筆資料
user1 = User.new
user1.name="Richard"
user1.age=18
user1.save

# 也可以用Active Record來找一筆資料
user2 = User.find 1
print user2.name


我並沒有在User類別中定義屬性name, age,但就是直接可以用,我也沒有在User類別中定義類別方法(static method),但User.find()這個方法是work的。

很神奇嗎?
Active Record透過Meta Programming幫你把這些都加上去了,ORMapping變輕鬆了!!

更好用的AES加密,4096 bit 安全性

最近在翻箱倒櫃整理以前寫過的一些程式,突然找到以前用Java寫的AES對稱式加密實作,而且我還把它寫成可以支援到4096 bit的加密強度,記得之前自己寫AES加密實作時,Java還沒有內建AES實作,所以這個Library用起來還是挺Happy的。(好吧…我承認我在自爽....Orz)

後來Java在1.4的時候已經提供了AES的實作,不過因為美國規定的加密技術輸出規則中所述,有些國家是不允許輸出高強度的加密技術的,所以Java預設只支援128 bit的加密強度,如果要用到192 bit或是256 bit時,還得去Sun原廠網站下載「JCE Unlimited Strength Jurisdiction Policy Files」。

不過沒有內含在JDK中用起來就是不方便,增加了Deploy的困擾,所以如果你有需要的話,可以到下列網址去下載Policy File:
http://java.sun.com/products/jce/index-14.html#UnlimitedDownload
注:到了JDK6預設仍然只有128bit的AES喔

什麼是AES?
寫到這突然發現我忘了介紹一下AES了,Advanced Encryption Standard (AES)-二十一世紀的一種加密演算法,美國國家技術標準局(NIST)在1998年所招攬的DES演算法繼承人,NIST希望在二十世紀結束之前,能夠預備好一個新的加密標準,要求加密區塊的明文長度為128位元,可接受的密鑰長度為128位元、192位元和256位元(含以上)。判定的標準為安全性、效率與適應性三方面。

於是在1998年8月20日第一次AES候選者的研討會中,出現了十五個候選的演算法。
列示如下:
CAST-256, CRYPTON, DEAL, DFC, E2, FROG, Hasty Pudding Cipher(HPC) , LOKI97, MARS, Magenta, RC6, RIJNDAEL, SAFER+, SERPENT, TWOFISH。

1999/3/22-23 NIST在義大利羅馬的奎爾諾飯店召開第二次AES候選者的研討會,NIST發出問卷調查,由參加者選出五種較有可能的演算法。

最後留下來參加決賽的有: MARS、RC6、Rijndael、Serpent、Twofish。

1999/4/15 Rijndael最後勝出,就是大家目前所使用的AES演算法,Rijndael是演算法作者的名字,盡管它很難唸…盡管大家都只知道AES而沒聽過Rijndael,我們還是別忘了他是作者。

Rijndael的理論基礎是將每個byte看成一個多項式:
例如:
有一個Byte的資料是01001010
則可將此Byte寫成多項式 0*X^7 + 1*X^6 + 0*X^5 + 0*X^4 + 1*X^3 + 0*X^0 + 1*X^1 + 0*X^0

然後利用迦羅瓦域定理經過一段運算而得,我並不打算在此說明理論細節,如果你有興趣的話,可以參考FIPS PUB 197,這篇Paper是AES演算法的詳細內容(在看之前惡補一下離散數學裡的迦羅瓦域定理有助於了解原理)

如何使用
我的AES Class很簡單使用,你可以參考以下範例:



// 指定加密強度,最高可到4096 bit
AESAlgorithm alg = new AESAlgorithm(AESAlgorithm.KEY_SIZE_4096);

// 產生Key
byte[] bytesKey = alg.createKey();
int[] wordsKeyExpansion = alg.createKeyExpansion(bytesKey);

String strMessage = "This is just an AES範例!!";

byte[] bytesMessage = strMessage.getBytes(); // 將字串轉成Byte Array
byte[] bytesEncrypted = alg.cipher(bytesMessage, wordsKeyExpansion); // 加密
byte[] bytesDecrypted = alg.invCipher(bytesEncrypted, wordsKeyExpansion); // 解密

System.out.println("decrypted= " + new String(bytesDecrypted) );


那裡可以下載?
最後,我的AES Library原始碼在這,供您參考!

2007年4月14日 星期六

純種Java與JavaScript的結合

JavaScript是個廣泛應用的描述性語言,除了製作動態網頁外,常常可以看到各種工具使用各種Script來讓使用者有更彈性的設定,例如:


  • 報表工具(如JasperReport)可以使用用Script自定欄位的顯示格式。

  • 各種Workflow、BPM的工具也可以使用Script訂定各站過站時的邏輯。

  • 在ERP中可以使用Script來設定成本計算公式。

  • 在MES中可以使用Script來設定良率計算公式。




將Script整合進應用程式所能達到的彈性空間的確十分強大,如果能在我們開發的系統也提供Scripting的能力,除了強化擴充性之外,整個系統的質感也是提升不少…爽度up!up!

但以往要達成這目標,有個難處,就是解釋並執行Script用的Interpreting Engine那裡來,自己寫顯然是太累了…。

Java 6這回帶來了好消息:Scripting API,在Java系統中結合Scripting再也不難了,你只需要import javax.script.*;

不可免俗的,還是讓我們來Hello一下吧:


import javax.script.*;

class HelloScript {
public static void main(String args[])
{
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
try {
jsEngine.eval("print('Hello, world!')");
} catch (ScriptException ex) {
ex.printStackTrace();
}
}
}


是的…我在Java程式中使用JavaScript印出了Hello, world!
只需要幾行而已,取得ScriptEngine,然後將要執行的Script傳入eval()中就好了。

不過,通常要讓Script在系統中派上用場,你需要更完全的整合:在Script中要能使用Java物件、在Script中要能使用Java API、Script的執行結果也要可以回報給Java、在Java中也要可以呼叫Script的method。

接著,讓我們來一一擊破…

1. 在Script中使用Java物件

// 先在Java中產生一個ArrayList
List namesList = new ArrayList();
namesList.add("Jill");
namesList.add("Bob");
namesList.add("Laureen");
namesList.add("Ed");

// 取得Script Engine
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");

// 把ArrayList物件透過Engine傳進去,取名叫namesListFromJava
jsEngine.put("namesListFromJava", namesList);
System.out.println("==== Executing in script environment... ====");
try {
// 執行Script,注意,此時已經可以在Script中使用namesListFromJava這個
// 由Java傳入的物件了
jsEngine.eval("var x;" +
"var names = namesListFromJava.toArray();" +
"for(x in names) {" +
" println(names[x]);" +
"}" +
"namesListFromJava.add(\"Dana\");");
} catch (ScriptException ex) {
ex.printStackTrace();
}



程式執行結果如下

==== Executing in script environment... ====
Jill
Bob
Laureen
Ed



2. 使用Java呼叫JavaScript Method,並取得其回傳值

// getting engine
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");

// 必須將Engine轉型成Invocable
Invocable invocableEngine = (Invocable)jsEngine;

try {
jsEngine.eval("function sumTwoValue(var1,var2) {" +
" return var1 + var2;" +
"}");


// 在Java中呼叫Script Method並印出回傳值
System.out.println("return value from javascript is : " +
invocableEngine.invokeFunction("sumTwoValue", 3, 4));

} catch (ScriptException ex) {
ex.printStackTrace();
} catch (NoSuchMethodException ex) {
ex.printStackTrace();
}




執行結果如下

return value from javascript is : 7.0


3. 在Script中使用Java API

// getting engine
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");

try {
jsEngine.eval(
"importPackage(javax.swing);" +
"var optionPane = " +
" JOptionPane.showMessageDialog(null, 'Hello, world!');");
} catch (ScriptException ex) {
ex.printStackTrace();
}



程式執行結果如下:


可以發現,我輕鬆的在JavaScript中叫出了一個Swing對話盒,向各位說「Hello, world!」。

JDK6 野馬與Script的整合,的確讓牠更狂野奔放了:D

2007年4月12日 星期四

爪哇人看Ruby(4) -- Duck Typing

以前上心理學時有聽過「刻版印象」一詞,它的定義是這樣的:

刻版印象為一種心理的機制,與類別的形成有關,這種機制協助人們運作由所處環境所獲得的資料;刻版印象如同「腦海中的圖畫」(pictures in our heads)(Lippmann, 1922)。

嗯…講白一點就是「貼標籤」,例如:

看到外籍新娘就覺得是花錢買親,但他們不可以是自由戀愛結婚的嗎?
看到開雙B的年輕人就覺得他是家裡有錢,但搞不好人家是年輕有為呢?

....

所以,我得承認,我也有刻版印象--聽到Scripting Language 就覺得不是物件導向、型別檢查不嚴謹。

但事實上,Ruby是很純的物件導向語言,並且,它的Typing也不是Weak Typing,而是Strong Typing,來看看以下的程式片段執行結果:


ivalue = 10
puts ivalue.class # ivalue的型別是Fixnum

puts "hello " + ivalue
# 此行會發生Error
# TypeError: can't convert Fixnum into String
# from (irb):3:in `+'
# from (irb):3


看看最後列印 「"hello " + ivalue」這段,因為ivalue的型別不是字串,所以發生錯誤,和一般的Scripting Language是不一樣的。

再看看下列這段:

puts "hello " + ivalue2
# 發生錯誤
# NameError: undefined local variable or method `ivalue2' for main:Object
# from (irb):2

我們可以發現,未經宣告的變數也是不能直接使用的。


講精確一點,Ruby是Dynamic Typing且Strong Typing的語言,而Java是Static Typing且Strong Typing的語言。

所以,在Ruby中,直譯器幫你在變數宣告時,依據初始值的型態來決定變數的型態,並且在你使用變數時,是「依據實際上Instance有沒有該功能決定能不能用」。

這個說法很玄,但這就是Duck Typing的意函 -- 「如果它走起路來像鴨子,叫起來也像鴨子,那麼它一定是鴨子」。

Ruby在意的不是物件的身分是什麼,而在於他能不能做什麼

來看個例子

在Ruby中,我有Order和People兩個類別,而我需要這兩種物件都能轉成xml,所以我在這兩個類別中都加入了to_xml()方法


然後看一下程式的實作,當類別定義完成後,我使用一個陣列,裡頭存了二個Order物件和一個People物件,接著使用一個迴圈將整個陣列裡的物件都轉成XML

class Order
def initialize(orderNO, description)
@orderNO = orderNO
@description = description
end

def to_xml
return "" + @orderNO +
"
" + @description + "
"
end
end

class People
def initialize(name, age)
@name = name
@age = age
end

def to_xml
return "" + @name +
"
" + @age.to_s + "
"
end
end

objs = [ Order.new("ord_0001", "test order 1"),
People.new("Richard", 30),
Order.new("ord_0002", "test order 2") ]

# 將各物件轉成xml
objs.each do |o|
puts o.to_xml
end



而類似的事情若要在Java中實作,則必須先定義好一個抽象類別或Interface,將to_xml()函式先定義好,再讓Order及People兩個class繼承並實作之,如下圖:


程式碼如下:

in XMLDoc.java

public abstract class XMLDoc {
public abstract String to_xml();
}


in Order.java
public class Order extends XMLDoc{
private String orderNO;
private String description;

public Order(String orderNO, String description) {
this.orderNO = orderNO;
this.description = description;
}

public String to_xml() {
return "" + this.orderNO +
"
" + this.description + "
";
}
}


in People.java
public class People extends XMLDoc{
private String name;
private int age;

public People(String name, int age) {
this.name = name;
this.age = age;
}

public String to_xml() {
return "" + this.name +
"
" + this.age + "
";
}
}


in XMLWriter.java
import java.util.*;

class XMLWriter {
public static void main(String args[])
{
List objs = new ArrayList();
objs.add(new Order("ord_0001", "Test Order 1"));
objs.add(new People("Richard", 30));
objs.add(new Order("ord_0002", "Test Order 2"));

for (int i = 0; i < objs.size(); i++) {
XMLDoc o = (XMLDoc)objs.get(i);
System.out.println(o.to_xml() + "\n");
}
}
}


有人覺得Java的做法比較嚴謹,有人覺得Ruby的做法更簡潔有彈性,不管如何,這是語言本身的特色。

2007年4月10日 星期二

寫Java Script的好工具--Aptana

JavaScript一直是網頁開發者又愛又恨的玩意,你能用它做出令人驚艷的網頁效果,但JavaScript過多的網頁卻又複雜而難以維護,這個痛苦現在可以減緩,只需要一個好用的工具。

Aptana是一套以Eclipse為核心的IDE,開發對像是Html和JavaScript,並且內建許多AJAX的Library,並且讓你能以更直覺的方式對JavaScript進行Debug,當然,設Break Point和Watch是最基礎的了,如下圖:
按下小蟲圖示開始Debug



Debug過程中可以自在的設中斷點和監看變數值




而且還支援Auto Completion,例如下例程式碼,我寫了一個自定函式test2(),則test2這個函式名稱不但可以打到一半自動完成,還可以再輸入到左括號時自動顯示出該傳入那些參數。



它和Eclipse緊密整合,所以若你本來就是使用Eclipse來開發你的Java Web Application的話,不用再害怕網頁中的JavaScript難以偵錯及維護了。

注:有需要的同好可以自行到Aptana 官網下載。

2007年4月4日 星期三

爪哇人的Ruby(3) -- Meta Programming簡介

Meta Programming是什麼呢?讓我用白話一點的方式來說明:
就是可以動態更改Class的設定,可以新增成員變數,也可以新增方法,看一下以下這段程式:



str1 = "hello meta programming"

class String
  def to_html
    return "<p><b>" + self + "</b></p>\n"
  end
end

str2 = "author: richard"

# 輸出 <p><b>hello meta programming</b></p>
print str1.to_html

# 輸出 <p><b>author: richard</b></p>
print str2.to_html



有發現什麼神奇之處嗎?
是的,我在這簡短的程式中,加了一個新的to_html()的新方法到String類別定義中,所以之後所有的字串都能使用to_html()這個成員方法了。

這個功能除了可以讓你輕鬆的完成所謂的AOP之外,還可以做到一些Java做不到的事,回想一下使用Hibernate、JPA或是Entity Bean的步驟吧!
1. 在資料庫中建好Table及欄位
2. 在XML檔中設定物件的Getter、Setter和實際欄位的對應…(現在可以改用annotation,但還是不夠好)
3. 寫JavaBean,必須包含各欄位的Getter和Setter

一開始辛苦一次還可以,但如果每次的Schema更動都要把各種檔案掃過一次呢?那可就累了,不是嗎?

如果Java中的Hibernate或是JPA,可以在程式讀取資料庫表格Schema後,自動將Getter和Setter都產生,然後不再需要XML設定你複雜的O-R Mapping,那會是多麼美好呀!

這樣好用的工具已經有了,可惜是出現在Ruby上,希望以後Java也會有。

2007年4月3日 星期二

爪哇人的Ruby(2)

Ruby的OO成分比Java更純,怎講呢?

Java的世界中,所有事物都是物件,只差了一點:Primitive Types(原生型態)

看看以下的程式吧:


Integer money = new Integer(10);
int moreMoney = 99999;

List listOfSaving = new ArrayList();

listOfSaving.add(money); // 成功
listOfSaving.add(moreMoney); // 失敗,因為moreMoney是Primitive Type不是物件


其實我個人也很習慣使用Primitive Type,原因有二:1. 符合C++使用者的習慣。 2. 增加一點點效能。

不過也付出一些代價:1. 程式碼變的比較醜,常常要使用到轉型。 2. JDK 5為解決程式碼醜的問題,引入了Auto Boxing機制,卻也帶來了更多麻煩(以後有空再來聊聊)。

Ruby中沒有原生型態這回事,任何在Ruby中出現的事物都是物件,事情變的比較單純了。

Ruby還提供了兩種在Java中沒有的OO機制:Meta Programming和Mix-In,這也是Ruby具有高生產力的密訣之一,這個就容我之後再講了。

2007年3月28日 星期三

爪哇人的Ruby (1)

1995年,我是個年輕小夥子,天氣好的時候會結伴打打藍球、游個泳,雖然偶爾夜裡沉迷在魔獸爭霸2的網路對抗,但可以自豪的說,我體格健壯輕盈,擁有結實的六塊腹肌,和不實人間煙火的遠大理想。

當時陪伴我的桌機是Pentium 100,搭配Tseng ET4000顯卡,還有32MB的記憶體,和一顆540MB的大容量Quantum牌硬碟,這些都是逸品,以當時來說。

雖然還是習慣使用Dos 6.0加倚天中文連上BBS,但Windows 95的問市的確也在我的硬碟中佔了一席之地,儘管我還是留有一份Windows 3.1。

那時想用C++寫個視窗程式並不是那麼容易的,鎖碎的Win32 API,混雜著Win16時代的遺毒,加上煩人的C++指標,如果再配合MFC,新手想上路得有一定的決心。

Java的出現立即吸引了我的目光,類似C++的語法,更純的OO,終極的跨平台解決方案-JVM
,內建的AWT可以優雅的建立視窗介面程式,而iostream不止檔案讀寫,也完美的將網路Socket讀寫整合,還有Applet可以輕鬆的建立高互動性的網頁……種種變革,讓打開文字編輯器撰寫Java變成是種享受。

JDK1.0體態輕盈,只有幾MB不到,並且各種姿態都深深吸引了C++使用者的目光,而且使用記事本就能輕鬆駕馭他,生產力驚人,比使用複雜的Visual C++ MFC 還更有生產力,雖然執行效能較C++差是罩門,但大部份的事情其實不需要多高的CPU執行效率,通常程式花最多執行時間的地方是在IO,而這點Java並不會比較差。

十二年過去,Java果然成長為主流程式語言之一,許多企業選擇Java做為建置資訊系統的主要語言,我也靠Java而得以擁有穩定的收入,我的工程團隊使用Java為客戶導入各種系統,賺取錢財。

這些系統使用JDK5.0,搭配各種MVC Framework、O-R Mapping Tool或是J2EE Container,硬碟的使用量成長不少,而一個新進程式員要上手前的訓綀也難上許多。

愈來愈多Framework使用XML做設定檔,設定檔不難,但是很雜,這逼得我必須使用先進的IDE開發工具協助管理這些設定檔,並且在變更發生時同步幫我更新許多設定檔、Java Bean Class…,這感覺和當年使用MFC開發必須大量依賴IDE提供的精靈一樣,鎖碎了、煩了…

我可以用Java完成各種任務,但那過程已經不再是享受…Java 肥了,和我一樣,我老婆說現在看著我的身體做愛,已經不再是享受了,但我的確比以前會賺錢。

Ruby on Rails 最近紅透半邊天,Ruby是個富有OO特性的Scripting Language,Rails是個革命性的MVC Framework。

Ruby有精簡易懂的語法、比Java更純的OO,而且Meta Programming、程式碼區塊、Mix-In等好用的OO特徵都是Java沒有的,這使得Ruby語言有驚人的生產力。

Rails使用Ruby的特性開發出來,你可以用他來建置MVC Web App,配合好用的O-R Mapping,但是你不再需要重覆的設定XML,重覆的更新Java Bean。

我會再之後的文章中以Java人的觀念介紹這些Ruby好用的特性。

2007年3月8日 星期四

SCEA第二階段---我的步驟

上回提到了文件中應該要包含Assumption、Design Decision、Diagram等要素讓它更具可讀性,這回我要講講設計的主軸應該要如何開始撰寫。

試題中提到了第二階段必須交附的四大項目:

1. Class Diagram --> 1張
2. Sequence Diagram --> 每個Use Case 1張
3. Component Diagram --> 1張
4. 輔助說明文件(需要包含Assumption和Design Decision)

由於我個人畫圖及寫文件的技巧不佳,因此花了些功夫才做出來,以下分享我的產出步驟:

1. 惡補GoF Design Pattern及J2EE Core Design Pattern,不是叫你照抄,但了解別人怎麼做,自己也才能做的更好,不是嗎!(如果你已經很熟就跳過吧)。
2. 決定整體架構的大方向,例如前端介面使用Web MVC,商業邏輯放在SLSB,Persistence使用EJB配合…
3. 惡補UML圖的繪製規格,練習使用UML Case Tool (如果你已經很熟就跳過吧)。
4. 把需求從頭到尾看過至少二次以上,將有疑問的地方寫成Assumption。
5. 從Domain Model長出Class Diagram,並寫下各個在設計類別時所牽涉到的Design Decision,注意符合需求是第一原則。
6. 決定各層有那些元件,一樣寫下Design Decision,注意要和其它圖保持設計的一致性。
7. 根據元件畫出各Use Case 的Sequence Diagram,此時你會再發現一些需要Assumption的地方,或設計不合理的地方,寫下來,並且回頭修正設計。
8. 畫出Component Diagram,寫下Design Decision,注意要和其它圖保持設計的一致性。
9. 寫說明文件,記得此文件要包含之前所整理的Assumption、Design Decision和各個UML圖,如果有不容易了解的地方,盡量用圖形輔助。
10. Review再Review,切記符合需求及一致性(不要有自相茅盾的地方)。
11. 送件。

2007年2月12日 星期一

UML or not? Less or More?


前幾年UML很流行,就跟很多新玩意一樣,眾人稱道,紛紛採用。



UML很好,但你不需要只擁有它,只要是可以讓文件更簡單易懂的表達方式,都可以出現在設計文件中。



過去幾年我經歷過幾個專案,客戶感受到UML的Power,相當喜愛,因此我也感受到他們的狂熱,這些專案被要求要以”OOAD”的方式開發,要享有”OOAD的好處”,他們需要一份包羅萬象的設計文件,內容必須包含UML的各種圖。



我被要求必須提供符合UML規範的圖形,而且”顆粒度愈細愈好”,所以我的Class Diagram包含了整個系統的所有類別,可能有二、三百個吧!而Sequence Diagram則被要求必須包含所有該專案產出的”每行”程式碼,畢竟對客戶而言,東西愈多愈好,一樣的價錢,能拿到愈多文件就是賺愈多。



那些年,我總是在專案最後收尾階段時才拼命補畫各種圖,也許你會講”設計文件後補”,不是正規的方法,但我有我的難處:


1. 顆粒度太細的設計圖,溝通的功能不足,不如直接看程式碼。


2. 專案開發過程中,只有高階架構有可能維持一致,實作細節則是不斷變更的,變更原因可能是Bug修正、效能調效或需求變更,如果同時要改Sequence Diagram,那程式員會殺了我吧…


3. 畫出顆粒度很細的UML圖形,其Effort比直接實作出來還高。



種種原因,使得專案團隊必須在程式碼交附後才開始趕文件,這些文件也許鉅細糜遺,但在我看來,卻對了解系統沒有幫助,如果客戶想將系統交接給新成員,他拿到厚厚一疊設計文件時,應該會覺得不如直接看程式碼比較容易了解。



這個問題,就和很多人喜歡用書的”厚度”來決定書的內涵一樣,厚的書賣的貴是理所當然,頁數不多的書賣貴了,是暴利。



其實,能將許多內容濃縮成幾張圖、幾句話,才是真正價值所在,所以我開始對專案設計文件的產出方式改變。不過,許多客戶還是對於能拿到”厚重的設計文件”這件事很重視,但我還是會嘗試的。

2007年1月28日 星期日

SCEA第二階段---如何開始


還記得拿到第二階段的考題後,便迫不及待的把他印出來,並且很認真的畫線、註解。
然後很快的看完了一遍,當時覺得應該不難,但反覆看過幾次後,卻還是不知如何開始…

在考SCEA之前,我也負責過幾個J2EE的案子,在客戶面前也都能自傲的吹噓我們的OOAD技術,UML的各種圖也都畫了出來,在"結案"前也準時的交付,但仔細想想…那些文件的用途是什麼?
老實說,是為了結案。

如果客戶需要顧用一位新員工來維護該系統,新員工能在沒有我出面說明的情形下,只憑文件就了解系統嗎? 系統中的各項設計訣擇,是在什麼情形下產生的?

一個個問題在我回想中一一浮現…是的,我的確沒有認真想過,以致只能看著試題發呆…

當你拿到一份需求時,首先要做的是徹底了解,接著思考可行的架構,一個好的架構必然是符合需求且具全面性考量的,以下我要來介紹一些名詞,都是在設計時必須好好思考的名詞:



1. Assumption:
我在SI公司待了很久,就是那種專門接案件開發的單位,東奔西跑下來,看過的需求還真不少,使用者提出需求一定有他的想法需要你幫他滿足,但可惜的是他們永遠說不清楚,他們需要一位有經驗的IT從業人員來導引出真正清楚可行的需求。

不論是那裡來的需求書,看完後必然有一堆問號(如果你有認真看的話),把這些問號都寫下來,去找實際的使用者,一一問清楚,然後做成正式的會議記錄,要求大家簽名同意,並更新文件,這個階段的每個遺漏,都是你這開發者將來會吃悶虧的地方。

SCEA第二部份的考題也是一份需求書,同樣的也是不清不楚,但這次沒有使用者可以幫你犛清所有問號,但沒關係,想像你自己是使用者,自問自答,把答案寫下來成為一條一條的Assumption。



2. Design Decision:
去年12月,我帶著身懷六甲的老婆出遊,行程是這樣設計的:
第一天:
08:00 出發,從茹冬交流道上北二高,往南至南投草屯接新中橫,目的地是合歡山
12:00 清境附近找一間不錯的民宿用餐
14:00 合歡山莊 Check in
15:00 武嶺賞雲海
16:00 合歡主峰登山步道
17:30 合歡主峰頂看日落


行程的設計訣擇:
1. 08:00出發-->因為當天是非假日,所以預期不會塞車,這時間出發到清境剛好約午餐時間。
2. 從茹冬交流道上北二高,往南至南投草屯-->因為從新竹經北二高到南投路徑短,車流速度快。
3. 12:00 清境附近找一間不錯的民宿用餐-->清境附近的用餐選擇很多,不致於找不到餐館,所以決定到時再找。
4. 從新中橫上山會先經過合歡主峰、武嶺再到合歡山莊,但我們決定先到合歡山莊Check in再回頭觀光 --> 因為合歡主峰日落景致很美,為了配合黃昏的時間,所以決定先Check in再繞回頭路。



我並不想寫一篇遊記,但思考一下行程與其設計訣擇之間的關係,其實是很有趣的。

如果只有行程表,而十年後的我看到這行程表時,能夠了解行程為何要這樣安排嗎?
如果只有行程表,而十年後的我想再安排一次合歡山之旅時,這行程有用嗎?(也許到時的交通環境和景點設施都已改變)

生活中的許多事物都存在著「Design<-->Design Decision」之間的關係,例如大樓的設計圖、食譜、引擎……數不盡。

軟體設計也一樣,好的設計文件,應該要將「重要的」「Design Decision」,以簡單明暸的方式寫下來,這會使你的文件更具可讀性及參考價值。



3. Good Diagram:
簡單的文字搭配良好的圖解,可以讓你的文件可讀性事半功倍,這裡的Diagram並不限定要符合UML規格,只要是可以幫助說明的,都可以放在文件中,記住一點:沒有人員輔助說明就看不懂的文件,不能叫好。

2007年1月11日 星期四

SCEA Part 2考試心得(二)

在完成SCEA第二階段的報名手續後, 你會收到考試中心通知,要求受試者至Sun CertManager下載考題,網址如下:

https://www.certmanager.net/sun_assignment/

你必須使用在Prometric報考第一階段時的考試ID (Candidate ID)做為帳號登入,然後下載作業題目(Assignment)

開啟後原則上每個人的題目都一樣,是一個航空系統專案,內容方面,礙於保密協定的關系,我就不透露了,但其實在網路上應該很容易可以找到這份題目。

第二階段的作業要求你產出一份文件,內容包含Class Diagram, Component Diagram, Sequence Diagram for each Use Case,以及對系統的說明和假設。

這些文件都必須使用英文撰寫成網頁格式,並在最後打包成一個.jar上傳回Sun CertManager。

在你上傳之後,便可以至Prometric報名第三階段的考試,考試類型是四題問答題,問的就是你系統的設計方式,及為什麼要這樣設計,目的是確認作業真的是你本人寫的。

完成第三階段後,在家等待四到六週,你會收到一封EMail通知你的考試成績。如果大於70分,那恭喜你過關了,如果小於70分,那你還有一次機會補考310-300R,修改好你的作業後再度上傳。

補考時唯一可以參考的資訊是考試成績,Sun不會把為什麼不及格的原因告訴你,你只會得到一份成績單,裡頭寫著Class Diagram的分數、Component Diagram的分數、Sequence Diagram的分數。

根據這些分數,你可以補強作業中失分較多的地方,重新寫完後再上傳到Sun CertManager。

補考的流程我沒有實際體驗過,不過根據我在Java Ranch看來的資料,如果補考後還是失敗的話,必須再回到第一階段重新考起。

在此提供大家一個網站:http://www.javaranch.com
該網站有一個SCEA考試版,提供很多SCEA考試方面的相關討論,幾乎是所有準備報考SCEA同好的必到之處。