Screen Shot 2557-11-14 at 12.38.51 AM
วันนี้เลือกเข้าทำ workshop เกี่ยวกับ Test-Driven Development
ที่งาน Agile Singapore 2014 ซึ่ง workshop นี้จัดโดยคุณ James Grenning
ซึ่งในตอนแรกจะไม่เข้า แต่พอลองเข้าไปเท่านั้นแหละ ถึงกับถอนตัวไม่ขึ้นเลย
มาดูกันว่าใน workshop นี้ให้อะไรกับตัวเองบ้าง

เริ่มต้นทางเจ้าของ workshop ได้ทำการอธิบายรายละเอียดคร่าวๆ
ต่อจากนั้นตามด้วยคำถามต่างๆ เหล่านี้

1. คุณชอบ code แบบไหน ? และไม่ชอบ code แบบไหน ?

คำตอบที่ได้เรื่อง code ที่ชอบ ก็ไม่น่าสนใจเท่าไร
เพราะว่าส่วนใหญ่ก็ชอบ code ที่อ่านเข้าใจง่าย
ชอบ code ที่ไม่ซับซ้อน และอื่นๆ อีกมากมาย

แต่สิ่งที่น่าสนใจคือ code ที่ไม่ชอบ เช่น
legacy code หรือ code ที่ไม่มี test
พวก spaghetti code
code ที่ไม่มีเอกสาร
code ที่ซับซ้อน

แปลกนะ เรามักไม่ชอบในสิ่งที่เราชอบทำ !!

2. ดังนั้นเรามาสร้าง code ในแบบที่เราชอบดีกว่าไหม ?

นั่นคือ การสร้าง code ตามแนวคิด  Test-Driven Development (TDD) กัน

3. เราจะเขียนด้วยภาษา C นะ ในห้องใครเขียนได้บ้าง ?

ส่วนใหญ่ เขียน C++ ได้ จึงเปลี่ยนไปใช้กัน
ส่วนผมนั้น ไม่เคยเขียนนะ ก็ตามน้ำไป

4. แล้วจะใช้ Editor อะไรเขียน code ล่ะ ?

เนื่องจากการใช้ Editor อาจจะไม่สะดวกเท่าไรนัก
ดังนั้นทางเจ้าของ workshop จึงทำการติดตั้ง Cyper Dojo มาให้โดยเฉพาะ
เนื่องจากมีหลายๆ อย่างที่ต้องควบคุมให้อยู่ในรูปแบบเดียวกัน
ไม่เช่นนั้น workshop อาจจะล่มได้

ใช้งานจากที่นี่ Cyber Dojo for Agile Singapore
ซึ่งไม่แน่ใจว่าจะปิดไปเมื่อใด

แต่ถ้าต้องการใช้ฝึกเชิญได้ที่นี่ Cyber Dojo

5. โจทย์ที่ใช้คืออะไร ?

ปัญหาคือ Circular Buffer

6. สิ่งที่ต้องสนใจก่อนลงมือเขียน code ?

เนื่องจากการเขียน code ตามแนวทางของ TDD นั้น
ไม่ใช่จะลงมือเขียน test เขียน code และ refactoring ทันที
แต่ต้องกระทำสิ่งต่างๆ ต่อไปนี้ก่อนนะ มันสำคัญมากๆ

  • สิ่งที่คุณจะพัฒนานั้นต้องมี APIs หรือ ชื่อ function/method อะไรบ้าง ?
  • แล้วการทำงานภายในของปัญหาที่จะแก้ไขเป็นอย่างไร มีโครงสร้างข้อมูลอย่างไร ?
  • มีสิ่งใดที่คุณต้องแก้ไขบ้าง ?

ออกแบบง่ายๆ ดังนี้

Screen Shot 2557-11-14 at 12.18.29 AM

ถ้าตอบสิ่งต่างๆ เหล่านี้ได้ จึงเริ่มเขียน code กัน !!

7. เริ่มเขียน code โดยเริ่มจากการเขียน test ก่อนนะ แต่จะเริ่มที่ test ไหนล่ะ ?

ถ้าเราต้องเริ่มเขียน test แล้ว test แรกน่าจะเป็น 
การตรวจสอบว่า buffer ว่างหรือไม่เมื่อทำการสร้าง ?

ต่อจากนั้นทำการแก้ไข code ให้ผ่านตาม test case
ด้วยการสร้าง method isEmpty() ขึ้นมา ดังนี้

จาก code ดังกล่าวพบว่าเราทำการโกงด้วยการ hard code
ซึ่งมันก็ดีตรงที่ test ผ่านนะเออ
ตรงนี้ไม่มี code ส่วนไหนที่ต้องการ refactor นะ

ต่อมา test ที่สอง
ก็ยังสนใจที่ buffer ว่าง หรือ ไม่ เช่นเดิม
โดยถ้าทำการสร้าง และ เพิ่มข้อมูล (put) เข้าไปแล้ว
แล้ว buffer ต้องไม่ว่าง ดังนี้

โดย test นี้มันคือการขจัด hard code ที่เราทำไว้ออกไป
ซึ่งเพิ่มตัวแปร count มาเพื่อนับจำนวนใน buffer ดังนี้

มาถึงตรงนี้ก็ยังไม่มีอะไรที่ต้อง refactor นะ

จากนั้นยังสนใจที่การตรวจสอบว่า buffer ว่างหรือไม่อยู่
นั่นคือ สนใจที่ behavior ของการทำงานว่า
ถ้าทำการเพิ่ม และ ดึงข้อมูล (get) ออกมา
แล้ว buffer จะต้องว่าง ดังนี้

โดยใน test ที่สามนี้ ต้องการตรวจสอบในเงื่อนไขอื่นๆ
ที่อยู่ในขอบเขตการทำงานที่เราสนใจ นั่นคือ buffer ว่างหรือไม่
สามารถแก้ไขได้ด้วยการเพิ่ม method get() เข้ามา ดังนี้

มาถึงตรงนี้จะพบว่า เราสนใจไปที่พฤติกรรมการทำงานว่าจะทำให้ buffer ว่างหรือไม่
โดยไม่ได้สนใจว่าข้อมูลจะถูกเพิ่ม หรือ ลบ ออกไปจาก buffer จริงๆ หรือไม่

ต่อไปเราจะสนใจ การทดสอบพฤติกรรม เมื่อเพิ่มข้อมูลเข้าไปใน buffer
จากการออกแบบเราจะมี

  • index สำหรับตัวชี้ว่าข้อมูลที่เพิ่มอยู่ตำแหน่งใด (put)
  • outdex สำหรับชี้ว่าข้อมูลที่ลบ หรือ ดึงออกมา อยู่ตำแหน่งใด (get)

ดังนั้น เริ่มจาก test case ของการเพิ่มข้อมูลเข้าไปใน buffer ดังนี้

จะเห็นได้ว่า test case นี้ทำมาเพิ่มขจัด hard code ใน method get() ออกไป
ที่ทำการ return 9999; ออกมาเสมอ ดังนี้

หยุดดู code ที่สร้างขึ้นมานิดนึงนะ คุณเห็นอะไรไหม ?
เห็นไหมว่าตอนนี้ เรายังไม่ได้ทำการเก็บข้อมูลลงใน buffer เลย !!
และเรายังทำการ hard code ไว้อีกใช่ไหม ?

คำถาม
สิ่งที่เราทำต่อไปคืออะไร ?
คำตอบ
เราควรที่จะเลือก data structure หนึ่งเพื่อมาเก็บข้อมูลของ buffer นะ

เราจะใช้ data structure อะไรดีล่ะ ?
ส่วนใหญ่ใน workshop จะตอบว่า List ไงล่ะ !!!

คำถาม
แล้ว List มันง่ายแล้วหรือ ?
มีอะไรที่ง่ายกว่านี้ไหม ?
คำตอบ
ง่ายๆ ก็ array ไง มาเริ่มกันเลย

เริ่มด้วยการเขียน test case การเพิ่มข้อมูลเข้าไป 3 ค่าดังนี้

เป็น test case ที่ดูสมเหตุสมผลมากมาย
ดังนั้นมาแก้ไข code ด้วยการสร้าง array ชื่อว่า values ขนาด 10 ขึ้นมา
แล้วนำมาใส่ใน method put() และ get() ดังนี้

หยุดคิดกันอีกหน่อย
ตอนนี้เราทำการเขียน code แบบ Test driven มา
จนมีการทำงานภายในที่ถูกต้องแล้ว
สามารถอ้างอิงด้วย index และ outdex ได้
แต่ทำงานได้กับ buffer ที่มีขนาดเดียวเท่านั้น นั่นคือ 10 (fized size)
ซึ่งมันไม่ใช่ Circular Buffer ใช่ไหม ?

ดังนั้นเขียนเพิ่มสิครับ !!!

  • การทดสอบเรื่องดึงค่าขนาด default ของ Circular buffer
  • การทดสอบเรื่องการกำหนดขนาดของ Circular buffer
  • จากนั้นก็ทำการทดสอบการทำงานภายในอีกครั้ง กับ buffer ขนาดใหญ่ๆ
  • ทำการทดสอบเรื่อง buffer เต็มหรือไม่

สุดท้ายแล้ว แต่ละกลุ่มมีผลงานดังนี้

นี่คือ dashboard แสดงการเขียน code ของแต่ละกลุ่มที่เข้าร่วม workshop
ซึ่งใช้เวลาไม่น่าเกิน 20 นาที ซึ่งสนุกสนานมากๆ

ส่วนผมทำการ run test ไป 50 ครั้ง แบบมั่วๆ สุดท้ายก็ผ่าน ดังรูป

Screen Shot 2557-11-13 at 11.16.49 PM

ปิดท้ายด้วยคำถามต่างๆ

คำถาม
เราจะรู้ได้อย่างไรว่าควรจะหยุดเขียน test ได้แล้ว ?
คำตอบ
คุณเขียน code คนเดียวหรือไม่ ?
ถ้าไม่ แนะนำให้ทำการ pair programming และ code review นะ
แล้วคุณจะได้คำตอบเอง

คำถาม
ค่า code coverage นั้นมีความสำคัญอย่างไร ?
คำตอบ
ถ้าค่า code coverage สูง แสดงว่าคุณไม่มีอะไรต้องทำ
แต่ถ้า code coverage ต่ำนะสิ คุณมีสิ่งที่ต้องทำอีกเยอะ
นั่นคือสนใจเรื่องการ improvement และ fast feedback

โดยสรุปแล้ว สิ่งที่ได้รับจาก workshop ประกอบไปด้วย

  • ได้เขียน TDD with C++ เป็นครั้งแรก ซึ่งไม่ได้ยากอะไร
  • ได้เข้าใจกับเรื่องของการ focus งานในแต่ละส่วน
  • ได้เข้าใจว่าการออกแบบก่อนเขียน code นั้นเป็นอย่างไร
  • ได้เข้าใจว่าจังหวะของ TDD นั้นมันเป็นอย่างไร
  • ได้เข้าใจว่า Small Step นั้นเป็นอย่างไร
  • ได้เข้าใจว่า ลำดับของ test caseสำคัญมากๆ
  • ได้เข้าใจว่า ไม่ต้องใช้การ debug มาช่วยเลย
  • ได้เข้าใจว่า คนดูแล workshop สามารถควบคุม workshop ได้ดีเป็นอย่างไร
  • ได้เข้าใจแนวคิด และ จังหวะ ที่อยู่ข้างหลังโจทย์ง่ายๆ ว่ามันเป็นอย่างไร
  • ได้สนุกกับการเขียน code ด้วยแนวคิด TDD
  • ได้ตาลุกวาว ในการเขียน code ที่สั้นๆ แต่สนุกมาก
  • สุดท้ายได้เรียนรู้เพิ่มอีกแล้ว

We are learning …