安全编码-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

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