Design by Contract

    ในบทความที่แล้ว ผมเขียนถึงการใช้คำสั่ง Assert ในการตรวจจับข้อผิดพลาด แต่ก็มีคำถามที่สำคัญที่พบกันบ่อย นั่นก็คือ ควรจะดักละเอียดแค่ไหน มีกรณีใดบ้างที่ควรดัก หรือกรณีใดที่ไม่ควรดัก

แนวคิดแรกเริ่ม: ดักให้เยอะเข้าไว้ เหลือดีกว่าขาด

    แนวคิดนี้เน้นปลอดภัยไว้ก่อน ใช้คำสั่ง Assert ดักทุกจุดเท่าที่ดักได้ ตามแบบฉบับของ Defensive Programming ผมเองก็เคยเห็นชอบด้วยกับวิธีนี้ แต่พอลงมือทำจริงๆ ก็พบสัจธรรมครับ Code มักดูรกไปหมด แล้วยิ่งไปกว่านั้น สัจธรรมอีกข้อที่ค้นพบ ก็คือผมไม่สามารถดักได้ทุกกรณี ยังไงมันก็หลุด เรื่องนี้ทำให้ผมย้อนนึกไปถึงเวลาเรียนสมัยมัธยม "ขี่ช้างจับตั๊กแตน" อาจารย์เคยถามว่า มันหมายความว่าอย่างไร ผมก็ตอบตามสูตรไปว่า ลงทุ่นมากได้ผลน้อย ก็เลยโดนอาจารย์สวนกลับมาว่า "เธอคิดว่าขี่ช้างแล้วจะจับตั๊กแตนที่กระโดดอยู่บนพื้นได้หรือ?"

Design by Contract

    ปรมาจารย์ท่านหนึ่งคือ Dr. Bertrand Meyer ประกาศว่า ที่บอกว่า เหลือดีกว่าขาดนั้น ไม่เห็นด้วยอย่างยิ่ง  Meyer มองตรงข้ามกับ Defensive Programming เขาบอกว่า ความรกมันจะฆ่าคุณ code ยิ่งมาก ตัว Assert ที่แทรกมันยิ่งมากเป็นเงาตามตัว

 

แนวคิด Client / Supplier

   Meyer มองการเขียนโปรแกรม เหมือนกับ ระบบคุณภาพทั้งหลาย กล่าวคือ ถ้าทุกส่วนที่เกี่ยวข้อง ต่างคนต่างทำงานของตนสมบูรณ์ ไม่มีข้อบกพร่อง ระบบก็จะไม่มีปัญหา Meyer มองว่า แต่ละส่วนนั้นเชื่อมโยงกันด้วยสิ่งที่เรียกว่า Contract หรือสัญญา ตราบใดถ้าทุกคนยังรักษาพันธะนี้โดยไม่บิดพริ้ว ระบบจะไม่มีปัญหา Meyer เรียกฝั่งผู้ขอรับบริการว่า Client ส่วนผู้ให้บริการคือ Supplier

    ลองนึกภาพดูว่า ในการจัดงานปีใหม่ เรารับหน้าที่เป็นแม่งาน ตามหน้าที่แล้วเราต้องทำหน้าที่ประสานงานทุกอย่าง (Client) ทำทุกวิถีทางที่ให้งานสำเร็จลุล่วงไปได้ จึงเป็นความจำเป็นอยู่เอง ที่เราต้องติดต่อกับบุคคลกลุ่มต่างๆ เพื่อให้มาร่วมกันทำงานนี้ให้สำเร็จ มาลองดูตัวอย่างวงดนตรี (Supplier) ที่เราจะจ้างมาด้วยเงิน 20,000 บาท เต็มวง มีหางเครื่องสาวๆ  5 เต้น เพลง ก็ต้องมีเป็นเพลงดิ้นๆ จะได้สนุกกัน เวลามาก็ต้องเริ่มตั้งแต่ 5 โมงเย็นไปถึงเที่ยงคืน สิ่งเหล่านี้ ล้วนแล้วแต่เป็นสัญญา หรือ Contract ทั้งสิ้น คงไม่มีใครแน่ ที่จ่ายเงิน 20,000 บาท แล้วปล่อยให้วงดนตรีจัดการกันเอง อยากมาเมื่อไรก็มา จะร้องเพลงอะไรก็ได้ ตามใจคนร้อง หางเครื่องไม่อยากเต้นก็ไม่ต้องเต้น เราในฐานะแม่งาน ไม่สนใจรายละเอียด ไม่พอใจอะไร ไปแก้กันหน้างาน ผมเชื่อว่าคงไม่มีใครทำเช่นนั้น แต่เวลาเราเขียนโปรแกรมเรามักทำเช่นนี้ อยากเขียนอะไรก็เขียน method ไม่รองรับงานพอ ก็ไปเพิ่ม parameters  เขียนไปปรับไป โปรแกรมตอนเสร็จกับตอนคิดเริ่มแรก อาจจจะต่างกันมาก

     Meyer บอกว่าเวลาเราเขียนโปรแกรม เราควรเล่นสองบทบาท บทบาทแรกคือบทบาทของแม่งาน หรือพูดให้ดูหรูหน่อยก็คือคือบทบาทของนักออกแบบ กำหนด prototype ของ method ต่างๆ ว่าจะมี parameters อะไรบ้าง และจะ return ค่าอะไรออกไป แค่เพียงกำหนดยังไม่นะครับ ต้องเขียนตัวทดสอบที่นี่เลย เมื่อเริ่มเข้า method เราต้องตรวจสอบ parameters ทุกตัวที่ส่งมาจากข้างนอกนั้น เป็นค่าที่เป็นไปได้ หรือสอดคล้องกันทั้งหมดหรือไม่ เช่นส่งอายุมานั้น อยู่ระหว่าง 1 ถึง 150 หรือไม่ อย่าปล่อยให้ค่าที่ผิดพลาดไปรวนระบบ คำสั่ง Assert ก็จะได้ใช้ที่นี่ครับ การตรวจสอบในขั้นนี้เป็นการตรวจสอบ ข้อมูลเข้า ดูว่าสัญญาที่ตกลงกันไว้ ผู้เรียกใช้ method นั้นยังรักษามันหรือไม่

     สิ่งหนึ่งที่เป็นหัวใจหลักของเรื่องนี้ก็คือ เมื่อคุณเขียนตัวทดสอบ parameters ต่างๆ ที่อยู่ต้น method แล้ว คุณต้องไม่ทดสอบมันอีกในส่วนอื่นของ Method อย่างเด็ดขาด เพื่อไม่ให้เกิดความซ้ำซ้อนและดูรก ถือเป็นกฏที่ต้องปฏิบัติตามอย่างเคร่งครัด

     เมื่อตรวจสอบ parameters ทุกตัวแล้ว ก็เขียน code ทดสอบตอนท้าย เราใช้ Assert ที่จุดนี้เพื่อดักค่าส่งกลับ ทั้งค่า return value, ค่า pass by value ตลอดจนค่า Fields ต่างๆ ที่เปลี่ยนไป ถ้าเราหาความสัมพันธ์ระหว่าง ค่าที่ส่งมากับค่าที่ส่งกลับ/Fields ได้ มันจะทำให้การ Assert นั้นยิ่งดักข้อผิดพลาดได้ดียิ่งขึ้น ยกตัวอย่างง่ายๆ เช่น ถ้าเราต้องการสร้าง method add(num1, num2) เราก็สามารถตรวจสอบ return value ว่า มันจะได้ num1 + num2 หรือไม่ แต่การทำเช่นนี้ ค่า num1 และ num2 ต้องอ่านอย่างเดียวตั้งแต่ต้นจนจบ method ห้ามแก้ เพราะถ้าแก้แล้วมันจะทำให้มีการตรวจสอบผิดพลาด

       เมื่อกำหนดทุกอย่างและเขียนตัวดักเรียบร้อยแล้ว เราก็ค่อยเริ่มบทบาทที่สอง คือการเขียนโปรแกรม Meyer เชื่อว่า วิธีนี้จะทำให้การเขียนโปรแกรมมีความถูกต้องสูงขึ้นมาก

Design by Contract

    เรามาลองดูกันว่า Meyer นิยาม Design by Contract ว่าอย่างไรบ้าง เริ่มจากการดัก parameters ต่างๆ ที่ผมกล่าวถึง ทาง Meyer เรียกว่า precondition ส่วนการดักค่า return values/Fields เขาเรียกว่า postcondition ยังไม่หมดครับ ถ้า Assert ไม่ผ่านใน precondition ก็คือ ความผิดพลาดจะตกอยู่กับผู้เรียกเข้ามา (Client) แต่ ถ้า Assert ไม่ผ่านใน postcondition ความผิดพลาดนั้นอยู่ที่ผู้เขียน method นั้นเองครับ

เพราะถ้าทำแค่นี้มันยังไม่เป็น Object-Oriented มันเหมือนออกมาเพื่อสนับสนุน Top-down มากกว่า Design by Contract ยังมี การดักอีกจุดหนึ่งครับ คือ Invariant 

    ก่อนพูดถึง Invariant ผมทบทวน Object-Oriented อีกที ปรมาจารย์ Grady Booch นิยาม Object ว่า มันคือสิ่งที่ต้องมี State, Behavior และ Identity  ผมขอเน้นเฉพาะ State มันคือสถานะของ Object ถ้าพูดให้เป็นรูปธรรมมากขึ้น มันก็คือพวก ที่เป็น Fields หรือ ตัวแปรระดับ class นั่นเอง ตัวแปรเหล่านี้เอง มีส่วนในการกำหนดพฤติกรรมของ Object นั้น ถ้า State ผิดเพี้ยน การทำงานของโปรแกรมโดยรวมก็ต้องเพี้ยนไปด้วย Invariant จึงถูกออกแบบมาเพื่อรักษาความถูกต้องของ State ต่างๆ นั่นเอง

    รักษาความถูกต้องของ State ได้อย่างไร การที่เราจะตอบปัญหานี้ เราลอง inverse logic ดูว่า เราจะทำให้ State มันผิดได้อย่างไร พบว่าการที่ State มันผิดได้นั้น มันเกิดจากสิ่งเร้าภายนอก มายัง methods ต่างๆ นั่นเอง และ methods นี่เอง ถ้ากำหนด State ผิด ก็จะเกิดปัญหาข้างต้น

    แนวคิดของ Invariant  คือการดักค่าให้ State ต่างๆ สอดคล้องกัน เช่น ถ้าเรามี Field Min และ Max เราต้องตรวจสอบอยู่เสมอว่า ค่า Max จะต้องไม่ต่ำกว่าค่า Min เช่นนี้เป็นต้น แล้วการดัก เราควรจะดักที่ใดบ้าง? เพื่อความมั่นใจ เราควรดักที่จุดเริ่มของ ทุก Method/Function เพื่อให้เรามั่นใจได้ว่าก่อนเรียกใช้ Method/Function นั้น ค่าของ State ยังถูกต้องอยู่ และ เราต้องดักก่อนการ return ค่ากลับ เพื่อให้มั่นใจว่า code ของเราไม่ได้ไปทำให้ State ผิดพลาด เพื่อให้ code ไม่ดูรกรุงรัง เราควรเอา code ของการ Invariant ไปเก็บไว้ที่ฟังก์ชัน แล้วให้ method/function อื่นๆ มาเรียกใช้ตอนเริ่มต้นและตอนจบ

ข้อดีของ Design by Contract

แต่ข้อเสียก็มีครับ เราต้องเสียเวลาตรวจสอบค่าต่างๆ เวลารันโปรแกรม แต่ผมเชื่อว่า ไม่ค่อยมีผลกระทบด้านความเร็วมากนัก

ทิ้งท้าย

    Meyer แสดงให้เห็นถึงการใช้งาน Design by Contract โดยเอาภาษาที่ตนเองสร้างเอาไว้มาเป็นเครื่องมือ นั่นก็คือภาษา Eiffel ซึ่งถ้าใครได้ลองใช้ดูจะเห็นว่ามันรองรับ Design by Contract อย่างเต็มที่ Meyer ออกแบบภาษาให้มี section ในการวาง precondition, postcondition และ invariant อย่างสวยงาม ยิ่งไปกว่านั้น มันสามารถดึงค่าเดิมก่อนถูกแก้ไขของ parameters มาใช้ในส่วนของ postcondtion   แถมยังเรียกใช้ Invarient โดยอัตโนมัติ เราไม่จำเป็นต้องแทรก Code ในแต่ละ methods

    ในปัจจุบัน มีผู้พยายามทำตัวสนับสนุน Design by Contract ในเกือบทุกภาษาของโลก Sun เองก็เคยคิดเพิ่มการรองรับ Design by Contract ใน Java 1.4 แต่ในที่สุดก็ล้มเลิกไป แต่ก็มี Third Parties ให้เลือกอยู่ไม่น้อยในแต่ละภาษา ผมเอง ก็ลองทดลองมาหลายตัวแล้ว แต่ก็ยังติดภาพ Eiffel อยู่ ทำให้ไม่ถูกใจ เลยไม่ขอแนะนำครับ ถ้าอยากใช้ ลอง Search ดูใน Internet ครับ มีให้เลือกใช้เยอะมาก

Supoj

04-AUG-04