ฉันต้องทนทุกข์ทรมานจากการห่อหุ้มมากเกินไป?


11

ฉันสังเกตเห็นบางอย่างในรหัสของฉันในโครงการต่าง ๆ ที่ดูเหมือนว่ากลิ่นรหัสให้ฉันและสิ่งที่ไม่ดีที่จะทำ แต่ฉันไม่สามารถจัดการกับมัน

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

นี่คือตัวอย่างของคลาสที่อ่านข้อมูลบางอย่างจากไฟล์. csv และส่งคืนกลุ่มลูกค้า (วัตถุอื่นที่มีเขตข้อมูลและแอตทริบิวต์ต่าง ๆ )

public class GroupOfCustomersImporter {
    //... Call fields ....
    public GroupOfCustomersImporter(String filePath) {
        this.filePath = filePath;
        customers = new HashSet<Customer>();
        createCSVReader();
        read();
        constructTTRP_Instance();
    }

    private void createCSVReader() {
        //....
    }

    private void read() {
        //.... Reades the file and initializes the class attributes
    }

    private void readFirstLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readSecondLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readCustomerLine(String[] inputLine) { 
        //.... Method used by the read() method
    }

    private void constructGroupOfCustomers() {
        //this.groupOfCustomers = new GroupOfCustomers(**attributes of the class**);
    }

    public GroupOfCustomers getConstructedGroupOfCustomers() {
        return this.GroupOfCustomers;
    }
}

ในขณะที่คุณสามารถเห็นชั้นเรียนมีเพียงตัวสร้างที่เรียกวิธีการส่วนตัวบางอย่างเพื่อให้งานเสร็จฉันรู้ว่ามันไม่ใช่วิธีปฏิบัติที่ดีโดยทั่วไป แต่ฉันชอบที่จะสรุปการทำงานทั้งหมดในชั้นเรียนแทนที่จะทำให้วิธีการสาธารณะ ลูกค้าควรใช้วิธีนี้:

GroupOfCustomersImporter importer = new GroupOfCustomersImporter(filepath)
importer.createCSVReader();
read();
GroupOfCustomer group = constructGoupOfCustomerInstance();

ฉันชอบสิ่งนี้เพราะฉันไม่ต้องการใส่โค้ดที่ไม่มีประโยชน์ในโค้ดด้านข้างของลูกค้ารบกวนคลาสของลูกค้าพร้อมรายละเอียดการใช้งาน

ดังนั้นนี่เป็นนิสัยที่ไม่ดีจริง ๆ หรือ ถ้าใช่ฉันจะหลีกเลี่ยงได้อย่างไร โปรดทราบว่าข้างต้นเป็นเพียงตัวอย่างง่ายๆ ลองนึกภาพสถานการณ์เดียวกันที่เกิดขึ้นในสิ่งที่ซับซ้อนขึ้นเล็กน้อย

คำตอบ:


17

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

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

เป็นการดีที่คุณควรจะสามารถทดสอบหน่วยของคุณกับอินเทอร์เฟซสาธารณะเท่านั้นและถ้ามีการอ้างอิงภายนอกคุณอาจต้องแนะนำวัตถุปลอม / จำลอง / ต้นขั้วเพื่อแยกรหัสที่กำลังทดสอบ

อย่างไรก็ตามถ้าคุณทำสิ่งนี้และคุณยังรู้สึกว่าคุณไม่สามารถทดสอบทุกส่วนของชั้นเรียนได้ง่ายมันก็มีโอกาสมากที่ชั้นเรียนของคุณจะทำมากเกินไป ด้วยจิตวิญญาณของหลักการ SRPคุณสามารถทำตามสิ่งที่Michael Feathersเรียกว่า "Sprout Class Pattern" และแยกคลาสดั้งเดิมของคุณเข้าสู่คลาสใหม่

ในตัวอย่างของคุณคุณมีคลาสผู้นำเข้าที่รับผิดชอบการอ่านไฟล์ข้อความด้วย หนึ่งในตัวเลือกของคุณคือการแยกไฟล์การอ่านรหัสลงในคลาส InputFileParser แยกต่างหาก ตอนนี้ฟังก์ชั่นส่วนตัวเหล่านั้นกลายเป็นสาธารณะและสามารถทดสอบได้ง่าย ในขณะเดียวกันคลาส parser ไม่ใช่สิ่งที่ลูกค้าภายนอกมองเห็น (ทำเครื่องหมายว่า "ภายใน" อย่าเผยแพร่ไฟล์ส่วนหัวหรือเพียงแค่ไม่โฆษณาเป็นส่วนหนึ่งของ API ของคุณ) เพราะสิ่งเหล่านั้นจะยังคงใช้ต้นฉบับต่อไป ผู้นำเข้าที่อินเทอร์เฟซจะยังคงสั้นและหวาน


1

เป็นอีกทางเลือกหนึ่งในการวางวิธีการมากมายในการสร้างคลาสของคุณ - ซึ่งฉันเห็นว่าเป็นนิสัยที่ควรหลีกเลี่ยง - คุณสามารถสร้างวิธีการจากโรงงานเพื่อจัดการกับการเริ่มต้นสิ่งต่าง ๆ ทั้งหมดที่คุณไม่ต้องการรบกวนลูกค้า เคียงข้างกับ นี่หมายความว่าคุณจะต้องเปิดเผยวิธีการที่ดูเหมือนว่าconstructGroupOfCustomers()วิธีการของคุณเป็นแบบสาธารณะและใช้เป็นวิธีการคงที่ของคลาสของคุณ:

GroupOfCustomersImporter importer = 
    new GroupOfCustomersImporter.CreateInstance();

หรือเป็นวิธีการแยกระดับโรงงานอาจ:

ImporterFactory factory = new ImporterFactory();
GroupOfCustomersImporter importer = factory.CreateGroupOfCustomersImporter();

เหล่านี้เป็นเพียงคู่แรกของตัวเลือกที่คิดออกตรงด้านบนของหัวของฉัน

นอกจากนี้ยังควรพิจารณาด้วยว่าสัญชาตญาณของคุณกำลังพยายามบอกบางอย่างกับคุณ เมื่อลำไส้ของคุณบอกคุณว่ารหัสกำลังเริ่มมีกลิ่นมันอาจมีกลิ่นและจะเป็นการดีกว่าที่จะทำอะไรสักอย่างก่อนที่มันจะกลับมา reeks! แน่นอนว่าบนพื้นผิวอาจไม่มีอะไรผิดปกติกับรหัสของคุณต่อการตรวจสอบอย่างไรก็ตามการตรวจสอบอย่างรวดเร็วและ refactor จะช่วยในการกำหนดความคิดของคุณเกี่ยวกับปัญหาเพื่อให้คุณสามารถย้ายไปยังสิ่งอื่น ๆ ได้อย่างมั่นใจโดยไม่ต้องกังวล บ้านที่มีศักยภาพของการ์ด ในกรณีพิเศษนี้การมอบหมายความรับผิดชอบบางอย่างของคอนสตรัคเตอร์ - หรือถูกเรียกโดย - โรงงานช่วยให้คุณมั่นใจได้ว่าคุณสามารถควบคุมระดับอินสแตนซ์ของคลาสของคุณได้ดียิ่งขึ้น


1

เพิ่มคำตอบของฉันเองเพราะมันนานเกินไปสำหรับความคิดเห็น

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

อย่างไรก็ตามคุณสามารถทำให้ตัวอย่างทดสอบได้มากขึ้นโดยการให้สตรีมแทนชื่อไฟล์ - วิธีที่คุณเพียงแค่จัดหา csv ที่รู้จักจากหน่วยความจำหรือไฟล์ถ้าคุณต้องการ มันจะทำให้ชั้นเรียนของคุณแข็งแกร่งขึ้นเมื่อคุณต้องการเพิ่มการสนับสนุน http)

นอกจากนี้ให้ดูที่การใช้ lazy-init:

public class Csv
{
  private CustomerList list;

  public CustomerList get()
  {
    if(list == null)
        load();
     return list;
  }
}

1

ตัวสร้างไม่ควรทำมากเกินไปยกเว้นการเริ่มต้นตัวแปรบางอย่างหรือสถานะของวัตถุ การใช้ตัวสร้างมากเกินไปอาจทำให้เกิดข้อยกเว้นแบบรันไทม์ ฉันจะพยายามหลีกเลี่ยงลูกค้าที่ทำอะไรซักอย่างเช่นนี้:

MyClass a;
try {
   a = new MyClass();
} catch (MyException e) {
   //do something
}

แทนที่จะทำ:

MyClass a = new MyClass(); // Or a factory method
try {
   a.doSomething();
} catch (MyException e) {
   //do something
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.