ความรู้มากมาย แต่เริ่มต้นเขียนโปรแกรมไม่ถูกซักที ตอนที่ 1 – การออกแบบ(1)

ความรู้มากมาย แต่เริ่มต้นเขียนโปรแกรมไม่ถูกซักที ตอนที่ 1 – การออกแบบ(1)

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

คุณสมบัติในที่นี้คือ “ความสามารถในการออกแบบ”
บทความนี้ จึงจะขอพูดถึงการออกแบบในเชิงบรรยายมากกว่าในลักษณะของโค้ดเหมือนบทความที่ผ่านมา ซึ่งแม้จะอ่านได้ง่าย แต่การทำความเข้าใจและนำไปปฏิบัติจริงนั้น ยากเกินกว่าที่จะสามารถทำได้ด้วยการอ่านผ่านๆรอบเดียวโดยไม่ผ่านการฝึกฝนเลย อนึ่ง เรื่องของการออกแบบนั้น เป็นเรื่องที่ละเอียดอ่อน และถือเป็นศิลปะ เนื่องจากไม่มีทั้งวิธีที่ถูก หรือวิธีที่ผิด จะมีก็แต่วิธีที่เหมาะสมกับงานนั้นๆ แต่ก็ใช่ว่าสิ่งที่เหมาะสมในตอนนี้ จะเป็นสิ่งที่เหมาะสมตลอดไป
พูดถึงการออกแบบนั้น ถือว่าเป็นคำที่กว้างมาก เพราะหมายถึงทั้งการออกแบบโครงสร้างของโปรแกรม (Structural) การออกแบบรูปแบบการทำงานของโปรแกรม (Behavioral) หรือทั้งการออกแบบการตอบสนองของโปรแกรม (Sequence) หรือถ้าโปรแกรมของเรามี Graphics User Interface (GUI) เราก็ต้องนำมาผ่านขั้นตอนการออกแบบด้วย ซึ่งโดยทั่วไปนั้น เราจะทำการออกแบบองค์ประกอบทั้งหมด ไปพร้อมๆกัน ยกเว้น GUI อาจจะแยกไว้ตังหากเพราะจะทำให้การออกแบบการทำงานอย่างอื่นซับซ้อนเกินไป และเมื่อเราเริ่มทำการออกแบบนี้ ก็ถือว่าเราได้เริ่มเขียนโปรแกรมในขั้นแรกแล้ว
เพื่อที่จะให้ง่ายต่อความเข้าใจ เราจึงจะทำการศึกษาการออกแบบทีละด้าน เพื่อไม่ให้ความรู้ตีกันในหัวเรา
ทั้งนี้ จะขอยกโจทย์ขึ้นมาโจทย์หนึ่ง เพื่อใช้ในทุกๆหัวข้อ จะได้ดูเป็นแบบแผนเดียวกัน
โจทย์ต้องการเขียนโปรแกรมร้านเช่าหนังสือ โดยกำหนดคุณสมบัติของโปรแกรมดังนี้

  • หนังสือมี 3 ประเภท
    • Comic Book
      • Title
      • Volume
      • Publisher
      • Illustrator
      • Price
      • Barcode
      • ยืมได้ 1 วัน
    • Magazine
      • Title
      • Volume
      • Publisher
      • Price
      • Period (Weekly / Biweekly / Monthly / Other)
      • Barcode
      • ยืมได้ 3 วัน
    • Pocket Book
      • Title
      • ISBN
      • Publisher
      • Price
      • Author
      • Barcode
      • ยืมได้ 7 วัน
  • Feature
    • ทำการ List รายชื่อหนังสือในระบบออกมาแสดงได้
    • ค่าเช่า คิดเป็น 10% ของราคาหนังสือโดยปัดเศษลง
    • ยืมหนังสือโดยระบุหมายเลข ISBN
      • กรณีที่หนังสือถูกยืมไปแล้ว ให้แจ้งว่าหนังสือจะคืนภายในวันที่เท่าไร
      • กรณีที่หนังสือยังอยู่ ให้ทำการยืมหนังสือ และแจ้งว่าต้องมาคืนวันที่เท่าไร
    • คืนหนังสือโดยระบุหมายเลข ISBN
      • หากเกินวันที่ต้องคืน ให้ทำการเรียกเก็บค่าปรับเพิ่มเติม
  • Condition
    • ไม่ต้องเก็บข้อมูลใน Database

 

การออกแบบเชิงโครงสร้าง (Structural Design)

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

โดยการออกแบบเชิงโครงสร้างนั้น เราสามารถแบ่งวิธีการออกแบบได้เป็น 2 ลักษณะก็คือ Top-down(การออกแบบจากโครงสร้างที่ใหญ่ไปสู่โครงสร้างย่อย) และ Bottom-up (การออกแบบจากโครงสร้างเล็กไปใหญ่)

ในที่นี้เราจะทำการออกแบบแบบ Top-down

แน่นอนว่า เมื่อเราจะทำร้านเช่าหนังสือแล้ว เราก็ต้องมี Class ของร้านหนังสือแน่นอน

Class BookStore

ก่อนอื่น เราจะต้องทำการวิเคราะห์ให้แน่ใจก่อน ว่าสิ่งเรากำลังจะสร้างนั้น ทำหน้าที่อะไรในโครงสร้างของเราบ้าง ซึ่งเมื่อเรามองถึง BookStore ของเราแล้ว ตัวของ BookStore นั้น ก็เปรียบเสมือนระบบโดยรวมที่ควบคุมการทำงานของโครงสร้างอื่นๆทั้งหมด ไม่ว่าจะทำการเก็บ ยืม คืน แสดง หนังสือ หรือแม้แต่การคิดค่าปรับ ก็ล้วนแต่เป็นหน้าที่ของ BookStore ทั้งนั้น
แต่เมื่อมองในเชิงของลักษณะการ Design แล้ว เราอาจจะมองว่าการยืม และ คืนหนังสือ ถือเป็นงานที่ซับซ้อน เพราะต้องมีทั้งการคำนวนวันที่มาคืน การบันทึกว่าเล่มไหนถูกยืมไปบ้าง การคำนวนค่าปรับ ซึ่งเราอาจจะ Design ให้การทำงานทั้งหมดนี้ ออกมาอยู่ภายใต้ Class อีก Class หนึ่งก็ได้ ซึ่งไม่ว่าจะแยกการทำงานเหล่านี้ ออกเป็นอีก Class หรือว่าจะเก็บการทำงานเหล่านี้ไว้ใน Class ของ BookStore ก็มีข้อดีข้อเสียแตกต่างกันไป
ซึ่งถ้าใครจะแยกเรื่องของการยืม-คืนออกไปเป็นอีก Class หนึ่ง แม้จะเสียเวลาเพิ่ม แต่ก็มีข้อดีอีกมากมาย เช่น เราอาจนำ Class นี้ ไปใช้ในระบบของร้านหนังสืออื่นๆได้ โดยเพียงแค่ทำการสืบทอด Class นี้ไปแล้วเปลี่ยนหรือเพิ่มคุณสมบัติบางอย่างเข้าไป ก็นำไปใช้ได้แล้ว
ผู้เขียน จะขอเลือกวิธีรวมการทำงานเหล่านี้ไว้ใน BookStore เลย เพื่อให้ง่ายต่อการเขียนและการทำความเข้าใจ
การเก็บหนังสือของระบบ
เนื่องจากหนังสือของเรานั้น สามารถมีการเพิ่ม หรือ ลดจำนวนลงได้ ฉะนั้น การเก็บหนังสือด้วยวิธีการใช้ Array นั้น เป็นวิธีการที่ไม่ดีแน่นอน เพราะโดยปกติแล้ว Array จะไม่สามารถเปลี่ยนขนาดของตัวเองหลังจากทำการประกาศไปแล้วได้ และหากต้องการจะเปลี่ยนขนาด ก็ต้องลบข้อมูลเก่าออกไป ก่อน จึงเป็นวิธีที่ไม่ดีแน่ๆ
ฉะนั้น เราจึงต้องใช้ Class จำพวก Collector มาเก็บ(บางภาษาเรียก Container) หรือก็คือ Class ดังต่อไปนี้ List(C#, VB), ArrayList(Java),  list หรือ vector(C++)
เนื่องจาก Class ที่เป็น Collector พวกนี้ มีความสามารถในการเพิ่ม ลด ค้นหา อย่างมากมาย จึงถือเป็นการฉลาด ที่จะเก็บ หนังสือ ไว้ใน Collector พวกนี้
การเพิ่มหนังสือเข้าสู่ระบบ
เนื่องจากทั้งใน List, ArrayList และ list+vector นั้น มีฟังชั่นในการเพิ่ม Object เข้าไปอยู่แล้ว เราจึงใช้ฟังชั่นเหล่านั้นเป็นหลัก โดยอาจจะแค่เพิ่มการทำงานในส่วนของการเช็คไม่ให้มี Primary Key ซ้ำกันก็เพียงพอ ซึ่งการกำหนด Primary Key ของหนังสือนั้น เราจะทำการวิเคราะห์ต่อไปทีหลัง
การลบหนังสือออกจากระบบ
งานนี้ อาจพบไม่บ่อยนัก เพราะเราเป็นร้านเช่า เมื่อลูกค้ายืมไปก็ต้องนำมาคืน ฉะนั้นหนังสือหนึ่งๆ จะอยู่ในระบบตลอดเวลา แค่อยู่กับลูกค้าหรือเราเท่านั้น แต่ก็อาจมีเหตุการณ์บางอย่าง ที่ทำให้หนังสือหลุดจากระบบก็ได้ เช่นหนังสือเหล่านั้น ถูกเรียกเก็บคืนจากสำนักพิมพ์ หรือ การ์ตูนบางเรื่อง ถูกสั่งห้ามขายอีกต่อไปเนื่องจากมีเนื้อหาที่ส่อเสียด(ซึ่งก็มีให้เห็นบ่อยๆ) หรือลูกค้าทำหาย แล้วเราหามาใส่ไม่ได้  งานลบหนังสือ จึงเป็นงานที่จะมองข้ามไม่ได้หากจะให้การออกแบบของเราทำงานได้อย่างครอบคลุม
งานนี้ก็ทำไม่ยากเช่นเดียวกับการเพิ่มหนังสือ เพราะตัวของ Collector ต่างๆนั้นมีความสามารถในการลบ Object ออกไปอยู่แล้ว แต่เราอาจจะต้องเพิ่มขั้นตอนต่างๆเข้าไปบ้าง ในขณะที่กำลังจะลบหนังสือออกไป
การเช่าหนังสือ
ก่อนอื่นต้องมีการตรวจสอบก่อนว่า หนังสือที่จะเช่านั้น ถูกเช่าไปหรือเปล่า เมื่อตรวจสอบเสร็จ ก็มีการคำนวนวันที่ต้องนำมาคืน แล้วก็เก็บเงินกลับลูกค้า ซึ่งลูกค้า อาจจะเช่าหนังสือทีละหลายๆเล่มพร้อมกันก็ได้ อันนี้ต้องวางระบบให้ดี เรื่องเช่าหนังสือก็ถือว่าจบกันไป
แต่พอมองในส่วนของการตรวจสอบว่า หนังสือถูกเช่าไปแล้วหรือยัง หากเรามาวิเคราะห์ดีๆ เราก็จะต้องมานั่งคิดว่า เราจะตรวจสอบอย่างไรว่าหนังสือถูกเช่าไปหรือยัง โดยสามารถแตกออกมาได้ 4 วิธี
1. เราอาจทำเป็น Attribute ใน หนังสือเลยก็ได้ ว่าถูกเช่าอยู่หรือไม่ หากจะตรวจสอบ ก็ทำการตรวจสอบกับหนังสือได้เลย
2. เราอาจทำเป็นลิสต์ขึ้นมาก็ได้ ว่าหนังสือเล่มไหนถูกเช่าอยู่ หากต้องการตรวจสอบ ก็ให้มาตรวจสอบที่ลิสต์นี้
3. ทำทั้งคู่
4. สร้างคลาสขึ้นมาใหม่ ให้เก็บทั้งสถานะการยืม และเก็บหนังสือของตัวเอง แล้วเอา Class นี้ไปเก็บไว้ในลิสต์การยืมอีกที
แน่นอนว่า การทำงาน 2 วิธีแรกนั้น มีข้อดีและข้อเสียแตกต่างกันไปคนละแบบ ซึ่งแน่นอนว่าไม่มีวิธีที่ดีกว่าในทุกสถานการณ์ เราจึงต้องมองข้อดีข้อเสียนั้นให้ออก แล้วทำการเลือกวิธีการทำงานของเรา ส่วนวิธีที่ 3 จะเป็นการลดข้อเสียของทั้งคู่ลงในอัตราหนึ่ง แต่ก็เพิ่มข้อเสียของตัวเองขึ้นมาเช่นกัน ส่วนวิธีที่ 4 เป็นวิธีการลดความซับซ้อนในการทำงานของวิธีที่ 3 แต่ก็ทำให้เราเขียนได้ยุ่งยากขึ้น(การวิเคราะห์เช่นนี้ อาจไม่จำเป็นนักเมื่อเราเก็บหนังสือในรูปแบบของ Database ซึ่งจะกล่าวถึงในบทความถัดๆไป)
วิธีการเก็บเป็น Attribute นั้น มีข้อดีคือจะทำงานได้เร็วและไม่ซับซ้อนเมื่อเราต้องการทราบถึงสถานะการยืมผ่านทางตัวของหนังสือโดยตรง แต่หากเราต้องการทราบว่า หนังสือเล่มไหนในระบบที่ถูกยืมไปบ้าง เราจะต้องทำการค้นหนังสือทั้งหมด เพื่อจะลิสต์รายชื่อหนังสือทั้งหมดที่ถูกยืม ซึ่งหากเรามีหนังสือซัก หมื่นเล่ม การทำงานนี้จะต้องช้ามากอย่างแน่นอน
สำหรับวิธีการสร้างลิสต์ของหนังสือที่ถูกยืมขึ้นมาใหม่นั้น จะทำงานได้เร็วเมื่อต้องการทราบถึงหนังสือทั้งหมดที่ถูกยืมไปว่ามีอะไรบ้าง แต่ในทางกลับกัน เมื่อต้องการทราบว่าหนังสือเล่มใดๆถูกยืมไปหรือไม่ เราก็ต้องทำการค้นในลิสต์ของหนังสือที่ถูกยืม เพื่อจะหาว่าหนังสือที่เราต้องการถูกยืมไปหรือไม่ ซึ่งก็เป็นการช้าอีก
แต่เมื่อมองถึงหลักความเป็นจริง เราจะพบว่า การลิสต์หนังสือที่ถูกยืมทั้งหมดออกมานั้น แทบจะเป็นคำสั่งที่ไม่ถูกใช้งานเลย แต่ในทางกลับกัน การตรวจสอบว่าหนังสือนั้นถูกยืมไปหรือยัง เป็นคำสั่งที่จะถูกเรียกใช้ทุกๆการยืมหรือคืน หรือแม้แต่การค้นหาหนังสือด้วยซ้ำ วิธีที่เหมาะสมจึงควรจะเป็นวิธีที่รวดเร็วเมื่อเราต้องการเข้าถึงสถานะการยืมได้ผ่านหนังสือแต่ละเล่ม
แต่เราก็อาจมองได้ว่า หากเราต้องการทำการสร้างลิสต์หนังสือแล้ว วิธีการเก็บข้อมูลแบบด้านบน จะต้องใช้เวลามากๆในงานหนึ่งๆจนเกินไป และหากเทียบการค้นใน ลิสต์หนังสือทั้งหมด กับการค้นในลิสต์ที่ถูกยืม การค้นในลิสต์ที่ถูกยืม ต้องไวกว่าอย่างแน่นอน(หนังสือที่ถูกยืมมักมีขนาดเพียง 5% ของหนังสือทั้งหมด) ฉะนั้น การเพิ่มเวลาเพียงเล็กน้อยให้งานสั้นๆอย่างการยืมและคืน แม้อาจทำให้เวลาการทำงานทั้งหมดเพิ่มขึ้นเล็กน้อย แต่ก็จะทำให้การทำงานทั้งหมดราบรื่น เพราะไม่มีงานใดๆที่ต้องเสียเวลาอย่างมากอย่างการลิสต์หนังสือที่ถูกยืมในแบบแรก วิธีนี้ จึงเหมาะสมกับผู้ใช้มากกว่า
อันนี้ก็แล้วแต่ผู้ออกแบบ ว่าจะให้ความสำคัญกับสิ่งองค์ประกอบใดมากกว่ากัน แล้วก็ตัดสินใจไป ว่าจะใช้วิธีการไหนในการออกแบบ
ทีนี้เรายังเหลือวิธีการที่ 3 และ 4 อยู่ ซึ่งวิธีที่ 3 นั้น ก็คือการทำทั้งลิสต์ของหนังสือที่ถูกยืมและทำการเก็บสถานะที่ถูกยืมไว้ในหนังสือเช่นกัน เมื่อต้องการนำเสนอสถานะการยืมด้วยวิธีการใดๆ ก็ให้เข้าถึงสถานะด้วยวิธีการที่เหมาะสมกับการกระทำนั้นๆ ได้ แต่ปัญหาที่จะเกิดขึ้นก็คือ การทำให้ในลิสต์ที่ถูกยืม และสถานะในหนังสือตรงกัน อาจต้องเพิ่มความซับซ้อนในการเขียน และจะทำให้เกิดเวลาเพิ่มเล็กน้อยเมื่อจะทำการยืมหรือคืน ก็ได้
วิธีสุดท้าย คือการเปลี่ยนสถานะให้อยู่ในรูปแบบของ Class ซึ่งจะช่วยให้ตัว Class สถานะนี้ สามารถอยู่ได้ทั้ง 2 ที่ ทั้งใน ลิสต์ และในหนังสือ ทำให้เราไม่ต้องกลัวว่าจะเกิดการผิดพลาดในเรื่องของสถานะอีกต่่อไป แต่ข้อเสียคือ การเขียนโค้ดจะยากขึ้นไป รวมทั้งการออกแบบหนังสือ การออกแบบระบบยืมคืน และการออกแบบลิสต์ จะต้องสอดคล้องกันทั้งหมด แต่ถึงอย่างไร วิธีนี้ก็เป็นวิธีที่ครอบคลุม และเกิดปัญหาน้อยที่สุดแล้ว
ทีนี้ก็อยู่ที่นักออกแบบ จะตัดสินใจแล้วละ ว่าจะเก็บข้อมูลการยืมเช่นไร
นอกจากนี้ ยังมีเรื่องของวันที่จะต้องคืน ซึ่งก็จะมีวิเคราะห์ที่คล้ายกับการตรวจสอบสถานะการยืม จึงไม่ขอกล่าวซ้ำไปมากกว่านี้
การคืนหนังสือ
การคืนหนังสือนี้ ไม่มีอะไรมาก มีแค่การคำนวนค่าปรับ และแก้สถานะหนังสือว่าถูกคืนเป็นที่เรียบร้อยแล้ว ก็เป็นอันจบกัน
การแสดงหนังสือทั้งหมด
ก็คือการแสดงสถานะของหนังสือทั้งหมดนั่นแหละ แต่ในความเป็นจริง เราแสดงทุกเล่มพร้อมกันไม่ได้อยู่แล้ว ก็แล้วแต่เราว่า จะค่อยๆแสดงยังไง เช่นหากจะเพิ่มไปในลิสต์ เราจะเพิ่มทีละทั้งหมดเพื่อทำงานรอบเดียว หรือจะเพิ่มในช่วงสั้นๆเฉพาะที่ดูได้เพื่อประหยัดเวลา หรือแม้อาจจะเพิ่มเฉพาะช่วงที่ดูได้ไปก่อน แล้วจึงเพิ่มในส่วนที่ยังมองไม่เห็นโดยการทำงานแบบ Background ต่อไป ก็แล้วแต่ผู้ออกแบบว่าจะทำอย่างไร อันนี้ขอให้ไปวิเคราะห์เพิ่มเติมกันเอาเอง
นอกจากนี้ หากเราต้องการแสดงหนังสือเป็น String แล้ว เราก็ควรที่จะให้ BookStore ของเรา ทำการเรียกรายละเอียดของหนังสือออกมาเป็น String จากหนังสือแต่ละเล่มเช่นกัน ไม่ใช่ทำการเข้าถึง Attribute ของหนังสือแล้วค่อยแปลงออกมาเป็น String ที่อธิบายตรงนี้ ก็เพื่อที่จะให้เราสามารถแบ่งส่วนของงานได้อย่างถูกต้องว่า ส่วนไหน ควรจะเป็นหหน้าที่ของใคร โดยไม่ก้าวก่ายและกินแรงกันและกัน

ในขั้นตอนการออกแบบตอนนี้ คิดว่าคงเยอะแล้ว ในตอนต่อไป เราจะมาออกแบบเชิง Structural กันต่อ (ยังไม่จบเรอะ!!!) เพราะยังเหลือการออกแบบหนังสือที่ยังไม่ได้ทำ และเรายังเหลือการออกแบบอีก 2 แง่มุม นอกจากนี้ การออกแบบเพียงอย่างเดียว ก็ยังไม่เพียงพอที่จะทำให้เราเขียนโปรแกรมไปอย่างถูกทางได้ ฉะนั้นใน Series “ความรู้มากมาย แต่เริ่มต้นเขียนโปรแกรมไม่ถูกซักที” นั้น จะกล่าวขึงเรื่องอื่นนอกจากการออกแบบแน่นอน ขออย่าเพิ่งท้อถอยไปซะก่อน(คนเขียนแหละท้อ OTL) และโปรดติดตามกันต่อไป

Leave a Reply

Your email address will not be published. Required fields are marked *