ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 20장. 예외와 상태 관리
    CLR Via C# 2022. 6. 30. 21:28

    다음은 예외 처리를 위한 일반적인 구조이다.

    private vodi SomeMethod(){
        try{
            // 안정적으로 오류가 처리되어야하고 깔끔하게 정리되어야 하는 코드를 여기에 둔다.
        }
        catch(InvalidOperationException){
            // InvalidOperationException 발생 시 처리해야 하는 코드르 여기에 둔다.
        }
        catch(IOException){
            // IOException 발생 시 처리해야 하는 코드를 여기에 둔다.
        }
        catch{
            // 앞서 기수한 두 가지 예외를 제외한 다른 예외가 발생할 경우에 처리해야 하는 코드를 여기에 둔다
           throw;
        }
        finally{
            // try 블럭을 벋어날 때 수행해야 하는 정리 코드를 여기에 둔다.
            // 이 블록 내의 코드는 예외의 발생 여부와 상관없이 항상 수행된다.
        }
        // finally 블록 아래쪽의 코드는 try 블록 내에세 예외가 발생하지 않은 경우 혹은 
        // 예외가 발생하였고 catch 블록을 통해서 예외를 잡았으나, 그 내부에서 
        // 다시 예외를 발생시키지 않은 경우에만 수행된다. 
    }

    try 블록 내에 두는 코드는 정리 작업이 반드시 필요하거나 잠재적으로 예외를 발생시킬 가능성이 있는 코드를 둔다. try 블록은 finally 블록이나 적어도 하나 이상의 catch 블록이 같이 나타나야 한다.

    catch블록은 예외 발생에 대응하기 위한 코드를 포함한다. try 블록 내의 코드가 예외를 발생시키지 않는다면 catch블록 내의 어떠한 코드도 수행하지 않는다. CLR은 코드의 위에서 아래쪽으로 일치하는 catch 타입을 찾아 내려가기 때문에 좀 더 구체적인 예외 타입을 상단에 위치시켜야 한다. 일치하는 catch블록을 찾지 못하면 호출 스택(call stack) 을 거슬러 올라가면서 일치하는 catch 타입이나 catch블록을 찾는다. 만약 호출 스택의 가장 상단까지 검색하였음에도 일치하는 catch타입과 catch블록이 없으면, 처리되지 않은 예외가 발생하게 된다.

    CLR이 일치하는 catch 타입을 찾으면 해당하는 catch블록으로 이동한 후, 거쳐 올라온 메서드의 가장 안쪽에 위치한 finally 블록들을 수행하는데, 예외를 처음 던진 try블록의 finally블록부터 catch타입이 일치하는 catch블록으로 호출 스택을 거슬러 올라가면서 finally 블록을 수행한다. 이때 catch타입이 일치하는 마지막 try블록과 연관된 finally블록은 이 시점에는 수행하지 않는다. 이 finally블록은 마지막 catch블록이 수행된 이후에 수행될 것이다.

    이 외에 처리되지 않은 예외의 경우 상태 정보가 손상되었을 수가 있다. 이 경우 프로세스를 계속 수행하는 것은 문제가 심각해질 가능성이 있어 프로세스를 종료하는 것도 괜찮은 방법이다. 이때 사용 가능한 것이 Environment의 정적 메서드인 FailFast이다. 이 메서드는 호출해야 하는 try/finally 블록이나 Finalize 메서드를 전혀 호출하지 않고 프로새서를 종료시켜버린다.

     

     

    지침과 모범 사례

     

    finally 블록은 아낌없이 사용하라

    finally블록은 예외의 발생 여부와 상관없이 항상 수행될 것임이 보장된다. 그래서 finally블록에 정리 코드(파일 닫기)를 작성하는 것은 자원 누수를 막기 위해 좋은 선택이다.

    using System;
    using System.IO;
    
    public sealed class SomeType {
        private void SomeMethod() {
            FileStream fs = new FileStream(@"C:\Data.bin", FileMode.Open);
            try {
                // 파일 내의 첫 번째 바이트로 100을 나눈 후 그 결과를 출력한다. 
                Console.WriteLine(100 / fs.ReadByte());
            }
            finally {
                // 예외 발생(예: 첫 번째 바이트가 0이라면) 여부와 상관 없이
                // 파일 닫기를 수행할 수 있도록 코드를 finally 블록에 둔다. 
                if(fs != null) fs.Dispose();
            }
        }
    }

    C#에서는 look, using, foreach 등을 사용할 경우 자동적으로 try/finally 블록을 사용한다. 예를 들어 다음의 C#코드는 using 문장의 장점을 이용하도록 작성되었다. 이 코드는 앞서 예보다 좀 더 짧지만, 컴파일러가 생성하는 코드는 완전히 동일하다.

    using System;
    using System.IO;
    
    public sealed class SomeType {
        private void SomeMethod() {
            using (FileStream fs = new FileStream(@"C:\Data.bin", FileMode.Open)) {
                    // 파일 내의 첫 번째 바이트로 100을 나눈 후 그 결과를 출력한다. 
                    Console.WriteLine(100 / fs.ReadByte());
            }
        }
    }

     

     

    모든 예외를 잡으려 하지 마라

    System.Exception 예외를 잡고 catch 블록 내부에서 몇몇 작업을 수행한 후 예외를 없애버리면 그저 실패 상황을 숨겨버리게 된다. 만약 System.Exception으로 예외를 잡았다면 throw를 통해 호출 스택을 거슬러 올라가 처리할 수 있게 해줘야 한다. 그리고 만일 발생된 예외를 처리하지 못하면 프로세스를 종료시킨다.

     

     

    복구할 수 없는 예외 발생 시 부분적으로 완료된 작업들을 취소하여 상태 유지

    내부적으로 사용된 여러 메서드들 중 일부는 성공적으로 작업을 완료하였지만 또 다른 일부는 그렇지 않을 수 있다. 이런 경우 일부만 성공된 파일의 상태는 손상된 상태가 된다. 이 경우 파일의 상태를 전체 작업이 실행되기 전의 상태로 되돌린다면 좋을 것이다. 다음에 이러한 방법을 구현하는 예를 나타냈다.

    public void SerializeObjectGraph(FileStream fs, IFormatter formatter
        , Object rootObj) {
        // 파일에서의 현재 위치를 저장해둔다. 
        Int64 beforeSerialization = fs.Position;
    
        try {
            // 파일에 객체 그래프를 저장(serialize)한다. 
            formatter.Serialize(fs, rootObj);
        }
        catch {    // 모든 예외를 잡는다. 
            // 만일 조금이라도 잘못된 부분이 있으면, 파일을 이전 상태로 재설정한다. 
            fs.Position = beforeSerialization;
    
            // 파일 크기 재설정
            fs.SetLength(fs.Position);
    
            // 참고: 파일 저장이 실패하였을 때만 스트림을 재설정해야 하기 때문에
            // 앞쪽의 코드는 finally 블록에 둘 수 없다.
            throw;
        }
    }

    'CLR Via C#' 카테고리의 다른 글

    21장. 관리 힙과 가비지 수집  (0) 2022.07.13
    19장. Null 값 타입  (0) 2022.06.21
    18장. 사용자 정의 특성  (0) 2022.03.16
    17장. 델리게이트  (0) 2022.03.09
    16장. 배열  (0) 2022.02.28
Designed by Tistory.