Information Hiding (and Leakage) (การซ่อนข้อมูลและการรั่วไหล)

บทที่ 4 โต้แย้งว่า Module ควรจะลึก บทนี้และบทต่อๆ ไปอีกสองสามบท จะอธิบายเทคนิคต่างๆ สำหรับการสร้าง Module ที่ลึก

5.1 Information hiding (การซ่อนข้อมูล)

เทคนิคที่สำคัญที่สุดสำหรับการสร้าง Module ที่ลึกคือ information hiding เทคนิคนี้ได้รับการอธิบายครั้งแรกโดย David Parnas1 แนวคิดพื้นฐานคือแต่ละ Module ควรจะห่อหุ้มข้อมูลบางส่วนที่แสดงถึงการตัดสินใจออกแบบ ข้อมูลนั้นฝังตัวอยู่ในการนำไปใช้ของ Module แต่ไม่ปรากฏในส่วนติดต่อของมัน ทำให้มันไม่มองเห็นได้สำหรับ Module อื่นๆ

ข้อมูลที่ซ่อนอยู่ภายใน Module มักประกอบด้วยรายละเอียดเกี่ยวกับวิธีการนำกลไกบางอย่างไปใช้ นี่คือตัวอย่างบางส่วนของข้อมูลที่อาจจะซ่อนอยู่ใน Module:

  • วิธีการเก็บข้อมูลใน B-tree และวิธีการเข้าถึงได้อย่างมีประสิทธิภาพ
  • วิธีการระบุ Block ดิสก์ทางกายภาพที่สอดคล้องกับแต่ละ Block ทางตรรมชาติภายในไฟล์
  • วิธีการนำ TCP Network Protocol ไปใช้
  • วิธีการจัดตารางเวลา Thread บน Multi-core Processor
  • วิธีการ Parse JSON Documents

ข้อมูลที่ซ่อนอยู่รวมถึง Data Structures และ Algorithms ที่เกี่ยวกับกลไก นอกจากนี้ยังสามารถรวมรายละเอียดที่ต่ำกว่า เช่น ขนาดของ Page และสามารถ รวมแนวคิดที่มีความเป็นนามธรรมมากขึ้น เช่น สมมติฐานที่ว่าไฟล์ส่วนใหญ่มีขนาดเล็ก

Information hiding ลดความซับซ้อนในสองวิธี ประการแรก มันลดความซับซ้อนของส่วนติดต่อของ Module ส่วนติดต่อสะท้อนมุมมองที่ง่ายขึ้นและเป็นนามธรรมมากขึ้นของการทำงานของ Module และซ่อนรายละเอียด ซึ่งช่วยลดภาระการรับรู้ของผู้พัฒนาที่ใช้ Module ตัวอย่างเช่น ผู้พัฒนาที่ใช้ B-tree Class ไม่จำเป็นต้องกังวลเกี่ยวกับ Fanout ที่เหมาะสมสำหรับ Node ในต้นไม้หรือวิธีการรักษาต้นไม้ให้สมดุล ประการที่สอง Information hiding ทำให้ง่ายขึ้นในการวิวัฒนาระบบ ถ้าข้อมูลบางส่วนซ่อนอยู่ จะไม่มี Dependencies บนข้อมูลนั้นนอก Module ที่มีข้อมูล ดังนั้นการเปลี่ยนแปลงการออกแบบที่เกี่ยวกับข้อมูลนั้นจะมีผลกระทบต่อ Module เดียว ตัวอย่างเช่น ถ้า TCP Protocol เปลี่ยน (เพื่อแนะนำกลไกใหม่สำหรับการควบคุมการปะติดปะต่อ ตัวอย่างเช่น) การนำ Protocol ไปใช้จะต้องได้รับการปรับเปลี่ยน แต่ไม่ควรต้องมีการเปลี่ยนแปลงใด ๆ ในโค้ดระดับสูงกว่าที่ใช้ TCP เพื่อส่งและรับข้อมูล

เมื่อออกแบบ Module ใหม่ คุณควรคิดอย่างรอบคอบเกี่ยวกับข้อมูลใดที่สามารถซ่อนอยู่ใน Module นั้น ถ้าคุณสามารถซ่อนข้อมูลได้มากขึ้น คุณควรจะสามารถลดความซับซ้อนของส่วนติดต่อของ Module ได้ และสิ่งนี้ทำให้ Module ลึกขึ้น

หมายเหตุ: การซ่อน Variables และ Methods ในคลาสโดยการประกาศให้เป็น private นั้นไม่เหมือนกับ Information hiding เอลิเมนต์ที่เป็น Private สามารถช่วยใน Information hiding ได้ เนื่องจากทำให้ไม่สามารถเข้าถึงรายการได้โดยตรงจากนอกคลาส อย่างไรก็ตาม ข้อมูลเกี่ยวกับรายการที่เป็น Private สามารถยังคงสัมผัสได้ผ่าน Public Methods เช่น Getter และ Setter Methods เมื่อสิ่งนี้เกิดขึ้น ลักษณะและการใช้งานของ Variables นั้นจึงถูกเปิดเผยเท่าที่พวกมันจะเป็น Public

รูปแบบที่ดีที่สุดของ Information hiding คือเมื่อข้อมูลซ่อนอยู่ภายใน Module ทั้งหมด ทำให้มันไม่เกี่ยวข้องและมองไม่เห็นสำหรับผู้ใช้ของ Module อย่างไรก็ตาม Information hiding บางส่วนก็มีคุณค่า ตัวอย่างเช่น ถ้าฟีเจอร์หรือข้อมูลบางส่วนจำเป็นสำหรับผู้ใช้เพียงส่วนน้อยของคลาสเท่านั้น และเข้าถึงผ่าน Separate Methods ทำให้มันไม่มองเห็นได้ในกรณีการใช้งานที่พบบ่อยที่สุด ดังนั้นข้อมูลนั้นส่วนใหญ่จึงถูกซ่อน ข้อมูลดังกล่าวจะสร้าง Dependencies น้อยกว่า Information ที่มองเห็นได้สำหรับผู้ใช้ทั้งหมดของคลาส

5.2 Information leakage (การรั่วไหลของข้อมูล)

ตรงกันข้ามกับ Information hiding คือ information leakage Information leakage เกิดขึ้นเมื่อการตัดสินใจออกแบบนั้นแสดงออกมาใน Module หลายตัว สิ่งนี้สร้าง Dependency ระหว่าง Module: การเปลี่ยนแปลงใด ๆ ต่อการตัดสินใจออกแบบนั้นจะต้องมีการเปลี่ยนแปลง Module ที่เกี่ยวข้องทั้งหมด ถ้าข้อมูลบางส่วนนั้นแสดงออกมาในส่วนติดต่อของ Module โดยความหมายแล้วมันก็ได้รับการรั่วไหลแล้ว ดังนั้นส่วนติดต่อที่ง่ายกว่านั้นจึงเกี่ยวข้องกับ Information hiding ที่ดีกว่า อย่างไรก็ตาม ข้อมูลสามารถรั่วไหลได้แม้ว่ามันจะไม่ปรากฏในส่วนติดต่อของ Module ก็ตาม สมมติว่าคลาสสองตัวต่างเข้าใจมากมายเกี่ยวกับรูปแบบไฟล์เฉพาะ (อาจจะคลาสหนึ่งอ่านไฟล์ในรูปแบบนั้นและคลาสอื่นเขียน) แม้ว่าไม่มีคลาสไหนจะเปิดเผยข้อมูลนั้นในส่วนติดต่อของมัน พวกมันต่างอาศัยรูปแบบไฟล์: ถ้ารูปแบบเปลี่ยน คลาสทั้งสองจะต้องได้รับการปรับเปลี่ยน Back-door Leakage เช่นนี้มีความเป็นอันตรายมากกว่า Leakage ผ่านส่วนติดต่อ เพราะมันไม่ชัดเจน

Information leakage เป็นหนึ่งในธงแดงที่สำคัญที่สุดในการออกแบบซอฟต์แวร์ หนึ่งในทักษะที่ดีที่สุดที่คุณสามารถเรียนรู้ได้ในฐานะ Software Designer คือการมี Sensitivity ระดับสูงต่อ Information leakage ถ้าคุณพบ Information leakage ระหว่างคลาส ให้ถามตัวเองว่า "ฉันสามารถจัดระเบียบคลาสเหล่านี้ใหม่อย่างไรเพื่อให้ข้อมูลเฉพาะนี้มีผลกระทบต่อคลาสเดียวเท่านั้น" ถ้าคลาสที่ได้รับผลกระทบมีขนาดค่อนข้างเล็กและเกี่ยวข้องอย่างใกล้ชิดกับข้อมูลที่รั่วไหล อาจจะสมเหตุสมผลที่จะรวมพวกมันเข้าเป็นคลาสเดียว อีกวิธีที่เป็นไปได้คือดึงข้อมูลออกจากคลาสที่ได้รับผลกระทบทั้งหมดและสร้าง Class ใหม่ที่ห่อหุ้มข้อมูลนั้นเท่านั้น อย่างไรก็ตาม วิธีนี้จะมีประสิทธิผลก็ต่อเมื่อคุณสามารถหา Interface ง่ายๆ ที่ทำให้เป็นนามธรรมจากรายละเอียดได้; ถ้า Class ใหม่เปิดเผยความรู้ส่วนใหญ่ผ่านส่วนติดต่อของมัน มันจะไม่ให้คุณค่ามากนัก (คุณเพียงแค่แทนที่ Back-door Leakage ด้วย Leakage ผ่านส่วนติดต่อ)

img Red Flag: Information Leakage (ธงแดง: Information Leakage) img

Information leakage เกิดขึ้นเมื่อข้อมูลชิ้นเดียวกันถูกใช้ในหลายตำแหน่ง เช่น คลาสต่างสองตัวที่ต่างเข้าใจรูปแบบของไฟล์ประเภทใดประเภทหนึ่ง

5.3 Temporal decomposition (การแยกส่วนตามเวลา)

สาเหตุที่พบบ่อยอย่างหนึ่งของ Information leakage คือรูปแบบการออกแบบที่ฉันเรียกว่า temporal decomposition ใน Temporal decomposition โครงสร้างของระบบจะสอดคล้องกับลำดับเวลาในการเกิดขึ้นของ Operation พิจารณา Application ที่อ่านไฟล์ในรูปแบบเฉพาะ ปรับเปลี่ยนเนื้อหาของไฟล์ แล้วเขียนไฟล์ออกอีกครั้ง ด้วย Temporal decomposition Application นี้อาจแบ่งออกเป็นสามคลาส: หนึ่งอ่านไฟล์ อีกตัวหนึ่งดำเนินการแก้ไข และตัวที่สามเขียนเวอร์ชันใหม่ออก ขั้นตอนการอ่านไฟล์และการเขียนไฟล์ทั้งสองตัวมีความรู้เกี่ยวกับรูปแบบไฟล์ ซึ่งส่งผลให้ Information leakage วิธีแก้ไขคือรวมกลไกพื้นฐานสำหรับการอ่านและเขียนไฟล์เข้าด้วยกันเป็นคลาสเดียว Class นี้จะได้รับการใช้ในช่วงการอ่านและเขียนของ Application ทั้งสองขั้นตอน มันง่ายที่จะ Fall into the Trap ของ Temporal decomposition เพราะลำดับการเกิดขึ้นของ Operation มักอยู่ในใจของคุณเมื่อเขียนโค้ด อย่างไรก็ตาม การตัดสินใจออกแบบส่วนใหญ่แสดงออกมาในเวลาต่างกันหลายครั้งตลอดช่วงชีวิตของ Application; ดังนั้น Temporal decomposition มักจะส่งผลให้ Information leakage

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

img Red Flag: Temporal Decomposition (ธงแดง: Temporal Decomposition) img

ใน Temporal decomposition ลำดับการดำเนินการจะแสดงออกมาในโครงสร้างโค้ด: Operation ที่เกิดขึ้นในเวลาต่างกันจะอยู่ใน Method หรือคลาสต่างกัน ถ้าข้อมูลเดียวกันถูกใช้ในจุดต่างกันของการดำเนินการ มันจะเข้ารหัสในหลายตำแหน่ง ส่งผลให้ Information leakage

5.4 Example: HTTP server (ตัวอย่าง: HTTP Server)

เพื่อให้เห็นประเด็นใน Information hiding ลองพิจารณาการตัดสินใจออกแบบที่นักเรียนสร้างระหว่างการนำ HTTP Protocol ไปใช้ในวิชาการออกแบบซอฟต์แวร์ มันเป็นประโยชน์ที่จะได้เห็นทั้งสิ่งที่พวกเขาทำได้ดีและสาขาที่พวกเขามีปัญหา

HTTP เป็นกลไกที่ใช้โดย Web Browsers เพื่อติดต่อสื่อสารกับ Web Servers เมื่อผู้ใช้คลิกลิงก์ใน Web Browser หรือส่ง Form ให้ Browser ใช้ HTTP เพื่อส่ง Request ผ่าน Network ไปยัง Web Server เมื่อ Server ประมวลผล Request แล้ว มันจะส่ง Response กลับไปยัง Browser; Response ปกติจะมี Web Page ใหม่เพื่อแสดงผล HTTP Protocol ระบุรูปแบบของ Requests และ Responses ซึ่งทั้งสองอย่างนั้นแสดงในลักษณะข้อความ Figure 5.1 แสดง HTTP Request ตัวอย่างที่อธิบาย Form Submission นักเรียนในวิชาเรียนได้รับการขอให้นำ Class หนึ่งตัวหรือมากกว่าไปใช้เพื่อให้ Web Servers สามารถรับ HTTP Requests ที่เข้ามาและส่ง Responses ได้อย่างง่ายดาย

img

Figure 5.1: POST Request ใน HTTP Protocol ประกอบด้วยข้อความที่ส่งผ่าน TCP Socket แต่ละ Request มีบรรทัดเริ่มต้น คอลเลคชันของ Headers ที่ยุติด้วยบรรทัดว่าง และเนื้อหา Body ที่เป็นทางเลือก บรรทัดเริ่มต้นมีประเภท Request (POST ใช้สำหรับการส่ง Form Data) URL ที่ระบุ Operation (/comments/create) และ Parameters ที่เป็นทางเลือก (photo_id มีค่า 246) และเวอร์ชั่น HTTP Protocol ที่ใช้โดยผู้ส่ง แต่ละบรรทัด Header ประกอบด้วยชื่อเช่น Content-Length ตามด้วยค่าของมัน สำหรับ Request นี้ Body มี Parameters เพิ่มเติม (comment และ priority)

5.5 Example: too many classes (ตัวอย่าง: คลาสเยอะเกินไป)

ความผิดพลาดที่พบบ่อยที่สุดที่นักเรียนสร้างคือการแบ่งโค้ดของพวกเขาออกเป็นจำนวนคลาส Shallow จำนวนมาก ซึ่งส่งผลให้ Information leakage ระหว่างคลาส Team หนึ่งใช้คลาสต่างสองตัวสำหรับการรับ HTTP Requests; คลาสแรกอ่าน Request จาก Network Connection ลงในสตริง และคลาสที่สองแยก String นี่คือตัวอย่างของ Temporal decomposition ("ก่อนอื่นเราอ่าน Request แล้วเราแยกมัน") Information leakage เกิดขึ้นเพราะ HTTP Request ไม่สามารถอ่านได้โดยไม่ต้อง Parse มากมายของข้อความ; ตัวอย่างเช่น Content-Length Header ระบุความยาวของ Request Body ดังนั้น Headers จะต้องแยก Parser ไป เพื่อคำนวณความยาว Request ทั้งหมด ดังนั้นคลาสทั้งสองต่างต้องเข้าใจโครงสร้างของ HTTP Requests ส่วนใหญ่ และ Parsing Code ก็ถูกทำซ้ำในคลาสทั้งสอง วิธีนี้ยังสร้าง Complexity ส่วนเพิ่มเติมสำหรับผู้เรียก ที่ต้องเรียก Methods สองตัวใน Classes ต่างกัน ในลำดับที่กำหนด เพื่อรับ Request

เนื่องจากคลาสมีการแบ่งปันข้อมูลจำนวนมากดังนั้น มันจึงจะดีกว่าถ้ารวมพวกมันเข้าเป็นคลาสเดียวที่จัดการทั้งการอ่านและการแยก Request นี่จะให้ Information hiding ที่ดีกว่า เนื่องจากมันแยกความรู้ทั้งหมดของรูปแบบ Request ลงในคลาสเดียว และให้ Interface ที่ง่ายขึ้นสำหรับผู้เรียก (เพียงแค่ Method เดียวที่ต้องเรียก)

ตัวอย่างนี้แสดงให้เห็นธีมทั่วไปในการออกแบบซอฟต์แวร์: Information hiding มักสามารถปรับปรุงได้โดยการทำให้คลาสใหญ่ขึ้นเล็กน้อย เหตุผลหนึ่งสำหรับการทำเช่นนี้คือการนำโค้ดทั้งหมดที่เกี่ยวข้องกับความสามารถเฉพาะอย่าง (เช่น Parsing HTTP Request) มารวมกัน เพื่อให้คลาส Result ประกอบด้วยทุกอย่างที่เกี่ยวข้องกับความสามารถนั้น เหตุผลที่สองสำหรับการเพิ่มขนาดของคลาสคือการยกระดับของ Interface; ตัวอย่างเช่น แทนที่จะมี Methods ที่แยกต่างหากสำหรับแต่ละสามขั้นตอนของ Computation ให้มี Method เดียวที่ดำเนินการ Computation ทั้งหมด สิ่งนี้อาจส่งผลให้ Interface ที่ง่ายขึ้น ประโยชน์ทั้งสองอย่างนี้ใช้ได้กับตัวอย่างของย่อหน้าก่อนหน้า: การรวมคลาสนำโค้ดทั้งหมดที่เกี่ยวข้องกับการแยก HTTP Request มารวมกัน และแทนที่ Methods ที่มองเห็นได้จากภายนอกสองตัวด้วยหนึ่งตัว คลาส Combined นั้นลึกกว่าคลาส Original

แน่นอน มันเป็นไปได้ที่จะใช้แนวคิดของคลาสที่ใหญ่ขึ้นไปไกลเกินไป (เช่น Class เดียวสำหรับ Application ทั้งหมด) บทที่ 9 จะอธิบายเงื่อนไขที่ทำให้สมเหตุสมผลในการแยกโค้ดเข้า Classes ที่เล็กกว่าหลายตัว

5.6 Example: HTTP parameter handling (ตัวอย่าง: HTTP Parameter Handling)

หลังจากที่ HTTP Request ได้รับโดย Server แล้ว Server ต้องเข้าถึงข้อมูลบางส่วนจาก Request โค้ดที่จัดการ Request ใน Figure 5.1 อาจจำเป็นต้องรู้ค่าของ photo_id Parameter Parameters สามารถระบุได้ในบรรทัดแรกของ Request (photo_id ใน Figure 5.1) หรือบางครั้ง ใน Body (comment และ priority ใน Figure 5.1) แต่ละ Parameter มีชื่อและค่า ค่าของ Parameters ใช้ Encoding พิเศษที่เรียกว่า URL encoding; ตัวอย่างเช่น ในค่าสำหรับ comment ใน Figure 5.1 "+" ใช้แทนช่องว่าง และ "%21" ใช้แทน "!" เพื่อประมวลผล Request Server จะต้องการค่าของบาง Parameters และต้องการให้พวกมันอยู่ในรูปแบบที่ไม่ได้เข้ารหัส

โปรเจค Student ส่วนใหญ่สร้างทางเลือกดีสองประการเกี่ยวกับการจัดการ Parameter ประการแรก พวกเขายอมรับว่า Server Applications ไม่สนใจว่า Parameter ระบุไว้ใน Header Line หรือ Body ของ Request ดังนั้นพวกเขาจึงซ่อนความแตกต่างนี้จากผู้เรียกและรวม Parameters จากทั้งสองตำแหน่งเข้าด้วยกัน ประการที่สอง พวกเขาซ่อนความรู้เกี่ยวกับ URL Encoding: HTTP Parser ถอด Decode Parameter Values ก่อนส่งกลับไปยัง Web Server ดังนั้นค่าของ comment Parameter ใน Figure 5.1 จะถูกส่งกลับเป็น "What a cute baby!" ไม่ใช่ "What+a+cute+baby%21") ในทั้งสองกรณีนี้ Information hiding ส่งผลให้ APIs ที่ง่ายขึ้นสำหรับโค้ดใช้ HTTP Module

อย่างไรก็ตาม โปรเจค Student ส่วนใหญ่ใช้ Interface สำหรับ Returning Parameters ที่มีความลึกไม่เพียงพอ และสิ่งนี้ส่งผลให้สูญเสียโอกาส Information hiding โปรเจค ส่วนใหญ่ใช้ Object ของประเภท HTTPRequest เพื่อเก็บ HTTP Request ที่ได้ Parse และ HTTPRequest Class มี Method เดียวเช่นต่อไปนี้เพื่อ Return Parameters:

public Map<String, String> getParams() {
       return this.params;
}

แทนที่จะ Return Parameter เดียว Method จะ Return Reference ไปยัง Map ที่ใช้ภายในเพื่อเก็บ Parameters ทั้งหมด Method นี้มีความลึกไม่เพียงพอ และมันเปิดเผยการนำไปใช้ภายในที่ใช้โดย HTTPRequest Class เพื่อเก็บ Parameters การเปลี่ยนแปลงใด ๆ ต่อการนำไปใช้นั้นจะส่งผลให้การเปลี่ยนแปลงส่วนติดต่อ ซึ่งจะต้องมีการปรับเปลี่ยนสำหรับผู้เรียกทั้งหมด เมื่อนำไปใช้ที่ถูกแก้ไข การเปลี่ยนแปลงมักจะเกี่ยวข้องกับการเปลี่ยนแปลงในการแสดงข้อมูลที่สำคัญ (เพื่อปรับปรุงประสิทธิภาพ ตัวอย่างเช่น) ดังนั้นสิ่งสำคัญคือต้องหลีกเลี่ยงการเปิดเผย Data Structures ภายในให้มากที่สุด วิธีนี้ยังทำให้เกิดการทำงานมากขึ้นสำหรับผู้เรียก: ผู้เรียกต้องก่อนอื่นเรียก getParams จากนั้นต้องเรียก Method อื่นเพื่อดึง Parameter เฉพาะจาก Map สุดท้าย ผู้เรียกต้องตระหนักว่าพวกเขาไม่ควรแก้ไข Map ที่ส่งกลับโดย getParams เนื่องจากจะมีผลกระทบต่อสถานะภายในของ HTTPRequest

นี่คือ Interface ที่ดีกว่าสำหรับ Retrieving Parameter Values:

public String getParameter(String name) { ... }
public int getIntParameter(String name) { ... }

getParameter Return Parameter Value เป็น String มันให้ Interface ที่ลึกขึ้นเล็กน้อยมากกว่า getParams ด้านบน; ที่สำคัญกว่านั้น มันซ่อนการแสดงข้อมูลภายในของ Parameters getIntParameter แปลงค่าของ Parameter จากรูปแบบสตริงใน HTTP Request เป็น Integer (เช่น photo_id Parameter ใน Figure 5.1) นี่จึงช่วยผู้เรียกจากการร้องขอ String-to-Integer Conversion แยกต่างหาก และซ่อนกลไกนั้นจากผู้เรียก Methods เพิ่มเติมสำหรับประเภทข้อมูล Data Types อื่นๆ เช่น getDoubleParameter สามารถกำหนดได้หากจำเป็น (Methods ทั้งหมดนี้จะ Throw Exceptions ถ้า Parameter ที่ต้องการไม่มีอยู่ หรือถ้ามันไม่สามารถแปลงเป็น ประเภทที่ขอได้; Exception Declarations ได้ถูกละเว้นในโค้ดด้านบน)

5.7 Example: defaults in HTTP responses (ตัวอย่าง: ค่า Default ใน HTTP Responses)

โปรเจค HTTP ยังต้องจัดเตรียม Support สำหรับการสร้าง HTTP Responses ความผิดพลาดที่พบบ่อยที่สุดที่นักเรียนทำในพื้นที่นี้คือ Defaults ที่ไม่เพียงพอ แต่ละ HTTP Response ต้องระบุเวอร์ชั่น HTTP Protocol; Team หนึ่งต้องให้ผู้เรียกระบุเวอร์ชั่นนี้อย่างชัดแจ้งเมื่อสร้างวัตถุ Response อย่างไรก็ตาม Response Version ต้องสอดคล้องกับที่อยู่ใน Request Object และ Request ต้องผ่านแล้วเป็น Argument เมื่อส่ง Response (มันบ่งชี้ว่าจะส่ง Response ไปที่ไหน) ดังนั้นมันจึงสมเหตุสมผลมากขึ้นสำหรับ HTTP Classes เพื่อจัดเตรียม Response Version โดยอัตโนมัติ ผู้เรียกไม่น่าจะรู้ว่าจะระบุค่าอะไร และถ้าผู้เรียกระบุค่า มันอาจจะส่งผลให้ Information leakage ระหว่าง HTTP Library และผู้เรียก HTTP Responses ยังรวม Date Header ที่ระบุเวลาเมื่อ Response ถูกส่ง; HTTP Library ควรจัดเตรียม Sensible Default สำหรับสิ่งนี้เช่นกัน

Defaults แสดงให้เห็นหลักการว่า Interfaces ควรออกแบบให้ Common Case ง่ายที่สุดเท่าที่เป็นไปได้ นอกจากนี้พวกเขาเป็นตัวอย่างของ Information hiding บางส่วน: ในปกติ ผู้เรียกไม่จำเป็นต้องรู้เกี่ยวกับการมีอยู่ของ Defaulted Item ในกรณีที่เกิดขึ้นไม่บ่อยนักเมื่อผู้เรียกต้องการแทนที่ Default มันจะต้องรู้เกี่ยวกับค่า และสามารถเรียก Method พิเศษเพื่อแก้ไขมันได้

ไม่ว่าเป็นไปได้เท่าไหร่ Classes ควร "ทำสิ่งที่ถูกต้อง" โดยไม่ถูกขอให้ชัดแจ้ง Defaults เป็นตัวอย่างของสิ่งนี้ ตัวอย่าง Java I/O ในหน้า 26 แสดงให้เห็นประเด็นนี้ในลักษณะเชิงลบ Buffering ใน File I/O เป็นประโยชน์ถึงแนวปกติมากจนไม่มีใครควรต้องขออย่างชัดแจ้งหรือยังตระหนักถึงการมีอยู่ของมัน; I/O Classes ควร "ทำสิ่งที่ถูกต้อง" และจัดเตรียมมันโดยอัตโนมัติ ฟีเจอร์ที่ดีที่สุดคือ ที่คุณได้รับโดยไม่รู้ด้วยซ้ำว่าพวกมันมีอยู่

img Red Flag: Overexposure (ธงแดง: Overexposure) img

ถ้า API สำหรับฟีเจอร์ที่ใช้บ่อยบังคับให้ผู้ใช้เรียนรู้เกี่ยวกับฟีเจอร์อื่นๆ ที่ใช้ไม่บ่อยนัก สิ่งนี้จะเพิ่มภาระการรับรู้สำหรับผู้ใช้ที่ไม่ต้องการฟีเจอร์ที่ใช้ไม่บ่อยนั้น

5.8 Information hiding within a class (Information Hiding ภายในคลาส)

ตัวอย่างในบทนี้เน้นไปที่ Information hiding เนื่องจากมันเกี่ยวข้องกับ APIs ที่มองเห็นได้จากภายนอกสำหรับคลาส แต่ Information hiding สามารถนำไปใช้ได้ที่ระดับอื่นๆ ในระบบ เช่น ภายในคลาส ลองออกแบบ Private Methods ภายในคลาสเพื่อให้แต่ละ Method ห่อหุ้มข้อมูลหรือความสามารถบางอย่างและซ่อนมันจากส่วนที่เหลือของคลาส นอกจากนี้ ให้พยายามลดจำนวนตำแหน่งที่ใช้ Instance Variables แต่ละตัว ตัวแปรบางตัวอาจจำเป็นต้องเข้าถึงได้กว้างขวางทั่ว Class แต่บางตัวอาจจำเป็นในเพียงไม่กี่สถานที่; หากคุณสามารถลดจำนวนตำแหน่งที่ใช้ตัวแปร คุณจะขจัด Dependencies ภายใน Class และลดความซับซ้อนของมันได้

5.9 Taking it too far (ดำเนินการไปไกลเกินไป)

Information hiding มีความหมายเพียงเมื่อข้อมูลที่ถูกซ่อนนั้นไม่จำเป็นนอก Module ของมัน ถ้าข้อมูลจำเป็นนอก Module คุณจึง ต้องไม่ ซ่อนมัน สมมติว่าประสิทธิภาพของ Module ได้รับผลกระทบจากพารามิเตอร์ Configuration บางตัว และการใช้ Module ต่างๆ จะต้องการ Settings ต่างกันของพารามิเตอร์ ในกรณีนี้สิ่งสำคัญคือ Parameters นั้นจะต้องเปิดเผยในส่วนติดต่อของ Module เพื่อให้พวกมันสามารถเปิด Appropriately ได้ ในฐานะ Software Designer เป้าหมายของคุณควรจะลดจำนวนข้อมูลที่จำเป็นนอก Module; ตัวอย่างเช่น ถ้า Module สามารถปรับ Configuration ของมันเองโดยอัตโนมัติ นั่นจะดีกว่า Exposing Configuration Parameters แต่สิ่งสำคัญคือต้องรู้ว่าข้อมูลใดที่จำเป็นนอก Module และสตร็ค Ensure ที่มันเปิดเผยออกมา

5.10 Conclusion (บทสรุป)

Information hiding และ Deep Modules มีความเกี่ยวข้องอย่างใกล้ชิด ถ้า Module ซ่อนข้อมูลจำนวนมาก นั่นมีแนวโน้มที่จะเพิ่มจำนวนการทำงานของ Module ขณะเดียวกันก็ลดส่วนติดต่อของมัน สิ่งนี้ทำให้ Module ลึกขึ้น ในทางกลับกัน ถ้า Module ไม่ซ่อนข้อมูลมากนัก แล้วมันไม่มีการทำงานจำนวนมากก็ตาม มันมี Interface ที่ซับซ้อน; ไม่ว่าด้วยวิธีใดก็ตาม Module นั้นจะมีความลึกไม่เพียงพอ

เมื่อแยกระบบออกเป็น Modules ให้พยายามอย่าให้ได้รับอิทธิพลจากลำดับ Operations ที่จะเกิดขึ้นในขณะ Runtime; นั่นจะนำคุณลงสู่เส้นทาง Temporal decomposition ซึ่งจะส่งผลให้ Information leakage และ Shallow Modules แทนนั้น ให้คิด Pieces ต่างกันของความรู้ที่จำเป็นเพื่อดำเนินการ Tasks ของ Application ของคุณ และออกแบบแต่ละ Module เพื่อห่อหุ้ม Pieces เดียวหรือบางอย่างเพียงไม่กี่อย่างของความรู้นั้น สิ่งนี้จะสร้างการออกแบบที่สะอาดและเรียบง่ายด้วย Deep Modules

1David Parnas, "On the Criteria to be Used in Decomposing Systems into Modules," Communications of the ACM, December 1972.