บทนี้แนะนำอีกวิธีในการคิดเกี่ยวกับการสร้างคลาสที่ลึกขึ้น สมมติว่าคุณกำลังพัฒนาโมดูลใหม่ และค้นพบความซับซ้อนที่หลีกเลี่ยงไม่ได้ส่วนหนึ่ง คำถามคือ วิธีไหนดีกว่า: ควรให้ผู้ใช้โมดูลจัดการกับความซับซ้อนนั้น หรือควรจัดการความซับซ้อนนั้นภายในโมดูลเอง? ถ้าความซับซ้อนนั้นเกี่ยวข้องกับฟังก์ชันการทำงานที่โมดูลมีให้ คำตอบที่สองมักจะเป็นคำตอบที่ถูกต้อง โมดูลส่วนใหญ่มีผู้ใช้งานมากกว่าผู้พัฒนา ดังนั้นจึงดีกว่าที่ผู้พัฒนาจะรับภาระมากกว่าผู้ใช้ ในฐานะผู้พัฒนาโมดูล คุณควรพยายามทำให้ชีวิตของผู้ใช้โมดูลของคุณง่ายที่สุดเท่าที่จะทำได้ แม้ว่านั่นหมายถึงงานพิเศษสำหรับคุณก็ตาม อีกวิธีในการสื่อแนวคิดนี้คือ โมดูลควรมี interface ที่เรียบง่ายสำคัญกว่ามี implementation ที่เรียบง่าย
ในฐานะนักพัฒนา มันง่ายที่จะทำในทางตรงกันข้าม: แก้ปัญหาง่าย ๆ และโยนปัญหายากให้คนอื่น ถ้ามีเงื่อนไขที่คุณไม่แน่ใจว่าจะจัดการยังไง วิธีที่ง่ายที่สุดคือการ throw exception และให้ caller จัดการต่อ ถ้าคุณไม่แน่ใจว่าจะ implement policy แบบไหน ก็สามารถกำหนด configuration parameters สองสามตัวเพื่อควบคุม policy นั้น และปล่อยให้ system administrator หาค่าที่ดีที่สุดให้เอง
แนวทางแบบนี้จะทำให้ชีวิตคุณง่ายขึ้นในระยะสั้น แต่มันขยายความซับซ้อน ให้คนจำนวนมากต้องจัดการกับปัญหานั้นแทนที่จะเป็นแค่คนเดียว ตัวอย่างเช่น ถ้าคลาสหนึ่ง throw exception ทุก caller ของคลาสนั้นจะต้องจัดการกับมัน ถ้าคลาสหนึ่ง export configuration parameters ทุก system administrator ในทุกการติดตั้งจะต้องเรียนรู้วิธีการตั้งค่าพวกมัน
8.1 Example: editor text class (ตัวอย่าง: คลาสจัดการข้อความของ editor)
ลองพิจารณาคลาสที่จัดการข้อความของไฟล์สำหรับ GUI text editor ซึ่งถูกพูดถึงใน Chapter 6 และ 7 . คลาสนี้ให้ methods ในการอ่านไฟล์จาก disk เข้ามาใน memory, query และแก้ไขสำเนาของไฟล์ใน memory, และเขียนเวอร์ชันที่แก้ไขแล้วกลับไปยัง disk เมื่อนักศึกษาต้อง implement คลาสนี้ หลายคนเลือกใช้ line-oriented interface ที่มี methods สำหรับอ่าน, แทรก, และลบทั้งบรรทัดของข้อความ ซึ่งส่งผลให้ implementation ของคลาสง่าย แต่สร้างความซับซ้อนให้กับซอฟต์แวร์ระดับสูงกว่า ในระดับของ user interface การดำเนินการส่วนใหญ่ไม่เกี่ยวข้องกับทั้งบรรทัด ตัวอย่างเช่น การกดแป้นพิมพ์ทำให้มีการแทรกตัวอักษรแต่ละตัวภายในบรรทัดที่มีอยู่ การ copy หรือลบ selection สามารถแก้ไขส่วนของหลายบรรทัดที่แตกต่างกันได้ ด้วย line-oriented text interface ซอฟต์แวร์ระดับสูงกว่าต้อง split และ join บรรทัดเพื่อ implement user interface
Character-oriented interface อย่างที่อธิบายไว้ใน Section 6.3 ดึงความซับซ้อนลง (pulls complexity downward) ซอฟต์แวร์ user interface สามารถ insert และลบช่วงข้อความใด ๆ ได้โดยไม่ต้อง split และ merge บรรทัด ทำให้มันง่ายขึ้น implementation ของ text class อาจซับซ้อนขึ้น: ถ้ามันแทนข้อความภายในเป็น collection ของบรรทัด มันจะต้อง split และ merge บรรทัดเพื่อ implement การดำเนินการแบบ character-oriented วิธีนี้ดีกว่าเพราะมัน encapsulate ความซับซ้อนของการ split และ merge ไว้ภายใน text class ซึ่งลดความซับซ้อนโดยรวมของระบบ
8.2 Example: configuration parameters (ตัวอย่าง: configuration parameters)
Configuration parameters เป็นตัวอย่างของการย้ายความซับซ้อนขึ้นไปข้างบนแทนที่จะลงข้างล่าง แทนที่จะกำหนดพฤติกรรมเฉพาะภายในคลาส คลาสสามารถ export parameters สองสามตัวที่ควบคุมพฤติกรรมของมัน เช่น ขนาดของ cache หรือจำนวนครั้งที่จะ retry request ก่อนจะยอมแพ้ ผู้ใช้คลาสจะต้องระบุค่าที่เหมาะสมสำหรับ parameters เหล่านั้น Configuration parameters ได้รับความนิยมอย่างมากในระบบปัจจุบัน บางระบบมีเป็นร้อยๆ ตัว
ผู้สนับสนุนโต้แย้งว่า configuration parameters เป็นสิ่งที่ดีเพราะมันช่วยให้ผู้ใช้ปรับแต่งระบบตามความต้องการและปริมาณงานเฉพาะของพวกเขา ในบางสถานการณ์ เป็นเรื่องยากที่ low-level infrastructure code จะรู้ policy ที่ดีที่สุดที่จะใช้ ในขณะที่ผู้ใช้คุ้นเคยกับ domain ของพวกเขามากกว่า ตัวอย่างเช่น ผู้ใช้อาจรู้ว่า request บางตัวต้องการเวลามากกว่าตัวอื่น ดังนั้นจึงสมเหตุสมผลที่ผู้ใช้จะระบุ priority ที่สูงกว่าสำหรับ request เหล่านั้น ในสถานการณ์แบบนี้ configuration parameters สามารถส่งผลให้ประสิทธิภาพดีขึ้นใน domain ที่หลากหลายมากขึ้น
อย่างไรก็ตาม configuration parameters ก็เป็นข้ออ้างง่ายๆ ในการหลีกเลี่ยงการจัดการกับประเด็นสำคัญและส่งต่อให้คนอื่น ในหลายกรณี ผู้ใช้หรือ administrator ไม่สามารถหรือยากที่จะกำหนดค่าที่ถูกต้องสำหรับ parameters ในกรณีอื่นๆ ค่าที่ถูกต้องสามารถถูกกำหนดได้โดยอัตโนมัติหากมีความพยายามเพิ่มเติมอีกเล็กน้อยในการ implement ระบบ ลองพิจารณา network protocol ที่ต้องจัดการกับแพ็กเก็ตที่สูญหาย ถ้ามันส่ง request แต่ไม่ได้รับการตอบกลับภายในช่วงเวลาหนึ่ง มันจะส่ง request ซ้ำ วิธีหนึ่งในการกำหนด retry interval คือการใส่ configuration parameter อย่างไรก็ตาม transport protocol สามารถคำนวณค่าที่สมเหตุสมผลได้เองโดยการวัด response time สำหรับ request ที่สำเร็จ และใช้ค่าหลายเท่าของค่านี้เป็น retry interval วิธีนี้ดึงความซับซ้อนลง (pulls complexity downward) และช่วยผู้ใช้ไม่ต้องคิดหา retry interval ที่ถูกต้อง มันยังมีข้อดีเพิ่มเติมคือการคำนวณ retry interval แบบ dynamic ดังนั้นมันจะปรับตัวเองโดยอัตโนมัติถ้าสภาวะการทำงานเปลี่ยนแปลง ในทางตรงกันข้าม configuration parameters อาจล้าสมัยได้ง่าย
ดังนั้น คุณควรหลีกเลี่ยง configuration parameters เท่าที่จะทำได้ ก่อนที่จะ export configuration parameter ให้ถามตัวเองว่า "ผู้ใช้ (หรือ higher-level modules) จะสามารถกำหนดค่าที่ดีกว่าที่เรากำหนดได้ที่นี่หรือไม่?" เมื่อคุณสร้าง configuration parameters ให้ดูว่าคุณสามารถคำนวณค่าเริ่มต้นที่สมเหตุสมผลโดยอัตโนมัติได้หรือไม่ เพื่อให้ผู้ใช้จำเป็นต้องระบุค่าเฉพาะในสภาวะพิเศษเท่านั้น ตามหลักการแล้ว แต่ละโมดูลควรแก้ปัญหาให้สมบูรณ์ configuration parameters ทำให้เกิดโซลูชันที่ไม่สมบูรณ์ ซึ่งเพิ่มความซับซ้อนให้กับระบบ
8.3 Taking it too far (การทำมากเกินไป)
ใช้วิจารณญาณเมื่อดึงความซับซ้อนลง แนวคิดนี้สามารถถูกใช้มากเกินไปได้ง่าย วิธีการที่รุนแรงคือการดึงฟังก์ชันการทำงานทั้งหมดของแอปพลิเคชันลงไปไว้ในคลาสเดียว ซึ่งเห็นได้ชัดว่าไม่สมเหตุสมผล การดึงความซับซ้อนลงจะเหมาะสมที่สุดเมื่อ (a) ความซับซ้อนที่กำลังถูกดึงลงมีความเกี่ยวข้องอย่างใกล้ชิดกับฟังก์ชันการทำงานที่มีอยู่ของคลาส (b) การดึงความซับซ้อนลงจะทำให้เกิดความเรียบง่ายขึ้นมากมายในที่อื่นๆ ของแอปพลิเคชัน และ (c) การดึงความซับซ้อนลงทำให้ interface ของคลาสเรียบง่ายขึ้น จำไว้ว่าเป้าหมายคือการลดความซับซ้อนโดยรวมของระบบ
Chapter 6 อธิบายว่านักศึกษาบางคนกำหนด methods ใน text class ที่สะท้อน user interface เช่น method ที่ implement ฟังก์ชันการทำงานของปุ่ม backspace อาจดูเหมือนว่าสิ่งนี้ดีเพราะมันดึงความซับซ้อนลง อย่างไรก็ตาม การเพิ่มความรู้เกี่ยวกับ user interface เข้าไปใน text class ไม่ได้ทำให้ higher-level code ง่ายขึ้นมากนัก และความรู้ด้าน user interface นั้นไม่เกี่ยวข้องกับฟังก์ชันหลักของ text class ในกรณีนี้ การดึงความซับซ้อนลงแค่ทำให้เกิด information leakage
8.4 Conclusion (บทสรุป)
เมื่อพัฒนาโมดูล ให้มองหาโอกาสที่จะรับภาระเพิ่มอีกเล็กน้อยด้วยตัวคุณเอง เพื่อลดความลำบากของผู้ใช้ของคุณ