Thực hành tốt nhất về lập trình phòng thủ được yêu thích (thông minh)


148

Nếu bạn phải chọn các kỹ thuật yêu thích (thông minh) cho mã hóa phòng thủ, chúng sẽ là gì? Mặc dù các ngôn ngữ hiện tại của tôi là Java và Objective-C (có nền tảng về C ++), vui lòng trả lời bằng bất kỳ ngôn ngữ nào. Nhấn mạnh ở đây sẽ là về các kỹ thuật phòng thủ thông minh khác với những kỹ thuật mà 70% chúng ta ở đây đã biết. Vì vậy, bây giờ là thời gian để đào sâu vào túi thủ đoạn của bạn.

Nói cách khác, hãy cố gắng nghĩ về những thứ khác ngoài ví dụ không thú vị này :

  • if(5 == x) thay vì if(x == 5) : để tránh sự phân công ngoài ý muốn

Dưới đây là một số ví dụ về một số thực tiễn lập trình phòng thủ tốt nhất hấp dẫn (ví dụ cụ thể về ngôn ngữ trong Java):

- Khóa các biến của bạn cho đến khi bạn biết rằng bạn cần thay đổi chúng

Đó là, bạn có thể khai báo tất cả các biến finalcho đến khi bạn biết rằng bạn sẽ cần thay đổi nó, tại thời điểm đó bạn có thể loại bỏ final. Một thực tế thường được biết là điều này cũng hợp lệ cho các tham số của phương thức:

public void foo(final int arg) { /* Stuff Here */ }

- Khi có điều gì xấu xảy ra, hãy để lại dấu vết bằng chứng

Có một số điều bạn có thể làm khi có ngoại lệ: rõ ràng đăng nhập nó và thực hiện một số dọn dẹp sẽ là một số ít. Nhưng bạn cũng có thể để lại một dấu vết bằng chứng (ví dụ: đặt các biến thành các giá trị sentinel như "UNABLE TO LOAD FILE" hoặc 99999 sẽ hữu ích trong trình gỡ lỗi, trong trường hợp bạn catchtình cờ thổi qua một ngoại lệ -block).

- Khi nói đến sự nhất quán: ma quỷ nằm trong các chi tiết

Hãy nhất quán với các thư viện khác mà bạn đang sử dụng. Ví dụ, trong Java, nếu bạn đang tạo một phương pháp mà chiết xuất một loạt các giá trị làm cho thấp hơn bị ràng buộc bao gồm và các ràng buộc trên độc quyền . Điều này sẽ làm cho nó phù hợp với các phương thức như String.substring(start, end)hoạt động theo cùng một cách. Bạn sẽ tìm thấy tất cả các loại phương thức này trong Sun JDK để hành xử theo cách này vì nó thực hiện các hoạt động khác nhau bao gồm lặp các phần tử phù hợp với mảng, trong đó các chỉ số từ Zero ( bao gồm ) đến độ dài của mảng (loại trừ ).

Vì vậy, một số thực hành phòng thủ yêu thích của bạn là gì?

Cập nhật: Nếu bạn chưa có, hãy thoải mái bấm chuông. Tôi sẽ có cơ hội nhận được nhiều phản hồi hơn trước khi tôi chọn câu trả lời chính thức .


Tôi muốn đại diện cho 30% chúng ta không biết gì ở đây có thể không biết các kỹ thuật đơn giản . Bất cứ ai cũng có một liên kết đến các kỹ thuật "rõ ràng" mà mọi người nên biết như là một nền tảng?
elliot42


Tại sao bạn chọn một câu trả lời chính thức? Điều đó chỉ có vẻ hoàn toàn không chú ý.
bzlm

Chà, khi nói đến lập trình, ngay cả sự thông minh cũng phải ngồi ghế sau để tôn vinh "quy tắc ít gây ngạc nhiên nhất". Đối với tôi để phá vỡ với tinh thần Stack Overflow đánh dấu câu trả lời tốt nhất sẽ vi phạm giao thức Stack Overflow (do đó phá vỡ quy tắc đã nói). Bên cạnh đó: Tôi thích đóng cửa :-)
Ryan Delucchi

Câu trả lời:


103

Trong c ++, tôi đã từng thích xác định lại mới để nó cung cấp thêm một số bộ nhớ để bắt lỗi hàng rào.

Hiện tại, tôi thích tránh lập trình phòng thủ có lợi cho Test Driven Development . Nếu bạn bắt lỗi nhanh chóng và từ bên ngoài, bạn không cần phải làm rối mã của mình bằng các thao tác phòng thủ, mã của bạn là DRY -er và bạn sẽ có ít lỗi hơn để chống lại.

Như WikiKnowledge đã viết :

Tránh lập trình phòng thủ, thất bại nhanh thay.

Bằng lập trình phòng thủ, ý tôi là thói quen viết mã cố gắng bù đắp một số lỗi trong dữ liệu, viết mã giả định rằng người gọi có thể cung cấp dữ liệu không phù hợp với hợp đồng giữa người gọi và chương trình con và bằng cách nào đó chương trình con phải đối phó với nó.


8
Lập trình phòng thủ đang cố gắng đối phó với các điều kiện bất hợp pháp được giới thiệu bởi các phần khác của chương trình. Xử lý đầu vào người dùng không đúng là điều hoàn toàn khác nhau.
Joe Soul-bringer

5
Errr ... lưu ý rằng định nghĩa về lập trình phòng thủ này thậm chí không gần với định nghĩa được sử dụng ngầm trong câu hỏi.
Sol

15
Không bao giờ tránh lập trình phòng thủ. Điều không phải là "bù đắp" cho những thất bại trong dữ liệu mà là bảo vệ bản thân khỏi dữ liệu độc hại được thiết kế để làm cho mã của bạn làm những việc mà nó không phải làm. Xem Tràn bộ đệm, SQL Injection. Không có gì thất bại nhanh hơn một trang web theo XSS nhưng nó không đẹp lắm
Jorge Córdoba

6
Tôi sẽ lập luận rằng các thủ tục "thất bại nhanh" là một hình thức lập trình phòng thủ.
Ryan Delucchi

6
@ryan hoàn toàn chính xác, thất bại nhanh là một khái niệm phòng thủ tốt. Nếu trạng thái bạn đang ở là không thể, đừng cố gắng đi khập khiễng, FAIL NHANH CHÓNG VÀ LOUD! Đặc biệt quan trọng nếu bạn đang điều khiển siêu dữ liệu. Lập trình phòng thủ không chỉ kiểm tra các thông số của bạn ...
Bill K

75

SQL

Khi tôi phải xóa dữ liệu, tôi viết

select *    
--delete    
From mytable    
Where ...

Khi tôi chạy nó, tôi sẽ biết nếu tôi quên hoặc làm hỏng mệnh đề where. Tôi có một sự an toàn. Nếu mọi thứ đều ổn, tôi làm nổi bật mọi thứ sau mã thông báo '-' và chạy nó.

Chỉnh sửa: nếu tôi xóa nhiều dữ liệu, tôi sẽ sử dụng tính (*) thay vì chỉ *


Tôi đã viết điều này trên iPhone của tôi, nơi tôi không thể chọn văn bản. Nếu ai đó có thể đánh dấu mã của tôi là mã, nó sẽ thực sự được đánh giá cao. Cảm ơn.
John MacIntyre

6
Tôi chỉ gói nó trong một giao dịch để tôi có thể quay lại nếu tôi làm hỏng ...
rmeador

36
BEGIN GIAO DỊCH | GIAO DỊCH ROLLBACK
Dalin Seivewright

3
+1 Có, tôi cho rằng điều này có thể được thay thế bằng một giao dịch, nhưng tôi thích sự đơn giản khi thực hiện theo cách này.
Ryan Delucchi

3
Sử dụng một giao dịch là tốt hơn; điều đó có nghĩa là bạn có thể chạy nó hàng chục lần và xem các hiệu ứng thực tế , cho đến khi bạn chắc chắn rằng mình đã hiểu đúng và có thể cam kết.
Ryan Lundy

48

Phân bổ một đoạn bộ nhớ hợp lý khi ứng dụng khởi động - Tôi nghĩ Steve McConnell gọi đây là một chiếc dù bộ nhớ trong Code Complete.

Điều này có thể được sử dụng trong trường hợp một cái gì đó nghiêm trọng đi sai và bạn được yêu cầu chấm dứt.

Phân bổ bộ nhớ này lên phía trước cung cấp cho bạn một mạng lưới an toàn, vì bạn có thể giải phóng nó và sau đó sử dụng bộ nhớ khả dụng để làm như sau:

  • Lưu tất cả dữ liệu liên tục
  • Đóng tất cả các tệp thích hợp
  • Viết thông báo lỗi vào một tệp nhật ký
  • Trình bày một lỗi có ý nghĩa cho người dùng

Tôi đã thấy điều này được gọi là một quỹ ngày mưa.
plinth

42

Trong mọi câu lệnh chuyển đổi không có trường hợp mặc định, tôi thêm trường hợp hủy bỏ chương trình với thông báo lỗi.

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}

2
Hoặc ném trong một ngôn ngữ răng nanh hiện đại.
Tom Hawtin - tackline

2
Khẳng định có lợi thế là các hiệu ứng của nó có thể bị vô hiệu hóa trên toàn cầu tại thời điểm biên dịch. Tuy nhiên, trong một số tình huống, ném có thể phù hợp hơn, nếu ngôn ngữ của bạn hỗ trợ nó.
Diomidis Spinellis

2
Khẳng định có một ưu điểm khác giúp nó hữu ích cho mã sản xuất: Khi có sự cố xảy ra, nó sẽ cho bạn biết chính xác lỗi nào và lỗi của chương trình xuất phát từ dòng nào. Một báo cáo lỗi như thế là tuyệt vời!
Mason Wheeler

2
@Diomidis: Một góc độ khác là: Khẳng định có nhược điểm là các hiệu ứng của nó có thể bị vô hiệu hóa trên toàn cầu tại thời điểm biên dịch.
vỡ mộng

1
ném thực sự là tốt hơn. Và sau đó bạn chắc chắn rằng phạm vi kiểm tra của bạn là đủ.
Đaminh Cronin

41

Khi bạn xử lý các trạng thái khác nhau của enum (C #):

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Sau đó, bên trong một số thói quen ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

Tại một số điểm tôi sẽ thêm một loại tài khoản khác vào enum này. Và khi tôi làm, tôi sẽ quên sửa câu lệnh chuyển đổi này. Vì vậy, các Debug.Failsự cố khủng khiếp (trong chế độ Gỡ lỗi) để thu hút sự chú ý của tôi vào thực tế này. Khi tôi thêm case AccountType.MyNewAccountType:, sự cố khủng khiếp dừng lại ... cho đến khi tôi thêm một loại tài khoản khác và quên cập nhật các trường hợp ở đây.

(Vâng, tính đa hình có lẽ tốt hơn ở đây, nhưng đây chỉ là một ví dụ ngoài đỉnh đầu của tôi.)


4
Hầu hết các trình biên dịch đủ thông minh để đưa ra cảnh báo nếu bạn không xử lý một số enum trong khối trường hợp. Nhưng đặt mặc định thành thất bại vẫn là hình thức tốt - enum chỉ là một con số và nếu bạn bị hỏng bộ nhớ, bạn có thể xuất hiện một giá trị không hợp lệ.
Adam Hawes

trong c, tôi đã sử dụng để thêm một không hợp lệAccountType ở cuối. điều này đôi khi hữu ích
Ray Tayek

1
@Adam - trình biên dịch sẽ đưa ra cảnh báo nếu bạn biên dịch lại mọi thứ . Nếu bạn thêm các lớp mới và chỉ biên dịch lại một phần, bạn có thể không nhận thấy điều gì đó giống như ở trên và trường hợp mặc định sẽ cứu bạn. Nó sẽ không âm thầm thất bại.
Eddie

4
Sự phá vỡ được ngụ ý trong các ý kiến, Slashene. : P
Ryan Lundy

2
Sau khi gỡ lỗi sẽ là 'ném NotSupportedException ()' mới cho mã sản xuất.
dùng7116

35

Khi in ra các thông báo lỗi bằng một chuỗi (đặc biệt là một chuỗi phụ thuộc vào đầu vào của người dùng), tôi luôn sử dụng các dấu ngoặc đơn ''. Ví dụ:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

Việc thiếu dấu ngoặc kép %snày thực sự rất tệ, bởi vì nói tên tệp là một chuỗi rỗng hoặc chỉ là khoảng trắng hoặc một cái gì đó. Tất nhiên, thông điệp được in ra sẽ là:

ERROR: Could not open file

Vì vậy, luôn luôn tốt hơn để làm:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

Sau đó, ít nhất người dùng sẽ thấy điều này:

ERROR: Could not open file ''

Tôi thấy rằng điều này tạo ra một sự khác biệt lớn về chất lượng của các báo cáo lỗi được gửi bởi người dùng cuối. Nếu có một thông báo lỗi trông buồn cười như thế này thay vì một âm thanh chung chung, thì nhiều khả năng họ sẽ sao chép / dán nó thay vì chỉ viết "nó sẽ không mở các tệp của tôi".


4
thật tốt, tôi cũng đã thấy vấn đề này
Alex Baranosky

28

An toàn SQL

Trước khi viết bất kỳ SQL nào sẽ sửa đổi dữ liệu, tôi bao bọc toàn bộ trong một giao dịch được khôi phục:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

Điều này ngăn bạn thực hiện xóa / cập nhật xấu vĩnh viễn. Và, bạn có thể thực thi toàn bộ và xác minh số lượng bản ghi hợp lý hoặc thêm các SELECTcâu lệnh giữa SQL của bạn và ROLLBACK TRANSACTIONđể đảm bảo mọi thứ đều ổn.

Khi bạn hoàn toàn chắc chắn nó làm những gì bạn mong đợi, hãy thay đổi ROLLBACKthành COMMITvà chạy thật.


Bạn đánh tôi đến cú đấm này! :-)
Howard Pinsley

2
Tôi đã từng làm điều này mọi lúc, nhưng nó gây ra quá nhiều chi phí cho các cụm DB mà tôi phải dừng lại.
Zan Lynx

25

Đối với tất cả các ngôn ngữ:

Giảm phạm vi của các biến đến mức tối thiểu có thể yêu cầu. Các biến Eschew chỉ được cung cấp để đưa chúng vào câu lệnh tiếp theo. Các biến không tồn tại là các biến bạn không cần phải hiểu và bạn không thể chịu trách nhiệm. Sử dụng Lambdas bất cứ khi nào có thể cho cùng một lý do.


5
Chính xác có nghĩa là gì phần eschew? Đôi khi tôi giới thiệu các biến chỉ tồn tại cho đến dòng tiếp theo. Chúng phục vụ như một tên cho một biểu thức, làm cho mã dễ đọc hơn.
zoul

4
Có tôi cũng không đồng ý. Với các biểu thức rất phức tạp, thường nên chia chúng thành hai hoặc đôi khi ngắn hơn và đơn giản hơn bằng cách sử dụng các biến tạm thời. Đó là ít lỗi dễ bảo trì hơn và trình biên dịch sẽ tối ưu hóa temps
Cruachan

1
Tôi đồng ý rằng có những trường hợp đặc biệt khi tôi khai báo các biến cho rõ ràng (và đôi khi để tạo mục tiêu để gỡ lỗi). Kinh nghiệm của tôi là thực tiễn chung là sai theo hướng ngược lại.
dkretz

Tôi đồng ý với cả hai quan điểm; mặc dù khi tôi ngắt các biểu thức thành các biến tạm thời, tôi cố gắng làm như vậy trong một phạm vi riêng biệt. Lambdas là tuyệt vời cho điều này, cũng như các phương pháp trợ giúp.
Erik Forbes

19

Khi nghi ngờ, đánh bom ứng dụng!

Kiểm tra từng tham số ở đầu mỗi phương thức (dù tự mã hóa nó hay sử dụng lập trình dựa trên hợp đồng không quan trọng ở đây) và ném bom với ngoại lệ chính xác và / hoặc thông báo lỗi có ý nghĩa nếu có bất kỳ điều kiện tiên quyết nào đối với mã không gặp

Tất cả chúng ta đều biết về các điều kiện tiên quyết ngầm này khi chúng ta viết mã , nhưng nếu chúng không được kiểm tra rõ ràng, chúng ta sẽ tạo ra mê cung cho chính mình khi có sự cố xảy ra sau đó và hàng chục phương thức gọi riêng biệt sự xuất hiện của triệu chứng và vị trí thực tế trong đó một điều kiện tiên quyết không được đáp ứng (= nơi thực sự có vấn đề / lỗi).


Và tất nhiên: Sử dụng mã chung (một thư viện nhỏ, phương thức mở rộng trong C #, bất cứ điều gì) để thực hiện điều này dễ dàng. Vì vậy, bạn có thể viết đôi khi param.NotNull("param")thay vìif ( param == null ) throw new ArgumentNullException("param");
peSHIr

2
Hoặc sử dụng lập trình dựa trên hợp đồng, như Spec #!
bzlm

Phụ thuộc vào ứng dụng gì. Tôi hy vọng những người viết mã cho máy bay bay và máy tạo nhịp tim không nghĩ như peSHIr.
MarkJ

6
@MarkJ: Bạn không thực sự hiểu nó, phải không? Nếu nó ném bom sớm (= trong quá trình phát triển và thử nghiệm) thì nó sẽ không bao giờ đánh bom khi nó được sản xuất. Vì vậy, tôi thực sự hy vọng họ làm chương trình như thế này!
peSHIr

Tôi phải thừa nhận tôi không thích điều đó lắm, đặc biệt là đối với các phương pháp riêng tư và được bảo vệ. Lý do: a) bạn làm lộn xộn mã với các kiểm tra hoàn toàn không giống với yêu cầu kinh doanh b) những kiểm tra đó khó kiểm tra đối với các phương thức không công khai c) trong nhiều trường hợp, vô dụng vì giá trị null sẽ khiến phương thức bị lỗi dù sao hai dòng sau
Erich Kitzmueller

18

Trong Java, đặc biệt là với các bộ sưu tập, hãy sử dụng API, vì vậy nếu phương thức của bạn trả về Danh sách loại (ví dụ), hãy thử như sau:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

Đừng cho phép bất cứ điều gì thoát khỏi lớp học của bạn mà bạn không cần!


+1 Trong C # có các bộ sưu tập chỉ đọc cho việc này.
tobsen

1
+1 cho Java. Tôi sử dụng điều này mọi lúc
Fortyrunner

+1 ... Tôi làm điều này rất nhiều (mặc dù tôi ước nhiều người sẽ nhớ làm điều này).
Ryan Delucchi

Chỉ cần đảm bảo rằng không có gì khác có thể hiện của biến danh sách cơ bản, hoặc điều này sẽ không giúp bạn nhiều ...
GreenieMeanie

5
FYI, Collections.unmodifiableList trả về một cái nhìn bất biến của danh sách, không phải là một bản sao bất biến . Vì vậy, nếu danh sách ban đầu được sửa đổi, thì cũng sẽ xem!
Zarkonnen

17

ở Perl, ai cũng vậy

use warnings;

tôi thích

use warnings FATAL => 'all';

Điều này làm cho mã chết cho bất kỳ cảnh báo trình biên dịch / thời gian chạy. Điều này chủ yếu là hữu ích trong việc bắt các chuỗi chưa được khởi tạo.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here

Mong muốn điều này được quảng cáo nhiều hơn một chút ...
DJG

16

C #:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}

Tương tự trong Java và có lẽ hầu hết các ngôn ngữ OO.
MiniQuark

Điều này thật tuyệt vời trong Objective-C, nơi có thể gửi tin nhắn đến nil (phương thức gọi của một đối tượng null, với một giấy phép nhất định). Gọi [nil isEqualToString: @ "Moo"] trả về sai.
zoul

Tôi không đồng ý với ví dụ C #. Một giải pháp tốt hơn là sử dụng "if (myString ==" someValue ")". Cũng không có ngoại lệ tham chiếu null, và chắc chắn dễ đọc hơn.
Dan C.

13
ví dụ này chỉ cho phép bạn che giấu một tình huống nguy hiểm tiềm tàng trong chương trình của bạn. Nếu bạn không mong đợi nó là null, bạn MUỐN nó ném một ngoại lệ và nếu bạn đang mong đợi nó là null, bạn nên xử lý nó như vậy. Đây là một thực hành xấu.
rmeador

2
Trong C #, tôi luôn chỉ sử dụng chuỗi.Equals (<string1>, <string2>). Không có vấn đề gì nếu một trong hai là null.
Darcy Casselman

15

Trong kiểm tra c # của chuỗi.IsNullOrEmpty trước khi thực hiện bất kỳ thao tác nào trên chuỗi như length, indexOf, mid, v.v.

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[Chỉnh sửa]
Bây giờ tôi cũng có thể thực hiện các thao tác sau trong .NET 4.0, kiểm tra thêm nếu giá trị chỉ là khoảng trắng

string.IsNullOrWhiteSpace(myString)

7
Đó không phải là phòng thủ, nó bỏ qua vấn đề. Nên if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
enashnash

14

Trong Java và C #, đặt cho mỗi luồng một tên có ý nghĩa. Điều này bao gồm chủ đề nhóm chủ đề. Nó làm cho các bãi chứa stack có ý nghĩa hơn nhiều. Phải mất thêm một chút nỗ lực để đặt một cái tên có ý nghĩa cho các luồng xử lý nhóm luồng, nhưng nếu một nhóm luồng có vấn đề trong một ứng dụng chạy dài, tôi có thể khiến kết xuất ngăn xếp xảy ra (bạn có biết về SendSignal.exe không? ), lấy các bản ghi và không phải làm gián đoạn một hệ thống đang chạy Tôi có thể biết chủ đề nào là ... bất cứ điều gì. Bế tắc, rò rỉ, phát triển, bất kể vấn đề là gì.


Và - trên Windows - cũng cho C ++! (được kích hoạt với ngoại lệ đặc biệt SEH và theo dõi bắt).
Brian Haak

12

Với VB.NET, có tùy chọn Explicit và Option Strict được bật theo mặc định cho toàn bộ Visual Studio.


Mặc dù tôi đang làm việc với mã cũ hơn, vì vậy tôi không bật tùy chọn nghiêm ngặt (gây ra quá nhiều lỗi trình biên dịch), việc bật cả hai tùy chọn này có thể (và trong trường hợp của tôi) đã cứu được rất nhiều trái tim đau nhức
Kibbee

1
Nhân tiện, đối với bất kỳ ai chuyển đổi mã cũ không sử dụng Option Strict để sử dụng nó, hãy lưu ý rằng trong trường hợp các mục được tự động chuyển thành Chuỗi, họ sẽ không sử dụng ToString (); họ đang sử dụng một chuỗi để đúc. Trong những ngày đầu .NET của tôi, tôi sẽ thay đổi những thứ này để sử dụng ToString () và nó sẽ phá vỡ mọi thứ, đặc biệt là với enums.
Ryan Lundy

10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

sau đó thay thế tất cả các cuộc gọi ' xóa pPtr ' và ' xóa [] pPtr ' của bạn bằng SAFE_DELETE (pPtr)SAFE_DELETE_ARRAY (pPtr)

Bây giờ do nhầm lẫn nếu bạn sử dụng con trỏ 'pPtr' sau khi xóa nó, bạn sẽ gặp lỗi 'vi phạm truy cập'. Nó dễ dàng sửa chữa hơn nhiều so với hỏng bộ nhớ ngẫu nhiên.


1
Tốt hơn nên sử dụng một mẫu. Đó là phạm vi và quá tải.

Tôi đã định nói điều đó. Sử dụng một mẫu thay vì một macro. Bằng cách đó, nếu bạn cần phải bước qua mã, bạn có thể.
Steve Rowe

Tôi đã học được cách dễ dàng hơn để gỡ lỗi một cách khó khăn ở trường. Đó là một sai lầm mà tôi đã không mắc phải kể từ đó. :)
Greg D

3
Hoặc sử dụng Con trỏ thông minh ... Hoặc heck, tránh mới / xóa càng nhiều càng tốt.
Arafangion

1
@Arafangion: chỉ cần tránh delete. Sử dụng newlà tốt, miễn là đối tượng mới được sở hữu bởi một con trỏ thông minh.
Alexandre C.

10

Với Java, có thể thuận tiện để sử dụng từ khóa khẳng định, ngay cả khi bạn chạy mã sản xuất với các xác nhận đã tắt:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

Ngay cả khi xác nhận tắt, ít nhất là mã tài liệu thực tế là param dự kiến ​​sẽ được đặt ở đâu đó. Lưu ý rằng đây là chức năng trợ giúp riêng tư và không phải là thành viên của API công khai. Phương pháp này chỉ có thể được gọi bởi bạn, vì vậy bạn có thể đưa ra một số giả định nhất định về cách sử dụng nó. Đối với các phương thức công khai, có lẽ tốt hơn là ném một ngoại lệ thực sự cho đầu vào không hợp lệ.


Trong .NET, Debug.Assert cũng làm điều tương tự. Bất cứ khi nào bạn có một nơi mà bạn nghĩ rằng "Tài liệu tham khảo này không thể ở đây, phải không?", Bạn có thể đặt Debug.Assert ở đó, để nếu nó có thể là null, bạn có thể sửa lỗi hoặc thay đổi các giả định của mình.
Ryan Lundy

1
+1, các phương thức công khai sẽ gây ra thất bại trong hợp đồng, các tư nhân nên khẳng định
user7116

9

Tôi đã không tìm thấy readonlytừ khóa cho đến khi tôi tìm thấy ReSharper, nhưng bây giờ tôi sử dụng nó theo bản năng, đặc biệt là cho các lớp dịch vụ.

readonly var prodSVC = new ProductService();

Tôi sử dụng từ khóa 'cuối cùng' tương đương của Java trên tất cả các lĩnh vực mà tôi có thể. Nó thực sự giúp bạn không phải di chuyển đầu xương như không đặt trường / viết các công tắc khó hiểu có thể đặt trường nhiều lần. Tôi ít có khả năng đánh dấu các tham số / tham số cục bộ nhưng tôi đoán nó không thể bị tổn thương.
Lập trình viên ngoài vòng pháp luật

C # không cho phép bạn đánh dấu các biến cục bộ là chỉ đọc, vì vậy chúng tôi thậm chí không có lựa chọn nào ...
Jason Punyon

Tôi không chắc những gì chỉ đọc ở đây có nghĩa là gì, nhưng cuối cùng của Java là không đủ với tôi. Nó chỉ có nghĩa là con trỏ đến một đối tượng là không thay đổi, nhưng không có cách nào để ngăn chặn sự thay đổi trên chính đối tượng đó.
Slartibartfast

Trong C #, cấu trúc chỉ đọc sẽ ngăn không cho nó bị thay đổi.
Samuel

9

Trong Java, khi một cái gì đó đang xảy ra và tôi không biết tại sao, đôi khi tôi sẽ sử dụng Log4J như thế này:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

bằng cách này, tôi nhận được một dấu vết ngăn xếp cho tôi thấy tôi đã gặp phải tình huống bất ngờ như thế nào, nói rằng một khóa không bao giờ được mở khóa, một cái gì đó không thể là null, v.v. Rõ ràng, nếu một Ngoại lệ thực sự bị ném, tôi không cần phải làm điều này. Đây là khi tôi cần xem những gì đang xảy ra trong mã sản xuất mà không thực sự làm phiền bất cứ điều gì khác. Tôi không muốn ném Ngoại lệ và tôi đã không bắt được. Tôi chỉ muốn một dấu vết ngăn xếp được ghi lại với một thông điệp thích hợp để gắn cờ tôi với những gì đang xảy ra.


Hmm, điều này hơi gọn gàng nhưng tôi lo ngại nó sẽ gây ra một số pha trộn mã xử lý chức năng và lỗi. Mặc dù cơ chế bắt thử có thể ở phía vụng về, nhưng nó buộc người ta phải thực hiện định tuyến lại ngoại lệ từ khối này sang khối khác (bắt), đó là nơi xử lý tất cả lỗi
Ryan Delucchi

1
Điều này không được sử dụng để xử lý lỗi, mà chỉ cho chẩn đoán . Điều này cho phép bạn xem đường dẫn mà mã của bạn gặp phải tình huống không mong muốn mà không làm gián đoạn dòng mã của bạn. Tôi sử dụng phương thức này khi bản thân phương thức có thể xử lý tình huống bất ngờ, nhưng vẫn không nên xảy ra.
Eddie

2
bạn cũng có thể sử dụng Ngoại lệ mới ("tin nhắn"). printStackTrace (); Không cần ném hay bắt, nhưng bạn vẫn có được một stacktrace đẹp trong nhật ký. Rõ ràng, điều này không nên có trong mã sản xuất, nhưng nó có thể rất hữu ích để gỡ lỗi.
Jorn

9

Nếu bạn đang sử dụng Visual C ++, hãy sử dụng từ khóa ghi đè bất cứ khi nào bạn phương thức của lớp cơ sở. Bằng cách này, nếu bất cứ ai từng thay đổi chữ ký lớp cơ sở, nó sẽ đưa ra một lỗi trình biên dịch thay vì phương thức sai được gọi âm thầm. Điều này sẽ cứu tôi một vài lần nếu nó tồn tại trước đó.

Thí dụ:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}

đối với Java, đây là lớp Foo {void doS Something () {}} class Bar {@Override void doS Something () {}} Sẽ đưa ra cảnh báo nếu nó thiếu chú thích (vì vậy bạn không thể âm thầm ghi đè một phương thức mà bạn không có nghĩa là) và khi chú thích ở đó nhưng nó không ghi đè lên bất cứ điều gì.
Jorn

Thật tuyệt ... Tôi không biết về điều này ... quá tệ, nó dường như là đặc thù của Microsoft ... nhưng một số #defines tốt có thể giúp làm cho nó di động
e.tadeu

8

Tôi đã học được ở Java gần như không bao giờ chờ đợi một khóa để mở khóa, trừ khi tôi thực sự mong đợi rằng nó có thể mất một thời gian dài vô tận. Nếu thực tế, khóa sẽ mở khóa trong vòng vài giây, sau đó tôi sẽ chỉ đợi trong một khoảng thời gian nhất định. Nếu khóa không mở khóa, thì tôi phàn nàn và đổ chồng vào nhật ký, và tùy thuộc vào điều gì là tốt nhất cho sự ổn định của hệ thống, hãy tiếp tục như thể khóa được mở khóa, hoặc tiếp tục như thể khóa không bao giờ mở khóa.

Điều này đã giúp cô lập một vài điều kiện chủng tộc và điều kiện bế tắc giả là bí ẩn trước khi tôi bắt đầu làm điều này.


1
Chờ đợi với thời gian chờ được sử dụng như thế này là một dấu hiệu của vấn đề khác, tồi tệ hơn.
dicroce

2
Trong một hệ thống phải có thời gian hoạt động cao, ít nhất nên lấy thông tin chẩn đoán để bạn có thể tìm ra vấn đề. Đôi khi, bạn muốn thoát một chuỗi hoặc trả về một phản hồi cho người gọi khi hệ thống ở trạng thái đã biết, vì vậy bạn chờ đợi trên một semaphore. Nhưng bạn không muốn treo mãi mãi
Eddie

8

C #

  • Xác minh các giá trị không null cho các tham số loại tham chiếu trong phương thức công khai.
  • Tôi sử dụng sealedrất nhiều cho các lớp học để tránh giới thiệu các phụ thuộc mà tôi không muốn chúng. Cho phép thừa kế nên được thực hiện rõ ràng và không phải ngẫu nhiên.

8

Khi bạn đưa ra thông báo lỗi, ít nhất là cố gắng cung cấp cùng thông tin mà chương trình có khi đưa ra quyết định đưa ra lỗi.

"Quyền bị từ chối" cho bạn biết có vấn đề về quyền, nhưng bạn không biết tại sao hoặc sự cố xảy ra. "Không thể viết nhật ký giao dịch / my / file: Hệ thống tệp chỉ đọc" ít nhất cho bạn biết cơ sở quyết định được đưa ra, ngay cả khi nó sai - đặc biệt là nếu nó sai: tên tệp sai? mở sai? lỗi bất ngờ khác? - và cho bạn biết bạn đã ở đâu khi gặp sự cố.


1
Khi viết mã cho thông báo lỗi, hãy đọc tin nhắn và hỏi những gì bạn muốn biết tiếp theo , sau đó thêm nó. Lặp lại cho đến khi không hợp lý. Ví dụ: "Ngoài phạm vi." Những gì ngoài phạm vi? "Foo đếm ra khỏi phạm vi." Giá trị là gì? "Foo đếm (42) ngoài phạm vi." Phạm vi là gì? "Số lượng Foo (42) ngoài phạm vi (549 đến 666)."
HABO

7

Trong C #, sử dụng astừ khóa để truyền.

string a = (string)obj

sẽ ném một ngoại lệ nếu obj không phải là một chuỗi

string a = obj as string

sẽ để lại là null nếu obj không phải là một chuỗi

Bạn vẫn cần tính đến null, nhưng điều đó thường đơn giản hơn về phía trước sau đó tìm kiếm các ngoại lệ diễn viên. Đôi khi bạn muốn hành vi loại "cast hoặc blow up", trong trường hợp đó (string)objcú pháp được ưu tiên.

Trong mã của riêng tôi, tôi thấy tôi sử dụng ascú pháp khoảng 75% thời gian và (cast)cú pháp khoảng 25%.


Đúng, nhưng bây giờ bạn phải kiểm tra null trước khi sử dụng tài liệu tham khảo.
Brian Rasmussen

7
Không có được nó. Có vẻ là một quyết định tồi tệ với tôi, để thích null. Bạn sẽ gặp vấn đề ở đâu đó trong thời gian chạy mà không có gợi ý cho lý do ban đầu.
Kai Huppmann

Đúng. Điều này chỉ hữu ích nếu bạn thực sự muốn null khi nó không phải là loại chính xác. Hữu ích trong một số trường hợp: trong Silverlight nơi logic điều khiển và thiết kế thường được tách ra, logic chỉ muốn sử dụng điều khiển "Lên" nếu đó là một nút. Nếu không, nó như thể nó không tồn tại (= null).
Sander

2
Điều này có vẻ như phòng thủ như cửa sổ phòng khiêu vũ lớn trong một pháo đài. Nhưng đó là một góc nhìn đáng yêu!
Pontus Gagge

1
Điều này khiến tôi nhớ đến một người không sử dụng ngoại lệ vì 'họ đã phá vỡ chương trình'. Nếu bạn muốn một số hành vi nhất định khi một đối tượng không phải là một lớp bạn mong đợi, tôi nghĩ rằng tôi sẽ viết mã theo cách khác. is/instanceofbất chợt nghĩ đến.
Kirschstein

6

Hãy chuẩn bị cho bất kỳ đầu vào , và bất kỳ đầu vào nào bạn nhận được bất ngờ, đổ vào nhật ký. (Trong lý do. Nếu bạn đang đọc mật khẩu từ người dùng, đừng đổ nó vào nhật ký! Và đừng ghi nhật ký hàng ngàn loại tin nhắn này vào nhật ký mỗi giây. .)

Tôi không chỉ nói về xác nhận đầu vào của người dùng. Ví dụ: nếu bạn đang đọc các yêu cầu HTTP mà bạn muốn chứa XML, hãy chuẩn bị cho các định dạng dữ liệu khác. Tôi đã rất ngạc nhiên khi thấy các phản hồi HTML mà tôi chỉ mong đợi XML - cho đến khi tôi nhìn và thấy rằng yêu cầu của tôi đang thông qua một proxy minh bạch, tôi không biết và khách hàng đã tuyên bố không biết gì - và proxy đã hết thời gian cố gắng hoàn thành yêu cầu. Do đó, proxy đã trả lại một trang lỗi HTML cho khách hàng của tôi, gây nhầm lẫn cho khách hàng chỉ mong đợi dữ liệu XML.

Do đó, ngay cả khi bạn nghĩ rằng bạn điều khiển cả hai đầu dây, bạn có thể nhận được các định dạng dữ liệu bất ngờ mà không có bất kỳ kẻ xấu nào tham gia. Hãy chuẩn bị, mã phòng thủ và cung cấp đầu ra chẩn đoán trong trường hợp đầu vào bất ngờ.


6

Tôi cố gắng sử dụng Thiết kế theo cách tiếp cận Hợp đồng. Nó có thể được mô phỏng thời gian chạy bằng bất kỳ ngôn ngữ. Mọi ngôn ngữ đều hỗ trợ "khẳng định", nhưng thật dễ dàng và thuận tiện để viết một triển khai tốt hơn cho phép bạn quản lý lỗi theo cách hữu ích hơn.

Trong 25 lỗi lập trình nguy hiểm nhất , "Xác thực đầu vào không đúng" là lỗi nguy hiểm nhất trong phần "Tương tác không an toàn giữa các thành phần".

Thêm các xác nhận điều kiện tiên quyết vào đầu các phương thức là một cách tốt để đảm bảo rằng các tham số phù hợp. Khi kết thúc các phương thức tôi viết các postconditions , kiểm tra xem đầu ra đó có phải là những gì được dự định không.

Để thực hiện bất biến , tôi viết một phương thức trong bất kỳ lớp nào kiểm tra "tính nhất quán của lớp", nên được gọi là authomatically bởi macro điều kiện tiên quyết và hậu điều kiện.

Tôi đang đánh giá Thư viện hợp đồng mã .


6

Java

Api java không có khái niệm về các đối tượng bất biến, đó là xấu! Cuối cùng có thể giúp bạn trong trường hợp đó. Gắn thẻ cho mỗi lớp là bất biến với cuối cùng và chuẩn bị lớp phù hợp .

Đôi khi rất hữu ích khi sử dụng cuối cùng trên các biến cục bộ để đảm bảo chúng không bao giờ thay đổi giá trị của chúng. Tôi thấy điều này hữu ích trong các cấu trúc vòng lặp xấu xí, nhưng cần thiết. Nó chỉ đơn giản là dễ dàng vô tình sử dụng lại một biến mặc dù nó là một hằng số.

Sử dụng sao chép phòng thủ trong getters của bạn. Trừ khi bạn trả về một kiểu nguyên thủy hoặc một đối tượng bất biến, hãy đảm bảo bạn sao chép đối tượng để không vi phạm đóng gói.

Không bao giờ sử dụng bản sao, sử dụng một constructor sao chép .

Tìm hiểu hợp đồng giữa bằng và hashCode. Điều này bị vi phạm thường xuyên. Vấn đề là nó không ảnh hưởng đến mã của bạn trong 99% trường hợp. Mọi người ghi đè bằng, nhưng không quan tâm đến hashCode. Có những trường hợp trong mã của bạn có thể phá vỡ hoặc hành xử lạ, ví dụ: sử dụng các đối tượng có thể thay đổi làm khóa trong bản đồ.


5

Tôi đã quên viết echobằng PHP quá nhiều lần:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

Tôi sẽ mất mãi mãi để cố gắng tìm hiểu tại sao -> baz () không trả lại bất cứ thứ gì trong khi thực tế tôi chỉ không lặp lại nó! : -S Vì vậy, tôi đã tạo một EchoMelớp có thể được bao quanh bất kỳ giá trị nào cần được lặp lại:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

Và sau đó, đối với môi trường phát triển, tôi đã sử dụng EchoMe để bọc những thứ cần được in:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

Sử dụng kỹ thuật đó, ví dụ đầu tiên thiếu echohiện tại sẽ tạo ra một ngoại lệ ...


Không nên kiểm tra hàm hủy cho $ this-> đã in! == true?
Karsten

Bạn nên cân nhắc sử dụng một hệ thống mẫu, nhúng php vào html là tối ưu phụ trên hầu hết các hệ thống.
Cruachan

Có lẽ tôi đang thiếu một cái gì đó? Nhưng có vẻ như tôi đang cố gắng bù đắp cho tiền mã hóa: AnObject.ToStringthay vì Writeln(AnObject.ToString)?
vỡ mộng

Đúng, nhưng lỗi dễ dàng hơn nhiều trong PHP
quá nhiều php

4

C ++

Khi tôi gõ mới, tôi phải gõ ngay lập tức. Đặc biệt là cho mảng.

C #

Kiểm tra null trước khi truy cập các thuộc tính, đặc biệt là khi sử dụng mẫu Người hòa giải. Các đối tượng được thông qua (và sau đó nên được sử dụng như, như đã được lưu ý), và sau đó kiểm tra chống null. Ngay cả khi bạn nghĩ rằng nó sẽ không có giá trị, dù sao đi nữa, hãy kiểm tra. Tôi đã rất ngạc nhiên.


Tôi thích điểm đầu tiên của bạn. Tôi làm những điều tương tự khi, ví dụ, viết một phương thức trả về một bộ sưu tập một cái gì đó. Tôi tạo bộ sưu tập trên dòng đầu tiên và viết ngay câu lệnh return. Tất cả những gì còn lại là điền vào cách bộ sưu tập được phổ biến.
Lập trình viên ngoài vòng pháp luật

5
Trong C ++, khi bạn nhập mới, bạn nên gán ngay con trỏ đó vào AutoPtr hoặc vùng chứa được tính tham chiếu. C ++ có các hàm hủy và mẫu; sử dụng chúng một cách khôn ngoan để xử lý việc xóa tự động.
Trả lời

4

Sử dụng một hệ thống ghi nhật ký cho phép năng động, chạy điều chỉnh mức nhật ký thời gian. Thông thường nếu bạn phải dừng chương trình để bật ghi nhật ký, bạn sẽ mất bất kỳ trạng thái hiếm gặp nào xảy ra lỗi. Bạn cần có thể bật thêm thông tin đăng nhập mà không cần dừng quá trình.

Ngoài ra, 'strace -p [pid]' trên linux sẽ hiển thị cho bạn muốn hệ thống gọi một quy trình (hoặc luồng linux) đang thực hiện. Thoạt nhìn có vẻ lạ, nhưng một khi bạn đã quen với những cuộc gọi hệ thống thường được thực hiện bởi những gì libc gọi, bạn sẽ thấy điều này là vô giá trong chẩn đoán hiện trường.

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.