Chúng ta có cần xác nhận toàn bộ việc sử dụng mô-đun hay chỉ là đối số của các phương thức công khai?


9

Tôi đã nghe nói rằng nên xác thực các đối số của các phương thức công khai:

Động lực là điều dễ hiểu. Nếu một mô-đun sẽ được sử dụng sai cách, chúng tôi muốn ném ngoại lệ ngay lập tức thay vì bất kỳ hành vi không thể đoán trước.

Điều làm phiền tôi, đó là các đối số sai không phải là lỗi duy nhất có thể được thực hiện trong khi sử dụng một mô-đun. Dưới đây là một số tình huống lỗi trong đó chúng tôi cần thêm kiểm tra logic nếu chúng tôi thực hiện theo khuyến nghị và không muốn leo thang lỗi:

  • Cuộc gọi đến - cuộc tranh cãi bất ngờ
  • Cuộc gọi đến - mô-đun ở trạng thái sai
  • Cuộc gọi bên ngoài - kết quả không mong đợi được trả về
  • Cuộc gọi bên ngoài - tác dụng phụ không mong muốn (nhập kép vào mô-đun gọi, phá vỡ các trạng thái phụ thuộc khác)

Tôi đã cố gắng tính đến tất cả các điều kiện này và viết một mô-đun đơn giản với một phương thức (xin lỗi, không phải là 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;
    }
}

Bây giờ an toàn =)

Nó khá đáng sợ. Nhưng hãy tưởng tượng nó đáng sợ như thế nào trong một mô-đun thực với hàng tá phương thức, trạng thái phức tạp và rất nhiều cuộc gọi bên ngoài (hi, những người yêu thích tiêm phụ thuộc!). Lưu ý rằng nếu bạn đang gọi một mô-đun mà hành vi có thể bị ghi đè (lớp không được niêm phong trong C #) thì bạn đang thực hiện một cuộc gọi bên ngoài và hậu quả không thể dự đoán được trong phạm vi của người gọi.

Tóm tắt, cách nào là đúng và tại sao? Nếu bạn có thể chọn từ các tùy chọn bên dưới, hãy trả lời các câu hỏi bổ sung.

Kiểm tra toàn bộ mô-đun sử dụng. Chúng ta có cần kiểm tra đơn vị không? Có ví dụ về mã như vậy? Có nên hạn chế tiêm phụ thuộc trong việc sử dụng (vì nó sẽ gây ra logic kiểm tra nhiều hơn)? Không thực tế khi chuyển các kiểm tra đó sang thời gian gỡ lỗi (không bao gồm trong bản phát hành)?

Chỉ kiểm tra đối số. Từ kinh nghiệm của tôi, kiểm tra đối số - đặc biệt là kiểm tra null - là kiểm tra ít hiệu quả nhất, bởi vì lỗi đối số hiếm khi dẫn đến các lỗi phức tạp và leo thang lỗi. Hầu hết thời gian bạn sẽ nhận được NullReferenceExceptionở dòng tiếp theo. Vậy tại sao kiểm tra đối số là đặc biệt?

Đừng kiểm tra sử dụng mô-đun. Đó là ý kiến ​​khá phổ biến, bạn có thể giải thích tại sao?


Kiểm tra nên được thực hiện trong quá trình phân công trường để đảm bảo giữ bất biến.
Basilevs 16/07/2015

@Basilevs Thú vị ... Có phải từ Code Hợp đồng tư tưởng hay cái gì đó cũ hơn? Bạn có thể giới thiệu một cái gì đó để đọc (liên quan đến bình luận của bạn)?
astef 16/07/2015

Đó là một sự tách biệt cơ bản của mối quan tâm. Tất cả các trường hợp của bạn được bảo hiểm, trong khi sao chép mã là tối thiểu và trách nhiệm được xác định rõ.
Basilevs 16/07/2015

@Basilevs Vì vậy, đừng kiểm tra hành vi của các mô-đun khác mà hãy kiểm tra các bất biến trạng thái của chính nó. Nghe có vẻ hợp lý. Nhưng tại sao tôi không thấy biên nhận đơn giản này trong các câu hỏi liên quan về kiểm tra đối số?
astef 16/07/2015

Chà, một số kiểm tra hành vi vẫn cần thiết, nhưng chúng chỉ nên được thực hiện trên các giá trị thực sự được sử dụng, chứ không phải các giá trị được chuyển tiếp ở nơi khác. Ví dụ: bạn dựa vào triển khai Danh sách để kiểm tra lỗi OOB, trái ngược với kiểm tra chỉ mục trong mã máy khách. Thông thường, chúng là các lỗi khung mức thấp và không yêu cầu phải được phát ra bằng tay.
Basilevs 16/07/2015

Câu trả lời:


2

TL; DR: Xác thực thay đổi trạng thái, dựa vào [hiệu lực của] trạng thái hiện tại.

Dưới đây tôi chỉ xem xét xác minh kích hoạt phát hành. Các xác nhận hoạt động chỉ gỡ lỗi là một dạng tài liệu, hữu ích theo cách riêng của nó và nằm ngoài phạm vi của câu hỏi này.

Xem xét các nguyên tắc sau:

  • Tâm lý chung
  • Thất bại nhanh
  • KHÔ
  • SRP

Định nghĩa

  • Thành phần - một đơn vị cung cấp API
  • Máy khách - người dùng API của thành phần

Trạng thái đột biến

Vấn đề

Trong các ngôn ngữ bắt buộc, triệu chứng lỗi và nguyên nhân của nó có thể được phân tách bằng nhiều giờ nâng vật nặng. Tham nhũng nhà nước có thể che giấu bản thân và biến đổi dẫn đến thất bại không thể giải thích được, vì việc kiểm tra nhà nước hiện tại không thể tiết lộ toàn bộ quá trình tham nhũng và do đó, nguồn gốc của lỗi.

Giải pháp

Mỗi thay đổi của nhà nước nên được chế tạo cẩn thận và xác minh. Một cách để đối phó với trạng thái đột biến là giữ nó ở mức tối thiểu. Điều này đạt được bằng cách:

  • loại hệ thống (tuyên bố const và thành viên cuối cùng)
  • giới thiệu bất biến
  • xác minh mọi thay đổi trạng thái của thành phần thông qua API công khai

Khi mở rộng trạng thái của một thành phần, hãy xem xét làm như vậy bằng cách để trình biên dịch thực thi tính bất biến của dữ liệu mới. Ngoài ra, thực thi mọi ràng buộc thời gian chạy hợp lý, giới hạn các trạng thái kết quả tiềm năng ở một tập hợp nhỏ nhất có thể được xác định rõ.

Thí dụ

// 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;
    }
}

Sự lặp lại và sự gắn kết trách nhiệm

Vấn đề

Kiểm tra các điều kiện tiên quyết và các điều kiện hậu của hoạt động dẫn đến sự trùng lặp mã xác minh trong cả máy khách và thành phần. Xác thực việc gọi thành phần thường buộc khách hàng phải chịu một số trách nhiệm của thành phần.

Giải pháp

Dựa vào thành phần để thực hiện xác minh trạng thái khi có thể. Các thành phần là để cung cấp một API không yêu cầu xác minh sử dụng đặc biệt (ví dụ xác minh đối số hoặc thực thi chuỗi hoạt động) để giữ trạng thái thành phần được xác định rõ. Họ có nghĩa vụ xác minh các đối số gọi API theo yêu cầu, báo cáo các thất bại bằng các phương tiện cần thiết và cố gắng ngăn chặn tham nhũng nhà nước của họ.

Khách hàng nên dựa vào các thành phần để xác minh việc sử dụng API của họ. Không chỉ tránh sự lặp lại, khách hàng không còn phụ thuộc vào chi tiết triển khai thêm của thành phần. Xem xét khuôn khổ là một thành phần. Chỉ viết mã xác minh tùy chỉnh khi các bất biến của thành phần không đủ nghiêm ngặt hoặc để đóng gói ngoại lệ các thành phần làm chi tiết triển khai.

Nếu một hoạt động không thay đổi trạng thái và không nằm trong các xác minh thay đổi trạng thái, hãy xác minh mọi đối số ở mức sâu nhất có thể.

Thí dụ

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;
   }
}

Câu trả lời

Khi các nguyên tắc được mô tả được áp dụng cho ví dụ trong câu hỏi, chúng tôi nhận được:

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();            
    }
}

Tóm lược

Trạng thái của khách hàng bao gồm các giá trị trường riêng và các phần của trạng thái thành phần không được bao phủ bởi các bất biến của chính nó. Việc xác minh chỉ nên được thực hiện trước khi thay đổi trạng thái thực tế của khách hàng.


1

Một lớp chịu trách nhiệm cho nhà nước riêng của mình. Vì vậy, xác nhận đến mức nó giữ hoặc đặt mọi thứ ở trạng thái chấp nhận được.

Nếu một mô-đun sẽ được sử dụng sai cách, chúng tôi muốn ném ngoại lệ ngay lập tức thay vì bất kỳ hành vi không thể đoán trước.

Không, đừng ném ngoại lệ, thay vào đó hãy đưa ra hành vi có thể dự đoán được. Hệ quả của trách nhiệm nhà nước là làm cho lớp / ứng dụng trở nên khoan dung như thực tế. Ví dụ, đi nulltới aCollection.Add()? Chỉ cần không thêm và tiếp tục đi. Bạn nhận được nullđầu vào để tạo một đối tượng? Tạo một đối tượng null hoặc một đối tượng mặc định. Ở trên, doorlà đã open? Vì vậy, những gì, tiếp tục đi. DoorFactoryđối số là null? Tạo một cái mới. Khi tôi tạo một enumtôi luôn có một Undefinedthành viên. Tôi sử dụng tự do của Dictionarys và enumsđể xác định rõ ràng mọi thứ; và điều này đi một chặng đường dài hướng tới việc cung cấp hành vi dự đoán.

(hi, những người yêu thích tiêm phụ thuộc!)

Phải, mặc dù tôi đi qua bóng của thung lũng các tham số tôi sẽ không sợ tranh luận. Trước đó tôi cũng sử dụng các tham số mặc định và tùy chọn càng nhiều càng tốt.

Tất cả các bên trên cho phép xử lý nội bộ để tiếp tục. Trong một ứng dụng cụ thể, tôi có hàng tá phương thức trên nhiều lớp chỉ với một nơi duy nhất có ngoại lệ được ném. Ngay cả sau đó, không phải vì đối số null hoặc tôi không thể tiếp tục xử lý vì mã cuối cùng đã tạo ra một đối tượng "không chức năng" / "null".

biên tập

trích dẫn nhận xét của tôi trong toàn bộ. Tôi nghĩ rằng thiết kế không nên đơn giản là "bỏ cuộc" khi gặp 'null'. Đặc biệt là sử dụng một đối tượng tổng hợp.

Chúng ta đang quên các khái niệm / giả định chính ở đây - encapsulation& single responsibility. Hầu như không có kiểm tra null sau lớp đầu tiên, tương tác máy khách. Các mã là khoan dung mạnh mẽ. Các lớp được thiết kế với các trạng thái mặc định và do đó hoạt động mà không được viết như thể mã tương tác là lỗi, rogue rác. Một cha mẹ hỗn hợp không phải tiếp cận các lớp con để đánh giá tính hợp lệ (và bằng hàm ý, kiểm tra null trong tất cả các ngóc ngách). Phụ huynh biết trạng thái mặc định của trẻ có nghĩa là gì

kết thúc chỉnh sửa


1
Không thêm một yếu tố thu thập không hợp lệ là một hành vi rất khó lường.
Basilevs

1
Nếu tất cả các giao diện sẽ được thiết kế theo cách khoan dung như vậy, một ngày nào đó, vì lỗi banal, các chương trình sẽ vô tình thức tỉnh và hủy diệt loài người.
astef 20/07/2015

Chúng ta đang quên các khái niệm / giả định chính ở đây - encapsulation& single responsibility. Hầu như không có nullkiểm tra sau lớp đầu tiên, tương tác máy khách. Mã này <strike> khoan dung </ strike> mạnh mẽ. Các lớp được thiết kế với các trạng thái mặc định và do đó hoạt động mà không được viết như thể mã tương tác là lỗi, rogue rác. Một cha mẹ hỗn hợp không phải tiếp cận các lớp con để đánh giá tính hợp lệ (và bằng hàm ý, kiểm tra nulltất cả các ngóc ngách). Cha mẹ biết những gì có nghĩa là trạng thái mặc định của một đứa trẻ
radarbob
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.