安全編碼-1

2.1 不要抑制或忽略已檢查異常:
  java checked exceptions are the ones that you must handle in your code, like "SQLException" etc.

       on the other hand, unchecked exceptions are the ones that you don't need to handle. like "NullPointerException" etc.

       public class RuntimeExceptionextends ExceptionRuntimeException 是那些可能在 Java 虛擬機正常運行期間拋出的異常的超類。 

可能在執行方法期間拋出但未被捕獲的 RuntimeException 的任何子類都無需在 throws 子句中進行聲明。 

也就是未檢查異常。 

相反就是已檢查異常,如: 

public class IOExceptionextends Exception當發生某種 I/O 異常時,拋出此異常。此類是失敗或中斷的 I/O 操作生成的異常的通用類。 


view plaincopy to clipboardprint?
package cn.partner4java.exception;  
  
import java.io.IOException;  
  
public class CheckedTest {  
  
    public static void main(String[] args) {  
        //必須要捕獲或者拋出  
        try {  
            checkedTest();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
      
    public static void checkedTest() throws IOException{  
        System.out.println("Say,hello world!");  
    }  
  
    public static void checkedTest2() {  
        //必須要被捕獲和拋出,拋出後面也不能再寫代碼  
        try {  
            throw new IOException();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Say,hello world!");  
    }  
}  

 


view plaincopy to clipboardprint?
package cn.partner4java.exception;  
  
public class UnCheckedTest {  
  
    public static void main(String[] args) {  
        //調用不需要捕獲  
        unCheckedTest();  
    }  
      
    public static void unCheckedTest() throws RuntimeException{  
        System.out.println("Say,hello world!");  
    }  
      
      
    public static void unCheckedTest2() {  
        System.out.println("Say,hello world!");  
        //不用被捕獲和拋出  
        throw new RuntimeException();  
    }  
  
}  

       對於"已檢查異常"必須提供異常處理器,以Class.forName爲例
       //forName()方法可能會拋出已檢查異常,所以必須提供異常處理器,
       //  否則編譯器在編譯時會報告錯誤
public class TestException
{
  public static void main(String[] args)
  {
    try
    { String name ="haha";
      Class c=Class.forName(name);
      System.out.println(c.getName());
    }
    catch(Exception e)
    {
   e.printStackTrace();
 }
  }
}
若不提供異常處理器,
public class TestException
{
  public static void main(String[] args)
  {
      String name ="haha";
      Class c=Class.forName(name);
      System.out.println(c.getName());
  }
}
則編譯時編譯器會報告一下錯誤:
未報告的異常 java.lang.ClassNotFoundException;
必須對其進行捕捉或聲明以便拋出
      Class c=Class.forName(name);


異常處理是java的一個強大而實用的特性,使異常變得更加容易使用,但同時也使我們更不容易恰如其分的使用――《practical java》 
java的異常由try/catch/finally段組合,它們又可以任意嵌套(nest)。如果程序中有多處異常發生,但返回給最初調用端的卻只有最後發生的異常,前邊的多個異常會被遮掩或丟失了――如果我們不進行有效處理的話!可見,我們不應該忽略異常,或是catch了卻不處理,這將導致後序程序更易出錯且無法很好判斷錯誤的源頭;同時,也不應該丟失在拋出過程中一些異常,因爲通常需要根據異常信息來產生回覆,而我們必須得到每一個應該得到的異常信息。 
下面一個簡單的例子說明了上述bad情況是會經常發生的:
package test; 
public class hideexceptionyn {
    public static void main(string[] args) {
        hideexceptionyn hideexceptionyn = new hideexceptionyn();
        try {
            hideexceptionyn.foo();
        } catch (exception ex) {
            system.out.println("in main, caught the exception: "+ ex.getmessage());
        }
    } 
    public void foo() throws exception {
        try {
            throw new exception("first e");
        } catch (exception ex) {
            throw new exception("second e");
        } finally {
            throw new exception("third exception");
        }
    }
}
程序運行結果,僅打印出:
in main, caught the exception: third exception
可見,first e 和 second e丟失了,這是我不願看到的結果。
具體的情況可以這樣描述:如果在try段中讀取一個文件,可能會拋出filenotfoundexception或讀取失敗的ioexception,而我們通常必須在finally段中對打開的文件進行關閉,以釋放non-memory資源,這裏又可能拋出ioexception,最後這個異常將遮掩住前面拋出的所有異常,如果不處理,最初調用端將只得到最後這個異常!
根據經驗,我們可以創建一個vector對象,用來存放程序運行過程中拋出的所有異常,最後將這個vector對象返回給調用端,從而解決上述問題。當然我們應該儘可能的就地處理異常!這裏探討的是需要不斷將內層異常往外層throw的情況。
處理方法比較簡單,那就是在進入可能發生異常的方法時定義一個vector(arraylist)對象,然後在每個catch塊中都將exception對象添加到vector中,然後在finally塊的最後判斷vector的size是否爲零,不爲零則說明有異常發生了――將其拋給調用端即可。這樣,調用端就收到了從它開始到返回它的運行過程中的所有異常。示例代碼如下:
vector vector=new vector();
try{
//….
}catch(xxxexception e){
    vector.add(e);   
}catch(yyyexception e2){
    vector.add(e2);
}finally{
  try{
  //…
}catch(dddexcetpion e3){
  vector.add(e3);
}
  if(vector.size()!=0)
{
throw new userdefineexception(vector);
        }
}
//需要定義一個userdefineexception類:有一個vector類的field,並有setvector和getvector()方法即可。
處理方法比較簡單,那就是在進入可能發生異常的方法時定義一個vector(arraylist)對象,然後在每個catch塊中都將exception對象添加到vector中,然後在finally塊的最後判斷vector的size是否爲零,不爲零則說明有異常發生了――將其拋給調用端即可。這樣,調用端就收到了從它開始到返回它的運行過程中的所有異常。示例代碼如下:
vector vector=new vector();
try{
//….
}catch(xxxexception e){
    vector.add(e);   
}catch(yyyexception e2){
    vector.add(e2);
}finally{
  try{
  //…
}catch(dddexcetpion e3){
  vector.add(e3);
}
  if(vector.size()!=0)
{
throw new userdefineexception(vector);
        }
}
//需要定義一個userdefineexception類:有一個vector類的field,並有setvector和getvector()方法即可。

2.2 禁止在異常中泄露敏感信息:
    
     用戶名、密碼、tokenid、設備信息等

2.3 方法發生異常時要恢復到之前的對象狀態:

Praxis27--拋出異常之前先將對象恢復爲有效狀態(valid state)  
        拋出異常很容易,困難的是如何將拋異常所導致的損害減至最小。常見的情況是,程序員被誤導,以爲關鍵詞try、throw、catch、throws和finally就是錯誤處理的起點和終點。其實它們只是正確處理錯誤的起點而已。
        拋出異常是爲了什麼?明顯的目的是將已發生的問題通知系統的其他部分。隱含的目的則是讓軟件捕獲異常,使系統有可能從問題中抽身回覆(recover)而維持正常運行狀態。如果異常被引發後,系統回覆並繼續運轉,引發異常的那一段代碼可以被“復入”(reentered)。那些回覆至“異常拋出前之狀態”的對象,在系統繼續運行期間可以再被使用。令人迷惑的是,當拋出異常後,這些對象的狀態究竟如何?系統還可以正確運轉嗎?
        如果你的對象處於一種無效(invalid)狀態或未定義(undefined)狀態,拋出異常又有什麼用呢?如果你的對象處於不良(bad)狀態,那麼異常回復後的代碼還是很可能失敗。因此在拋出異常之前,你必須考慮對象當時處於什麼狀態。如果它們的狀態會使得“即使異常回復,代碼仍然失敗”,你就得在拋出異常之前,考慮如何讓它們處於有效狀態。
        通常,程序員會假設函數中的代碼將毫無錯誤的完成工作。一旦錯誤發生,原先假設的部分甚至全部便靠不住了。考慮下面的代碼:
class SomeException extends Exception {
}
class MyList {
}
class Foo {
    private int numElements;
    private MyList myList;
    
    public void add(Object o) throws SomeException {
        //...
        numElements++;                                       //1
        if (myList.maxElements() < numElements) {
            //Reallocate myList
            //Copy elements as necessary
            //Could throw exceptions
        }
        myList.addToList(o);  //Could throw exception        //2
    }
}
這段代碼包含了一個add(),用以將一個對象加入到list中。這個函數首先在//1將一個計數器值累加1,該值記錄了list中的對象數量。然後它有條件地重新分配list,並在//2爲list添加一個對象。這個代碼有嚴重缺陷,請注意//1和//2之間可能會拋出異常。如果//1身後拋出異常,那麼Foo對象就處於無效狀態,因爲計數器numElements的值不對。如果函數調用者從拋出的異常中回覆過來,並再次調用這個函數,由於Foo對象處於無效狀態,很可能會發生其他問題。
        修正這個問題很容易:
class Foo {
    private int numElements;
    private MyList myList;
    
    public void add(Object o) throws SomeException {
        //...
        if (myList.maxElements() == numElements)             //1
        {
            //Reallocate myList
            //Copy elements as necessary
            //Could throw exceptions
        }
        myList.addToList(o);  //Could throw exception
        numElements++;                                       //2
    }
}
爲了修正錯誤,只要修改//1的測試行爲,並將numElements的累加動作移到函數尾部的//2處,這樣就確保計數器numElements永遠準確,因爲你在成功地將對象添加到list之後才增加計數器值。這是一個簡單範例,但它揭示了一個潛在的嚴重問題。
        你所放心不下的,肯定不僅僅是“執行中的那個對象”,還包括“被我們關注的那個函數修改過的對象”。舉個例子,當異常發生時,函數內或許已經完全建立或部分建立了一個文件,打開了一個socket,或是對另一臺計算機發出了遠程調用(remote method calls)。如果你希望你的代碼被複入後還能正常運轉,則需要了解所有受到影響的對象的狀態,以及系統本身的狀態。考慮以下代碼:
import java.io.IOException;
class MutualFund {
    public void buyMoreShares(double money) {
    }
    //...
}
class Customer {
    private MutualFund[] fundArray;
    
    public Customer() {
    }
    
    public MutualFund[] funds() {
        return fundArray;
    }
    
    public void updateMutualFund(MutualFund fund) throws
            DatabaseException {
    }
    
    public void writePortfolioChange() throws IOException {
    }
    //...
}
class DatabaseException extends Exception {
}
class Services {
    public void invest(Customer cust, double money) throws
            IOException, DatabaseException {
        MutualFund[] array = cust.funds();                        //1
        int size = array.length;
        
        for (int i=0; i<size; i++) {
            ((MutualFund)array[i]).buyMoreShares(money);            //2
            cust.updateMutualFund(array[i]);
            cust.writePortfolioChange();
        }
        //...
    }
}
在這個實例中,invest()將客戶(customer)的資金投資到客戶的各個互惠基金(mutual fund)的帳戶(accounts)中。Customer class內含一個funds(),返回客戶持有之全部基金(funds)所組成的一個arrays。invest()通過buyMoreShares()對客戶的每個基金買進相同金額,然後更新數據庫內的MutualFund對象,並修改Customer對象的有價證卷(portfolio)信息。對有價證卷的修改動作會建立其一筆帳目活動記錄(account activity),讓客戶得知事務(transaction)情況。循環中的最後兩個函數有可能失敗並拋出異常。由於invest()並不處理異常,爲避免編譯錯誤,將這些異常列入throws子句中。
        某些程序員寫出了這樣的代碼,測試後確定運轉正常,於是以爲萬事大吉。測試這段程序時,他們發現,如果其中某個函數失敗,會拋出一個相應異常,然後如預期般地退出函數。如果他們的測試不夠徹底,很可能不會發現潛伏的問題。
        每當你撰寫可能引發異常的代碼時,都必須問自己:(1)異常發生之時(2)異常處理過後(3)復入這段代碼時,會發生什麼事情?代碼運轉還能夠正常嗎?在這個例子中,代碼確實存在一些問題。
        假設某位客戶在三個互惠基金中都擁有股票(shares),並打算爲每個基金各自再買進$1,000。//1取得了所有基金構成的array,//2爲array的第一筆基金購買$1,000的股票,並通過updateMutualFund()成功地將更新後的基金寫入數據庫。然後調用writePortfolioChange(),將某些信息寫入文件。這時候這個函數失敗了,因爲磁盤空間不足,無法建立文件。於是在對第一個MutualFund對象完成“完整的三個步驟”之前,拋出了一個異常,意外而魯莽地退出了invest()。
        假設invest()調用者通過釋放磁盤空間等手法,順利的處理了這個異常,而後再次調用invest()。當invest()再次執行時,//1取得基金array,而後進入循環,爲每支基金購買股票。但是不要忘了,先前第一次調用invest()的時候,你已經爲第一支基金購買了$1,000的股票。此時又再做一遍。如果這次invest()順利爲客戶的三筆基金完成了操作,你就是爲第一筆基金購買了$2,000,而其他兩支基金各購買$1,000,這是不正確的。顯然不是你期望的結果。
        就“首先釋放磁盤空間,而後再次調用這個函數”的做法而言,你正確地處理了異常。而沒有做的是,在拋出異常之後、退出invest()之前,關注對象的狀態。
        此問題的一個修正辦法是,在MutualFund和Customer兩個classes中添加函數,並在invest()處理異常時調用它們。這些函數用來撤銷未竟全功的一些事件。invest()必須增加catch區段,處理任何被引發的異常。這些catch區段應該調用相應函數,重置對象狀態,這麼一來如果這個函數下次再被調用,就可以正確執行了。修改後的代碼看起來是這樣:
import java.io.IOException;
class MutualFund {
    public void buyMoreShares(double money) {
    }
    public void sellShares(double money) {
    }
    //...
}
class Customer {
    private MutualFund[] fundArray;
    
    public Customer() {
    }
    
    public MutualFund[] funds() {
        return fundArray;
    }
    
    public void updateMutualFund(MutualFund fund) throws
            DatabaseException {
    }
    public void undoMutualFundUpdate(MutualFund fund) {
    }
    
    public void writePortfolioChange() throws IOException {
    }
    //...
}
class DatabaseException extends Exception {
}
class Services {
    public void invest(Customer cust, double money) throws
            IOException, DatabaseException {
        MutualFund[] array = cust.funds();
        int size = array.length;
        
        for (int i=0; i<size; i++) {
            ((MutualFund)array[i]).buyMoreShares(money);
            try {
                cust.updateMutualFund(array[i]);
            } catch(DatabaseException dbe)  //Catch exception and return
            {                             //object to a valid state.
                ((MutualFund)array[i]).sellShares(money);
                throw dbe;
            }
            try {
                cust.writePortfolioChange();
            } catch(IOException ioe)  //Catch exception and return object
            {                       //to a valid state.
                ((MutualFund)array[i]).sellShares(money);
                cust.undoMutualFundUpdate(array[i]);
                throw ioe;
            }
        }
        //...
    }
}
        現在,即使invest()拋出異常,而後復入invest(),上述代碼也能一如期望地正確執行了。爲了讓這段代碼正常運轉,MutualFund和Customer classes各增加一個函數,用以在發生異常時取消(回覆,undo)已對對象進行的操作。invest()內的循環也有了修改,捕捉可能發生的異常,並重置(reset)對象狀態。在對象成爲有效狀態、確保復入(reentered)時可正確運轉後,重新拋出異常,這樣調用端便可嘗試回覆(recovery)。
        這段代碼與原先版本相比,較不易撰寫和遵循,但對於強固的、可從異常狀態中回覆的代碼而言,這些步驟必不可少。
        另一個需要考慮的問題是,萬一“回複函數”undoMutualFundUpdate()和sellShare()也失敗了,情況會變得如何?如果要保持系統順利運轉,也需要對付這些失敗。的確,正如所看到,在拋出異常的時候保持對象處於有效狀態,可能非常困難。
        如果希望調用者從你所引發的異常中回覆,則必須查看所有可能發生異常的地方。如果函數在異常發生處不顧一切地退出(exit),那麼就有必要檢查這種行爲是否使你的對象處於以下狀態:“復入這個函數時可產生正確結果”。如果沒有令對象處於有效狀態,你就必須採取必要措施,確保重新進入函數時得以按我們所期望的方式正常運轉。
        本實踐內容所表述的過程和所謂的“事務”(transaction)十分類似。在完成與事務相關的工作時,你必須有一個“提交+回滾”(commit and rollback)計劃。如果事務所牽涉的部分無法全部正確完成,就必須取消整筆事務。你也許會考慮一個全局事務(global transaction)或一個“提交+回滾”策略來實現解決方案。
        不幸的是,解決這類問題十分困難和耗時。欲正確實現這些解決方案,不僅得花費打量編程時間和測試時間,還需要打量的艱苦思考。當如果你在編程之後纔開始忙於這些問題,那可比在設計和編程初始就將這些問題銘記於心要棘手得多——你將因此花費更多的工作和努力!

3.1 臨時文件使用完畢要及時刪除:
    ead& Write: There are no different between normal file and temporary file, what apply to normal text file, will apply to temporary file as well.
  
  Delete: Temporary file is used to store the less important and temporary data, which should always be deleted when your system is terminated. The best practice is use the File.deleteOnExit() to do it.
  
  package org.hello.i;
  
  import java.io.BufferedReader;
  
  import java.io.BufferedWriter;
  
  import java.io.File;
  
  import java.io.FileReader;
  
  import java.io.FileWriter;
  
  import java.io.IOException;
  
  public class JavaTempFile {
  
  public static void main(String[] args){
  
  try{
  
  //create a temp file
  
  File temp = File.createTempFile(“temp-file-name”, “.tmp”);
  
  System.out.println(“Temp File: ”+ temp.getAbsolutePath());
  
  //write content to it
  
  BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
  
  bw.write(“Actually, there are no different between normal file and temporary file, what apply to normal text file, will apply to temporary file as well.”);
  
  bw.close();
  
  System.out.println(“Content has been writen ”);
  
  //read it
  
  BufferedReader br = new BufferedReader(new FileReader(temp));
  
  System.out.println(“Content: ”+ br.readLine());
  
  br.close();
  
  //Get tempropary file path
  
  String absolutePath = temp.getAbsolutePath();
  
  String tempFilePath = absolutePath.substring(0,absolutePath.lastIndexOf(File.separator));
  
  System.out.println(“Temp file path : ” + tempFilePath);
  
  //delete temporary file when the program is exited
  
  temp.deleteOnExit();
  
  //delete immediate
  
  //temp.delete();
  
  }catch(IOException e){
  
  e.printStackTrace();
  
  }
  
  }
  
  }
  
  運行結果:
  
  Temp File: C:\Users\harrypoter\AppData\Local\Temp\temp-file-name2692138736359297894.tmp
  
  Content has been writen
  
  Content: Actually, there are no different between normal file and temporary file, what apply to normal text file, will apply to temporary file as well.
  
  Temp file path : C:\Users\harrypoter\AppData\Local\Temp

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章