| Topic 51 | ชุดเครื่องมือเริ่มต้นแบบ Pragmatic |
|---|
อารยธรรมก้าวหน้าไปได้ด้วยการขยายจำนวนของ
การกระทำสำคัญๆ ที่เราทำได้โดยไม่ต้องคิด
Alfred North Whitehead
ย้อนกลับไปตอนที่รถยนต์ยังเป็นของแปลกใหม่ คำแนะนำการสตาร์ทรถ Model-T Ford นั้นยาวกว่าสองหน้ากระดาษ แต่กับรถยนต์สมัยใหม่ คุณแค่กดปุ่ม ขั้นตอนการสตาร์ทเป็นไปโดยอัตโนมัติและผิดพลาดไม่ได้ คนที่ทำตามรายการคำแนะนำอาจทำให้เครื่องยนต์ท่วมได้ แต่ระบบสตาร์ทอัตโนมัติจะไม่มีวันทำแบบนั้นครับ
ถึงแม้ว่าการพัฒนาซอฟต์แวร์จะยังอยู่ในยุค Model-T แต่เราไม่มีปัญญาไปนั่งอ่านคำแนะนำสองหน้ากระดาษซ้ำแล้วซ้ำเล่าสำหรับงานทั่วไปได้หรอกครับ ไม่ว่าจะเป็นขั้นตอนการ build และ release การทดสอบ เอกสารโปรเจกต์ หรืองานที่ต้องทำซ้ำๆ อื่นใดในโปรเจกต์ มันต้องเป็นไปโดยอัตโนมัติและทำซ้ำได้บนเครื่องที่มีความสามารถเพียงพอทุกเครื่อง
นอกจากนี้ เราต้องการให้เกิดความสม่ำเสมอและความสามารถในการทำซ้ำได้ในโปรเจกต์ด้วย ขั้นตอนที่ทำด้วยมือปล่อยให้ความสม่ำเสมอขึ้นอยู่กับดวง ความสามารถในการทำซ้ำก็ไม่ได้รับประกัน โดยเฉพาะถ้าบางแง่มุมของขั้นตอนนั้นเปิดให้แต่ละคนตีความต่างกันได้ครับ
หลังจากที่เราเขียน The Pragmatic Programmer ฉบับพิมพ์ครั้งแรกเสร็จ เราอยากสร้างหนังสือเพิ่มเติมเพื่อช่วยให้ทีมพัฒนาซอฟต์แวร์ได้ดีขึ้น เราคิดว่าควรเริ่มจากจุดเริ่มต้น นั่นคืออะไรคือองค์ประกอบพื้นฐานที่สุดและสำคัญที่สุดที่ ทุก ทีมต้องการ ไม่ว่าจะใช้ระเบียบวิธี ภาษา หรือ technology stack แบบไหนก็ตาม แล้วไอเดียของ Pragmatic Starter Kit ก็ถือกำเนิดขึ้น ครอบคลุม 3 หัวข้อสำคัญที่เกี่ยวโยงกันดังนี้ครับ
- Version Control
- Regression Testing
- Full Automation
นี่คือสามขาที่ค้ำจุนทุกโปรเจกต์ไว้ มาดูกันว่าแต่ละอย่างทำงานยังไงครับ
Drive with Version Control (ขับเคลื่อนด้วย Version Control)
อย่างที่เราพูดไว้ใน Topic 19, Version Control คุณควรเก็บทุกอย่างที่จำเป็นต่อการ build โปรเจกต์ไว้ใน version control แนวคิดนี้ยิ่งสำคัญมากขึ้นไปอีกในบริบทของตัวโปรเจกต์เองครับ
ข้อแรก มันทำให้เครื่อง build เป็นแบบชั่วคราวได้ แทนที่จะมีเครื่องเก่าแก่เครื่องเดียวที่ทุกคนไม่กล้าแตะอยู่มุมออฟฟิศ [80] เครื่อง build และ/หรือคลัสเตอร์จะถูกสร้างขึ้นตามความต้องการในรูปแบบ spot instance บนคลาวด์ การตั้งค่าการดีพลอยก็อยู่ใน version control ด้วยเช่นกัน ดังนั้นการ release ขึ้น production จึงสามารถจัดการได้โดยอัตโนมัติครับ
และนี่คือส่วนสำคัญ ในระดับโปรเจกต์ version control ขับเคลื่อน กระบวนการ build และ release ครับ
| Tip 89 | ใช้ Version Control ขับเคลื่อนการ Build, Test และ Release |
|---|
กล่าวคือ build, test และการดีพลอยจะถูก trigger ผ่านการ commit หรือ push เข้า version control และถูก build ขึ้นในคอนเทนเนอร์บนคลาวด์ การ release ไปยัง staging หรือ production ก็ระบุด้วยการใช้ tag ในระบบ version control ของคุณ การ release จึงกลายเป็นส่วนหนึ่งของชีวิตประจำวันที่ไม่ต้องพิธีรีตองมากขึ้นเยอะ นี่คือ continuous delivery ที่แท้จริง ไม่ผูกติดกับเครื่อง build หรือเครื่องของนักพัฒนาคนใดคนหนึ่งครับ
Ruthless and Continuous Testing (ทดสอบอย่างไม่ปรานีและต่อเนื่อง)
นักพัฒนาหลายคนทดสอบแบบเบาๆ โดยไม่รู้ตัวว่าลึกๆ แล้วรู้อยู่ว่าโค้ดจะพังตรงไหน แล้วก็หลบเลี่ยงจุดอ่อนนั้น Pragmatic Programmer นั้นแตกต่างออกไปครับ เรา มุ่งมั่น ที่จะหาบั๊กของเรา ตอนนี้เลย เพื่อที่เราจะได้ไม่ต้องทนกับความอับอายที่คนอื่นมาเจอบั๊กของเราทีหลัง
การหาบั๊กก็คล้ายๆ กับการตกปลาด้วยแห เราใช้แหตาถี่และเล็ก (unit test) จับปลาซิว และแหตาหยาบและใหญ่ (integration test) จับฉลามตัวร้าย บางครั้งปลาก็หนีรอดไปได้ เราจึงต้องปะรูรั่วที่เราเจอ ด้วยความหวังว่าจะจับข้อบกพร่องที่ลื่นไหลไปมาในสระโปรเจกต์ของเราได้มากขึ้นเรื่อยๆ ครับ
| Tip 90 | ทดสอบแต่เนิ่นๆ ทดสอบบ่อยๆ และทดสอบแบบอัตโนมัติ |
|---|
เราอยากเริ่มทดสอบทันทีที่มีโค้ด ปลาซิวตัวจิ๋วพวกนั้นมีนิสัยแย่ๆ คือโตเป็นฉลามยักษ์กินคนได้เร็วมาก และการจับฉลามก็ยากกว่ามากครับ เราจึงเขียน unit test เยอะมาก
ความจริงแล้ว โปรเจกต์ที่ดีอาจมีโค้ดสำหรับทดสอบ มากกว่า โค้ด production ด้วยซ้ำ เวลาที่ใช้ไปในการเขียนโค้ดทดสอบนี้คุ้มค่าความพยายามครับ สุดท้ายแล้วมันถูกกว่ามากในระยะยาว และคุณก็มีโอกาสจริงๆ ที่จะสร้างผลิตภัณฑ์ที่แทบไม่มีข้อบกพร่องเลยได้
นอกจากนี้ การรู้ว่าคุณผ่านการทดสอบแล้วยังทำให้คุณมั่นใจในระดับสูงว่าโค้ดชิ้นนั้น “เสร็จ” แล้วจริงๆ ด้วยครับ
| Tip 91 | โค้ดยังไม่เสร็จจนกว่าเทสต์ทั้งหมดจะผ่าน |
|---|
การ build อัตโนมัติจะรันเทสต์ทั้งหมดที่มีอยู่ สิ่งสำคัญคือต้องมุ่งเป้าไปที่การ “ทดสอบแบบสมจริง” กล่าวคือ สภาพแวดล้อมทดสอบควรใกล้เคียงกับสภาพแวดล้อม production ให้มากที่สุด ช่องว่างตรงไหนก็เป็นแหล่งเพาะพันธุ์บั๊กได้ทั้งนั้นครับ
การ build อาจครอบคลุมการทดสอบซอฟต์แวร์หลักๆ หลายประเภท ได้แก่ unit testing, integration testing, validation และ verification, และ performance testing
รายการนี้ยังไม่ครบถ้วนสมบูรณ์แน่นอน และบางโปรเจกต์ที่เฉพาะทางก็อาจต้องการการทดสอบประเภทอื่นๆ อีกด้วย แต่มันก็เป็นจุดเริ่มต้นที่ดีให้เราครับ
Unit Testing (การทดสอบระดับยูนิต)
unit test คือโค้ดที่ทดสอบการทำงานของโมดูล เราพูดถึงเรื่องนี้ไปแล้วใน Topic 41, Test to Code unit testing คือรากฐานของการทดสอบรูปแบบอื่นๆ ทั้งหมดที่เราจะพูดถึงในหัวข้อนี้ ถ้าชิ้นส่วนแต่ละชิ้นทำงานด้วยตัวเองไม่ได้ มันก็มักจะทำงานร่วมกันไม่ได้ดีเช่นกัน โมดูลทั้งหมดที่คุณใช้ต้องผ่าน unit test ของตัวเองก่อนที่คุณจะไปต่อได้ครับ
เมื่อโมดูลที่เกี่ยวข้องทั้งหมดผ่านการทดสอบแยกส่วนของตัวเองแล้ว คุณก็พร้อมสำหรับขั้นตอนถัดไป คุณต้องทดสอบว่าโมดูลทั้งหมดใช้งานและทำงานร่วมกันทั่วทั้งระบบอย่างไรครับ
Integration Testing (การทดสอบการทำงานร่วมกัน)
integration testing แสดงให้เห็นว่าซับซิสเต็มหลักๆ ที่ประกอบกันเป็นโปรเจกต์นั้นทำงานและเข้ากันได้ดีกับตัวอื่นๆ ถ้ามี contract ที่ดีวางไว้และทดสอบมาอย่างดี ปัญหาการรวมระบบใดๆ ก็จะถูกตรวจพบได้ง่าย ไม่อย่างนั้นแล้ว การรวมระบบก็จะกลายเป็นแหล่งเพาะพันธุ์บั๊กชั้นดี อันที่จริงมันมักเป็นแหล่งที่มาของบั๊กที่ใหญ่ที่สุดเพียงจุดเดียวในระบบด้วยซ้ำครับ
integration testing ที่จริงแล้วก็แค่การขยายผลของ unit testing ที่เราอธิบายไปแล้วนั่นเอง เพียงแต่คุณกำลังทดสอบว่าซับซิสเต็มทั้งหมดทำตาม contract ของตัวเองอย่างไรครับ
Validation and Verification (การตรวจสอบความถูกต้องและยืนยันผล)
ทันทีที่คุณมี user interface หรือ prototype ที่รันได้จริง คุณต้องตอบคำถามที่สำคัญที่สุดข้อหนึ่งให้ได้ คือผู้ใช้บอกคุณว่าพวกเขาต้องการอะไร แต่นั่นคือสิ่งที่พวกเขาจำเป็นต้องมีจริงๆ หรือเปล่า
มันตอบโจทย์ functional requirement ของระบบไหม เรื่องนี้ก็ต้องทดสอบด้วยเช่นกันครับ ระบบที่ไม่มีบั๊กเลยแต่ตอบคำถามผิดข้อก็ไม่ค่อยมีประโยชน์เท่าไหร่ ต้องตระหนักถึงรูปแบบการใช้งานของ end-user จริงๆ และมันแตกต่างจากข้อมูลทดสอบของนักพัฒนาอย่างไร (ดูตัวอย่างเรื่องราวเกี่ยวกับแปรงทาสี ที่นี่ )
Performance Testing (การทดสอบประสิทธิภาพ)
การทดสอบประสิทธิภาพหรือการทดสอบภายใต้ความกดดัน (stress testing) ก็อาจเป็นแง่มุมสำคัญของโปรเจกต์ได้เช่นกันครับ
ลองถามตัวเองว่าซอฟต์แวร์ตอบโจทย์ข้อกำหนดด้านประสิทธิภาพภายใต้สภาวะจริงหรือเปล่า ด้วยจำนวนผู้ใช้ การเชื่อมต่อ หรือธุรกรรมต่อวินาทีตามที่คาดไว้ มันขยายสเกลได้ไหมครับ
สำหรับบางแอปพลิเคชัน คุณอาจต้องใช้ฮาร์ดแวร์หรือซอฟต์แวร์ทดสอบเฉพาะทางเพื่อจำลองโหลดให้สมจริงครับ
Testing the Tests (ทดสอบตัวเทสต์เอง)
เพราะเราเขียนซอฟต์แวร์ที่สมบูรณ์แบบไม่ได้ จึงตามมาว่าเราก็เขียนซอฟต์แวร์ทดสอบที่สมบูรณ์แบบไม่ได้เช่นกัน เราต้องทดสอบตัวเทสต์เองด้วยครับ
ลองนึกภาพชุด test suite ของเราเป็นเหมือนระบบรักษาความปลอดภัยที่ซับซ้อน ออกแบบมาให้ส่งเสียงเตือนเมื่อมีบั๊กโผล่ขึ้นมา แล้ววิธีที่ดีกว่าการลองบุกเข้าไปเพื่อทดสอบระบบรักษาความปลอดภัยจะมีอะไรดีไปกว่านี้ครับ
หลังจากที่คุณเขียนเทสต์เพื่อตรวจจับบั๊กตัวหนึ่งแล้ว ให้ สร้าง บั๊กนั้นขึ้นมาโดยตั้งใจ แล้วตรวจสอบว่าเทสต์ร้องเตือนขึ้นมาจริง วิธีนี้ทำให้มั่นใจได้ว่าเทสต์จะจับบั๊กได้ถ้ามันเกิดขึ้นจริงครับ
| Tip 92 | ใช้ผู้ก่อวินาศกรรมทดสอบการทดสอบของคุณ |
|---|
ถ้าคุณจริงจังกับการทดสอบ จริงๆ ให้แยก branch ของ source tree ออกมาต่างหาก จงใจใส่บั๊กเข้าไป แล้วตรวจสอบว่าเทสต์จะจับมันได้ ในระดับที่สูงขึ้นไปอีก คุณสามารถใช้เครื่องมืออย่าง Chaos Monkey ของ Netflix [81] เพื่อรบกวน (คือ “ฆ่า”) เซอร์วิสต่างๆ และทดสอบความสามารถในการทนทานของแอปพลิเคชันคุณครับ
เมื่อเขียนเทสต์ ต้องแน่ใจว่าสัญญาณเตือนจะดังขึ้นเมื่อมันควรดังครับ
Testing Thoroughly (ทดสอบอย่างละเอียดถี่ถ้วน)
เมื่อคุณมั่นใจแล้วว่าเทสต์ของคุณถูกต้อง และกำลังหาบั๊กที่คุณสร้างขึ้นเจอ คุณจะรู้ได้อย่างไรว่าคุณทดสอบโค้ดเบสได้ละเอียดถี่ถ้วนพอแล้ว
คำตอบสั้นๆ คือ “คุณไม่มีทางรู้” และคุณก็จะไม่มีวันรู้ครับ คุณอาจลองใช้เครื่องมือ coverage analysis ที่คอยจับตาดูโค้ดของคุณระหว่างการทดสอบและติดตามว่าบรรทัดไหนถูกรันแล้วและบรรทัดไหนยังไม่ถูกรัน เครื่องมือเหล่านี้ช่วยให้คุณรู้สึกคร่าวๆ ว่าการทดสอบของคุณครอบคลุมแค่ไหน แต่อย่าคาดหวัง coverage 100% [82]
ถึงแม้คุณจะบังเอิญรันโค้ดทุกบรรทัดได้จริง นั่นก็ยังไม่ใช่ภาพทั้งหมด สิ่งที่ สำคัญ จริงๆ คือจำนวน state ที่โปรแกรมของคุณอาจมีได้ state ไม่เท่ากับจำนวนบรรทัดของโค้ด ตัวอย่างเช่น สมมติว่าคุณมีฟังก์ชันที่รับตัวเลขจำนวนเต็มสองตัว แต่ละตัวเป็นเลขตั้งแต่ 0 ถึง 999
| | int test( int a, int b) { |
|---|---|
| | return a / (a + b); |
| | } |
ในทางทฤษฎี ฟังก์ชันสามบรรทัดนี้มี 1,000,000 logical state โดย 999,999 state จะทำงานถูกต้อง และมี 1 state ที่จะไม่ถูกต้อง (เมื่อ a + b เท่ากับศูนย์) แค่รู้ว่าคุณรันบรรทัดโค้ดนี้แล้วไม่ได้บอกคุณเรื่องนั้นเลย คุณต้องระบุ state ที่เป็นไปได้ทั้งหมดของโปรแกรมด้วย แต่น่าเสียดายที่โดยทั่วไปแล้วนี่เป็นปัญหาที่ ยากมากๆ ยากในระดับที่ว่า “ดวงอาทิตย์คงเย็นเป็นก้อนน้ำแข็งก่อนที่คุณจะแก้ปัญหานี้ได้”
| Tip 93 | ทดสอบ State Coverage ไม่ใช่ Code Coverage |
|---|
Property-Based Testing (การทดสอบแบบอิง Property)
วิธีที่ดีเยี่ยมในการสำรวจว่าโค้ดของคุณรับมือกับ state ที่ไม่คาดคิดอย่างไรคือให้คอมพิวเตอร์สร้าง state เหล่านั้นขึ้นมาเอง
ใช้เทคนิคการทดสอบแบบ property-based เพื่อสร้างข้อมูลทดสอบตาม contract และ invariant ของโค้ดที่กำลังทดสอบอยู่ เราพูดถึงหัวข้อนี้อย่างละเอียดใน Topic 42, Property-Based Testing
Tightening the Net (ขึงแหให้แน่นขึ้น)
สุดท้ายนี้ เราอยากเปิดเผยแนวคิดที่สำคัญที่สุดเพียงข้อเดียวในการทดสอบ มันเป็นเรื่องชัดเจนมาก และแทบทุกตำราก็บอกให้ทำแบบนี้ แต่ด้วยเหตุผลบางอย่าง โปรเจกต์ส่วนใหญ่ก็ยังไม่ทำมันอยู่ดีครับ
ถ้าบั๊กหลุดรอดผ่านแหของเทสต์ที่มีอยู่ไปได้ คุณต้องเพิ่มเทสต์ใหม่เพื่อดักมันไว้ในครั้งต่อไปครับ
| Tip 94 | หาบั๊กให้เจอแค่ครั้งเดียว |
|---|
เมื่อผู้ทดสอบที่เป็นมนุษย์เจอบั๊กตัวหนึ่งแล้ว นั่นควรเป็นครั้ง สุดท้าย ที่มนุษย์ต้องมาเจอบั๊กตัวนั้นด้วยตัวเอง เทสต์อัตโนมัติควรถูกปรับแก้ให้ตรวจสอบบั๊กตัวนั้นโดยเฉพาะตั้งแต่นั้นเป็นต้นไป ทุกครั้ง ไม่มีข้อยกเว้น ไม่ว่ามันจะดูเล็กน้อยแค่ไหน และไม่ว่านักพัฒนาจะบ่นแค่ไหนว่า “โอ๊ย มันไม่มีทางเกิดขึ้นอีกหรอก”
เพราะมันจะเกิดขึ้นอีกแน่นอนครับ และเราก็ไม่มีเวลามานั่งไล่ล่าบั๊กที่เทสต์อัตโนมัติควรจะหาให้เราเจออยู่แล้ว เราต้องใช้เวลาไปกับการเขียนโค้ดใหม่ และบั๊กใหม่ๆ ต่างหาก
Full Automation (การทำงานอัตโนมัติเต็มรูปแบบ)
อย่างที่เราพูดไว้ตอนต้นของหัวข้อนี้ การพัฒนาซอฟต์แวร์สมัยใหม่พึ่งพาขั้นตอนที่เขียนเป็นสคริปต์และเป็นอัตโนมัติ ไม่ว่าคุณจะใช้อะไรง่ายๆ อย่าง shell script คู่กับ rsync และ ssh หรือใช้โซลูชันเต็มรูปแบบอย่าง Ansible, Puppet, Chef หรือ Salt ก็ตาม แค่อย่าพึ่งพาการแทรกแซงด้วยมือก็พอครับ
กาลครั้งหนึ่ง เราเคยไปอยู่ที่ไซต์ลูกค้าแห่งหนึ่งที่นักพัฒนาทุกคนใช้ IDE ตัวเดียวกัน ผู้ดูแลระบบของพวกเขาให้ชุดคำแนะนำการติดตั้งแพ็กเกจเสริมสำหรับ IDE แก่นักพัฒนาแต่ละคน คำแนะนำเหล่านี้ยาวหลายหน้ากระดาษ เต็มไปด้วยคลิกตรงนี้ เลื่อนตรงนั้น ลากอันนี้ ดับเบิลคลิกอันนั้น แล้วก็ทำซ้ำอีกครั้ง
ไม่น่าแปลกใจเลยที่เครื่องของนักพัฒนาแต่ละคนถูกติดตั้งมาต่างกันเล็กน้อย ความแตกต่างเล็กๆ น้อยๆ ในพฤติกรรมของแอปพลิเคชันเกิดขึ้นเมื่อนักพัฒนาต่างคนรันโค้ดเดียวกัน บั๊กก็โผล่ขึ้นมาบนเครื่องหนึ่งแต่ไม่โผล่บนเครื่องอื่น การตามหาความแตกต่างของเวอร์ชันของคอมโพเนนต์ตัวไหนก็ตามมักเผยเรื่องน่าประหลาดใจออกมาเสมอครับ
| Tip 95 | อย่าใช้ขั้นตอนที่ทำด้วยมือ |
|---|
คนเราไม่สามารถทำซ้ำได้แม่นยำเท่าคอมพิวเตอร์หรอกครับ และเราก็ไม่ควรคาดหวังให้เป็นแบบนั้นด้วย shell script หรือโปรแกรมจะรันคำสั่งเดิมๆ ในลำดับเดิมๆ ซ้ำแล้วซ้ำเล่า มันอยู่ภายใต้ version control ด้วยตัวเอง ดังนั้นคุณจึงตรวจสอบการเปลี่ยนแปลงของขั้นตอน build/release ย้อนหลังได้เช่นกัน (“แต่มัน เคย ทำงานได้นะ…”)
ทุกอย่างขึ้นอยู่กับระบบอัตโนมัติ คุณ build โปรเจกต์บนเซิร์ฟเวอร์คลาวด์นิรนามไม่ได้ ถ้าการ build ไม่เป็นอัตโนมัติเต็มรูปแบบ คุณดีพลอยอัตโนมัติไม่ได้ ถ้ามีขั้นตอนที่ต้องทำด้วยมือปะปนอยู่ และทันทีที่คุณเริ่มมีขั้นตอนที่ทำด้วยมือ (“แค่ส่วนนี้ส่วนเดียวนะ…”) คุณก็ทำหน้าต่างบานใหญ่มากแตกไปแล้วครับ [83]
ด้วยสามขานี้ ทั้ง version control การทดสอบอย่างไม่ปรานี และระบบอัตโนมัติเต็มรูปแบบ โปรเจกต์ของคุณจะมีรากฐานที่มั่นคงที่คุณต้องการ เพื่อให้คุณโฟกัสกับส่วนที่ยากจริงๆ ได้ นั่นคือการสร้างความประทับใจให้ผู้ใช้ครับ
Related Sections Include (หัวข้อที่เกี่ยวข้อง)
- Topic 11, Reversibility
- Topic 12, Tracer Bullets
- Topic 17, Shell Games
- Topic 19, Version Control
- Topic 41, Test to Code
- Topic 49, Pragmatic Teams
- Topic 50, Coconuts Don't Cut It
Challenges (แบบฝึกหัดท้าทายความคิด)
-
การ build ทุกคืนหรือแบบต่อเนื่องของคุณเป็นอัตโนมัติ แต่การดีพลอยขึ้น production ไม่เป็นใช่ไหมครับ ทำไมถึงเป็นแบบนั้น เซิร์ฟเวอร์ตัวนั้นมีอะไรพิเศษ
-
คุณทดสอบโปรเจกต์ของคุณได้ครบถ้วนแบบอัตโนมัติหรือเปล่า หลายทีมถูกบังคับให้ตอบว่า “ไม่ได้” ทำไมล่ะ มันยากเกินไปที่จะกำหนดผลลัพธ์ที่ยอมรับได้หรือเปล่า แล้วแบบนี้จะทำให้พิสูจน์ให้สปอนเซอร์เห็นว่าโปรเจกต์ “เสร็จ” แล้วยากขึ้นไหมครับ
-
การทดสอบ logic ของแอปพลิเคชันแยกออกจาก GUI นั้นยากเกินไปหรือเปล่า เรื่องนี้บอกอะไรเกี่ยวกับ GUI ของคุณบ้าง แล้วเกี่ยวกับ coupling ล่ะครับ