Friday, November 18, 2005

Re Party with Lisp

ช่วงนี้กำลังหัดเขียน common lisp อยู่
อ่าน post เรื่อง party ของ mk แล้ว
ก็เลยทดลองเขียน clisp ดูเล่นๆดีกว่า

เริ่มกันที่ funcation should-criticize?
ซึ่งเราจะใช้สั่ง run แบบนี้(should-criticize "ธีรยุทธ" "ทักษิณ")

(defun should-criticize? (a b)
(if (better? a b) t nil))

ความหมายก็คือ a มีคุณสมบัติพอที่จะวิจารณ์ b ได้หรือไม่
ก็ให้ call ไป function better? ถ้า return true
ก็จะ return t กลับไป ถ้าไม่ใช่ ก็จะ return nil (false ในความหมายของ lisp)

ลองนิยาม function better? ต่อ

(defun better (a b)
(if (> (how-good a) (how-good b)) t nil))

เขียนง่ายๆ ว่าถ้า a มีแต้ม how-good มากกว่า b
(how-good คือ function ที่ return คะแนนความดีกลับออกมา)
ก็ให้ return t (true)

ลองนิยาม function how-good
อืมม์อันนี้สิเริ่มยากแล้ว
เรารู้กันว่าความดีเป็น subjective แล้ว
a อาจจะดีกว่า b ในสายตา y
หรือ
b ดีกว่า a ในสายตา x
อย่างนี้ต้องย้อนกลับไป refactor code เดิม
ให้ส่ง c (คนที่มอง) มาด้วยดีกว่า
(งั้งเวลาสั่ง run ก็จะเป็นแบบนี้แทน (should-criticize "ธีรยุทธ" "ทักษิณ" "mk")))

(defun should-criticize? (a b c)
(if (better? a b c) t nil))

(defun better (a b c)
(if (> (how-good a c) (how-good b c)) t nil))


ดีขึ้นหน่อย
คราวนี้ก็มาถึงว่าแต้มความดีจะคำนวณอย่างไร
คงต้องมองเป็นหลายๆด้าน
งั้นเริ่มจากกำหนด list ที่เก็บคุณสมบัติต่างๆกันก่อน

(setf *attrs* '("father" "business" "charisma" "pricky"))

(เอ้ คุณธีรยุทธ แกแต่งงานหรือยังหว่า?)

คราวนี้ต้อง loop ไปตาม list เพื่อเก็บคะแนนรวม
อืมม์ชักยากแล้วสิ ยังอ่านไม่ถึงบทที่ว่าเรื่อง iterator เลย

(defun how-good (subject object)
(let ((total 0))
(dolist (attr *attrs*)
(setf total (+ total (get-score subject object attr))))
total))

คนที่ไม่เคยใช้ lisp อาจจะร้องอุทานว่า "โอ อะไรกันนี้"
ส่วนผมที่พึ่งหัดใช้ ก็ยังร้องแบบเดียวกันอยู่
คำอธิบายก็คือ let เป็นการกำหนด scope ของตัวแปร
ในที่เราเขียนนี้ตัวแปร total จะมี life-cycle อยู่ใน ขอบเขตของ let เท่านั้น
ส่วน dolist เป็นการ loop ไปตาม list *attrs* ที่ให้มาโดย
ดึงค่าออกมาใส่ตัวแปร attr ทีละตัว
(setf x 1) มีความหมาย = int x = 1 ใน java
ส่วน get-score ก็คือ function ที่ return คะแนนที่ subject ให้กับ
object ในส่วนของคุณสมบัติต่างๆ
บรรทัดสุดท้ายก็คือการ return ค่า total กลับออกมา

อืมม์ดูแล้ว ลืมเรื่อง weight ไปเลย
เพราะแต่ละคนอาจจะให้นำ้หนักของ attributes ต่างๆไม่เท่ากันอีก
เปลี่ยนเป็นอย่างนี้ดีกว่า

(defun how-good (subject object)
(let ((total 0))
(dolist (attr *attrs*)
(setf total (+ total
(* (get-weight subject object attr)
(get-score subject object attr)))))
total))


เฮ้อ พอพอแค่ดีกว่า ไม่รู้จะ implement get-weight กับ get-score อย่างไงดี

Note:

CL-USER > (is-annoy-me "สนธิ")
T

แปลว่า ผมก็รำคาญสนธิเหมือนกัน
แต่ระหว่างทักษิณกับสนธิ, ผมรำคาญใครมากกว่ากันนั้น ไม่บอก

Related link from Roti

Wednesday, November 16, 2005

Cache with Ruby Hash

เวลาเราสร้าง Hash object ใน Ruby
เราสามารถ pass block เข้าไปได้ด้วย
ซึ่ง block นี้จะถูกเรียกใช้
เมื่อมีการ access hash ตาม key ที่ให้มา แล้วไม่พบค่าใน hash นั้น
ลองดู code
require 'pp'
h = Hash.new { |h, key|
h[key] = "result from #{key}"
}
puts h[1]
puts h[2]
pp h

ได้ผลลัพท์ดังนี้
result from 1
result from 2
{1=>"result from 1", 2=>"result from 2"}


Feature นี้ทำให้เราสามารถ implement cache ได้อย่างง่ายๆ
ลองดูตัวอย่างนี้
def fib(n)
return n if n < 2
fib(n-1) + fib(n-2)
end
puts fib(20)

เขียนแบบใช้ Hash จะได้ดังนี้

fib_h = Hash.new {|h, k|
if k < 2
h[k] = k
else
h[k] = h[k-1] +h[k-2]
end
}
puts fib_h[30]


ทดลองจับเวลา เปรียบเทียบการหาค่า n ที่เท่ากับ 30
ถ้าไม่มี cache จะใช้เวลา 4.3 วินาที
ส่วน version ที่ใช้ cache จะใช้เวลา 0.002 วินาที

ทีนี้ลองดู version พิสดารบ้าง
ไปเห็นมาจาก Concise Memoization
กับของ Cheap Ruby memoize using Hash default block

version พิสดาร จะใช้แบบนี้
สมมติเรามี method fib แบบ simple
def fib(n)
return n if n < 2
fib(n-1) + fib(n-2)
end

กรณีที่เราต้องการทำ cache method นี้
เราก็จะสั่งดังนี้
memoize(:fib)

โดย method memoize มีหน้าตาดังนี้
def memoize( name )
meth = method( name )
cache = Hash.new { |cache, args|
cache[args] = meth.call( *args )
}
( class << self ; self ; end ).class_eval do
define_method( name ) { |*args| cache[args] }
end
cache
end

จะเห็นมีว่าการนำ class_eval กับ define_method มาใช้
คนที่ยังไม่คุ้นกับ 2 method นี้ลองดูตัวอย่างนี้
class X
def hi
puts 'Hello'
end
end

X.new.hi # output -> Hello

X.class_eval do
define_method(:hi) { |*args|
puts 'hi pok'
}
end

X.new.hi # output -> hi pok

Related link from Roti

Tuesday, November 15, 2005

Mouseover DOM Inspector

MODI เป็น Bookmarklet ที่ทำให้เรา inspect DOM element บน page ได้

Related link from Roti

Cross Browser Screenshots

browsershots มีบริการทดสอบเปรียบเทียบ Page ของเราว่าจะมีหน้าตา
อย่างไร เมื่อ browse ด้วย browser ต่างๆ
browser ที่มีบริการ ประกอบด้วย
  • MSIE 6.0
  • Epiphany 1.4.8
  • Firefox 1.0.4
  • Galeon 1.3.20
  • Konqueror 3.3
  • Mozilla 1.7.8
  • Opera 8.50
  • Safari 2.0

เวลา post ไปแล้ว จะยังไม่ได้ผลลัพท์ทันที
เขาจะ queue request เราไว้ก่อน

ลองดูตัวอย่างผลลัพท์ที่ได้ Results

เท่าที่ลองดูปรากฎว่าช่วงนี้ queue เต็ม

น่าสนใจว่า เขา implement program กันอย่างไง

Related link from Roti

SPECjAppServer2004

วันนี้อ่านเจอ Thread ใน TSS
ที่ว่า Websphere ทำคะแนน SPECjAppServer2004
ได้ 2921.48 แต้ม
เทียบกับ Sun/BEA/Oracle ที่ post คะแนนไว้ที่ 1781.47

ในส่วนของ App Server Hardware
SUN/BEA/Oracle ใช้ Sun Fire X4100
processor AMD Opteron 280 (dual core)
จำนวน 5 เครื่อง เครื่องละ 2 cpu
memory เครื่องละ 4 GB
เทียบกับ IBM ที่ใช้ P5 550
processor POWER5+(dual core)
จำนวน 8 เครื่อง เครื่องละ 2 cup
memory เครื่องละ 4 GB

น่าสนใจที่เครื่องของทั้ง IBM และ Sun
อยู่ในกลุ่ม entry-level server ทั้งคู่

Hardware ที่น่าสนใจอีกตัวก็คือ
Database Server
ทาง Sun ใช้ Sun Fire E6900
cpu UltraSPARC IV (dual core)
ใส่ไปแค่ 16 ตัว
ส่วน Ram อัดไปแค่ 64 GB
ส่วนทางฝั่ง IBM
ใช้ IBM eServer p5 570
cpu POWER5 ทั้งหมด 4 ตัว
ส่วน Ram ใช้เท่ากันคือ 64 GB

db server ที่ทั้งคู่ใช้ จัดอยู่ในกลุ่ม Mid-range เหมือนกัน

ประเด็นที่สงสัยก็คือ ทำไมทางฝั่ง sun มันใช้ cpu บน DB Server
เยอะขนาดนั้น อยากรู้ % cpu ว่ามันกินไปเท่าไร ระหว่างที่ run beachmark
(ข้อเสียของ spec พวกนี้ก็คือ เห็นแต่ตัวเลขสรุป
ไม่เห็น graph หรือข้อมูลอื่นๆเลย)

ที่น่าสนใจตรง post จาก BEA ที่มีข้อมูลราคาให้ดูด้วย
IBM/WAS/DB2 publish:
AppServer Hardware: $192,000
AppServer Software: $480,000
Database Hardware: $377,000
Database Software: $181,000
Total = $1,230,000
Total $/JOPS = $1,230,000/2921 JOPS = $421/JOP

SUN/BEA/Oracle publish:
AppServer Hardware: $38,000
AppServer Software: $100,000
Database Hardware: $752,000
Database Software: $566,000
Total = $1,456,000
Total $/JOPS = $1,456,000/1781JOPS = $817/JOP


Steve Realmuto จาก BEA
post ใน Thread ต่อด้วยว่า

That said, the total SPECjAppServer2004 JOPS number for any result is also dependent on the underlying hardware, so you need to look this number in the context of the hardware required to achieve it. The design of this benchmark is such that the application server tier is infinitely scalable. Essentially, you can achieve any desired result by throwing enough hardware at it.

Another way to normalize results across different hardware platforms is to look at the number of application server CPU cores required to achieve a given result. For example, how many CPUs per 1000 JOPS were used. This method is far from perfect, but it can provide some useful insights. IBM's latest result achieved 2921.48 JOPS@Standard using 32 Power5 application server CPU cores or 10.95 CPUs per 1000 JOPs. The previous high result from IBM achieved 1343.47 JOPS@Standard using 20 Intel Xeon application server CPU cores or 14.89 CPUs per 1000 JOPS. For comparison, BEA's WebLogic Server achieved 1664.36 JOPS@Standard using 12 Intel Xeon application server CPU cores or 7.21 CPUs per 1000 JOPS.


Note: post นี้ไม่มีข้อสรุป ฟังหูไว้หู

Related link from Roti

Sunday, November 13, 2005

Trip พายเรือ #1

ได้เล่น google earth แล้วรู้สึกชอบใจ
ก็เลยขอบันทึกเส้นทางการเดินทาง ที่เคยพายเรือ
จากเชียงใหม่ลงมากรุงเทพฯ เมื่อ 8 ปีก่อน

เริ่มเดินทาง 12/12/2540 เวลาบ่าย
โดยใช้เรือ canoe เป็นพาหนะ
ใช้พายแบบพายเดี่ยว
เรือนี้ยืมมาจากเรือโรงแรมที่ให้บริการที่เขื่อนภูมิพล



ที่หัวเรือจะมีหมายเลข 20
ระหว่างทางที่พายลงมา ชาวบ้านที่พบเห็น
ก็จะคอยส่งเสียงเชียร์ พร้อมกันทางว่า
จะแข่งพายไปไหน
ก็เลยต้องคอยอธิบายว่า พายเล่นๆ ไม่ได้แข่งกับใคร
ส่วนอีกพวก พอเห็นเลข 20
ก็จะเอาเลขนี้ไปแทงหวยกัน
ไม่ได้ข่าวว่ามีใครถูกบ้าง
คาดว่า คงถูกกินเรียบกันหมด

ระหว่างทางมีฝายกั้นน้ำ 3 แห่ง



การนำเรือผ่านฝาย ต้องใช้วิธีขนของทั้งหมดลงจากเรือ
แล้วก็ปล่อยให้เรือลอยลงไปเอง
(เรือมันจะเบา ทำให้ไม่พุ่งชนก้อนหิน)
จริงๆแล้ว มันก็ดูเหมือนพอจะพายลงมาได้เหมือนกัน
แต่ไม่อยากเสี่ยง เรือแตก จบ trip ตั้งแต่วันแรก

การผ่านฝายแต่ละฝายจะใช้เวลาค่อนข้างมาก
เพราะต้องขนของลงเรือ แบกของไปอีกฝั่ง
ปล่อยเรือ ขนของขึ้นเรือ
เหนื่อยสุดๆ

ทิวทัศน์ 2 ข้างทางในช่วงนี้
ประกอบด้วยบ้านผู้มีอันจะกินทั้งสิ้น
(ประชันความหรู ตลอดทาง)

จำได้ว่า รู้สึกตื่นเต้นมากเมื่อถึงตอนเย็น
รีบถามชาวบ้านใหญ่เลยว่า
ตอนนี้อยู่ห่างจากเชียงใหม่มากไหม
ชาวบ้านก็ทำหน้างงๆ
บอกว่า ประมาณ 10 โล
ไอ้เราก็นึกว่า ป่านนี้คงมาได้ไกลโขแล้ว

หลังจากมี google earth
แล้วจึงรู้ว่าระยะทางที่พายได้ 7 โลนิดๆ

หมู่บ้านที่ขออาศัยพื้นที่พักนอน
เป็นหมู่บ้านของพวกคริสต์
สถาปัตยกรรมน่าสนใจทีเดียว
การขอนอน ต้องซ้อนท้ายมอเตอร์ไซด์
ไปหาผู้ใหญ่บ้าน เพื่อขออนุญาติพักนอน

ค้างคืนวันแรกโดยใช้เปลผูกต้นไม้ริ่มฝั่ง
ปีนั้นเป็นปีที่อากาศไม่หนาว
ทำให้ยุงเยอะมาก
ตื่นขึ้นมา ท้ายทอยส่วนที่สัมผัสเปล
มีอาการบวมตุยเชียว
(ยุงมันเล่นกัดรอดผ้าเลย)

อุปสรรคที่หนักสุดสำหรับวันแรก
ก็คือ ความกลัว
กลางวันไม่มีอะไรอยู่แล้ว
แต่กลางคืนนี่สิ
น่ากลัวมาก
(แต่หลังจากผ่านไปได้ 8 วัน
ไม่มีผีโผล่มาหลอกสักครั้ง
ก็เลยหายกลัวโดยสิ้นเชิง)

Related link from Roti

Saturday, November 12, 2005

Behaviour Driven Development

ช่วงนี้เริ่มได้ยินศัพท์ BDD มากขึ้น
BDD ย่อจาก Behavior Driven Development
เป็น concept ที่ evolve มาจาก TDD
หรืือ Test Driven Development

ที่เกิด concept ใหม่นี้ขึ้นมาก็เพราะ
เขามองกันว่า TDD มันใช้ wording ที่มีแต่คำว่า "test"
เช่น เราเขียน TestCase ซึ่งภายในมี method testXXX
และ run TestCase ด้วย TestRunner
ไอ้คำว่า "Test" ทั้งหลายนี่มัน block
mental ของเราให้เห็นแต่เรื่อง test
(บางคนก็มี mental block ที่ว่า ยังไม่ทันเขียน program เลย
จะให้เขียน test ได้อย่างไร)


Sapir-Whorf hypothesis (SWH)
there is a systematic relationship between the
grammatical categories of the language a person
speaks and how that person both understands the
world and behaves in it


BDD ก็เลยเกิดขึ้นมา
โดยพยายามเปลี่ยน wording เพื่อให้
หลีกเลี่ยง mental block
โดยพยายาม shift ไปสู่ Specification หรือ Behavior แทน

BDD focus บน

Bob Martin’s "Specification, not Verification"
  • you’re not verifying that the code works correctly
  • you’re specifying what it means for it to work correctly



ตอนนี้เท่าที่เห็น opensource project ที่เริ่มทำกันก็มี


ซึ่งถ้าตามไปดูเอกสารของทั้ง 2
ก็ดูเหมือนว่า มันเป็นแค่การเปลี่ยนแค่คำศัพท์ที่ใช้เท่านั้น
เช่นจาก assert -> should
ก็ต้องตามดูกันต่อไปว่า
แค่การเปลี่ยนศัพท์เฉยๆ จะทำให้เกิด effect อย่างที่เขาว่าจริงหรือไม่

แต่มี Idea ที่น่าสนใจอยู่อัน
อันนี้เจอที่ obie, has it been 9 years already?
เขาลองร่าง DSL (Domain Specific Language) ที่ implment ด้วย ruby ขึ้นมา
หน้าตาแบบนี้ (จริงๆไม่อยาก copy มาเท่าไรเลย แต่อยากให้เห็นภาพกัน)

actors :audits => Audit::Logger,
:db => PayrollDatabase,
:employee => Employee,
:transaction => Transaction

story "A new employee is added by the receipt of an AddEmployee message." do

Name = "William Jones"
AnnualSalary = 60000
MonthlySalary = 5000
MonthlyType = Employee::Monthly

scenario "add a salaried employee earning 60k per year" do

db.pretend! do
on_create :with => (:name => Name, :salary => AnnualSalary),
:return => Employee.new(Name, MonthlyType, AnnualSalary)
end

transaction = AddSalariedEmployeeMsg.new(Name, AnnualSalary)
transaction.execute()

to_verify do
employee = db.find_by_name(Name)
employee_classification == MonthlyType
employee_salary == MonthlySalary

audits_received :log, NEW_EMPLOYEE
end

end

end

เห็นแล้วรู้สึกน่าสนใจทีเดียว
และก็ชอบตรงที่ Obie เขียนว่า

I started figuring out that there are significantly better ways to achieve the goals of RSpec in Ruby, as long as we remember that Ruby is not Java.


Note:
blockquote ตัดมาจาก presentation ของ Dave Astels

Related link from Roti

Friday, November 11, 2005

gem_server

[ruby]

สงสััยมานานแล้ว
เวลาที่เรา install ruby module ด้วยคำสั่ง gem install
มันจะมีอยู่ช่วงหนึ่งที่มัน generate rdoc ให้เรา
ซึ่ง rdoc ที่ generate ก็ช่างซ่อนอยู่ใน directory
ที่ยากแก่การเข้าถึงเสียเหลือเกิน

พึงรู้วันนี้ว่ามีคำสั่ง gem_server
ที่ start webbrick บน port 8808
ทำให้เรา access rdoc ได้จาก browser

Related link from Roti

DWR

DWR - Direct Web Remoting
คือ framework ที่ช่วยให้เราสามารถ acesss
Java Object ที่อยู่บน Server ได้จาก javascript โดยตรง
ซึ่ง feature ที่น่าสนใจก็คือ มันสามารถใช้กับ spring bean ได้ด้วย

ลองดูตัวอย่าง
สมมติเรามี Bean ที่ทำหน้าที่ค้นหาชื่อลูกค้า

public class CustomerSvr {
.. // hibernate stuff

public List findCustomerByName(String name) {
...
}

}

public class CustomerDao {
private long id;
private String name;

.. // setter & getter here
}



ถ้าเราต้องการให้ DWR expose CustomerSvr
ก็ต้อง config ดังนี้
เริ่มจาก add DWR Servlet ก่อน

<servlet>
<servlet-name>dwr-invoker</servlet-name>
<display-name>DWR Servlet</display-name>
<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

จากนั้นก็ต้อง config DWR ว่ามี Bean อะไรที่ expose function
ให้ javascript เห็นได้บ้าง

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
"http://www.getahead.ltd.uk/dwr/dwr10.dtd">

<dwr>
<allow>
<convert
converter="bean"
match="service.*"/>

<create
creator="spring"
javascript="customerSvr">
<param name="beanName" value="customerSvr"/>
</create>
</allow>
</dwr>

โดยเราสามารถกำหนด
  • method อะไรที่ต้องการให้เห็นบ้าง
  • วิธีการ convert ผลลัพท์ที่ได้
    เท่าที่เห็นคร่าวๆ ก็มี
    • Bean converter
    • Hibernate converter
      ต่างกับ bean converter ตรงที่ทำ lazy loaded properties ได้
    • JDom Converter


ให้สังเกตุว่าเราใช้ creator="spring"
เป็นการบอกว่าให้ไป lookup หา bean จาก spring context

ถ้า config ไว้ถูกต้อง เราก็จะสามารถทดสอบการทำงานเบื้องต้นได้โดย
ใช้ browser เปิด url http://server:port/dwr/
DWR servlet จะแสดงหน้าจอที่ใช้ในการทดสอบการทำงานของ Bean เรา



ที่นี้ลองดูที่ฝั่ง javascript บ้าง
ถ้าต้องการที่จะ call CustomerSvr
ที่ฝั่ง javascript จะเขียนดังนี้

เริ่มจากให้ include generated script ก่อน

<script type='text/javascript' src='/dwr/interface/customerSvr.js'></script>
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/util.js'></script>


ส่วน script ที่เรียกใช้งาน หน้าตาดังนี้

<script>

function findCustomerName(name) {
customerSvr.findByName(name, displayCustomerName);
}

function displayCustomerName(customers) {
var x = $("custName");

var text = 'Customers: <br><ul>';
for(i = 0; i < customers.length; i++) {
text += '<li>';
text += customers[i].name;

text += ' (';
text += customers[i].id;
text += ')';
text += '</li>';
}
x.innerHTML = text;
}

</script>

มีที่น่าสังเกตุคือ เราเรียกใช้ bean โดยอ้างถึงชื่อที่เรา config ใน dwr.xml ได้เลย
ในที่นี้ก็คือประโยค customerSvr.findByName
แต่ต้องเพิ่ม callback function เข้าไปใน parameters ที่ส่งให้ findByName ด้วย

ส่วนใน callback function จะเห็นว่า
เราใช้ $('xxx') ซึ่งเป็น util function
ที่เรา include เข้ามาจาก DWRUtil.js
โดย function นี้ใช้ในการ access DHTML Element

สุดท้ายก็คือ html element ที่เรียกใช้งาน

<body>
<input type="text" onBlur="findCustomerName(this.value)"/><br/>
<span id="custName">Name<span>
</span>
</body>


ข้อดีของ DWR
จะเห็นว่า DWR ซ่อนรายละเอียดต่างๆไว้เบื้องหลังหมด
เราแค่รู้ชื่อ service กับชื่อ method ตลอดจน return structure เท่านั้นเอง

ข้อเสียเท่าที่เห็น ก็มี
เนื่องจาก object ที่ส่งกลับ ต้องมีการทำ serialize กับ deSerialize
ดังนั้น object ที่ DWR support สามารถใช้ได้ก็จะมีจำกัด
ส่งผลให้บางครั้ง เราต้องเพิ่ม method ที่ให้ผลลัพท์แบบที่ DWR สามารถ convert ได้
ทำให้มี method ที่ redundant ใน business layer เพิ่มขึ้นจำนวนหนึ่ง

อ่านเพิ่มเติมได้ที่นี่
Ajax with Direct Web Remoting

Related link from Roti

ri18n error on Windows Platform

สืบเนื่องจาก post เรื่อง ri18n ในครั้งก่อน
ได้รับแจ้งว่า ไม่สามารถ run บน windows platform ได้
โดยเกิด error เมื่อเรียกใช้ rake i18n

C:\users\pok\rails\test>rake i18n
(in C:/users/pok/rails/test)
rake aborted!
uninitialized constant Catalog::Iconv


ค้นหาบน google พบว่าเกิดจาก missing library ที่ชื่อ iconv
โดย ruby ที่ install จาก one install click
จะไม่ include library ตัวนี้ให้เรา
เราต้อง install library เอง
ตัว installer ของ iconv หาได้จากที่นี่ link

Related link from Roti

Wednesday, November 09, 2005

Spring 's Configuration file with Jacn

ปัจจุบันผมใช้ Spring framework
เป็น container หลักในการพัฒนา Application
แต่มีปัญหาหนึ่งที่กวนใจอยู่บ้าง
ก็คือ configuration file ของ spring
ซึ่งประเด็นปัญหาที่กวนใจก็คือ
  • การอ่าน xml file
    ถ้าใครเคยเปิด config file ที่ตัวใหญ่ๆดู
    จะรู้ว่ามันลายตาขนาดไหน มีแต่คำว่า bean เต็มไปหมด
  • การ edit xml file
    เรื่องสะกดผิด เป็นเรื่องปกติ
    ซึ่งต้อง run ก่อนจึงจะเห็น stack trace ที่เกิดขึ้น


ใน post นี้จะพูดถึงกรณีที่ 2 เพียงอย่างเดียวก่อน
solution ที่มักใช้กันสำหรับปัญหานี้ก็คือ
Spring IDE
ซึ่งก็ใช้ได้ดีที่เดียว เพียงแต่มีข้อจำกัดว่า project เราต้องใช้ Eclipse ในการพัฒนา

solution อีกอันหนึ่งสำหรับประเด็นปัญหานี้ก็คือ
Jacn (ไม่รู้ย่อมาจากอะไร)
ซึ่งเปลี่ยนจากการ config ด้วย xml file
ไปเป็นการ config ด้วย java file แทน

ลองดูตัวอย่าง config file
กรณีใช้ xml file
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="messageSvr" class="test.MessageImpl"/>

<bean id="greeting" class="test.Greeting">
<property name="messageSvr" ref="messageSvr"/>
</bean>
</beans>


กรณีใช้ Jacl
package test;

import bran.spring.jacn.AbstractJacnConfigFile;

public class Beans extends AbstractJacnConfigFile {

static Greeting greeting;
static MessageImpl messageImpl;

public void wire() throws Exception {
greeting.setMessageSvr(messageImpl);
}

}


เวลาเรียกใช้งาน ApplicationContext
เราจะเรียกใช้ผ่านทาง bran.spring.jacn.JacnApplicationContext แทน
ตามตัวอย่างนี้
public class TestJacn extends TestCase {

ApplicationContext context;

protected void setUp() throws Exception {
context = new JacnApplicationContext(test.Beans.class);
}

public void test() {
Greeting greeting = (Greeting) context.getBean("greeting");
assertEquals("hello world", greeting.getMessage());
}

}


ข้อดีสำหรับ Jacn ก็คือ แทนที่จะเขียน xml โดยตรง เราก็เปลี่ยนมา
เขียน java แทน ซึ่งทำให้เราสามารถใช้ feature code completion
เข้ามาช่วยเขียน รวมทั้งใช้ javac ช่วยในการตรวจสอบ validation ได้ด้วย

การทำงานภายในของ Jacn ที่น่าสนใจก็คือ
คนเขียนเขาใช้วิธีการแปลง class file ที่เราเขียน ให้เป็น DOM
ซึ่งอยู่ในรูปแบบเดียวกับที่ spring ใช้ในอ่าน xml file
เมื่อได้ DOM มาแล้วก็ค่อยส่งต่อให้ spring เป็นผู้ resolve และทำ dependency injection ต่อไป

ส่วนวิธีการที่ใช้ implement การแปลง class file ไปเป็น DOM นั้น
เขาใช้ ASM เข้ามาช่วยทำ โดย Implement ClassVisitor ขึ้นมา

Related link from Roti

Tuesday, November 08, 2005

NASA & Eclipse RCP


Did you know that one of the mission-critical operations tools for the Spirit and Opportunity Mars rover mission is built on the Eclipse Rich Client Platform? NASA has also chosen the Eclipse Rich Client Platform as the framework for many of the mission operations tools that will be used on the 2007 Mars lander and 2009 Mars rover missions.


อ่านรายละเอียดใน blog ของ Scott Schram's Blog

Related link from Roti

Friday, November 04, 2005

ri18n

คุณยุทธถามมาเรื่อง ruby module ri18n
ที่ใช้ทำ localization
ได้เรื่องเลย เรื่องนี้เป็นเรื่องที่ผม postpone มานานแล้ว
เห็นทำ app ภาษาไทยได้ก็พอใจแล้ว ยังไม่ได้มองกรณีทำหลายภาษาเลย

หลังจากสั่ง

gem install ri18n


ก็ได้ source code มาจำนวนหนึ่ง
ตัวเอกสารไม่ต้องพูดถึง มีน้อยมาก
การใช้งานพึงแกะจาก source code เป็นหลัก

Note: ผมแกะมาพอให้เริ่มใช้งานได้นะครับ
ยังมีรายละเอียดอีกจำนวนหนึ่งที่ยังไม่ได้ดู
เช่น catalog, interpolation, phurals form


การใช้งาน ri18n กับ RoR
  • Environment Setup
    แก้ไข file config/environment.rb
    เพิ่มเนื้อหาส่วนนี้ลงไป
    # Include your app's configuration here:
    require 'i18nservice'

    I18nService.instance.po_dir="#{RAILS_ROOT}/i18n"

    ให้สังเกตว่าเราจะมี directory ที่ชื่อ i18n ที่จะไว้ใช้เก็บ
    po file (translation file)
    ข้อสังเกตอีกอัน ก็คือ I18nService ใช้ sigleton pattern
    ดังนั้นเราจึง access ผ่าน instance attribute

  • การแสดง message ใน views
    ในการ render output ใน rhtml
    แทนที่จะเขียนออกไปตรงๆ เราจะใช้สัญญลักษณ์ _('msgid') แทนที่ msg ที่เราต้องการ
    <html>
    <head>
    <title>hello</title>
    </head>
    <body>
    <p>
    <%= _('hello') %>
    </p>
    </body>
    </html>


  • เพิ่ม Task i18n ใน Rakefile
    โดย ri18n ได้เตรียม Rake Task ที่ชื่อ gettext ไว้แล้ว
    gettext นี้มีหน้าที่ scan file เพื่อหา msg ที่ต้องการทำ translation
    เพื่อทำการ genereate .po file ให้เรา
    ...
    require 'gettext'
    require 'i18nservice'

    ...
    RAILS_ROOT=File.dirname(__FILE__)
    ...

    desc "generate po (i18n) file"
    Rake::GettextTask.new { |gettext|
    gettext.source_files = FileList['**/*.rhtml']
    gettext.new_langs = ['th','en']
    I18nService.instance.po_dir="#{RAILS_ROOT}/i18n"
    }

    desc "create directory for i18n po file"
    task :mki18ndir do
    Dir.mkdir("i18n") unless File.exists?("i18n")
    end

    task :i18n => [:mki18ndir, :gettext]

    จะเห็นได้ว่าเรามีการ config task class ที่ชื่อ Rake::GettextTask
    โดยระบุว่าให้ scan message ที่ต้องการแปลงจาก file ที่มีนามสกุลเป็น rhtml
    และระบุด้วยว่า ให้สร้าง po file สำหรับ language 2 ภาษา ก็คือ thai (th) กับ english (en)
    Note: ผมทำแบบ struts ก็คือ ใน views ระบุเป็น message id
    เราสามารถทำอีกแบบได้ ก็คือ ใน views เป็น english แล้วทำเฉพาะส่วนแปลเป็น th


  • generate po file
    โดยใช้คำสั่ง rake i18n
    จะได้ file ออกมา 2 file ก็คือ th.po กับ en.po

  • ทำการแปล
    โดยการ add message ที่ต้องการลงไปใน po file
    ขั้นนี้ผมยังไม่ได้ลองเรื่อง encoding เลย
    ที่ทดลองทำก็คือแค่ใช้ editor ที่ save encoding เป็น utf-8 ได้
    ทำการ edit file เพิ่มเนื้อหา ภาษาไทย ลงใน file th.po
    ส่วน file en.po ก็เพิ่มส่วน ภาษาอังกฤษ ลงไป
    ตัวอย่าง file th.po
    Ôªø# SOME DESCRIPTIVE TITLE.
    # Copyright (C) YEAR Free Software Foundation, Inc.
    # This file is distributed under the same license as the PACKAGE package.
    # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
    #
    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION\n"
    "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    "POT-Creation-Date: 2002-12-10 22:11+0100\n"
    "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    "Language-Team: LANGUAGE <LL@li.org>\n"
    "MIME-Version: 1.0\n"
    "Content-Type: text/plain; charset=ASCII\n"
    "Content-Transfer-Encoding: 8bit\n"
    "Plural-Forms: nplurals=2; plural=n == 0;\n"

    msgid "hello"
    msgstr "สวัสดีครับ"

    Note: ผมยังไม่ได้ดูเรื่อง header ใน po file เลย
    ใช้ค่า default ไปก่อน (ซึ่งมันก็ทำงานได้)


  • การเลือกใช้ language
    เราสามารถเลือกใช้ language ที่ต้องการได้โดย
    การสั่ง I18nService.instance.lang='th'
    โดยในตัวอย่างนี้ผมใส่ไว้ใน controller โดย check ว่ามี
    parameter ที่ชื่อ lang pass มาหรือไม่ ถ้าไม่มีก็ให้ default เป็น thai
    class MainController < ApplicationController
    def index
    lang = params[:lang]
    lang = "th" if lang == nil
    I18nService.instance.lang=lang
    end
    end



ทำเสร็จแล้ว ก็ start server ทดสอบได้เลย
ผลลัพท์ที่ได้ก็เป็นที่น่าพอใจ


ส่วนประเด็นที่เหลือ ก็อาจจะเป็นการเรื่องการจัดการ po file
กรณีที่มีการแก้ไขเพิ่มเติม views แล้วเราสามารถ regenerate เป็นแบบ partial
ได้ไหม (อันนี้ยังไม่ได้ลองแกะดู แต่เท่าที่ลองสั่งมันมี message แปลก display ขึ้นมา)

Related link from Roti

การแสดง source code ใน Post

คุณ ohm ถามว่าเราจะแสดง xml tag, html tag ใน
post ของ blogger อย่างไร
เผอิญไม่มี email ของคุณ ohm ก็เลยขอตอบในที่นี้แล้วกัน

เริ่มแรกสุดก็คือเราจะแสดงผล xml หรือ html tag ใน post ได้อย่างไร
สมมติเราต้องการแสดงผลแบบนี้
<x>
<y>hello</y>
</x>

เวลาเราเขียน blog ก็ต้องทำการ encoding ให้เรียบร้อยก่อน
แล้วค่อยใส่ไว้ใน pre tag
หน้าตาของ post ที่จะแสดงผลเป็น xml แบบข้างบน ก็จะเขียนแบบนี้
<pre>&lt;x&gt;
&lt;y&gt;hello&lt;/y&gt;
&lt;/x&gt;</pre>


ซึ่งวิธีการทำแบบข้างบนนี้ ผมเขียน script find & replace เข้าไป editor
แล้วก็ทำ menu ไว้ เวลาจะแปลงก็เลือก highlight text ส่วนที่ต้องการ
แล้วก็เรียกใช้ script

ขั้นที่ 2 ก็คือ เกิดเราต้องการ syntax highlight หล่ะ
เราจะทำอย่างไร อันนี้ต้องหา tool เข้ามาช่วย
ผมเคย post วิธีที่ผมใช้แล้ว ลองอ่านดูนะครับ

ขั้นตอนเวลาผม post ส่วนที่เป็น source code
ผมก็จะเขียน code ลงใน jedit ก่อน
จากนั้นก็เรียกใช้ action จาก plugin ที่ผมเขียนขึ้น
ซึ่งมันจะทำการแปลง source code ให้อยู่ในรูป syntax highlight
จากนั้นก็ใส่ไว้ใน clipboard
เพื่อที่ผมจะได้ใช้คำสั่ง copy จาก clipboard ลง file ที่ต้องการได้เลย

กรณีที่คุณ ohm ไม่ถนัด java
ผมเคยใช้ perl module ที่ชื่อ vimcolor
ตัวนี้มันจะใช้ vim syntax feature ในการทำ syntax highlight

Note: กรณีต้องการ source code ของ plugin
ก็ให้ทิ้ง mail ไว้ครับ จะได้ส่งไปให้

Related link from Roti

Higher Order Messaging

อ่านเจอเรื่อง Higher Order Messaging ของ Mistaeks I hav Made
ก็เลยมาลองทำดูบ้าง เพื่อจะได้ซาบซื้งยิ่งขึ้น

ขั้นแรก ก็ต้องรู้ก่อนว่า Higer Order Message มีนิยามว่าอะไร
ถ้าตามไปดูใน paper ที่เขาอ้าง ก็จะพบประโยคนี้
Higher Order Messages allow user-defined message dispatch mechanism to be expressed using an optimally compact syntax that is a natural extension of plain messaging and also have a simple conceptual model.

คนอื่นเป็นไงไม่รู้ แต่ผมอ่านแล้ว ก็มึน
ดู definition อันนี้บ้าง
A higher order message is a message that takes another message as an "argument". It defines how that message is forwarded on to one or more objects and how the responses are collated and returned to the sender. They fit well with collections; a single higher order message can perform a query or update of all the objects in a collection.

ฟังดูง่ายขึ้นอีกนิด
ประมาณว่าเกี่ยวกับ การ update หรือ query Collections
โดยใช้กลไก forward message

ลองดูตัวอย่าง จะเห็นภาพกว่า
สมมติว่าเรามี Array ของ Class Person
ซึ่งภายในประกอบด้วย object จำนวนหนึ่ง
แล้วเราต้องการค้นหาชื่อเฉพาะบุคคลที่ยังเป็น Toddler (เด็กหัดเดิน)
(ตัวอย่างนี้ ได้รับแรงบันดาลใจจากลูกชาย)
ถ้าเขียนโปรแกรมแบบตรงเผงเลย ก็จะเขียนประมาณนี้
names = Array.new
for person in persons
if person.toddler? then
names << person.name
end
end
puts names


บรรดากูรูทั้งหลายเห็น code นี้แล้วบอกว่า ยาวไป
เห็นแล้วไม่เกิดแรงบันดาลใจ
ก็เลยไปทำการประดิษฐ์ block เข้ามาช่วย
code ข้างบนก็เลยกลายเป็น
puts persons.select { |p| p.toddler? }.collect {|p|  p.name} 


กูรูคนถัดไปเห็นแล้ว ก็บอกว่า block มันเกะกะ
อยากได้อะไรที่มันสร้างแรงบันดาลใจได้มากกว่านี้
ก็เลยเกิด Higher Order Message ขึ้น
ซึ่ง code ที่ได้จะเป็นดังนี้
puts persons.where.toddler?.extract.name


ตอนนี้ก็เลยเป็นหน้าที่ของโปรแกรมเมอร์อย่างเรา
ที่จะต้องเรียนรู้แล้วว่า HOM นั้น implement อย่างไร
จากตัวอย่างนี้ ผม implement ดังนี้ (จริงๆแล้ว ก็ลอกๆเขามานั่นแหล่ะ)

เริ่มด้วยสร้าง base class ก่อน
class HigherOrderMessage
def HigherOrderMessage.is_vital(method)
return method =~ /__(.+)__|method_missing/
end

for method in instance_methods
undef_method(method) unless is_vital(method)
end

def initialize(handler)
@handler = handler
end
end


จริงๆแล้ว class นี้จะเขียนแค่นี้ก็ได้
class HigherOrderMessage
def initialize(handler)
@handler = handler
end
end

ที่เขาใส่เพิ่มเข้ามา คงไว้ playsafe,
for loop ที่เขาใส่เข้ามาจะทำหน้าที่ block ไม่ให้ object ภายนอก
มองเห็น methods ของ HigherOrderMessage
ยกเว้นแต่ method ที่ชื่อ __send__ กับ __id__
ใครไม่รู้จัก __send__ ลองดูตัวอย่างนี้
[1,2].__send__('length') #=> 2
[1,2].length #=> 2

2 ประโยคข้างบนนั้นเท่าเทียมกัน

กลับมาดูกันต่อว่า where นั้น implement อย่างไร
class Where < HigherOrderMessage
def method_missing(id, *args)
return @handler.select {|o| o.__send__(id, *args)}
end
end

ก่อนอธิบายการทำงาน ต้องดูการนำไปใช้ก่อน
เนื่องจากเราต้องการให้ทำ persons.where ได้
แสดงว่า Array class ต้องมี method ที่ชื่อ where อยู่
ดังนันเราก็เลย declare mixin ขึ้นที่ Array class
class Array 

def where
return Where.new(self)
end

end


ซึ่งก็ใช้ได้ในตัวอย่างเรา
แต่ถ้าต้องการ generic กว่านั้นก็ควรไป declare ใน module Enumerable แทน
module นี้เป็นแหล่งรวม code ที่เกี่ยวกับ iterate collection
(ซึ่ง Array จะ include Enuerable อีกที)
module Enumerable 

def where
return Where.new(self)
end

end


การทำงาน ก็คือถ้ามีการเรียกใช้ where เมื่อไรก็ตาม ก็จะมีการ return Where object
กลับไป โดยภายใน where object จะมี handler ซึ่งเก็บ array object ไว้
และเมื่อมีการเรียกใช้ method อะไรก็ตาม (ดักโดยใช้กลไก method missing ของ ruby)
ก็จะทำการ loop collection นั้น
และ forward ชื่อ method ที่เข้ามาให้กับ object ใน collections นั้นอีกที
ในกรณีของเรา method toddler? ก็จะถูกใช้เป็นตัว screen ใน
select method

ส่วน extract ก็เขียนง่ายๆโดยใช้ collect ช่วย
class Extract < HigherOrderMessage
def method_missing(id, *args)
return @handler.collect {|o| o.__send__(id, *args)}
end
end


หลังจากลอง implement ดู แล้วย้อนกลับไปอ่าน definition อีกที
ก็พบว่า เริ่มซืมเข้าหัวมากขึ้นแล้ว

Related link from Roti

Thursday, November 03, 2005

Implement Bound properties with AspectJ

วันนี้อ่านเจอ post ของ damnhandy
ที่เขียนถึง JavaBean Aspect
โดยเขาตั้งโจทย์ว่า
เขาต้องการใช้ JGoodies เป็นตัวกลางในการ
bind hibernate persistent class เข้ากับ Swing component
ซึ่ง JGoodies Require ว่า class ที่ต้องการ bind
เข้ากับ Swing component ต้องเป็น Java Bean ที่ support
Bound properties (สามารถ add propertyChangeListener ได้)

ซึ่ง solution ที่เขาเขียนนั้นเขาใช้ JBoss AOP
วันนี้ผมก็เลยทดลองใช้ AspectJ implement ดูว่า
AspectJ สามารถทำ feature ประเภทนี้ได้ไหม

solution ของ damnhandy นั้น มี feature
พอสมควร มีการใช้ annotation เข้ามาช่วยด้วย
ผมจะตัด feature บางอันออกก่อน เพราะเราจะทำแค่
ทดลองชิม AspectJ ว่ามีรสประมาณไหนก่อน

โจทย์ที่จะทดลองทำ จะมีแค่
ในกรณีที่เรามี POJO class อยู่แล้ว
และต้องการ add feature bound properties เข้าไป
โดยใช้ AspectJ เราจะต้องทำได้อย่างไร

ในตัวอย่างของ AspectJ มีตัวอย่างเรื่องนี้อยู่แล้ว
ในหัวข้อ BoundPoint aspect
แต่เขาทำในลักษณะของ per class, per method
นั่นก็คือ ต้อง declare ทุก class, ทุก method ที่ต้องการ add bound properties
ผมก็เลยทดลอง declare ให้เป็น Generic มากขึ้นดู

สมมติว่าเรามี Class ที่ชื่อ Person
package domain;

public class Person {
private Long id;
private String name;
private String addresss;

public String getAddresss() {
return addresss;
}
public void setAddresss(String addresss) {
this.addresss = addresss;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}


เริ่มด้วย สร้าง interface ที่ชื่อ IValueObject ขึ้นมาก่อน
package domain;

import java.beans.PropertyChangeListener;

public interface IValueObject {

public void addPropertyChangeListener(
PropertyChangeListener listener);

public void addPropertyChangeListener(
String propertyName,
PropertyChangeListener listener) ;

public void removePropertyChangeListener(
PropertyChangeListener listener) ;

public void removePropertyChangeListener(
String propertyName,
PropertyChangeListener listener) ;

}


เตรียม Abstract Aspect ที่ implement method ในส่วนของ IValueObject
package domain;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import org.apache.commons.beanutils.PropertyUtils;

public abstract aspect ValueObjectChangeAspect {

public PropertyChangeSupport IValueObject.support =
new PropertyChangeSupport(this);

public void IValueObject.addPropertyChangeListener(
PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}

public void IValueObject.addPropertyChangeListener(
String propertyName,
PropertyChangeListener listener) {
support.addPropertyChangeListener(propertyName, listener);
}

public void IValueObject.removePropertyChangeListener(
PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}

public void IValueObject.removePropertyChangeListener(
String propertyName,
PropertyChangeListener listener) {
support.removePropertyChangeListener(propertyName, listener);
}

abstract pointcut setter(IValueObject vo);

void around(IValueObject vo): setter(vo) {
String tmp = thisJoinPointStaticPart.getSignature()
.getName().substring("set".length());
String propName = tmp.substring(0, 1).toLowerCase()
+ tmp.substring(1);
try {
Object newValue = thisJoinPoint.getArgs()[0];
Object oldValue = PropertyUtils.getProperty(vo, propName);
proceed(vo);
vo.support.firePropertyChange(propName, oldValue, newValue);

} catch (Exception e) {
e.printStackTrace();
}
}

}

Aspect ValueObjectChangeAspect จะ delcare java.beans.PropertyChangeSupport instance
ที่จะไว้ใช้ช่วยทำ bound properties
โดยเราจะ declare pointcut ที่ชื่อ setter ไว้
ให้สังเกตุว่า pointcut นี้ยังเป็น abstract อยู่ นั่นก็คือว่ายังไม่มีการระบุให้ชัดว่า
จะเอาไปใช้กับ method อะไร,ที่ไหน
ในส่วนของ advice around จะ trig เมื่อ pointcut setter
ถูกเรียกใช้ ขั้นตอนการทำงาน ก็จะทำการหาค่า oldValue กับ newValue
(ใช้ common BeanUtils ของ apache เข้ามาช่วย)
เพื่อที่จะไว้ firePropertyChange
Note: โปรดระวัง code นี้ไม่สามารถใช้กับ production ได้
เพราะว่า ยังไม่ได้คิดว่า ถ้าเกิด exception แล้วจะทำอะไร


ถึงขั้นนี้ เราก็มี abstract aspect เตรียมไว้แล้ว ขั้นต่อไป ก็เป็นการ
ทำ Aspect PersonChangeAspect ที่เป็นการ implement IValueObject เข้ากับ Person Class
package domain;

public aspect PersonChangeAspect extends ValueObjectChangeAspect {

declare parents: Person implements IValueObject;

pointcut setter(IValueObject vo): call(void Person.set*(*)) && target(vo);

}

ข้างใน aspect ก็จะมีการ declare ว่าให้ Person ไป implement IValueObject เสีย
จากนั้นก็ declare pointcut setter ว่าให้ไปดักการทำงานของ
ทุก method ที่มีชื่อขึ้นต้นว่า set ใน class Person

สุดท้ายก็ทดสอบโปรแกรมดูดังนี้
package domain;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class TestRun implements PropertyChangeListener {

/**
* @param args
*/
public static void main(String[] args) {
// initialize object
Person p1 = new Person();
p1.setName("x");

IValueObject vo = (IValueObject) p1;
vo.addPropertyChangeListener(new TestRun());

p1.setName("y");
}

public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
System.out.println(evt.getOldValue());
System.out.println(evt.getNewValue());
}

}


Output ที่ได้ ก็เป็นดังนี้

name
x
y

Related link from Roti

Wednesday, November 02, 2005

ทำไมไม่ใช้ RoR ในโปรเจคใหญ่

ผมเคย post ไว้ว่า จะเลือกใช้ RoR ในกรณีที่ project เล็กๆ
แล้วก็มี comment ถามเข้ามาว่า ทำไมถึงเลือก choice นั้น
ใช้เหตุผลด้านไหนเป็นตัวตัดสิน

วันนี้อ่านเจอ blog ของ Glenn Vanderburg
หัวข้อเรื่อง The Right Team for Rails
เขาเขียนได้ตรงใจทีเดียว

A good Rails team will consist of programmers who know what they should be doing, and usually have the discipline to do it.


The wrong Rails team is one that doesn’t understand those principles and practices. The fact that Rails makes things easy won’t be enough. In my experience, such teams expend amazing effort and ingenuity to do the wrong thing.


กรณีของผม ที่ผมวางแนวทางไว้ก็คือ
ถ้าเป็น developer มือใหม่ เข้ามาในบริษัทฯ
ผมจะยังไม่ให้ใช้ RoR เลย
จะให้หัดใช้ mvc แบบ Struts ให้เข้าใจ framework เสียก่อน
,ใช้ hibernate ได้อย่างไม่เก้อเขิน
,มอง springFramework เป็นเหมือนสวนหลังบ้าน (เปรียบเทียบเวอร์ไปนิด)
ให้ดี ก็ต้องใช้ tapestry เป็นด้วย (จะได้ซาบซื้งกับหลักการ reuse component)
แล้วก็ผ่านการพัฒนา project ด้วย java มาอย่างน้อยสัก 1 project ก่อน
คราวนี้แหล่ะที่พร้อมจะใช้ RoR ได้แล้ว
(ความหมายของคำว่า "พร้อม" นี้
หมายถึงพร้อมที่จะร่วมพัฒนาระบบงานใหญ่ๆ พร้อมๆกันเป็น team)

แก้ไขเพิ่มเติม ลืมบอกลักษณะของโปรเจคไป
โปรเจคที่ผมทำส่วนใหญ่เป็น web application ทีเป็นพวก Data Entry
มี form เยอะแยะเต็มไปหมด (ส่วนใหญ่จะ replace terminal app หรือ client-server
app ของเดิม

Related link from Roti

Tuesday, November 01, 2005

รูปจาก trip เที่ยวใต้

รูปนี้เป็นรูปอุปกรณ์จัดปลาหมึกของชาวบ้าน
ที่ปราณบุรี
(ใช้หอยร้อยเชือกเป็นสายยาว
เวลาใช้ก็ปล่อยลงไปในน้ำ
เมื่อกู้ขึ้นมา ปลาหมึกจะติดอยู่ในเเปลือกหอย)


ส่วนรูปนี้เป็นรูปปูสูงอายุ ถ้าทางจะอายุมากเชียวหล่ะ

Related link from Roti

Thursday, October 27, 2005

พักการ post

พาลูกชายกับพี่เลี้ยงเด็ก(แม่เด็ก)
ไปตลุยภาคใต้มา
8 วัน 7 คืน 1660 km.

กลับมา ก็พบว่า adsl ที่บ้านต่อไม่ได้
อาการลงแดง(อยาก net) ก็กำเริบทันที
แต่หลังจากไม่ได้ใช้ net มา 2 อาทิตย์
ตอนนี้อาการอยาก net เริ่มดีขึ้นแล้ว ไม่ทุรนทุรายแล้ว

ส่วน adsl ที่มีปัญหา เกิดจากสายโทรศัพท์มี noice รบกวน
มากเกินไป connection เลยมีปัญหา

ช่วงนี้เลยขอพักการ post ชั่วคราว

Related link from Roti

Wednesday, October 12, 2005

Link น่าสนใจ (2005-10-12)

Related link from Roti