NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    Java Java vs C# (Exception process, 예외 처리) - 13부

    페이지 정보

    본문

    안녕하세요. 소심비형입니다. 응용 프로그램을 개발하는데 있어 가장 중요한 부분중의 하나가 예외 처리입니다. 이는 예기치 못한 상황에서 안전하게 프로그램을 보호하고 주 프로그램이 종료되는 불상사(?)를 방지하기 위함입니다. 우리가 윈도우 95 또는 98시절에 흔히 보아왔던 블루스크린이 적절한 예가 될 수 있겠네요^^;

     

    Windows 9x 시절 블루 스크린!

    3dojlEW.jpg

     

     

    치명적인 에러가 발생하면 아래와 같은 화면도 보이곤 했습니다.

    Z1Etg2n.png

     

     

    윈도우의 경우에는 운영 체제이기 때문에 문제가 심각하지만, 업무용 응용 프로그램의 경우에는 그렇게 심각하게 받아들이지 않는 분위기인듯 합니다. 자동화팀에서 이미 사용자가 알아서 값을 입력하기 때문에 예외 처리는 할 필요가 없다고 말하기도 하지요. 하지만 대부분이 숫자를 입력해야 하는 부분에서 오타를 발생시키면 다시 입력할 수 있을거라 생각합니다. 하지만, 입력된 값을 받아서 연산하는 로직이 들어가 있다면 이 응용 프로그램은 사용자에게 "알 수 없는 문제로 응용 프로그램이 종료됩니다."라는 메시지를 보여주게 될겁니다. 이런 에러 메시지를 자주 보는것은 고객 입장에서도 짜증나는 일이지요. 하지만 이런 문제에 대해서 관대한 이유는 대부분 응용 프로그램을 이용해서 데이타를 보거나, 의사 결정에 필요한 정보를 얻는데 사용하기 때문입니다. 만약, 워드나 파워 포인트, 엑셀처럼 장시간 작업을 해야 한다면, 이런 문제는 짜증 정도가 아닌 뚜껑이 열리는 상황이 되어버립니다. (그래서 일정 시간을 주기로 자동 저장 하는 기능이 생겨나게 되었죠.)

    UwgS1QY.png

     

     

    예외 처리는 심각한 주제지만, 오늘은 간단하게 Java와 C#에서 공통적으로 제공하고 있는 try ~ catch ~ finally에 대해서 알아보도록 하겠습니다.

    Java

    import java.io.PrintWriter;
    import java.io.StringWriter;
    
    public class ExceptionProcess {
    	public static void main(String[] args) {
    		try {
    			Method2();
    		} catch (Exception ex) {
    			StringWriter sw = new StringWriter();
    			ex.printStackTrace(new PrintWriter(sw));
    			String exceptionAsString = sw.toString();
    			System.out.println(exceptionAsString);
    			System.out.println("Trace End.");
    		}
    	}
    
    	static void Method2() throws Exception {
    		try {
    			Method1();
    		} catch (Exception ex) {
    			throw ex;
    		}
    	}
    
    	static void Method1() throws Exception {
    		try {
    			throw new Exception("This is thrown from Method1");
    		} catch (Exception ex) {
    			throw ex;
    		}
    	}
    }

     

     

    위의 코드를 실행하면 아래와 같은 결과를 확인할 수 있습니다. getMessage 메소드를 통해 예외 메지시를 받아볼수도 있지만, printStackTrace를 통해 메시지와 예외가 발생된 메소드를 추적할 수도 있습니다.

    cboK5CD.png

     

    C#은 자바보다는 좀 더 편리하게 StackTrace를 확인할 수 있습니다. 아래 코드를 작성한 후 실행해 보세요.

    C#

    using System;
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    Method2();
                }
                catch (Exception ex)
                {
                    Console.Write(ex.StackTrace.ToString()); Console.ReadLine();
                }
            }
    
            static void Method2()
            {
                try
                {
                    Method1();
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
    
            static void Method1()
            {
                try
                {
                    throw new Exception("This is thrown from Method1");
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }
    }

     

     

    C#은 Java와 다르게 StackTrace로 예외 메시지를 받아볼 수 없습니다. Exception의 읽기 전용 속성인 Message를 통해 예외를 발생시킨 메시지를 확인할 수 있습니다. 하지만, Java보다는 예외에 대한 추적이 간단합니다.

    tXQBHVO.png

     

     

    이 예제에서 중요한 부분은 예외 메시지나 예외 추적에 대한 부분이 아닙니다. 어떻게하면 "예외를 효과적으로 처리할 것인가?"가 중요합니다. 여기에서 Java와 C#의 차이가 명확해집니다. 위의 예제를 보면 Java는 throw와 throws가 존재합니다. C#은 throws가 없구요. 기본적으로 C#은 throw를 통해 자신을 호출한 상위 메소드에 예외를 던지게끔 되어 있습니다. Java의 throw는 메소드 내에서 예외를 처리한다는 의미이며, throws는 자신을 호출한 상위 메소드로 예외를 던지는 역할을 합니다.

     

    Java의 경우에는 예외를 던지는 용도로 throw를 사용할 경우 반드시 throws와 같이 사용되어야 합니다. 공통점은 Java의 30라인과 C#의 36라인처럼 강제로 예외를 발생시킬 때 사용하는 부분입니다.

     

    C#의 경우에는 특이하게도 throw와 throw ex에 차이가 존재합니다. 이 부분은 미묘하면서도 상당히 중요한 부분이기도 합니다. 단순한 시스템에서는 크게 문제가 되지 않겠지만, 엔터프라이즈급 시스템 또는 회사 프레임워크를 사용하고 있다면 예외를 추적하는데 있어서 구현 내용에 따라 정확한 정보를 제공받을 수 없을지도 모릅니다. 아래와 같이 코드를 수정한 후 StackTrace 정보를 확인해 보도록 하겠습니다.

    C#

    using System;
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    Method2();
                }
                catch (Exception ex)
                {
                    Console.Write(ex.StackTrace.ToString()); Console.ReadLine();
                }
            }
    
            static void Method2()
            {
                try
                {
                    Method1();
                }
                catch (Exception ex)
                {
                    // throw                
                    throw ex;
                }
            }
    
            static void Method1()
            {
                try
                {
                    throw new Exception("This is thrown from Method1");
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }
    }

     

     

    위의 29라인과 같이 throw ex로 변경하고 실행해보면 아래와 같은 결과를 확인할 수 있습니다.

    VgDCO5P.png

     

     

    C#은 throw를 통해 전달받은 예외를 상위 메소드에 던집니다. 하지만, ex를 명시하면 전달받은 예외는 사라지고 자신의 예외를 던지게 됩니다. 그렇기 때문에 최하위(Method1)에 있는 메소드만 ex를 던지고 참조되는 메소드들은 ex를 통해 자체 처리(로그를 남기거나 메시지를 띄우거나... 등등...)에 사용하고 throw를 호출하여 원본 예외를 던져야 합니다. 이제 finally 블록에 대해서 알아보겠습니다. finally의 역할은 Java와 C#이 동일하므로 비교보다는 사용법에 대한 내용입니다. 가장 많이 사용하며, 흔하게 볼 수 있는 코드가 아래와 같은 코드입니다. 데이타베이스에 접속하기 위한 SqlConnection 개체를 사용할때죠.

    C#

    using System;
    using System.Configuration;
    using System.Data;
    using System.Data.SqlClient;
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    Method2();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message); 
                    Console.Write(ex.StackTrace.ToString()); 
                    Console.ReadLine();
                }
            }
    
            static void Method2()
            {
                try
                {
                    Method1();
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
    
            static void Method1()
            {
                SqlConnection connection = null; try
                {
                    DataSet ds = new DataSet(); 
                    connection = new SqlConnection(ConfigurationManager.ConnectionStrings["NGMASTER_DATABASE"].ConnectionString); 
                    SqlDataAdapter adapter = new SqlDataAdapter(); 
                    adapter.SelectCommand = new SqlCommand("SELECT (SYSDATE - 1) AS YESTERDAY FROM DUAL", connection); 
                    connection.Open(); 
                    adapter.Fill(ds);
                    // connection.Close();           
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    connection.Close();
                }
            }
        }
    }

     

     

    위 코드를 실행하면 에러가 발생됩니다. 완벽한 코드이기는 하나 예제이기 때문에 실제 ConnectionString이 존재하지 않습니다. 여하튼, 데이타베이스 접속 정보를 가지고 있는 ConnectionString이 존재한다고 가정합시다. 그렇다면 위의 코드는 50번 라인에서 에러를 발생시키게 됩니다. 이유는 간단한데 MSSQL Database에 접속하기 위한 Connection 개체를 생성했으므로 MSSQL 함수를 사용해야함에도 불구하고 Oracle 함수를 사용했기 때문이죠-_-;

     

    그렇다면 데이타베이스 접속 세션을 열어둔체로 예외로 빠지게 됩니다. 이런 경우에는 52라인에 있는 코드에 접근할 수 없게 됩니다. 그래서 예외가 발생하거나 또는 발생하지 않더라도 무조건 실행되어야 할 필요가 생기게 됩니다. 무조건 실행될 수 있도록 하는게 바로 fnally의 역할입니다.

     

    위의 코드는 try가 실행되고, 예외가 발생하면 finally를 실행하고 catch가 실행됩니다. 만약, 예외가 발생되지 않는다면 try 다음에 바로 finally가 실행됩니다. 이렇게 해서 파일 IO라던가, 다른 서버의 연결등을 안전하게 처리할 수 있습니다.

     

    사실 위의 코드를 좀 더 C#스럽게 만든다고 하면 아래처럼 using을 사용하여 블럭으로 감싸주어야 합니다.

                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    connection.Open();     
                    // 작업...    
                    // 이 using 블럭을 벗어나면 자동으로 close가 호출되고 연결은 닫힙니다.
                }

     

     

    마지막으로 C#은 아래 30라인과 같이 catch 블록에서 매개 변수를 생략해도 정상 동작합니다. Java는 필수죠.

    using System;
    using System.Configuration;
    using System.Data;
    using System.Data.SqlClient;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    Method2();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message); Console.Write(ex.StackTrace.ToString()); Console.ReadLine();
                }
            }
    
            static void Method2()
            {
                try
                {
                    Method1();
                }
                catch
                {
                    throw;
                }
            }
            
            static void Method1()
            {
                SqlConnection connection = null;
    
                try
                {
                    DataSet ds = new DataSet(); 
                    connection = new SqlConnection(ConfigurationManager.ConnectionStrings["NGMASTER_DATABASE"].ConnectionString); 
                    SqlDataAdapter adapter = new SqlDataAdapter(); 
                    adapter.SelectCommand = new SqlCommand("SELECT (SYSDATE - 1) AS YESTERDAY FROM DUAL", connection); 
                    connection.Open(); 
                    adapter.Fill(ds);
                    // connection.Close();            
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    connection.Close();
                }
            }
        }
    }

     

     

    예외 처리 및 방어 코드에 대해서 하고 싶은 이야기는 많지만, 모든 내용을 여기에서 다 풀어낼 수는 없습니다. 좀 더 많은 정보를 원하시면 Oracle이나 MSDN을 찾아보시기 바랍니다. 언젠가는 홈페이지에서 진행중인 Java와 C#에서 다룰수도 있겠지만, 언제가 될지는 모르겠군요-_-;

     

    다음 시간에...

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.