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


9

ฉันได้ยินมาว่าขอแนะนำให้ตรวจสอบข้อโต้แย้งของวิธีการสาธารณะ:

แรงจูงใจสามารถเข้าใจได้ หากโมดูลจะถูกใช้ในทางที่ผิดเราต้องการที่จะโยนข้อยกเว้นทันทีแทนที่จะเป็นพฤติกรรมที่คาดเดาไม่ได้

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

  • สายเรียกเข้า - อาร์กิวเมนต์ที่ไม่คาดคิด
  • สายเรียกเข้า - โมดูลอยู่ในสถานะไม่ถูกต้อง
  • โทรภายนอก - ส่งคืนผลลัพธ์ที่ไม่คาดคิด
  • การโทรภายนอก - ผลข้างเคียงที่ไม่คาดคิด (การป้อนสองครั้งในโมดูลการโทร, การแยกสถานะการอ้างอิงอื่น ๆ )

ฉันได้ลองคำนึงถึงเงื่อนไขเหล่านี้และเขียนโมดูลง่าย ๆ ด้วยวิธีการหนึ่ง (ขออภัยไม่ใช่ -C # guys):

public sealed class Room
{
    private readonly IDoorFactory _doorFactory;
    private bool _entered;
    private IDoor _door;
    public Room(IDoorFactory doorFactory)
    {
        if (doorFactory == null)
            throw new ArgumentNullException("doorFactory");
        _doorFactory = doorFactory;
    }
    public void Open()
    {
        if (_door != null)
            throw new InvalidOperationException("Room is already opened");
        if (_entered)
            throw new InvalidOperationException("Double entry is not allowed");
        _entered = true;
        _door = _doorFactory.Create();
        if (_door == null)
            throw new IncompatibleDependencyException("doorFactory");
        _door.Open();
        _entered = false;
    }
}

ตอนนี้มันปลอดภัย =)

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

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

ตรวจสอบการใช้งานโมดูลทั้งหมด เราจำเป็นต้องมีการทดสอบหน่วยหรือไม่? มีตัวอย่างของรหัสดังกล่าวหรือไม่ การใช้การฉีดที่ต้องพึ่งพานั้น จำกัด ในการใช้งาน (เนื่องจากจะทำให้ตรรกะการตรวจสอบมากขึ้น)? การย้ายการตรวจสอบเหล่านั้นไปใช้ในการดีบั๊กเป็นจริงหรือไม่?

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

อย่าตรวจสอบการใช้งานโมดูล มันค่อนข้างเป็นความคิดเห็นที่ไม่เป็นที่นิยมคุณช่วยอธิบายได้ไหม


ควรทำการตรวจสอบระหว่างการมอบหมายงานภาคสนามเพื่อให้แน่ใจว่ามีค่าคงที่
Basilevs

@Basilevs ที่น่าสนใจ ... มันมาจากอุดมการณ์สัญญารหัสหรือสิ่งที่เก่ากว่า? คุณช่วยแนะนำให้อ่าน (เกี่ยวกับความคิดเห็นของคุณ) ได้ไหม?
astef

มันเป็นการแยกความกังวลพื้นฐาน ทุกกรณีของคุณได้รับความคุ้มครองในขณะที่การทำสำเนารหัสมีน้อยที่สุดและมีความรับผิดชอบที่ชัดเจน
Basilevs

@Basilevs ดังนั้นอย่าตรวจสอบพฤติกรรมของโมดูลอื่นเลย แต่ตรวจสอบค่าคงที่ของรัฐ ฟังดูสมเหตุสมผล แต่ทำไมฉันไม่เห็นใบเสร็จอย่างง่ายนี้ในคำถามที่เกี่ยวข้องเกี่ยวกับการตรวจสอบอาร์กิวเมนต์?
astef

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

คำตอบ:


2

TL; DR:ตรวจสอบความถูกต้องของการเปลี่ยนแปลงสถานะพึ่งพา [ความถูกต้องของ] สถานะปัจจุบัน

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

พิจารณาหลักการต่อไปนี้:

  • การใช้ความคิดเบื้องต้น
  • ล้มเหลวอย่างรวดเร็ว
  • แห้ง
  • SRP

คำนิยาม

  • Component - หน่วยที่ให้บริการ API
  • ลูกค้า - ผู้ใช้ API ขององค์ประกอบ

รัฐไม่แน่นอน

ปัญหา

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

สารละลาย

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

  • ระบบพิมพ์ (ประกาศ const และสมาชิกสุดท้าย)
  • แนะนำค่าคงที่
  • การตรวจสอบการเปลี่ยนแปลงสถานะของทุกองค์ประกอบผ่าน API สาธารณะ

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

ตัวอย่าง

// Wrong
class Natural {
    private int number;
    public Natural(int number) {
        this.number = number;
    }
    public int getInt() {
      if (number < 1)
          throw new InvalidOperationException();
      return number;
    }
}

// Right
class Natural {
    private readonly int number;
    /**
     * @param number - positive number
     */
    public Natural(int number) {
      // Going to modify state, verification is required
      if (number < 1)
        throw new ArgumentException("Natural number should be  positive: " + number);
      this.number = number;
    }
    public int getInt() {
      // State is guaranteed by construction and compiler
      return number;
    }
}

การทำงานร่วมกันซ้ำซ้อนและความรับผิดชอบ

ปัญหา

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

สารละลาย

พึ่งพาส่วนประกอบเพื่อทำการตรวจสอบสถานะเมื่อเป็นไปได้ ส่วนประกอบคือการจัดหา API ที่ไม่จำเป็นต้องมีการตรวจสอบการใช้งานพิเศษ (การตรวจสอบข้อโต้แย้งหรือการบังคับใช้ลำดับการดำเนินงานเป็นต้น) เพื่อให้สถานะของส่วนประกอบมีความชัดเจน พวกเขามีหน้าที่ตรวจสอบข้อโต้แย้งการร้องขอ API ตามที่ต้องการรายงานความล้มเหลวด้วยวิธีการที่จำเป็นและพยายามป้องกันการทุจริตของรัฐ

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

หากการดำเนินการไม่เปลี่ยนสถานะและไม่ครอบคลุมโดยการตรวจสอบการเปลี่ยนแปลงสถานะให้ตรวจสอบทุกอาร์กิวเมนต์ในระดับที่ลึกที่สุดที่เป็นไปได้

ตัวอย่าง

class Store {
  private readonly List<int> slots = new List<int>();
  public void putToSlot(int slot, int data) {
    if (slot < 0 || slot >= slots.Count) // Unnecessary, validated by List, only needed for custom error message
      throw new ArgumentException("data");
    slots[slot] = data;
  }
}

class Natural {
   int _number;
   public Natural(int number) {
       if (number < 1)
          number = 1;  //Wrong: client can't rely on argument verification, additional state uncertainity is introduced.  Right: throw new ArgumentException(number);
       _number = number;
   }
}

ตอบ

เมื่อหลักการที่อธิบายถูกนำไปใช้กับตัวอย่างที่เป็นปัญหาเราจะได้รับ:

public sealed class Room
{
    private bool _entered = false;
    // Do not use lazy instantiation if not absolutely necessary, this introduces additional mutable state
    private readonly IDoor _door;
    public Room(IDoorFactory doorFactory)
    {
        // Rely on system null check
        IDoor door = _doorFactory.Create();
        // Modifying own state, verification is required
        if (door == null)
           throw new ArgumentNullException("Door");
        _door = door;
    }
    public void Enter()
    {
        // Room invariants do not guarantee _entered value. Door state is indirectly a part of our state. Verification is required to prevent second door state change below.
        if (_entered)
           throw new InvalidOperationException("Double entry is not allowed");
        _entered = true;     
        // rely on immutability for _door field to be non-null
        // rely on door implementation to control resulting door state       
        _door.Open();            
    }
}

สรุป

สถานะของลูกค้าประกอบด้วยค่าฟิลด์ของตัวเองและบางส่วนของสถานะของส่วนประกอบที่ไม่ครอบคลุมโดยค่าคงที่ของมันเอง การยืนยันควรกระทำก่อนการเปลี่ยนแปลงสถานะจริงของลูกค้า


1

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

หากโมดูลจะถูกใช้ในทางที่ผิดเราต้องการที่จะโยนข้อยกเว้นทันทีแทนที่จะเป็นพฤติกรรมที่คาดเดาไม่ได้

ไม่ไม่ส่งข้อยกเว้นให้ส่งพฤติกรรมที่คาดเดาได้แทน ข้อพิสูจน์ความรับผิดชอบของรัฐคือการทำให้ชั้นเรียน / การสมัครเป็นเรื่องที่ยอมรับได้ในทางปฏิบัติ ตัวอย่างเช่นส่งผ่านnullไปยังaCollection.Add()? อย่าเพิ่มและก้าวต่อไป คุณได้รับnullข้อมูลสำหรับการสร้างวัตถุหรือไม่? สร้างวัตถุเป็นโมฆะหรือวัตถุเริ่มต้น ข้างต้นdoorเป็นอยู่แล้วopen? ดังนั้นทำอะไรต่อไป DoorFactoryข้อโต้แย้งเป็นโมฆะ? สร้างใหม่ เมื่อฉันสร้างenumฉันมักจะมีUndefinedสมาชิก ฉันใช้งานอย่างเสรีDictionaryและenumsกำหนดสิ่งต่าง ๆ อย่างชัดเจน และสิ่งนี้จะนำไปสู่การสร้างพฤติกรรมที่สามารถคาดการณ์ได้

(สวัสดีคนรักการฉีดพึ่งพา!)

แม้ว่าฉันจะเดินผ่านเงาของหุบเขาของพารามิเตอร์ฉันจะไม่กลัวข้อโต้แย้ง ก่อนหน้านี้ฉันยังใช้พารามิเตอร์เริ่มต้นและเป็นตัวเลือกให้มากที่สุด

ทั้งหมดข้างต้นช่วยให้การประมวลผลภายในดำเนินต่อไป ในแอปพลิเคชันเฉพาะฉันมีวิธีการมากมายในหลายคลาสโดยมีเพียงที่เดียวที่มีข้อยกเว้นเกิดขึ้น ถึงอย่างนั้นมันก็ไม่ได้เกิดจากการมีข้อโต้แย้งที่เป็นโมฆะหรือฉันไม่สามารถดำเนินการต่อได้เพราะรหัสสิ้นสุดลงการสร้างวัตถุ "ไม่ทำงาน" / "โมฆะ"

แก้ไข

อ้างถึงความคิดเห็นของฉันมันครบถ้วน ฉันคิดว่าการออกแบบไม่ควร "ยอมแพ้" เมื่อเผชิญหน้ากับ 'โมฆะ' โดยเฉพาะการใช้วัตถุคอมโพสิต

เรากำลังลืมแนวคิดหลัก / สมมติฐานที่นี่ - และencapsulation single responsibilityไม่มีการตรวจสอบว่างเปล่าหลังจากเลเยอร์แรกที่โต้ตอบกับลูกค้า รหัสใจกว้างที่แข็งแกร่ง คลาสได้รับการออกแบบโดยมีสถานะเริ่มต้นและทำงานได้โดยไม่ต้องถูกเขียนราวกับว่ารหัสการโต้ตอบเป็นขยะข้อผิดพลาดอันธพาล ผู้ปกครองคอมโพสิตไม่จำเป็นต้องเข้าถึงเลเยอร์ของเด็กเพื่อประเมินความถูกต้อง (และโดยนัยให้ตรวจสอบว่าเป็นโมฆะในทุกซอกทุกมุม) ผู้ปกครองรู้ว่าสถานะเริ่มต้นของเด็กหมายถึงอะไร

สิ้นสุดการแก้ไข


1
การไม่เพิ่มองค์ประกอบคอลเลกชันที่ไม่ถูกต้องเป็นพฤติกรรมที่คาดเดาไม่ได้มาก
Basilevs

1
หากอินเทอร์เฟซทั้งหมดจะได้รับการออกแบบในลักษณะที่ทนต่อความอดทนหนึ่งวันเนื่องจากข้อผิดพลาดซ้ำซ้อนโปรแกรมจะตื่นขึ้นมาโดยไม่ตั้งใจและทำลายมนุษยชาติ
astef

เรากำลังลืมแนวคิดหลัก / สมมติฐานที่นี่ - และencapsulation single responsibilityแทบไม่มีการnullตรวจสอบหลังจากเลเยอร์แรกที่โต้ตอบกับลูกค้า รหัสคือ <strike> ความอดทน </strike> แข็งแกร่ง คลาสได้รับการออกแบบโดยมีสถานะเริ่มต้นและทำงานได้โดยไม่ต้องถูกเขียนราวกับว่ารหัสการโต้ตอบเป็นขยะข้อผิดพลาดอันธพาล ผู้ปกครองแบบรวมไม่จำเป็นต้องเข้าถึงเลเยอร์เด็กเพื่อประเมินความถูกต้อง (และโดยนัยให้ตรวจสอบnullในทุกซอกทุกมุม) ผู้ปกครองรู้ว่าสถานะเริ่มต้นของเด็กหมายถึงอะไร
Radarbob
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.