Điều này biện minh cho tuyên bố goto?


15

Tôi đã bắt gặp câu hỏi này một giây trước và tôi đang rút một số tài liệu ra khỏi đó: Có tên nào cho cấu trúc 'break n' không?

Đây dường như là một cách phức tạp không cần thiết để mọi người phải hướng dẫn chương trình thoát ra khỏi vòng lặp lồng nhau kép:

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

Tôi biết sách giáo khoa muốn nói các tuyên bố goto là ma quỷ, và bản thân tôi không thích chúng, nhưng điều này có biện minh cho một ngoại lệ cho quy tắc này không?

Tôi đang tìm kiếm câu trả lời liên quan đến n-lồng cho các vòng lặp.

LƯU Ý: Cho dù bạn trả lời có, không, hoặc ở đâu đó ở giữa, các câu trả lời hoàn toàn gần gũi đều không được chào đón. Đặc biệt nếu câu trả lời là không, thì hãy cung cấp một lý do chính đáng, hợp lý tại sao (điều này không quá xa so với quy định của Stack Exchange).


2
Nếu bạn có thể viết điều kiện vòng lặp bên trong for (j = 10; j > 0 && !broken; j--)thì bạn sẽ không cần break;câu lệnh. Nếu bạn di chuyển khai báo brokenđến trước khi vòng lặp bên ngoài bắt đầu, thì nếu điều đó có thể được viết như vậy for (i = 0; i < 10 && !broken; i++)thì tôi nghĩ không break; cần thiết.
Thất vọngWithFormsDesigner

8
Lấy ví dụ là từ C # - tham chiếu C # trên goto: goto (Tham chiếu C #) - "Câu lệnh goto cũng hữu ích để thoát khỏi các vòng lặp lồng nhau sâu sắc."

OMG, tôi không bao giờ biết c # đã có một tuyên bố goto! Những điều bạn học được trên StackExchange. (Tôi không thể nhớ lần cuối cùng tôi muốn có khả năng tuyên bố goto, nó dường như không bao giờ xuất hiện).
John Wu

6
IMHO nó thực sự phụ thuộc vào "tôn giáo lập trình" và moonphase hiện tại của bạn. Đối với tôi (và hầu hết mọi người xung quanh), tôi có thể sử dụng một chiếc goto để thoát khỏi tổ sâu nếu vòng lặp khá nhỏ và sự thay thế sẽ là một kiểm tra thừa của biến bool được giới thiệu chỉ để phá vỡ điều kiện vòng lặp. Điều này nhiều hơn khi bạn muốn thoát ra giữa chừng cơ thể, hoặc khi sau vòng lặp trong cùng, có một số mã cũng cần phải kiểm tra bool.
PlasmaHH

1
@JohnWu gotothực sự được yêu cầu trong một chuyển đổi C # để chuyển sang trường hợp khác sau khi thực hiện bất kỳ mã nào trong trường hợp đầu tiên. Đó là trường hợp duy nhất tôi đã sử dụng goto, mặc dù.
Dan Lyons

Câu trả lời:


33

Nhu cầu rõ ràng cho một tuyên bố đi đến phát sinh từ việc bạn chọn các biểu thức điều kiện kém cho các vòng lặp .

Bạn nói rằng bạn muốn vòng lặp bên ngoài tiếp tục miễn là vòng lặp i < 10trong cùng tiếp tục miễn là j > 0.

Nhưng trong thực tế đó không phải là điều bạn muốn , bạn chỉ đơn giản là không nói với các vòng lặp tình trạng thực mà bạn muốn họ đánh giá, sau đó bạn muốn giải quyết nó bằng cách sử dụng break hoặc goto.

Nếu bạn nói với các vòng lặp ý định thực sự của bạn để bắt đầu , thì không cần phải nghỉ hoặc goto.

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}

Xem ý kiến ​​của tôi về các câu trả lời khác. Mã ban đầu in iở cuối vòng ngoài, do đó, việc đoản mạch mức tăng là rất quan trọng để làm cho mã trông tương đương 100%. Tôi không nói bất cứ điều gì trong câu trả lời của bạn là sai, tôi chỉ muốn chỉ ra rằng việc phá vỡ vòng lặp ngay lập tức là một khía cạnh quan trọng của mã.
pswg

1
@pswg Tôi không thấy bất kỳ mã in nào iở cuối vòng ngoài trong câu hỏi của OP.
Tulains Córdova

OP đã tham chiếu mã từ câu hỏi của tôi ở đây , nhưng bỏ qua phần đó. Tôi đã không đánh giá thấp bạn, BTW. Câu trả lời là hoàn toàn chính xác liên quan đến việc lựa chọn chính xác các điều kiện vòng lặp.
pswg

3
@pswg Tôi đoán nếu bạn thực sự thực sự thực sự muốn biện minh cho một goto, bạn có thể thiết lập một vấn đề cần một mặt khác, tôi đã lập trình chuyên nghiệp trong hai thập kỷ và không có một vấn đề nào trong thế giới thực mà cần một.
Tulains Córdova

1
Mục đích của mã không cung cấp trường hợp sử dụng hợp lệ cho goto(tôi chắc chắn có những câu hỏi hay hơn), ngay cả khi nó đưa ra một câu hỏi dường như sử dụng nó như vậy, nhưng để minh họa hành động cơ bản của các break(n)ngôn ngữ không ' t ủng hộ nó
pswg

34

Trong trường hợp này, bạn có thể cấu trúc lại mã thành một thói quen riêng, và sau đó chỉ returntừ vòng lặp bên trong. Điều đó sẽ phủ nhận sự cần thiết phải thoát ra khỏi vòng lặp bên ngoài:

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}

2
Điều đó có nghĩa là có, những gì bạn thể hiện là một cách phức tạp không cần thiết để thoát khỏi các vòng lặp lồng nhau, nhưng không, có một cách để cấu trúc lại mà không làm cho một goto hấp dẫn hơn.
Roger

2
Chỉ cần một lưu ý phụ: trong mã gốc, nó sẽ in isau khi kết thúc vòng lặp bên ngoài. Vì vậy, để thực sự phản ánh đó ilà giá trị quan trọng, ở đây nên return true;return false;cả hai nên return i;.
pswg

+1, sắp đăng câu trả lời này và tin rằng đây sẽ là câu trả lời được chấp nhận. Tôi không thể tin rằng câu trả lời được chấp nhận đã được chấp nhận vì phương pháp đó chỉ hoạt động đối với một tập hợp con cụ thể của các thuật toán vòng lặp lồng nhau và không nhất thiết làm cho mã sạch hơn nhiều. Phương pháp trong câu trả lời này hoạt động nói chung.
Ben Lee

6

Không, tôi không nghĩ gotolà cần thiết. Nếu bạn viết nó như thế này:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

&&!brokentrên cả hai vòng cho phép bạn thoát ra khỏi cả hai vòng khi i==j.

Đối với tôi, cách tiếp cận này là thích hợp hơn. Các điều kiện vòng lặp cực kỳ rõ ràng : i<10 && !broken.

Có thể xem phiên bản làm việc tại đây: http://rextester.com/KFMH48414

Mã số:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

Đầu ra:

vòng lặp bị phá vỡ khi i == j == 1
2
0

Tại sao bạn thậm chí sử dụng brokentrong ví dụ đầu tiên của bạn ở tất cả ? Chỉ cần sử dụng break trên vòng lặp bên trong. Ngoài ra, nếu bạn hoàn toàn phải sử dụng broken, tại sao phải khai báo nó bên ngoài các vòng lặp? Chỉ cần khai báo nó bên trong các ivòng lặp. Đối với whilevòng lặp, xin vui lòng không sử dụng đó. Tôi thành thật nghĩ rằng nó quản lý để thậm chí ít đọc hơn phiên bản goto.
luiscubal

@luiscubal: Một biến có tên brokenđược sử dụng vì tôi không thích sử dụng breakcâu lệnh (ngoại trừ trong trường hợp chuyển đổi, nhưng đó là về nó). Nó được khai báo bên ngoài vòng lặp bên ngoài trong trường hợp bạn muốn phá vỡ TẤT CẢ các vòng lặp khi i==j. Nói chung, bạn đúng, brokenchỉ cần khai báo trước vòng lặp ngoài cùng mà nó sẽ phá vỡ.
Thất vọngWithFormsDesigner

Trong bản cập nhật của bạn, i==2ở cuối vòng lặp. Điều kiện thành công cũng sẽ làm ngắn mạch mức tăng nếu nó tương đương 100% với a break 2.
pswg

2
Cá nhân tôi thích broken = (j == i)hơn là nếu và, nếu ngôn ngữ cho phép, đặt khai báo bị hỏng bên trong khởi tạo vòng lặp bên ngoài. Mặc dù thật khó để nói những điều này cho ví dụ cụ thể này, vì toàn bộ vòng lặp là không có tác dụng (nó không trả về gì và không có tác dụng phụ) và nếu nó làm gì đó, nó có thể làm điều đó bên trong nếu (mặc dù tôi vẫn thích broken = (j ==i); if broken { something(); }hơn cá nhân)
Martijn

@Martijn Trong mã gốc của tôi, nó được in isau khi kết thúc vòng lặp bên ngoài. Nói cách khác, suy nghĩ thực sự quan trọng duy nhất là khi chính xác nó thoát ra khỏi vòng lặp bên ngoài.
pswg

3

Câu trả lời ngắn gọn là "nó phụ thuộc". Tôi ủng hộ để lựa chọn về mức độ dễ đọc và dễ hiểu của mã của bạn. Cách goto có thể dễ đọc hơn điều chỉnh điều kiện hoặc ngược lại. Bạn cũng có thể tính đến nguyên tắc ít bất ngờ nhất, hướng dẫn được sử dụng trong cửa hàng / dự án và tính nhất quán của cơ sở mã. Chẳng hạn, goto rời khỏi switch hoặc để xử lý lỗi là một cách làm phổ biến trong cơ sở mã hạt nhân Linux. Cũng lưu ý rằng một số lập trình viên có thể gặp vấn đề khi đọc mã của bạn (hoặc được nêu lên với câu thần chú "goto là ác" hoặc không học vì giáo viên của họ nghĩ vậy) và một vài trong số họ thậm chí có thể "phán xét bạn".

Nói chung, một goto đi xa hơn trong cùng một chức năng thường được chấp nhận, đặc biệt là nếu bước nhảy không quá dài. Trường hợp sử dụng của bạn để lại một vòng lặp lồng nhau là một trong những ví dụ đã biết của goto "không quá ác".


2

Trong trường hợp của bạn, goto là một cải tiến đủ để trông hấp dẫn, nhưng nó không phải là một cải tiến quá đáng để phá vỡ quy tắc chống lại việc sử dụng chúng trong cơ sở mã của bạn.

Hãy nghĩ về người đánh giá trong tương lai của bạn: anh ấy sẽ phải suy nghĩ: "Người này có phải là một lập trình viên tồi hay thực tế đây là một trường hợp mà goto đáng giá không? Hãy để tôi dành 5 phút để tự mình quyết định hoặc nghiên cứu về nó Internet." Tại sao bạn lại làm điều đó với lập trình viên tương lai của mình khi bạn không thể sử dụng hoàn toàn goto?

Nói chung, tâm lý này áp dụng cho tất cả các mã "xấu" và thậm chí định nghĩa nó: nếu nó hoạt động ngay bây giờ và tốt trong trường hợp này, nhưng tạo ra nỗ lực để xác minh nó là chính xác, trong thời đại này, một goto sẽ luôn làm, sau đó không làm đi.

Điều đó đang được nói, vâng, bạn đã sản xuất một trong những trường hợp hơi gai góc. Bạn có thể:

  • sử dụng các cấu trúc điều khiển phức tạp hơn, như cờ boolean, để thoát ra khỏi cả hai vòng
  • đặt vòng lặp bên trong của bạn bên trong thói quen của riêng nó (có thể là lý tưởng)
  • đặt cả hai vòng trong một thói quen và trở lại thay vì phá vỡ

Thật khó cho tôi để tạo ra một trường hợp trong đó một vòng lặp đôi không gắn kết đến mức dù sao nó cũng không phải là thói quen của riêng nó.

Nhân tiện, cuộc thảo luận tốt nhất về điều này tôi đã thấy là Hoàn thành mã của Steve McConnell . Nếu bạn thích suy nghĩ về những vấn đề này một cách nghiêm túc, tôi thực sự khuyên bạn nên đọc, thậm chí là che đậy mặc dù nó rất lớn.


1
Đó là công việc lập trình viên của chúng tôi để đọc và hiểu mã. Không chỉ mã đơn giản trừ khi đó là bản chất của cơ sở mã của bạn. Có, và sẽ luôn có ngoại lệ đối với tính tổng quát (không phải quy tắc) đối với việc sử dụng gotos vì chi phí được hiểu. Bất cứ khi nào lợi ích vượt xa chi phí là khi sử dụng goto; như trường hợp của tất cả các quyết định mã hóa trong công nghệ phần mềm.
dietbuddha

1
@dietbuddha có chi phí trong việc vi phạm các quy ước mã hóa phần mềm được chấp nhận cần được tính toán đầy đủ. Có chi phí trong việc tăng tính đa dạng của các cấu trúc điều khiển được sử dụng trong một chương trình ngay cả khi cấu trúc điều khiển này có thể phù hợp với tình huống. Có chi phí trong việc phá vỡ phân tích tĩnh của mã. Nếu đây là tất cả vẫn còn giá trị thì tôi là ai để tranh luận.
djechlin

1

TLD; DR: Không cụ thể, có nói chung

Đối với ví dụ cụ thể bạn đã sử dụng, tôi sẽ nói không vì những lý do tương tự mà nhiều người khác đã chỉ ra. Bạn có thể dễ dàng cấu trúc lại mã thành một hình thức tương đương tốt mà không cần phải có goto.

Nói chung, công tố chống lại gotolà một tổng quát dựa trên bản chất được hiểu gotovà chi phí sử dụng nó. Một phần của công nghệ phần mềm đang đưa ra quyết định thực hiện. Biện minh cho những quyết định đó xảy ra bằng cách cân nhắc chi phí với lợi ích và bất cứ khi nào lợi ích lớn hơn chi phí thì việc thực hiện là hợp lý. Tranh cãi nảy sinh vì định giá khác nhau, cả về chi phí và lợi ích.

Vấn đề là sẽ luôn có những trường hợp ngoại lệ trong đó a gotolà hợp lý, nhưng chỉ đối với một số vì định giá khác nhau. Điều đáng tiếc là nhiều kỹ sư chỉ đơn giản loại trừ các kỹ thuật trong sự thiếu hiểu biết có chủ đích vì họ đọc nó trên internet thay vì dành thời gian để hiểu lý do tại sao.


Bạn có thể giới thiệu bất kỳ bài viết nào về tuyên bố goto giải thích thêm về chi phí không?
Thys

-1

Tôi không nghĩ trường hợp này biện minh cho một ngoại lệ, vì điều này có thể được viết là:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
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.