Pages

วันพุธ, มีนาคม 02, 2554

Vavar ปะทะ Java Swing #4 : EDT + MVC

มาถึงตอนที่ 4 ขอแอบบ่นขั้นเวลาหน่อยครับ ...

เป็นธรรมดาที่เวลาเขียน Application ใดๆ ก็ตามที่ต้องมี User Input  ...
สิ่งที่ต้องตามมาก็คือ User Interface ... แน่นอนว่า คงไม่มีใครอยากเขียน code เยอะๆ ...
เพื่อแสดง power แน่นอน ... แต่สิ่งที่ต้องมาคู่กับการ Design User Interface นั้น ...
จำเป็นที่จะต้องสนใจ input + output ของ process นั้นๆ ด้วย ....
เพราะหากเราคำนึงถึงเพียงแค่การแสดงผล ให้กับ user บนหน้าจอ คงจะไม่ถูกต้องนัก ...

มีตลกร้ายอยู่เรื่องนึง สมัยทำ Web Application เมื่อนานมาแล้ว
ด้วยความที่ Web Application นั้น เป็นการทำ Document Management System...
สิ่งที่สำคัญในระบบนั้นคือ

การเก็บเอกสารที่สามารถค้นหาให้ได้ง่ายที่สุดเท่าที่จะทำได้ ...

ไม่ว่าจะเป็น คำพ้องรูป หรือคำพ้องเสียง หรือแม้แต่
ค้นหาจาก "เนื้อข้อมูล" ภายในเอกสาร ก็จะต้องพึงกระทำได้ ...
สิ่งที่เกิดขึ้นจาก Output ที่กล่าวไปเบื้องต้น นั้น ... คือ กระบวนการ Indexing ...
ซึ่งถ้าหาก Design มาไม่ดี ... ระบบโดยรวมก็จะช้าทั้งหมด .....

และสิ่งที่ไม่คาดฝันก็เกิดขึ้น ... อาจจะเพราะด้วย Design ที่ไม่คำนึงถึง Concurrent ที่เกิดจาก
การ input มาจากหลายช่องทาง ( Multi-User ) จึงทำให้ Index Repository ... ล่มสลาย ...
ต้อง ทำการสร้าง index ให้กับเอกสารที่นำเข้าไปแล้วใหม่ทั้งระบบ ... เจ็บกันไป ...

นี่คือตัวอย่างของการคิดแบบง่ายๆ ที่เกิดขึ้นจากการออกแบบ ที่ไม่ได้คำนึงถึง concurrent
ที่เกิดขึ้นในระบบ ... วิธีแก้ มีหลายวิธี ไม่ว่าจะเป็นการทำ Thread Queue หรือแม้แต่การแยก
Indexing Service ไปไว้ในการทำงานทีหลัง ... เท่านี้ก็จะสามารถทำให้ระบบมีประสิทธิภาพมากขึ้นได้ ...

ในการทำ UI ใน Java ก็เช่นกัน ... ใน Java Desktop จะมี Thread อยู่ตัวหนึ่่งที่ชื่อว่า

Event Dispatcher Thread 

หรือที่เรียกสั้นๆ ว่า EDT .... ในส่วนของ EDT นี้ จะเกิดขึ้นทุกครั้งที่มีการส่ง
Event ที่เกิดขึ้นจาก User Input ที่เกิดขึ้นกับ UI ไปหา Listener ที่เราทำการ Add ไว้อยู่ทั้งหมด ...
ซึ่งการเขียน Listener จึงต้องมีความระมัดระวังในการเขียนค่อนข้างมาก เนื่องจากว่าเราจะต้องรู้
"พฤติกรรม" ที่ Listener นั้นๆ จะได้รับ เมื่อเกิดเหตุการณ์ XX , YY , หรือ ZZ เพราะไม่เช่นนั้น
การใส่ Listener เยอะๆ ไป จะทำให้เราไม่สามารถ กำหนดต้นทางของข้อมูลได้เลยว่า มาจากจุดไหน
หรือแม้แต่กำหนด "พฤติกรรม" ที่เราต้องการจะทำได้

ยกตัวอย่างเช่น

ItemListener กับ ActionListener

สิ่งที่แตกต่างกัน ระหว่าง Listener 2 ตัวนี้คือ ...

ItemListener จะทำการ detect ทุึกครั้งเมื่อ Item State นั้นมีการเปลี่ยน  ....
การเปลี่ยนในที่นี้หาก Component ที่ต้องการเพิ่ม Listener นั้น มีเพียง 1 Item ..
เราก็สามารถ trigger Event นั้นได้อย่างง่ายดาย เช่นเดียวกับ ActionListener ที่มีการ "กระทำ"
กับ Component นั้นๆ แต่เมื่อ Component นั้นมี Item มากกว่า 1 items ... สิ่งที่เกิดขึ้นตามมาก็คือ

Item State Event จะถูกส่งมา 2 ครั้ง นั่นคือ

1. Item เก่า ถูก Deselect
2. Item ใหม่ ถูก Select

ซึ่งถ้าเราเขียน code ตรงจุดดนี้ไม่ดี ... หรือการส่งค่า input บางอย่างไปยัง ฐานข้อมูล
ก็จะทำให้เกิด Duplicate Record ได้ง่าย .... ซึ่งจะแตกต่างจาก ActionListener ที่
เราต้อง Handle เพียง 1 Event วิธีแก้ไขง่ายที่สุดคือ
"เลือก implement Listener เฉพาะพฤติกรรมที่เราต้องการ"

การทำงาน ใน EDT ก็ยังมีความต้องระวังอีกจุดหนึ่งนั่นคือ
การนำ "Long-Session Process" ไปประมวลผลใน EDT .... ยกตัวอย่างเช่น
การที่ user กดปุ่ม Save แล้วทำการ Commit ข้อมูลลง Database บนสภาวะ เนตเวิร์คกากๆ
สิ่งที่เกิดขึ้นก็คือเมื่อเราทำการประมวลผลอะไรนานๆ บน EDT จะทำให้ ...

จาวาค้างส์

ดังนั้นสิ่งที่ทำให้ค้างส์ ไม่ใช่ จาวา แต่เป็นเพราะคนเขียน นะจ๊ะ ... จุบุ จุบุ ...
แต่ก็ไม่ต้องแปลกใจว่าทำไม ไม่มีใครคอยบอก / แนะนำ ว่าไม่ควรทำบน EDT ....
ความรู้นี้ก็ได้มากจาก Best Practices ต่างๆ ช่วงหลังๆ ที่ SUN มาเอาจริงเอาจัง
กับ Java-Desktop แล้ว ... ซึ่ง App เก่าๆ ไม่ต้องพูดถึง .... วิ่งอยู่บน EDT ซะเยอะ ... ก็ค้างส์กันไป ...

วิธีแก้แบบง่ายที่สุดในการลด Long-Session Process บน EDT คือการ "แตก" Thread
ให้มาทำงานนอก EDT แล้วหากต้องการ Update Event ใดๆ ที่เกี่ยวกับ UI ให้ใช้
SwingUtilities.invokeLater ( Runable runable )
สิ่งที่ขัดใจจอร์จอีกอย่างเวลาทำการแก้ code ที่เป็น Legacy นั่นคือ
การใช้ UI มาทำการ Handle Process ที่เกี่ยวข้องกับการประมวลผล ...
ยกตัวอย่างเช่น

เอา State ของ Checkbox มากำหนดเงื่อนไขของ Boolean แบบ True / False ซึ่งดูๆ
ไปแล้วอาจจะ Common มากในการที่จะบอกว่า "ก็ disabled ไปสิ เค้าก็แก้ไม่ได้แล้ว"
แต่การ Disabled เป็นเพียงการแก้ปัญหาที่ปลายเหตุเอามากๆ ถ้าเกิดเจอ User เกรียนๆ
ที่ Trigger UI ไปมา แล้วใน Trigger นั้นเป็น การ Query Data 10k Record พร้อมกับต่อ Database ....
ซึ่งถ้าเรามีการ Handle ข้อมูลโดยแบ่งเป็น MVC ที่ดีแล้ว ... ปัญหาดังกล่าวจะขี้เล็บมากๆ
เมื่อเทียบกับการปล่อยมันเอาไว้ แล้วเกิดปัญหาที่รับมือได้ยากทีหลังนั้น
มันค่อนข้างจะคุ้มค่ามากกว่าไม่ใช่เหรอ ?