การใช้ try-catch ที่ซ้อนกันบล็อกการป้องกันรูปแบบหรือไม่


95

สิ่งนี้เป็นปฏิปักษ์หรือไม่? มันเป็นวิธีปฏิบัติที่ยอมรับได้?

    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?
            }
        }
    }

คุณช่วยให้เรากรณีนี้ได้หรือไม่ เหตุใดคุณไม่สามารถจัดการข้อผิดพลาดทุกประเภทในระดับสูงสุดได้
Morons

2
ฉันได้เห็นโค้ดประเภทนี้เมื่อเร็ว ๆ นี้ดำเนินการโดยโปรแกรมเมอร์ที่ไม่มีประสบการณ์ซึ่งไม่รู้จริง ๆ ว่าพวกเขากำลังโทรหาอะไรในบล็อคลองและพวกเขาไม่ต้องการรบกวนการทดสอบโค้ด ในตัวอย่างโค้ดที่ฉันเห็นมันเป็นการดำเนินการเดียวกัน แต่ดำเนินการทุกครั้งด้วยพารามิเตอร์ทางเลือก
มิสเตอร์สมิ ธ

@ LokiAstari - ตัวอย่างของคุณคือการลองในส่วนสุดท้าย .. ที่ไม่มีการจับ นี่คือซ้อนในส่วนลอง .. มันแตกต่างกัน
Morons

4
ทำไมมันควรเป็นรูปแบบการต่อต้าน?

2
+1 สำหรับ "ลองจับมากกว่าหรือไม่"
JoelFan

คำตอบ:


85

บางครั้งไม่สามารถหลีกเลี่ยงได้โดยเฉพาะอย่างยิ่งหากรหัสกู้คืนของคุณอาจมีข้อผิดพลาด

ไม่สวย แต่บางครั้งก็ไม่มีทางเลือก


17
@MisterSmith - ไม่เสมอไป
Oded

4
ใช่นี่คือสิ่งที่ฉันพยายามจะทำ แน่นอนว่ามีจุดในคำสั่งลอง / จับที่ซ้อนกันซึ่งคุณต้องบอกว่าเพียงพอแล้ว ฉันทำกรณีสำหรับการทำรังซึ่งตรงข้ามกับการลอง / จับตามลำดับโดยบอกว่ามีสถานการณ์ที่คุณต้องการให้โค้ดในการลองครั้งที่สองเพื่อเรียกใช้งานหากการลองครั้งแรกล้มเหลว
AndrewC

5
@MisterSmith: ฉันต้องการลองจับที่ซ้อนกันเพื่อลองจับที่ต่อเนื่องที่ถูกควบคุมบางส่วนด้วยตัวแปรธง (ถ้าพวกเขาใช้งานได้เหมือนกัน)
FrustratedWithFormsDesigner

31
ลอง {transaction.commit (); } catch {ลอง {transaction.rollback (); } catch {จริงจังlogging ()} notsoseriouslogging (); } เป็นตัวอย่างของการลองแบบซ้อนที่จำเป็น
Thanos Papathanasiou

3
อย่างน้อยก็แยกบล็อก catch ออกเป็นวิธี, guys! อย่างน้อยก็ทำให้สิ่งนี้อ่านง่าย
Mr Cochese

43

ฉันไม่คิดว่ามันเป็นปฏิปักษ์ แต่ใช้ผิดวัตถุประสงค์อย่างกว้างขวาง

การลองจับที่ซ้อนกันส่วนใหญ่นั้นสามารถหลีกเลี่ยงได้และน่าเกลียดน่ากลัวสำหรับเราโดยปกติแล้วเป็นผลิตภัณฑ์ของผู้พัฒนารุ่นเยาว์

แต่มีบางครั้งที่คุณไม่สามารถช่วยได้

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

นอกจากนี้คุณจะต้องมีบูลพิเศษเพื่อแสดงว่าการย้อนกลับล้มเหลว ...


19

ตรรกะนั้นดี - มันสามารถเข้าท่าได้อย่างสมบูรณ์ในบางสถานการณ์ที่จะลองใช้วิธีการย้อนกลับซึ่งตัวมันเองจะได้สัมผัสกับเหตุการณ์พิเศษ ... ดังนั้นรูปแบบนี้จึงค่อนข้างหลีกเลี่ยงไม่ได้

อย่างไรก็ตามฉันขอแนะนำต่อไปนี้เพื่อให้โค้ดดีขึ้น:

  • Refactor ลองภายใน ... จับบล็อกออกไปในฟังก์ชั่นที่แยกต่างหากเช่นและattemptFallbackMethodattemptMinimalRecovery
  • เฉพาะเจาะจงมากขึ้นเกี่ยวกับประเภทข้อยกเว้นเฉพาะที่กำลังถูกจับ คุณคาดหวังว่าsubclass ของข้อยกเว้นใด ๆจริงๆ หรือไม่และถ้าเป็นเช่นนั้นคุณต้องการจัดการพวกมันด้วยวิธีเดียวกันทั้งหมดหรือไม่?
  • พิจารณาว่าfinallyบล็อกอาจมีเหตุผลมากกว่านี้หรือไม่นี่เป็นกรณีสำหรับสิ่งที่รู้สึกเหมือน "รหัสการล้างทรัพยากร"

14

ไม่เป็นไร. การปรับเปลี่ยนสิ่งที่ต้องพิจารณาคือการผลักดันรหัสไปยังวิธีการของตนเองและใช้การออกก่อนหน้าเพื่อความสำเร็จช่วยให้คุณเขียนความพยายามที่แตกต่างเพื่อทำบางสิ่งในระดับเดียวกัน:

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?

เมื่อคุณแยกมันออกมาเป็นอย่างนั้นคุณสามารถคิดถึงเรื่องนี้ในรูปแบบกลยุทธ์

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");
    }
}

จากนั้นใช้เพียงTrySeveralThingsStrategyซึ่งเป็นกลยุทธ์คอมโพสิต (สองรูปแบบสำหรับราคาหนึ่ง!)

ข้อแม้ขนาดใหญ่หนึ่งข้อ: อย่าทำสิ่งนี้เว้นแต่ว่ากลยุทธ์ของคุณจะซับซ้อนพอสมควรหรือคุณต้องการใช้มันอย่างยืดหยุ่น มิฉะนั้นคุณจะต้องใช้โค้ดง่ายๆสองสามบรรทัดที่มีการวางแนววัตถุที่ไม่จำเป็น


7

ฉันไม่คิดว่ามันจะเป็นแบบป้องกันโดยอัตโนมัติ แต่ฉันจะหลีกเลี่ยงได้ถ้าฉันสามารถหาวิธีที่ง่ายและสะอาดกว่าในการทำสิ่งเดียวกัน หากภาษาการเขียนโปรแกรมที่คุณใช้อยู่มีfinallyโครงสร้างซึ่งอาจช่วยล้างข้อมูลในบางกรณี


6

ไม่ใช่รูปแบบการต่อต้านต่อ se แต่เป็นรูปแบบรหัสที่บอกให้คุณต้องปรับโครงสร้าง

และมันค่อนข้างง่ายคุณต้องรู้จักกฎง่ายๆที่เขียนไม่เกินบล็อกลองในวิธีเดียวกัน หากคุณรู้จักการเขียนโค้ดที่เกี่ยวข้องเข้าด้วยกันโดยปกติจะเป็นการคัดลอกและวางแต่ละบล็อกลองด้วย catch catch และวางในวิธีการใหม่แล้วแทนที่บล็อกเดิมด้วยการเรียกใช้วิธีนี้

กฎข้อนี้ใช้ตามคำแนะนำของ Robert C. Martin จากหนังสือ 'Clean Code' ของหนังสือของเขา:

หากคำหลัก 'ลอง' มีอยู่ในฟังก์ชั่นควรเป็นคำแรกในฟังก์ชั่นและไม่ควรมีอะไรหลังจากที่ catch / สุดท้ายบล็อก

ตัวอย่างรวดเร็วเกี่ยวกับ "pseudo-java" สมมติว่าเรามีสิ่งนี้:

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;
        }
    }
}

จากนั้นเราสามารถ refactor แต่ละครั้งลอง catch และในกรณีนี้แต่ละ try-catch block พยายามสิ่งเดียวกัน แต่ในสถานที่ต่างกัน (ความสะดวก: D) เราเพียงคัดลอกวางหนึ่งในบล็อก try-catch และสร้างวิธีการ .

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

ตอนนี้เราใช้สิ่งนี้โดยมีจุดประสงค์เช่นเดียวกับเมื่อก่อน

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

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

ฉันหวังว่าจะช่วย :)


ตัวอย่างที่ดี ตัวอย่างนี้เป็นประเภทของรหัสที่เราต้องทำการรีแฟคเตอร์อย่างแท้จริง อย่างไรก็ตามบางครั้งก็จำเป็นต้องใช้การลองแบบซ้อน
linehrr

4

มันลดการอ่านรหัสได้อย่างแน่นอน ฉันจะบอกว่าถ้าคุณมีโอกาสลองหลีกเลี่ยงการจับรัง

หากคุณต้องลองทำรังให้หยุดเป็นเวลาหนึ่งนาทีแล้วคิดว่า:

  • ฉันจะมีโอกาสรวมพวกเขาได้อย่างไร

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • ฉันควรแยกส่วนที่ซ้อนกันออกเป็นวิธีการใหม่ได้หรือไม่ รหัสจะสะอาดมากขึ้น

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

จะเห็นได้ชัดว่าคุณต้องซ้อนการทดลองสามระดับขึ้นไปในวิธีการเดียวนั่นคือสัญญาณของเวลาที่แน่นอนสำหรับการ refactor


3

ฉันเห็นรูปแบบนี้ในรหัสเครือข่ายและจริง ๆ แล้วสมเหตุสมผล นี่คือแนวคิดพื้นฐานใน pseudocode:

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

โดยทั่วไปมันเป็นฮิวริสติก ความพยายามที่ล้มเหลวในการเชื่อมต่ออาจเป็นเพียงความผิดพลาดของเครือข่าย แต่หากเกิดขึ้นสองครั้งนั่นอาจหมายความว่าเครื่องที่คุณพยายามเชื่อมต่อนั้นไม่สามารถเข้าถึงได้ อาจมีวิธีอื่น ๆ ในการนำแนวคิดนี้ไปใช้


2

ฉันแก้ไขสถานการณ์เช่นนี้ (ลองจับด้วยทางเลือก):

$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

ฉัน "เคย" ทำเช่นนี้ในคลาสทดสอบโดยบังเอิญ (JUnit) ซึ่งเมธอด setUp () ต้องสร้างวัตถุที่มีพารามิเตอร์คอนสตรัคเตอร์ที่ไม่ถูกต้องในตัวสร้างที่สร้างข้อยกเว้น

ถ้าฉันต้องสร้างวัตถุที่ไม่ถูกต้อง 3 ตัวล้มเหลวตัวอย่างเช่นฉันต้องมีบล็อกลอง 3 ชิ้นซ้อนกัน ฉันสร้างวิธีการใหม่แทนโดยที่มีข้อยกเว้นที่ติดอยู่และค่าส่งคืนเป็นตัวอย่างใหม่ของชั้นเรียนที่ฉันกำลังทดสอบเมื่อประสบความสำเร็จ

แน่นอนฉันต้องการเพียง 1 วิธีเพราะฉันทำ 3 ครั้งเหมือนกัน อาจไม่ใช่วิธีแก้ปัญหาที่ดีสำหรับบล็อกซ้อนกันที่ทำสิ่งต่าง ๆ โดยสิ้นเชิง แต่อย่างน้อยโค้ดของคุณจะอ่านได้ง่ายขึ้นในกรณีส่วนใหญ่


0

จริง ๆ แล้วฉันคิดว่ามันเป็น antipattern

ในบางกรณีคุณอาจต้องการลองหลายครั้ง แต่ถ้าคุณไม่ทราบว่ามีข้อผิดพลาดประเภทใดเช่น:

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)
        {
        }
    }
}

หากคุณไม่รู้ว่ากำลังมองหาอะไรคุณต้องใช้วิธีแรกซึ่งก็คือ IMHO น่าเกลียดและไม่สามารถใช้งานได้ ฉันเดาว่าดีกว่านี้มาก

ดังนั้นหากคุณรู้ว่าคุณกำลังมองหาข้อผิดพลาดประเภทใดให้ระบุไว้อย่างเฉพาะเจาะจง ไม่จำเป็นต้องใช้การลองแบบซ้อนหรือหลายครั้งภายในวิธีเดียวกัน


2
รหัสเช่นเดียวกับที่คุณแสดงนั้นไม่สมเหตุสมผลในกรณีส่วนใหญ่ อย่างไรก็ตาม OP หมายถึงการจับแบบซ้อนซึ่งเป็นคำถามที่ค่อนข้างแตกต่างจากคำสั่งที่ต่อเนื่องกันหลายรายการ
JimmyB

0

ในบางกรณีการลองจับที่ซ้อนกันเป็นสิ่งที่หลีกเลี่ยงไม่ได้ ตัวอย่างเช่นเมื่อรหัสข้อผิดพลาดการกู้คืนตัวเองสามารถโยนและยกเว้น แต่เพื่อปรับปรุงความสามารถในการอ่านของรหัสคุณสามารถแยกบล็อกที่ซ้อนกันออกเป็นวิธีของตัวเองได้เสมอ ลองดูโพสต์บล็อกนี้สำหรับตัวอย่างเพิ่มเติมเกี่ยวกับบล็อก Try-Catch-สุดท้ายที่ซ้อนกัน


0

ไม่มีสิ่งใดพูดถึงเป็น Anti Pattern ใน java ได้ทุกที่ ใช่เราเรียกการปฏิบัติที่ดีและการปฏิบัติที่ไม่ดี

หากบล็อก try / catch จำเป็นต้องมีภายใน catch block ที่ต้องการคุณไม่สามารถช่วยได้ และไม่มีทางเลือกอื่น ในฐานะที่เป็น catch catch ไม่สามารถทำงานเป็นส่วนลองได้ถ้ามีการโยนข้อยกเว้น

ตัวอย่างเช่น :

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

ที่นี่ในตัวอย่างข้างต้นวิธีการโยนข้อยกเว้น แต่ doMethod (ใช้สำหรับการจัดการข้อยกเว้นวิธีการ) แม้จะโยนข้อยกเว้น ในกรณีนี้เราต้องใช้ try catch ภายใน try catch

สิ่งที่แนะนำไม่ให้ทำคือ ..

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)
{
}

นี้ไม่ได้ดูเหมือนจะนำเสนออะไรที่สำคัญกว่าก่อน 12 คำตอบ
ริ้น
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.