예외

try{

}catch(<예외 클래스 1> 변수명){

}finally{
//예외 발생 여부와 상관없이 항상 실행되는 코드
// 생략가능
}

catch와 예외 클래스

  • C#의 예외와 마찬가지로 특정 예외를 캐치할 수 있음
    • IOException 입출력과 관련된 예외 클래스들의 부모 클래스
      • EOFException,FileNotFoundFException, FileSystemFException 등
      • ArithmeticException : 산술과 관련된 예외(0으로 나누기)
      • IndexOutOfBoundsException : 배열이나 문자열의 색인 범위가 넘어갈 때 등등
  • 특정하기 어렵거나 모든 예외를 잡고 싶다면 Exception 클래스 사용

다중 catch블록 작성시 주의할 점

  • 부모 예외 클래스가 자식보다 먼저 나오면 안 됨
try{

}catch(Exception e){

}catch(FileNotFoundException e){

}finally{
    ...
}
// 잘못된 예
try{

}catch(FileNotFoundException e){

}catch(Exception e){

}finally{
    ...
}

// 올바른 예
  • FileNotFoundException가 발생해도 첫 번째 catch블록이 실행됨

C# 예외 처리 예

static void PrintLines(string path){
    if(!File.Exists(path)){
        return;
    }
    string[] lines;
    try
    {
        lines = File.ReadAllLines(path);
    }
    catch(FileNotFoundException e)
    {
        Console.Error.WriteLine($"file not found: {e.Message}");
        return;
    }

    foreach (var line in lines)
    {
        //출력
    }
}

java 예시

public void printLines(String relativePath){
    Path path = Paths.get(getClassPath(), relativePath);
    
    List<String> lines;
    try{
        lines = File.readAllLines(path);
        System.out.format("%d lines read%s", lines.size(), NEW_LINE);
    }catch(IOException e){
        System.out.println(e.getMessage());
        e.printStackTrace();
        return;
    }

    for(String line : lines){
        System.out.println(line);
    }
}

printStackTrace getMessage

public void printStackTrace()
  • 현재 발생한 예외의 호출 스택을 보여줌
  • C#의 StackTrace 프로퍼티와 동일
public String getMessage()
  • 예외가 왜 발생했는지를 설명
  • 메시지가 업을 수도 있음 이때는 null 반환

finally 사용 예

파일에 두번 쓰는 예 C#

static void SriteByte(string path, byte b)
{
    FileStream fs = null;
    try
    {
        fs = File.Open(path, FileMode.Open);
        fs.WriteByte(b);
    }
    catch (IOException e)
    {
        Console.Error.WriteLine($"{e.Message}");
        return;
    }
}

string path;
WriteByte(path,67);
WriteByte(path,67)
  • 두번째 쓰려고하면 예외 발생 파일을 닫지 않았기 때문
static void SriteByte(string path, byte b)
{
    FileStream fs = null;
    try
    {
        fs = File.Open(path, FileMode.Open);
        fs.WriteByte(b);
        fs.Close();
    }
    catch (IOException e)
    {
        Console.Error.WriteLine($"{e.Message}");
        fs.Close();
        return;
    }
}
  • 이렇게 닫는 코드를 추가해도 문제가 발생할 수 있었음
  • IOException 말고 다른 예외가 발생할 경우
static void SriteByte(string path, byte b)
{
    FileStream fs = null;
    try
    {
        fs = File.Open(path, FileMode.Open);
        fs.WriteByte(b);
    }
    catch (IOException e)
    {
        Console.Error.WriteLine($"{e.Message}");
        return;
    }
    finally
    {
        if(fs != null)
        {
            fs.Close();
        }
    }
}

자바에서

public void writeByte(String relativePath, byte b){
    Path path = Paths.get(getClassPath(), relativePath);
    FileOutputStream out = null;
    try{
        out = new FileOutputStream(new File(path.toString()), true);
        out.write(b);
        out.close();
    }catch (IOException e){
        e.printStackTrace();
        return;
    }
}

  • 이 java코드가 정상적으로 도는 것 처럼 보일 수 있음 그러나 이렇게 하지 말것

GC가 파일을 대신 닫아준다 그러나

  • 가비지 수집기가 참조되지 않는 FileOutputStream개체를 해제
  • 그때 GC가 호출하는 finalize()가 clase()메서드를 호출
  • 그러나 그 시점이 언제인지 알 수 없음
  • 또한 GC가 실행되기전에 OS 리소스의 한계에 다다를 수도 있음 이럴때도 예외 발생

java9부터 finalize() 사용을 피하라함

  • 향후 해당 메서드를 삭제할 예정
  • 따라서 finalize()에 의존해 clase()를 호출하는 습관을 버려야 함
  • 훌륭한 프로그래머는 이런 마법같은것(언젠가 GC가 내가 닫지 않은 파일을 닫아준다)에 의존하지 않음
  • finalize()에 대해 좀 더 궁금하다면 java 문서를 확인할 것

close()를 호출하는 방법

  • 직접 호출
  • try with resources
    • java 7부터 사용가능
    • 이 과목에서 다루지 않음
    • 궁금하면 직접 java 문서를 확인할것

      올바른 예 java

public void writeByte(String relativePath, byte b){
    Path path = Paths.get(getClassPath(), relativePath);
    FileOutputStream out = null;
    try{
        out = new FileOutputStream(new File(path.toString()), true);
        out.write(b);
    }catch (IOException e){
        e.printStackTrace();
        return;
    } finally {
        if(out != null){
            try{
                out.close();
            }catch(Exception e){
                
            }
            // C#에서는 생략가능
        }
    }
}

정리 예외가 발생했을 때 진행 순서

  1. try 블록의 실행이 중단됨
  2. catch 블록중에 발생한 예외를 처리할 수 있는 블록이 있는지 찾음 위에서부터 하나식 평가
  3. 예외를 처리할수 있는 catch 블록이 없다면
    1. finally 블록을 실행후 한단계 높으느 try 블록으로 전달
  4. 예외를 처리할 수 있는 catch 블록이 있다면
    1. 해당 catch 블록 안의 코드들이 실행
    2. finally블록을 실행
    3. try 블록 이후의 코드들이 실행됨

catch블록에서 다시 예외 던지기

  • catch and rethrow
  • 단 이때 호출 스택이 유지되어야 함
  • 잘못사용하면 호출 스택이 날라감
  • 언어마다 방식이 다름

C#

static void SriteByte(string path, byte b)
{
    FileStream fs = null;
    try
    {
        fs = File.Open(path, FileMode.Open);
        fs.WriteByte(b);
    }
    catch (ArgumentNullException e)
    {
        throw;
    }
    catch (Exception e)
    {
        Console.Error.WriteLine($"{e.Message}");
        return;
    }
    finally
    {
        if(fs != null)
        {
            fs.Close();
        }
    }
}

C#에 throw e 하면 다날라감

java

public void writeByte(String relativePath, byte b){
    Path path = Paths.get(getClassPath(), relativePath);
    FileOutputStream out = null;
    try{
        out = new FileOutputStream(new File(path.toString()), true);
        out.write(b);
    }catch (NullPointerException e){
        System.err.println("found null pointer!");
        throw e;
    }catch (IOException e){
        e.printStackTrace();
        return;
    } finally {
        if(out != null){
            try{
                out.close();
            }catch(Exception e){
                
            }
            // C#에서는 생략가능
        }
    }
}
  • rethrow는 좋은 습관이 아님 그러나 반드시 해야한다면 호출 스택을 유지하면서 던질 것.

나만의(custom) 예외 만들기

public final class UserNotFoundException extends RuntimeException{
    public UserNotFoundException(){
        super();
    }
    public UserNotFoundException(String message){
        super(message);
    }
    public UserNotFoundException(String message, Throw cause){
        super(message, cause);
    }
}

RuntimeException의 생성자

RuntimeException()
RuntimeException(String message)
RuntimeException(String message, Throwable cause)
RuntimeException(Throwable cause)
  • 자식 클래스들의 생성자에서 반드시 super()를 호출해야 함
  • 호출 안 할 경우 message, cause가 저장되지 않음
  • message는 getMessage()로 확인 가능
  • cause는 getCause()로 확인 가능

사용예

public static void main(String[] args){
    db.findUser("pope");
    ...
}
public User findUser(String username){
    for(User user : users){
        if(user.getUsername().equals(username)){
            return user;
        }
    }
    // NOTE : 좋은 코드는 아님. 그저 예외를 설명하기 위한 예
    throw new UserNotFoundException(username);
}

C# 예

public sealed class UserNotFoundException : Exception
{
    public UserNotFoundException(string message) : base(message)
    {
    }
}
  • C#은 Exception을 상속
  • java는 RuntimeException을 상속
  • java도 Exception을 상속 받을 수있음

Exception을 상속받은 예외

public final class UserNotFoundException extends Exception{
    public UserNotFoundException(){
        super();
    }
    public UserNotFoundException(String message){
        super(message);
    }
    public UserNotFoundException(String message, Throw cause){
        super(message, cause);
    }
}

java의 예외는 크게 두 분류로 나뉨

  • 다른 언어의 예외와 다른 점
  • 역사적인 이유도 있고 정신적인 이유도 있음
  • 그래서 배경 스토리를 먼저 좀 알아야함

JVM 환경에서 도는 프로그램에서 발생한 예외를 전형 처리 안 하면 어떻게 되는지 알아보자

  • main 메서드에서 까지 아무런 처리를 안 해주면 jvm이 오류 메시지를 보여주고 프로그램을 종료시킴
  • JVM에서 프로그램을 종료시켜서 OS나 기계에는 아무 영향없음

가상머신없는는 옛 언어의 작동

  • 언젯적 Os/기계를 쓰느냐에 따라 다름

아주 옜날 하드웨어/OS

  • 한 번에 프로그램 하나만 실행할 수 있음
  • 따라서 하드웨어에 있는 메모리를 모든 프로그램이 공유함
  1. CPU에서 0으로 나누려 함
  2. CPU의 ALU는 0으로 나누는 연산을 처리할 수 없음
  3. 하드웨어가 멈춤
  4. 기계를 재부팅해야 함.

웹서버같이 지속적인 조작 업이 알아서 실행돼야 하는 프로그램이라면?

근래의 하드웨어/os

  • 여러 프로그램이 동시에 실행 됨
  • 따라서 각 프로그램마다 별도의 메모리 공간(가상 메모리)을 제공
  • jvm이 보장해주던 안전성을 os가 책임져주는 꼴
    1. cpu에서 0으로 나누려 함
    2. cpu의 alu는 0으로 나누는 연산을 처리할 수 없음
    3. cpu등에서 문제가 있다는 인터럽트나 시그널등을 보내줌(이걸 프로그램에서 제대로 처리 안 해주면 프로그램이 크래시 날 수 있음)
    4. os가 그 상황을 캐치한 뒤 프로그램을 종료하고 가상 메모리를 해제(그 프로그램의 외부 리소스에 영향이 미치지 않음)

예외처리를 제대로 하지 못하는 이유

이런 주장을 하는 사람이 있음

  • 함수에서 오류코드(INT, 열거형,null 등)을 반환해서 오류 상황을 알려주는 건 절대 금지

  • 함수에서 반환하는 것은 무조건 올바른 값
  • 문제가 잇으면 무조건 예외를 던져야한다. 그리고 호출자는 그 예외를 제대로 처리해줘야한다.

문제는 수십 년이 지나도록 이걸 제대로 한 사람이 드물었음

함수가 더이상 블랙박스가 아니게 됨

  • 어느 함수에서 어떤 예외를 던지는지 한눈에 안 보임
  • 알려면 모든 함수의 속을 다 보며 확인해야 함
  • 여러단계에 함수 호출 시 확인해야 하는 함수 수는 수천 개
  • 캡슐화의 반대되는 행위
  • 함수 위 주석에 표기하기도 했지만 사람들이 잘 안읽음
  • 인간의 이해 없이 만든 방법론은 실패하기 마련

java는 이에 좀 더 대비되어 있었음

  • java가 예외를 크게 두 분류로 나눴기 때문
  • checked 예외 : java에만 있는 예외
    • IOException
    • SQLException
    • TimeoutException
  • unchecked 예외
    • ArithmeticException
    • BufferOverflowException
    • ClassCastException

unchecked 예외

  • C# 예외와 동일 : 어디서 어떤 예외가 나는지 한눈에 안 보임
  • RunctimeException을 상속
  • 컴파일러가 따로 검사를 안 해줘서 unchecked 예외

checked 예외

  • 컴파일러가 예외 처리를 제대로 하는지 확인 해줌
  • 어느 메서드가 어떤 예외를 던지는지 명확히 알 수 있음
  • 예외가 발생하는 코드에서 이 둘 중 하나를 안 하면 컴파일 오류
    • 발생한 예외를 그 메서드 안에서 처리
    • 처리를 안 할 경우 그 사실을 메서드 시그내처 옆에 표기

public User findUser(String username) throws UserNotFoundException{
    for(User user : users){
        if(user.getUsername().equals(username)){
            return user;
        }
    }
    // NOTE : 좋은 코드는 아님. 그저 예외를 설명하기 위한 예
    throw new UserNotFoundException(username);
}
// throws UserNotFoundException 빼먹으면 컴파일 오류
public static void main(String[] args){
    User user = null;
    try{
        user = db.findUser("pope");
    } catch (UserNotFoundException e){
        e.printStackTrace();
    }
    //무조건 예외처리를 해야함
}
<메서드 선언> throws <예외 클래스 이름>
public User findUser(String username) throws UserNotFoundException{}
  • 다음과 같은 메서드의 시그내처 옆에 반드시 추가해야함
    • checked 예외를 던지는 메서드
    • 다른 메서드에서 발생한 checked 예외를 처리하지 않는 메서드
  • 추가 안 하면 컴파일 오류
// 위처럼 main에서 예외처리를 하기 싫은 경우

public static void main(String[] args) throws UserNotFoundException{
        user = db.findUser("pope");
}

사실 RuntimeException도 Exception을 상속 받음

  • 과거 java 커뮤니티는 checked 예외를 선호
  • 지금은 약간 실패한 느낌

checked 예외를 넣었던 이유

API 제작자가 이건 클라이언트가 반드시 처리해야 할 예외라고 알려주는 용도

처리하라는 의미

  1. 프로그램 종료? : JVM까지 예외를 올린다.
    1. 처리 안하고 계속 올릴시에 throw가 굉장히 많아짐
    2. 이게 의도였다면 unchecked 예외를 사용 하면 되기에 틀린 추측
  2. 예외를 무시하고 진행
    1. 출력만 하고 try 블록 다음으로 진행
    2. 그러면 왜 예외를 던지는가
  3. 어떻게든 프로그램을 정상상태로 회복할것.

exception safe programming

  • 과연 회복이 쉬운가? : 매우 힘듬
  • 클래스 몇 개만 있는 간단한 구조에서는 가능함
  • 복잡해질수록 어려워 짐
  • 여전히 필요한 곳이 일부 있음
  • 모든곳에서 이럴 수 없다는 것

근래의 예외 처리 트렌드

  1. 그냥 unchecked 예외를 쓰자고 함
    1. 다시 다른 언어와 똑같아짐
    2. 심지어 checked예외를 unchecked 예외로 감싸서 다시 던지자 라고 의견을 제시하는 사람들도 증가함
    3. 호출 트리에서 봤던 문제는 고치지 못함 : 누가 어떤 예외를 던지는지 모름.
  2. 예외로부터 안전한 최선의 방법은 재부팅
    1. 예외로부터 회복하지 않는다
    2. 디버깅에 필요한 정보를 최대한 남기고 프로그램 종료
    3. 뒤에 좀 더 자세히 배움

예외를 얼마나 세분화해서 처리해야하는가(exception granularity)

예전에 좀 더 선호하던 방향

예외를 종류별로 catch한다
  • 예외 형(type)에 따라 처리를 달리 해줘야 했기 때문 : 그리고 궁극적으론 회복
  • 주로 checked 예외를 사용했음
  • 따라서 문제 하나하나마다 예외 클래스를 만들곤 했음

요즘에 좀 더 선호하는 방향

모든 예외는 한번에 처리하자
Exception으로 한방에 잡자 (많이 동의)
  • 주로 main()함수에서 한 번만(덜 동의)
  • 따라서 다시 예외를 던지는 일도 훨씬 적어짐
  • 여전히 던질 때는 세세한 예외 형을 던짐(커스텀 예외 포함)
  • catch를 Exception으로 한방에 할 뿐(많이 동의)
  • 조금더 극단적인 주장도 있음
    • 던지는 것도 무조건 RunTimeException()으로 던짐

이런 글이 많이 보이니 조심할것

대부분의 프로그램은 모든 예외로부터 회복해야한다~
안 그러고 프로그램을 종료시켜도 되는 유일한 경우는 프로그램을 시작할 때 뿐이다.
  • 기본적으로 나쁜 조건

    지켜보는 사람 없이 작동해야한다면?

  • 대표적인 예 : 웹서버
  • 근래의 OS는 하드웨어 재부팅이 필요 없음
    • 각 프로그램마다 별도의 가상 메모리를 제공
    • 프로그램 크래시가 기계 크래시로 이어지지 않음
  • 프로그램 재실행은 다른 프로그램이 담당
    • 실행 중인 프로세스가 사라지면 다시실행하도록 설정 가능
    • 재시작 시 가상 메모리를 배정받으니 프로그램이니 깨끗한 상태로 시작
  • 이걸 무시하고 예외로부터 안전한 프로그래밍을 시도하다 실패했다면?
    • 프로그램이 이상한 상태에 빠져 더 큰 문제가 생길 수도 있음
    • 이런 상태의 프로그램을 흔히 좀비 프로그램이라고 함
    • 보자 자세한 내용은 C++~에서

상황에 맞는 예외처리 방법을 선택해야 한다

제어 흐름용으로 예외를 사용하지 말 것

제어흐름 용으로 예외를 사용

  • goto와 개념이 같음
  • goto가 아니라 호출 스택 어디로도 점프가능
  • goto보다 더 기가 막힌 hack

절대 다음에 실행할 코드를 결정하는 용도로 쓰지 말 것

정말 나쁜 예 재귀함수 한방에 빠져나오기

void search(TreeNode node, Object data) throws ResultException{
    if(node.data.equals(data)){
        throw new ResultException(node);
    }else {
        search(node.leftChild, data);
        search(node.rightCild, data);
    }
}

자바 자체 라이브러리에도 이런 부분이 있음

public static int parseInt(...) throws ...
  • 문자열로부터 정수를 읽는 데 실패하면 무조건 예외 발생
  • 정수가 아닌 경우에 프로그램의 로직을 달리하고 싶다면
    • 예: 사용자에게 다시 정수를 입력해달라고 요청
    • 어쩔 수 없이 이건 예외를 받아서 판단하는 수 밖에 없음

C# 은 int.TryParse()로 해결

  • 프로그래머의 의도에 따라 원하는 함수를 선택
    • 반드시 int라고 가정하면, Parse()
    • int가 아닐 경우 로직을 달리하고 싶다면, TryParse()
  • java에서도 정 원한다면 직접 구현해서 사용가능
    • 본인의 메서드 안에서 try-catch를 사용해야함
    • 궁금하면 검색

예외적인 상황에만 예외를 사용해야 하는 경우

  • 할 수 있다고 다 해야 하는 건 아니다.

Why not use exceptions as regular flow of control?

  • 예외는 오류상황이라는 전제하에 모든 툴이 개발되어 있음
  • 그걸 자기 혼자 이상하게 사용하면 툴의 혜택을 못 받음

컴파일 경고를 안 고치는 것과 똑같은 이야기

  • 컴파일 할 때마다 별 의미 없는 경고가 94개 나옴
  • 정말 실수한 걸 컴파일러가 찾아서 95번째 경고를 보여주면 프로그래머가 알아 챌 수 있을까

올바른 오류 처리 방법

오류상황, 예외 상황

  • 프로그램은 기본적으로 happy path를 따름
  • 근데 그 happy path가 아닌 경우가 생기기도함
    • error condition exceptional condition 이라함
    • 근데 예외 상황이 프로그래밍 언어의 Exception을 의미하는 경우도 있음
    • 오류상황이라는 용어를 사용해서 혼란을 줄일 계획
  • 오류상황에 빠진 프로그램은 어떻게 진행해야 하는가
    • 그걸 결정하는게 오류 처리방법
    • throw try catch등으로 예외를 던지고 처리하는 것도 그중 일부

오류상황은 예측 가능한 상황을 의미

  • 프로그램 실행 중에 기본적으로 일어나지 않는 일
  • 하지만 여전히 일어날 수 있는 일
    • 따라서 이런 상황을 처리하는 코드는 프로그램 기능의 일부
  • 그러나 프로그래머가 미리 예측치 못했다면?
    • 처리 코드가 있을 수 업음
    • 버그
    • 버그 발견후 제대로 처리하는 코드를 추가 -> 다시 빌드

4가지 오류 상황 처리법

  • The Object Oriented Thought Process 에 기초
  1. 무시하고 넘어간다 무시
  2. 문제를 일으킬수 있는 상황이 있는지 검사하고, 그렇다면 프로그램을 종료한다 종료
  3. 문제를 일으킬 수 있는 상황이 있는지 검사하고, 그렇다면 실수를 고친 뒤 계속 프로그램을 실행되게 한다. 수정
  4. 문제가 발생하면 예외를 던진다 예외
  • 1,2,3은 java 탄생전부터 많이 사용하던 방법
  • 한동안 4만하면 프로그램 품질이 높아진다고 약 팔던 사람들이 잇었음
  • 1,2,3은 개체지향적으로 사용할 수 없다며 배척하려는 움직임도

훌륭한 프로그래머는 다르다 했다

  • 훌륭한 프로그래머는 문제 해결에 가장 적합한 방법을 사용
  • 4가지 방법마다 다 적합한 상황이 있음
  • 따라서 이 방법들을 모두 다 사용
  • 이런 점을 잘 염두에 두고 이번 강의를 볼 것

처음부터 문제없는 코드가 최고

프로그램은 여러 시스템으로 구성되어 있다.

  • 각 공간의 제작자는 그 공간을 완전히 통제
    • 자기 공간에 대해 매우 잘 앎
  • 그러나 다른 제작자의 공간은 통제 못함
    • 다른 공간에 대해 잘 알지 못하기 쉬움

가장 훌륭한 마음가짐

내 가족 믿기

  • 내 시스템 안에 들어온 데이터는 언제나 유효
    • 예외 상황을 고려할 필요가 없음
    • 예외 상황이 발생하면 그건 버그
    • 깊은 함수 호출이 있어도 복잡하지 않음

이웃 의심

  • 남으로부터 받아오는 데이터는
    • 언제든 유효하지 않을 수 있음
    • 경계에서 반드시 검증해야함
    • 잘못된 데이터는 경계에서 곧바로 거부
    • 남에게 문제가 있다고 알려줌

남에게 문제를 알려주는 방법

  1. boolean, null 반환
  2. 오류코드(int, enum) 반환
  3. 예외 던짐

통제가능한 코드냐 아니냐가 중요

  • 경계에서 검증만 잘하면 꽤 많은 오류 처리 코드를 줄일 수 있음

1. 무시한다

다음 중 한가지 일이 발생

  1. 곧바로 크래시
  2. 일단 작동하지만 언젠가 크래시
  3. 안정적이지 못한 상태로 계속 동작

2. 미리 검사 후 프로그램을 종료한다.

  • 어떤 문제가 있엇는지 사용자에게 보여주고 정상 종료
    • 주로 팝업 창이나 로그(log) 파일
    • 종료방법
      • C# : Application.Exit() 호출
      • java :System.exit(int) 호출
  • 사용자는 그런 문제가 있었군이라 생각하고 다시 프로그램을 실행
  • 크래시에 비해 나은점
    • 제대로 시스템 상태를 정리하고 프로그램을 종료할 가능성이 높음
    • 따라서 프로그램 종료 후 시스템이 좀 더 안정적이 ㄹ가능성이 높음
  • 그보다 훨씬 더 큰 장점
    • 작업하던 내용을 날리지 않고 저장해 줄 수 있음

3. 미리 검사 후 문제를 고친다.

  • 예 분모가 0일경우 1로 바꿈
  • UX 고려 시 이보다 나은 해법
    • 사용자에게 올바른 값을 입력하라고 다시 요청
    • 이게 방금 전에 말한 입력값 검증 방법 중 하나
  • 이건 개체지향적이 아니라며 무조건 배척하려는 사람들도 있음
    • 특히 모든 오류 처리를 예외로 하자는 사람들
    • 이 덕에 오류코드파 vs 예외파 논쟁이 한동안 계속됨
  • UX를 포기하면서까지 OO정신을 계승할 이유가 없음
  • 예외는 다른 방법에 비해 성능이 가장 느림
  • 결론: 설계와 필요한 성능 수준에 따라 OO에서도 충분히 좋은 방법
  • 단점도 있음
    • 문제가 처음 발생한 곳을 파악하기가 쉽지 않음
    • 문제가 발생했다는 사실조차 모른 채 오랜시간이 흐를 수 있음
    • 예: 파일 읽는 함수에서 파일을 못 찾았는데 빈 문자열을 반환하면?
      • 빈 파일을 읽는 것과 다르지 않으므로 그 순간에는 올바른 방법일 수 있음
      • 나중에 그 파일에 문자열을 덧붙이려고 하면 그건 불가능한 연산
      • 프로그래머는 있던 파일이 사라졌다 생각할 수도 있음

4 예외를 던진다

  • 예외는 다음 두 가지를 지원
    • 문제가 발생했다는 사실을 알려줄 수 있음
    • 그 예외를 처리할 수 있음
  • 대부분의 OO언어는 예외를 지원

예외는 OO의 일부가 아니다.

  • 비슷한 시기에 나온게 고작
  • 극단적인 진영은 OO에서 무조건 예외처리만 해야한다고 함.

반대증거 : 안드로이드 앱은 Java 기반이다.

예외 외에는 해결책이 없는 경우 : 생성자

  • 문제를 수정한다 는 불가능
    • 이미 개체가 생성됨
    • 생성자에서 null을 반환할 수 있음
  • 문제가 발생했다는 사실을 알려주려면 예외가 유일한 방법
  • 언어의 제약으로 인해 생성자 안에서 문제가 생기면 되돌릴 방법이 없음

크래시의 진정한 문제점은 작업물을 잃어버리는 것.

크래시의 문제점 해결 : 자동 세이브

  • 문서 편집기가 5분마다 자동으로 세이브해 줌
  • 심지어 온라인 문서 편집기는 언제나 저장됨
    • history 무한 되돌리기 기능까지 존재
    • 뻑나도 문제X

크래시와 메모리 덤프

  • 디버깅을 잘하는 프로그래머들은 오히려 크래시를 선호
    • 문제를 일찍 발견
    • 메모리 덤프 등을 통해 실제 크래시가 발생한 이유를 곧바로 조사 가능
  • 메모리 덤프 예
    • 외부 프로그램이 내 프로그램 실행
    • 내 프로그램에서 크래시 발생
    • 메모리 덤프 생성
    • 개발사에게 인터넷으로 전송
    • 이 메모리 덤프를 디버거에서 열면 실제 모든 스택 값을 다 볼 수 있음
    • 게임이나 윈도우 OS등의 프로그램이 이걸 잘 활용 중
  • 예외를 던지면 이 정도 정보 밖에 없음
    • 호출 스택 정보(문자열)
    • 메시지(null인 경우가 빈번)
    • 예외 형

프로그램 종료가 매력적인 이유

  • 잘못된 예외처리 때문에 이상한 상태에 빠지는 일을 방지
  • 물론 크래시보다 좋음
    • 최근 5분 동안의 작업물도 다 저장해 줄 수 있으니

4가지 처리법의 순위

  1. 수정
  2. 종료
  3. 예외 : 남에게 폭탄 돌리는 꼴
  4. 무시 : 책임감과 많이 멈

이미 예측한 상황 + 고치기 쉬운경우

  • 고치려면 일단 고쳐도 안전하며 인간이 하기에 너무 어렵지 않아야 함
  • 고치고 계속 프로그램 진행(수정, 예외)
    • 내 코드 안에서는 수정이 더 나은 방법이라 생각
    • 예외는 두 공간 사이의 경계에서만 던지는 게 더 명백
  • 다른 시스템에서 발생한 예외 역시 경계에서 처리하고 계속 진행

    이미 예측한 상황 + 고치기 어려운 경우

  • 최종 사용자에게 메시지를 보여주고 싶다면
    • 예 : 프로그램에서 팝업 창을 띄워 메시지 보여주기
    • 중간 단계 어디에서도 catch안하고 최상위 함수까지 예외를 전달
    • 최상위 함수에서 팝업창을 띄워 메시지를 보여줌
    • GUI앱이 아니라면 main 함수에서 로그 파일에 기록
  • 최종 사용자에게 메시지를 보여주기 실핟면
    • 예 : 처음부터 GUI가 없는 프로그램
    • 최소한 로그는 남기는 게 좋음(이메일로 로그를 보내달라 요청할 수 있음)
    • 크게 두 방법
      • 문제 발생지점에서 로그를 남기고 곧바로 프로그램 종료
      • 예외를 던져 main에서 catch후 로그를 남기고 종료

예외를 main에서 로그를 남기고 종료

  1. 앞에서 봤듯이 중간에 누가 catch하는 문제
    1. 내 의도와는 달리 클라이언트가 잘못 catch
  2. 로그 외에 자세한 디버깅 정보도 없다는 문제
    1. 메모리 덤프나 스택정보가 없음
    2. 차라리 문제지점에서 일부로 크래시를 내서 메모리 덤프를 하는게 나을지도
  3. 로그를 제대로 안 적는 문제
    1. 문서화도 잘 안 하는 프로그래머 로그를 제대로 남길까
    2. 메모리 덤프는 프로그래머가 게을러도 모든 정보가 있음

예측 못한 상황

  • 처리 코드가 있을 리 없음
  • 따라서 수정하는 방법을 논할 가치도 없음
  • 앞서 본 이미예측 + 고치기 어려운 경우의 방법과 비슷
  • 예측 못했으니 로그를 남겼을리 없음
  • 프로그래밍 언어 따라 둘 중하나
    • 혹시라도 발생할 수 있는 모든 예외를 main()에서 catch한 뒤 로그를 남김
      • 단 자세한 디버깅 정보 없음
    • 크래시가 나서 곧바로 메모리 덤프가 생김
      • 충분히 자세한 디버깅 정보 있음

jvm에서도 메모리 덤프는 가능하지만

  • 제한적인 경우에만 가능하면 설정도 안 쉬움
  • 자바 진영에 예외를 최고라 생각한 사람이 많아서?

마지막 교훈

실행중에 문제를 고치려 한다고 프로그램의 안정성이 높아지는 건 아니다. 좀비가 되면 그게 더 큰 문제