Mục đích của việc sử dụng dấu ngoặc nhọn (tức là {}) cho một dòng nếu hoặc vòng lặp là gì?


321

Tôi đang đọc một số ghi chú bài giảng của giảng viên C ++ của mình và ông đã viết như sau:

  1. Sử dụng thụt lề // OK
  2. Không bao giờ dựa vào quyền ưu tiên của nhà điều hành - Luôn sử dụng dấu ngoặc đơn // OK
  3. Luôn sử dụng khối {} - ngay cả đối với một dòng // không ổn , tại sao ???
  4. Đối tượng Const ở bên trái so sánh // OK
  5. Sử dụng không dấu cho các biến là> = 0 // mẹo hay
  6. Đặt Con trỏ thành NULL sau khi xóa - Bảo vệ xóa hai lần // không tệ

Kỹ thuật thứ 3 không rõ ràng với tôi: tôi sẽ đạt được gì khi đặt một dòng trong một { ... }?

Ví dụ: lấy mã kỳ lạ này:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

và thay thế nó bằng:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

Lợi ích của việc sử dụng phiên bản 1 là gì?


256
Dễ đọc và duy trì. Không rõ ràng ngay lập tức khối lệnh 'j ++' thuộc về điều gì và việc thêm mã sau khi nó sẽ không được liên kết với câu lệnh if.
Rừng và cây

27
Tôi luôn được yêu cầu sử dụng dấu ngoặc nhọn {} cho các dòng này vì một vài lý do. Nó làm cho mã rõ ràng hơn để đọc. Ngoài ra, một người khác trong sáu tháng có thể cần chỉnh sửa mã của bạn để sự rõ ràng là rất quan trọng và với các dấu ngoặc nhọn, lỗi sẽ ít xảy ra hơn. Không có gì chính xác hơn về nó, đó không chỉ là vấn đề thực hành tốt. Hãy ghi nhớ một dự án có thể có hàng ngàn và hàng ngàn dòng mã cho một số người mới tham gia!
RossC

30
Tôi không đồng ý với 6, vì nó sẽ ẩn xóa hai lần và có khả năng ẩn các lỗi logic.
Luchian Grigore

28
# 5 có thể khó khăn - hãy xem xét vòng lặp này : for (unsigned i = 100; i >= 0; --i).
Archie

36
Btw, (i % 2 == 0)mâu thuẫn (2). Bạn đang dựa vào quyền ưu tiên của nhà điều hành, và ý nghĩa là tất nhiên ((i % 2) == 0)chứ không phải (i % (2 == 0)). Tôi sẽ phân loại quy tắc 2 là "một tình cảm hợp lệ nhưng" luôn luôn "là sai".
Steve Jessop

Câu trả lời:


509

Chúng ta cũng cố gắng sửa đổi ikhi chúng ta tăng j:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

Ôi không! Đến từ Python, điều này có vẻ ổn, nhưng thực tế thì không, vì nó tương đương với:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

Tất nhiên, đây là một sai lầm ngớ ngẩn, nhưng một lỗi mà ngay cả một lập trình viên có kinh nghiệm cũng có thể mắc phải.

Một lý do rất tốt khác được chỉ ra trong câu trả lời của ta.speot.is .

Một phần ba tôi có thể nghĩ là được lồng vào nhau if:

if (cond1)
   if (cond2) 
      doSomething();

Bây giờ, giả sử bây giờ bạn muốn doSomethingElse() khi cond1không được đáp ứng (tính năng mới). Vì thế:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

Điều này rõ ràng là sai, vì các elsecộng sự với bên trong if.


Chỉnh sửa: Vì điều này đang được chú ý, tôi sẽ làm rõ quan điểm của tôi. Câu hỏi tôi đã trả lời là:

Lợi ích của việc sử dụng phiên bản 1 là gì?

Mà tôi đã mô tả. Có một số lợi ích. Nhưng, IMO, quy tắc "luôn luôn" không luôn luôn được áp dụng. Vì vậy, tôi không hoàn toàn hỗ trợ

Luôn sử dụng khối {} - ngay cả đối với một dòng // không ổn, tại sao ???

Tôi không nói luôn luôn sử dụng một {}khối. Nếu đó là một điều kiện & hành vi đủ đơn giản, thì không. Nếu bạn nghi ngờ ai đó có thể đến sau & thay đổi mã của bạn để thêm chức năng, hãy làm.


5
@Science_Fiction: Đúng, nhưng nếu bạn thêm i++ trước j++ đó, thì cả hai biến vẫn sẽ nằm trong phạm vi khi chúng được sử dụng.
Mike Seymour

26
Điều này nghe có vẻ rất hợp lý, nhưng bỏ qua thực tế là trình soạn thảo thực hiện thụt lề chứ không phải bạn và nó sẽ thụt lề i++;theo cách cho thấy ngay rằng nó không phải là một phần của vòng lặp. (Trước đây, đây có thể là một cuộc tranh luận hợp lý và tôi đã gặp những vấn đề như vậy. Khoảng 20 năm trước. Không kể từ đó.)
James Kanze

43
@James: đó không phải là "sự thật", tuy nhiên, đó là quy trình làm việc của bạn. Và quy trình làm việc của rất nhiều người, nhưng không phải của tất cả mọi người. Tôi không nghĩ rằng đó nhất thiết là lỗi khi coi nguồn C ++ là tệp văn bản thuần túy, thay vì đầu ra của trình soạn thảo WYSIWYG (vi / emacs / Visual Studio) thực thi các quy tắc định dạng. Vì vậy, quy tắc này là bất khả tri biên tập vượt quá những gì bạn cần, nhưng không vượt quá những gì mọi người thực sự sử dụng để chỉnh sửa C ++. Do đó "phòng thủ".
Steve Jessop

31
@JamesKanze Bạn có thực sự dựa vào giả định rằng mọi người luôn làm việc trong các IDE mạnh mẽ? CI cuối cùng được viết là bằng Nano. Ngay cả khi cho rằng, một trong những điều đầu tiên tôi có xu hướng tắt trong IDE là tự động thụt lề - vì IDE có xu hướng cản trở quy trình làm việc phi tuyến tính của tôi , cố gắng sửa chữa 'lỗi' của tôi dựa trên thông tin không đầy đủ . IDE không giỏi trong việc tự động thụt dòng tự nhiên của mọi lập trình viên. Những lập trình viên sử dụng các tính năng như vậy có xu hướng hợp nhất phong cách của họ với IDE của họ, điều này tốt nếu bạn chỉ sử dụng một IDE nhưng không thông thường nếu bạn làm việc trên nhiều.
Rushyo

10
Đây là một sai lầm ngớ ngẩn, nhưng một lỗi mà ngay cả một lập trình viên có kinh nghiệm cũng có thể mắc phải. - Giống như tôi đã nói trong câu trả lời của mình, tôi không tin điều đó. Tôi nghĩ rằng đó là một trường hợp hoàn toàn không có vấn đề trong thực tế.
Konrad Rudolph

324

Rất dễ dàng để vô tình thay đổi luồng điều khiển bằng các bình luận nếu bạn không sử dụng {}. Ví dụ:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

Nếu bạn nhận xét do_something_else()bằng một nhận xét một dòng, bạn sẽ kết thúc với điều này:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

Nó biên dịch, nhưng must_always_do_this()không phải lúc nào cũng được gọi.

Chúng tôi đã có vấn đề này trong cơ sở mã của chúng tôi, nơi ai đó đã vào để vô hiệu hóa một số chức năng rất nhanh trước khi phát hành. May mắn thay, chúng tôi đã bắt được nó trong đánh giá mã.


3
Ôi cậu bé !! đó là hành vi được xác định must_always_do_this();sẽ thực thi nếu bạn nhận xét // do_s Something_else ();
Mr.Anubis

1
@Supr, như đã được viết lần đầu tiên, anh ấy nói rằng rất khó để phá vỡ dòng chảy chính xác nếu bạn sử dụng niềng răng, và sau đó đưa ra một ví dụ về việc dễ dàng phá vỡ mà không có mã được đặt đúng dấu ngoặc
SeanC

20
Tôi đã chạy vào đây chỉ ngày khác. if(debug) \n //print(info);. Về cơ bản lấy ra cả một thư viện.
Kevin

4
Fortunately we caught it in code review.Ôi! Nghe có vẻ sai quá. Fortunately we caught it in unit tests.sẽ tốt hơn nhiều
BЈовић

4
@ BЈовић Nhưng nếu mã trong một bài kiểm tra đơn vị thì sao? Tâm trí kính râm. (Đùa thôi, đây là một ứng dụng cũ. Không có bài kiểm tra đơn vị nào.)
ta.speot.is

59

Tôi có nghi ngờ về năng lực của giảng viên. Xem xét điểm của mình:

  1. đồng ý
  2. Bất cứ ai sẽ thực sự viết (hoặc muốn đọc) (b*b) - ((4*a)*c)? Một số ưu tiên là rõ ràng (hoặc nên), và các dấu ngoặc đơn thêm chỉ gây thêm nhầm lẫn. (Mặt khác, bạn _should_ sử dụng dấu ngoặc đơn trong các trường hợp ít rõ ràng hơn, ngay cả khi bạn biết rằng chúng không cần thiết.)
  3. Sắp xếp Có hai quy ước trải rộng để định dạng các điều kiện và vòng lặp:
    nếu (cond) {
        mã;
    }
    
    và:
    nếu (cond)
    {
        mã;
    }
    
    Đầu tiên, tôi đồng ý với anh ta. Phần mở đầu {không nhìn thấy được, vì vậy tốt nhất là giả sử nó luôn ở đó. Tuy nhiên, trong lần thứ hai, tôi (và hầu hết những người tôi đã làm việc cùng) không gặp vấn đề gì với việc bỏ niềng răng cho một tuyên bố duy nhất. (Tất nhiên, với điều kiện là sự thụt lề có hệ thống và bạn sử dụng phong cách này một cách nhất quán. (Và rất nhiều lập trình viên rất giỏi, viết mã rất dễ đọc, bỏ qua dấu ngoặc ngay cả khi định dạng theo cách đầu tiên.)
  4. NO . Những thứ như if ( NULL == ptr )là đủ xấu để cản trở khả năng đọc. Viết các so sánh bằng trực giác. (Mà trong nhiều trường hợp dẫn đến hằng số ở bên phải.) 4 của anh ấy là lời khuyên tồi; bất cứ điều gì làm cho mã không tự nhiên làm cho nó ít đọc hơn.
  5. NO . Bất cứ điều gì nhưng intđược dành riêng cho các trường hợp đặc biệt. Để lập trình viên C và C ++ có kinh nghiệm, việc sử dụng các unsignedtoán tử bit tín hiệu. C ++ không có loại hồng y thực sự (hoặc bất kỳ loại phân loại hiệu quả nào khác); unsignedkhông hoạt động cho các giá trị số, vì các quy tắc quảng cáo. Các giá trị số mà trên đó không có phép toán số học nào có ý nghĩa, như số sê-ri, có lẽ có thể là unsigned. Tuy nhiên, tôi tranh luận với nó bởi vì nó gửi thông điệp sai: các thao tác bitwise cũng không có ý nghĩa. Quy tắc cơ bản là các loại tích phân là int, _unless_ có một lý do quan trọng để sử dụng loại khác.
  6. NO . Làm điều này một cách có hệ thống là sai lệch, và không thực sự bảo vệ chống lại bất cứ điều gì. Trong mã OO nghiêm ngặt, delete this;thường là trường hợp thường xuyên nhất (và bạn không thể đặt thisthành NULL), và nếu không, hầu hết deletelà trong các hàm hủy, vì vậy dù sao bạn cũng không thể truy cập con trỏ. Và thiết lập nó để NULLkhông làm bất cứ điều gì về bất kỳ con trỏ khác nổi xung quanh. Đặt con trỏ một cách có hệ thống để NULLmang lại cảm giác an toàn sai lầm và không thực sự mua cho bạn bất cứ thứ gì.

Nhìn vào mã trong bất kỳ tài liệu tham khảo điển hình. Ví dụ, Stroustrup vi phạm mọi quy tắc bạn đưa ra ngoại trừ quy tắc đầu tiên.

Tôi đề nghị bạn nên tìm một giảng viên khác. Một người thực sự biết những gì anh ấy đang nói về.


13
Số 4 có thể xấu nhưng có một mục đích cho nó. Nó đang cố gắng ngăn chặn nếu (ptr = NULL). Tôi không nghĩ rằng tôi đã từng sử dụng delete this, nó có phổ biến hơn tôi đã thấy không? Tôi không có xu hướng nghĩ rằng việc đặt một con trỏ thành NULL sau khi sử dụng là điều không nên làm nhưng YMMV. Có lẽ đó chỉ là tôi nhưng hầu hết các hướng dẫn của anh ấy dường như không tệ.
Firedragon

16
@Firedragon: Hầu hết các trình biên dịch sẽ cảnh báo if (ptr = NULL)trừ khi bạn viết nó dưới dạng if ((ptr = NULL)). Phải đồng ý với James Kanze rằng sự xấu xí của việc có NULLtrước tiên làm cho nó trở thành một KHÔNG rõ ràng đối với tôi.
Leo

34
@JamesKanze: Tôi phải nói rằng tôi không đồng ý với hầu hết những gì bạn đã nêu ở đây - mặc dù tôi đánh giá cao và tôn trọng lập luận của bạn khi đến với họ. Đối với các lập trình viên C và C ++ có kinh nghiệm, việc sử dụng các toán tử bit tín hiệu không dấu. - Tôi hoàn toàn không đồng ý: Việc sử dụng toán tử bit báo hiệu việc sử dụng toán tử bit. Đối với tôi, việc sử dụng unsignedchỉ ra một nguyện vọng từ phía lập trình viên rằng biến chỉ nên đại diện cho các số dương. Trộn lẫn với các số đã ký thường sẽ gây ra một cảnh báo trình biên dịch có lẽ là điều mà giảng viên đang có ý định.
Hợp phần 10

18
Đối với các lập trình viên C và C ++ có kinh nghiệm, việc sử dụng các toán tử bit tín hiệu không dấu Hoặc không. size_t, bất kỳ ai?
ta.speot.is

16
@James Kanze, xem xét mục đích. Bạn đang so sánh mã được tạo bởi một lập trình viên có kinh nghiệm với các ví dụ hướng dẫn. Những quy tắc này được cung cấp bởi các giảng viên bởi vì chúng là những loại sai lầm mà ông thấy sinh viên của mình mắc phải. Với kinh nghiệm, các sinh viên có thể thư giãn hoặc bỏ qua những điều tuyệt đối này.
Joshua Shane Liberman

46

Tất cả các câu trả lời khác bảo vệ quy tắc 3 của giảng viên.

Hãy để tôi nói rằng tôi đồng ý với bạn: quy tắc là dư thừa và tôi sẽ không khuyên bạn. Đúng là về mặt lý thuyết, nó ngăn ngừa lỗi nếu bạn luôn thêm dấu ngoặc nhọn. Mặt khác, tôi chưa bao giờ gặp phải vấn đề này trong cuộc sống thực : trái với những gì câu trả lời khác ngụ ý, tôi đã không bao giờ quên thêm dấu ngoặc nhọn một khi chúng trở nên cần thiết. Nếu bạn sử dụng thụt lề thích hợp, điều rõ ràng là bạn cần thêm dấu ngoặc nhọn khi có nhiều hơn một câu lệnh được thụt lề.

Câu trả lời của Thành phần 10 10 thực sự làm nổi bật trường hợp duy nhất có thể hiểu được trong đó điều này thực sự có thể dẫn đến một lỗi. Nhưng mặt khác, việc thay thế mã thông qua biểu thức chính quy luôn luôn đảm bảo sự chăm sóc rất lớn.

Bây giờ chúng ta hãy nhìn vào mặt khác của huy chương: có bất lợi khi luôn luôn sử dụng dấu ngoặc nhọn không? Các câu trả lời khác chỉ đơn giản là bỏ qua điểm này. Nhưng có một bất lợi: nó chiếm rất nhiều không gian màn hình thẳng đứng, và điều này đến lượt nó có thể làm cho mã của bạn không thể đọc được bởi vì nó có nghĩa là bạn phải di chuyển nhiều hơn mức cần thiết.

Hãy xem xét một hàm có nhiều mệnh đề bảo vệ ngay từ đầu (và vâng, sau đây là mã C ++ xấu nhưng trong các ngôn ngữ khác thì đây sẽ là một tình huống khá phổ biến):

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}

Đây là mã khủng khiếp và tôi lập luận mạnh mẽ rằng những điều sau đây dễ đọc hơn nhiều:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}

Tương tự, các vòng lặp lồng nhau ngắn có lợi khi bỏ qua các dấu ngoặc nhọn:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

So sánh với:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

Mã đầu tiên là súc tích; mã thứ hai bị cồng kềnh.

Và vâng, điều này có thể được giảm nhẹ đến một mức độ nào đó bằng cách đặt nẹp mở trên dòng trước đó. Nhưng điều đó vẫn sẽ sẽ dễ đọc hơn mã mà không có dấu ngoặc nhọn.

Tóm lại: không viết mã không cần thiết chiếm không gian màn hình.


26
Nếu bạn không tin vào việc viết mã chiếm không gian màn hình một cách không cần thiết, thì bạn không có doanh nghiệp nào đặt dấu ngoặc mở trên dòng riêng của nó. Bây giờ có lẽ tôi sẽ phải tránh né và chạy trốn khỏi sự báo thù thần thánh của GNU, nhưng nghiêm túc - hoặc bạn muốn mã của mình được thu gọn theo chiều dọc, hoặc bạn thì không. Và nếu bạn làm như vậy, đừng làm những việc được thiết kế chỉ để làm cho mã của bạn nhỏ gọn hơn theo chiều dọc. Nhưng như bạn nói, sau khi cố định đó, bạn sẽ vẫn còn muốn loại bỏ niềng răng không cần thiết. Hoặc có lẽ chỉ viết if (a == nullptr) { throw null_ptr_error("a"); }như một dòng.
Steve Jessop

6
@Steve Như một vấn đề của thực tế, tôi làm đặt nẹp mở trên dòng trước đó, với lý do rất bạn đã nêu. Tôi đã sử dụng phong cách khác ở đây để làm cho nó rõ ràng hơn mức độ khác biệt có thể lớn đến mức nào.
Konrad Rudolph

9
+1 Tôi hoàn toàn đồng ý rằng ví dụ đầu tiên của bạn dễ đọc hơn nhiều nếu không có dấu ngoặc nhọn. Trong ví dụ thứ hai, phong cách mã hóa cá nhân của tôi là sử dụng dấu ngoặc trên vòng lặp for bên ngoài chứ không phải bên trong. Tôi không đồng ý với @SteveJessop về việc phải cực đoan hay khác về mã nhỏ gọn theo chiều dọc. Tôi bỏ qua các dấu ngoặc nhọn với một lớp lót để giảm không gian dọc, nhưng tôi đặt dấu ngoặc nhọn của mình lên một dòng mới bởi vì tôi thấy dễ dàng hơn để xem phạm vi khi các dấu ngoặc được xếp thành hàng. Mục tiêu là khả năng đọc và đôi khi điều đó có nghĩa là sử dụng nhiều không gian theo chiều dọc hơn, lần khác có nghĩa là sử dụng ít hơn.
Travesty3

22
"Tôi chưa bao giờ gặp phải vấn đề này trong cuộc sống thực": may mắn cho bạn. Những thứ như thế này không chỉ đốt cháy bạn, chúng còn gây bỏng 90% độ ba (và đó chỉ là một vài lớp quản lý yêu cầu sửa chữa vào buổi tối).
Richard

9
@Richard Đơn giản là tôi không mua cái đó. Như tôi đã giải thích trong cuộc trò chuyện, ngay cả khi lỗi này có thể xảy ra (mà tôi thấy không có khả năng) thì việc sửa chữa một cách tầm thường là một khi bạn nhìn vào dấu vết ngăn xếp bởi vì rõ ràng là lỗi chỉ bằng cách nhìn vào mã. Yêu cầu phóng đại của bạn là hoàn toàn vô căn cứ.
Konrad Rudolph

40

Cơ sở mã hóa mà tôi đang làm việc bị phân tán bởi mã bởi những người có ác cảm bệnh lý với niềng răng, và đối với những người đi sau, nó thực sự có thể tạo ra sự khác biệt cho khả năng duy trì.

Ví dụ có vấn đề thường gặp nhất mà tôi gặp phải là:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;

Vì vậy, khi tôi đi cùng và muốn thêm một tuyên bố sau đó, tôi có thể dễ dàng kết thúc với điều này nếu tôi không cẩn thận:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}

Cho rằng phải mất ~ 1 giây để thêm niềng răng và có thể giúp bạn tiết kiệm tối thiểu một vài phút gỡ lỗi, tại sao bạn không bao giờ đi với tùy chọn giảm mơ hồ? Có vẻ như nền kinh tế sai lầm với tôi.


16
Không phải là vấn đề trong ví dụ này ở chỗ thụt không đúng cách và đường quá dài chứ không phải là niềng răng?
Honza Brabec

7
Có, nhưng tuân theo các nguyên tắc thiết kế / mã hóa chỉ "an toàn" giả định rằng mọi người cũng đang tuân theo các hướng dẫn khác (chẳng hạn như không có dòng quá dài) dường như đang yêu cầu sự cố. Nếu đã niềng răng ngay từ đầu, sẽ không thể kết thúc với một khối if không chính xác trong tình huống này.
kẹt

16
Làm thế nào để thêm niềng răng (làm cho nó if(really long...editor){ do_foo;}giúp bạn tránh trường hợp này? Có vẻ như vấn đề vẫn như vậy. Cá nhân tôi thích tránh niềng răng khi không cần thiết, tuy nhiên không liên quan gì đến thời gian cần thiết để viết nhưng khả năng đọc giảm do có thêm hai dòng trong mã.
Grizzly

1
Điểm hay - Tôi đã giả định rằng việc thực thi sử dụng niềng răng cũng sẽ dẫn đến việc chúng được đặt ở một nơi hợp lý, nhưng tất nhiên ai đó quyết tâm làm cho mọi thứ trở nên khó khăn có thể khiến chúng phù hợp như trong ví dụ của bạn. Tôi tưởng tượng hầu hết mọi người sẽ không, mặc dù.
kẹt

2
Điều đầu tiên và cuối cùng tôi làm khi chạm vào một tập tin là nhấn nút tự động định dạng. Nó loại bỏ hầu hết các vấn đề này.
Jonathan Allen

20

2c của tôi:

Sử dụng thụt lề

Chắc chắn

Không bao giờ dựa vào quyền ưu tiên của nhà điều hành - Luôn sử dụng dấu ngoặc đơn

Tôi sẽ không sử dụng các từ "không bao giờ và" luôn luôn ", nhưng nói chung tôi thấy quy tắc này hữu ích. Trong một số ngôn ngữ (Lisp, Smalltalk) đây không phải là vấn đề.

Luôn sử dụng khối {} - ngay cả đối với một dòng

Tôi không bao giờ làm điều đó và không bao giờ có một vấn đề duy nhất, nhưng tôi có thể thấy làm thế nào nó có thể tốt cho sinh viên, đặc biệt. nếu họ học Python trước.

Đối tượng Const ở bên trái so sánh

Điều kiện Yoda? Không, làm ơn. Nó làm tổn thương khả năng đọc. Chỉ cần sử dụng mức cảnh báo tối đa khi bạn biên dịch mã của mình.

Sử dụng không dấu cho các biến> = 0

ĐỒNG Ý. Buồn cười quá, tôi đã nghe Stroustrup không đồng ý.

Đặt Con trỏ thành NULL sau khi xóa - Bảo vệ xóa hai lần

Lời khuyên tệ! Không bao giờ có một con trỏ trỏ đến một đối tượng bị xóa hoặc không tồn tại.


5
+1 chỉ cho điểm cuối cùng thôi. Một con trỏ thô không có bộ nhớ sở hữu doanh nghiệp nào.
Konrad Rudolph

2
Liên quan đến việc sử dụng unsign: không chỉ Stroustrup, mà cả K & R (bằng C), Herb Sutter và (tôi nghĩ) Scott Meyers. Trong thực tế, tôi chưa bao giờ nghe bất cứ ai thực sự hiểu các quy tắc của C ++ tranh luận về việc sử dụng không dấu.
James Kanze

2
@JamesKanze Trên thực tế, trong cùng một dịp tôi đã nghe ý kiến ​​của Stroustrup (một hội nghị ở Boston năm 2008), Herb Sutter đã ở đó và không đồng ý với Bjarne ngay tại chỗ.
Nemanja Trifunovic

3
Chỉ cần hoàn thành " unsignedbị hỏng", một trong những vấn đề là khi C ++ so sánh các loại có chữ ký và không dấu có kích thước tương tự, nó sẽ chuyển đổi thành loại không dấu trước khi thực hiện so sánh. Điều này dẫn đến một sự thay đổi trong giá trị. Chuyển đổi sang chữ ký không nhất thiết phải tốt hơn nhiều; việc so sánh sẽ thực sự diễn ra "như thể" cả hai giá trị được chuyển đổi thành loại lớn hơn có thể đại diện cho tất cả các giá trị ở một trong hai loại.
James Kanze

1
@SteveJessop Tôi nghĩ bạn phải sử dụng nó trong bối cảnh hàm trả về unsigned. Tôi chắc rằng anh ta không có vấn đề gì với việc exp(double)trả lại giá trị nhiều hơn MAX_INT:-). Nhưng một lần nữa, vấn đề thực sự là chuyển đổi ngầm. int i = exp( 1e6 );C ++ hoàn toàn hợp lệ. Stroustrup thực sự đề xuất phản đối mất mát chuyển đổi ngầm định tại một thời điểm, nhưng ủy ban đã không quan tâm. (Một câu hỏi thú vị: sẽ unsigned-> intđược coi là mất mát. Tôi sẽ xem xét cả hai unsigned-> intint-> unsignedmất mát. Điều đó sẽ đi một chặng đường dài để làm cho unsignedOK
James Kanze

18

nó trực quan hơn và dễ hiểu Nó làm cho ý định rõ ràng.

Và nó đảm bảo rằng mã không bị hỏng khi người dùng mới có thể vô tình bỏ lỡ {, }trong khi thêm một tuyên bố mã mới.


Makes the intent clear+1, đây có lẽ là lý do ngắn gọn và chính xác nhất.
Qix - MONICA ĐƯỢC PHÂN PHỐI

13

Để thêm vào các đề xuất rất hợp lý ở trên, một ví dụ tôi gặp phải khi tái cấu trúc một số mã trong đó điều này trở nên quan trọng như sau: Tôi đã thay đổi một cơ sở mã rất lớn để chuyển từ API này sang API khác. API đầu tiên có lệnh gọi để đặt Id công ty như sau:

setCompIds( const std::string& compId, const std::string& compSubId );

trong khi đó sự thay thế cần hai cuộc gọi:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );

Tôi bắt đầu thay đổi điều này bằng cách sử dụng các biểu thức chính quy rất thành công. Chúng tôi cũng đã chuyển mã thông qua astyle , điều này thực sự làm cho nó dễ đọc hơn rất nhiều. Sau đó, một phần trong quá trình xem xét, tôi phát hiện ra rằng trong một số trường hợp có điều kiện, nó đã thay đổi điều này:

if ( condition )
   setCompIds( compId, compSubId );

Về điều này:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );

đó rõ ràng không phải là những gì được yêu cầu. Tôi đã phải quay lại từ đầu để làm điều này một lần nữa bằng cách coi việc thay thế hoàn toàn trong một khối và sau đó thay đổi thủ công bất cứ thứ gì cuối cùng trông có vẻ ngớ ngẩn (ít nhất là nó sẽ không chính xác.)

Tôi nhận thấy rằng astyle hiện có tùy chọn --add-bracketscho phép bạn thêm dấu ngoặc ở những nơi không có và tôi thực sự khuyên bạn nên làm điều này nếu bạn thấy mình ở vị trí giống như tôi.


5
Tôi đã từng thấy một số tài liệu có đồng tiền tuyệt vời "Microsoftligent". Có, có thể phạm sai lầm đáng kể với tìm kiếm và thay thế toàn cầu. Điều đó chỉ có nghĩa là tìm kiếm và thay thế toàn cầu phải được sử dụng một cách thông minh, không phải là microsoftligently.
Pete Becker

4
Tôi biết đây không phải là công cụ hậu kỳ của tôi để thực hiện, nhưng nếu bạn định thay thế văn bản trên mã nguồn thì bạn nên thực hiện theo các quy tắc tương tự bạn sử dụng cho loại thay thế văn bản được đánh giá cao trong ngôn ngữ: macro. Bạn không nên viết một macro #define FOO() func1(); \ func2(); (với dấu ngắt dòng sau dấu gạch chéo ngược), tương tự với tìm kiếm và thay thế. Điều đó nói rằng, tôi đã thấy "luôn luôn sử dụng dấu ngoặc nhọn" tiên tiến như một quy tắc kiểu chính xác bởi vì nó giúp bạn tiết kiệm tất cả các macro đa câu lệnh của bạn do .. while(0). Nhưng tôi không đồng ý.
Steve Jessop

2
Btw, đó là "được thiết lập tốt" theo nghĩa là nút thắt của Nhật Bản được thiết lập tốt: Tôi không nói rằng chúng ta nên tránh sử dụng macro và thay thế văn bản, nhưng tôi nói rằng khi chúng ta làm như vậy điều, chúng ta nên làm theo cách hiệu quả, thay vì làm một việc chỉ hoạt động nếu một quy tắc kiểu cụ thể được áp đặt trên toàn bộ cơ sở mã :-)
Steve Jessop

1
@SteveJessop Một người cũng có thể tranh luận về niềng răng và thắt lưng. Nếu bạn phải sử dụng các macro như vậy (và chúng tôi đã làm, trước C ++ và inline), thì có lẽ bạn nên nhắm mục tiêu cho chúng hoạt động giống như một chức năng nhất có thể, sử dụng do { ... } while(0)thủ thuật nếu cần thiết (và rất nhiều dấu ngoặc đơn bổ sung. Nhưng điều đó vẫn không Sẽ không ngăn bạn sử dụng niềng răng ở mọi nơi, nếu đó là kiểu nhà. (FWIW: Tôi đã làm việc ở những nơi có nhiều kiểu nhà khác nhau, bao gồm tất cả các kiểu được thảo luận ở đây. Tôi chưa bao giờ thấy có vấn đề gì nghiêm trọng.)
James Kanze

1
Và tôi nghĩ, càng nhiều phong cách bạn đã làm việc với việc đọc và chỉnh sửa mã càng cẩn thận. Vì vậy, ngay cả khi bạn có một sở thích dễ đọc nhất, bạn vẫn sẽ đọc thành công những thứ khác. Tôi đã làm việc trong một công ty nơi các thành phần khác nhau được viết bởi các "kiểu nhà" khác nhau bởi các nhóm khác nhau và giải pháp chính xác là phàn nàn về nó trong phòng ăn trưa để không ảnh hưởng lớn, không cố gắng tạo ra một phong cách toàn cầu :-)
Steve Jessop

8

Tôi đang sử dụng {}ở mọi nơi ngoại trừ một vài trường hợp rõ ràng. Dòng đơn là một trong những trường hợp:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

Nó có thể làm tổn thương bạn khi bạn thêm một số phương pháp trước khi trở lại. Sự thụt lề chỉ ra rằng trả về là thực thi khi điều kiện được đáp ứng, nhưng nó sẽ luôn luôn trở lại.

Ví dụ khác trong C # với việc sử dụng statment

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}

tương đương với

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}

1
Chỉ cần sử dụng dấu phẩy trong usingcâu lệnh và bạn không cần phải :)
Ry-

1
@minitech Điều đó đơn giản là không hoạt động ở đây - bạn chỉ có thể sử dụng dấu phẩy khi các loại bằng nhau, không phải cho các loại không bằng nhau. Cách làm của Lukas là cách chính tắc, IDE thậm chí định dạng khác (lưu ý việc không thụt lề tự động của lần thứ hai using).
Konrad Rudolph

8

Ví dụ thích hợp nhất tôi có thể nghĩ ra:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

ifsẽ elseđược kết hợp với? Sự thụt lề ngụ ý rằng bên ngoài ifcó được else, nhưng đó không thực sự là cách trình biên dịch sẽ nhìn thấy nó; các bên if sẽ nhận được else, và bên ngoài ifthì không. Bạn sẽ phải biết rằng (hoặc thấy nó hoạt động theo cách đó trong chế độ gỡ lỗi) để tìm ra bằng cách kiểm tra lý do tại sao mã này có thể không đáp ứng mong đợi của bạn. Sẽ khó hiểu hơn nếu bạn biết Python; trong trường hợp đó bạn biết rằng thụt đầu dòng xác định các khối mã, vì vậy bạn sẽ mong đợi nó đánh giá theo cách thụt lề. C #, tuy nhiên, không đưa ra một cú lật về khoảng trắng.

Bây giờ, điều đó nói rằng, tôi không đặc biệt đồng ý với quy tắc "luôn luôn sử dụng dấu ngoặc" này trên mặt của nó. Nó làm cho mã rất ồn theo chiều dọc, làm giảm khả năng đọc qua nó một cách nhanh chóng. Nếu tuyên bố là:

if(someCondition)
   DoSomething();

... Sau đó nó nên được viết như thế này. Câu lệnh "luôn luôn sử dụng dấu ngoặc" nghe giống như "luôn bao quanh các phép toán với dấu ngoặc đơn". Điều đó sẽ biến tuyên bố rất đơn giản a * b + c / dthành ((a * b) + (c / d)), giới thiệu khả năng thiếu một paren gần (nguyên nhân của nhiều lập trình viên), và để làm gì? Thứ tự của các hoạt động là nổi tiếng và được thực thi tốt, vì vậy các dấu ngoặc là dư thừa. Bạn chỉ sử dụng dấu ngoặc đơn để thực thi một thứ tự hoạt động khác so với thông thường sẽ được áp dụng: a * (b+c) / dví dụ: Niềng răng khối cũng tương tự; sử dụng chúng để xác định những gì bạn muốn làm trong trường hợp nó khác với mặc định và không "rõ ràng" (chủ quan, nhưng thường khá thông thường).


3
@AlexBrown ... đó chính xác là quan điểm của tôi. Quy tắc như đã nêu trong OP là "luôn luôn sử dụng dấu ngoặc, ngay cả đối với các dòng đơn", mà tôi không đồng ý với lý do tôi đã nêu. Chân đế sẽ giúp với ví dụ mã đầu tiên, bởi vì mã sẽ không hành xử theo cách nó được thụt vào; bạn phải sử dụng dấu ngoặc để ghép nối elsevới cái đầu tiên ifthay vì cái thứ hai. Vui lòng xóa downvote.
KeithS

5

Nhìn qua các câu trả lời, không ai nói rõ loại thực hành mà tôi tạo thói quen, kể câu chuyện về mã của bạn:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

Trở thành:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0) j++;
}

Đưa j++trên cùng một dòng như nếu nên báo hiệu cho bất cứ ai khác, "Tôi chỉ muốn khối này để tăng bao giờ j" . Của coursethis chỉ có giá trị nếu dòng càng đơn giản càng tốt, bởi vì đặt một điểm dừng ở đây, như peri đề cập, sẽ không hữu ích lắm.

Trong thực tế, tôi vừa chạy qua một phần API Storm của Twitter có 'loại' mã này trong java, đây là đoạn mã phụ thuộc dạng mã thực thi, trên trang 43 của trình chiếu này :

...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...

Khối vòng lặp for có hai thứ trong đó, vì vậy tôi sẽ không nội tuyến mã đó. Tức là không bao giờ :

int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;

Thật tồi tệ và tôi thậm chí không biết nếu nó hoạt động (như dự định); không làm điều này . Các dòng và dấu ngoặc mới giúp phân biệt các đoạn mã riêng biệt nhưng có liên quan, giống như cách một dấu phẩy hoặc dấu chấm phẩy thực hiện trong văn xuôi. Khối trên xấu như một câu thực sự dài với một vài mệnh đề và một số câu khác không bao giờ ngắt hoặc tạm dừng để phân biệt các phần riêng biệt.

Nếu bạn thực sự muốn điện báo cho người khác, đó là công việc một dòng chỉ sử dụng toán tử hoặc ?:biểu mẫu ba:

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;

Nhưng điều này đang tràn ngập trong môn đánh gôn, và tôi nghĩ không phải là môn tập luyện tuyệt vời (Tôi không rõ liệu tôi có nên đặt j ++ ở một bên :hay không). NB Tôi chưa từng chạy toán tử ternary trong C ++ trước đây, tôi không biết điều này có hoạt động không, nhưng nó có tồn tại không .

Nói ngắn gọn:

Hãy tưởng tượng cách người đọc của bạn (tức là người duy trì mã) diễn giải câu chuyện của bạn (mã). Làm cho nó rõ ràng cho họ càng tốt. Nếu bạn biết lập trình viên / sinh viên mới làm quen đang duy trì điều này, thậm chí có thể để lại càng nhiều {}càng tốt, để họ không bị nhầm lẫn.


1
(1) Đặt câu lệnh trên cùng một dòng làm cho nó ít đọc hơn , không nhiều hơn. Đặc biệt là suy nghĩ đơn giản như một sự gia tăng dễ dàng bị bỏ qua. Đừng đặt chúng trên một dòng mới. (2) Tất nhiên bạn có thể đặt forvòng lặp của mình trên một dòng duy nhất, tại sao điều này không nên hoạt động? Nó hoạt động với cùng một lý do mà bạn có thể bỏ qua niềng răng; dòng mới chỉ đơn giản là không đáng kể trong C ++. (3) Ví dụ về toán tử có điều kiện của bạn, ngoài việc khủng khiếp, là C ++ không hợp lệ.
Konrad Rudolph

@KonradRudolph cảm ơn, tôi hơi bất lịch sự ở C ++. Tôi chưa bao giờ nói (1) dễ đọc hơn, nhưng nó sẽ báo hiệu rằng đoạn mã đó có nghĩa là trực tuyến một dòng. (2) Nhận xét của tôi là nhiều hơn rằng tôi sẽ không thể đọc nó và biết nó hoạt động, dù có hay không như dự định; đó là một ví dụ về những gì không nên làm vì lý do này. (3) Cảm ơn, tôi đã không viết C ++ trong một thời gian dài. Tôi sẽ sửa nó ngay bây giờ.
Pureferret

2
Ngoài ra, việc đặt nhiều hơn một biểu thức trong một dòng sẽ khiến việc gỡ lỗi mã trở nên khó khăn hơn. Làm thế nào để bạn đặt điểm dừng trên expresion thứ 2 trong dòng đó?
Piotr Perak

5

Bởi vì khi bạn có hai tuyên bố mà không có {}, rất dễ bỏ lỡ một vấn đề. Hãy giả sử rằng mã trông như thế này.

int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();

if ((err = prepare_hash(hash, &hash_result))) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
    goto fail;
    goto fail;
if ((err = hash_finish(hash)) != 0)
    goto fail;

error = do_important_stuff_with(hash);

fail:
hash_free(hash);
return error;

Nó trông ổn đấy. Vấn đề với nó thực sự rất dễ bỏ lỡ, đặc biệt là khi hàm chứa mã lớn hơn nhiều. Vấn đề goto faillà chạy vô điều kiện. Bạn có thể dễ dàng tưởng tượng điều này gây khó chịu như thế nào (khiến bạn hỏi tại sao cuối cùng hash_updateluôn thất bại, sau khi tất cả mọi thứ có vẻ tốt trong hash_updatechức năng).

Tuy nhiên, điều đó không có nghĩa là tôi thêm vào {}mọi nơi (theo tôi, nhìn thấy {}mọi nơi đều gây phiền nhiễu). Mặc dù nó có thể gây ra sự cố, nhưng nó không bao giờ làm cho các dự án của riêng tôi, vì phong cách mã hóa cá nhân của tôi cấm các điều kiện mà không có {}khi chúng không cùng dòng (vâng, tôi đồng ý rằng phong cách mã hóa của tôi là độc đáo, nhưng tôi thích nó, và tôi sử dụng kiểu mã của dự án khi đóng góp cho các dự án khác). Điều này làm cho mã sau đây tốt.

if (something) goto fail;

Nhưng không phải cái sau.

if (something)
    goto fail;

Chính xác. Chỉ cần không đặt dòng mới (hoàn toàn không cần thiết) + thụt lề và bạn hoàn toàn bỏ qua vấn đề này để mọi người luôn nhanh chóng đưa ra.
Alexander - Tái lập Monica

4

Nó làm cho mã của bạn dễ đọc hơn bằng cách xác định rõ phạm vi của các vòng lặp và các khối có điều kiện. Nó cũng cứu bạn khỏi những sai lầm tình cờ.


4

wrt 6: An toàn hơn vì xóa một con trỏ null là không có. Vì vậy, nếu bạn vô tình đi qua con đường đó hai lần, bạn sẽ không làm hỏng bộ nhớ để giải phóng bộ nhớ miễn phí hoặc đã được phân bổ cho thứ khác.

Đây là hầu hết các vấn đề với các đối tượng và singletons phạm vi tệp tĩnh không có thời gian sống rất rõ ràng và được biết là được tạo lại sau khi chúng bị phá hủy.

Trong hầu hết các trường hợp, bạn có thể tránh sự cần thiết này bằng cách sử dụng auto_ptrs


6
Nếu bạn tình cờ đi qua con đường đó hai lần, bạn đã gặp lỗi lập trình. Đặt con trỏ thành null để làm cho lỗi này ít gây hại hơn không giải quyết được vấn đề tiềm ẩn.
Pete Becker

1
Đồng ý, nhưng tôi đã thấy đề xuất này trước đây và tôi tin rằng nó trong một số tiêu chuẩn lập trình chuyên nghiệp. Tôi đã bình luận nhiều hơn về lý do tại sao giáo sư của bài viết đã đưa ra nó, hơn là khi nó tốt
Tom Tanner

2
Theo những gì Pete Becker nói: nó không giải quyết được vấn đề tiềm ẩn, nhưng nó có thể che giấu nó. (Có những trường hợp bạn sẽ đặt một con trỏ NULLsau khi xóa nó. Nếu NULLlà một giá trị chính xác cho con trỏ có trong các trường hợp đó, ví dụ: con trỏ trỏ đến một giá trị được lưu trong bộ đệm và NULLchỉ ra bộ đệm không hợp lệ. Nhưng khi bạn thấy ai đó cài đặt một con trỏ đến NULLdòng cuối cùng trong hàm hủy, bạn tự hỏi liệu anh ta có biết C ++ không.)
James Kanze

4

Tôi thích câu trả lời được chấp nhận của Luchian, trên thực tế tôi đã học được một cách khó khăn rằng anh ấy đúng, vì vậy tôi luôn sử dụng niềng răng, ngay cả đối với các khối đơn. Tuy nhiên, cá nhân tôi tạo một ngoại lệ khi viết bộ lọc, như bạn đang ở trong ví dụ của bạn. Điều này:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

có vẻ lộn xộn với tôi. Nó tách vòng lặp for và câu lệnh if thành các hành động riêng biệt, khi thực sự ý định của bạn là một hành động duy nhất: đếm tất cả các số nguyên chia hết cho 2. Trong một ngôn ngữ biểu cảm hơn, điều này có thể được viết như sau:

j = [1..100].filter(_%2 == 0).Count

Trong các ngôn ngữ thiếu các bao đóng, bộ lọc không thể được biểu thị trong một câu lệnh đơn lẻ, nhưng phải là một vòng lặp for theo sau là một câu lệnh if. Tuy nhiên, đó vẫn là một hành động trong tâm trí của lập trình viên và tôi tin rằng điều đó nên được phản ánh trong mã, như vậy:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
  if (i % 2 == 0)
{
    j++;
}

7
Tôi thích cách mọi người cố gắng phớt lờ for (int i = 0; i < 100; i += 2);, vì mục đích tiếp tục tranh luận về việc thụt lề ;-) Có lẽ chúng ta có thể có một cuộc chiến hoàn toàn riêng biệt, cách "tốt nhất" để diễn đạt logic "cho mỗi người itrong một phạm vi nhất định với một thuộc tính nhất định" trong C ++ không có vòng lặp, sử dụng một số kết hợp ác mộng của các thuật toán tiêu chuẩn filter_iteratorvà / hoặc counting_iterator.
Steve Jessop

1
Ngoài ra, nếu chúng ta có điều đó thì chúng ta có thể không đồng ý về cách thụt vào câu lệnh lớn.
Steve Jessop

2
@Steve, đây chỉ là một ví dụ. Có rất nhiều sử dụng hợp pháp của mô hình. Rõ ràng nếu bạn muốn đếm các số từ 1 đến 100 chia hết cho 2, tất cả những gì bạn phải làm là 100/2.
MikeFHay

2
Chắc chắn, tôi biết, đó là lý do tại sao tôi trừu tượng hóa "cho mỗi itrong một phạm vi nhất định với một tài sản nhất định". Chỉ là thông thường trên SO, mọi người rất nhanh chóng bỏ qua câu hỏi thực tế có lợi cho một cách tiếp cận hoàn toàn khác với ví dụ được đưa ra. Nhưng thụt lề rất quan trọng , vì vậy chúng tôi không ;-)
Steve Jessop

4

Một tùy chọn để giúp ngăn ngừa các lỗi đã được mô tả ở trên là nội tuyến những gì bạn muốn xảy ra khi bạn không sử dụng niềng răng. Nó làm cho khó khăn hơn nhiều để không nhận thấy các lỗi khi bạn cố gắng sửa đổi mã.

if (condition) doSomething();
else doSomethingElse();

if (condition) doSomething();
    doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong

5
Tùy chọn thứ hai sẽ mang lại lỗi biên dịch, vì elsekhông liên quan đến if.
Luchian Grigore

Một vấn đề không dễ thấy với nội tuyến là hầu hết các IDE mặc định thay đổi nó thành kiểu thụt lề khi sử dụng tiện ích autoformat của chúng.
Honza Brabec

@Honza: đó là một vấn đề chính trị có tính phí cao, mặc dù. Nếu chúng tôi hợp tác trên cơ sở mã, chúng tôi phải sử dụng cùng kiểu thụt lề cho đến từng chi tiết cuối cùng hoặc chúng tôi phải đồng ý không tự động định dạng mã hiện tại "chỉ vì". Nếu trước đây kiểu đồng ý vẫn có thể bao gồm điều này, nhưng bạn phải cấu hình IDE của mình để tôn trọng nó hoặc không sử dụng tự động định dạng. Đồng ý rằng định dạng phổ biến là "bất cứ điều gì IDE tự động định dạng" đều rất tốt nếu tất cả chúng ta sử dụng cùng một IDE mãi mãi, không tốt lắm.
Steve Jessop

3

Nếu bạn là một trình biên dịch, nó sẽ không tạo ra bất kỳ sự khác biệt nào. Cả hai đều giống nhau.

Nhưng đối với các lập trình viên, cái đầu tiên rõ ràng hơn, dễ đọc và ít bị lỗi hơn.


2
{Dù sao, ngoài việc mở trên dòng riêng của nó, dù sao.
Cướp

3

Một ví dụ khác về việc thêm dấu ngoặc nhọn. Khi tôi đang tìm kiếm một lỗi và tìm thấy mã như vậy:

void SomeSimpleEventHandler()
{
    SomeStatementAtTheBeginningNumber1;
    if (conditionX) SomeRegularStatement;
    SomeStatementAtTheBeginningNumber2;
    SomeStatementAtTheBeginningNumber3;
    if (!SomeConditionIsMet()) return;
    OtherwiseSomeAdditionalStatement1;
    OtherwiseSomeAdditionalStatement2;
    OtherwiseSomeAdditionalStatement3;
}

Nếu bạn đọc từng phương thức theo từng dòng, bạn sẽ nhận thấy rằng có một điều kiện trong phương thức trả về nếu nó không đúng. Nhưng thực tế có vẻ như 100 trình xử lý sự kiện đơn giản khác đặt một số biến dựa trên một số điều kiện. Và một ngày, Bộ giải mã nhanh xuất hiện và thêm câu lệnh thiết lập biến bổ sung vào cuối phương thức:

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionnaly;
}

Kết quả là SetAnotherVariableUnconditionnaly được thực thi khi someConditionIsMet (), nhưng anh chàng nhanh chóng không chú ý đến nó bởi vì tất cả các dòng có kích thước gần như tương tự và ngay cả khi điều kiện quay trở lại được thụt theo chiều dọc, nó không đáng chú ý.

Nếu lợi nhuận có điều kiện được định dạng như thế này:

if (!SomeConditionIsMet())
{
    return;
}

nó rất đáng chú ý và Fast Coder sẽ tìm thấy nó trong nháy mắt.


Nếu bộ mã hóa nhanh của bạn không thể bị làm phiền khi phát hiện một returncâu lệnh được tô sáng cú pháp trong phần thân của hàm trước khi thêm một cái gì đó vào nó, bạn không nên để bộ mã hóa nhanh đến gần mã của bạn. Bạn sẽ không ngăn chặn một kẻ như vậy troll xung quanh mã của bạn bằng cách bao gồm cả niềng răng.
cmaster - phục hồi monica

@cmaster Anh ấy không làm việc với chúng tôi nữa. Dù sao, đánh dấu cú pháp là tốt, nhưng hãy nhớ rằng có những người không nhìn rõ (tôi đã thấy ngay cả một bài đăng từ một lập trình viên mù năm ngoái).
Artemix

2

Tôi xem xét cái đầu tiên là rõ ràng sau đó thứ hai. Nó mang lại cảm giác đóng hướng dẫn, với ít mã là tốt khi mã trở nên phức tạp {...}giúp ích rất nhiều ngay cả khi nó là endifhoặcbegin...end

//first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}


//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

1

Tốt nhất là đặt con trỏ thành NULL khi bạn đã hoàn thành với nó.

Đây là một ví dụ tại sao:

Lớp A thực hiện như sau:

  1. Phân bổ một khối bộ nhớ
  2. Sau đó một thời gian, nó xóa khối bộ nhớ này nhưng không đặt con trỏ thành NULL

Lớp B làm như sau

  1. Phân bổ bộ nhớ (và trong trường hợp này, nó tình cờ được cung cấp cùng một khối bộ nhớ đã bị xóa bởi lớp A.)

Tại thời điểm này, cả Lớp A và Lớp B đều có con trỏ trỏ đến cùng một khối bộ nhớ, theo như Lớp A có liên quan thì khối bộ nhớ này không tồn tại vì nó đã kết thúc với nó.

Hãy xem xét vấn đề sau:

Điều gì xảy ra nếu có lỗi logic trong Lớp A dẫn đến việc nó ghi vào bộ nhớ mà bây giờ thuộc về Lớp B?

Trong trường hợp cụ thể này, bạn sẽ không gặp phải lỗi ngoại lệ truy cập xấu vì địa chỉ bộ nhớ là hợp pháp, tất cả các lớp A hiện đang làm hỏng dữ liệu lớp B một cách hiệu quả.

Lớp B cuối cùng có thể gặp sự cố nếu gặp phải các giá trị không mong muốn và khi nó gặp sự cố, rất có thể, bạn sẽ mất khá nhiều thời gian để săn lỗi này trong lớp B khi sự cố thuộc lớp A.

Nếu bạn đã đặt con trỏ bộ nhớ đã xóa thành NULL, bạn sẽ gặp lỗi ngoại lệ ngay khi có bất kỳ lỗi logic nào trong Lớp A cố ghi vào con trỏ NULL.

Nếu bạn lo lắng về lỗi logic khi xóa hai lần khi con trỏ là NULL lần thứ hai, thì hãy thêm xác nhận cho việc này.

Ngoài ra: Nếu bạn sẽ bỏ phiếu, xin vui lòng giải thích.


2
Nếu có lỗi logic, nó cần được sửa, thay vào đó là che giấu nó.
uınbɐɥs

@Barmar, OP nói ... 6. Đặt Con trỏ thành NULL sau khi xóa - Bảo vệ xóa hai lần // không tệ. Một số người đã phản hồi về việc không đặt nó thành Null và tôi đang nói tại sao nên đặt nó thành NULL, phần nào trong số 6. câu trả lời của tôi để đặt NULL không phù hợp với 6?
Giăng

1
@Shaquin, và làm thế nào để bạn đề xuất trong việc tìm ra các lỗi logic này ngay từ đầu? Khi bạn đặt biến con trỏ thành NULL sau khi bộ nhớ đã bị xóa. Bất kỳ nỗ lực nào để tham chiếu con trỏ NULL sẽ gặp sự cố với trình gỡ lỗi trên dòng mà nỗ lực bất hợp pháp của bạn đã được thực hiện. Bạn có thể theo dõi lại và xem lỗi logic ở đâu và khắc phục sự cố. Nếu bạn không đặt biến con trỏ thành NULL sau khi bạn đã xóa bộ nhớ, thì việc bạn cố ghi bộ nhớ bị xóa này do lỗi logic UNAWARE có thể thành công và do đó không bị sập tại thời điểm đó. Nó không che giấu nó.
Giăng

1

Luôn luôn có niềng răng là quy tắc rất đơn giản và mạnh mẽ. Tuy nhiên, mã có thể trông không phù hợp khi có nhiều dấu ngoặc nhọn. Nếu các quy tắc cho phép bỏ qua các dấu ngoặc nhọn thì nên có các quy tắc kiểu chi tiết hơn và các công cụ tinh vi hơn. Nếu không, nó có thể dễ dàng dẫn đến mã hỗn loạn và khó hiểu (không thanh lịch). Do đó, tìm kiếm quy tắc kiểu đơn tách biệt với phần còn lại của hướng dẫn kiểu và công cụ được sử dụng có thể không có kết quả. Tôi sẽ chỉ đưa ra một số chi tiết quan trọng về quy tắc số 3 mà thậm chí chưa được đề cập trong các câu trả lời khác.

Chi tiết thú vị đầu tiên là hầu hết những người đề xuất quy tắc đó đồng ý vi phạm nó trong trường hợp else. Nói cách khác, họ không yêu cầu xem lại mã như vậy:

// pedantic rule #3
if ( command == Eat )
{
    eat();
}
else
{
    if ( command == Sleep )
    {
        sleep();
    }
    else
    {
        if ( command == Drink )
        {
            drink();
        }
        else
        {
            complain_about_unknown_command();
        }
    }
}

Thay vào đó, nếu họ thấy nó, họ thậm chí có thể đề nghị viết nó như thế:

// not fully conforming to rule #3
if ( command == Eat )
{
    eat();
}
else if ( command == Sleep )
{
    sleep();
}
else if ( command == Drink )
{
    drink();
}
else
{
   complain_about_unknown_command();
}

Đó là vi phạm kỹ thuật của quy tắc đó vì không có dấu ngoặc nhọn giữa elseif. Tính hai mặt như vậy xuất hiện khi cố gắng áp dụng nó vào cơ sở mã tự động với một công cụ không cần suy nghĩ. Thật vậy, tại sao để tranh luận, chỉ cần để một công cụ để áp dụng phong cách tự động.

Chi tiết thứ hai (cũng thường bị quên bởi những người đề xuất quy tắc đó) là các lỗi có thể xảy ra không bao giờ chỉ vì vi phạm quy tắc số 3 đó. Trên thực tế những điều đó hầu như luôn liên quan đến việc vi phạm quy tắc số 1 quá (mà không ai tranh cãi). Một lần nữa theo quan điểm của các công cụ tự động, không khó để tạo ra một công cụ phàn nàn ngay lập tức khi quy tắc số 1 bị vi phạm và vì vậy hầu hết các lỗi có thể được phát hiện kịp thời.

Chi tiết thứ ba (thường bị các đối thủ của quy tắc đó lãng quên) là bản chất khó hiểu của tuyên bố trống rỗng được thể hiện bằng dấu chấm phẩy đơn. Hầu hết các nhà phát triển với một số kinh nghiệm đã sớm bị nhầm lẫn bởi dấu chấm phẩy không đúng chỗ hoặc bởi câu lệnh trống được viết bằng dấu chấm phẩy duy nhất. Hai dấu ngoặc nhọn thay vì dấu chấm phẩy đơn dễ nhìn thấy hơn.


1

Tôi phải thừa nhận rằng không phải lúc nào cũng sử dụng {}cho một dòng, nhưng đó là một thực hành tốt.

  • Hãy nói rằng bạn viết một mã không có dấu ngoặc giống như thế này:

    for (int i = 0; i <100; ++ i) for (int j = 0; j <100; ++ j) DoSingleStuff ();

Và sau một thời gian bạn muốn thêm vào jvòng lặp một số thứ khác và bạn chỉ cần làm điều đó bằng cách căn chỉnh và quên thêm dấu ngoặc.

  • Giải quyết bộ nhớ nhanh hơn. Hãy nói rằng bạn có phạm vi lớn và tạo các mảng lớn bên trong (không có newchúng trong ngăn xếp). Những mảng đó sẽ bị xóa khỏi bộ nhớ ngay sau khi bạn rời khỏi phạm vi. Nhưng có thể bạn sử dụng mảng đó ở một nơi và nó sẽ được xếp chồng lên nhau trong một thời gian và là một loại rác. Vì một ngăn xếp có kích thước hạn chế và khá nhỏ, có thể vượt quá kích thước ngăn xếp. Vì vậy, trong một số trường hợp tốt hơn là viết {}để ngăn chặn điều đó. LƯU Ý đây không phải cho một dòng mà cho các tình huống như vậy:

    if (...) {// someStuff ... {// chúng tôi không có if, while, v.v. // someOtherStuff} // someMoreStuff}

  • Cách thứ ba để sử dụng nó tương tự với thứ hai. Nó không chỉ để làm cho stack sạch hơn mà để mở một số chức năng. Nếu bạn sử dụng mutextrong các chức năng dài thường thì tốt hơn là khóa và mở khóa ngay trước khi truy cập dữ liệu và ngay sau khi đọc xong / ghi nó. LƯU Ý cách này được sử dụng nếu bạn có một số của riêng bạn classhoặc structconstructordestructorđể khóa bộ nhớ.

  • Hơn thế nữa:

    if (...) if (...) someStuff (); other someOtherStuff (); // đi đến lần thứ hai nếu, nhưng phân bổ cho thấy nó ở trên ...

Tất cả trong tất cả, tôi không thể nói, cách tốt nhất để luôn luôn sử dụng {}cho một dòng duy nhất nhưng không có gì xấu khi làm điều đó.

EDIT QUAN TRỌNG Nếu bạn viết biên dịch mã biên dịch cho một dòng duy nhất thì không có gì, nhưng nếu mã của bạn sẽ bị hiểu thì nó làm chậm mã rất ít. Rất nhẹ.


1

Có một số cách có thể để viết các tuyên bố kiểm soát; một số kết hợp nhất định có thể cùng tồn tại mà không làm giảm mức độ dễ đọc, nhưng các kết hợp khác sẽ gây rắc rối. Phong cách

if (condition)
  statement;

sẽ cùng tồn tại thoải mái với một số cách viết câu kiểm soát khác, nhưng không tốt với những cách khác. Nếu báo cáo kiểm soát nhiều dòng được viết là:

if (condition)
{
  statement;
  statement;
}

sau đó sẽ hiển thị rõ ràng ifcâu lệnh nào điều khiển một dòng đơn và câu lệnh nào điều khiển nhiều dòng. Tuy nhiên, nếu các ifcâu lệnh nhiều dòng được viết là:

if (condition) {
  statement;
  statement;
}

thì khả năng ai đó cố gắng mở rộng cấu trúc một câu lệnh ifmà không thêm các dấu ngoặc cần thiết có thể cao hơn nhiều.

Câu lệnh một câu lệnh tiếp theo ifcũng có thể có vấn đề nếu cơ sở mã sử dụng biểu mẫu

if (condition) statement;

Sở thích riêng của tôi là có câu lệnh trên dòng riêng của mình nói chung giúp tăng cường mức độ dễ đọc trừ trường hợp có nhiều ifcâu lệnh có khối điều khiển tương tự, ví dụ:

if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc.

trong trường hợp đó, tôi thường sẽ đi trước và theo dõi các nhóm ifcâu lệnh như vậy với một dòng trống để tách chúng ra khỏi mã khác một cách trực quan. Có một loạt các câu lệnh mà tất cả bắt đầu bằng ifcùng một vết lõm sẽ cung cấp một dấu hiệu trực quan rõ ràng rằng có điều gì đó bất thườ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.