Tôi có nên thêm mã dự phòng ngay bây giờ chỉ trong trường hợp có thể cần thiết trong tương lai không?


114

Đúng hay sai, hiện tại tôi tin rằng tôi nên luôn cố gắng làm cho mã của mình mạnh nhất có thể, ngay cả khi điều này có nghĩa là thêm mã / kiểm tra dự phòng mà tôi biết sẽ không được sử dụng ngay bây giờ, nhưng chúng có thể là x số năm xuống dòng.

Ví dụ: tôi hiện đang làm việc trên một ứng dụng di động có đoạn mã này:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
    }

    //2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
    if(rows.Count == 0)
    {
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

Nhìn cụ thể vào phần hai, tôi biết rằng nếu phần một là đúng, thì phần hai cũng sẽ đúng. Tôi không thể nghĩ ra bất kỳ lý do nào là tại sao phần một sẽ sai và phần hai đánh giá đúng, điều này làm cho iftuyên bố thứ hai trở nên dư thừa.

Tuy nhiên, có thể có một trường hợp trong tương lai nơi iftuyên bố thứ hai này thực sự cần thiết, và vì một lý do đã biết.

Một số người có thể nhìn vào điều này ban đầu và nghĩ rằng tôi đang lập trình với suy nghĩ trong tương lai, đó rõ ràng là một điều tốt. Nhưng tôi biết một vài trường hợp loại mã này có lỗi "ẩn" đối với tôi. Có nghĩa là nó khiến tôi mất nhiều thời gian hơn để tìm hiểu tại sao chức năng lại hoạt xyzđộng abckhi nó thực sự nên làm def.

Mặt khác, cũng có nhiều trường hợp loại mã này đã giúp việc cải thiện mã với các hành vi mới dễ dàng hơn nhiều, vì tôi không phải quay lại và đảm bảo rằng tất cả các kiểm tra có liên quan đều được thực hiện.

Có bất kỳ hướng dẫn quy tắc chung nào cho loại mã này không? (Tôi cũng muốn biết liệu điều này sẽ được coi là thực hành tốt hay xấu?)

Lưu ý: Điều này có thể được coi là tương tự với câu hỏi này, tuy nhiên không giống như câu hỏi đó, tôi muốn có một câu trả lời giả sử không có thời hạn.

TLDR: Tôi có nên đi xa hơn để thêm mã dự phòng để làm cho nó có tiềm năng mạnh mẽ hơn trong tương lai không?



95
Trong phát triển phần mềm, những điều không bao giờ nên xảy ra mọi lúc.
Roman Reiner

34
Nếu bạn biết if(rows.Count == 0)sẽ không bao giờ xảy ra, thì bạn có thể đưa ra một ngoại lệ khi nó xảy ra - và kiểm tra xem tại sao giả định của bạn trở nên sai.
Knut

9
Không liên quan đến câu hỏi, nhưng tôi nghi ngờ mã của bạn có lỗi. Khi các hàng là null, một danh sách mới được tạo và (tôi đoán) bị ném đi. Nhưng khi các hàng không rỗng, một danh sách hiện có sẽ được thay đổi. Một thiết kế tốt hơn có thể là để khẳng định rằng máy khách vượt qua trong một danh sách có thể có hoặc không trống.
Theodore Norvell

9
Tại sao sẽ rowslà null? Không bao giờ có một lý do chính đáng, ít nhất là trong .NET, cho một bộ sưu tập là null. Trống rỗng , chắc chắn, nhưng không null . Tôi sẽ ném một ngoại lệ nếu rowslà null, bởi vì nó có nghĩa là có một lỗi trong logic của người gọi.
Kyralessa

Câu trả lời:


176

Như một bài tập, trước tiên hãy xác minh logic của bạn. Mặc dù như chúng ta sẽ thấy, bạn có vấn đề lớn hơn bất kỳ vấn đề logic nào.

Gọi điều kiện thứ nhất A và điều kiện thứ hai B.

Trước tiên bạn nói:

Nhìn cụ thể vào phần hai, tôi biết rằng nếu phần một là đúng, thì phần hai cũng sẽ đúng.

Đó là: A ngụ ý B, hoặc, theo các thuật ngữ cơ bản hơn (NOT A) OR B

Và sau đó:

Tôi không thể nghĩ ra bất kỳ lý do nào về lý do tại sao phần một sẽ sai và phần hai đánh giá đúng, điều này làm cho câu lệnh thứ hai trở nên dư thừa.

Đó là : NOT((NOT A) AND B). Áp dụng luật của Demorgan để có (NOT B) OR Ađược B ngụ ý A.

Do đó, nếu cả hai phát biểu của bạn đều đúng thì A ngụ ý B và B ngụ ý A, có nghĩa là chúng phải bằng nhau.

Do đó, kiểm tra là dư thừa. Bạn dường như có bốn đường dẫn mã thông qua chương trình nhưng thực tế bạn chỉ có hai đường dẫn.

Vì vậy, bây giờ câu hỏi là: làm thế nào để viết mã? Câu hỏi thực sự là: hợp đồng đã nêu của phương pháp là gì? Câu hỏi liệu các điều kiện có dư thừa là cá trích đỏ không. Câu hỏi thực sự là "tôi đã thiết kế một hợp đồng hợp lý chưa và phương pháp của tôi có thực hiện rõ ràng hợp đồng đó không?"

Hãy nhìn vào tuyên bố:

public static CalendarRow AssignAppointmentToRow(
    Appointment app,    
    List<CalendarRow> rows)

Nó là công khai, do đó, nó phải được mạnh mẽ để dữ liệu xấu từ những người gọi tùy ý.

Nó trả về một giá trị, vì vậy nó sẽ hữu ích cho giá trị trả về của nó, không phải cho tác dụng phụ của nó.

Tuy nhiên, tên của phương thức là một động từ, cho thấy rằng nó hữu ích cho tác dụng phụ của nó.

Hợp đồng của tham số danh sách là:

  • Một danh sách null là OK
  • Một danh sách có một hoặc nhiều yếu tố trong đó là OK
  • Một danh sách không có yếu tố nào trong đó là sai và không nên có thể.

Hợp đồng này là điên rồ . Hãy tưởng tượng viết tài liệu cho việc này! Hãy tưởng tượng viết trường hợp thử nghiệm!

Lời khuyên của tôi: bắt đầu lại. API này có giao diện máy kẹo được viết trên đó. . sai mục

Đây là cách thiết kế hợp đồng hợp lý:

Làm cho phương thức của bạn hữu ích cho tác dụng phụ của nó hoặc giá trị trả về của nó, không phải cả hai.

Không chấp nhận các loại có thể thay đổi làm đầu vào, như danh sách; nếu bạn cần một chuỗi thông tin, hãy sử dụng IEnumerable. Chỉ đọc trình tự; không ghi vào bộ sưu tập đã qua trừ khi rất rõ ràng đây là hợp đồng của phương thức. Bằng cách sử dụng IEnumerable, bạn gửi tin nhắn cho người gọi rằng bạn sẽ không làm thay đổi bộ sưu tập của họ.

Không bao giờ chấp nhận null; một chuỗi null là một sự gớm ghiếc. Yêu cầu người gọi vượt qua một chuỗi trống nếu điều đó có ý nghĩa, không bao giờ là null.

Sụp đổ ngay lập tức nếu người gọi vi phạm hợp đồng của bạn, để dạy họ rằng bạn có nghĩa là kinh doanh và để họ bắt lỗi trong thử nghiệm chứ không phải sản xuất.

Thiết kế hợp đồng trước tiên phải hợp lý nhất có thể, và sau đó thực hiện rõ ràng hợp đồng. Đó là cách để chứng minh một thiết kế trong tương lai.

Bây giờ, tôi chỉ nói về trường hợp cụ thể của bạn và bạn đã hỏi một câu hỏi chung. Vì vậy, đây là một số lời khuyên chung bổ sung:

  • Nếu có một thực tế là bạn là nhà phát triển có thể suy luận nhưng trình biên dịch thì không thể, sau đó sử dụng một xác nhận để ghi lại thực tế đó. Nếu một nhà phát triển khác, như bạn trong tương lai hoặc một trong những đồng nghiệp của bạn, vi phạm giả định đó thì khẳng định đó sẽ cho bạn biết.

  • Nhận một công cụ bảo hiểm thử nghiệm. Hãy chắc chắn rằng các bài kiểm tra của bạn bao gồm mọi dòng mã. Nếu có mã chưa được khám phá thì bạn có một bài kiểm tra bị thiếu hoặc bạn có mã chết. Mã chết là nguy hiểm đáng ngạc nhiên bởi vì nó thường không có ý định chết! Lỗ hổng bảo mật "goto fail" cực kỳ khủng khiếp của Apple trong vài năm trở lại đây ngay lập tức xuất hiện trong tâm trí.

  • Lấy một công cụ phân tích tĩnh. Heck, nhận được một số; mỗi công cụ đều có đặc sản riêng và không có công cụ nào khác. Hãy chú ý khi nó nói với bạn rằng có mã không thể truy cập hoặc dự phòng. Một lần nữa, đó là những lỗi có khả năng.

Nếu nó có vẻ như tôi đang nói: đầu tiên, hãy thiết kế mã tốt và thứ hai, kiểm tra cái quái đó để chắc chắn rằng nó đúng ngày hôm nay, đó là điều tôi đang nói. Làm những việc đó sẽ giúp việc đối phó với tương lai dễ dàng hơn nhiều; phần khó nhất về tương lai là đối phó với tất cả các mã máy kẹo lỗi mà mọi người đã viết trong quá khứ. Có được nó ngay hôm nay và chi phí sẽ thấp hơn trong tương lai.


24
Tôi chưa bao giờ thực sự nghĩ về các phương pháp như thế này trước đây, nghĩ về nó bây giờ tôi dường như luôn cố gắng và bao quát mọi tình huống, trong thực tế nếu người / phương thức gọi phương thức của tôi không vượt qua tôi những gì tôi cần, thì tôi không nên ' Tôi không cố gắng sửa chữa lỗi lầm của họ (khi tôi thực sự không biết họ dự định gì), tôi nên thoát ra. Cảm ơn vì điều này, một bài học quý giá đã được học!
KidCode

4
Thực tế là một phương thức trả về một giá trị không có nghĩa là nó cũng không hữu ích cho tác dụng phụ của nó. Trong mã đồng thời, khả năng các hàm trả về cả hai thường rất quan trọng (hãy tưởng tượng CompareExchangemà không có khả năng đó!) Và ngay cả trong các tình huống không đồng thời, một cái gì đó như "thêm một bản ghi nếu nó không tồn tại và trả lại một trong hai bản ghi hoặc phương thức tồn tại "có thể thuận tiện hơn bất kỳ cách tiếp cận nào không sử dụng cả tác dụng phụ và giá trị trả về.
supercat

5
@KidCode Vâng, Eric rất giỏi trong việc giải thích mọi thứ rõ ràng, thậm chí một số chủ đề thực sự phức tạp. :)
Mason Wheeler

3
@supercat Chắc chắn, nhưng nó cũng khó hơn để lý do. Đầu tiên, có lẽ bạn sẽ muốn xem xét một cái gì đó không sửa đổi trạng thái toàn cầu, do đó tránh được cả các vấn đề tương tranh và tham nhũng nhà nước. Khi điều này không hợp lý, bạn vẫn muốn cách ly cả hai - điều đó mang lại cho bạn những địa điểm rõ ràng trong đó vấn đề đồng thời là một vấn đề (và do đó được coi là cực kỳ nguy hiểm) và những nơi xử lý. Đây là một trong những ý tưởng cốt lõi trong bài báo OOP ban đầu và là cốt lõi của sự hữu ích của các diễn viên. Không có quy tắc tôn giáo - chỉ thích tách hai khi nó có ý nghĩa. Nó thường làm.
Luaan

5
Đây là một bài viết rất hữu ích cho tất cả mọi người!
Adrian Buzea

89

Những gì bạn đang làm trong mã bạn đang hiển thị ở trên không phải là bằng chứng trong tương lai nhiều như nó là mã hóa phòng thủ .

Cả hai ifbáo cáo kiểm tra cho những thứ khác nhau. Cả hai đều là các bài kiểm tra phù hợp tùy thuộc vào nhu cầu của bạn.

Phần 1 kiểm tra và sửa chữa một nullđối tượng. Lưu ý bên lề: Tạo danh sách không tạo bất kỳ mục con nào (ví dụ CalendarRow:).

Phần 2 kiểm tra và sửa lỗi người dùng và / hoặc lỗi thực hiện. Chỉ vì bạn List<CalendarRow>không có nghĩa là bạn có bất kỳ mục nào trong danh sách. Người dùng và người triển khai sẽ làm những việc mà bạn không thể tưởng tượng chỉ vì họ được phép làm điều đó, cho dù điều đó có ý nghĩa với bạn hay không.


1
Trên thực tế, tôi đang dùng 'thủ thuật người dùng ngu ngốc' để nói về đầu vào. CÓ bạn không bao giờ nên tin tưởng đầu vào. Ngay cả từ bên ngoài lớp học của bạn. Xác thực! Nếu bạn nghĩ rằng đây chỉ là mối quan tâm trong tương lai, tôi rất vui khi hack bạn hôm nay.
candied_orange

1
@CandiedOrange, đó là ý định, nhưng từ ngữ không truyền tải được sự hài hước đã cố gắng. Tôi đã thay đổi từ ngữ.
Adam Zuckerman

3
Chỉ cần một câu hỏi nhanh ở đây, nếu đó là lỗi triển khai / dữ liệu xấu, tôi không nên gặp sự cố, thay vì cố gắng sửa lỗi của họ?
KidCode

4
@KidCode Bất cứ khi nào bạn cố gắng khôi phục, "sửa lỗi của họ", bạn cần thực hiện hai điều, trở lại trạng thái tốt đã biết và không bị mất đầu vào có giá trị một cách lặng lẽ. Theo quy tắc đó trong trường hợp này đặt ra câu hỏi: danh sách hàng không có giá trị đầu vào không?
candied_orange

7
Không đồng ý mạnh mẽ với câu trả lời này. Nếu một hàm nhận đầu vào không hợp lệ, điều đó có nghĩa là theo định nghĩa, có một lỗi trong chương trình. Cách tiếp cận đúng là ném một ngoại lệ vào đầu vào không hợp lệ, để bạn phát hiện ra vấn đề và sửa lỗi. Cách tiếp cận mà bạn mô tả chỉ là để che giấu các lỗi khiến chúng trở nên quỷ quyệt và khó theo dõi hơn. Mã hóa phòng thủ có nghĩa là bạn không tự động tin tưởng đầu vào nhưng xác thực nó, nhưng điều đó không có nghĩa là bạn nên đoán ngẫu nhiên để "sửa" đầu vào không hợp lệ hoặc bất ngờ.
JacquesB

35

Tôi đoán, câu hỏi này về cơ bản là xuống hương vị. Có, bạn nên viết mã mạnh mẽ, nhưng mã trong ví dụ của bạn là vi phạm nhẹ nguyên tắc KISS (vì sẽ có rất nhiều mã "bằng chứng trong tương lai" như vậy).

Cá nhân tôi có thể sẽ không bận tâm đến việc làm mã chống đạn cho tương lai. Tôi không biết tương lai, và như vậy, bất kỳ mã "chống đạn trong tương lai" nào cũng sẽ bị thất bại thảm hại khi tương lai đến dù sao đi nữa.

Thay vào đó, tôi sẽ thích một cách tiếp cận khác nhau: Làm cho các giả định rằng bạn thực hiện rõ ràng bằng cách sử dụng assert()cơ sở vật chất vĩ mô hoặc tương đương. Theo cách đó, khi tương lai đến trước cửa, nó sẽ cho bạn biết chính xác nơi mà các giả định của bạn không còn giữ được nữa.


4
Tôi thích quan điểm của bạn về việc không biết tương lai sẽ ra sao. Ngay bây giờ tất cả những gì tôi thực sự đang làm là đoán những gì có thể là một vấn đề, sau đó đoán lại cho giải pháp.
KidCode

3
@KidCode: Quan sát tốt. Suy nghĩ của bạn ở đây thực sự thông minh hơn rất nhiều so với nhiều câu trả lời ở đây, bao gồm cả câu trả lời bạn đã chấp nhận.
JacquesB

1
Tôi thích câu trả lời này. Giữ mã tối thiểu để người đọc trong tương lai dễ hiểu tại sao cần phải kiểm tra. Nếu một người đọc trong tương lai thấy kiểm tra cho những thứ có vẻ không cần thiết, họ có thể lãng phí thời gian để cố gắng hiểu tại sao kiểm tra lại ở đó. Một con người trong tương lai có thể đang sửa đổi lớp này, hơn là những người khác sử dụng nó. Ngoài ra, đừng viết mã bạn không thể gỡ lỗi , đây sẽ là trường hợp nếu bạn cố gắng xử lý các trường hợp hiện không thể xảy ra. (Trừ khi bạn viết các bài kiểm tra đơn vị thực hiện các đường dẫn mã mà chương trình chính không có.)
Peter Cordes

23

Một nguyên tắc khác bạn có thể muốn nghĩ đến là ý tưởng thất bại nhanh chóng . Ý tưởng là khi có sự cố xảy ra trong chương trình của bạn, bạn muốn dừng chương trình hoàn toàn ngay lập tức, ít nhất là trong khi bạn đang phát triển nó trước khi phát hành nó. Theo hiệu trưởng này, bạn muốn viết nhiều séc để đảm bảo các giả định của bạn được giữ, nhưng nghiêm túc xem xét việc chương trình của bạn ngừng chết trong các dấu vết của nó bất cứ khi nào các giả định bị vi phạm.

Nói một cách táo bạo, nếu có một lỗi nhỏ trong chương trình của bạn, bạn muốn nó bị sập hoàn toàn trong khi bạn xem!

Điều này nghe có vẻ trái ngược, nhưng nó cho phép bạn phát hiện ra các lỗi nhanh nhất có thể trong quá trình phát triển thường xuyên. Nếu bạn đang viết một đoạn mã và bạn nghĩ rằng nó đã kết thúc, nhưng nó bị sập khi bạn kiểm tra nó, không có câu hỏi nào là bạn chưa hoàn thành. Hơn nữa, hầu hết các ngôn ngữ lập trình cung cấp cho bạn các công cụ gỡ lỗi tuyệt vời, dễ sử dụng nhất khi chương trình của bạn gặp sự cố hoàn toàn thay vì cố gắng làm hết sức sau khi xảy ra lỗi. Ví dụ lớn nhất, phổ biến nhất là nếu bạn làm hỏng chương trình của mình bằng cách ném ngoại lệ chưa xử lý, thông báo ngoại lệ sẽ cho bạn biết một lượng thông tin đáng kinh ngạc về lỗi, bao gồm dòng mã nào bị lỗi và đường dẫn mã của chương trình đã xảy ra theo cách của nó đến dòng mã đó (dấu vết ngăn xếp).

Để biết thêm suy nghĩ, hãy đọc bài tiểu luận ngắn này: Đừng đóng đinh chương trình của bạn ở vị trí thẳng thắn


Điều này có liên quan đến bạn bởi vì đôi khi, có thể là những tấm séc bạn đang viết vì bạn muốn chương trình của bạn cố gắng tiếp tục chạy ngay cả khi có sự cố. Ví dụ, hãy xem xét việc triển khai ngắn gọn này của chuỗi Fibonacci:

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

Điều này hoạt động, nhưng nếu ai đó chuyển một số âm cho chức năng của bạn thì sao? Nó sẽ không hoạt động sau đó! Vì vậy, bạn sẽ muốn thêm một kiểm tra để đảm bảo rằng hàm được gọi với đầu vào không âm.

Nó có thể hấp dẫn để viết chức năng như thế này:

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    // Make sure the input is nonnegative
    if(n < 0) {
        n = 1; // Replace the negative input with an input that will work
    }

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

Tuy nhiên, nếu bạn làm điều này, sau đó vô tình gọi hàm Fibonacci của bạn bằng đầu vào âm, bạn sẽ không bao giờ nhận ra điều đó! Tồi tệ hơn, chương trình của bạn có thể sẽ tiếp tục chạy nhưng bắt đầu tạo ra kết quả vô nghĩa mà không cung cấp cho bạn bất kỳ manh mối nào về việc có sự cố xảy ra. Đây là những loại lỗi khó sửa nhất.

Thay vào đó, tốt hơn là viết một tấm séc như thế này:

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    // Make sure the input is nonnegative
    if(n < 0) {
        throw new ArgumentException("Can't have negative inputs to Fibonacci");
    }

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

Bây giờ, nếu bạn vô tình gọi hàm Fibonacci với đầu vào âm, chương trình của bạn sẽ ngay lập tức dừng lại và cho bạn biết rằng có gì đó không đúng. Hơn nữa, bằng cách cung cấp cho bạn dấu vết ngăn xếp, chương trình sẽ cho bạn biết phần nào trong chương trình của bạn đã cố thực hiện chức năng Fibonacci không chính xác, cung cấp cho bạn một điểm khởi đầu tuyệt vời để gỡ lỗi những gì sai.


1
Không c # có một loại ngoại lệ cụ thể để chỉ ra các đối số không hợp lệ hoặc các đối số ngoài phạm vi?
JDługosz

@ JDługosz Đúng! C # có ArgumentException và Java có IllegalArgumentException .
Kevin

Câu hỏi đã được sử dụng c #. Đây là C ++ (liên kết đến tóm tắt) cho đầy đủ.
JDługosz

3
"Thất bại nhanh đến trạng thái an toàn gần nhất" là hợp lý hơn. Nếu bạn làm cho ứng dụng của mình bị hỏng khi có sự cố bất ngờ xảy ra, bạn có nguy cơ mất dữ liệu của người dùng. Những nơi mà dữ liệu của người dùng đang gặp nguy hiểm là những điểm tuyệt vời để xử lý ngoại lệ "áp chót" (luôn có trường hợp bạn không thể làm gì ngoài việc giơ tay lên - sự cố cuối cùng). Chỉ làm cho nó bị lỗi khi gỡ lỗi chỉ là mở một hộp giun khác - bạn cần kiểm tra xem bạn đang triển khai cho người dùng bằng cách nào và bây giờ bạn đang dành một nửa thời gian thử nghiệm của mình cho phiên bản mà người dùng sẽ không bao giờ thấy.
Luaan

2
@Luaan: đó không phải là trách nhiệm của chức năng ví dụ.
whatsisname

11

Bạn có nên thêm mã dự phòng? Không.

Nhưng, những gì bạn mô tả không phải là mã dự phòng .

Những gì bạn mô tả là lập trình phòng thủ chống lại việc gọi mã vi phạm các điều kiện tiên quyết của chức năng của bạn. Cho dù bạn làm điều này hay chỉ đơn giản là để người dùng đọc tài liệu và tránh những vi phạm đó, thì hoàn toàn chủ quan.

Cá nhân, tôi là một người tin tưởng lớn vào phương pháp này, nhưng như với mọi thứ bạn phải thận trọng. Lấy ví dụ, của C ++ std::vector::operator[]. Tạm gác việc thực hiện chế độ gỡ lỗi của VS sang một lúc, chức năng này không thực hiện kiểm tra giới hạn. Nếu bạn yêu cầu một yếu tố không tồn tại, kết quả là không xác định. Người dùng có thể cung cấp một chỉ số vectơ hợp lệ. Điều này khá có chủ ý: bạn có thể "chọn tham gia" để kiểm tra giới hạn bằng cách thêm nó vào cuộc gọi, nhưng nếu việc operator[]triển khai thực hiện thì bạn sẽ không thể "từ chối". Là một chức năng khá thấp, điều này có ý nghĩa.

Nhưng nếu bạn đang viết một AddEmployee(string name)hàm cho một số giao diện cấp cao hơn, tôi hoàn toàn mong đợi hàm này ít nhất sẽ đưa ra một ngoại lệ nếu bạn cung cấp một khoảng trống name, cũng như điều kiện tiên quyết này được ghi lại ngay trên phần khai báo hàm. Bạn có thể không cung cấp đầu vào của người dùng không được xác nhận cho chức năng này ngay hôm nay, nhưng làm cho nó "an toàn" theo cách này có nghĩa là mọi vi phạm điều kiện tiên quyết xuất hiện trong tương lai có thể được chẩn đoán dễ dàng, thay vì có khả năng gây ra một chuỗi các trò chơi thống trị khó thực hiện phát hiện lỗi. Đây không phải là dư thừa: đó là sự siêng năng.

Nếu tôi phải đưa ra một quy tắc chung (mặc dù, như một quy tắc chung, tôi cố gắng tránh các quy tắc đó), tôi sẽ nói rằng một chức năng thỏa mãn bất kỳ điều nào sau đây:

  • sống trong một ngôn ngữ siêu cao cấp (giả sử, JavaScript chứ không phải C)
  • ngồi ở một ranh giới giao diện
  • không quan trọng về hiệu suất
  • trực tiếp chấp nhận đầu vào của người dùng

Có thể hưởng lợi từ lập trình phòng thủ. Trong các trường hợp khác, bạn vẫn có thể viết assertcác ion kích hoạt trong quá trình thử nghiệm nhưng bị vô hiệu hóa trong các bản dựng phát hành, để tăng thêm khả năng tìm lỗi của bạn.

Chủ đề này được khám phá thêm trên Wikipedia ( https://en.wikipedia.org/wiki/Defensive_programming ).


9

Hai trong số mười điều răn lập trình có liên quan ở đây:

  • Bạn sẽ không cho rằng đầu vào là chính xác

  • Bạn sẽ không tạo mã để sử dụng trong tương lai

Ở đây, kiểm tra null không phải là "tạo mã để sử dụng trong tương lai". Tạo mã để sử dụng trong tương lai là những thứ như thêm giao diện vì bạn nghĩ rằng chúng có thể hữu ích "một ngày nào đó". Nói cách khác, điều răn là không thêm các lớp trừu tượng trừ khi chúng cần thiết ngay bây giờ.

Kiểm tra null không liên quan gì đến việc sử dụng trong tương lai. Nó phải làm với Điều răn # 1: đừng cho rằng đầu vào sẽ đúng. Không bao giờ giả định chức năng của bạn sẽ nhận được một số tập hợp con của đầu vào. Một hàm sẽ đáp ứng một cách hợp lý cho dù các đầu vào không có thật và làm rối tung các đầu vào như thế nào.


5
Những điều răn lập trình này ở đâu Bạn có một liên kết? Tôi tò mò vì tôi đã làm việc trên một số chương trình đăng ký thứ hai của những điều răn đó, và một số chương trình thì không. Lúc nào cũng vậy, những người đăng ký điều răn Thou shall not make code for future useđã gặp phải các vấn đề về khả năng bảo trì sớm hơn , bất chấp logic trực quan của điều răn. Tôi đã tìm thấy, trong mã hóa thực tế, điều răn đó chỉ có hiệu lực trong mã khi bạn kiểm soát danh sách tính năng và thời hạn, và đảm bảo rằng bạn không cần mã tìm kiếm trong tương lai để tiếp cận chúng ... điều đó có nghĩa là, không bao giờ.
Cort Ammon

1
Có thể chứng minh một cách tầm thường: nếu người ta có thể ước tính xác suất sử dụng trong tương lai và giá trị dự kiến ​​của mã "sử dụng trong tương lai" và sản phẩm của hai loại này lớn hơn chi phí thêm mã "sử dụng trong tương lai", thì nó tối ưu về mặt thống kê thêm mã. Tôi nghĩ rằng điều răn xuất hiện trong các tình huống mà các nhà phát triển (hoặc người quản lý) buộc phải thừa nhận rằng các kỹ năng ước tính của họ không đáng tin cậy như họ muốn, vì vậy, như một biện pháp phòng thủ, họ chỉ chọn không ước tính nhiệm vụ trong tương lai.
Cort Ammon

2
@CortAmmon Không có chỗ cho các điều răn tôn giáo trong lập trình - "thực hành tốt nhất" chỉ có ý nghĩa trong ngữ cảnh và học "thực hành tốt nhất" mà không có lý do khiến bạn không thể thích nghi. Tôi đã thấy YAGNI rất hữu ích, nhưng điều đó không có nghĩa là tôi không nghĩ về những nơi mà việc thêm điểm mở rộng sau này sẽ tốn kém - điều đó có nghĩa là tôi không phải nghĩ về những trường hợp đơn giản trước thời hạn. Tất nhiên, điều này cũng thay đổi theo thời gian, vì ngày càng có nhiều giả định được đưa vào mã của bạn, làm tăng hiệu quả giao diện mã của bạn - đó là một hành động cân bằng.
Luaan

2
@CortAmmon Trường hợp "có thể chứng minh được" của bạn bỏ qua hai chi phí rất quan trọng - chi phí ước tính và chi phí bảo trì của điểm mở rộng (có thể không cần thiết). Đó là nơi mọi người có được những ước tính cực kỳ không đáng tin cậy - bằng cách đánh giá thấp các ước tính. Đối với một tính năng rất đơn giản, có thể chỉ cần suy nghĩ trong vài giây, nhưng rất có thể bạn sẽ tìm thấy toàn bộ một con giun xuất phát từ tính năng "đơn giản" ban đầu. Giao tiếp là chìa khóa - khi mọi thứ trở nên lớn, bạn cần nói chuyện với lãnh đạo / khách hàng của mình.
Luaan

1
@Luaan Tôi đã cố gắng tranh luận cho quan điểm của bạn, không có chỗ cho các điều răn tôn giáo trong lập trình. Chừng nào còn tồn tại một trường hợp kinh doanh trong đó các chi phí ước tính và bảo trì điểm mở rộng đã đủ ràng buộc, có một trường hợp nói "điều răn" là đáng nghi ngờ. Từ kinh nghiệm của tôi với mã, câu hỏi có nên để lại các điểm mở rộng như vậy hay không chưa bao giờ phù hợp với một điều răn dòng duy nhất theo cách này hay cách khác.
Cort Ammon

7

Định nghĩa về 'mã dự phòng' và 'YAGNI' thường phụ thuộc vào tôi tìm thấy về phía trước của bạn.

Nếu bạn gặp một vấn đề, thì bạn có xu hướng viết mã tương lai theo cách để tránh vấn đề đó. Một lập trình viên khác đã không gặp phải vấn đề cụ thể đó có thể xem xét mã thừa của bạn quá mức cần thiết.

Đề nghị của tôi là theo dõi xem bạn dành bao nhiêu thời gian cho 'những thứ chưa bị lỗi' nếu tải của nó và các đồng nghiệp của bạn đang vượt qua các tính năng nhanh hơn bạn, sau đó giảm nó.

Tuy nhiên, nếu bạn giống như tôi, tôi hy vọng bạn sẽ gõ tất cả 'theo mặc định' và nó thực sự không còn đưa bạn đến nữa.


6

Đó là một ý tưởng tốt để ghi lại bất kỳ giả định về các tham số. Và đó là một ý tưởng tốt để kiểm tra xem mã khách hàng của bạn không vi phạm các giả định đó. Tôi sẽ làm điều này:

/** ...
*   Precondition: rows is null or nonempty
*/
public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    Assert( rows==null || rows.Count > 0 )
    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

[Giả sử đây là C #, Assert có thể không phải là công cụ tốt nhất cho công việc, vì nó không được biên dịch trong mã phát hành. Nhưng đó là một cuộc tranh luận cho một ngày khác.]

Tại sao điều này tốt hơn những gì bạn đã viết? Mã của bạn có ý nghĩa nếu trong tương lai nơi khách hàng của bạn thay đổi, khi khách hàng chuyển qua một danh sách trống, điều cần làm sẽ là thêm một hàng đầu tiên và thêm ứng dụng vào các cuộc hẹn của nó. Nhưng làm thế nào để bạn biết rằng đó sẽ là trường hợp? Bây giờ tốt hơn là đưa ra ít giả định về tương lai.


5

Ước tính chi phí thêm mã đó ngay bây giờ . Nó sẽ tương đối rẻ bởi vì tất cả đều mới mẻ trong tâm trí của bạn, vì vậy bạn sẽ có thể làm điều này một cách nhanh chóng. Thêm các bài kiểm tra đơn vị là cần thiết - không có gì tệ hơn sau đó sử dụng một số phương pháp một năm sau đó, nó không hoạt động và bạn nhận ra rằng nó đã bị hỏng ngay từ đầu và chưa bao giờ thực sự hoạt động.

Ước tính chi phí thêm mã khi cần thiết. Nó sẽ đắt hơn, bởi vì bạn phải quay lại mã, nhớ tất cả, và nó khó hơn rất nhiều.

Ước tính xác suất mà mã bổ sung sẽ thực sự cần thiết. Sau đó làm toán.

Mặt khác, mã đầy đủ với các giả định "X sẽ không bao giờ xảy ra" là khủng khiếp để gỡ lỗi. Nếu một cái gì đó không hoạt động như dự định, nó có nghĩa là một sai lầm ngu ngốc, hoặc một giả định sai. "X sẽ không bao giờ xảy ra" của bạn là một giả định và khi có lỗi, điều đó thật đáng ngờ. Mà buộc các nhà phát triển tiếp theo phải lãng phí thời gian vào nó. Thông thường tốt hơn là không dựa vào bất kỳ giả định nào như vậy.


4
Trong đoạn đầu tiên của bạn, bạn đã quên đề cập đến chi phí duy trì mã đó theo thời gian, khi hóa ra tính năng thực sự cần thiết là loại trừ lẫn nhau với tính năng được thêm vào một cách không cần thiết. . .
ruakh

Bạn cũng cần ước tính chi phí của các lỗi có thể xuất hiện trong chương trình vì bạn không thất bại với đầu vào không hợp lệ. Nhưng bạn không thể ước tính chi phí của lỗi do theo định nghĩa chúng là bất ngờ. Thế là toàn bộ "làm toán" sụp đổ.
JacquesB

3

Câu hỏi chính ở đây là "điều gì sẽ xảy ra nếu bạn làm / không"?

Như những người khác đã chỉ ra, loại lập trình phòng thủ này là tốt, nhưng đôi khi nó cũng nguy hiểm.

Ví dụ: nếu bạn cung cấp một giá trị mặc định, thì bạn đang duy trì chương trình. Nhưng chương trình bây giờ có thể không được làm những gì bạn muốn nó làm. Ví dụ: nếu nó ghi mảng trống đó vào một tệp, thì bây giờ bạn có thể đã chuyển lỗi của mình từ "sự cố do tôi cung cấp null do vô tình" thành "xóa các hàng lịch vì tôi đã vô tình cung cấp null". (ví dụ: nếu bạn bắt đầu loại bỏ những thứ không xuất hiện trong danh sách trong phần đó có nội dung "// blah")

Chìa khóa cho tôi là Không bao giờ bị hỏng dữ liệu . Hãy để tôi nhắc lại rằng; KHÔNG BAO GIỜ. ĐÚNG. DỮ LIỆU. Nếu chương trình của bạn ngoại lệ, bạn nhận được một báo cáo lỗi bạn có thể vá; nếu nó ghi dữ liệu xấu vào một tệp mà sau này, bạn sẽ phải gieo đất bằng muối.

Tất cả các quyết định "không cần thiết" của bạn nên được đưa ra với tiền đề đó trong tâm trí.


2

Những gì bạn đang giải quyết ở đây về cơ bản là một giao diện. Bằng cách thêm hành vi "khi đầu vào là null, khởi tạo đầu vào", bạn đã mở rộng giao diện phương thức một cách hiệu quả - bây giờ thay vì luôn luôn hoạt động trên một danh sách hợp lệ, bạn đã làm cho nó "sửa chữa" đầu vào. Cho dù đây là phần chính thức hay không chính thức của giao diện, bạn có thể đặt cược ai đó (rất có thể bao gồm cả bạn) sẽ sử dụng hành vi này.

Các giao diện nên được giữ đơn giản, và chúng phải tương đối ổn định - đặc biệt là trong một cái gì đó giống như một public staticphương pháp. Bạn có một chút chậm trễ trong các phương thức riêng tư, đặc biệt là các phương thức cá nhân. Bằng cách mở rộng giao diện, bạn đã làm cho mã của mình phức tạp hơn trong thực tế. Bây giờ, hãy tưởng tượng rằng bạn không thực sự muốn sử dụng đường dẫn mã đó - vì vậy bạn tránh nó. Bây giờ bạn đã có một chút mã chưa được kiểm tra, giả vờ như đó là một phần của hành vi của phương thức. Và tôi có thể nói với bạn ngay bây giờ rằng nó có thể có một lỗi: khi bạn vượt qua một danh sách, danh sách đó bị thay đổi bởi phương thức. Tuy nhiên, nếu bạn không, bạn tạo một địa phươngliệt kê, và ném nó đi sau. Đây là loại hành vi không nhất quán sẽ khiến bạn khóc trong nửa năm, khi bạn cố gắng theo dõi một lỗi tối nghĩa.

Nói chung, lập trình phòng thủ là một điều khá hữu ích. Nhưng các đường dẫn mã cho kiểm tra phòng thủ phải được kiểm tra, giống như bất kỳ mã nào khác. Trong trường hợp như thế này, họ đang làm phức tạp mã của bạn mà không có lý do và tôi chọn cách thay thế như thế này:

if (rows == null) throw new ArgumentNullException(nameof(rows));

Bạn không muốn một đầu vào rowslà null và bạn muốn làm rõ lỗi cho tất cả những người gọi của bạn càng sớm càng tốt .

Có nhiều giá trị bạn cần phải xoay quanh khi phát triển phần mềm. Ngay cả bản thân sự mạnh mẽ cũng là một chất lượng rất phức tạp - ví dụ, tôi sẽ không xem xét việc kiểm tra phòng thủ của bạn mạnh mẽ hơn là ném ngoại lệ. Các ngoại lệ khá tiện lợi để cung cấp cho bạn một nơi an toàn để thử lại từ một nơi an toàn - các vấn đề tham nhũng dữ liệu thường khó theo dõi hơn là nhận ra vấn đề sớm và xử lý an toàn. Cuối cùng, họ chỉ có xu hướng cho bạn ảo tưởng về sự mạnh mẽ, và sau đó một tháng bạn nhận thấy rằng một phần mười các cuộc hẹn của bạn đã biến mất, bởi vì bạn không bao giờ nhận thấy rằng một danh sách khác đã được cập nhật. Ôi.

Hãy chắc chắn để phân biệt giữa hai. Lập trình phòng thủ là một kỹ thuật hữu ích để bắt lỗi tại một nơi có liên quan nhất, giúp cải thiện đáng kể các nỗ lực sửa lỗi của bạn và xử lý ngoại lệ tốt, ngăn chặn "tham nhũng lén lút". Thất bại sớm, thất bại nhanh. Mặt khác, những gì bạn đang làm giống như "ẩn lỗi" - bạn đang tung hứng các đầu vào và đưa ra các giả định về ý nghĩa của người gọi. Điều này rất quan trọng đối với mã hướng tới người dùng (ví dụ: kiểm tra chính tả), nhưng bạn nên cảnh giác khi thấy điều này với mã đối mặt với nhà phát triển.

Vấn đề chính là bất cứ điều gì trừu tượng mà bạn tạo ra, nó sẽ bị rò rỉ ("Tôi muốn gõ, không phải là trước! Trình kiểm tra chính tả ngu ngốc!"), Và mã để xử lý tất cả các trường hợp đặc biệt và sửa lỗi vẫn là mã cho bạn cần duy trì và hiểu, và mã bạn cần kiểm tra. So sánh nỗ lực đảm bảo một danh sách không có giá trị được thông qua với việc sửa lỗi bạn có một năm sau đó trong sản xuất - đó không phải là một sự đánh đổi tốt. Trong một thế giới lý tưởng, bạn muốn mọi phương thức chỉ hoạt động với đầu vào của chính nó, trả về kết quả và không sửa đổi trạng thái toàn cầu. Tất nhiên, trong thế giới thực, bạn sẽ tìm thấy rất nhiều trường hợp không phải vậygiải pháp đơn giản và rõ ràng nhất (ví dụ như khi lưu tệp), nhưng tôi thấy rằng việc giữ các phương thức "thuần túy" khi không có lý do gì để chúng đọc hoặc thao tác trạng thái toàn cầu khiến cho mã trở nên dễ dàng hơn nhiều. Nó cũng có xu hướng cung cấp cho bạn nhiều điểm tự nhiên hơn để chia phương thức của bạn :)

Điều này không có nghĩa là mọi thứ bất ngờ sẽ làm cho ứng dụng của bạn bị sập, ngược lại. Nếu bạn sử dụng tốt các ngoại lệ, chúng sẽ tự nhiên hình thành các điểm xử lý lỗi an toàn, nơi bạn có thể khôi phục trạng thái ứng dụng ổn định và cho phép người dùng tiếp tục những gì họ đang làm (lý tưởng trong khi tránh mọi mất dữ liệu cho người dùng). Tại các điểm xử lý đó, bạn sẽ thấy các cơ hội để khắc phục sự cố ("Không tìm thấy số thứ tự 2212. Ý bạn là 2212b?") Hoặc trao quyền kiểm soát người dùng ("Lỗi kết nối với cơ sở dữ liệu. Hãy thử lại?"). Ngay cả khi không có tùy chọn nào khả dụng, ít nhất nó sẽ cho bạn cơ hội không có gì bị hỏng - Tôi đã bắt đầu đánh giá cao mã sử dụng usingtry... finallynhiều hơn try...catch, nó cung cấp cho bạn rất nhiều cơ hội để duy trì bất biến ngay cả trong những điều kiện đặc biệt.

Người dùng không nên mất dữ liệu và công việc của họ. Điều này vẫn phải được cân bằng với chi phí phát triển, v.v., nhưng đó là một hướng dẫn chung khá tốt (nếu người dùng quyết định có nên mua phần mềm của bạn hay không - phần mềm nội bộ thường không có sự xa xỉ đó). Ngay cả toàn bộ ứng dụng bị sập cũng trở thành một vấn đề nếu người dùng có thể khởi động lại và quay lại với những gì họ đang làm. Đây là sự mạnh mẽ thực sự - Word tiết kiệm công việc của bạn mọi lúc mà không làm hỏng tài liệu của bạn trên đĩa và cung cấp cho bạn một tùy chọnđể khôi phục những thay đổi đó sau khi khởi động lại Word sau khi gặp sự cố. Là nó tốt hơn so với không có lỗi ở nơi đầu tiên? Có lẽ là không - mặc dù đừng quên rằng công việc dành một lỗi hiếm có thể được chi tiêu tốt hơn ở mọi nơi. Nhưng nó tốt hơn nhiều so với các lựa chọn thay thế - ví dụ: tài liệu bị hỏng trên đĩa, tất cả công việc kể từ lần lưu cuối bị mất, tài liệu tự động được thay thế bằng các thay đổi trước khi sự cố xảy ra là Ctrl + A và Xóa.


1

Tôi sẽ trả lời điều này dựa trên giả định của bạn rằng mã mạnh mẽ sẽ có lợi cho bạn "năm" kể từ bây giờ. Nếu lợi ích lâu dài là mục tiêu của bạn, tôi sẽ ưu tiên thiết kế và khả năng bảo trì hơn là sự mạnh mẽ.

Sự đánh đổi giữa thiết kế và sự mạnh mẽ, là thời gian và trọng tâm. Hầu hết các nhà phát triển muốn có một bộ mã được thiết kế tốt ngay cả khi nó có nghĩa là phải trải qua một số điểm rắc rối và thực hiện một số điều kiện bổ sung hoặc xử lý lỗi. Sau một vài năm sử dụng, những nơi bạn thực sự cần nó có thể đã được xác định bởi người dùng.

Giả sử thiết kế có cùng chất lượng, ít mã sẽ dễ bảo trì hơn. Điều này không có nghĩa là chúng ta sẽ tốt hơn nếu bạn để những vấn đề đã biết xảy ra trong một vài năm, nhưng việc thêm những thứ bạn biết bạn không cần sẽ gây khó khăn. Tất cả chúng ta đã xem mã di sản và tìm thấy những phần không cần thiết. Bạn phải có một mã thay đổi độ tin cậy cao đã hoạt động trong nhiều năm.

Vì vậy, nếu bạn cảm thấy ứng dụng của mình được thiết kế tốt nhất có thể, dễ bảo trì và không có bất kỳ lỗi nào, hãy tìm thứ gì đó tốt hơn để làm hơn là thêm mã bạn không cần. Đó là điều tối thiểu bạn có thể làm vì tôn trọng tất cả các nhà phát triển khác làm việc nhiều giờ với các tính năng vô nghĩa.


1

Không, bạn không nên. Và bạn đang thực sự trả lời câu hỏi của chính mình khi bạn nói rằng cách mã hóa này có thể che giấu các lỗi . Điều này sẽ không làm cho mã mạnh mẽ hơn - thay vào đó nó sẽ làm cho nó dễ bị lỗi hơn và làm cho việc gỡ lỗi khó khăn hơn.

Bạn nêu những kỳ vọng hiện tại của bạn về rowsđối số: Hoặc là không hoặc nếu không nó có ít nhất một mục. Vì vậy, câu hỏi là: Có nên viết mã để xử lý bổ sung trường hợp thứ ba không mong muốn khi rowskhông có mục nào không?

Câu trả lời là không. Bạn nên luôn luôn ném một ngoại lệ trong trường hợp đầu vào bất ngờ. Xem xét điều này: Nếu một số phần khác của mã phá vỡ kỳ vọng (nghĩa là hợp đồng) của phương thức của bạn, điều đó có nghĩa là có lỗi . Nếu có một lỗi bạn muốn biết càng sớm càng tốt để bạn có thể sửa nó, và một ngoại lệ sẽ giúp bạn làm điều đó.

Những gì mã hiện đang làm là đoán cách phục hồi từ một lỗi có thể tồn tại hoặc không tồn tại trong mã. Nhưng ngay cả khi có một lỗi, bạn không thể biết cách phục hồi hoàn toàn từ nó. Lỗi theo định nghĩa có hậu quả chưa biết. Có thể một số mã khởi tạo không chạy như mong đợi, nó có thể có nhiều hậu quả khác ngoài việc chỉ thiếu hàng.

Vì vậy, mã của bạn sẽ trông như thế này:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    if (rows != null && rows.Count == 0) throw new ArgumentException("Rows is empty."); 

    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

Lưu ý: Có một số trường hợp cụ thể trong đó có ý nghĩa để "đoán" cách xử lý dữ liệu nhập không hợp lệ thay vì chỉ ném một ngoại lệ. Ví dụ: nếu bạn xử lý đầu vào bên ngoài, bạn không có quyền kiểm soát. Các trình duyệt web là một ví dụ khét tiếng bởi vì họ cố gắng xử lý một cách duyên dáng bất kỳ loại đầu vào không đúng và không hợp lệ. Nhưng điều này chỉ có ý nghĩa với đầu vào bên ngoài, không phải với các cuộc gọi từ các phần khác của chương trình.


Chỉnh sửa: Một số câu trả lời khác nói rằng bạn đang lập trình phòng thủ . Tôi không đồng ý. Lập trình phòng thủ có nghĩa là bạn không tự động tin tưởng đầu vào là hợp lệ. Vì vậy, xác thực các tham số (như trên) là một kỹ thuật lập trình phòng thủ, nhưng điều đó không có nghĩa là bạn nên thay đổi đầu vào bất ngờ hoặc không hợp lệ bằng cách đoán. Cách tiếp cận phòng thủ mạnh mẽ là xác nhận đầu vào sau đó đưa ra một ngoại lệ trong trường hợp đầu vào bất ngờ hoặc không hợp lệ.


1

Tôi có nên thêm mã dự phòng ngay bây giờ chỉ trong trường hợp có thể cần thiết trong tương lai không?

Bạn không nên thêm mã dự phòng bất cứ lúc nào.

Bạn không nên thêm mã chỉ cần thiết trong tương lai.

Bạn nên chắc chắn rằng mã của bạn hoạt động tốt cho dù điều gì xảy ra.

Định nghĩa của "cư xử tốt" là tùy theo nhu cầu của bạn. Một kỹ thuật tôi muốn sử dụng là ngoại lệ "hoang tưởng". Nếu tôi chắc chắn 100% rằng một trường hợp nào đó không bao giờ có thể xảy ra, tôi vẫn lập trình một ngoại lệ, nhưng tôi làm theo cách mà a) nói rõ với mọi người rằng tôi không bao giờ mong muốn điều này xảy ra, và b) được hiển thị và ghi lại rõ ràng và do đó không dẫn đến tham nhũng leo sau này.

Ví dụ mã giả:

file = File.open(">", "bla")  or raise "Paranoia: cannot open file 'bla'"

file.write("xytz") or raise "Paranoia: disk full?"

file.close()  or raise "Paranoia: huh?!?!?"

Điều này thông báo rõ ràng rằng tôi chắc chắn 100% rằng tôi luôn có thể mở, viết hoặc đóng tệp, tức là tôi không đi đến phạm vi tạo ra một xử lý lỗi phức tạp. Nhưng nếu (không: khi nào) tôi không thể mở tệp, chương trình của tôi sẽ vẫn thất bại theo cách được kiểm soát.

Giao diện người dùng tất nhiên sẽ không hiển thị các thông báo như vậy cho người dùng, chúng sẽ được ghi lại nội bộ cùng với dấu vết ngăn xếp. Một lần nữa, đây là những trường hợp ngoại lệ "Paranoia" nội bộ, chỉ cần đảm bảo rằng mã "dừng" khi có điều gì đó bất ngờ xảy ra. Ví dụ này hơi khó chịu, trong thực tế, tôi tất nhiên sẽ thực hiện xử lý lỗi thực sự cho các lỗi trong khi mở tệp, vì điều này xảy ra thường xuyên (tên tệp sai, gắn USB chỉ đọc, bất cứ điều gì).

Một thuật ngữ tìm kiếm liên quan rất quan trọng sẽ là "thất bại nhanh", như đã lưu ý trong các câu trả lời khác và rất hữu ích để tạo ra phần mềm mạnh mẽ.


-1

Có rất nhiều câu trả lời quá phức tạp ở đây. Có thể bạn đã hỏi câu hỏi này vì bạn không cảm thấy đúng về đoạn mã đó nhưng không chắc tại sao hoặc cách khắc phục nó. Vì vậy, câu trả lời của tôi là vấn đề rất có thể xảy ra trong cấu trúc mã (như mọi khi).

Đầu tiên, tiêu đề phương thức:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)

Gán hẹn hàng? Điều đó cần được rõ ràng ngay lập tức từ danh sách tham số. Nếu không có thêm kiến ​​thức, tôi sẽ mong đợi các thông số phương thức trông như thế này : (Appointment app, CalendarRow row).

Tiếp theo, "kiểm tra đầu vào":

//1. Is rows equal to null? - This will be the case if this is the first appointment.
if (rows == null) {
    rows = new List<CalendarRow> ();
}

//2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
if(rows.Count == 0)
{
    rows.Add (new CalendarRow (0));
    rows [0].Appointments.Add (app);
}

Đây là nhảm nhí.

  1. kiểm tra) Người gọi phương thức chỉ cần đảm bảo rằng nó không vượt qua các giá trị chưa được khởi tạo bên trong phương thức. Đó là trách nhiệm của một lập trình viên (để thử) không phải là ngu ngốc.
  2. kiểm tra) Nếu tôi không tính đến việc chuyển rowsvào phương thức có lẽ là sai (xem nhận xét ở trên), thì không nên chịu trách nhiệm về phương thức được gọi AssignAppointmentToRowđể thao túng các hàng theo bất kỳ cách nào khác ngoài việc chỉ định cuộc hẹn ở đâu đó.

Nhưng toàn bộ khái niệm chỉ định các cuộc hẹn ở đâu đó là lạ (trừ khi đây là một phần GUI của mã). Mã của bạn dường như chứa (hoặc ít nhất là nó cố gắng) một cấu trúc dữ liệu rõ ràng đại diện cho một lịch (đó là List<CalendarRows><- nên được định nghĩa là Calendarmột nơi nào đó nếu bạn muốn đi theo cách này, sau đó bạn sẽ chuyển Calendar calendarsang phương thức của mình). Nếu bạn đi theo cách này, tôi sẽ mong đợi calendarđược điền sẵn các vị trí nơi bạn đặt (chỉ định) các cuộc hẹn sau đó (ví dụ: calendar[month][day] = appointmentsẽ là mã phù hợp). Nhưng sau đó, bạn cũng có thể chọn bỏ cấu trúc lịch hoàn toàn khỏi logic chính và chỉ cần có List<Appointment>các Appointmentđối tượng chứa thuộc tínhdate. Và sau đó nếu bạn cần kết xuất lịch ở đâu đó trong GUI, bạn có thể tạo cấu trúc 'lịch rõ ràng' này ngay trước khi kết xuất.

Tôi không biết chi tiết về ứng dụng của bạn nên một số điều này có thể không áp dụng cho bạn nhưng cả hai kiểm tra này (chủ yếu là kiểm tra thứ hai) cho tôi biết rằng có điều gì đó không đúng với việc tách các mối quan tâm trong mã của bạn.


-2

Để đơn giản, hãy giả sử rằng cuối cùng bạn sẽ cần đoạn mã này trong N ngày (không muộn hơn hoặc sớm hơn) hoặc không cần gì cả.

Mã giả:

let C_now   = cost of implementing the piece of code now
let C_later = ... later
let C_maint = cost of maintaining the piece of code one day
              (note that it can be negative)
let P_need  = probability that you will need the code after N days

if C_now + C_maint * N < P_need*C_later then implement the code else omit it.

Các yếu tố cho C_maint:

  • Liệu nó có cải thiện mã nói chung, làm cho nó tự viết tài liệu hơn, dễ kiểm tra hơn không? Nếu có, C_maintdự kiến tiêu cực
  • Liệu nó làm cho mã lớn hơn (do đó khó đọc hơn, biên dịch lâu hơn, thực hiện các bài kiểm tra, v.v.)?
  • Có bất kỳ tái cấu trúc / thiết kế lại đang chờ xử lý? Nếu có, vết sưng C_maint. Trường hợp này đòi hỏi công thức phức tạp hơn, với nhiều lần biến hơn N.

Điều lớn đến mức chỉ cần giảm mã và có thể chỉ cần trong 2 năm với xác suất thấp nên bị bỏ qua, nhưng một điều nhỏ cũng đưa ra các xác nhận hữu ích và 50% sẽ cần trong 3 tháng, cần thực hiện trong 3 tháng. .


Bạn cũng cần phải tính đến chi phí của các lỗi có thể xuất hiện trong chương trình vì bạn không từ chối đầu vào không hợp lệ. Vậy làm thế nào để bạn ước tính chi phí của các lỗi khó tìm tiềm năng?
JacquesB
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.