General-Purpose Modules are Deeper (โมดูลวัตถุประสงค์ทั่วไปมีความลึกกว่า)

เมื่อออกแบบโมดูลใหม่ หนึ่งในการตัดสินใจที่พบบ่อยที่สุดคือ ต้องเลือกว่าจะสร้างมันเพื่อใช้วัตถุประสงค์ทั่วไปหรือเฉพาะ บางคนยืนยันว่าควรใช้แนวทางวัตถุประสงค์ทั่วไป ซึ่งสร้างกลไกที่ตอบสนองต่อปัญหาที่หลากหลายกว้าง ไม่ใช่เพียงปัญหาที่สำคัญวันนี้ โดยหวังว่ากลไกใหม่จะมีการใช้งานที่ไม่คาดคิดในอนาคต ช่วยประหยัดเวลา แนวทางนี้สอดคล้องกับ Mindset การลงทุนที่กล่าวในคณิตศาสตร์ Chapter 3 ซึ่งแสดงว่าการใช้เวลามากตั้งแต่แรกช่วยประหยัดเวลาต่อไป

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

6.1 Make classes somewhat general-purpose (ทำให้ Classes มีวัตถุประสงค์ค่อนข้างทั่วไป)

จากประสบการณ์ของผม จุดแลกเปลี่ยนที่ดีที่สุดคือ สร้าง Module ใหม่ในลักษณะ somewhat general-purpose (วัตถุประสงค์ค่อนข้างทั่วไป) หมายความว่าฟังก์ชันของ Module ควรสะท้อนความต้องการปัจจุบัน แต่ Interface ไม่ควร Interface ควรเป็นแบบทั่วไปพอที่รองรับการใช้งานหลายแบบ และใช้งานง่ายสำหรับความต้องการวันนี้โดยไม่ผูกติดกับความต้องการนั้นโดยตรง คำว่า "somewhat" สำคัญ อย่าละเลยทำให้บางสิ่งทั่วไปจนใช้งานยากสำหรับความต้องการปัจจุบัน

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

6.2 Example: storing text for an editor (ตัวอย่าง: เก็บข้อความสำหรับ Editor)

ลองพิจารณาตัวอย่างจากชั้นเรียน Software Design ซึ่ง Student ถูกขอให้สร้าง Text Editor แบบ GUI แบบง่าย Editor ต้องแสดงไฟล์และให้ User ชี้ คลิก และพิมพ์เพื่อแก้ไข ต้องรองรับมุมมองของไฟล์เดียวกันพร้อมกันในหน้าต่างต่างๆ พร้อมทั้ง Undo และ Redo หลายระดับ

ทุก Project ของ Student มี Class ที่จัดการข้อความพื้นฐาน Text Class นี้มักให้ Methods สำหรับ Load ไฟล์เข้า Memory Read และ Modify ข้อความ และ Write กลับไปยังไฟล์

Student Teams จำนวนมากสร้าง APIs เฉพาะสำหรับ Text Class พวกเขารู้ว่า Class นี้จะใช้ใน Interactive Editor ดังนั้นจึงคิดถึง Features ที่ Editor ต้องให้ และปรับแต่ง API ให้เหมาะสมกับ Features นั้น ตัวอย่างเช่น เมื่อ User พิมพ์ Backspace Key Editor ลบ Character ทางซ้าย Cursor; Delete Key ลบ Character ทางขวา เมื่อรู้เรื่องนี้ Teams สร้าง Method ใน Text Class สำหรับแต่ละ Feature:

void backspace(Cursor cursor);
void delete(Cursor cursor);

แต่ละ Method รับ Cursor Position; Special Type Cursor แทน Position Editor ต้องรองรับ Selection ที่ Copy หรือ Delete ได้ Student จึง Define Selection Class และ Pass Object ไปยัง Text Class ระหว่าง Deletion:

void deleteSelection(Selection selection);

Student อาจคิดว่าจะง่ายกว่าสร้าง User Interface หาก Methods ของ Text Class ตรงกับ Features ที่ User เห็น แต่จริงๆแล้ว Specialization นี้ให้ประโยชน์น้อยต่อ User Interface Code และสร้าง Cognitive Load สูงสำหรับ Developers ที่ทำงาน User Interface หรือ Text Class Text Class ลงเอยด้วย Methods จำนวนมากที่ Shallow แต่ละ Methods เหมาะสำหรับ User Interface Operation เพียงตัวเดียว Methods หลายตัว เช่น delete ถูกเรียกแค่ครั้งเดียว ผลก็คือ Developer ที่ทำงาน User Interface ต้อง Learn Methods จำนวนมากของ Text Class

Approach นี้สร้าง Information Leakage ระหว่าง User Interface และ Text Class Abstractions ของ User Interface เช่น Selection หรือ Backspace Key ปรากฏใน Text Class; มันเพิ่ม Cognitive Load สำหรับ Developers ที่ทำงาน Text Class ทุก User Interface Operation ใหม่ต้อง Define Method ใหม่ใน Text Class ดังนั้น Developer ที่ทำงาน User Interface อาจลงเอยทำงาน Text Class ด้วย หนึ่งในเป้าหมายของ Class Design คือ ให้แต่ละ Class ได้รับการพัฒนาอย่างอิสระ แต่ Specialized Approach นี้ผูก User Interface และ Text Classes เข้าด้วยกัน

6.3 A more general-purpose API (API ที่ทั่วไปมากขึ้น)

Approach ที่ดีกว่าคือ ทำให้ Text Class เป็น Generic มากขึ้น API ควร Define ในแง่ของ Basic Text Features เท่านั้น โดยไม่สะท้อน Higher-level Operations ที่จะ Implement ด้วยมัน ตัวอย่างเช่น Methods สองตัวเท่านั้นต้องการสำหรับ Modify Text:

void insert(Position position, String newText);
void delete(Position start, Position end);

Method แรก Insert String ใดๆ ณ Position ใดๆ ภายใน Text Method ที่สอง Delete ทั้งหมด Characters ที่ Positions Greater Than หรือ Equal To start แต่ Less Than end API นี้ใช้ Generic Type Position แทน Cursor ซึ่งสะท้อน Specific User Interface Text Class ควรให้ General-purpose Facilities สำหรับ Manipulating Positions ภายใน Text เช่น:

Position changePosition(Position position, int numChars);

Method นี้ Return New Position ที่ Given Number ของ Characters ห่างจาก Given Position ถ้า numChars เป็น Positive New Position จะอยู่ Later ใน File กว่า position; ถ้าเป็น Negative New Position จะอยู่ Before position Method Automatically Skip ไป Next หรือ Previous Line เมื่อจำเป็น ด้วย Methods เหล่านี้ Delete Key สามารถ Implement ด้วย Code นี้ (Assuming cursor Variable Hold Current Cursor Position):

text.delete(cursor, text.changePosition(cursor, 1));

ในทำนองเดียวกัน Backspace Key สามารถ Implement ดังนี้:

text.delete(text.changePosition(cursor, -1), cursor);

ด้วย General-purpose Text API Code ที่ Implement User Interface Functions เช่น Delete และ Backspace จะยาวกว่า Original Approach ที่ใช้ Specialized Text API เล็กน้อย แต่ Code ใหม่นี้ Obvious กว่า Code เดิมมาก Developer ที่ทำงาน User Interface Module สนใจ Characters ที่ Deleted ด้วย Backspace Key ด้วย Code ใหม่นี้ Obvious ทันที ด้วย Code เดิม Developer ต้องไป Text Class และ Read Documentation และ/หรือ Code ของ backspace Method เพื่อ Verify Behavior นอกจากนี้ General-purpose Approach มี Code น้อยกว่า Specialized Approach โดยรวม เพราะแทนที่ Large Number ของ Special-purpose Methods ใน Text Class ด้วย Smaller Number ของ General-purpose Ones

Text Class ที่ Implement ด้วย General-purpose Interface อาจถูกใช้สำหรับ Purposes นอกเหนือจาก Interactive Editor ตัวอย่างเช่น สมมติคุณ Building Application ที่ Modify Specified File โดย Replace ทั้งหมด Occurrences ของ Particular String ด้วย Another String Methods จาก Specialized Text Class เช่น backspace และ delete จะมี Little Value สำหรับ Application นี้ แต่ General-purpose Text Class จะมี Most ของ Functionality ที่ต้องการแล้ว สิ่งเดียวที่ Missing คือ Method เพื่อ Search สำหรับ Next Occurrence ของ Given String เช่น:

Position findNext(Position start, String string);

แน่นอนว่า Interactive Text Editor อาจมี Mechanism สำหรับ Searching และ Replacing แล้ว ซึ่ง Case นี้ Text Class อยู่แล้ว Include Method นี้

6.4 Generality leads to better information hiding (ความทั่วไปนำไปสู่ Information Hiding ที่ดีขึ้น)

General-purpose Approach ให้ Cleaner Separation ระหว่าง Text และ User Interface Classes ซึ่งสร้าง Better Information Hiding Text Class ไม่ต้อง Aware ของ Specifics ของ User Interface เช่น How Backspace Key ถูก Handle; Details เหล่านี้ Encapsulated ใน User Interface Class New User Interface Features สามารถ Add ได้โดยไม่ต้อง Create New Supporting Functions ใน Text Class General-purpose Interface ยัง Reduce Cognitive Load: Developer ที่ทำงาน User Interface Module ต้อง Learn เพียงสองสามวิธีง่ายๆ ซึ่ง Reuse ได้สำหรับ Variety ของ Purposes

backspace Method ใน Original Version ของ Text Class เป็น False Abstraction มัน Purport ที่จะ Hide Information เกี่ยวกับ Characters ที่ถูก Deleted แต่ User Interface Module Really Need ที่จะรู้นี้; User Interface Developers Likely Read Code ของ backspace Method เพื่อ Confirm Precise Behavior Putting Method ใน Text Class Just Makes It Harder สำหรับ User Interface Developers ที่จะ Get Information ที่พวกเขา Need หนึ่งในส่วนที่สำคัญที่สุดของ Software Design คือ Determining Who Needs Know What และ When เมื่อ Details Important ดีกว่า Make Them Explicit และ Obvious เช่นเดียวกับ Revised Implementation ของ Backspace Operation Hiding Information Behind Interface Just Create Obscurity

6.5 Questions to ask yourself (คำถามที่ควรถามตัวเอง)

มันง่ายกว่าที่จะ Recognize Clean General-purpose Class Design มากกว่า Create One นี่เป็น Questions ที่คุณสามารถถาม Yourself ซึ่งจะ Help You Find Right Balance ระหว่าง General-purpose และ Special-purpose สำหรับ Interface

What is the simplest interface that will cover all my current needs? (Interface ที่ง่ายที่สุดที่จะ Cover ความต้องการปัจจุบันทั้งหมดของฉันคืออะไร?) ถ้าคุณ Reduce Number ของ Methods ใน API โดยไม่ Reduce Overall Capabilities Then คุณ Probably Create More General-purpose Methods Specialized Text API มี At Least Three Methods สำหรับ Deleting Text: backspace, delete, และ deleteSelection More General-purpose API มี Only One Method สำหรับ Deleting Text ซึ่ง Served All Three Purposes Reducing Number ของ Methods Makes Sense เพียงเท่าที่ API สำหรับแต่ละ Individual Method Stay Simple; ถ้าคุณ Introduce Lots ของ Additional Arguments เพื่อ Reduce Number ของ Methods Then คุณ May ไม่ Really Be Simplifying Things

In how many situations will this method be used? (Method นี้จะถูกใช้ในกี่สถานการณ์?) ถ้า Method Designed สำหรับ One Particular Use เช่น backspace Method That Red Flag ที่อาจ Too Special-purpose See ถ้าคุณสามารถ Replace Several Special-purpose Methods ด้วย Single General-purpose Method

Is this API easy to use for my current needs? (API นี้ใช้งานง่ายสำหรับความต้องการปัจจุบันของฉันหรือไม่?) Question นี้สามารถ Help You Determine When You Have Gone Too Far ใน Making API Simple และ General-purpose ถ้าคุณ Write Lot ของ Additional Code เพื่อใช้ Class สำหรับ Current Purpose That Red Flag ที่ Interface ไม่ Provide Right Functionality ตัวอย่างเช่น One Approach สำหรับ Text Class เป็น Design It เกี่ยวกับ Single-character Operations: insert Inserts Single Character และ delete Deletes Single Character API นี้ Simple และ General-purpose However It ไม่ Particularly Easy ใช้สำหรับ Text Editor: Higher-level Code จะ Contain Lots ของ Loops ที่ Insert หรือ Delete Ranges ของ Characters Single-character Approach จะ Also Inefficient สำหรับ Large Operations Thus It Better สำหรับ Text Class ที่ Built-in Support สำหรับ Operations บน Ranges ของ Characters

6.6 Conclusion (สรุป)

General-purpose Interfaces มี Many Advantages Over Special-purpose Ones They Tend Be Simpler ด้วย Fewer Methods That Deeper They Also Provide Cleaner Separation ระหว่าง Classes ขณะ Special-purpose Interfaces Tend Leak Information ระหว่าง Classes Making Your Modules Somewhat General-purpose คือ One ของ Best Ways ที่จะ Reduce Overall System Complexity