Nhà phát triển khẳng định nếu các câu lệnh không có điều kiện phủ định và phải luôn có một khối khác


169

Tôi có một người quen, một nhà phát triển dày dạn hơn tôi. Chúng tôi đã nói về thực hành lập trình và tôi đã bị bất ngờ bởi cách tiếp cận của anh ấy về các tuyên bố 'nếu'. Ông nhấn mạnh vào một số thực hành liên quan đến việc nếu các tuyên bố mà tôi thấy khá lạ.

Đầu tiên , một câu lệnh if nên được theo sau bởi một câu lệnh khác, cho dù có một cái gì đó để đưa vào nó hay không. Điều này dẫn đến mã trông như thế này:

if(condition) 
{
    doStuff();
    return whatever;
}
else
{
}

Thứ hai , tốt hơn là kiểm tra các giá trị thực chứ không phải sai. Điều đó có nghĩa là tốt hơn hết là kiểm tra biến 'doorCloses' thay vì biến '! DoorOpened'

Lập luận của ông là nó làm cho rõ ràng hơn những gì mã đang làm.

Điều này làm tôi bối rối khá nhiều, vì sự kết hợp của hai quy tắc đó có thể khiến anh ấy viết loại mã này nếu anh ấy muốn làm gì đó khi điều kiện không được đáp ứng.

if(condition)
{
}
else
{
    doStuff();
    return whatever;
}

Cảm giác của tôi về điều này là nó thực sự rất xấu và / hoặc sự cải thiện chất lượng, nếu có, là không đáng kể. Nhưng là một thiếu niên, tôi dễ nghi ngờ bản năng của mình.

Vì vậy, câu hỏi của tôi là: Nó có tốt / xấu / "không quan trọng" không? Có phải là thông lệ?



57
Có thể đồng nghiệp của bạn đến từ một nền tảng mà cuộc sống và chân tay có thể bị đe dọa? Tôi tin rằng tôi đã thấy một vài đề cập đến loại điều này trong các hệ thống trong đó một tấn thử nghiệm và phân tích tự động được thực hiện trên mã và việc hiểu sai có thể khiến ai đó phải trả giá theo nghĩa đen.
jpmc26

11
Hướng dẫn phong cách mã công ty của bạn nói gì?
Thorbjørn Ravn Andersen

4
Trả lời một phần cho câu hỏi "Thứ hai" của bạn. Nếu một trong các khối hành động dài và ngắn khác, hãy kiểm tra một điều kiện đặt khối ngắn trước, để nó ít bị bỏ sót khi đọc mã. Điều kiện đó có thể là một phủ định hoặc đổi tên để biến nó thành một thử nghiệm cho sự thật.
Ethan Bolker

9
Resharper sẽ có một cái gì đó để nói với bạn của bạn, dọc theo dòng "Mã của bạn là dư thừa và vô dụng, bạn có muốn tôi xóa / tái cấu trúc nó không?"
BgrWorker

Câu trả lời:


184

elseKhối rõ ràng

Quy tắc đầu tiên chỉ gây ô nhiễm mã và làm cho nó không dễ đọc hơn, cũng không dễ bị lỗi hơn. Mục tiêu của đồng nghiệp của bạn - tôi cho rằng - là rõ ràng, bằng cách cho thấy rằng nhà phát triển đã nhận thức đầy đủ rằng điều kiện có thể đánh giá false. Mặc dù đó là một điều tốt để được rõ ràng, nhà thám hiểm như vậy không nên trả giá bằng ba dòng mã bổ sung .

Tôi thậm chí không đề cập đến thực tế rằng một iftuyên bố không nhất thiết phải theo sau bởi một elsehoặc không có gì: Nó cũng có thể được theo sau bởi một hoặc nhiều elifs.

Sự hiện diện của returntuyên bố làm cho mọi thứ tồi tệ hơn. Ngay cả khi bạn thực sự có mã để thực thi trong elsekhối, thì sẽ dễ đọc hơn khi làm như thế này:

if (something)
{
    doStuff();
    return whatever;
}

doOtherThings();
return somethingElse;

Điều này làm cho mã mất ít hơn hai dòng và hủy liên kết elsekhối. Điều khoản bảo vệ là tất cả về điều đó.

Tuy nhiên, lưu ý rằng kỹ thuật của đồng nghiệp của bạn có thể giải quyết một phần mô hình rất khó chịu của các khối điều kiện xếp chồng lên nhau không có khoảng trắng:

if (something)
{
}
if (other)
{
}
else
{
}

Trong mã trước, việc thiếu một dòng ngắt lành mạnh sau ifkhối đầu tiên làm cho nó rất dễ hiểu sai mã. Tuy nhiên, trong khi quy tắc của đồng nghiệp của bạn sẽ khiến việc đọc sai mã trở nên khó khăn hơn, một giải pháp dễ dàng hơn là chỉ cần thêm một dòng mới.

Kiểm tra true, không chofalse

Quy tắc thứ hai có thể có ý nghĩa, nhưng không phải ở dạng hiện tại.

Không sai khi thử nghiệm cho một cánh cửa đóng là trực quan hơn so với thử nghiệm cho một cánh cửa không mở . Các phủ định và đặc biệt là các phủ định lồng nhau, thường rất khó hiểu:

if (!this.IsMaster || (!this.Ready && !this.CanWrite))

Để giải quyết điều đó, thay vì thêm các khối trống, hãy tạo các thuộc tính bổ sung, khi có liên quan hoặc các biến cục bộ .

Điều kiện trên có thể được đọc dễ dàng hơn:

if (this.IsSlave || (this.Synchronizing && this.ReadOnly))

169
Khối trống luôn trông giống như sai lầm với tôi. Nó sẽ ngay lập tức thu hút sự chú ý của tôi và tôi sẽ hỏi 'Tại sao lại ở đây?'
Nelson

50
@Neil Việc phủ định một điều kiện không nhất thiết phải có thêm thời gian CPU. C được biên dịch thành x86 chỉ có thể hoán đổi lệnh nhảy từ JZ(Jump if Zero) if (foo)thành JNZ(Jump if Not Zero) cho if (!foo).
8bittree

71
" Tạo các thuộc tính bổ sung với tên rõ ràng ": Trừ khi bạn có phân tích sâu hơn về tên miền, điều này có thể sẽ thay đổi phạm vi toàn cầu (lớp gốc) để giải quyết vấn đề cục bộ (áp dụng quy tắc kinh doanh cụ thể). Một điều có thể được thực hiện ở đây là tạo các booleans cục bộ với trạng thái mong muốn, chẳng hạn như "var isSlave =! This.IsMaster" và áp dụng if của bạn với các booleans cục bộ thay vì tạo một số thuộc tính khác. Vì vậy, hãy đưa ra lời khuyên với một hạt muối và dành chút thời gian để phân tích tên miền trước khi tạo các thuộc tính mới.
Machado

82
Đó không phải là về "ba dòng mã", mà là về cách viết cho đối tượng thích hợp (ai đó có ít nhất một nắm bắt cơ bản về lập trình). Một ifđã rõ ràng về thực tế là tình trạng này có thể là sai, hoặc bạn sẽ không được kiểm tra cho nó. Một khối trống làm cho mọi thứ trở nên ít rõ ràng hơn, không nhiều hơn, bởi vì không có độc giả nào được dự đoán là sẽ mong đợi nó, và bây giờ họ phải dừng lại và suy nghĩ về cách nó có thể đến đó.
hobbs

15
@Mark điều đó ít rõ ràng hơn, ngay cả với nhận xét, hơn là nói if (!commonCase) { handle the uncommon case },.
Paul

62

Về quy tắc đầu tiên, đây là một ví dụ về cách gõ vô dụng. Không chỉ mất nhiều thời gian để gõ, nó sẽ gây ra sự nhầm lẫn lớn cho bất cứ ai đọc mã. Nếu mã không cần thiết, đừng viết nó. Điều này thậm chí sẽ mở rộng để không có dân cư elsetrong trường hợp của bạn khi mã trả về từ ifkhối:

if(condition) 
{
    doStuff();
    return whatever;
}

doSomethingElse(); // no else needed
return somethingelse;

Về điểm thứ hai, thật tốt để tránh các tên boolean có chứa âm:

bool doorNotOpen = false; // a double negative is hard to reason
bool doorClosed = false; // this is much clearer

Tuy nhiên, việc mở rộng điều này để không kiểm tra lại âm tính, như bạn chỉ ra, dẫn đến việc gõ nhiều hơn. Sau đây là rõ ràng hơn nhiều so với có một ifkhối trống :

if(!condition)
{
    doStuff();
    return whatever;
}

120
Vì vậy, ... bạn có thể nói rằng tiêu cực kép là không-không? ;)
Patsuan

6
@Patsuan, rất thông minh. Tôi thích điều đó. :)
David Arno

4
Tôi cảm thấy như không có nhiều người đồng ý về việc nên xử lý các khoản hoàn trả khác như thế nào, và một số người thậm chí có thể nói rằng không nên xử lý như vậy trong mọi tình huống. Bản thân tôi không có sở thích mạnh mẽ, nhưng tôi chắc chắn có thể thấy lập luận đằng sau nhiều cách làm khác.
Dukeling

6
Rõ ràng, if (!doorNotOpen)là khủng khiếp. Vì vậy, tên nên càng tích cực càng tốt. if (! doorClosed)là hoàn toàn tốt
David Schwartz

1
Liên quan đến ví dụ cửa của bạn, hai tên ví dụ không nhất thiết phải tương đương. Trong thế giới vật chất doorNotOpenkhông giống doorClosednhư có một trạng thái chuyển tiếp giữa mở và đóng. Tôi thấy điều này mọi lúc trong các ứng dụng công nghiệp của tôi là các trạng thái boolean được xác định bởi các cảm biến vật lý. Nếu bạn có một cảm biến duy nhất ở phía mở cửa thì bạn chỉ có thể nói cửa đang mở hoặc không mở. Tương tự như vậy nếu cảm biến ở phía đóng, bạn chỉ có thể nói đóng hoặc không đóng. Ngoài ra, cảm biến tự xác định cách xác nhận trạng thái logic.
Peter M

45

1. Một lập luận ủng hộ các elsetuyên bố trống rỗng .

Tôi thường sử dụng (và tranh luận) một cái gì đó giống với cấu trúc đầu tiên đó, một cái khác. Nó báo hiệu cho người đọc mã (cả công cụ phân tích tự động và con người) rằng lập trình viên đã đặt một số suy nghĩ vào tình huống. Những elsetuyên bố mất tích đáng lẽ phải có mặt đã giết người, đâm xe và tiêu tốn hàng triệu đô la. MISRA-C, ví dụ, bắt buộc ít nhất một bình luận nói rằng trận chung kết còn thiếu là cố ý trong một if (condition_1) {do_this;} else if (condition_2) {do_that;} ... else if (condition_n) {do_something_else;}chuỗi. Các tiêu chuẩn có độ tin cậy cao khác còn đi xa hơn: với một vài ngoại lệ, các câu lệnh bị thiếu khác bị cấm.

Một ngoại lệ là một nhận xét đơn giản, một cái gì đó dọc theo dòng /* Else not required */. Điều này báo hiệu cùng một mục đích như ba dòng trống khác. Một ngoại lệ khác không cần thiết phải trống khác là ở chỗ nó rõ ràng rõ ràng đối với cả người đọc mã và các công cụ phân tích tự động mà trống khác không cần thiết. Ví dụ, if (condition) { do_stuff; return; }Tương tự, không cần một sản phẩm nào khác trong trường hợp throw somethinghoặc goto some_label1 thay cho return.

2. Một đối số cho thích if (condition)hơn if (!condition).

Đây là một mục yếu tố con người. Logic boolean phức tạp chuyến đi nhiều người lên. Ngay cả một lập trình viên dày dạn cũng sẽ phải suy nghĩ if (!(complex || (boolean && condition))) do_that; else do_this;. Tối thiểu, viết lại như là if (complex || (boolean && condition)) do_this; else do_that;.

3. Điều này không có nghĩa là người ta nên thích các thenbáo cáo trống .

Phần thứ hai nói "thích" hơn là "ngươi sẽ". Đó là một hướng dẫn chứ không phải là một quy tắc. Lý do cho hướng dẫn đó thích các ifđiều kiện tích cực là mã phải rõ ràng và rõ ràng. Một mệnh đề trống sau đó (ví dụ, if (condition) ; else do_something;) vi phạm điều này. Đó là chương trình bị xáo trộn, khiến ngay cả những lập trình viên dày dạn nhất cũng phải sao lưu và đọc lại ifđiều kiện ở dạng phủ định. Vì vậy, hãy viết nó ở dạng phủ định ở vị trí đầu tiên và bỏ qua câu lệnh khác (hoặc có một khoảng trống khác hoặc nhận xét về hiệu ứng đó nếu bắt buộc phải làm như vậy).



1 Tôi đã viết rằng sau đó các điều khoản kết thúc bằng return, throwhoặc gotokhông yêu cầu một sản phẩm nào khác. Rõ ràng là mệnh đề khác là không cần thiết. Nhưng còn cái gì goto? Bên cạnh đó, các quy tắc lập trình quan trọng về an toàn đôi khi không cho phép quay lại sớm và hầu như luôn không cho phép ném ngoại lệ. Tuy nhiên, chúng cho phép gotoở dạng hạn chế (ví dụ goto cleanup1;:). Việc sử dụng hạn chế gotonày là thực hành ưa thích ở một số nơi. Hạt nhân Linux, chẳng hạn, rất phức tạp với các gotocâu lệnh như vậy .


6
!cũng dễ dàng bị bỏ qua. Nó quá ngắn gọn.
Thorbjørn Ravn Andersen

4
Không, một sản phẩm nào khác không làm rõ hơn rằng lập trình viên đã đặt một số suy nghĩ vào đó. Nó dẫn đến sự nhầm lẫn nhiều hơn "Đã có một số tuyên bố được lên kế hoạch và họ đã quên hoặc tại sao có một điều khoản trống?" Một nhận xét rõ ràng hơn nhiều.
Simon

9
@Simon: Một hình thức điển hình là else { /* Intentionally empty */ }, hoặc một cái gì đó dọc theo những dòng đó. Các sản phẩm khác thỏa mãn bộ phân tích tĩnh mà vô tâm tìm kiếm các vi phạm quy tắc. Các bình luận thông báo cho độc giả rằng sự trống rỗng khác là cố ý. Sau đó, một lần nữa, các lập trình viên dày dạn thường bỏ qua bình luận là không cần thiết - nhưng không phải là trống rỗng. Lập trình độ tin cậy cao là một lĩnh vực của riêng mình. Các cấu trúc trông khá lạ đối với người ngoài. Những cấu trúc này được sử dụng vì những bài học từ trường phái của những cú gõ mạnh, những cú gõ rất khó khi sự sống và cái chết hoặc $$$$$$$$$ đang ở trong tầm tay.
David Hammen

2
Tôi không hiểu làm thế nào trống thì mệnh đề là một điều xấu khi các mệnh đề trống khác không (giả sử chúng ta chọn kiểu đó). Tôi có thể thấyif (target == source) then /* we need to do nothing */ else updateTarget(source)
Bergi

2
@ ThorbjørnRavnAndersen không, notlà một từ khóa trong C ++, cũng như các toán tử bitwise và logic khác. Xem đại diện nhà điều hành thay thế .
Ruslan

31

Tôi sử dụng một nhánh khác trống (và đôi khi là một nhánh if trống) trong các trường hợp rất hiếm: Khi rõ ràng cả phần if và phần khác nên được xử lý bằng cách nào đó, nhưng vì một số lý do không tầm thường, trường hợp này có thể được xử lý Không làm gì cả. Và do đó, bất cứ ai đọc mã với hành động khác cần thiết sẽ ngay lập tức nghi ngờ rằng thiếu một cái gì đó và lãng phí thời gian của họ.

if (condition) {
    // No action needed because ...
} else {
    do_else_action()
}

if (condition) {
    do_if_action()
} else {
    // No action needed because ...
}

Nhưng không:

if (condition) {
    do_if_action()
} else {
    // I was told that an if always should have an else ...
}

5
Điều này. Tôi thấy tuyên bố này if test succeeds -> no action needed -> else -> action neededrất khác với tuyên bố if it applies that the opposite of a test outcome is true then an action is needed. Tôi nhắc về câu nói của Martin Fowler: "bất cứ ai cũng có thể viết mã máy tính có thể hiểu; lập trình viên giỏi viết mã con người có thể hiểu".
Tasos Papastylianou

2
@TasosPapastylianou Đó là gian lận. Ngược lại với 'nếu thử nghiệm thành công -> không cần hành động' là 'nếu thử nghiệm không thành công -> hành động cần thiết'. Bạn đệm nó với khoảng 10 từ.
Jerry B

@JerryB nó phụ thuộc vào "bài kiểm tra". Đôi khi sự phủ định là (về mặt ngôn ngữ) đơn giản, nhưng đôi khi không, và sẽ dẫn đến sự nhầm lẫn. Trong những trường hợp đó, rõ ràng hơn khi nói về "phủ định / ngược lại với kết quả là đúng" và "kết quả không đúng"; tuy nhiên, vấn đề là sẽ còn rõ ràng hơn khi giới thiệu một bước 'trống rỗng' hoặc đảo ngược ý nghĩa của các tên biến. Đây là điển hình trong các tình huống "de morgan": ví dụ if ((missing || corrupt) && !backup) -> skip -> else do stuffrõ ràng hơn (+ ý định ngụ ý khác nhau) so vớiif ((!missing && !corrupt) || backup) -> do stuff
Tasos Papastylianou

1
@TasosPapastylianou Bạn có thể viết if(!((missing || corrupt) && !backup)) -> do stuffhoặc tốt hơn if((aviable && valid) || backup) -> do stuff. (Nhưng trong một ví dụ thực tế, bạn phải kiểm tra xem bản sao lưu có hợp lệ không trước khi sử dụng nó)
12431234123412341234123

2
@TasosPapastylianou ở đâu có hai tiêu cực trong những gì tôi nói? Chắc chắn, nếu bạn đang sử dụng uncorrupted như một tên biến sau đó bạn sẽ có một điểm rõ ràng. Mặc dù khi bạn đến với các toán tử boolean hỗn hợp như thế này, thể tốt hơn là có các biến tạm thời để sử dụng trong if: file_ok = !missing && !corrupt; if(file_ok || backup) do_stuff();mà cá nhân tôi cảm thấy làm cho nó thậm chí còn rõ ràng hơn.
Baldrickk

23

Tất cả những thứ khác đều bình đẳng, thích sự ngắn gọn.

Những gì bạn không viết, không ai phải đọc và hiểu.

Mặc dù rõ ràng có thể hữu ích, nhưng đó chỉ là trường hợp nếu nó rõ ràng mà không có sự dài dòng quá mức rằng những gì bạn viết thực sự là những gì bạn muốn viết.


Vì vậy, tránh các nhánh trống, chúng không chỉ vô dụng mà còn không phổ biến và do đó dẫn đến nhầm lẫn.

Ngoài ra, tránh viết một nhánh khác nếu bạn thoát trực tiếp ra khỏi nhánh if.

Một ứng dụng hữu ích của nhân chứng sẽ đưa ra một nhận xét bất cứ khi nào bạn rơi vào các trường hợp chuyển đổi // FALLTHRUvà sử dụng một nhận xét hoặc một khối trống trong đó bạn cần một tuyên bố trống for(a;b;c) /**/;.


7
// FALLTHRUUgh, không phải trong SCREAMING CAPS hoặc với chữ viết tắt txtspk. Nếu không, tôi đồng ý và làm theo thực hành này bản thân mình.
Cody Grey

7
@CodyGray - Đây là một nơi mà KHÔNG NÊN là một ý tưởng tốt. Hầu hết các báo cáo chuyển đổi kết thúc mỗi trường hợp với break. Thất bại có được điều đó breakđã dẫn đến một số lỗi phần mềm ngoạn mục. Một bình luận hét lên với độc giả về mã mà sự cố ý là một điều tốt. Các công cụ phân tích tĩnh đó có thể tìm kiếm mô hình cụ thể này và không phàn nàn về tình huống rơi vào tình huống khiến nó trở thành một điều thậm chí còn tốt hơn.
David Hammen

1
@Wossname: Tôi vừa kiểm tra vim, và nó không nhận ra bất kỳ biến thể nào của FALLTHRU. Và tôi nghĩ, vì lý do chính đáng: TODOvà các FIXMEbình luận là những bình luận cần phải được chỉnh sửa bởi vì bạn muốn có thể hỏi git grepnhững lỗi đã biết của bạn trong mã của bạn và vị trí của chúng. Một sự sụp đổ không phải là một khiếm khuyết, và không có lý do gì để grep cho nó, những gì đã từng như vậy.
cmaster

4
@DavidHammen Không, la hét không phải là một ý tưởng hay: Nếu bạn có // dự phòng thay cho kỳ nghỉ dự kiến;, điều đó là đủ để bất kỳ nhà phát triển nào hiểu ngay lập tức. Ngoài ra, // fallential không nói về một khiếm khuyết, nó chỉ đơn giản là báo hiệu rằng các trường hợp đã được xem xét và sự phá vỡ; mà dường như là mất tích, thực sự là dự định không có ở đó. Đây là một mục đích thông tin hoàn toàn, và không có gì để nói về.
cmaster

1
@cmaster - Tiếng hét đó không phải là ý kiến ​​hay là ý kiến của bạn . Ý kiến ​​của người khác. Và chỉ vì bạn cấu hình của vim không công nhận FALLTHRUkhông có nghĩa là những người khác đã không được cấu hình vim để làm như vậy.
David Hammen

6

Không có quy tắc cứng và nhanh về các điều kiện tích cực hoặc tiêu cực cho một tuyên bố IF, không phải theo hiểu biết của tôi. Cá nhân tôi thích mã hóa cho một trường hợp tích cực hơn là tiêu cực, khi áp dụng. Tôi chắc chắn sẽ không làm điều này mặc dù, nếu nó dẫn tôi tạo một khối IF trống, theo sau là một ELSE đầy logic. Nếu một tình huống như vậy phát sinh, sẽ mất khoảng 3 giây để cấu trúc lại nó để kiểm tra trường hợp dương tính.

Điều tôi thực sự không thích về các ví dụ của bạn là không gian dọc hoàn toàn không cần thiết được lấy bởi ELSE trống. Đơn giản là không có lý do gì để làm điều này. Nó không thêm bất cứ điều gì vào logic, nó không giúp ghi lại những gì mã đang làm và nó không làm tăng khả năng đọc. Trong thực tế, tôi sẽ lập luận rằng không gian dọc được thêm vào có thể làm giảm khả năng đọc.


2
Không gian dọc không cần thiết đó là hệ quả của các nhiệm vụ vì luôn luôn sử dụng niềng răng, vì không cho phép người khác bị mất và sử dụng kiểu Allman để thụt lề.
David Hammen

Chà, tôi sẽ nghĩ rằng luôn luôn sử dụng niềng răng là một điều tốt, bởi vì nó làm cho ý định của các nhà phát triển rõ ràng - không có chỗ cho phỏng đoán khi đọc mã của họ. Điều đó nghe có vẻ ngớ ngẩn, nhưng tôi tin tưởng, đặc biệt là khi tư vấn cho các nhà phát triển cơ sở, những sai lầm liên quan đến mất niềng răng xảy ra. Cá nhân tôi chưa bao giờ là một fan hâm mộ của thụt kiểu Allman, vì chính xác lý do bạn đề cập: không gian dọc không cần thiết. Nhưng đó chỉ là quan điểm của tôi và phong cách cá nhân. Đó chỉ là những gì tôi học được ban đầu, vì vậy nó luôn cảm thấy thoải mái hơn khi đọc.
Langecrew

Phong cách cá nhân: bất kể tôi sử dụng kiểu niềng răng nào (Allman, 1TB, ...), tôi luôn sử dụng niềng răng.
David Hammen

Những điều kiện phức tạp hoặc những điều bạn nghĩ có thể khó hiểu hầu như luôn có thể được giải quyết bằng cách tái hiện thành các phương thức / chức năng của riêng họ. Một cái gì đó như thế này if(hasWriteAccess(user,file))có thể phức tạp bên dưới, nhưng trong nháy mắt bạn biết chính xác kết quả sẽ như thế nào. Ngay cả khi đó chỉ là một vài điều kiện, ví dụ như if(user.hasPermission() && !file.isLocked())một cuộc gọi phương thức với một tên thích hợp sẽ được giải quyết, do đó, tiêu cực trở thành vấn đề ít hơn.
Chris Schneider

Đã đồng ý. Sau đó, một lần nữa, tôi là một fan hâm mộ lớn của tái cấu trúc bất cứ khi nào có thể :)
Langecrew

5

elseKhối rõ ràng

Tôi không đồng ý với điều này như một tuyên bố về chăn bao gồm tất cả các iftuyên bố nhưng có những lúc việc thêm một elsekhối theo thói quen là một điều tốt.

Một iftuyên bố, theo tôi, thực sự bao gồm hai chức năng riêng biệt.

Nếu chúng ta phải làm một cái gì đó, hãy làm nó ở đây.

Những thứ như thế này rõ ràng không cần một elsephần.

    if (customer.hasCataracts()) {
        appointmentSuggestions.add(new CataractAppointment(customer));
    }
    if (customer.isDiabetic()) {
        customer.assignNurse(DiabeticNurses.pickBestFor(customer));
    }

và trong một số trường hợp khăng khăng thêm vào một sai elselầm có thể.

    if (k > n) {
        return BigInteger.ZERO;
    }
    if (k <= 0 || k == n) {
        return BigInteger.ONE;
    }

không giống như

    if (k > n) {
        return BigInteger.ZERO;
    } else {
        if (k <= 0 || k == n) {
            return BigInteger.ONE;
        }
    }

mặc dù nó là chức năng như nhau. Viết cái đầu tiên ifvới một khoảng trống elsecó thể dẫn bạn đến kết quả thứ hai xấu xí không cần thiết.

Nếu chúng tôi đang kiểm tra một trạng thái cụ thể, thường là một ý tưởng tốt để thêm một khoảng trống elsechỉ để nhắc nhở bạn bao quát sự kiện đó

        // Count wins/losses.
        if (doors[firstChoice] == Prize.Car) {
            // We would have won without switching!
            winWhenNotSwitched += 1;
        } else {
            // We win if we switched to the car!
            if (doors[secondChoice] == Prize.Car) {
                // We picked right!
                winWhenSwitched += 1;
            } else {
                // Bad choice.
                lost += 1;
            }
        }

Hãy nhớ rằng các quy tắc này chỉ áp dụng khi bạn đang viết mã mới . IMHO Các elsemệnh đề trống cần được loại bỏ trước khi đăng ký.


Kiểm tra true, không chofalse

Một lần nữa, đây là lời khuyên tốt ở cấp độ chung nhưng trong nhiều trường hợp, điều này làm cho mã phức tạp không cần thiết và ít đọc hơn.

Mặc dù mã như

    if(!customer.canBuyAlcohol()) {
        // ...
    }

gây xúc động cho người đọc, nhưng làm cho nó

    if(customer.canBuyAlcohol()) {
        // Do nothing.
    } else {
        // ...
    }

ít nhất là xấu, nếu không nói là tồi tệ hơn.

Tôi đã mã hóa trong BCPL nhiều năm trước và trong ngôn ngữ đó có một IFmệnh đề một UNLESSmệnh đề để bạn có thể viết mã dễ đọc hơn nhiều như:

    unless(customer.canBuyAlcohol()) {
        // ...
    }

đó là tốt hơn đáng kể, nhưng vẫn không hoàn hảo.


Quá trình cá nhân của tôi

Nói chung, khi tôi viết mã mới, tôi sẽ thường thêm một elsekhối trống vào một ifcâu lệnh chỉ để nhắc nhở tôi rằng tôi chưa bao gồm sự kiện đó. Điều này giúp tôi tránh DFSbẫy và đảm bảo rằng khi tôi xem lại mã tôi nhận thấy rằng có nhiều việc phải làm. Tuy nhiên, tôi thường thêm một TODObình luận để theo dõi.

  if (returnVal == JFileChooser.APPROVE_OPTION) {
    handleFileChosen();
  } else {
    // TODO: Handle case where they pressed Cancel.
  }

Tôi thấy rằng nói chung tôi elsehiếm khi sử dụng mã của mình vì nó thường có thể chỉ ra mùi mã.


Xử lý của bạn của khối "trống rỗng" đọc như loại bỏ dần từng mã tiêu chuẩn khi thực hiện thngs ...
Deduplicator

Các unlesskhối là một gợi ý tốt, và hầu như không giới hạn BCPL.
tchrist

@TobySpeight Rất tiếc. Nó bằng cách nào đó đã thoát khỏi sự chú ý của tôi rằng những gì tôi đã viết ...là thực sự return. (Trên thực tế, với k=-1, n=-2lần trở lại đầu tiên được thực hiện nhưng phần lớn là tranh luận.)
David Richerby

Perl hiện đại có ifunless. Hơn nữa, nó cũng cho phép những điều này sau một tuyên bố, vì vậy bạn có thể nói print "Hello world!" if $born;hoặc print "Hello world!" unless $dead;cả hai sẽ làm chính xác những gì bạn nghĩ họ làm.
CVn

1

Đối với điểm đầu tiên, tôi đã sử dụng một ngôn ngữ buộc các câu lệnh IF được sử dụng theo cách này (trong Opal, ngôn ngữ đằng sau trình quét màn hình máy tính lớn để đặt giao diện người dùng GUI vào các hệ thống máy tính lớn) và chỉ có một dòng cho IF và ELSE. Đó không phải là một trải nghiệm thú vị!

Tôi hy vọng bất kỳ trình biên dịch nào sẽ tối ưu hóa các mệnh đề ELSE bổ sung như vậy. Nhưng đối với mã trực tiếp, nó không thêm bất cứ thứ gì (trong quá trình phát triển, nó có thể là một điểm đánh dấu hữu ích cho mã tiếp theo).

Một lần tôi sử dụng một cái gì đó giống như các mệnh đề bổ sung này là khi sử dụng kiểu xử lý CASE / WHEN. Tôi luôn thêm một mệnh đề mặc định ngay cả khi nó trống. Đây là thói quen lâu dài từ các ngôn ngữ sẽ có lỗi nếu một điều khoản như vậy không được sử dụng và buộc phải suy nghĩ liệu mọi thứ có thực sự nên bỏ qua hay không.

Từ lâu, máy tính lớn (ví dụ, PL / 1 và COBOL) đã chấp nhận rằng kiểm tra âm tính kém hiệu quả hơn. Điều này có thể giải thích điểm thứ 2, mặc dù những ngày này có những khoản tiết kiệm hiệu quả quan trọng hơn được coi là tối ưu hóa vi mô.

Logic tiêu cực có xu hướng ít đọc hơn, mặc dù không quá nhiều trên một câu lệnh IF đơn giản như vậy.


2
Tôi thấy, vì vậy nó đã được thực hành phổ biến tại một số điểm.
Patsuan

@Patsuan - vâng, ở một mức độ nào đó. Mặc dù đó là khi trình biên dịch chương trình máy tính lớn là một sự thay thế nghiêm trọng cho mã quan trọng về hiệu năng.
Khởi động

1
"Từ lâu ... đã chấp nhận rằng kiểm tra tiêu cực kém hiệu quả hơn." Vâng, họ trừ khi trình biên dịch tối ưu hóa if !A then B; else Cđể if A then C; else B. Tính toán phủ định điều kiện là một lệnh CPU bổ sung. (Mặc dù như bạn nói, điều này dường như không hiệu quả hơn đáng kể .)
David Richerby

@DavidR Richby Và nếu chúng ta đang nói chung, một số ngôn ngữ có thể làm cho việc tối ưu hóa như vậy thực sự khó khăn. Lấy C ++ với quá tải !==toán tử, ví dụ. Không phải ai cũng thực sự làm điều đó , làm phiền bạn ...
một CVn

1

Tôi sẽ đứng thứ hai trong hầu hết các câu trả lời rằng elsecác khối trống hầu như luôn là một sự lãng phí mực in điện tử. Đừng thêm những thứ này trừ khi bạn có một lý do rất chính đáng để làm như vậy, trong trường hợp đó, khối trống hoàn toàn không nên trống, nó sẽ chứa một bình luận giải thích tại sao nó ở đó.

Vấn đề về việc tránh tiêu cực đáng được chú ý hơn mặc dù: Thông thường, bất cứ khi nào bạn cần sử dụng giá trị boolean, bạn cần một số khối mã để hoạt động khi được đặt và một số khối khác hoạt động khi không được đặt. Như vậy, nếu bạn thực thi quy tắc không phủ định, bạn thực thi hoặc có các if() {} else {...}câu lệnh (với một ifkhối trống !) Hoặc bạn tạo một boolean thứ hai cho mỗi boolean chứa giá trị phủ định của nó. Cả hai tùy chọn đều xấu, vì chúng gây nhầm lẫn cho độc giả của bạn.

Một chính sách hữu ích là thế này: Không bao giờ sử dụng một hình thức phủ định trong tên của boolean và thể hiện phủ định dưới dạng một !. Một tuyên bố như if(!doorLocked)là hoàn toàn rõ ràng, một tuyên bố như if(!doorUnlocked)nút thắt não. Loại biểu thức sau này là điều bạn cần tránh bằng mọi giá, không phải là sự hiện diện của một !từ trong một if()điều kiện.


0

Tôi sẽ nói rằng đó chắc chắn là thực hành xấu. Thêm các câu lệnh khác sẽ thêm một loạt các dòng vô nghĩa vào mã của bạn mà không làm gì cả và làm cho nó trở nên khó đọc hơn.


1
Tôi nghe nói bạn chưa bao giờ làm việc cho một nhà tuyển dụng chấm điểm hiệu suất của bạn dựa trên SLOC được sản xuất mỗi kỳ ...
CVn

0

Có một mô hình khác chưa được đề cập về việc xử lý trường hợp thứ hai có khối if trống. Một cách để đối phó với nó sẽ là quay trở lại trong khối if. Như thế này:

void foo()
{
    if (condition) return;

    doStuff();
    ...
}

Đây là một mẫu phổ biến để kiểm tra các điều kiện lỗi. Trường hợp khẳng định vẫn được sử dụng, và bên trong của nó không còn trống để các lập trình viên tương lai tự hỏi liệu một no-op có cố ý hay không. Nó cũng có thể cải thiện khả năng đọc vì bạn có thể được yêu cầu tạo một chức năng mới (do đó phá vỡ các chức năng lớn thành các chức năng nhỏ hơn).


0

Có một điểm khi xem xét đối số "luôn luôn có một mệnh đề khác" mà tôi chưa thấy trong bất kỳ câu trả lời nào khác: nó có thể có ý nghĩa trong một phong cách lập trình chức năng. Sắp xếp

Trong một phong cách lập trình chức năng, bạn xử lý các biểu thức hơn là các câu lệnh. Vì vậy, mỗi khối mã có một giá trị trả về - bao gồm một if-then-elsebiểu thức. Tuy nhiên, điều đó sẽ ngăn chặn một elsekhối trống . Hãy để tôi cho một ví dụ của bạn:

var even = if (n % 2 == 0) {
  return "even";
} else {
  return "odd";
}

Bây giờ, trong các ngôn ngữ có cú pháp lấy cảm hứng từ kiểu C hoặc kiểu C (như Java, C # và JavaScript, chỉ để đặt tên cho một số), điều này có vẻ kỳ lạ. Tuy nhiên, nó trông quen thuộc hơn nhiều khi được viết như vậy:

var even = (n % 2 == 0) ? "even" : "odd";

Để elsetrống nhánh ở đây sẽ khiến giá trị không được xác định - trong hầu hết các trường hợp, không phải là trường hợp chúng ta muốn là trường hợp hợp lệ khi lập trình chức năng. Tương tự với việc bỏ nó hoàn toàn. Tuy nhiên, khi bạn lập trình lặp đi lặp lại, tôi đặt rất ít lý do để luôn có một elsekhối.


0

... một câu lệnh if nên được theo sau bởi một câu lệnh khác, cho dù có một cái gì đó để đưa vào nó hay không.

Tôi không đồng ý if (a) { } else { b(); }nên viết lại if (!a) { b(); }if (a) { b(); } else { }nên viết lại thành if (a) { b(); }.

Tuy nhiên, điều đáng nói là tôi hiếm khi có một chi nhánh trống. Điều này là do thông thường tôi đăng nhập rằng tôi đã đi vào chi nhánh trống. Bằng cách này tôi có thể phát triển hoàn toàn các thông điệp tường trình. Tôi hiếm khi sử dụng một trình sửa lỗi. Trong môi trường sản xuất, bạn không nhận được trình gỡ lỗi; thật tuyệt khi có thể khắc phục sự cố sản xuất bằng chính các công cụ mà bạn sử dụng để phát triển.

Thứ hai, tốt hơn là kiểm tra các giá trị thực chứ không phải sai. Điều đó có nghĩa là tốt hơn hết là kiểm tra biến 'doorCloses' thay vì biến '! DoorOpened'

Tôi có cảm xúc lẫn lộn về việc này. Một bất lợi khi có doorCloseddoorOpenednó có khả năng nhân đôi số lượng từ / thuật ngữ bạn cần phải biết. Một nhược điểm khác là theo thời gian ý nghĩa của doorCloseddoorOpenedcó thể thay đổi (các nhà phát triển khác đến sau bạn) và bạn có thể kết thúc với hai thuộc tính không còn là phủ định chính xác của nhau. Thay vì tránh phủ định, tôi đánh giá cao việc điều chỉnh ngôn ngữ của mã (tên lớp, tên biến, v.v.) cho vốn từ vựng của người dùng doanh nghiệp và các yêu cầu tôi đưa ra. Tôi sẽ không muốn tạo ra một thuật ngữ hoàn toàn mới để tránh!nếu thuật ngữ đó chỉ có ý nghĩa đối với nhà phát triển mà người dùng doanh nghiệp sẽ không hiểu. Tôi muốn các nhà phát triển nói cùng ngôn ngữ với người dùng và những người viết yêu cầu. Bây giờ nếu các điều khoản mới đơn giản hóa mô hình, thì điều đó quan trọng, nhưng điều đó nên được xử lý trước khi các yêu cầu được hoàn thành, không phải sau đó. Phát triển nên xử lý vấn đề này trước khi họ bắt đầu mã hóa.

Tôi dễ nghi ngờ bản năng của mình.

Thật tốt khi tiếp tục đặt câu hỏi cho chính mình.

Đây có phải là một thực hành tốt / xấu / "không quan trọng"?

Ở một mức độ nào đó rất nhiều điều này không quan trọng. Nhận mã của bạn được xem xét và thích ứng với nhóm của bạn. Đó thường là điều đúng đắn để làm. Nếu mọi người trong nhóm của bạn muốn viết mã theo một cách nhất định, có lẽ tốt nhất là làm theo cách đó (thay đổi 1 người - chính bạn - mất ít điểm câu chuyện hơn là thay đổi một nhóm người).

Có phải là thông lệ?

Tôi không thể nhớ đã từng thấy một nhánh trống. Tôi thấy mọi người thêm thuộc tính để tránh tiêu cực, mặc dù.


0

Tôi sẽ chỉ giải quyết phần thứ hai của câu hỏi và điều này xuất phát từ quan điểm của tôi về làm việc trong các hệ thống công nghiệp và các thiết bị thao tác trong thế giới thực.

Tuy nhiên, đây là một nhận xét mở rộng hơn là một câu trả lời.

Khi nó được nêu

Thứ hai, tốt hơn là kiểm tra các giá trị thực chứ không phải sai. Điều đó có nghĩa là tốt hơn hết là kiểm tra biến 'doorCloses' thay vì biến '! DoorOpened'

Lập luận của ông là nó làm cho rõ ràng hơn những gì mã đang làm.

Theo quan điểm của tôi, giả định đang được đưa ra ở đây rằng trạng thái cửa là trạng thái nhị phân trong khi thực tế nó ít nhất là trạng thái ternary:

  1. Cánh cửa đang mở.
  2. Cánh cửa không mở hoàn toàn cũng không đóng hoàn toàn.
  3. Cánh cửa được đóng.

Do đó, tùy thuộc vào vị trí của một cảm biến kỹ thuật số nhị phân đơn, bạn chỉ có thể phỏng đoán một trong hai khái niệm:

  1. Nếu cảm biến ở phía mở cửa: Cửa mở hoặc không mở
  2. Nếu cảm biến ở phía đóng của cửa: Cửa đóng hoặc không đóng.

Và bạn không thể trao đổi các khái niệm này thông qua việc sử dụng phủ định nhị phân. Do đó doorClosed!doorOpenedcó khả năng không đồng nghĩa và bất kỳ nỗ lực nào để giả vờ rằng chúng đồng nghĩa là suy nghĩ sai lầm giả định kiến ​​thức lớn hơn về trạng thái hệ thống so với thực tế tồn tại.

Khi trở lại câu hỏi của bạn, tôi sẽ hỗ trợ verbiage phù hợp với nguồn gốc của thông tin mà biến đại diện. Do đó, nếu thông tin được lấy từ phía đóng của cửa thì hãy đi với doorClosedvv Điều này có thể ngụ ý sử dụng một trong hai doorClosedhoặc !doorClosedkhi cần thiết trong các tuyên bố khác nhau theo yêu cầu, dẫn đến sự nhầm lẫn tiềm ẩn với việc sử dụng phủ định. Nhưng những gì nó không làm là ngầm tuyên truyền các giả định về trạng thái của hệ thống.


Đối với mục đích thảo luận cho công việc tôi làm, lượng thông tin tôi có sẵn về trạng thái của hệ thống phụ thuộc vào chức năng được yêu cầu bởi chính hệ thống.

Đôi khi tôi chỉ cần biết cửa đã đóng hay chưa đóng. Trong trường hợp đó chỉ có một cảm biến nhị phân duy nhất là đủ. Nhưng vào những lúc khác, tôi cần phải biết rằng cánh cửa đang mở, đóng hoặc đang trong quá trình chuyển đổi. Trong những trường hợp như vậy, sẽ có hai cảm biến nhị phân (ở mỗi cực của chuyển động cửa - với việc kiểm tra lỗi đối với cửa được mở và đóng đồng thời) hoặc sẽ có một cảm biến tương tự đo mức độ 'mở' của cửa.

Một ví dụ đầu tiên sẽ là cánh cửa trên lò vi sóng. Bạn kích hoạt hoạt động của lò dựa trên cửa bị đóng hoặc không đóng. Bạn không quan tâm làm thế nào mở cửa.

Một ví dụ về thứ hai sẽ là một bộ truyền động điều khiển động cơ đơn giản. Bạn ức chế lái xe máy về phía trước khi bộ truyền động hết. Và bạn ức chế lái xe máy ngược lại khi bộ truyền động hoàn toàn vào.

Về cơ bản, số lượng và loại cảm biến được đưa vào để chạy phân tích chi phí của các cảm biến dựa trên phân tích yêu cầu về những gì cần thiết để đạt được chức năng cần thiết.


"Theo quan điểm của tôi, giả định đang được đưa ra ở đây rằng trạng thái cửa là trạng thái nhị phân trong khi thực tế nó ít nhất là trạng thái tạm thời:""Bạn cho phép hoạt động của lò dựa trên cửa bị đóng hoặc không đóng Bạn không quan tâm làm thế nào mở cửa được. " mâu thuẫn với nhau. Ngoài ra, tôi không nghĩ câu hỏi là về cửa và cảm biến.
Sanchise

@Sanchises Các tuyên bố không mâu thuẫn vì bạn đã đưa chúng ra khỏi bối cảnh. Các báo cáo đầu tiên là trong bối cảnh hai phép đo nhị phân đối lập của một trạng thái không thể thay thế được thông qua phủ định nhị phân. Tuyên bố thứ hai là trong bối cảnh chỉ có kiến ​​thức một phần về trạng thái ternary có thể đủ để một ứng dụng hoạt động chính xác. Đối với các cửa, câu hỏi OP tham khảo các cửa được mở và đóng. Tôi chỉ nêu ra một giả định rõ ràng có thể ảnh hưởng đến cách xử lý dữ liệu.
Peter M

-1

Quy tắc phong cách cụ thể luôn luôn là xấu. Nguyên tắc là tốt, nhưng cuối cùng thường có trường hợp bạn có thể làm cho mọi thứ rõ ràng hơn bằng cách làm một cái gì đó không hoàn toàn theo hướng dẫn.

Điều đó nói rằng, có rất nhiều sự ghét bỏ đối với cái trống khác "nó lãng phí các dòng" "gõ thêm", điều đó thật tệ. Bạn có thể đưa ra đối số để di chuyển dấu ngoặc trên cùng một dòng nếu bạn thực sự cần không gian dọc, nhưng nếu đó là vấn đề bạn không nên đặt {trên một dòng riêng biệt.

Như đã đề cập ở nơi khác, có khối khác rất hữu ích để cho thấy rằng bạn rõ ràng không muốn điều gì xảy ra trong trường hợp khác. Sau khi thực hiện nhiều chương trình chức năng (nơi khác là bắt buộc), tôi đã học được rằng bạn nên luôn luôn cân nhắc điều khác khi viết if, mặc dù như @OldCurmudgeon đề cập thực sự có hai trường hợp sử dụng khác nhau. Người ta nên có một người khác, một người không nên. Đáng buồn thay, đó không phải là điều mà bạn luôn có thể nói trong nháy mắt, hãy để một mình với kẻ nói dối, do đó giáo điều 'luôn đặt một khối khác'.

Đối với 'không có tiêu cực', một lần nữa, các quy tắc tuyệt đối là xấu. Có một khoảng trống nếu có thể là lạ, đặc biệt nếu đó là loại nếu không cần người khác, vì vậy hãy viết tất cả những thứ đó chứ không phải là a! hoặc an == false là xấu. Điều đó nói rằng, có rất nhiều trường hợp tiêu cực có ý nghĩa. Một ví dụ phổ biến sẽ lưu vào bộ đệm một giá trị:

static var cached = null

func getCached() {
    if !cached {
        cached = (some calculation, etc)
    }

    return cached
}

nếu logic thực tế (tinh thần / tiếng Anh) liên quan đến một phủ định, thì câu lệnh if cũng vậy.

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.