Là `break` và` continue` thực hành lập trình xấu?


191

Ông chủ của tôi liên tục đề cập đến việc các lập trình viên xấu sử dụng breakcontinuetrong các vòng lặp.

Tôi sử dụng chúng mọi lúc vì chúng có ý nghĩa; Hãy để tôi chỉ cho bạn cảm hứng:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

Vấn đề ở đây là đầu tiên hàm kiểm tra các điều kiện là chính xác, sau đó thực thi chức năng thực tế. IMO tương tự áp dụng với các vòng lặp:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

Tôi nghĩ rằng điều này làm cho nó dễ đọc & gỡ lỗi hơn; nhưng tôi cũng không thấy nhược điểm.


2
Không mất nhiều để quên cái nào làm cái gì.

57
Không. Không phải là goto. Biết khi nào nên sử dụng chúng là chìa khóa. Chúng là các công cụ trong hộp công cụ. Bạn sử dụng chúng khi họ cung cấp mã rõ ràng và cô đọng.
orj

67
Tôi không thể nói lên sự hỗ trợ của mình cho phong cách mã hóa này đủ mạnh. Nhiều cấp độ của các điều kiện lồng nhau tồi tệ hơn nhiều so với phương pháp này. Tôi thường không chiến đấu về phong cách mã hóa, nhưng đây gần như là một công cụ đối phó với tôi.
Emil H

9
Rõ ràng ông chủ của bạn không viết mã (đủ). Nếu anh ta làm thế, anh ta sẽ biết rằng tất cả các từ khóa (có, thậm chí goto) đều hữu ích trong một số trường hợp.
sakisk

57
Lập trình viên xấu sử dụng break và tiếp tục không có nghĩa là lập trình viên giỏi thì không. Lập trình viên xấu sử dụng nếu và trong khi là tốt.
mouviciel

Câu trả lời:


240

Khi được sử dụng khi bắt đầu một khối, khi kiểm tra lần đầu tiên được thực hiện, chúng hoạt động như các điều kiện tiên quyết, vì vậy nó rất tốt.

Khi được sử dụng ở giữa khối, với một số mã xung quanh, chúng hoạt động như các bẫy ẩn, vì vậy nó rất tệ.


6
@Klaim: Có thể lập luận rằng bất kỳ thói quen nào có một vài điểm thoát là một thói quen kém. Một thói quen đúng đắn nên làm một việc và một điều duy nhất.
bit-twiddler

79
@ bit-twiddler: đây là một suy nghĩ rất C-ish. Một biến tạm thời có thể được sửa đổi sau này, do đó, một lỗi đánh máy 20 dòng từ đây có thể xóa kết quả được tạo cẩn thận đó. Tuy nhiên, một sự trở lại ngay lập tức (hoặc phá vỡ hoặc tiếp tục) là vô cùng rõ ràng: Tôi có thể ngừng đọc ngay bây giờ vì tôi biết nó không thể được sửa đổi thêm . Nó tốt cho bộ não nhỏ của tôi, thực sự làm cho việc chuyển qua mã dễ dàng hơn.
Matthieu M.

15
@Matthieu Tôi đồng ý. Thoát khỏi một khối khi bạn nhận được một kết quả thỏa mãn mục đích của khối.
Evan Plaice

8
@ bit-twiddler - nguyên tắc một điểm thoát duy nhất tách biệt với nguyên tắc trách nhiệm đơn. Tôi không đồng ý rằng việc kiểm tra luôn tách biệt với việc thực hiện trách nhiệm của WRT. Trong thực tế, "trách nhiệm duy nhất" luôn luôn đánh tôi như một thuật ngữ chủ quan. Ví dụ, trong một người giải quyết bậc hai, việc tính toán phân biệt đối xử có phải là một trách nhiệm riêng biệt không? Hoặc toàn bộ công thức bậc hai có thể là một trách nhiệm duy nhất? Tôi cho rằng điều đó phụ thuộc vào việc bạn có sử dụng riêng cho người phân biệt đối xử hay không - nếu không, coi đó là trách nhiệm riêng biệt có lẽ là quá mức.
Steve314

10
Câu trả lời đó là một quy tắc của ngón tay cái, không phải là một quy tắc cứng. Nó hoạt động trong hầu hết các trường hợp, hãy thoải mái phá vỡ nó nếu nó có ý nghĩa trong bối cảnh của bạn.
Klaim

87

Bạn có thể đọc Lập trình cấu trúc trên giấy năm 1974 của Donald Knuth với các Tuyên bố , trong đó ông thảo luận về các cách sử dụng khác nhau go tomà theo cấu trúc mong muốn. Chúng bao gồm tương đương breakvà các continuecâu lệnh (nhiều cách sử dụng go totrong đó đã được phát triển thành các cấu trúc hạn chế hơn). Có phải ông chủ của bạn là kiểu người gọi Knuth là một lập trình viên tồi?

(Các ví dụ được đưa lãi suất cho tôi. Thông thường, breakcontinueđang không thích bởi những người thích một bài dự thi và một lối ra từ bất kỳ đoạn mã, và đó là loại người cũng cau mày trên nhiều returnbáo cáo.)


8
Hầu hết những người thích các chức năng và thủ tục để có các điểm vào và thoát đơn lẻ đã lớn lên trên Pascal. Pascal không phải là ngôn ngữ đầu tiên mà tôi học được, nhưng nó có tác động sâu sắc đến cách tôi cấu trúc mã cho đến ngày nay. Mọi người luôn nhận xét về việc đọc mã Java của tôi dễ dàng như thế nào. Đó là bởi vì tôi tránh được nhiều điểm thoát cũng như khai báo xen kẽ với mã. Tôi cố gắng hết sức để khai báo mọi biến cục bộ được sử dụng trong một phương thức ở đầu phương thức. Cách thực hành này tránh mã lan man bằng cách buộc tôi giữ các phương thức cô đọng.
bit-twiddler

5
Pascal cũng có các hàm lồng nhau. Chỉ cần nói ...
Shog9

6
Từ những gì tôi nhớ, hồi trước, lý do chính khiến mọi người không thích nhiều câu trả về trong các hàm là vì trình gỡ lỗi không xử lý chúng đúng cách. Thật là một nỗi đau thực sự khi đặt một điểm dừng ở cuối hàm nhưng bạn không bao giờ nhấn nó vì một tuyên bố trả về trước đó. Đối với mọi trình biên dịch tôi sử dụng ngày nay không còn là vấn đề nữa.
Dunk

28
@ bit-twiddler: Tôi không chắc lắm về điều đó. Tôi vẫn đang sử dụng Pascal ngày hôm nay và tôi thường coi "lối ra một lần duy nhất", hoặc ít nhất là phần thoát đơn của nó, như lập trình sùng bái hàng hóa. Tôi chỉ xem xét Break, ContinueExitnhư các công cụ trong hộp công cụ của tôi; Tôi sử dụng chúng ở nơi nó làm cho mã dễ theo dõi hơn và không sử dụng chúng ở nơi nó sẽ khiến mọi thứ khó đọc hơn.
Mason Wheeler

2
@ bit-twiddler: amen đó. Tôi cũng sẽ nói thêm rằng một khi bạn xuống các khối dễ dàng phù hợp trên màn hình, nhiều điểm thoát trở nên ít rắc rối hơn.
Shog9

44

Tôi không tin họ là xấu. Ý tưởng rằng chúng là xấu xuất phát từ những ngày lập trình có cấu trúc. Nó liên quan đến khái niệm rằng một chức năng phải có một điểm vào duy nhất và một điểm thoát duy nhất, tức là chỉ có một điểm returncho mỗi chức năng.

Điều này có ý nghĩa nếu chức năng của bạn dài và nếu bạn có nhiều vòng lặp lồng nhau. Tuy nhiên, các chức năng của bạn nên ngắn và bạn nên quấn các vòng và cơ thể của chúng thành các chức năng ngắn của riêng chúng. Nói chung, việc buộc một hàm phải có một điểm thoát duy nhất có thể dẫn đến logic rất phức tạp.

Nếu chức năng của bạn rất ngắn, nếu bạn có một vòng lặp đơn, hoặc tệ nhất là hai vòng lặp lồng nhau và nếu thân vòng lặp rất ngắn, thì rõ ràng a breakhoặc a continuelàm gì. Nó cũng rõ ràng những gì nhiều returntuyên bố làm.

Những vấn đề này được giải quyết trong "Mã sạch" của Robert C. Martin và trong "Tái cấu trúc" của Martin Fowler.


12
"Làm cho các chức năng của bạn nhỏ. Sau đó làm cho chúng nhỏ hơn" -Robert C. Martin. Tôi thấy rằng điều này làm việc tốt đáng ngạc nhiên. Mỗi khi bạn thấy một khối mã trong một hàm cần một nhận xét giải thích những gì nó làm, hãy bọc nó thành một hàm riêng biệt với một tên mô tả. Ngay cả khi nó chỉ là một vài dòng, và ngay cả khi nó chỉ được sử dụng một lần. Thực hành này giúp loại bỏ hầu hết các vấn đề với phá vỡ / tiếp tục hoặc trả lại nhiều.
Dima

2
@Mikhail: Độ phức tạp theo chu kỳ thường tương quan khá mạnh với SLOC, điều đó có nghĩa là lời khuyên có thể được đơn giản hóa để "không viết các hàm dài".
John R. Strohm

3
Ý tưởng về một điểm thoát duy nhất bị giải thích sai. Ngày xửa ngày xưa, các chức năng không phải trả lại người gọi của họ. Họ có thể trở lại một số vị trí khác. Điều này thường được thực hiện trong ngôn ngữ lắp ráp. Fortran đã có một cấu trúc đặc biệt cho việc này; bạn có thể chuyển vào một số câu lệnh đứng trước ký hiệu CALL P(X, Y, &10)và trong trường hợp có lỗi, hàm có thể chuyển điều khiển cho câu lệnh đó, thay vì quay lại điểm của cuộc gọi.
kevin cline

@kevincline như đã thấy với Lua chẳng hạn.
Qix

1
@cjsimon bạn hiểu rồi Không chỉ các chức năng nên nhỏ. Các lớp học cũng nên nhỏ.
Dima

39

Lập trình viên xấu nói chuyện tuyệt đối (giống như Sith). Lập trình viên giỏi sử dụng giải pháp rõ ràng nhất có thể ( tất cả những thứ khác đều bằng nhau ).

Sử dụng break và tiếp tục thường xuyên làm cho mã khó theo dõi. Nhưng nếu thay thế chúng làm cho mã khó theo dõi hơn, thì đó là một thay đổi xấu.

Ví dụ bạn đưa ra chắc chắn là một tình huống trong đó nghỉ và tiếp tục nên được thay thế bằng một cái gì đó thanh lịch hơn.


Vâng, tôi đã phóng đại số lượng điều kiện để cung cấp các ví dụ về các trường hợp cần thoát.
Mikhail

9
Một ví dụ về mã thay thế bạn đã đề xuất là gì? Tôi nghĩ rằng đó là một ví dụ khá hợp lý của các tuyên bố bảo vệ.
simgineer

Dường như với tôi rằng "giải pháp rõ ràng nhất có thể" luôn ... có thể? Làm thế nào có thể không có một giải pháp "rõ ràng nhất"? Nhưng sau đó, tôi không phải là một người tuyệt đối, nên có lẽ bạn đúng.

@nocomprende Tôi không chắc bạn đang làm gì "Có thể" ở đây không chỉ ra rằng giải pháp tốt nhất không tồn tại - chỉ có điều rõ ràng, hoàn hảo nhất không phải là một điều. Rốt cuộc, nó chủ quan.
Matthew đọc

22

Hầu hết mọi người nghĩ rằng đó là một ý tưởng tồi bởi vì hành vi không dễ dự đoán. Nếu bạn đang đọc mã và bạn thấy while(x < 1000){}bạn cho rằng nó sẽ chạy cho đến khi x> = 1000 ... Nhưng nếu có dấu ngắt ở giữa, thì điều đó không đúng, vì vậy bạn không thể thực sự tin tưởng Vòng lặp...

Đó là lý do tương tự mà mọi người không thích GOTO: chắc chắn, nó có thể được sử dụng tốt, nhưng nó cũng có thể dẫn đến mã spaghetti hợp pháp, nơi mã nhảy ngẫu nhiên từ phần này sang phần khác.

Đối với bản thân tôi, nếu tôi định thực hiện một vòng lặp phá vỡ nhiều hơn một điều kiện, thì tôi sẽ while(x){}chuyển X thành sai khi tôi cần thoát ra. Kết quả cuối cùng sẽ giống nhau và bất kỳ ai đọc qua mã cũng sẽ biết để xem xét kỹ hơn những thứ đã chuyển giá trị của X.


2
+1 nói rất rõ và +1 (nếu tôi có thể làm khác) cho while(notDone){ }cách tiếp cận.
Thất vọngWithFormsDesigner

5
Mikhail: Vấn đề với sự phá vỡ là điều kiện cuối cùng cho vòng lặp không bao giờ được nêu đơn giản ở một nơi. Điều đó gây khó khăn cho việc dự đoán tình trạng hậu kỳ sau vòng lặp. Trong trường hợp tầm thường này (> = 1000), nó không khó. Thêm nhiều câu lệnh if và các mức lồng nhau khác nhau, nó có thể trở nên rất, rất khó để xác định điều kiện hậu của vòng lặp.
S.Lott

S.Lott đâm thẳng vào đầu đinh. Biểu thức điều khiển phép lặp nên bao gồm mọi điều kiện phải được đáp ứng để tiếp tục lặp lại.
bit-twiddler 17/03/2016

6
Thay thế break;bằng x=false;không làm cho mã của bạn rõ ràng hơn. Bạn vẫn phải tìm kiếm cơ thể cho tuyên bố đó. Và trong trường hợp x=false;bạn sẽ phải kiểm tra xem nó không bị x=true;giảm thêm nữa.
Sjoerd

14
Khi mọi người nói "Tôi thấy x và tôi giả sử y, nhưng nếu bạn làm z thì giả định đó không giữ được" Tôi có xu hướng nghĩ "vì vậy đừng đưa ra giả định ngu ngốc đó". Nhiều người sẽ đơn giản hóa điều đó thành "khi tôi thấy while (x < 1000)tôi cho rằng nó sẽ chạy 1000 lần". Chà, có nhiều lý do tại sao điều đó là sai, ngay cả khi xban đầu là con số không. Ví dụ, ai nói xđược tăng chính xác một lần trong vòng lặp và không bao giờ sửa đổi theo bất kỳ cách nào khác? Ngay cả đối với giả định của riêng bạn, chỉ vì một số thứ x >= 1000không có nghĩa là vòng lặp sẽ kết thúc - nó có thể được đặt lại trong phạm vi trước khi điều kiện được kiểm tra.
Steve314

14

Có, bạn có thể [viết lại] chương trình mà không có câu lệnh break (hoặc trả về từ giữa các vòng lặp, điều này làm điều tương tự). Nhưng bạn có thể phải giới thiệu các biến bổ sung và / hoặc sao chép mã cả hai điều này thường làm cho chương trình khó hiểu hơn. Pascal (ngôn ngữ lập trình) rất tệ, đặc biệt đối với những lập trình viên mới bắt đầu vì lý do đó. Sếp của bạn về cơ bản muốn bạn lập trình trong các cấu trúc điều khiển của Pascal. Nếu Linus Torvalds ở trong đôi giày của bạn, anh ta có thể sẽ cho ông chủ của bạn xem ngón tay giữa!

Có một kết quả khoa học máy tính được gọi là hệ thống cấu trúc điều khiển của Kosaraju, xuất hiện từ năm 1973 và được đề cập trong bài báo nổi tiếng của Knuth trên gotos từ năm 1974. (Nhân tiện, bài báo này của Knuth đã được David Thornley đề xuất ở trên .) Điều S. Rao Kosaraju đã chứng minh vào năm 1973 là không thể viết lại tất cả các chương trình có độ sâu đa cấp độ sâu n thành các chương trình có độ sâu phá vỡ nhỏ hơn n mà không đưa ra các biến phụ. Nhưng hãy nói rằng đó chỉ là một kết quả lý thuyết đơn thuần. (Chỉ cần thêm một vài biến số?! Chắc chắn bạn có thể làm điều đó để làm hài lòng sếp của bạn ...)

Điều quan trọng hơn nhiều từ góc độ công nghệ phần mềm là một bài báo gần đây hơn năm 1995 của Eric S. Roberts có tựa đề Thoát khỏi vòng lặp và lập trình có cấu trúc: Mở lại cuộc tranh luận ( http://cs.stanford.edu/people/eroberts/ con / SIGCSE- 1995 / LoopExits.pdf ). Roberts tóm tắt một số nghiên cứu thực nghiệm được thực hiện bởi những người khác trước ông. Ví dụ, khi một nhóm sinh viên loại CS101 được yêu cầu viết mã cho một hàm thực hiện tìm kiếm tuần tự trong một mảng, tác giả của nghiên cứu đã nói như sau về những sinh viên đã sử dụng break / return / goto để thoát khỏi vòng tìm kiếm tuần tự khi tìm thấy phần tử:

Tôi vẫn chưa tìm thấy một người nào đã thử một chương trình sử dụng [kiểu này], người đã tạo ra một giải pháp không chính xác.

Roberts cũng nói rằng:

Những sinh viên đã cố gắng giải quyết vấn đề mà không sử dụng một kết quả rõ ràng từ vòng lặp for thì kém hơn rất nhiều: chỉ có bảy trong số 42 sinh viên cố gắng chiến lược này quản lý để tạo ra các giải pháp chính xác. Con số đó thể hiện tỷ lệ thành công dưới 20%.

Có, bạn có thể có nhiều kinh nghiệm hơn sinh viên CS101, nhưng không sử dụng câu lệnh break (hoặc trả lại / goto tương đương từ giữa các vòng lặp), cuối cùng bạn sẽ viết mã mà trong khi có cấu trúc độc đáo là đủ lông về mặt logic bổ sung các biến và sao chép mã mà ai đó, có thể là chính bạn, sẽ đặt các lỗi logic trong đó trong khi cố gắng làm theo kiểu mã hóa của sếp.

Tôi cũng sẽ nói ở đây rằng bài viết của Roberts dễ tiếp cận hơn với các lập trình viên trung bình, vì vậy lần đọc đầu tiên tốt hơn Knuth. Nó cũng ngắn hơn và bao gồm một chủ đề hẹp hơn. Bạn thậm chí có thể giới thiệu nó cho sếp của mình, ngay cả khi anh ta là quản lý chứ không phải là loại CS.


2
Mặc dù lập trình có cấu trúc là cách tiếp cận của tôi trong nhiều năm, nhưng trong vài năm gần đây đã chuyển sang sử dụng hoàn toàn lối thoát rõ ràng ở cơ hội đầu tiên có thể. Điều này giúp thực hiện nhanh hơn và gần như loại bỏ các lỗi logic được sử dụng để dẫn đến các vòng lặp vô tận (treo).
DocSalvager

9

Tôi không xem xét sử dụng một trong những thực hành xấu này, nhưng sử dụng chúng quá nhiều trong cùng một vòng lặp sẽ đảm bảo xem xét lại logic được sử dụng trong vòng lặp. Sử dụng chúng một cách tiết kiệm.


:) vâng, ví dụ tôi cung cấp là khái niệm
Mikhail

7

Ví dụ bạn đưa ra không cần nghỉ cũng không tiếp tục:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

"Vấn đề" của tôi với 4 dòng trong ví dụ của bạn là tất cả chúng đều ở cùng cấp độ nhưng chúng làm những việc khác nhau: một số nghỉ, một số tiếp tục ... Bạn phải đọc từng dòng.

Theo cách tiếp cận lồng nhau của tôi, bạn càng đi sâu, mã càng trở nên 'hữu ích'.

Nhưng, nếu sâu bên trong bạn sẽ tìm thấy một lý do để dừng vòng lặp (không phải là điều kiện chính), thì việc ngắt hoặc trả lại sẽ được sử dụng. Tôi muốn sử dụng một cờ bổ sung sẽ được kiểm tra trong điều kiện cấp cao nhất. Việc nghỉ / trả lại trực tiếp hơn; nó tốt hơn nêu ý định hơn là thiết lập một biến khác.


+1 Nhưng thực ra sự <so sánh của bạn cần phải <=phù hợp với giải pháp OP
El Ronnoco

3
Trong hầu hết các trường hợp, nếu một cái quá sâu đến mức người ta cần sử dụng break / return để quản lý điều khiển luồng, thì chức năng / phương thức của một người quá phức tạp.
bit-twiddler

6

"Tính xấu" phụ thuộc vào cách bạn sử dụng chúng. Tôi thường sử dụng các ngắt trong các cấu trúc lặp CHỈ khi nó sẽ tiết kiệm cho tôi các chu kỳ không thể lưu thông qua việc tái cấu trúc thuật toán. Chẳng hạn, đạp xe qua bộ sưu tập tìm kiếm một vật phẩm có giá trị trong một thuộc tính cụ thể được đặt thành đúng. Nếu tất cả những gì bạn cần biết là một trong những mục có thuộc tính này được đặt thành đúng, một khi bạn đạt được kết quả đó, việc ngắt kết thúc vòng lặp là thích hợp.

Nếu sử dụng dấu ngắt sẽ không làm cho mã dễ đọc hơn, ngắn hơn để chạy hoặc lưu chu kỳ xử lý theo cách đáng kể, thì tốt nhất không nên sử dụng chúng. Tôi có xu hướng mã hóa thành "mẫu số chung thấp nhất" khi có thể để đảm bảo rằng bất kỳ ai theo dõi tôi đều có thể dễ dàng xem mã của tôi và tìm hiểu điều gì đang xảy ra (tôi không phải lúc nào cũng thành công trong việc này). Phá vỡ điều đó bởi vì họ giới thiệu điểm vào / ra lẻ. Bị lạm dụng, họ có thể cư xử rất giống với một tuyên bố "goto".


Tôi đồng ý với cả hai điểm! Theo như điểm thứ hai của bạn - tôi nghĩ việc theo dõi bài viết gốc của tôi dễ dàng hơn vì nó đọc giống như tiếng Anh. Nếu bạn có một kết hợp các điều kiện trong câu lệnh 1 if, thì đó gần như là một giải mã để tìm ra điều gì sẽ xảy ra cho if để thực thi true.
Mikhail

@Mikhail: Các mẫu bạn cung cấp có một chút vị tha cho chủ nghĩa hiện thực cụ thể. Theo tôi thấy, những mẫu đó rõ ràng, súc tích, dễ đọc hơn. Hầu hết các vòng lặp không như thế. Hầu hết các vòng lặp có một số loại logic khác mà chúng đang thực hiện và có khả năng phức tạp hơn nhiều điều kiện. Đó là trong những trường hợp mà break / continue có thể không phải là cách sử dụng tốt nhất bởi vì nó làm rối logic khi đọc.
Joel Etherton

4

Hoàn toàn không ... Có, việc sử dụng gotolà xấu vì nó làm hỏng cấu trúc chương trình của bạn và cũng rất khó để hiểu được dòng điều khiển.

Nhưng việc sử dụng các tuyên bố như breakcontinuehoàn toàn cần thiết trong những ngày này và không được coi là thực hành lập trình xấu cả.

Và cũng không khó để hiểu được dòng điều khiển sử dụng breakcontinue. Trong cấu trúc như switchcác breaktuyên bố là hoàn toàn cần thiết.


2
Tôi đã không sử dụng "tiếp tục" kể từ lần đầu tiên tôi học C vào năm 1981. Đây là một tính năng ngôn ngữ không cần thiết, vì mã được bỏ qua bởi câu lệnh tiếp tục có thể được gói bởi câu lệnh điều khiển có điều kiện.
bit-twiddler

12
Tôi thích sử dụng tiếp tục trong những trường hợp đó vì nó đảm bảo rằng mã của tôi không trở thành mã mũi tên. Tôi ghét mã mũi tên hơn các loại báo cáo goto. Tôi cũng đọc nó như là "nếu tuyên bố này là đúng, bỏ qua phần còn lại của vòng lặp này và tiếp tục với lần lặp tiếp theo." Rất hữu ích khi nó ở đầu vòng lặp for (ít hữu ích hơn trong các vòng lặp).
jsternberg

@jsternberg Vì chiến thắng! :-)
Notinlist

3

Khái niệm thiết yếu đến từ việc có thể phân tích ngữ nghĩa chương trình của bạn. Nếu bạn có một mục duy nhất và một lối thoát duy nhất, toán học cần thiết để biểu thị các trạng thái có thể dễ dàng hơn đáng kể so với việc bạn phải quản lý các đường dẫn.

Một phần, khó khăn này phản ánh khả năng lý luận về mã của bạn về mặt khái niệm.

Thành thật mà nói, mã thứ hai của bạn không rõ ràng. Nó đang làm gì vậy Liệu tiếp tục 'tiếp tục', hay nó 'tiếp theo' vòng lặp? Tôi không có ý kiến. Ít nhất là ví dụ đầu tiên của bạn là rõ ràng.


Khi tôi làm việc trong một dự án mà người quản lý bắt buộc phải sử dụng chúng, tôi đã phải sử dụng một sơ đồ để ghi nhớ việc sử dụng nó và một số lối thoát, đã tạo ra nhiều mã khó hiểu hơn ...
umlcat

bình luận "Thành thật" của bạn là cú pháp - "tiếp theo" là một điều sai trái; ngôn ngữ bình thường sử dụng "tiếp tục" có nghĩa là "bỏ qua vòng lặp này"
Mikhail

@Mik, có lẽ "bỏ qua lần lặp này" sẽ là một mô tả tốt hơn. Trân trọng, Tiến sĩ IM Pedantic
Pete Wilson

@Mikhail: Chắc chắn rồi. Nhưng khi một ngôn ngữ hoạt động với nhiều ngôn ngữ, nó có thể dẫn đến lỗi khi cú pháp không phổ biến giữa các ngôn ngữ.
Paul Nathan

Nhưng toán học không phải là lập trình. Mã là biểu cảm hơn toán học. Tôi nhận được rằng một lần nhập / thoát đơn có thể làm cho sơ đồ trông đẹp hơn nhưng với chi phí nào (Tức là nơi nghỉ / trả lại có thể làm cho mã hoạt động tốt hơn)?
Evan Plaice

3

Tôi sẽ thay thế đoạn mã thứ hai của bạn bằng

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

không vì bất kỳ lý do nào của sự căng thẳng - tôi thực sự nghĩ rằng điều này dễ đọc hơn và để ai đó hiểu những gì đang xảy ra. Nói chung, các điều kiện cho các vòng lặp của bạn nên được chứa hoàn toàn trong các điều kiện vòng lặp không được xả rác khắp cơ thể. Tuy nhiên, có một số tình huống breakcontinuecó thể giúp dễ đọc. breakmoreso hơn continuetôi có thể thêm: D


Dù tôi không đồng ý với điều gì, "Tôi thực sự nghĩ rằng điều này dễ đọc hơn", điều này thực hiện đúng mục đích giống như đoạn mã trên. Tôi chưa bao giờ thực sự nghĩ về 'break' như một nhà điều hành ngắn mạch cho đến bây giờ nhưng nó có ý nghĩa hoàn hảo. Đối với "Nói chung, các điều kiện cho các vòng lặp của bạn nên được chứa hoàn toàn trong các điều kiện vòng lặp đó", bạn sẽ làm gì khi bạn xử lý một vòng lặp foreach vì thực sự không có cách nào để chèn logic điều kiện vào định nghĩa vòng lặp?
Evan Plaice

@Evan Điều kiện này không áp dụng cho một foreachvòng lặp vì điều này sẽ chỉ lặp lại mọi mục trong bộ sưu tập. Một forvòng lặp tương tự ở chỗ nó không nên có điểm kết thúc có điều kiện. Nếu bạn cần một điểm cuối có điều kiện thì bạn cần sử dụng một whilevòng lặp.
El Ronnoco

1
@Evan Tôi thấy quan điểm của bạn mặc dù - tức là 'nếu bạn cần thoát ra khỏi vòng lặp foreach thì sao?' - Vâng, chỉ nên có một breaktối đa theo ý kiến ​​của tôi từ vòng lặp.
El Ronnoco

2

Tôi không đồng ý với sếp của bạn. Có những nơi thích hợp cho breakcontinueđược sử dụng. Trong thực tế, lý do mà việc thực thi và xử lý ngoại lệ được đưa vào các ngôn ngữ lập trình hiện đại là bạn không thể giải quyết mọi vấn đề bằng cách sử dụng structured techniques.

Bên cạnh đó, tôi không muốn bắt đầu một cuộc thảo luận tôn giáo ở đây nhưng bạn có thể cơ cấu lại mã của mình để dễ đọc hơn như thế này:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

Mặt khác

Cá nhân tôi không thích việc sử dụng ( flag == true )trong các điều kiện bởi vì nếu biến đã là boolean rồi, thì bạn đang đưa ra một so sánh bổ sung cần xảy ra khi giá trị của boolean có câu trả lời bạn muốn - trừ khi bạn chắc chắn rằng trình biên dịch của bạn sẽ tối ưu hóa so sánh thêm đi.


Tôi đã vặn vẹo câu hỏi này trong nhiều năm. Cách của bạn ('if (cờ) {...}') ngắn gọn hơn rất nhiều và có thể nó trông 'chuyên nghiệp' hoặc 'chuyên gia' hơn. Nhưng, bạn biết đấy, điều đó thường có nghĩa là người đọc / người bảo trì phải tự ngắt lời mình một cách ngắn gọn để nhớ lại ý nghĩa của công trình; và để chắc chắn về ý nghĩa của bài kiểm tra. Hiện tại, tôi đang sử dụng 'if (flag == true) {...}' chỉ vì nó có vẻ là tài liệu tốt hơn. Tháng tiếp theo? Quien sabe?
Pete Wilson

2
@Pete - Thuật ngữ này không ngắn gọn nhưng thanh lịch . Bạn có thể quấy rối tất cả những gì bạn muốn, nhưng nếu bạn lo lắng rằng người đọc / người bảo trì không hiểu booleaný nghĩa của thuật ngữ thanh lịch / ngắn gọn thì có lẽ bạn nên thuê một số người bảo trì thông minh hơn ;-)
Zeke Hansell

@Pete, tôi cũng đứng trước tuyên bố của tôi về mã được tạo. Bạn đang thực hiện một so sánh nữa bằng cách so sánh một cờ với một giá trị không đổi trước khi đánh giá giá trị boolean của biểu thức. Tại sao làm cho nó khó hơn nó phải là, biến cờ đã có giá trị bạn muốn!
Zeke Hansell

+1 công việc tốt. Chắc chắn thanh lịch hơn rất nhiều so với ví dụ trước đây.
Evan Plaice

2

Tôi đồng ý với sếp của bạn. Chúng là xấu bởi vì chúng tạo ra các phương pháp có độ phức tạp chu kỳ cao. Những phương pháp như vậy rất khó đọc và khó kiểm tra. May mắn thay, có một giải pháp dễ dàng. Trích xuất phần thân vòng lặp thành một phương thức riêng biệt, trong đó "tiếp tục" trở thành "trở lại". "Trả lại" sẽ tốt hơn vì sau khi "trả lại" nó đã kết thúc - không phải lo lắng về tình trạng địa phương.

Đối với "break" trích xuất chính vòng lặp thành một phương thức riêng biệt, thay thế "break" bằng "return".

Nếu các phương thức được trích xuất yêu cầu một số lượng lớn các đối số, thì đó là một dấu hiệu để trích xuất một lớp - hoặc thu thập chúng vào một đối tượng ngữ cảnh.


0

Tôi nghĩ đó chỉ là một vấn đề khi được lồng sâu bên trong nhiều vòng lặp. Thật khó để biết bạn đang phá vỡ vòng lặp nào. Có thể khó theo dõi tiếp, nhưng tôi nghĩ nỗi đau thực sự đến từ những lần nghỉ - logic có thể khó theo dõi.


2
Trên thực tế, thật dễ dàng để xem bạn có thụt lề đúng cách không, có ngữ nghĩa của những câu đó được nội tâm hóa và không quá buồn ngủ.

@delnan - đó là rất nhiều giả định;)
davidhaskins

2
Vâng, đặc biệt là cuối cùng.
Michael K

Chà, dù sao thì # 1 là cần thiết cho lập trình nghiêm túc, # 2 được mong đợi từ mọi người được gọi là lập trình viên và nói chung là # 3 khá hữu ích;)

Một số ngôn ngữ (chỉ có kịch bản mà tôi biết) hỗ trợ break 2; đối với những người khác, tôi đoán cờ bool tạm thời được sử dụng
Mikhail

0

Miễn là chúng không được sử dụng như goto trá hình như trong ví dụ sau:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

Tôi ổn với họ. (Ví dụ nhìn thấy trong mã sản xuất, meh)


Bạn có đề nghị cho các trường hợp như thế này để tạo chức năng?
Mikhail

Có và nếu không thể, một goto đơn giản. Khi tôi thấy một việc làm, tôi nghĩ rằng sự lặp lại, không phải bối cảnh để mô phỏng bước nhảy.
SuperBloup

oh tôi đồng ý, tôi chỉ hỏi làm thế nào bạn làm điều đó :) Các nhà khoa học máy tính làm điều đó nhiều lần
Mikhail

0

Tôi không thích một trong hai phong cách này. Đây là những gì tôi muốn:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

Tôi thực sự không thích sử dụng returnđể hủy bỏ một chức năng. Nó cảm thấy như một sự lạm dụng return.

Sử dụng breakcũng không phải lúc nào cũng rõ ràng để đọc.

Tốt hơn nữa có thể là:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

ít lồng nhau hơn và các điều kiện phức tạp được tái cấu trúc thành các biến (trong một chương trình thực, bạn phải có tên tốt hơn, rõ ràng ...)

(Tất cả các mã trên là mã giả)


11
Bạn thực sự sẽ thích 3 cấp độ lồng nhau hơn những gì tôi đã gõ ở trên?
Mikhail

@Mikhail: Có, hoặc tôi sẽ gán kết quả của điều kiện cho một biến. Tôi thấy nó dễ hiểu hơn nhiều so với logic của breakcontinue. Kết thúc bất thường một vòng lặp chỉ cảm thấy kỳ lạ và tôi không thích nó.
Thất vọngWithFormsDesigner

1
Trớ trêu thay, bạn đã đọc sai điều kiện của tôi. continuecó nghĩa là bỏ qua các chức năng đi đến vòng lặp tiếp theo; không "tiếp tục thực hiện"
Mikhail

@Mikhail: À. Tôi không sử dụng nó thường xuyên và khi tôi đọc nó, tôi bị lẫn lộn bởi ý nghĩa. Một lý do khác tôi không thích nó. : P Hãy cho tôi một phút để cập nhật ...
Thất vọngWithFormsDesigner

7
Quá nhiều lồng làm hỏng khả năng đọc. Và đôi khi, tránh phá vỡ / tiếp tục giới thiệu sự cần thiết phải đảo ngược logic của bạn trong các bài kiểm tra có điều kiện, điều này có thể dẫn đến việc giải thích sai về những gì mã của bạn đang làm - Tôi chỉ nói rằng
Zeke Hansell

0

Không. Đó là một cách để giải quyết vấn đề, và có nhiều cách khác để giải quyết nó.

Nhiều ngôn ngữ chính hiện tại (Java, .NET (C # + VB), PHP, viết riêng của bạn) sử dụng "break" và "continue" để bỏ qua các vòng lặp. Cả hai câu "cấu trúc goto (s)".

Không có họ:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

Với họ:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

Lưu ý rằng, mã "phá vỡ" và "tiếp tục" ngắn hơn và thường biến các câu "trong khi" thành "cho" hoặc "foreach".

Cả hai trường hợp là một vấn đề của phong cách mã hóa. Tôi không thích sử dụng chúng , bởi vì kiểu dài cho phép tôi có nhiều quyền kiểm soát mã hơn.

Tôi thực sự, làm việc trong một số dự án, trong đó bắt buộc phải sử dụng những câu đó.

Một số nhà phát triển có thể nghĩ rằng họ không phải là necesarilly, nhưng theo giả thuyết, nếu chúng tôi phải loại bỏ chúng, chúng tôi phải xóa "while" và "do while" ("lặp lại cho đến khi", các bạn pascal) cũng vậy ;-)

Kết luận, ngay cả khi tôi không thích sử dụng chúng, tôi nghĩ đó là một lựa chọn, không phải là một thực hành lập trình tồi.


Xin lỗi vì đã kén chọn, nhưng ví dụ thứ hai của bạn bị thiếu đầu ra khi không tìm thấy khóa (vì vậy tất nhiên nó trông ngắn hơn nhiều).
Thất vọngWithFormsDesigner

2
không đề cập đến việc ví dụ đầu tiên chỉ hoạt động nếu khóa là cái cuối cùng trong danh sách.
Mikhail

@FrustratedWithFormsDesigner CHÍNH XÁC. Tôi đã đưa vào purpouse để biểu thị lý do tại sao phương pháp đó lại thích hợp hơn ;-)
umlcat

tuy nhiên, bạn có hai thói quen với ngữ nghĩa khác nhau; do đó, chúng không tương đương về mặt logic.
bit-twiddler 17/03/2016

2
ví dụ thứ hai của bạn có hai lỗi, một cú pháp và một logic. 1. Nó sẽ không biên dịch vì iterator không được khai báo ngoài phạm vi của vòng lặp for (do đó không có sẵn trong đầu ra chuỗi). 2. Ngay cả khi iterator được khai báo bên ngoài vòng lặp, nếu khóa không được tìm thấy trong bộ sưu tập, đầu ra chuỗi sẽ in khóa của mục cuối cùng trong danh sách.
Evan Plaice

0

Tôi không chống lại continuebreakvề nguyên tắc, nhưng tôi nghĩ rằng chúng là những cấu trúc cấp thấp mà rất thường có thể được thay thế bằng một thứ thậm chí còn tốt hơn.

Tôi đang sử dụng C # làm ví dụ ở đây, xem xét trường hợp muốn lặp lại bộ sưu tập, nhưng chúng tôi chỉ muốn các yếu tố đáp ứng một số vị ngữ và chúng tôi không muốn thực hiện nhiều hơn tối đa 100 lần lặp.

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

Cái này trông thật sạch sẽ. Nó không quá khó hiểu. Tôi nghĩ rằng nó sẽ đứng để đạt được rất nhiều từ việc tuyên bố nhiều hơn mặc dù. So sánh nó với những điều sau đây:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

Có lẽ các cuộc gọi Where và Take thậm chí không nên theo phương thức này. Có lẽ việc lọc này nên được thực hiện TRƯỚC KHI bộ sưu tập được truyền cho phương thức này. Dù sao, bằng cách di chuyển khỏi các công cụ cấp thấp và tập trung nhiều hơn vào logic kinh doanh thực tế, nó càng trở nên rõ ràng hơn những gì chúng tôi THỰC SỰ quan tâm. Việc tách mã của chúng tôi thành các mô đun gắn kết tuân thủ nhiều hơn các thực tiễn thiết kế tốt và vì vậy trên, bật.

Các công cụ cấp thấp vẫn sẽ tồn tại trong một số phần của mã, nhưng chúng tôi muốn che giấu điều này càng nhiều càng tốt, vì nó cần năng lượng tinh thần mà chúng tôi có thể sử dụng để lý giải về các vấn đề kinh doanh thay thế.


-1

Code Complete có một phần hay về cách sử dụng gotovà nhiều trả về từ thường trình hoặc vòng lặp.

Nói chung nó không phải là thực hành xấu. breakhoặc continuenói chính xác những gì xảy ra tiếp theo. Và tôi đồng ý với điều này.

Steve McConnell (tác giả của Code Complete) sử dụng gần như các ví dụ giống như bạn để thể hiện lợi thế của việc sử dụng các gotocâu lệnh khác nhau .

Tuy nhiên, quá lạm dụng breakhoặc continuecó thể dẫn đến phần mềm phức tạp và không thể nhầm lẫn.

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.