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 ล่ะครับ