[Java] 避免巢狀 try-catch

所謂 nested try-catch 就是
在 try-catch 結構之中又有 try-catch
比起巢狀 if-else 或 巢狀 for 迴圈
try-catch 的程式碼更為冗長且不易閱讀
因此應盡可能避免

以下分為兩種情形:

在 try 區塊中

以 FileOutputStream 作為例子
new 和 flush 分別都要捕捉例外
可能發生以下情形

FileOutputStream out;
try {
    out = new FileOutputStream("test");
    try {
        out.flush();
    } catch (IOException e) {
        
    }
} catch (FileNotFoundException e) {
    
}

這種在 try 區塊中的 try-catch 是完全可以避免掉的
只要將 catch 區塊移動到外層 try 即可
換句話說,就算有多個例外
統一只在一層 try-catch 捕捉

FileOutputStream out;
try {
    out = new FileOutputStream("test");
    out.flush();
} catch (FileNotFoundException e) {
    
} catch (IOException e) {
    
}

在 catch 或 finally 區塊中

典型的例子是 java.sql.Connection 物件
一旦捕捉到例外要 rollback,結束要 close
但 rollback 與 close 各自又會再拋出例外
造就這樣繁瑣的寫法

Connection connection = null;
try {
    connection = DriverManager.getConnection("", "", "");
    
    connection.commit();
} catch (SQLException e) {
    if (connection != null) {
        try {
            connection.rollback();
        } catch (SQLException e1) {
            
        }
    }
} finally {
    if (connection != null) {
        try {
            connection.close();
        } catch (SQLException e1) {
            
        }
    }
}

這種 try-catch 無法省略
只能透過 extract method 重構優化:
將 rollback 和 close 分別提取成獨立的方法
try-catch 當然連帶移去自己的方法中
這樣程式主幹就變得清晰簡潔

public void demo() {
    Connection connection = null;
    try {
        connection = DriverManager.getConnection("", "", "");
        
        connection.commit();
    } catch (SQLException e) {
        rollback(connection);
    } finally {
        close(connection);
    }
}

public void rollback(Connection connection) {
    if (connection == null) {
        return;
    }
    try {
        connection.rollback();
    } catch (SQLException e1) {
        
    }
}

public void close(Connection connection) {
    if (connection == null) {
        return;
    }
    try {
        connection.close();
    } catch (SQLException e) {
        
    }
}

try-with-resources

比較簡潔的寫法
將資源放在 try 的小括弧中
一旦離開 try block 就自動 close
所以不需要 finally block
但缺點是在 catch block 中無法共用該資源

try (Connection connection = DriverManager.getConnection("", "", "");) {
    
    connection.commit();
} catch (SQLException e) {
    //connection.rollback();
}