Modules Should Be Deep (โมดูลควรลึก)

หนึ่งในเทคนิคที่สำคัญที่สุดสำหรับการจัดการความซับซ้อนของซอฟต์แวร์คือการออกแบบระบบเพื่อให้ผู้พัฒนาต้องเผชิญกับความซับซ้อนโดยรวมเพียงส่วนเล็กน้อยในแต่ละช่วงเวลา แนวทางนี้เรียกว่า modular design และบทนี้นำเสนอหลักการพื้นฐานของมัน

4.1 Modular design (การออกแบบโมดูลาร์)

ในการออกแบบโมดูลาร์ ระบบซอฟต์แวร์จะแบ่งออกเป็นชุดของ modules ที่มีความเป็นอิสระค่อนข้างมาก โมดูลสามารถมีได้หลายรูปแบบ เช่น class, subsystems หรือ services ในโลกที่เหมาะสม แต่ละโมดูลจะเป็นอิสระจากโมดูลอื่นโดยสิ้นเชิง: ผู้พัฒนาสามารถทำงานในโมดูลใดก็ได้โดยไม่ต้องรู้อะไรเลยเกี่ยวกับโมดูลอื่นๆ ในโลกเช่นนี้ ความซับซ้อนของระบบจะเป็นความซับซ้อนของโมดูลที่แย่ที่สุด

น่าเสียดายที่อุดมคตินี้ไม่สามารถบรรลุได้ โมดูลต้องทำงานร่วมกันโดยเรียกใช้ฟังก์ชันหรือเมธอดของกันและกัน เป็นผลให้โมดูลต้องรู้บางสิ่งเกี่ยวกับกันและกัน จะมีการขึ้นต่อกัน (dependencies) ระหว่างโมดูล: หากโมดูลหนึ่งเปลี่ยนแปลง โมดูลอื่นๆ อาจต้องเปลี่ยนแปลงไปเพื่อให้สอดคล้องกัน ตัวอย่างเช่น argument ของเมธอดสร้าง dependency ระหว่างเมธอดและโค้ดใดๆ ที่เรียกใช้เมธอดนั้น หากอาร์กิวเมนต์ที่จำเป็นเปลี่ยนแปลง การเรียกใช้เมธอดทั้งหมดจะต้องถูกปรับเพื่อให้สอดคล้องกับ signature ใหม่ Dependencies สามารถมีรูปแบบอื่นๆ ได้อีกมากมาย และอาจค่อนข้างละเอียดอ่อน เป้าหมายของการออกแบบโมดูลาร์คือการลดการขึ้นต่อกันระหว่างโมดูล

เพื่อจัดการ dependencies เราคิดถึงแต่ละโมดูลในสองส่วน: interface และ implementation Interface ประกอบด้วยสิ่งทั้งหมดที่ผู้พัฒนาที่ทำงานในโมดูลอื่นต้องรู้เพื่อใช้โมดูลที่กำหนด โดยปกติแล้ว interface จะอธิบายว่า อะไร ที่โมดูลทำ แต่ไม่ใช่ วิธี ที่มันทำ Implementation ประกอบด้วยโค้ดที่นำสิ่งที่สัญญาโดย interface มาปฏิบัติ ผู้พัฒนาที่ทำงานในโมดูลเฉพาะต้องเข้าใจ interface และ implementation ของโมดูลนั้น บวกกับ interfaces ของโมดูลอื่นๆ ที่เรียกใช้ผ่านโมดูลที่กำหนด ผู้พัฒนาไม่ควรต้องเข้าใจ implementations ของโมดูลอื่นๆ นอกจากโมดูลที่เขาหรือเธอกำลังทำงาน

ยกตัวอย่างโมดูลที่ implements balanced trees โมดูลอาจมีโค้ดที่ซับซ้อนเพื่อให้แน่ใจว่า tree ยังคงสมดุล อย่างไรก็ตาม ความซับซ้อนนี้ไม่ปรากฏให้ผู้ใช้โมดูลเห็น ผู้ใช้เห็น interface ที่ค่อนข้างง่ายสำหรับการเรียกใช้ operations ที่จะ insert, remove และ fetch nodes ในต้นไม้ เพื่อเรียกใช้ insert operation ผู้เรียกต้องระบุเพียงคีย์และค่าสำหรับ node ใหม่ขั้นตอนในการ traverse tree และ split nodes ไม่ปรากฏใน interface

สำหรับวัตถุประสงค์ของหนังสือเล่มนี้ โมดูลคือหน่วยโค้ดใดๆ ที่มี interface และ implementation แต่ละ class ในภาษา object-oriented programming เป็นโมดูล เมธอด (methods) ในคลาสหรือฟังก์ชัน (functions) ในภาษาที่ไม่ใช่ object-oriented ก็สามารถคิดว่าเป็นโมดูลได้: แต่ละตัวมี interface และ implementation และสามารถใช้เทคนิค modular design กับพวกเขาได้ Subsystems และ services ระดับสูงก็เป็นโมดูลเช่นเดียวกัน interfaces ของพวกเขาอาจมีรูปแบบต่างๆ เช่น kernel calls หรือ HTTP requests การอภิปรายเกี่ยวกับ modular design ในหนังสือเล่มนี้ส่วนใหญ่มุ่งเน้นไปที่การออกแบบ classes แต่เทคนิคและแนวคิดนั้นใช้ได้กับโมดูลประเภทอื่นๆ เช่นกัน

โมดูลที่ดีที่สุดคือโมดูลที่มี interfaces ที่ง่ายกว่า implementations มาก โมดูลเหล่านี้มีข้อดีสองประการ ประการแรก interface ที่ง่ายจะลดความซับซ้อนที่โมดูลกำหนดให้กับระบบที่เหลือ ประการที่สอง หากโมดูลถูกปรับปรุงในลักษณะที่ไม่เปลี่ยน interface โมดูลอื่นๆ ก็จะไม่ได้รับผลกระทบจากการปรับปรุง หากโมดูลมี interface ที่ง่ายกว่า implementation มากก็จะมีด้านต่างๆ มากมายของโมดูลที่สามารถเปลี่ยนแปลงได้โดยไม่กระทบต่อโมดูลอื่นๆ

4.2 What's in an interface? (อะไรมีอยู่ใน interface?)

Interface ของโมดูลมี 2 ประเภท: formal และ informal Formal parts ของ interface ถูกระบุไว้อย่างชัดเจนในโค้ด และบางส่วนสามารถตรวจสอบความถูกต้องโดยภาษาโปรแกรมมิ่ง ตัวอย่างเช่น formal interface ของเมธอดคือ signature ของมันซึ่งรวมชื่อและประเภทของ parameters, ประเภทของ return value และข้อมูลเกี่ยวกับ exceptions ที่โยนโดย method ภาษาโปรแกรมมิ่งส่วนใหญ่ให้แน่ใจว่าการเรียกใช้เมธอดแต่ละครั้งระบุจำนวนและประเภทของ arguments ที่ถูกต้องตามการจับคู่ signature Formal interface ของคลาสประกอบด้วย signatures ของ public methods ทั้งหมด บวกกับชื่อและประเภท public variables

Interface แต่ละ interface ยังรวมถึง informal elements ไม่ได้ระบุไว้ในลักษณะที่ภาษาโปรแกรมมิ่งสามารถเข้าใจหรือบังคับใช้ได้ Informal parts ของ interface รวมถึง high-level behavior เช่นข้อเท็จจริงที่ว่าฟังก์ชันลบไฟล์ที่ชื่อโดย arguments หนึ่งของมัน หากมี constraints ในการใช้คลาส (บางที method หนึ่งต้องถูกเรียกก่อนอีกอันหนึ่ง) สิ่งเหล่านี้ก็เป็นส่วนของ interface ของคลาสเช่นกัน โดยทั่วไปแล้ว หากผู้พัฒนาต้องรู้ข้อมูลเฉพาะเพื่อใช้โมดูล ข้อมูลนั้นก็เป็นส่วนของ interface ของโมดูล Informal aspects ของ interface สามารถอธิบายได้เฉพาะโดยใช้ comments และภาษาโปรแกรมมิ่งไม่สามารถให้แน่ใจว่าคำอธิบายนั้นสมบูรณ์หรือถูกต้อง1 สำหรับ interfaces ส่วนใหญ่ informal aspects นั้นใหญ่กว่าและซับซ้อนกว่า formal aspects

หนึ่งในข้อดีของ interface ที่ระบุไว้อย่างชัดเจนคือมันแสดงให้เห็นว่าผู้พัฒนาต้องรู้อะไรแน่ชัดเพื่อใช้โมดูลที่เกี่ยวข้อง สิ่งนี้ช่วยในการกำจัดปัญหา "unknown unknowns" ที่อธิบายไว้ใน Section 2.2

4.3 Abstractions (การแสดงแนวคิด)

คำว่า abstraction มีความเกี่ยวข้องอย่างใกล้ชิดกับแนวคิดของ modular design Abstraction คือมุมมองที่ทำให้เรียบง่ายขึ้นของเอนทิตี ซึ่งละเว้นรายละเอียดที่ไม่สำคัญ Abstractions มีประโยชน์เพราะมันทำให้มันง่ายขึ้นสำหรับเราที่จะคิดและจัดการสิ่งที่ซับซ้อน

ในการโปรแกรมมิ่งแบบโมดูลาร์ แต่ละโมดูลจะมี abstraction ในรูปแบบของ interface ของมัน Interface นำเสนอมุมมองที่ทำให้เรียบง่ายของ functionality ของโมดูล รายละเอียดของ implementation ไม่สำคัญจากมุมมองของ abstraction ของโมดูล ดังนั้นจึงถูกละเว้นจาก interface

ในคำนิยามของ abstraction คำว่า "unimportant" (ไม่สำคัญ) เป็นสิ่งสำคัญ ยิ่งละเว้นรายละเอียดที่ไม่สำคัญจาก abstraction มากเท่าใด abstraction นั้นก็จะยิ่งดีเท่านั้น อย่างไรก็ตาม รายละเอียดสามารถละเว้นจาก abstraction ได้ก็ต่อเมื่อมันไม่สำคัญ Abstraction สามารถผิดพลาดได้ 2 วิธี ประการแรก มันสามารถรวมรายละเอียดที่ไม่จำเป็นต้องสำคัญจริงๆ เมื่อสิ่งนี้เกิดขึ้น มันจะทำให้ abstraction ซับซ้อนมากกว่าที่จำเป็น ซึ่งเพิ่มภาระการรับรู้ (cognitive load) ให้กับผู้พัฒนาที่ใช้ abstraction ข้อผิดพลาดที่สองคือเมื่อ abstraction ละเว้นรายละเอียดที่ค่อนข้างสำคัญจริงๆ สิ่งนี้ส่งผลให้เกิด obscurity: ผู้พัฒนาที่ดูเฉพาะ abstraction จะไม่มีข้อมูลทั้งหมดที่พวกเขาต้องการเพื่อใช้ abstraction อย่างถูกต้อง Abstraction ที่ละเว้นรายละเอียดสำคัญคือ false abstraction: อาจดูเหมือนง่าย แต่ในความเป็นจริงมันไม่ใช่ กุญแจสำหรับการออกแบบ abstractions คือการทำความเข้าใจว่าอะไรคือสำคัญ และการมองหาการออกแบบที่ลดปริมาณข้อมูลที่สำคัญ

ยกตัวอย่างเช่น พิจารณา file system Abstraction ที่จัดเตรียมโดย file system ละเว้นรายละเอียดมากมาย เช่น mechanism สำหรับการเลือก blocks ใดบน storage device ที่จะใช้สำหรับข้อมูลในไฟล์ที่กำหนด รายละเอียดเหล่านี้ไม่สำคัญต่อผู้ใช้ file system (ตราบเท่าที่ระบบให้ performance ที่เพียงพอ) อย่างไรก็ตาม รายละเอียดบางอย่างของ implementation ของ file system นั้นสำคัญต่อผู้ใช้ File systems ส่วนใหญ่จะ cache data ใน main memory และอาจล่าช้าในการเขียน data ใหม่ไปยัง storage device เพื่อปรับปรุง performance บางแอปพลิเคชั่น เช่น databases ต้องรู้ว่าเวลาไหนที่ data จะถูกเขียนผ่านไปยัง storage พอดี เพื่อให้พวกเขาสามารถให้แน่ใจว่า data จะถูกอนุรักษ์ไว้หลังจากการ crashes ของระบบ ดังนั้น rules สำหรับการ flush data ไป secondary storage ต้องปรากฏใน interface ของ file system

เราอาศัย abstractions เพื่อจัดการความซับซ้อนไม่เพียงแต่ในการโปรแกรมมิ่ง แต่เป็นส่วนหนึ่งของชีวิตประจำวันของเรา Microwave oven มี electronics ที่ซับซ้อน เพื่อแปลง alternating current เป็น microwave radiation และกระจาย radiation นั้นไปทั่ว cooking cavity น่าโชคดีที่ผู้ใช้เห็น abstraction ที่ง่ายกว่ามาก ซึ่งประกอบด้วยปุ่มเพียงไม่กี่ปุ่มเพื่อควบคุม timing และ intensity ของ microwaves รถ (Cars) มี abstraction ง่ายๆ ที่ช่วยให้เราสามารถขับรถโดยไม่ต้องเข้าใจ mechanisms สำหรับ electrical motors, battery power management, anti-lock brakes, cruise control และอื่นๆ

4.4 Deep modules (โมดูลที่ลึก)

โมดูลที่ดีที่สุดคือโมดูลที่จัดเตรียม powerful functionality แต่มี simple interfaces ผมใช้คำว่า deep เพื่ออธิบายโมดูลเช่นนี้ เพื่อให้เห็นภาพแนวคิดของความลึก ลองจินตนาการว่าแต่ละโมดูลถูกแสดงโดยรูปสี่เหลี่ยม ดังแสดงใน Figure 4.1 พื้นที่ของแต่ละรูปสี่เหลี่ยมนั้นเป็นสัดส่วนกับ functionality ที่ implements โดยโมดูล ขอบด้านบนของรูปสี่เหลี่ยมแสดง interface ของโมดูล ความยาวของขอบนั้นบ่งชี้ถึงความซับซ้อนของ interface โมดูลที่ดีที่สุดนั้นลึก: มี functionality มากมายซ่อนอยู่เบื้องหลัง simple interface โมดูลที่ลึกคือ good abstraction เพราะเพียงเศษส่วนเล็กน้อยของความซับซ้อนภายในของมันปรากฏต่อผู้ใช้ของมัน

img

Figure 4.1: โมดูลที่ลึกและตื้น โมดูลที่ดีที่สุดคือโมดูลที่ลึก: ให้สิทธิ์ในการเข้าถึง functionality ที่มากมายผ่าน simple interface โมดูลที่ตื้นคือโมดูลที่มี relatively complex interface แต่ไม่มี functionality มากนัก: มันไม่ได้ซ่อนความซับซ้อนมากนัก

Module depth เป็นวิธีการคิดเกี่ยวกับ cost เทียบกับ benefit Benefit ที่จัดเตรียมโดยโมดูลคือ functionality ของมัน Cost ของโมดูล (ในแง่ของความซับซ้อนของระบบ) คือ interface ของมัน Interface ของโมดูลแสดงถึงความซับซ้อนที่โมดูลกำหนดให้กับระบบที่เหลือ: ยิ่ง interface เล็กและง่ายเท่าใด ความซับซ้อนที่มันนำเข้าก็จะน้อยเท่านั้น โมดูลที่ดีที่สุดคือโมดูลที่มี greatest benefit และ least cost Interfaces ดี แต่มากขึ้น หรือ larger interfaces ไม่จำเป็นต้องดีกว่าเสมอไป!

Mechanism สำหรับ file I/O ที่จัดเตรียมโดย Unix operating system และ descendants ของมัน เช่น Linux คือตัวอย่างที่สวยงามของ deep interface มี system calls พื้นฐาน 5 ตัวสำหรับ I/O โดยมี simple signatures:

int open(const char* path, int flags, mode_t permissions);
ssize_t read(int fd, void* buffer, size_t count);
ssize_t write(int fd, const void* buffer, size_t count);
off_t lseek(int fd, off_t offset, int referencePosition);
int close(int fd);

open system call รับ hierarchical file name เช่น /a/b/c และคืนค่า integer file descriptor ซึ่งใช้อ้างอิง open file Arguments อื่นๆ สำหรับ open จัดเตรียม optional information เช่นว่าไฟล์ถูกเปิดสำหรับการอ่านหรือเขียน ว่า file ใหม่ควรถูกสร้างหากไม่มี existing file และ access permissions สำหรับไฟล์ หากไฟล์ใหม่ถูกสร้าง read และ write system calls ถ่ายโอนข้อมูลระหว่างบริเวณ buffer ในหน่วยความจำแอปพลิเคชั่นกับไฟล์ close สิ้นสุดการเข้าถึงไฟล์ ไฟล์ส่วนใหญ่ถูกเข้าถึงแบบ sequential ดังนั้นนั่นคือค่าเริ่มต้น อย่างไรก็ตาม random access สามารถบรรลุได้โดยการเรียกใช้ lseek system call เพื่อเปลี่ยน current access position

Modern implementation ของ Unix I/O interface ต้องการ hundreds of thousands of lines ของโค้ด ซึ่งจัดการกับ complex issues เช่น:

  • ไฟล์แสดงถึง disk อย่างไรเพื่ออนุญาต efficient access?
  • Directories จัดเก็บอย่างไร และ hierarchical path names ประมวลผล อย่างไรเพื่อค้นหาไฟล์ที่พวกเขาหมายถึง?
  • Permissions ถูกบังคับใช้อย่างไร เพื่อว่า user หนึ่งไม่สามารถแก้ไขหรือลบไฟล์ของ user อื่น?
  • File accesses implemented อย่างไร? ตัวอย่างเช่น functionality ถูกแบ่งระหว่าง interrupt handlers และ background code อย่างไร และสององค์ประกอบนี้ communicate safely อย่างไร?
  • Scheduling policies ใดถูกใช้เมื่อมี concurrent accesses ไปยัง multiple files?
  • Recently accessed file data สามารถ cached ในหน่วยความจำเพื่อลด disk accesses ได้อย่างไร?
  • Variety ของ different secondary storage devices เช่น disks และ flash drives สามารถ incorporated เข้าสู่ single file system ได้อย่างไร?

ทั้งหมดของปัญหาเหล่านี้ และอีกมากมาย ถูกจัดการโดย Unix file system implementation พวกเขาไม่ปรากฏให้ programmers เห็นที่เรียกใช้ system calls Implementations ของ Unix I/O interface มี evolved radically ตลอดหลายปี แต่ kernel calls พื้นฐาน 5 ตัวไม่มีการเปลี่ยนแปลง

ตัวอย่างอื่นของ deep module คือ garbage collector ในภาษาเช่น Go หรือ Java โมดูลนี้ไม่มี interface เลย มันทำงานอย่างไม่มองเห็นเบื้องหลังฉากเพื่อ reclaim unused memory การเพิ่ม garbage collection ให้กับระบบนั้น actually shrinks overall interface ของมัน เนื่องจากมันกำจัด interface สำหรับ freeing objects Implementation ของ garbage collector นั้นค่อนข้างซับซ้อน แต่ความซับซ้อนนั้นซ่อนจาก programmers ที่ใช้ภาษา

Deep modules เช่น Unix I/O และ garbage collectors จัดเตรียม powerful abstractions เพราะ พวกเขาง่ายต่อการใช้งาน แต่พวกเขาซ่อน significant implementation complexity

4.5 Shallow modules (โมดูลที่ตื้น)

ในทางกลับกัน shallow module คือโมดูลที่มี interface relatively complex เมื่อเปรียบเทียบกับ functionality ที่มันจัดเตรียม ตัวอย่างเช่น class ที่ implements linked lists นั้นตื้น มันไม่ต้องใช้โค้ดมากเพื่อ manipulate linked list (inserting หรือ deleting element ต้องใช้เพียงไม่กี่บรรทัด) ดังนั้น linked list abstraction ไม่ซ่อนรายละเอียดจำนวนมาก ความซับซ้อนของ linked list interface นั้น nearly as great เท่ากับ implementation ของมัน Shallow classes บางครั้ง unavoidable แต่พวกเขาไม่ได้ช่วยมากในการจัดการความซับซ้อน

นี่คือตัวอย่างที่รุนแรงของ shallow method นำมาจากโปรเจคในชั้นเรียน software design:

private void addNullValueForAttribute(String attribute) {
       data.put(attribute, null);
}

จากมุมมองของการจัดการความซับซ้อน method นี้ทำให้สิ่งต่างๆ แย่ลงแทนที่จะดีขึ้น method นี้ไม่มี abstraction เพราะว่า functionality ทั้งหมดของมันมองเห็นได้ผ่าน interface ตัวอย่างเช่น callers อาจต้องรู้ว่า attribute จะถูกจัดเก็บใน data variable การคิดเกี่ยวกับ interface นั้นไม่ง่ายกว่าการคิดเกี่ยวกับ full implementation หาก method ถูก documented อย่างเหมาะสม documentation จะยาวกว่า code ของ method มันจึง takes more keystrokes เพื่อ invoke method มากกว่าที่จำเป็นต้องมีสำหรับ caller เพื่อ manipulate data variable directly Method เพิ่มความซับซ้อน (ในรูปแบบของ new interface สำหรับ developers ที่จะเรียนรู้) แต่ไม่มี compensating benefit

img Red Flag: Shallow Module img

Shallow module คือ module ที่มี interface ที่ซับซ้อนเกี่ยวข้องกับ functionality ที่มันจัดเตรียม Shallow modules ไม่ได้ช่วยมากในการต่อสู้กับความซับซ้อน เพราะว่า benefit ที่พวกเขาจัดเตรียม (ไม่ต้องเรียนรู้เกี่ยวกับวิธีการทำงานของพวกเขาจากภายใน) ถูก negated โดย cost ของการเรียนรู้และการใช้ interfaces ของพวกเขา Small modules tend to be shallow

4.6 Classitis (คลาสมากเกินไป)

น่าเสียดายที่ value ของ deep classes ไม่ได้รับการชื่นชมอย่างกว้างขวางในปัจจุบัน conventional wisdom ในการโปรแกรมมิ่งคือ classes ควรจะ small ไม่ใช่ deep นักศึกษามักถูกสอนว่าสิ่งที่สำคัญที่สุดในการออกแบบคลาสคือการ break up larger classes เป็น smaller ones คำแนะนำเดียวกันนี้มักให้เกี่ยวกับ methods: "Any method longer than N lines should be divided into multiple methods" (N can be as low as 10) แนวทางนี้ส่งผลให้ large numbers ของ shallow classes และ methods ซึ่ง add to overall system complexity

Extreme ของ "classes should be small" approach คือ syndrome ที่ผมเรียก classitis ซึ่งเกิดจาก mistaken view ว่า "classes ดี ดังนั้น classes มากกว่าจะดีกว่า" ในระบบ suffering from classitis developers ถูกสนับสนุนให้ minimize amount ของ functionality ในแต่ละ new class: ถ้าคุณต้องการ functionality มากขึ้น introduce มากขึ้น classes Classitis อาจส่งผลให้ classes ที่ individually simple แต่มันเพิ่มความซับซ้อนของ overall system Small classes ไม่ contribute much functionality ดังนั้นจึง have to be a lot ของพวกเขา แต่ละ with its own interface Interfaces เหล่านี้ accumulate เพื่อสร้าง tremendous complexity ที่ system level Small classes ยังส่งผลให้ verbose programming style เนื่องจากมี boilerplate required สำหรับแต่ละ class

4.7 Examples: Java and Unix I/O (ตัวอย่าง: Java และ Unix I/O)

หนึ่งในตัวอย่างที่มองเห็นได้ชัดเจนของ classitis ในปัจจุบัน คือ Java class library Java language ไม่ต้องการ lots ของ small classes แต่ culture ของ classitis ดูเหมือนได้เข้ารากลึกใน Java programming community ตัวอย่างเช่น เพื่อเปิด file เพื่อ read serialized objects จากนั้น คุณต้อง create 3 different objects:

FileInputStream fileStream =
new FileInputStream(fileName);
BufferedInputStream bufferedStream =
new BufferedInputStream(fileStream);
ObjectInputStream objectStream =
new ObjectInputStream(bufferedStream);

FileInputStream object จัดเตรียม only rudimentary I/O: มันไม่สามารถ perform buffered I/O ก็ไม่สามารถ read หรือ write serialized objects BufferedInputStream object adds buffering ไปยัง FileInputStream และ ObjectInputStream adds ability เพื่อ read และ write serialized objects 2 objects แรกใน code ด้านบน fileStream และ bufferedStream never used เมื่อ file ได้ถูกเปิด all future operations ใช้ objectStream

มันโดยเฉพาะที่น่ารำคาญ (และ error-prone) ว่า buffering ต้องขอให้ explicitly โดยการสร้าง separate BufferedInputStream object; หาก developer forget เพื่อสร้าง object นี้ จะมี no buffering และ I/O จะ slow maybe Java developers อาจจะเถียงว่า not everyone ต้องการที่จะใช้ buffering สำหรับ file I/O ดังนั้นมันไม่ควรจะ built into base mechanism บางที พวกเขาอาจเถียงว่ามันดีกว่าที่จะเก็บ buffering separate ดังนั้น people สามารถ choose ว่าจะใช้มัน หรือไม่ providing choice ดี แต่ interfaces ควร designed เพื่อทำให้ common case ง่ายที่สุดเท่าที่เป็นไปได้ (ดู formula บน page 6) almost every user ของ file I/O จะต้องการ buffering ดังนั้นมัน should be provided โดยค่าเริ่มต้น สำหรับ those few situations ที่ buffering ไม่ต้องการ library สามารถจัดเตรียม mechanism เพื่อ disable มัน any mechanism สำหรับ disabling buffering ควร cleanly separated ใน interface (สำหรับตัวอย่าง โดยการจัดเตรียม different constructor สำหรับ FileInputStream หรือ through method ที่ disables หรือ replaces buffering mechanism) ดังนั้น that most developers ไม่ต้อง aware แม้กระทั่งของ its existence

ตรงกันข้าม designers ของ Unix system calls ทำให้ common case ง่าย ตัวอย่างเช่น พวกเขารู้ว่า sequential I/O ส่วนใหญ่เป็นที่พบเห็นได้บ่อยที่สุด ดังนั้นพวกเขาทำให้นั่นเป็น default behavior Random access ยังคงค่อนข้างง่ายดำเนินการโดยใช้ lseek system call แต่ developer ทำเฉพาะ sequential access ไม่ต้อง be aware ของ mechanism นั้น หาก interface มี many features แต่ most developers ต้องการ only be aware ของ few ของพวกเขา effective complexity ของ interface นั้นเป็น just complexity ของ commonly used features

4.8 Conclusion (บทสรุป)

โดยการแยก interface ของ module จาก implementation ของมัน เราสามารถ hide complexity ของ implementation จาก rest ของ system Users ของ module ต้องเข้าใจเฉพาะ abstraction ที่จัดเตรียมโดย interface ของมัน ปัญหาที่สำคัญที่สุดใน designing classes และ other modules คือการทำให้พวกเขา deep ดังนั้นที่พวกเขามี simple interfaces สำหรับ common use cases แต่ยังคง provide significant functionality สิ่งนี้ maximize amount ของความซับซ้อนที่ concealed

1มี languages โดยส่วนใหญ่อยู่ใน research community ที่ overall behavior ของ method หรือ function สามารถอธิบายได้อย่างเป็นทางการ using specification language ระบุว่าสามารถตรวจสอบ automatically เพื่อให้แน่ใจว่ามั่นคง match implementation ปัญหาที่น่าสนใจคือว่า such formal specification ก็อาจ replace informal parts ของ interface ความคิดเห็นปัจจุบันของผม คือว่า interface described ใน English ที่อาจจะ intuitive และ understandable มากขึ้น สำหรับ developers มากกว่า one written ใน formal specification language