Là sử dụng các khối thử bắt lồng nhau là một mô hình chống?


95

Đây có phải là một antipotype? Đó là một thực tế có thể chấp nhận?

    try {
        //do something
    } catch (Exception e) { 
        try {
            //do something in the same line, but being less ambitious
        } catch (Exception ex) {
            try {
                //Do the minimum acceptable
            } catch (Exception e1) {
                //More try catches?
            }
        }
    }

Bạn có thể cho chúng tôi trường hợp này? Tại sao bạn không thể xử lý mọi loại lỗi trong mức bắt cao nhất?
Morons

2
Gần đây tôi đã thấy loại mã này, được thực hiện bởi các lập trình viên chưa có kinh nghiệm, những người không thực sự biết họ đang gọi gì trong các khối thử và họ không muốn kiểm tra mã. Trong mẫu mã tôi đã thấy, Đó là thao tác tương tự nhưng được thực hiện mỗi lần với các tham số dự phòng.
Ông Smith

@LokiAstari-Ví dụ của bạn là một thử trong Phần cuối cùng .. Trường hợp không có Catch. Đây là phần lồng nhau trong phần Thử .. Nó khác.
Morons

4
Tại sao nó phải là một mô hình chống?

2
+1 cho "bắt thử nhiều hơn?"
JoelFan

Câu trả lời:


85

Điều này đôi khi không thể tránh khỏi, đặc biệt là nếu mã khôi phục của bạn có thể ném ngoại lệ.

Không xinh, nhưng đôi khi không có lựa chọn thay thế.


17
@MisterSmith - Không phải lúc nào.
Oded

4
Vâng, đây là loại những gì tôi đã cố gắng để có được. Tất nhiên, có một điểm trong các câu lệnh try / Catch lồng nhau của bạn, nơi bạn chỉ cần nói đủ là đủ. Tôi đã tạo ra một trường hợp để lồng nhau trái ngược với lần thử / bắt liên tiếp, nói rằng có những tình huống mà bạn chỉ muốn mã bên trong lần thử thứ hai thực thi nếu lần thử đầu tiên rơi xuống.
AndrewC

5
@MisterSmith: Tôi thích các lần thử thử lồng nhau hơn các lần bắt thử liên tiếp được kiểm soát một phần với các biến cờ (nếu chúng giống nhau về mặt chức năng).
Thất vọngWithFormsDesigner

31
thử {giao dịch.commit (); } bắt {thử {giao dịch.rollback (); } bắt {Severlogging ()} notsoseriouslogging (); } là một ví dụ về một lần thử bắt lồng cần thiết
Thanos Papathanasiou

3
Ít nhất là giải nén khối bắt cho một phương thức, các bạn! Ít nhất hãy làm cho điều này có thể đọc được.
Ông Nam Kỳ

43

Tôi không nghĩ nó là một antipotype, chỉ được sử dụng rộng rãi.

Hầu hết các thử bắt được lồng nhau thực sự là địa ngục có thể tránh được và xấu xí, thường là sản phẩm của các nhà phát triển cơ sở.

Nhưng có những lúc bạn không thể giúp nó.

try{
     transaction.commit();
   }catch{
     logerror();
     try{
         transaction.rollback(); 
        }catch{
         seriousLogging();
        }
   }

Ngoài ra, bạn sẽ cần thêm một bool ở đâu đó để biểu thị sự thất bại ...


19

Logic là tốt - nó có thể có ý nghĩa hoàn hảo trong một số tình huống để thử một cách tiếp cận dự phòng, bản thân nó có thể trải nghiệm các sự kiện đặc biệt .... do đó mô hình này là khá khó tránh khỏi.

Tuy nhiên tôi sẽ đề xuất những điều sau đây để làm cho mã tốt hơn:

  • Tái cấu trúc thử bên trong ... bắt các khối thành các chức năng riêng biệt, ví dụ attemptFallbackMethodattemptMinimalRecovery.
  • Hãy cụ thể hơn về các loại ngoại lệ cụ thể đang bị bắt. Bạn có thực sự mong đợi bất kỳ lớp con Exception nào không và nếu vậy bạn có thực sự muốn xử lý chúng theo cùng một cách không?
  • Xem xét liệu một finallykhối có thể có ý nghĩa hơn hay không - đây thường là trường hợp đối với bất kỳ thứ gì có cảm giác như "mã dọn sạch tài nguyên"

14

Không sao đâu. Một cấu trúc lại để xem xét là đẩy mã vào phương thức riêng của nó và sử dụng các lối thoát sớm để thành công, cho phép bạn viết các nỗ lực khác nhau để làm một cái gì đó ở cùng cấp độ:

try {
    // do something
    return;
} catch (Exception e) {
    // fall through; you probably want to log this
}
try {
    // do something in the same line, but being less ambitious
    return;
} catch (Exception e) {
    // fall through again; you probably want to log this too
}
try {
    // Do the minimum acceptable
    return;
} catch (Exception e) {
    // if you don't have any more fallbacks, then throw an exception here
}
//More try catches?

Một khi bạn đã phá vỡ nó như thế, bạn có thể nghĩ về việc gói nó trong một mẫu Chiến lược.

interface DoSomethingStrategy {
    public void doSomething() throws Exception;
}

class NormalStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something
    }
}

class FirstFallbackStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something in the same line, but being less ambitious
    }
}

class TrySeveralThingsStrategy implements DoSomethingStrategy {
    private DoSomethingStrategy[] strategies = {new NormalStrategy(), new FirstFallbackStrategy()};
    public void doSomething() throws Exception {
        for (DoSomethingStrategy strategy: strategies) {
            try {
                strategy.doSomething();
                return;
            }
            catch (Exception e) {
                // log and continue
            }
        }
        throw new Exception("all strategies failed");
    }
}

Sau đó, chỉ cần sử dụng TrySeveralThingsStrategy, đó là một loại chiến lược tổng hợp (hai mẫu cho giá của một!).

Một cảnh báo lớn: đừng làm điều này trừ khi các chiến lược của bạn đủ phức tạp hoặc bạn muốn có thể sử dụng chúng theo những cách linh hoạt. Mặt khác, bạn đang cho vay một vài dòng mã đơn giản với một lượng lớn hướng đối tượng không cần thiết.


7

Tôi không nghĩ rằng nó tự động là một mô hình chống, nhưng tôi sẽ tránh nó nếu tôi có thể tìm ra một cách dễ dàng và sạch sẽ hơn để làm điều tương tự. Nếu ngôn ngữ lập trình bạn đang làm việc có finallycấu trúc, trong một số trường hợp có thể giúp dọn sạch ngôn ngữ này.


6

Không phải là một mẫu chống mỗi se, mà là một mẫu mã cho biết bạn cần cấu trúc lại.

Và nó khá dễ, bạn chỉ cần biết một quy tắc ngón tay cái không viết nhiều hơn một khối thử trong cùng một phương thức. Nếu bạn biết rõ để viết mã liên quan với nhau, thường chỉ là sao chép và dán từng khối thử với khối bắt của nó và dán nó bên trong một phương thức mới, sau đó thay thế khối ban đầu bằng lệnh gọi phương thức này.

Quy tắc này dựa trên đề xuất của Robert C. Martin từ cuốn sách 'Clean Code':

nếu từ khóa 'thử' tồn tại trong một hàm, thì đó sẽ là từ đầu tiên trong hàm và sẽ không có gì sau các khối bắt / cuối cùng.

Một ví dụ nhanh về "giả-java". Giả sử chúng ta có một cái gì đó như thế này:

try {
    FileInputStream is = new FileInputStream(PATH_ONE);
    String configData = InputStreamUtils.readString(is);
    return configData;
} catch (FileNotFoundException e) {
    try {
        FileInputStream is = new FileInputStream(PATH_TWO);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        try {
            FileInputStream is = new FileInputStream(PATH_THREE);
            String configData = InputStreamUtils.readString(is);
            return configData;
        } catch (FileNotFoundException e) {
            return null;
        }
    }
}

Sau đó, chúng tôi có thể cấu trúc lại mỗi lần thử bắt và trong trường hợp này, mỗi khối thử bắt thử cùng một thứ nhưng ở các vị trí khác nhau (tiện lợi như thế nào: D), chúng tôi chỉ phải sao chép dán một trong các khối thử bắt và thực hiện phương pháp của nó .

public String loadConfigFile(String path) {
    try {
        FileInputStream is = new FileInputStream(path);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        return null;
    }
}

Bây giờ chúng tôi sử dụng điều này với cùng mục đích như trước đây.

String[] paths = new String[] {PATH_ONE, PATH_TWO, PATH_THREE};

String configData;
for(String path : paths) {
    configData = loadConfigFile(path);
    if (configData != null) {
        break;
    }
}

Tôi hy vọng điều đó sẽ giúp :)


ví dụ tốt. ví dụ này thực sự là loại mã chúng ta phải cấu trúc lại. tuy nhiên một số lần khác, thử bắt lồng là cần thiết.
linehrr

4

Nó chắc chắn là giảm khả năng đọc mã. Tôi sẽ nói, nếu bạn có cơ hội , thì hãy tránh làm tổ thử bắt.

Nếu bạn phải làm tổ thử bắt, hãy luôn dừng lại trong một phút và suy nghĩ:

  • Tôi có cơ hội kết hợp chúng không?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • Tôi chỉ nên trích xuất phần lồng vào một phương thức mới? Mã sẽ sạch hơn nhiều.

    ...  
    try {  
      ... code  
    } catch (FirstKindOfException e) {  
       panicMethod();  
    }   
    ...
    
    private void panicMethod(){   
    try{  
    ... do the nested things  
    catch (SecondKindOfException e) {  
      ... do something else    
      }  
    }
    

Rõ ràng là nếu bạn phải lồng ba hoặc nhiều mức bắt thử, trong một phương thức duy nhất, đó là dấu hiệu chắc chắn về thời gian cho cấu trúc lại.


3

Tôi đã thấy mô hình này trong mã mạng và nó thực sự có ý nghĩa. Đây là ý tưởng cơ bản, trong mã giả:

try
   connect;
catch (ConnectionFailure)
   try
      sleep(500);
      connect;
   catch(ConnectionFailure)
      return CANT_CONNECT;
   end try;
end try;

Về cơ bản đó là một heuristic. Một lần thử kết nối thất bại có thể chỉ là một trục trặc mạng, nhưng nếu điều đó xảy ra hai lần, điều đó có thể có nghĩa là máy bạn đang cố gắng kết nối thực sự là không thể truy cập được. Có lẽ có nhiều cách khác để thực hiện khái niệm này, nhưng rất có thể chúng còn xấu hơn cả những lần thử lồng nhau.


2

Tôi đã giải quyết tình huống này như thế này (thử bắt với dự phòng):

$variableForWhichINeedFallback = null;
$fallbackOptions = array('Option1', 'Option2', 'Option3');
while (!$variableForWhichINeedFallback && $fallbackOptions){
    $fallbackOption = array_pop($fallbackOptions);
    try{
        $variableForWhichINeedFallback = doSomethingExceptionalWith($fallbackOption);
    }
    catch{
        continue;
    }
}
if (!$variableForWhichINeedFallback)
    raise new ExceptionalException();

2

Tôi đã "phải" làm điều này trong một lớp kiểm tra ngẫu nhiên (JUnit), trong đó phương thức setUp () phải tạo các đối tượng với các tham số hàm tạo không hợp lệ trong hàm tạo đã tạo một ngoại lệ.

Ví dụ, nếu tôi phải làm cho việc xây dựng 3 đối tượng không hợp lệ thất bại, tôi sẽ cần 3 khối thử bắt, lồng nhau. Thay vào đó, tôi đã tạo một phương thức mới, trong đó các trường hợp ngoại lệ bị bắt và giá trị trả về là một phiên bản mới của lớp mà tôi đang thử nghiệm khi nó thành công.

Tất nhiên, tôi chỉ cần 1 phương pháp vì tôi đã làm tương tự 3 lần. Nó có thể không phải là một giải pháp tốt cho các khối lồng nhau làm những việc hoàn toàn khác nhau, nhưng ít nhất mã của bạn sẽ trở nên dễ đọc hơn trong hầu hết các trường hợp.


0

Tôi thực sự nghĩ rằng đó là một antipotype.

Trong một số trường hợp, bạn có thể muốn có nhiều lần thử, nhưng chỉ khi bạn KHÔNG BIẾT loại lỗi nào bạn đang tìm kiếm, ví dụ:

public class Test
{
    public static void Test()
    {            
        try
        {
           DoOp1();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp2();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp3();
        }
        catch(Exception ex)
        {
            // treat
        }
    }

    public static void Test()
    {
        try
        {
            DoOp1();
            DoOp2();
            DoOp3();
        }
        catch (DoOp1Exception ex1)
        {
        }
        catch (DoOp2Exception ex2)
        {
        }
        catch (DoOp3Exception ex3)
        {
        }
    }
}

Nếu bạn không biết những gì bạn đang tìm kiếm, bạn phải sử dụng cách đầu tiên, đó là IMHO, xấu và không hoạt động. Tôi đoán sau này là tốt hơn nhiều.

Vì vậy, nếu bạn biết KIND lỗi nào bạn đang tìm kiếm, hãy cụ thể . Không cần lồng nhau hoặc nhiều lần thử trong cùng một phương thức.


2
Mã giống như mã bạn đã hiển thị thực sự không có ý nghĩa gì trong hầu hết các trường hợp nếu không phải tất cả các trường hợp. Tuy nhiên, OP đã đề cập đến các thử thách lồng nhau , đây là một câu hỏi khá khác so với các câu lệnh liên tiếp.
JimmyB

0

Trong một số trường hợp, một lần thử bắt được lồng nhau là không thể tránh khỏi. Ví dụ, khi mã khôi phục lỗi chính nó có thể ném và ngoại lệ. Nhưng để cải thiện khả năng đọc mã, bạn luôn có thể trích xuất khối lồng nhau thành một phương thức riêng. Kiểm tra bài đăng trên blog này để biết thêm ví dụ về các khối Thử-Bắt-Cuối cùng lồng nhau.


0

Không có gì được đề cập như Anti Pattern trong java ở bất cứ đâu. Vâng, chúng tôi gọi vài điều thực hành tốt và thực hành xấu.

Nếu một khối thử / bắt được yêu cầu bên trong khối bắt thì yêu cầu của bạn không thể giúp bạn. Và không có sự thay thế. Là một khối bắt không thể làm việc như một phần nếu ngoại lệ được ném.

Ví dụ :

String str=null;
try{
   str = method(a);
}
catch(Exception)
{
try{
   str = doMethod(a);
}
catch(Exception ex)
{
  throw ex;
}

Ở đây trong ví dụ trên, phương thức ném ngoại lệ nhưng doMethod (được sử dụng để xử lý ngoại lệ) thậm chí còn ném ngoại lệ. Trong trường hợp này, chúng ta phải sử dụng thử bắt trong thử bắt.

một số điều được đề nghị không làm là ..

try 
{
  .....1
}
catch(Exception ex)
{
}
try 
{
  .....2
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....4
}
catch(Exception ex)
{
}

điều này dường như không cung cấp bất cứ điều gì đáng kể trong 12 câu trả lời trước
gnat
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.