ระบบซอฟต์แวร์ถูกสร้างขึ้นเป็นเลเยอร์ โดยเลเยอร์ที่สูงกว่าจะใช้ facilities ที่เลเยอร์ต่ำกว่าจัดเตรียมไว้ให้ ในระบบที่ออกแบบมาอย่างดี แต่ละเลเยอร์จะให้ abstraction ที่แตกต่างจากเลเยอร์ด้านบนและด้านล่าง ถ้าคุณติดตาม operation เดียวขณะที่มันเคลื่อนที่ขึ้นลงผ่านเลเยอร์ด้วยการเรียก method abstraction ก็จะเปลี่ยนไปในแต่ละ method call ตัวอย่างเช่น:

  • ในระบบไฟล์ เลเยอร์บนสุดจะ implement file abstraction ไฟล์ประกอบด้วย array ของ bytes ที่มีขนาดแปรผัน ซึ่งสามารถอัปเดตได้โดยการอ่านและเขียนช่วง bytes ที่มีขนาดแปรผัน เลเยอร์ถัดลงมาในระบบไฟล์จะ implement cache ในหน่วยความจำสำหรับ disk blocks ที่มีขนาดคงที่ ผู้เรียกสามารถสันนิษฐานได้ว่า blocks ที่ใช้งานบ่อยจะอยู่ในหน่วยความจำและเข้าถึงได้เร็ว เลเยอร์ต่ำสุดประกอบด้วย device drivers ซึ่งทำการย้าย blocks ระหว่างอุปกรณ์จัดเก็บข้อมูลสำรองและหน่วยความจำ
  • ใน network transport protocol อย่าง TCP abstraction ที่เลเยอร์บนสุดให้ไว้คือ stream ของ bytes ที่ส่งจากเครื่องหนึ่งไปยังอีกเครื่องหนึ่งอย่างน่าเชื่อถือ ระดับนี้สร้างอยู่บนระดับที่ต่ำกว่าซึ่งส่ง packets ที่มีขนาดจำกัดระหว่างเครื่องแบบ best-effort: packets ส่วนใหญ่จะถูกส่งสำเร็จ แต่บาง packets อาจสูญหายหรือมาถึงไม่เรียงลำดับ

ถ้าระบบมีเลเยอร์ที่อยู่ติดกันซึ่งมี abstractions ที่คล้ายคลึงกัน นี่คือ red flag ที่บ่งบอกว่ามีปัญหากับการแบ่งแยกคลาส บทนี้จะพูดถึงสถานการณ์ที่สิ่งนี้เกิดขึ้น ปัญหาที่ตามมา และวิธีการ refactor เพื่อกำจัดปัญหาเหล่านั้น

7.1    Pass-through methods (เมธอดแบบผ่านต่อ)

เมื่อเลเยอร์ที่อยู่ติดกันมี abstractions ที่คล้ายคลึงกัน ปัญหามักจะปรากฏในรูปแบบของ pass-through methods pass-through method คือ method ที่แทบไม่ได้ทำอะไรเลยนอกจากเรียกอีก method หนึ่งซึ่งมี signature คล้ายหรือเหมือนกับ method ที่เรียก ตัวอย่างเช่น โครงงานนักศึกษาที่ implement GUI text editor มีคลาสที่ประกอบไปด้วย pass-through methods เกือบทั้งหมด นี่คือตัวอย่างบางส่วนจากคลาสนั้น:

public class TextDocument ... {

         private TextArea textArea;

         private TextDocumentListener listener;

         ...

         public Character getLastTypedCharacter() {

                 return textArea.getLastTypedCharacter();

         }

         public int getCursorOffset() {

                 return textArea.getCursorOffset();

         }

         public void insertString(String textToInsert,
int offset) {

                 textArea.insertString(textToInsert, offset);

         }

         public void willInsertString(String stringToInsert, int offset) {

                 if (listener != null) {

                      listener.willInsertString(this, stringToInsert, offset);

                 }

         }

         ...

}

จาก 15 public methods ในคลาสนั้น 13 method เป็น pass-through methods

img Red Flag: Pass-Through Method (ธงแดง: เมธอดแบบผ่านต่อ) img

pass-through method คือ method ที่ไม่ทำอะไรเลยนอกจากส่งต่อ arguments ไปยังอีก method หนึ่ง ซึ่งมักจะมี API เดียวกับ pass-through method โดยทั่วไปแล้วสิ่งนี้บ่งชี้ว่ามีการแบ่งแยกหน้าที่รับผิดชอบระหว่างคลาสที่ไม่ชัดเจน

Pass-through methods ทำให้คลาสตื้นลง: มันเพิ่มความซับซ้อนของ interface ของคลาส ซึ่งเพิ่ม complexity แต่ไม่ได้เพิ่มฟังก์ชันการทำงานโดยรวมของระบบ จากสี่ methods ข้างต้น มีแค่อันสุดท้ายเท่านั้นที่มีฟังก์ชันการทำงานจริง และถึงอย่างนั้นก็เป็นเรื่องเล็กน้อย คือ method ตรวจสอบความถูกต้องของตัวแปรตัวเดียว Pass-through methods ยังสร้าง dependencies ระหว่างคลาสอีกด้วย: ถ้า signature ของ method insertString ใน TextArea เปลี่ยนไป method insertString ใน TextDocument ก็จะต้องเปลี่ยนตามไปด้วย

Pass-through methods บ่งชี้ว่ามีความสับสนเกี่ยวกับการแบ่งแยกหน้าที่รับผิดชอบระหว่างคลาส ในตัวอย่างข้างต้น คลาส TextDocument มี method insertString แต่ฟังก์ชันการทำงานในการแทรกข้อความถูก implement ไว้ใน TextArea ทั้งหมด โดยทั่วไปแล้วนี่เป็นแนวคิดที่ไม่ดี: interface ของฟังก์ชันการทำงานควรอยู่ในคลาสเดียวกับที่ implement ฟังก์ชันนั้น เมื่อคุณเห็น pass-through methods จากคลาสหนึ่งไปยังอีกคลาสหนึ่ง ให้ลองพิจารณาทั้งสองคลาสและถามตัวเองว่า "แต่ละคลาสเหล่านี้รับผิดชอบ features และ abstractions อะไรบ้าง?" คุณอาจจะสังเกตเห็นว่ามีความซ้ำซ้อนของหน้าที่รับผิดชอบระหว่างคลาส

แนวทางแก้ไขคือการ refactor คลาสเพื่อให้แต่ละคลาสมีชุดหน้าที่รับผิดชอบที่ชัดเจนและสอดคล้องกัน Figure 7.1 แสดงวิธีการหลายวิธี วิธีหนึ่ง ดังที่แสดงใน Figure 7.1(b) คือการ expose คลาสระดับล่างให้กับผู้เรียกของคลาสระดับบนโดยตรง โดยลบหน้าที่รับผิดชอบทั้งหมดสำหรับ feature นั้นออกจากคลาสระดับบน อีกวิธีคือการกระจายฟังก์ชันการทำงานระหว่างคลาสใหม่ ดังใน Figure 7.1(c) สุดท้าย ถ้าไม่สามารถแยกคลาสออกจากกันได้ แนวทางที่ดีที่สุดอาจเป็นการรวมคลาสเข้าด้วยกัน ดังใน Figure 7.1(d) .

ในตัวอย่างข้างต้น มีสามคลาสที่มีหน้าที่รับผิดชอบที่พันกัน: TextDocument , TextArea , และ TextDocumentListener นักศึกษาได้กำจัด pass-through methods โดยการย้าย methods ระหว่างคลาสและยุบสามคลาสให้เหลือเพียงสองคลาส ซึ่งมีหน้าที่รับผิดชอบที่แตกต่างกันอย่างชัดเจนมากขึ้น

7.2    When is interface duplication OK? (เมื่อไหร่ที่การซ้ำซ้อนของ interface ถึงจะ acceptable)

การมี methods ที่มี signature เดียวกันไม่ใช่เรื่องแย่เสมอไป สิ่งสำคัญคือแต่ละ method ใหม่ควรมีส่วนช่วยในฟังก์ชันการทำงานที่สำคัญ Pass-through methods แย่เพราะมันไม่ได้ช่วยเพิ่มฟังก์ชันการทำงานใหม่ใดๆ เลย

ตัวอย่างหนึ่งที่การเรียก method อื่นด้วย signature เดียวกันมีประโยชน์คือ dispatcher dispatcher คือ method ที่ใช้ arguments ของมันเพื่อเลือก method ใด method หนึ่งจากหลายๆ method เพื่อเรียกใช้งาน จากนั้นก็ส่งต่อ arguments ส่วนใหญ่หรือทั้งหมดไปยัง method ที่เลือก signature ของ dispatcher มักจะเหมือนกับ signature ของ methods ที่มันเรียก ถึงอย่างนั้น dispatcher ก็ให้ฟังก์ชันการทำงานที่มีประโยชน์: มันเลือกว่าจะให้ method ใดในหลายๆ method เป็นผู้ดำเนินการแต่ละงาน

img

Figure 7.1: Pass-through methods. ใน (a) คลาส C1 มี pass-through methods สาม method ซึ่งไม่ได้ทำอะไรนอกจากเรียก methods ที่มี signature เดียวกันใน C2 (แต่ละสัญลักษณ์แทน method signature หนึ่งๆ) Pass-through methods สามารถกำจัดได้โดยให้ผู้เรียกของ C1 เรียก C2 โดยตรงดังใน (b) โดยการกระจายฟังก์ชันการทำงานระหว่าง C1 และ C2 ใหม่เพื่อหลีกเลี่ยงการเรียกข้ามคลาสดังใน (c) หรือโดยการรวมคลาสเข้าด้วยกันดังใน (d)

ตัวอย่างเช่น เมื่อ Web server ได้รับ HTTP request จาก Web browser มันจะเรียก dispatcher ที่ตรวจสอบ URL ใน request ที่เข้ามาและเลือก method เฉพาะเพื่อจัดการ request นั้น URL บางอันอาจถูกจัดการโดยการส่งคืนเนื้อหาของไฟล์บน disk ในขณะที่บางอันอาจถูกจัดการโดยเรียก procedure ในภาษาเช่น PHP หรือ JavaScript กระบวนการ dispatch อาจซับซ้อนพอสมควร และมักจะขับเคลื่อนโดยชุดกฏที่นำมา match กับ URL ที่เข้ามา

การมีหลาย methods ที่มี signature เดียวกันนั้นไม่เป็นปัญหา ตราบใดที่แต่ละ method ให้ฟังก์ชันการทำงานที่มีประโยชน์และแตกต่างกัน Methods ที่ถูกเรียกโดย dispatcher มีคุณสมบัตินี้ อีกตัวอย่างหนึ่งคือ interfaces ที่มีหลาย implementations เช่น disk drivers ในระบบปฏิบัติการ แต่ละ driver รองรับ disk คนละประเภท แต่ทั้งหมดมี interface เดียวกัน เมื่อมีหลาย methods ที่ให้ implementations ที่แตกต่างกันของ interface เดียวกัน มันช่วยลด cognitive load เมื่อคุณเคยชินกับ method ใด method หนึ่งแล้ว การทำงานกับ method อื่นๆ ก็จะง่ายขึ้น เพราะคุณไม่ต้องเรียนรู้ interface ใหม่ Methods แบบนี้มักจะอยู่ในเลเยอร์เดียวกันและ ไม่ได้เรียกหากัน

7.3    Decorators (รูปแบบ Decorator)

Decorator design pattern (หรือที่รู้จักในชื่อ "wrapper") คือ pattern ที่ส่งเสริมให้มีการซ้ำซ้อนของ API ข้ามเลเยอร์ decorator object จะรับ object ที่มีอยู่และขยายฟังก์ชันการทำงานของมัน มันมี API ที่คล้ายหรือเหมือนกับ object ต้นทาง และ methods ของมันจะเรียก methods ของ object ต้นทาง ในตัวอย่าง Java I/O จาก Chapter 4 คลาส BufferedInputStream เป็น decorator: เมื่อได้รับ InputStream object มันจะให้ API เดียวกันแต่เพิ่ม buffering เข้าไป ตัวอย่างเช่น เมื่อ method read ถูกเรียกเพื่ออ่านอักขระตัวเดียว มันจะเรียก read บน InputStream ต้นทางเพื่ออ่าน block ที่ใหญ่กว่ามาก และเก็บอักขระที่เหลือไว้ใช้ในการเรียก read ในอนาคต อีกตัวอย่างหนึ่งเกิดขึ้นในระบบ windowing: คลาส Window implement หน้าต่างแบบง่ายที่ไม่สามารถเลื่อนได้ และคลาส ScrollableWindow ตกแต่งคลาส Window โดยเพิ่ม scrollbars แนวนอนและแนวตั้ง

แรงจูงใจของ decorators คือการแยกส่วนขยายเฉพาะทางของคลาสออกจากแกนกลางทั่วไป อย่างไรก็ตาม decorator classes มักจะตื้น: มันนำเสนอ boilerplate จำนวนมากเพื่อฟังก์ชันการทำงานใหม่เพียงเล็กน้อย Decorator classes มักจะมี pass-through methods จำนวนมาก มันง่ายมากที่จะใช้ decorator pattern มากเกินไป โดยสร้างคลาสใหม่สำหรับทุก feature เล็กๆ น้อยๆ ซึ่งส่งผลให้เกิดการระเบิดของ shallow classes ดังตัวอย่าง Java I/O

ก่อนที่จะสร้าง decorator class ให้ลองพิจารณาทางเลือกอื่นๆ เช่น:

  • คุณสามารถเพิ่มฟังก์ชันการทำงานใหม่ลงในคลาสพื้นฐานโดยตรง แทนที่จะสร้าง decorator class หรือไม่? วิธีนี้เหมาะสมถ้าฟังก์ชันการทำงานใหม่เป็นแบบ general-purpose หรือมีความเกี่ยวข้องเชิงตรรกะกับคลาสพื้นฐาน หรือถ้าการใช้งานคลาสพื้นฐานส่วนใหญ่ก็จะใช้ฟังก์ชันการทำงานใหม่ด้วย ตัวอย่างเช่น ใครก็ตามที่สร้าง Java InputStream ก็จะสร้าง BufferedInputStream ด้วย และ buffering ก็เป็นส่วนธรรมชาติของ I/O ดังนั้นคลาสเหล่านี้ควรถูกรวมเข้าด้วยกัน
  • ถ้าฟังก์ชันการทำงานใหม่เป็น specialization สำหรับ use case เฉพาะ มันสมเหตุสมผลไหมที่จะรวมมันกับ use case นั้นเข้าด้วยกัน แทนที่จะสร้างคลาสแยกต่างหาก?
  • คุณสามารถรวมฟังก์ชันการทำงานใหม่กับ decorator ที่มีอยู่ แทนที่จะสร้าง decorator ใหม่หรือไม่? วิธีนี้จะได้ decorator class เดียวที่ลึกขึ้น แทนที่จะได้ shallow classes หลายอัน
  • สุดท้าย ให้ถามตัวเองว่าฟังก์ชันการทำงานใหม่จำเป็นต้อง wrap ฟังก์ชันการทำงานที่มีอยู่จริงหรือไม่: คุณสามารถ implement มันเป็นคลาสอิสระที่ไม่ขึ้นกับ base class ได้หรือไม่? ในตัวอย่างระบบ windowing scrollbars น่าจะสามารถ implement แยกจากหน้าต่างหลักได้ โดยไม่ต้อง wrap ฟังก์ชันการทำงานที่มีอยู่ทั้งหมด

บางครั้ง decorators ก็เหมาะสม แต่โดยปกติแล้วมักจะมีทางเลือกที่ดีกว่า

7.4    Interface versus implementation (Interface กับ Implementation)

การประยุกต์ใช้กฏ "different layer, different abstraction" อีกอย่างคือ interface ของคลาสควรแตกต่างจาก implementation ของมัน: ตัวแทนที่ใช้ภายในควรแตกต่างจาก abstractions ที่ปรากฏใน interface ถ้าทั้งสองมี abstractions ที่คล้ายกัน คลาสนั้นก็คงไม่ลึกมากนัก ตัวอย่างเช่น ในโปรเจกต์ text editor ที่พูดถึงใน Chapter 6 ทีมส่วนใหญ่ implement text module ในรูปของบรรทัดข้อความ โดยแต่ละบรรทัดถูกจัดเก็บแยกกัน บางทีมยังออกแบบ APIs สำหรับ text class รอบๆ บรรทัดอีกด้วย โดยมี methods เช่น getLine และ putLine อย่างไรก็ตาม สิ่งนี้ทำให้ text class ตื้นและใช้งานยาก ในโค้ด user interface ระดับสูง การแทรกข้อความตรงกลางบรรทัด (เช่น เมื่อผู้ใช้กำลังพิมพ์) หรือการลบช่วงข้อความที่ข้ามหลายบรรทัดเป็นเรื่องธรรมดา ด้วย API ที่ยึดตามบรรทัดสำหรับ text class ผู้เรียกถูกบังคับให้ต้อง split และ join บรรทัดเพื่อ implement operations ของ user interface โค้ดนี้ไม่ใช่เรื่องเล็กน้อยและถูกทำซ้ำและกระจายอยู่ทั่ว implementation ของ user interface

Text classes ใช้งานง่ายกว่ามากเมื่อมันให้ interface แบบ character-oriented เช่น method insert ที่แทรกสตริงข้อความใดๆ (ซึ่งอาจรวมถึง newlines) ที่ตำแหน่งใดๆ ในข้อความ และ method delete ที่ลบข้อความระหว่างสองตำแหน่งใดๆ ในข้อความ ภายใน ข้อความยังคงถูกแทนในรูปของบรรทัด Interface แบบ character-oriented จะ encapsulate ความซับซ้อนของการ split และ join บรรทัดไว้ภายใน text class ซึ่งทำให้ text class ลึกขึ้นและทำให้โค้ดระดับสูงที่ใช้คลาสนี้ง่ายขึ้น ด้วยวิธีนี้ text API จะแตกต่างอย่างมากจากกลไกการจัดเก็บแบบยึดตามบรรทัด ความแตกต่างนี้แสดงถึงฟังก์ชันการทำงานที่มีค่าที่คลาสจัดเตรียมให้

7.5    Pass-through variables (ตัวแปรแบบผ่านต่อ)

การซ้ำซ้อนของ API อีกรูปแบบหนึ่งข้ามเลเยอร์คือ pass-through variable ซึ่งเป็นตัวแปรที่ถูกส่งต่อลงไปผ่าน method chain ที่ยาว Figure 7.2(a) แสดงตัวอย่างจาก datacenter service argument ใน command-line อธิบาย certificates ที่จะ ใช้สำหรับการสื่อสารที่ปลอดภัย ข้อมูลนี้จำเป็นโดย method ระดับล่าง m3 ซึ่งเรียก library method เพื่อเปิด socket แต่มันถูกส่งต่อลงไปผ่านทุก methods ในเส้นทางระหว่าง main และ m3 ตัวแปร cert ปรากฏใน signature ของแต่ละ intermediate methods

Pass-through variables เพิ่มความซับซ้อนเพราะมันบังคับให้ intermediate methods ทั้งหมดต้องรับรู้ถึงการมีอยู่ของมัน แม้ว่า methods เหล่านั้นจะไม่ได้ใช้ตัวแปรเหล่านั้นก็ตาม ยิ่งไปกว่านั้น ถ้ามีตัวแปรใหม่เกิดขึ้น (เช่น ระบบถูกสร้างขึ้นครั้งแรกโดยไม่รองรับ certificates แต่ต่อมาคุณตัดสินใจเพิ่มการรองรับนั้น) คุณอาจต้องแก้ไข interfaces และ methods จำนวนมากเพื่อส่งต่อตัวแปรผ่านเส้นทางที่เกี่ยวข้องทั้งหมด

การกำจัด pass-through variables อาจเป็นเรื่องท้าทาย วิธีหนึ่งคือดูว่ามี object ที่แชร์ระหว่าง method บนสุดและล่างสุดอยู่แล้วหรือไม่ ในตัวอย่าง datacenter service ของ Figure 7.2 อาจมี object ที่เก็บข้อมูลอื่นๆ เกี่ยวกับ network communication ซึ่งทั้ง main และ m3 สามารถเข้าถึงได้ ถ้าเป็นเช่นนั้น main สามารถเก็บข้อมูล certificate ไว้ใน object นั้น ดังนั้นมันไม่จำเป็นต้องถูกส่งต่อผ่าน methods ทั้งหมดในเส้นทางไปยัง m3 (ดู Figure 7.2(b) ) อย่างไรก็ตาม ถ้ามี object ดังกล่าว มันก็อาจเป็น pass-through variable เอง (ไม่อย่างนั้น m3 จะเข้าถึงมันได้อย่างไร?)

อีกวิธีคือเก็บข้อมูลใน global variable ดังใน Figure 7.2(c) วิธีนี้หลีกเลี่ยงความจำเป็นในการส่งต่อข้อมูลจาก method ไปยัง method แต่ global variables เกือบจะสร้างปัญหาอื่นๆ เสมอ ตัวอย่างเช่น global variables ทำให้ไม่สามารถสร้างสอง instances ที่เป็นอิสระของระบบเดียวกันใน process เดียวกันได้ เนื่องจากการเข้าถึง global variables จะขัดแย้งกัน อาจดูไม่น่าเป็นไปได้ที่คุณจะต้องการ multiple instances ใน production แต่พวกมันมักมีประโยชน์ในการทดสอบ

วิธีแก้ที่ฉันใช้บ่อยที่สุดคือการแนะนำ context object ดังใน Figure 7.2(d) context จะเก็บ global state ทั้งหมดของแอปพลิเคชัน (อะไรก็ตามที่มิฉะนั้นจะเป็น pass-through variable หรือ global variable) แอปพลิเคชันส่วนใหญ่มีหลายตัวแปรใน global state ซึ่งแสดงถึงสิ่งต่างๆ เช่น configuration options, shared subsystems, และ performance counters มี context object หนึ่งอันต่อหนึ่ง instance ของระบบ context อนุญาตให้หลาย instances ของระบบอยู่ร่วมกันใน process เดียว โดยแต่ละ instance มี context ของตัวเอง

น่าเสียดายที่ context อาจจำเป็นในหลายที่ ดังนั้นมันจึงอาจกลายเป็น pass-through variable ได้ เพื่อลดจำนวน methods ที่ต้องรับรู้ถึงมัน การอ้างอิงไปยัง context สามารถเก็บไว้ใน objects หลักส่วนใหญ่ของระบบ ในตัวอย่างของ Figure 7.2(d) คลาสที่มี m3 เก็บ reference ไปยัง context เป็น instance variable ใน objects ของมัน เมื่อ object ใหม่ถูกสร้างขึ้น method ที่สร้างจะดึง context reference จาก object ของมันและส่งต่อไปยัง constructor ของ object ใหม่ ด้วยวิธีนี้ context จะพร้อมใช้งานทุกที่ แต่มันปรากฏเป็น explicit argument เฉพาะใน constructors เท่านั้น

img

Figure 7.2: เทคนิคที่เป็นไปได้สำหรับการจัดการกับ pass-through variable ใน (a) cert ถูกส่งผ่าน methods m1 และ m2 ทั้งที่พวกมันไม่ได้ใช้มัน ใน (b) main และ m3 สามารถเข้าถึง object ร่วมกันได้ ดังนั้นตัวแปรสามารถถูกเก็บไว้ที่นั่นแทนที่จะส่งผ่าน m1 และ m2 ใน (c) cert ถูกเก็บเป็น global variable ใน (d) cert ถูกเก็บใน context object พร้อมกับข้อมูลทั่วทั้งระบบอื่นๆ เช่น timeout value และ performance counters; reference ไปยัง context ถูกเก็บไว้ใน objects ทั้งหมดที่ methods ของมันจำเป็นต้องเข้าถึง

Context object ทำให้การจัดการข้อมูลทั่วทั้งระบบเป็นหนึ่งเดียวและขจัดความจำเป็นสำหรับ pass-through variables ถ้าต้องการเพิ่มตัวแปรใหม่ ก็สามารถเพิ่มเข้าไปใน context object ได้ โค้ดที่มีอยู่จะไม่ได้รับผลกระทบยกเว้น constructor และ destructor ของ context context ทำให้ง่ายต่อการระบุและจัดการ global state ของระบบ เนื่องจากมันถูกเก็บไว้ในที่เดียว context ยังสะดวกสำหรับการทดสอบ: โค้ดทดสอบสามารถเปลี่ยน global configuration ของแอปพลิเคชันโดยการปรับเปลี่ยน fields ใน context ซึ่งจะทำได้ยากกว่ามากถ้าระบบใช้ pass-through variables

Contexts เป็นวิธีที่ห่างไกลจากอุดมคติ ตัวแปรที่เก็บใน context มีข้อเสียส่วนใหญ่ของ global variables; ตัวอย่างเช่น อาจไม่ชัดเจนว่าทำไมตัวแปรบางตัวถึงมีอยู่ หรือถูกใช้ที่ไหน หากไม่มีวินัย context อาจกลายเป็นถุงรวมข้อมูลขนาดใหญ่ที่สร้าง dependencies ที่ไม่ชัดเจนทั่วทั้งระบบ Contexts อาจสร้างปัญหาเรื่อง thread-safety; วิธีที่ดีที่สุดในการหลีกเลี่ยงปัญหาคือให้ตัวแปรใน context เป็น immutable น่าเสียดายที่ฉันยังไม่พบวิธีแก้ที่ดีกว่า contexts

7.6    Conclusion (บทสรุป)

ทุกชิ้นส่วนของ design infrastructure ที่เพิ่มเข้าไปในระบบ เช่น interface, argument, function, class, หรือ definition ล้วนเพิ่มความซับซ้อน เนื่องจากนักพัฒนาต้องเรียนรู้เกี่ยวกับ element เหล่านี้ เพื่อให้ element หนึ่งให้ผลประโยชน์สุทธิในการต่อสู้กับความซับซ้อน มันต้องกำจัดความซับซ้อนบางอย่างที่จะมีอยู่ถ้าไม่มี element การออกแบบนั้น มิฉะนั้น คุณจะดีกว่าถ้าสร้างระบบโดยไม่มี element นั้น ตัวอย่างเช่น คลาสสามารถลดความซับซ้อนได้โดยการ encapsulate ฟังก์ชันการทำงาน เพื่อให้ผู้ใช้คลาสไม่จำเป็นต้องตระหนักถึงมัน

กฏ "different layer, different abstraction" เป็นเพียงการประยุกต์ใช้แนวคิดนี้: ถ้าเลเยอร์ที่แตกต่างกันมี abstraction เดียวกัน เช่น pass-through methods หรือ decorators ก็มีโอกาสสูงที่พวกมันไม่ได้ให้ประโยชน์เพียงพอที่จะชดเชยกับโครงสร้างพื้นฐานเพิ่มเติมที่พวกมันเป็นตัวแทน ในทำนองเดียวกัน pass-through arguments ทำให้แต่ละ methods ต้องรับรู้ถึงการมีอยู่ของมัน (ซึ่งเพิ่มความซับซ้อน) โดยไม่ได้ช่วยเพิ่มฟังก์ชันการทำงานเพิ่มเติม