Trong vòng lặp for, tôi có nên chuyển điều kiện ngắt vào trường điều kiện nếu có thể không? [đóng cửa]


48

Đôi khi tôi cần những vòng lặp cần nghỉ ngơi như thế này:

for(int i=0;i<array.length;i++){
    //some other code
    if(condition){
        break;
    }
}

Tôi cảm thấy không thoải mái với việc viết

if(condition){
    break;
}

bởi vì nó tiêu thụ 3 dòng mã. Và tôi thấy vòng lặp có thể được viết lại thành:

                                   ↓
for(int i=0;i<array.length && !condition;i++){
    //some other code
}

Vì vậy, câu hỏi của tôi là, có nên thực hiện tốt việc chuyển điều kiện vào trường điều kiện để giảm các dòng mã nếu có thể?


7
Phụ thuộc vào những gì conditioncụ thể là.
Bergi

4
Có một lý do tại sao misra cấm sử dụng "phá vỡ" và "tiếp tục" trong các vòng lặp. Xem thêm điều này: stackoverflow.com/q/3922599/476681
BЈовић

35
Tôi nghĩ rằng số lượng dòng chính nó không phải là vấn đề thực sự. Nó có thể được viết trên một dòng. nếu (điều kiện) phá vỡ; Vấn đề theo ý kiến ​​của tôi là khả năng đọc. Cá nhân tôi không thích phá vỡ một vòng lặp khác hơn là sử dụng điều kiện vòng lặp.
Joe

97
bởi vì nó tiêu tốn 3 dòng mã Một lý do rất, rất, rất tệ để không thích bất kỳ phong cách nào. IMO "tiêu thụ [ing] dòng mã" nằm ở đâu đó giữa không liên quan và điều tốt. Cố gắng nhồi quá nhiều logic vào quá ít dòng dẫn đến mã không thể đọc được và các lỗi khó tìm.
Andrew Henle

3
Có thể ý kiến ​​không phổ biến: for(int i=0;i<array.length && !condition;i++)tương đối không phổ biến và có thể bị bỏ qua bởi một người nào đó chỉ lướt qua mã; đây có thể là trường hợp sử dụng tốt cho một vòng lặp whilehoặc do whilethường có nhiều điều kiện ngắt trong định nghĩa vòng lặp.
Jules

Câu trả lời:


154

Hai ví dụ bạn đưa ra không tương đương về chức năng. Trong bản gốc, kiểm tra điều kiện được thực hiện sau phần "một số mã khác", trong khi ở phiên bản sửa đổi, nó được thực hiện trước, khi bắt đầu thân vòng lặp.

Mã không bao giờ được viết lại với mục đích duy nhất là giảm số lượng dòng. Rõ ràng đó là một phần thưởng tuyệt vời khi nó hoạt động theo cách đó, nhưng nó không bao giờ nên được thực hiện với chi phí dễ đọc hoặc chính xác.


5
Tôi hiểu tình cảm của bạn, nhưng tôi đã duy trì đủ mã kế thừa trong đó vòng lặp dài hàng trăm dòng và có nhiều lần nghỉ có điều kiện. Điều này làm cho việc gỡ lỗi trở nên rất khó khăn khi bạn mong đợi mã để có được sự phá vỡ có điều kiện mà bạn mong đợi nhưng mã trước đó đã được thực thi trước tiên. Làm thế nào để bạn xử lý mã đó? Trong các ví dụ ngắn như trên, thật dễ dàng để quên đi loại kịch bản quá phổ biến đó.
Berin Loritsch

84
@BerinLoritsch: Cách chính xác để đối phó với điều đó là không có các vòng lặp dài vài trăm dòng!
Chris

27
@Walfrat: Nếu viết lại nó không phải là một lựa chọn thì thực sự không còn câu hỏi nào nữa. Phải nói rằng bạn thường có thể sử dụng một bộ tái cấu trúc kiểu "Phương pháp trích xuất" một cách an toàn để di chuyển các khối mã xung quanh, điều này sẽ làm cho vòng lặp phương thức chính của bạn ngắn hơn và hy vọng làm cho luồng logic rõ ràng hơn. Nó thực sự là một trường hợp bởi trường hợp điều. Tôi sẽ tuân theo quy tắc chung của mình là giữ các phương thức ngắn và chắc chắn giữ logic logic vòng tiếp theo ngắn nhưng tôi không muốn cố gắng nói gì hơn thế theo nghĩa chung mặc dù ...
Chris

4
@AndrewHenle brevity là khả năng đọc, ít nhất là theo nghĩa tăng tính dài dòng luôn làm giảm khả năng đọc và khả năng duy trì. Giảm LỘC đạt được bằng cách tăng mật độ và tăng mức độ trừu tượng, và có mối tương quan rất chặt chẽ với khả năng duy trì như được chứng thực trong các cặp Q / A khác trên chính địa điểm này.
Leushenko

5
@Joe một cấu trúc Do-While rất hiếm khi dễ xảy ra cho các nhà phát triển chuyến đi cần duy trì mã đó sau này. Một vài nhà phát triển chưa bao giờ thực sự nhìn thấy chúng! Tôi không chắc chắn liệu một lần làm có cải thiện khả năng đọc hay không - nếu có bất cứ điều gì, nó sẽ làm tăng khó khăn để hiểu mã. Chỉ là một ví dụ - cơ sở mã hiện tại của tôi khoảng 15 tuổi và có khoảng 2 triệu LỘC. Khoảng năm mươi nhà phát triển đã chạm vào nó rồi. Nó có chính xác các vòng lặp do-while bằng không!
T. Sar - Tái lập Monica

51

Tôi không mua lập luận rằng "nó tiêu thụ 3 dòng mã" và do đó là xấu. Rốt cuộc, bạn chỉ có thể viết nó như sau:

if (condition) break;

và chỉ tiêu thụ một dòng.

Nếu ifxuất hiện một nửa trong vòng lặp, tất nhiên nó phải tồn tại như một thử nghiệm riêng biệt. Tuy nhiên, nếu kiểm tra này xuất hiện ở cuối khối, bạn đã tạo một vòng lặp có kiểm tra tiếp tục ở cả hai đầu của vòng lặp, có thể thêm vào độ phức tạp của mã. Mặc dù vậy, hãy lưu ý rằng đôi khi if (condition)thử nghiệm chỉ có thể có ý nghĩa sau khi khối đã được thực thi.

Nhưng giả sử đó không phải là trường hợp, bằng cách áp dụng phương pháp khác:

for(int i=0;i<array.length && !condition;i++){
    //some other code
}

các điều kiện thoát được giữ cùng nhau, điều này có thể đơn giản hóa mọi thứ (đặc biệt nếu " một số mã khác " dài).

Tất nhiên không có câu trả lời đúng ở đây. Vì vậy, hãy lưu ý đến các thành ngữ của ngôn ngữ, các quy ước mã hóa của nhóm bạn là gì, v.v. Nhưng để cân bằng, tôi sẽ áp dụng phương pháp thử nghiệm duy nhất khi nó có ý nghĩa chức năng để làm như vậy.


14
@ jpmc26, Tốt hơn? Không. Phong cách thay thế hợp lệ? Tất nhiên.
David Arno

6
Tốt hơn là trong khó khăn hơn để gây rối khi mã được chỉnh sửa sau đó, và không phải chịu bất kỳ nhược điểm đáng chú ý nào.
jpmc26

4
Giữ chúng lại với nhau vì "nội dung vòng lặp có thể dài" dường như là một đối số yếu. Khi nội dung của bạn khó đọc thì bạn gặp vấn đề khác ngoài việc lưu hai dòng mã.
BlueWizard

12
@ jpmc26 Không đồng ý. Niềng răng là sự lộn xộn không cần thiết trong các điều kiện ngắn nhất, một lớp lót theo câu trả lời này. Không có thanh lịch và dễ dàng hơn trên mắt. Họ giống như một nhà điều hành có điều kiện ternary. Tôi chưa bao giờ quên thêm các dấu ngoặc trong khi chia chúng thành các khối lệnh lớn hơn; Tôi đã thêm các dòng mới sau khi tất cả.
benxyzzy

3
Tất cả đều là sở thích về phong cách, nhưng nếu người ta đưa ra lập luận rằng nó an toàn hơn, tôi sẽ không đồng ý với lý do đó. Bản thân tôi thích không có niềng răng và trên dòng tiếp theo, nhưng điều đó không có nghĩa là nó ít nhiều có giá trị hơn các kiểu khác
phflack

49

Loại câu hỏi này đã gây ra cuộc tranh luận gần như là lập trình. Để ném mũ vào vòng, tôi sẽ chọn phiên bản với breakđiều kiện và đây là lý do (đối với trường hợp cụ thể này):

Các forvòng lặp chỉ là có để xác định các giá trị lặp trong vòng lặp. Mã hóa điều kiện phá vỡ trong đó chỉ làm mờ IMO logic.

Có một phân tách breakrõ ràng rằng trong quá trình xử lý thông thường, các giá trị được chỉ định trong forvòng lặp và quá trình xử lý sẽ tiếp tục ngoại trừ khi điều kiện xuất hiện.

Để làm nền tảng, tôi bắt đầu viết mã a break, sau đó đặt điều kiện vào forvòng lặp trong nhiều năm (dưới sự chỉ đạo của một lập trình viên đặc biệt) và sau đó quay trở lại breakphong cách vì nó chỉ cảm thấy sạch hơn (và là phong cách thịnh hành trong các bộ mã khác nhau tôi đã tiêu thụ).

Đó là một trong những cuộc chiến thần thánh vào cuối ngày - một số người nói rằng bạn không bao giờ nên thoát ra khỏi một vòng lặp một cách giả tạo, nếu không đó không phải là một vòng lặp thực sự. Làm bất cứ điều gì bạn cảm thấy có thể đọc được (cả cho bản thân và người khác). Nếu việc giảm tổ hợp phím thực sự là việc của bạn, hãy chơi golf vào cuối tuần - bia tùy chọn.


3
Nếu điều kiện có một tên hay, chẳng hạn như "tìm thấy" (ví dụ: bạn đang xem qua mảng cho một cái gì đó), thì đó là một ý tưởng tốt để đặt nó vào đầu vòng lặp for.
dùng949300

1
Tôi đã nói rằng forcác vòng lặp được sử dụng khi số lần lặp được biết đến (ví dụ khi không có điều kiện ngắt nhưng kết thúc mảng / danh sách) và whilekhi không. Đây dường như là trường hợp cho một whilevòng lặp. Nếu khả năng đọc là mối quan tâm thì idx+=1bên trong vòng lặp sẽ dễ đọc hơn một if/elsekhối
Laiv

Bạn không thể đặt điều kiện ngắt trong vòng lặp nếu có mã theo sau nó, vì vậy ít nhất bạn sẽ phải viết "if (condition) {
Found

Chúng tôi đã quá quen với for(int i=0; i<something;i++)các vòng lặp đến nỗi tôi đoán rằng một nửa các nhà phát triển không thực sự nhớ rằng forcác vòng lặp có thể được sử dụng khác nhau và do đó khó có thể hiểu được &&conditionphần này.
Ralf Kleberhoff

@RalfKleberhoff Thật vậy. Tôi đã thấy nhiều nhà phát triển mới bày tỏ sự ngạc nhiên rằng các biểu thức tuyên bố ba bên đều là tùy chọn. Tôi thậm chí không nhớ lại lần cuối cùng tôi nhìn thấy for(;;)...
Robbie Dee

43

forcác vòng lặp là để lặp đi lặp lại một cái gì đó 1 - chúng không chỉ là lol-let-pull-some-Random-Stuff-from-the-body-and-put-them-in-the-loop-header - ba biểu thức có rất ý nghĩa cụ thể:

  • Cái đầu tiên là để khởi tạo iterator . Nó có thể là một chỉ mục, một con trỏ, một đối tượng lặp hoặc bất cứ điều gì - miễn là nó được sử dụng để lặp lại .
  • Thứ hai là để kiểm tra nếu chúng ta đạt đến cuối.
  • Thứ ba là để thúc đẩy các vòng lặp.

Ý tưởng là để tách việc xử lý lặp đi lặp lại ( như thế nào để lặp) từ logic bên trong vòng lặp ( những gì cần làm trong mỗi lần lặp). Nhiều ngôn ngữ thường có một vòng lặp cho mỗi vòng lặp giúp bạn thoát khỏi các chi tiết của vòng lặp, nhưng ngay cả khi ngôn ngữ của bạn không có cấu trúc đó hoặc nếu nó không thể được sử dụng trong kịch bản của bạn - bạn vẫn nên giới hạn tiêu đề vòng lặp để xử lý lặp đi lặp lại.

Vì vậy, bạn cần phải tự hỏi mình - là điều kiện của bạn về việc xử lý lặp hoặc về logic bên trong vòng lặp? Rất có thể, đó là về logic bên trong vòng lặp - vì vậy nó nên được kiểm tra bên trong vòng lặp chứ không phải trong tiêu đề.

1 Trái ngược với các vòng lặp khác, đó là "chờ đợi" điều gì đó xảy ra. Một vòng lặp for/ foreachnên có khái niệm về một "danh sách" các mục để lặp lại - ngay cả khi danh sách đó là lười biếng hoặc vô hạn hoặc trừu tượng.


5
Đây là suy nghĩ đầu tiên của tôi khi tôi đọc câu hỏi. Thất vọng tôi đã phải cuộn qua nửa tá câu trả lời để thấy ai đó nói điều đó
Nemo

4
Umm ... tất cả các vòng lặp là để lặp - đó là từ "lặp" có nghĩa là :-). Tôi giả sử bạn có nghĩa là một cái gì đó như "lặp qua một bộ sưu tập" hoặc "lặp lại bằng cách sử dụng một trình vòng lặp"?
psmears

3
@psmears OK, có lẽ tôi đã chọn từ sai - Tiếng Anh không phải là ngôn ngữ mẹ đẻ của tôi và khi tôi nghĩ về việc lặp lại, tôi nghĩ đến "lặp đi lặp lại một cái gì đó". Tôi sẽ sửa câu trả lời.
Idan Arye

Một trường hợp trung gian là nơi điều kiện là một cái gì đó như someFunction(array[i]). Bây giờ cả hai phần của điều kiện là về điều bạn đang lặp đi lặp lại, và sẽ rất hợp lý khi kết hợp chúng với &&tiêu đề vòng lặp.
Barmar

Tôi tôn trọng vị trí của bạn, nhưng tôi không đồng ý. Tôi nghĩ rằng forcác vòng lặp ở tim là các bộ đếm, không phải các vòng lặp trong danh sách và điều này được hỗ trợ bởi cú pháp của forcác ngôn ngữ trước đó (các ngôn ngữ này thường có for i = 1 to 10 step 2cú pháp và steplệnh - thậm chí cho phép giá trị ban đầu không phải là chỉ mục của đầu tiên phần tử - gợi ý về bối cảnh số, không phải ngữ cảnh danh sách). Các chỉ trường hợp sử dụng mà tôi nghĩ rằng sự hỗ trợ forvòng như chỉ lặp lại trên một danh sách được parallelising, và điều đó đòi hỏi phải có cú pháp rõ ràng bây giờ anyway để không phá vỡ mã làm, ví dụ, một loại.
Pickup Logan

8

Tôi khuyên bạn nên sử dụng phiên bản với if (condition). Nó dễ đọc hơn và dễ gỡ lỗi hơn. Viết thêm 3 dòng mã sẽ không làm gãy ngón tay của bạn, nhưng sẽ giúp người tiếp theo hiểu mã của bạn dễ dàng hơn.


1
Chỉ khi (như đã được nhận xét), khối vòng lặp không có hàng trăm dòng mã và logic bên trong không phải là khoa học tên lửa. Tôi nghĩ rằng tranh luận của bạn là ổn, nhưng tôi bỏ lỡ một cái gì đó như naming thingsđúng để hiểu những gì đang xảy ra và khi điều kiện phá vỡ được đáp ứng.
Laiv

2
@Laiv Nếu khối vòng lặp có hàng trăm dòng mã bên trong thì vấn đề lớn hơn câu hỏi nên viết điều kiện ngắt.
Aleksander

Đó là lý do tại sao tôi đề xuất bối cảnh câu trả lời, bởi vì không phải lúc nào cũng đúng.
Laiv

8

Nếu bạn đang lặp lại một bộ sưu tập trong đó các yếu tố nhất định nên được bỏ qua, thì tôi khuyên bạn nên sử dụng một tùy chọn mới:

  • Lọc bộ sưu tập của bạn trước khi lặp

Cả Java và C # đều làm điều này tương đối tầm thường. Tôi sẽ không ngạc nhiên nếu C ++ có cách tạo ra một trình vòng lặp ưa thích để bỏ qua các yếu tố nhất định không áp dụng. Nhưng đây chỉ thực sự là một lựa chọn nếu ngôn ngữ bạn chọn hỗ trợ và lý do bạn đang sử dụng breaklà vì có những điều kiện về việc bạn có xử lý một yếu tố hay không.

Mặt khác, có một lý do rất chính đáng để biến điều kiện của bạn thành một phần của vòng lặp for - giả sử đánh giá ban đầu của nó là chính xác.

  • Dễ thấy hơn khi vòng lặp kết thúc sớm

Tôi đã làm việc với mã trong đó vòng lặp for của bạn mất vài trăm dòng với một số điều kiện và một số phép toán phức tạp đang diễn ra ở đó. Các vấn đề bạn gặp phải với điều này là:

  • break Các lệnh được chôn trong logic rất khó tìm và đôi khi gây ngạc nhiên
  • Gỡ lỗi đòi hỏi phải bước qua từng vòng lặp để thực sự hiểu những gì đang diễn ra
  • Rất nhiều mã không có các phép tái cấu trúc có thể đảo ngược dễ dàng có sẵn hoặc các bài kiểm tra đơn vị để giúp tương đương về chức năng sau khi viết lại

Trong tình huống đó, tôi đề xuất các hướng dẫn sau theo thứ tự ưu tiên:

  • Lọc bộ sưu tập để loại bỏ sự cần thiết breaknếu có thể
  • Tạo phần có điều kiện của các điều kiện vòng lặp for
  • Đặt các điều kiện có breakở đầu vòng lặp nếu có thể
  • Thêm một chú thích vẽ nhiều dòng chú ý đến sự phá vỡ, và tại sao nó ở đó

Các nguyên tắc này luôn tuân theo tính chính xác của mã của bạn. Chọn tùy chọn có thể thể hiện tốt nhất ý định và cải thiện sự rõ ràng của mã của bạn.


1
Hầu hết câu trả lời này là tốt. Nhưng ... tôi có thể thấy cách lọc một bộ sưu tập có thể loại bỏ nhu cầu continuebáo cáo, nhưng tôi không thấy chúng giúp ích nhiều như thế nào break.
dùng949300

1
@ user949300 Một takeWhilebộ lọc có thể thay thế các breakcâu lệnh. Nếu bạn có một - Java ví dụ chỉ được họ tại Jave 9 (không phải là nó khó có thể thực hiện nó cho mình)
Idan Arye

1
Nhưng trong Java bạn vẫn có thể lọc trước khi lặp. Sử dụng các luồng (1.8 trở lên) hoặc sử dụng Bộ sưu tập Apache (1.7 trở về trước).
Laiv

@IdanArye - có các thư viện bổ trợ thêm các tiện ích như vậy vào API luồng Java (ví dụ: JOO-lambda )
Jules

@IdanArye chưa bao giờ nghe nói về TakeWhile trước bình luận của bạn. Hoàn toàn liên kết bộ lọc với một đơn đặt hàng tấn công tôi là bất thường và hack, vì trong bộ lọc "bình thường", mọi yếu tố sẽ trả về đúng hoặc sai, độc lập với những gì đến trước hoặc sau. Bằng cách này bạn có thể chạy mọi thứ song song.
dùng949300

8

Tôi thực sự khuyên bạn nên thực hiện phương pháp ít bất ngờ nhất trừ khi bạn đạt được một lợi ích đáng kể khi làm khác.

Mọi người không đọc từng chữ cái khi đọc một từ và họ không đọc từng chữ khi đọc một cuốn sách - nếu họ giỏi đọc, họ nhìn vào các phác thảo của các từ và câu và để cho bộ não của họ lấp đầy phần còn lại .

Vì vậy, rất có thể nhà phát triển thỉnh thoảng sẽ cho rằng đây chỉ là một tiêu chuẩn cho vòng lặp và thậm chí không nhìn vào nó:

for(int i=0;i<array.length&&!condition;i++)

Nếu bạn muốn sử dụng phong cách đó bất kể, tôi khuyên bạn nên thay đổi các bộ phận for(int i=0;i<;i++)nói với bộ não của người đọc rằng đây là một tiêu chuẩn cho vòng lặp.


Một lý do khác để đi cùng if- breaklà bạn không thể luôn luôn sử dụng phương pháp của mình với forđiều kiện. Bạn phải sử dụng if- breakkhi điều kiện ngắt quá phức tạp để ẩn trong forcâu lệnh hoặc dựa vào các biến chỉ có thể truy cập bên trong forvòng lặp.

for(int i=0;i<array.length&&!((someWidth.X==17 && y < someWidth.x/2) || (y == someWidth.x/2 && someWidth.x == 18);i++)

Vì vậy, nếu bạn quyết định đi cùng if- break, bạn được bảo vệ. Nhưng nếu bạn quyết định đi theo for-conditions, bạn phải sử dụng kết hợp if- breakfor-conditions. Để thống nhất, bạn sẽ phải di chuyển các điều kiện qua lại giữa các forđiều kiện và if- breakbất cứ khi nào điều kiện thay đổi.


4

Chuyển đổi của bạn đang giả định rằng bất cứ điều gì conditionđược ước truetính khi bạn vào vòng lặp. Chúng tôi không thể nhận xét về tính chính xác của điều đó trong trường hợp này vì nó không được xác định.

Đã thành công trong việc "giảm dòng mã" ở đây, sau đó bạn có thể tiếp tục xem xét

for(int i=0;i<array.length;i++){
    // some other code
    if(condition){
        break;
    }
    // further code
}

và áp dụng chuyển đổi tương tự, đến

for(int i=0;i<array.length && !condition;i++){
    // some other code
    // further code
}

Đó là một chuyển đổi không hợp lệ, như bây giờ bạn vô điều kiện làm further codenơi mà trước đây bạn không làm.

Một sơ đồ chuyển đổi an toàn hơn nhiều là trích xuất some other codevà đánh giá conditionthành một hàm riêng biệt

bool evaluate_condition(int i) // This is an horrible name, but I have no context to improve it
{
     // some other code
     return condition;
}

for(int i=0;i<array.length && !evaluate_condition(i);i++){
    // further code
}

4

TL; DR Làm bất cứ điều gì đọc tốt nhất trong hoàn cảnh cụ thể của bạn

Hãy lấy ví dụ này:

for ( int i = 1; i < array.length; i++ ) 
{
    if(something) break;
    // perform operations
}

Trong trường hợp này, chúng tôi không muốn bất kỳ mã nào trong vòng lặp for thực thi nếu somethinglà đúng, do đó, việc di chuyển kiểm tra somethingvào trường điều kiện là hợp lý.

for ( int i = 1; i < array.length && !something; i++ )

Sau đó, một lần nữa, tùy thuộc vào việc somethingcó thể được đặt thành truetrước vòng lặp hay không, nhưng điều này có thể mang lại sự rõ ràng hơn:

if(!something)
{
    for ( int i = 1; i < array.length; i++ )
    {...}
}

Bây giờ hãy tưởng tượng điều này:

for ( int i = 1; i < array.length; i++ ) 
{
    // perform operations
    if(something) break;
    // perform more operations
}

Chúng tôi đang gửi một thông điệp rất rõ ràng ở đó. Nếu somethingtrở thành đúng trong khi xử lý mảng thì từ bỏ toàn bộ thao tác tại thời điểm này. Để di chuyển kiểm tra vào trường điều kiện bạn cần làm:

for ( int i = 1; i < array.length && !something; i++ ) 
{
    // perform operations
    if(!something)
    {
        // perform more operations
    }
}

Có thể cho rằng, "thông điệp" đã trở nên lầy lội và chúng tôi đang kiểm tra tình trạng lỗi ở hai nơi - điều gì xảy ra nếu điều kiện thất bại thay đổi và chúng ta quên một trong những nơi đó?

Tất nhiên có nhiều hình dạng khác nhau hơn cho một vòng lặp và điều kiện có thể, tất cả đều mang sắc thái riêng.

Viết mã để nó đọc tốt (điều này rõ ràng là hơi chủ quan) và chính xác. Bên ngoài một bộ hướng dẫn mã hóa hà khắc, tất cả các "quy tắc" đều có ngoại lệ. Viết những gì bạn cảm thấy thể hiện quyết định tốt nhất và giảm thiểu cơ hội cho những sai lầm trong tương lai.


2

Mặc dù nó có thể hấp dẫn bởi vì bạn thấy tất cả các điều kiện trong tiêu đề vòng lặp và breakcó thể gây nhầm lẫn bên trong các khối lớn, forvòng lặp là một mẫu mà hầu hết các lập trình viên mong đợi lặp trong phạm vi nào đó.

Đặc biệt là khi bạn làm như vậy, việc thêm một điều kiện phá vỡ bổ sung có thể gây nhầm lẫn và khó xem liệu nó có tương đương về mặt chức năng hay không.

Thường là một lựa chọn tốt là sử dụng một whilehoặc do {} whilevòng lặp mà kiểm tra cả hai điều kiện, giả sử mảng của bạn có ít nhất một phần tử.

int i=0
do {
    // some other code
    i++; 
} while(i < array.length && condition);

Sử dụng một vòng lặp chỉ kiểm tra một điều kiện và không chạy mã từ tiêu đề vòng lặp, bạn sẽ thấy rất rõ khi điều kiện được kiểm tra và điều kiện thực tế và mã nào có tác dụng phụ.


Mặc dù câu hỏi này đã có sẵn một số câu trả lời tốt, tôi muốn đặc biệt nêu lên câu hỏi này bởi vì nó tạo ra một điểm quan trọng: với tôi, có bất cứ điều gì khác ngoài việc kiểm tra ràng buộc đơn giản trong vòng lặp for ( for(...; i <= n; ...)) thực sự làm cho mã khó đọc hơn vì tôi có dừng lại và suy nghĩ về những gì đang xảy ra ở đây và có thể bỏ lỡ điều kiện hoàn toàn trên đường chuyền đầu tiên bởi vì tôi không mong đợi nó sẽ ở đó. Đối với tôi, một forvòng lặp ngụ ý rằng bạn đang lặp trên một phạm vi nào đó, nếu có một điều kiện thoát đặc biệt thì nó phải được làm rõ ràng bằng một ifhoặc bạn có thể sử dụng một while.
CompuChip

1

Điều này thật tệ, vì mã được đọc bởi con người - các forvòng lặp không thành ngữ thường khó hiểu, điều đó có nghĩa là các lỗi có nhiều khả năng được ẩn ở đây, nhưng mã gốc có thể đã được cải thiện trong khi giữ cả hai ngắn gọn và dễ đọc

Nếu điều bạn muốn là tìm một giá trị trong một mảng (mẫu mã được cung cấp có phần chung chung, nhưng cũng có thể là mẫu này), bạn chỉ có thể cố gắng sử dụng một hàm được cung cấp bởi ngôn ngữ lập trình dành riêng cho mục đích đó. Ví dụ: mã giả, vì ngôn ngữ lập trình không được chỉ định, các giải pháp thay đổi tùy theo ngôn ngữ cụ thể).

array.find(item -> item.valid)

Điều này đáng chú ý nên rút ngắn giải pháp trong khi đơn giản hóa nó vì mã của bạn nói cụ thể những gì bạn cần.


1

Vài lý do theo ý kiến ​​của tôi mà nói, bạn không nên.

  1. Điều này làm giảm khả năng đọc.
  2. Đầu ra có thể không giống nhau. Tuy nhiên, nó có thể được thực hiện với một do...whilevòng lặp (không while) vì điều kiện được kiểm tra sau khi thực thi mã.

Nhưng trên hết, hãy xem xét điều này,

for(int i=0;i<array.length;i++){

    // Some other code
    if(condition1){
        break;
    }

    // Some more code
    if(condition1){
        break;
    }

    // Some even more code
}

Bây giờ, bạn có thể thực sự đạt được nó bằng cách thêm các điều kiện này vào forđiều kiện đó không?


" Mở " là gì? Bạn có nghĩa là " ý kiến "?
Peter Mortensen

Bạn có ý gì bởi "Vài lý do trong sự cởi mở nói rằng, bạn không nên" ? Bạn có thể xây dựng?
Peter Mortensen

0

Tôi nghĩ loại bỏ breakcó thể là một ý tưởng tốt, nhưng không phải để lưu các dòng mã.

Ưu điểm của việc viết nó mà không có breakđiều kiện hậu của vòng lặp là rõ ràng và rõ ràng. Khi bạn hoàn thành vòng lặp và thực hiện dòng tiếp theo của chương trình, điều kiện vòng lặp của bạn i < array.length && !conditionphải là sai. Do đó, hoặc ilớn hơn hoặc bằng chiều dài mảng của bạn hoặc conditionlà đúng.

Trong phiên bản với break, có một gotcha ẩn. Điều kiện vòng lặp nói rằng vòng lặp chấm dứt khi bạn chạy một số mã khác cho từng chỉ số mảng hợp lệ, nhưng thực tế có một breakcâu lệnh có thể chấm dứt vòng lặp trước đó và bạn sẽ không thấy nó trừ khi bạn xem lại mã vòng lặp rất cẩn thận. Và các máy phân tích tĩnh tự động, bao gồm tối ưu hóa trình biên dịch, cũng có thể gặp khó khăn trong việc suy luận các điều kiện hậu của vòng lặp là gì.

Đây là lý do ban đầu Edgar Dijkstra đã viết về Goto được coi là có hại. Đây không phải là vấn đề về phong cách: thoát ra khỏi một vòng lặp khiến cho việc lập luận chính thức về tình trạng của chương trình trở nên khó khăn. Tôi đã viết rất nhiều break;báo cáo trong cuộc sống của mình, và một khi đã trưởng thành, tôi thậm chí đã viết một goto(để thoát ra khỏi nhiều cấp độ của vòng lặp bên trong quan trọng về hiệu suất và sau đó tiếp tục với một nhánh khác của cây tìm kiếm). Tuy nhiên, tôi có nhiều khả năng viết một returncâu lệnh có điều kiện từ một vòng lặp (không bao giờ chạy mã sau vòng lặp và do đó không thể làm hỏng các điều kiện hậu của nó) hơn a break.

Bản thảo

Trong thực tế, tái cấu trúc mã để đưa ra hành vi tương tự thực sự có thể cần nhiều dòng mã hơn. Tái cấu trúc của bạn có thể hợp lệ đối với một số mã khác hoặc nếu chúng tôi có thể chứng minh rằng điều đó conditionsẽ bắt đầu sai. Nhưng ví dụ của bạn là tương đương trong trường hợp chung với:

if ( array.length > 0 ) {
  // Some other code.
}
for ( int i = 1; i < array.length && !condition; i++ ) {
  // Some other code.
}

Nếu một số mã khác không phải là một lớp lót, thì bạn có thể chuyển nó sang một hàm để không lặp lại chính mình, và sau đó bạn gần như đã tái cấu trúc vòng lặp của mình thành một hàm đệ quy đuôi.

Tuy nhiên, tôi nhận ra đây không phải là điểm chính trong câu hỏi của bạn và bạn đã giữ nó đơn giản.


Vấn đề không phải là sự phá vỡ làm cho nó khó tranh luận về mã, vấn đề là sự phá vỡ ở những nơi ngẫu nhiên làm cho mã trở nên phức tạp. Nếu điều đó thực sự cần thiết, thì không khó để tranh luận vì sự phá vỡ, nhưng vì mã cần phải làm những việc phức tạp.
gnasher729

Đây là lý do tại sao tôi không đồng ý: nếu bạn biết không có breaktuyên bố nào , thì bạn sẽ biết điều kiện vòng lặp là gì và vòng lặp dừng khi điều kiện đó là sai. Nếu có break? Sau đó, có nhiều cách vòng lặp có thể chấm dứt, và trong trường hợp chung, tìm ra khi nào một trong số chúng có thể dừng vòng lặp đó thực sự là giải quyết vấn đề Dừng. Trong thực tế, lo lắng lớn hơn của tôi là vòng lặp trông giống như nó làm một việc, nhưng thực ra, bất cứ ai đã viết mã sau vòng lặp đều bỏ qua một trường hợp cạnh bị chôn vùi trong logic phức tạp của nó. Thứ trong điều kiện vòng lặp là khó bỏ lỡ.
Davislor

0

Có, nhưng cú pháp của bạn là độc đáo và do đó xấu (tm)

Hy vọng rằng ngôn ngữ của bạn hỗ trợ một cái gì đó như

while(condition) //condition being a condition which if true you want to continue looping
{
    do thing; //your conditionally executed code goes here
    i++; //you can use a counter if you want to reference array items in order of index
}

2
có lẽ tôi nên sử dụng một từ khác cho 'điều kiện' Tôi không có nghĩa theo nghĩa đen là cùng một điều kiện chính xác trong ví dụ
Ewan

1
Không chỉ là vòng lặp for thậm chí không độc đáo từ xa, hoàn toàn không có lý do gì vòng lặp while của hình thức này tốt hơn vòng lặp tương đương. Trong thực tế, hầu hết mọi người sẽ nói nó tồi tệ hơn, chẳng hạn như các tác giả của Nguyên tắc cốt lõi CPP
Sean Burton

2
Một số người chỉ ghét sự thay thế sáng tạo. Hoặc nhìn vào một vấn đề theo một cách khác với cách đặt ra trong đầu họ. Hay nói chung hơn, lừa thông minh. Tôi cảm thấy nỗi đau của bạn bro!
Martin Maat

1
@sean xin lỗi anh bạn, tôi biết mọi người ghét bị nói rằng họ sai, nhưng tôi giới thiệu bạn đến es.77
Ewan

2
Điều này whilenhấn mạnh con quái vật trong mã - ngoại lệ được ẩn trong mã thường quy / trần tục. Logic kinh doanh là phía trước và trung tâm, các cơ chế lặp được giảm bớt. Nó tránh được phản ứng "đợi một chút ..." đối với forphương án vòng lặp. Câu trả lời này khắc phục vấn đề. hoàn toàn bỏ phiếu.
radarbob

0

Nếu bạn lo lắng về một fortuyên bố quá phức tạp khó đọc, đó chắc chắn là một mối quan tâm hợp lệ. Lãng phí không gian dọc cũng là một vấn đề (mặc dù ít quan trọng hơn).

(Cá nhân tôi không thích các quy tắc mà ifbáo cáo phải luôn có niềng răng, trong đó phần lớn là nguyên nhân của không gian dọc lãng phí ở đây. Lưu ý rằng vấn đề là lãng phí không gian thẳng đứng. Sử dụng không gian thẳng đứng với dòng có ý nghĩa không phải là một vấn đề.)

Một lựa chọn là chia nó thành nhiều dòng. Đây chắc chắn là những gì bạn nên làm nếu bạn đang sử dụng các forcâu lệnh thực sự phức tạp , với nhiều biến và điều kiện. (Điều này đối với tôi là sử dụng tốt hơn không gian theo chiều dọc, đưa ra càng nhiều dòng càng tốt một cái gì đó có ý nghĩa.)

for (
   int i = 0, j = 0;
   i < array.length && !condition;
   i++, j++
) {
   // stuff
}

Một cách tiếp cận tốt hơn có thể là cố gắng sử dụng một hình thức khai báo và ít bắt buộc hơn. Điều này sẽ thay đổi tùy thuộc vào ngôn ngữ hoặc bạn có thể cần phải viết các chức năng của riêng mình để kích hoạt loại điều này. Nó cũng phụ thuộc vào những gì conditionthực sự là. Có lẽ nó phải có khả năng thay đổi bằng cách nào đó, tùy thuộc vào những gì trong mảng.

array
.takeWhile(condition)  // condition can be item => bool or (item, index) => bool
.forEach(doSomething); // doSomething can be item => void or (item, index) => void

Chia một fortuyên bố phức tạp hoặc bất thường thành nhiều dòng là ý tưởng tốt nhất trong toàn bộ cuộc thảo luận này
user949300

0

Một điều đã bị lãng quên: Khi tôi thoát khỏi một vòng lặp (không thực hiện tất cả các lần lặp có thể, bất kể được triển khai như thế nào), đó thường là trường hợp không chỉ tôi muốn thoát khỏi vòng lặp, tôi cũng sẽ muốn biết tại sao điều này xảy ra . Ví dụ, nếu tôi tìm kiếm một mục X, tôi không chỉ thoát khỏi vòng lặp, tôi cũng muốn biết rằng X đã được tìm thấy và nó ở đâu.

Trong tình huống đó, có một điều kiện bên ngoài vòng lặp là khá tự nhiên. Vì vậy, tôi bắt đầu với

bool found = false;
size_t foundIndex;

và trong vòng lặp tôi sẽ viết

if (condition) {
    found = true;
    foundIndex = i;
    break;
}

với vòng lặp for

for (i = 0; i < something && ! found; ++i)

Bây giờ kiểm tra điều kiện trong vòng lặp là khá tự nhiên. Việc có câu lệnh "break" hay không phụ thuộc vào việc có xử lý thêm trong vòng lặp mà bạn muốn tránh hay không. Nếu một số xử lý là cần thiết và xử lý khác là không, bạn có thể có một cái gì đó như

if (! found) { ... }

một nơi nào đó trong vòng lặp của bạ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.