it-swarm-vi.com

Làm thế nào bạn sẽ cấu trúc lại các Báo cáo IF lồng nhau?

Tôi đang bay vòng quanh thế giới blog lập trình khi tôi tình cờ thấy bài đăng này về GOTO:

http://giuliozambon.blogspot.com/2010/12/programmer-tabu.html

Ở đây, người viết nói về việc "người ta phải đi đến kết luận rằng có những tình huống mà các GOTO tạo ra mã dễ đọc hơn và dễ bảo trì hơn" và sau đó tiếp tục đưa ra một ví dụ tương tự như sau:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

Sau đó, người viết đề xuất rằng việc sử dụng GOTO sẽ giúp mã này dễ đọc và duy trì hơn nhiều.

Cá nhân tôi có thể nghĩ ra ít nhất 3 cách khác nhau để làm phẳng nó và làm cho mã này dễ đọc hơn mà không cần dùng đến phương pháp phá vỡ dòng chảy của GOTO. Đây là hai yêu thích của tôi.

1 - Hàm nhỏ lồng nhau. Lấy mỗi if và khối mã của nó và biến nó thành một hàm. Nếu kiểm tra boolean thất bại, chỉ cần trả lại. Nếu nó vượt qua, sau đó gọi hàm tiếp theo trong chuỗi. (Boy, nghe có vẻ giống như đệ quy, bạn có thể làm điều đó trong một vòng lặp duy nhất với các con trỏ hàm không?)

2 - Biến số Sentinal. Với tôi đây là điều dễ nhất. Chỉ cần sử dụng biến blnContinueProcessing và kiểm tra xem liệu nó có còn đúng trong kiểm tra if của bạn không. Sau đó, nếu kiểm tra thất bại, đặt biến thành sai.

Có bao nhiêu cách khác nhau để loại vấn đề mã hóa này có thể được tái cấu trúc để giảm lồng và tăng khả năng bảo trì?

34
saunderl

Thật sự rất khó để nói mà không biết các kiểm tra khác nhau tương tác như thế nào. Tái cấu trúc nghiêm ngặt có thể theo thứ tự. Tạo một cấu trúc liên kết của các đối tượng thực thi khối chính xác tùy thuộc vào loại của chúng, cũng là một mẫu chiến lược hoặc mẫu trạng thái có thể thực hiện thủ thuật.

Không biết phải làm gì tốt nhất tôi sẽ xem xét hai phép tái cấu trúc đơn giản có thể được tái cấu trúc thêm bằng cách trích xuất thêm các phương thức.

Cách đầu tiên tôi không thực sự thích vì tôi luôn thích làm điểm thoát trong một phương thức (tốt nhất là một)

if (!Check#1)
{ 
    return;
}
CodeBlock#1

if (!Check#2)
{
    return;
}
CodeBlock#2
...

Cái thứ hai loại bỏ nhiều lợi nhuận nhưng cũng gây ra nhiều tiếng ồn. (về cơ bản nó chỉ loại bỏ việc làm tổ)

bool stillValid = Check#1
if (stillValid)
{
  CodeBlock#1
}

stillValid = stillValid && Check#2
if (stillValid)
{
  CodeBlock#2
}

stillValid = stillValid && Check#3
if (stillValid)
{
  CodeBlock#3
}
...

Cái cuối cùng này có thể được tái cấu trúc độc đáo thành các hàm và khi bạn đặt cho chúng tên tốt thì kết quả có thể hợp lý ';

bool stillValid = DoCheck1AndCodeBlock1()
stillValid = stillValid && DoCheck2AndCodeBlock2()
stillValid = stillValid && DoCheck3AndCodeBlock3()

public bool DoCheck1AndCodeBlock1()
{
   bool returnValid = Check#1
   if (returnValid)
   {
      CodeBlock#1
   }
   return returnValid
}

Tất cả trong tất cả có nhiều khả năng là lựa chọn tốt hơn

33
KeesDijk

Điều đó được gọi là "Mã mũi tên" vì hình dạng của mã với thụt lề thích hợp.

Jeff Atwood đã có một bài đăng blog hay trên Coding H khiếp sợ về cách làm phẳng các mũi tên:

Làm phẳng mã mũi tên

Đọc bài viết để điều trị đầy đủ, nhưng đây là những điểm chính ..

  • Thay thế điều kiện bằng các mệnh đề bảo vệ
  • Phân tách các khối có điều kiện thành các hàm riêng biệt
  • Chuyển đổi kiểm tra tiêu cực thành kiểm tra tích cực
  • Luôn luôn quay trở lại cơ hội càng sớm càng tốt từ chức năng
40
JohnFx

Tôi biết một số người sẽ tranh luận rằng đó là một chiếc goto, nhưng return; là tái cấu trúc rõ ràng, tức là.

if (!Check#1)
{ 
        return;
}
CodeBlock#1
if (!Check#2)
{
    return;
}
CodeBlock#2
.
.
.
if (Check#7)
{
    CodeBlock#7
}
else
{
    rest - of - the - program
}

Nếu nó thực sự chỉ là một loạt các kiểm tra bảo vệ trước khi chạy mã, thì điều này hoạt động tốt. Nếu nó phức tạp hơn thế, thì điều này sẽ chỉ làm cho nó đơn giản hơn một chút và bạn cần một trong những giải pháp khác.

26
Richard Gadsden

Mã spaghetti đó có vẻ như là ứng cử viên hoàn hảo để tái cấu trúc nó thành một máy trạng thái.

10
Pemdas

Điều này có thể đã được đề cập, nhưng câu trả lời "đi tới" (ý định chơi chữ) của tôi đối với "phản hạt đầu mũi tên" được hiển thị ở đây là để đảo ngược các if. Kiểm tra điều ngược lại với các điều kiện hiện tại (đủ dễ với một toán tử không) và trả về từ phương thức nếu điều đó là đúng. Không có gotos (mặc dù nói theo phương pháp giáo dục, trả về khoảng trống ít hơn một bước nhảy đến dòng mã gọi, với bước bổ sung tầm thường là bật một khung ra khỏi ngăn xếp).

Trường hợp tại điểm, đây là bản gốc:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

Tái cấu trúc:

if (!Check#1) return;

CodeBlock#1

if (!Check#2) return;

CodeBlock#2

if (!Check#3) return;

CodeBlock#3

if (!Check#4) return;

CodeBlock#4

if (!Check#5) return;

CodeBlock#5

if (!Check#6) return;

CodeBlock#6

if (Check#7)
    CodeBlock#7
else
{
    //rest of the program
}

Tại mỗi nếu, về cơ bản, chúng tôi kiểm tra xem nếu chúng ta nên tiếp tục. Nó hoạt động chính xác giống như bản gốc chỉ với một cấp độ lồng.

Nếu có một cái gì đó nằm ngoài phần cuối của đoạn mã này cũng sẽ được chạy, hãy trích xuất mã này thành phương thức riêng của nó và gọi nó từ bất cứ nơi nào mã này hiện đang tồn tại, trước khi tiếp tục mã sẽ xuất hiện sau đoạn mã này. Đoạn mã tự nó đủ dài, được cung cấp đủ LỘC thực tế trong mỗi khối mã, để biện minh cho việc tách ra một số phương thức khác, nhưng tôi lạc đề.

6
KeithS

Nếu bạn thường xuyên có logic thực sự yêu cầu kiểm tra if kim tự tháp này, thì có lẽ bạn (theo nghĩa bóng) sử dụng cờ lê để đóng đinh. Bạn sẽ được phục vụ tốt hơn khi thực hiện loại logic xoắn, phức tạp đó bằng ngôn ngữ hỗ trợ loại logic xoắn và phức tạp đó với cấu trúc tốt hơn tuyến tính if/else if/else- cấu trúc kiểu.

Các ngôn ngữ có thể phù hợp hơn với loại cấu trúc này có thể bao gồm SNOBOL4 (với phân nhánh kiểu GOTO kép kỳ quái của nó) hoặc các ngôn ngữ logic như PrologMercury (với khả năng thống nhất và quay lui của chúng, không đề cập đến DCG của chúng cho biểu hiện khá ngắn gọn của các quyết định phức tạp) .

Tất nhiên, nếu đây không phải là một lựa chọn (vì hầu hết các lập trình viên, một cách bi thảm, không phải là đa âm), những ý tưởng mà người khác đưa ra cũng tốt như sử dụng nhiều OOP - cấu trúc hoặc chia các mệnh đề thành các hàm hoặc thậm chí, nếu bạn tuyệt vọng, và đừng bận tâm đến thực tế là hầu hết mọi người đều thấy chúng không thể đọc được, sử dụng máy trạng thái .

Tuy nhiên, giải pháp của tôi vẫn tiếp cận với một ngôn ngữ cho phép bạn diễn đạt logic nào bạn đang cố gắng diễn đạt (giả sử đây là điều phổ biến trong mã của bạn) theo cách dễ đọc hơn.

1

Từ tại đây :

Rất thường xuyên khi tôi nhìn vào một bộ pac-man nếu tôi thấy rằng nếu tôi rút ra một cái gì đó giống như một bảng sự thật về tất cả các điều kiện liên quan, tôi có thể tìm ra một cách tốt hơn để giải quyết vấn đề.

Bằng cách đó, bạn cũng có thể đánh giá liệu có một phương pháp tốt hơn hay không, làm thế nào bạn có thể phá vỡ nó hơn nữa và (và đây là một phương pháp lớn với loại mã này) xem có lỗ hổng nào trong logic không.

Làm xong việc đó có lẽ bạn có thể chia nó thành một vài câu lệnh chuyển đổi và một vài phương thức và lưu lại cái mook nghèo tiếp theo phải trải qua rất nhiều vấn đề.

1
Jim G.

Cá nhân, tôi thích gói các câu lệnh if này thành các hàm riêng biệt trả về bool nếu hàm thành công.

Cấu trúc trông như sau:


if (DoCheck1AndCodeBlock1() && 
    DoCheck2AndCodeBlock2() && 
    DoCheck3AndCodeBlock3()) 
{
   // ... you may perform the final operation here ....
}

Hạn chế duy nhất là các hàm này sẽ phải xuất dữ liệu bằng các thuộc tính được truyền bởi các biến tham chiếu hoặc biến thành viên.

1
gogisan

Sử dụng một OO tiếp cận một mẫu tổng hợp trong đó lá là điều kiện đơn giản và thành phần kết hợp các phần tử đơn giản làm cho loại mã này có thể mở rộng và thích ứng

1
guiman

Nếu bạn muốn một giải pháp hướng đối tượng có thể, mã trông giống như một ví dụ chính tắc để tái cấu trúc bằng cách sử dụng chuỗi mẫu thiết kế trách nhiệm .

0
FinnNk

Chia chúng ra thành các chức năng có thể giúp gì?

Bạn gọi hàm đầu tiên và khi hoàn thành, nó sẽ gọi hàm tiếp theo, việc đầu tiên mà hàm sẽ làm là kiểm tra kiểm tra hàm đó.

0
Toby

Nó phụ thuộc rất nhiều vào kích thước của từng khối mã và tổng kích thước, nhưng tôi có xu hướng phân tách bằng một "phương pháp trích xuất" thẳng trên một khối hoặc bằng cách di chuyển các phần vô điều kiện thành các phương thức riêng biệt. Nhưng hãy cẩn thận @KeesDijk đề cập. Cách tiếp cận trước đây cung cấp cho bạn một loạt các chức năng như

if (Check#1)
{
    CodeBlock#1
    NextFunction
}

Cái này có thể hoạt động tốt nhưng lại dẫn đến sự phình to mã và "phương pháp chỉ được sử dụng một lần". Vì vậy, tôi thường thích cách tiếp cận này:

if (CheckFunctionOne)
{
    MethodOneWithDescriptiveName
    if (CheckFunctionTwo)
    {
        MethodTwoWithDescriptiveName
        ....

Với việc sử dụng thích hợp các biến riêng và tham số truyền nó có thể được thực hiện. Có liếc nhìn lập trình biết chữ có thể giúp đỡ ở đây.

0
Мסž