Pull Complexity Downwards (ดึงความซับซ้อนลงมา)

บทนี้นำเสนออีกวิธีการคิดเกี่ยวกับวิธีสร้างคลาสที่ลึกกว่า สมมติว่าคุณกำลังพัฒนา Module ใหม่ แล้วคุณพบความซับซ้อนที่หลีกเลี่ยงไม่ได้ คำถามคือ: ควรให้ผู้ใช้ Module รับมือกับความซับซ้อนนี้ หรือควรให้ Module จัดการมันภายในตัวเองดี? ถ้าความซับซ้อนนั้นเกี่ยวข้องกับ Functionality ที่ Module ให้บริการ คำตอบที่สอง มักจะเป็นคำตอบที่ถูกต้อง เนื่องจาก Module ส่วนใหญ่มีผู้ใช้มากกว่า Developer จึงดีกว่าให้ Developer ทุ่มเท แทนที่จะให้ผู้ใช้ทุกคนทุ่มเท ผม ในฐานะ Module Developer ควรพยายามให้ชีวิตของผู้ใช้ Module ง่ายที่สุดเท่าที่เป็นไปได้ แม้ว่าจะต้องทำงานเพิ่มเติมสำหรับตัวเอง วิธีหนึ่งในการแสดงความคิดนี้คือ สำหรับ Module มันสำคัญกว่าที่จะมี Interface ที่เรียบง่าย แทนที่จะเป็น Implementation ที่เรียบง่าย

ในฐานะ Developer มันหลีกเลี่ยงไม่ได้ที่จะรู้สึกอยากทำตรงกันข้าม: แก้ไขปัญหาที่ง่ายและปล่อยปัญหาหนักให้คนอื่น ถ้ามีสถานการณ์ที่คุณไม่แน่ใจว่าจะจัดการอย่างไร วิธีที่ง่ายที่สุดคือโยน Exception และให้ Caller จัดการ ถ้าคุณไม่แน่ใจว่าจะ Implement Policy อะไร คุณสามารถ Define Parameter หลายตัวเพื่อควบคุม Policy แล้วปล่อยให้ System Administrator คิดหาค่าที่ดีที่สุด

วิธีการเหล่านี้จะทำให้ชีวิตคุณง่ายขึ้นในระยะสั้น แต่มันจะเพิ่มความซับซ้อน ทำให้หลายคนต้องรับมือกับปัญหาแทนที่จะเป็นเพียงคนเดียว ตัวอย่างเช่น ถ้า Class โยน Exception ผู้เรียก Class ทั้งหมดจะต้องจัดการมัน ถ้า Class Export Configuration Parameter ทุก System Administrator ในทุก Installation จะต้องเรียนรู้วิธี Set ค่า

8.1 Example: editor text class (ตัวอย่าง: Text Class ของตัวแก้ไขข้อความ)

พิจารณา Class ที่จัดการข้อความของไฟล์สำหรับตัวแก้ไขข้อความ GUI ซึ่งได้กล่าวถึง บทที่ 6 และ 7 Class นี้มี Method สำหรับอ่านไฟล์จากดิสก์ลงในหน่วยความจำ สอบถาม และ Modify สำเนาในหน่วยความจำของไฟล์ และเขียน Version ที่ถูก Modify กลับไปที่ดิสก์ เมื่อนักเรียนต้อง Implement Class นี้ หลายคนเลือก Interface ที่ Oriented ตามบรรทัด ด้วย Method สำหรับอ่าน แทรก และลบบรรทัดข้อความทั้งหมด ซึ่งส่งผลให้ Implementation ของ Class เรียบง่าย แต่สร้าง Complexity สำหรับซอฟต์แวร์ระดับสูง ในระดับ User Interface การดำเนินการไม่ค่อยเกี่ยวข้องกับบรรทัดทั้งหมด ตัวอย่างเช่น Keystroke ทำให้อักขระแต่ละตัวถูกแทรกในบรรทัดที่มีอยู่; การคัดลอกหรือลบ Selection สามารถ Modify บางส่วนของหลายบรรทัด ด้วย Line-Oriented Text Interface ซอฟต์แวร์ระดับสูงต้อง Split และ Join บรรทัดเพื่อ Implement User Interface

Character-Oriented Interface เช่นตัวที่อธิบายไว้ใน Section 6.3 ดึง Complexity ลงมา User Interface Software สามารถแทรก และลบ Range ของข้อความใด ๆ โดยไม่ต้อง Split และ Merge บรรทัด ดังนั้นจึงง่ายขึ้น Implementation ของ Text Class อาจจะ Complex มากขึ้น: ถ้า Represent ข้อความภายในเป็น Collection ของ Line มันจะต้อง Split และ Merge Line เพื่อ Implement Character-Oriented Operation วิธีการนี้ดีกว่า เนื่องจาก Encapsulate Complexity ของ Splitting และ Merging ใน Text Class ซึ่งลด Overall System Complexity

8.2 Example: configuration parameters (ตัวอย่าง: Configuration Parameter)

Configuration Parameter เป็นตัวอย่างของการ Move Complexity ขึ้นไป แทนที่จะ Determine Behavior ใดคนหนึ่ง ภายใน Class สามารถ Export Parameter ไม่กี่ตัวที่ Control Behavior ได้ เช่น ขนาดของ Cache หรือจำนวนครั้งที่จะลองใหม่ Request ก่อน Giving Up ผู้ใช้ Class ต้องระบุค่าที่เหมาะสมสำหรับ Parameter Configuration Parameter กลายมาเป็นที่นิยมมากในระบบวันนี้; บางระบบมี Parameter หลายร้อยตัว

ผู้สนับสนุน Argue ว่า Configuration Parameter ดีเนื่องจากให้ผู้ใช้ Tune System สำหรับ Requirement และ Workload เฉพาะ ในบางสถานการณ์ Code Infrastructure ระดับต่าจะ Hard ที่จะรู้ว่า Policy ที่ดีที่สุดคืออะไร ขณะที่ผู้ใช้ Familiar กับ Domain ของพวกเขามากกว่า ตัวอย่างเช่น ผู้ใช้อาจรู้ว่า Request บางตัวมี Time-Critical มากกว่า ดังนั้น Make Sense ให้ผู้ใช้ Specify Priority ที่สูงกว่าสำหรับ Request เหล่านั้น ในสถานการณ์แบบนี้ Configuration Parameter สามารถ Result ในประสิทธิภาพที่ดีขึ้นใน Domain ที่หลากหลาย

อย่างไรก็ตาม Configuration Parameter ยังให้ Excuse ที่ง่าย ในการหลีกเลี่ยงการ Deal กับ Issue ที่สำคัญ และ Pass ให้คนอื่น ในหลายกรณี Hard หรือ Impossible สำหรับผู้ใช้หรือ Administrator ที่จะ Determine ค่าที่ถูกต้อง ในกรณีอื่น ค่าที่ถูกต้องสามารถ Determine Automatically ได้ด้วย Extra Work ในระบบ Implementation พิจารณา Network Protocol ที่ต้อง Deal กับ Lost Packet ถ้า Send Request แต่ไม่ได้ Receive Response ภายใน Time Period ใดคนหนึ่ง มันจะ Resend Request วิธีหนึ่งในการ Determine Retry Interval คือ Introduce Configuration Parameter อย่างไรก็ตาม Transport Protocol สามารถ Compute Reasonable Value ได้ด้วยตัวเอง โดยการ Measure Response Time สำหรับ Request ที่ Success แล้วใช้ Multiple ของ Response Time นี้สำหรับ Retry Interval วิธีการนี้ Pull Complexity ลงมา และ Save ผู้ใช้จากการ Figure Out Retry Interval ที่ถูกต้อง มันมี Additional Advantage ของการ Compute Retry Interval Dynamically ดังนั้นมันจะ Adjust Automatically ถ้า Operating Condition เปลี่ยน ในทางตรงกันข้าม Configuration Parameter สามารถ Easily Become Out of Date

ดังนั้น คุณควร Avoid Configuration Parameter มากที่สุดเท่าที่เป็นไปได้ ก่อนที่ Export Configuration Parameter ให้ถามตัวเองว่า: "ผู้ใช้ (หรือ Module ระดับสูง) สามารถ Determine ค่าที่ดีกว่าสิ่งที่เราสามารถ Determine ที่นี่ได้หรือไม่?" เมื่อคุณ Create Configuration Parameter Verify ว่าคุณสามารถ Compute Reasonable Default Automatically ได้ ดังนั้นผู้ใช้จะต้อง Provide Value เฉพาะภายใต้ Exceptional Condition Ideally แต่ละ Module ควรจะ Solve ปัญหาทั้งหมด Configuration Parameter Result ใน Incomplete Solution ซึ่ง Add ไป System Complexity

8.3 Taking it too far (ไปไกลเกินไป)

Use Discretion เมื่อ Pull Complexity ลงมา; นี่คือความคิดที่สามารถ Overdone ได้ง่าย Extreme Approach จะเป็น Pull Functionality ทั้งหมดของ Application ทั้งหมด ลงไปใน Class เดียว ซึ่ง Clearly ไม่ Make Sense Pull Complexity Down Make Sense ที่สุด ถ้า (a) Complexity ที่ Pull Down เกี่ยวข้องกันอย่างใกล้ชิด กับ Existing Functionality ของ Class (b) Pull Complexity Down จะ Result ใน Simplification มากมาย ที่อื่น ๆ ใน Application และ (c) Pull Complexity Down Simplify Interface ของ Class Remember ว่า Goal คือ Minimize Overall System Complexity

บทที่ 6 Describe ว่า นักเรียนบางคน Define Method ใน Text Class ที่ Reflect User Interface เช่น Method ที่ Implement Functionality ของ Backspace Key ดูเหมือนว่า นี่ดี เนื่องจาก Pull Complexity Down อย่างไรก็ตาม Adding Knowledge ของ User Interface ไป Text Class ไม่ Simplify Higher-Level Code มากมาย และ User-Interface Knowledge ไม่เกี่ยวข้องกับ Core Function ของ Text Class ในกรณีนี้ Pull Complexity Down Just Result ใน Information Leakage

8.4 Conclusion (สรุป)

เมื่อ Develop Module Seek Opportunity ให้ Sacrifice ตัวเอง เล็กน้อย เพื่อ Reduce Suffering ของ User ของคุณ