본문 바로가기

춘기IT/춘기개발

예외 처리로 코드 만들기

Today I Learned (250709) 5.

예외 (Exception)

- 프로그램이 실행되는 동안 발생할 수 있는 예외적인 상황

- 예외를 처리해주지 않으면 프로그램이 종료되지만, 처리해주면 프로그램이 정상적으로 실행됨

- 모든 종류의 예외는 Exception 클래스를 상속 받아서 구성됨
- Exception 클래스를 상속 받지 않고 Exception 클래스로 객체를 생성해줘도 되지만,
  그렇게하면 그 예외가 어떤 타입의 예외인지 알기 어렵기 때문에
  예외의 타입을 명확히 하기 위해서 Exception 클래스를 상속 받은 클래스로 객체를 생성해 주는 것이 좋음
var exception = Exception("무슨 예외일까요 ?")

// 이렇게 Exception 클래스를 직접 써서 예외 객체를 만들어도 되지만
예외가 발생했을 때 Exception을 보면 어떤 유형의 예외인지 알 수 없음..​

예외 종류 - 정의하는 주체에 따라 2가지로 나뉨

  • Dart가 미리 정의해 둔 예외
  • 사용자가 직접 정의한 예외

1. Dart가 미리 정의해 둔 예외

DeferredLoadException

- 필요한 시점에 로드되도록 설정한 라이브러리가 로드되지 못 했을 때 발생

    • 라이브러리: 자주 사용하는 코드들을 미리 정의해 두고, 뽑아서 쓸 수 있게 함 !
    • 라이브러리를 쓰려면 라이브러리를 불러와야 하는데, 라이브러리를 불러오는 것에 실패하면 예외 발생
DeferredLoadException(String message)

//message에 예외에 대한 설명을 쓰면됨

 

FormatException

- 데이터가 처리 가능한 형태가 아니어서 데이터를 처리하기 어려울 때 발생

FormatException([String message = "", dynamic source, int? offset])

/* message: 예외에 대한 설명
source: 예외가 발생한 원인이 되는 것
// 오류발생하는모습
String invalidNumber = "abc";
int result = int.parse(invalidNumber);

/* 참고로, int.parse() 는 매개변수에 넣은 문자열을 정수형으로 바꿔줌
매개변수에 넣은 문자열을 정수형으로 바꿀 수 없으면 FormatException 발생

 

 IOException

- 입출력 관련 동작을 하는 중에 발생하는 오류

- 입출력 동작 중에 발생하는 오류 종류

FileSystemException
- 파일을 다룰 때 발생
- 파일에 접근할 수 없는 경우, 파일이 없는 경우, 파일을 찾을 수 없는 경우 정도

HttpException

ProcessException

SignalException

SocketException

StdinException

StdoutException

TlsException

WebSocketException
IOException()

 

OSError

- 운영체제 레벨에서 발생하는 오류

- 운영체제로부터 받은 오류에 대한 정보를 들고 있음

OSError([String message = "", int errorCode = noErrorCode])

/* message에는 운영체제가 제공하는 예외에 대한 설명이 들어감
운영체제가 설명을 제공하지 않으면 빈 문자열 (””) 적용
errorCode 에는 운영체제가 제공하는 오류 코드가 들어감.

TimeoutException

- 비동기 결과를 기다리는 동안 특정 시간이 초과되었을 때 발생

TimeoutException(String? message, [Duration? duration])

/* message에는 시간 초과가 발생한 원인에 대한 설명이 들어가고,
duration 에는 초과된 시간이 들어감

 

2. 사용자가 직접 정의한 예외

- Dart가 정의한 예외들이 마음에 안 들어서 커스텀하고 싶을 때 사용

- Exception 클래스를 상속 받아 새로운 예외 클래스를 만듦

class AgeException implements Exception {
  final String message;
  
  AgeException(this.message);
}

//상속 받을 때 implements 키워드 사용
class AgeException implements Exception {
  final String? message;
  
  AgeException(this.message);
  
  @override
  String toString() => message ?? 'AgeException 이 발생했어요 !';
}

/* 예외 클래스도 당연히 @override 통해 재정의 할 수 있음
상속 받은 후에 @override를 통해 재정의 할 수 있음

 

사용자가 직접 정의한 예외 예시 코드

class AgeException implements Exception {
  final String? message;
  
  AgeException(this.message);
}

void printAge(int age) {
	if (age < 0) {
		throw AgeException(null);
	}
  print(age);
}

void main() {
	try {
		printAge(-8);
	} catch (e) {
    print(e);
  }
}

/* printAge()의 매개변수에 음수값을 전달했기 때문에
Instance of 'AgeException’이 출력
class AgeException implements Exception {
  final String? message;
  
  AgeException(this.message);
}

void printAge(int age) {
	if (age < 0) {
		throw AgeException(null);
	}
  print(age);
}

void main() {
	try {
		printAge(8);
	} catch (e) {
    print(e);
  }
}

/* printAge()의 매개변수에 0이상의 값을 전달했기 때문에 8만 출력
class AgeException implements Exception {
  final String? message;
  
  AgeException(this.message);
  
  @override
  String toString() => message ?? 'AgeException 이 발생했어요 !';
}

void printAge(int age) {
	if (age < 0) {
		throw AgeException(null);
	}
  print(age);
}

void main() {
	try {
		printAge(-8);
	} catch (e) {
    print(e);
  }
}

/* printAge()의 매개변수에 음수값을 전달했기 때문에
AgeException 이 발생했어요 ! 출력
class AgeException implements Exception {
  final String? message;
  
  AgeException(this.message);
  
  @override
  String toString() => message ?? 'AgeException 이 발생했어요 !';
}

void printAge(int age) {
	if (age < 0) {
		throw AgeException('0 이상의 값을 넣어야 해요 !');
	}
  print(age);
}

void main() {
	try {
		printAge(-8);
	} catch (e) {
    print(e);
  }
}

/* AgeException 의 생성자에 null 이 아닌 값을 넣어 주었기 때문에
0 이상의 값을 넣어야 해요 ! 출력

예외에 사용하는 키워드 - 목적에 따라 크게 2가지로 나뉨

1. 발생한 예외를 처리하기 위한 경우 (try, catch, on, finally 사용)

try

- 예외가 발생할 수 있는 코드를 넣는 부분

- 예외 처리 구문을 쓸 때 꼭 넣어야 함

- try의 코드 블록 실행 중에 예외가 발생하면, try의 코드 블록에 있는 뒷 코드들은 실행되지 않음 > catch 문으로 넘어감

try {
	// 예외가 발생할 수 있는 코드
}

 

catch

- try의 코드 블록 실행 중에 예외가 발생했을 때 실행할 코드를 넣는 부분

- try의 코드 블록에서 예외가 발생하지 않으면 실행되지 않음

- 예외 처리 구문을 쓸 때 꼭 넣어야 하고, 개수에 제한이 없음

catch (e) {
	print(e);
}

//발생한 예외의 객체가 catch의 매개변수로 전달됨

 

on

- try의 코드 블록에서 발생할 수 있는 예외 중 특정 타입의 예외를 다루는 부분

- 예외 처리 구문을 쓸 때 넣지 않아도 됨

- 단독으로 사용할 수 없고, catch와 항상 같이 써야함

- 예외 타입이 특정되기 때문에 catch에 객체가 전달되었을 때 그 객체를 가지고 할 수 있는 동작이 더 많음

- on으로 예외 타입을 특정해주지 않으면 Exception이 기본적으로 가지고 있는 4자기 요소만 사용할 수 있음
- on으로 예외 타입을 특정해주면 그 타입이 가지고 있는 요소들을 사용할 수 있음
on IOException catch (e) {
	// IOException 이 발생했을 때 실행할 코드
}

 

finally

- 예외 발생 여부와 상관없이 실행할 코드를 넣는 부분

- try, catch를 모두 거친 후 실행됨

- 예외 처리 구문을 쓸 때 넣지 않아도 됨

finally {
	// 예외 처리 후 실행할 코드
}

 

예시코드

try {
	String invalidNumber = "abc";
	int result = int.parse(invalidNumber);
	print(result);
} catch (e) {
	print(e);
}

/* e에 FormatException 클래스의 객체가 전달돼서
FormatException: abc 라고 출력
try 의 코드 블록에 있는 int.parse(invalidNumber); 에서 예외가 발생했기 때문에
그 아래에 있는 print(result); 는 실행되지 않고, catch 문으로 넘어감.
try {
	String invalidNumber = "abc";
	int result = int.parse(invalidNumber);
	print(result);
} on FormatException catch (e) {
	print(e);
}

/* 마찬가지로 e 에 FormatException 클래스의 객체가 전달돼서
FormatException: abc 라고 출력.
try {
	String invalidNumber = "abc";
	int result = int.parse(invalidNumber);
	print(result);
} on FormatException catch (e) {
	print(e);
} catch (e) {
	print(e);
}

/* on FormatException catch (e) { … } 는 FormatException이 발생하면 실행되고,
catch (e) { … } 는 FormatException 이 아닌 다른 예외가 발생하면 실행됨
try {
  String invalidNumber = "abc";
  int result = int.parse(invalidNumber);
  print(result);
} catch (e) {
  print(e);
} finally {
  print('예외 처리 끝 -');
}

/* FormatException: abc 랑 예외 처리 끝 - 이 차례대로 출력
try {
  String validNumber = "123";
  int result = int.parse(validNumber);
  print(result);
} catch (e) {
  print(e);
} finally {
  print('예외 처리 끝 -');
}

/* “123” 은 정수형으로 변환할 수 있기 때문에 예외가 발생하지 않음
그래서 result 값인 123 이랑 예외 처리 끝 - 이 차례대로 출력

 

2. 예외를 의도적으로 발생시키기 위한 경우

throw

- 특정 경우에 예외를 의도적으로 발생시키기 위해 Exception 클래스나 Error 클래스를 통해 생성한 객체를 던짐

   ㄴ Exception 클래스나 Error 클래스를 상속 받은 클래스도 가능

- throw를 통해 발생된 예외는 try, catch, on으로 처리할 수 있음

void printAge(int age) {
  if (age < 0) {
    throw Exception('나이는 0 이상이어야 해요 !');
  }
  print(age);
}

void main() {
  try {
    printAge(-8);
  } catch (e) {
    print(e);
  }
}

/* printAge() 의 매개변수에 음수값을 전달하면 Exception 이 발생하기 때문에
Exception: 나이는 0 이상이어야 해요 ! 가 출력
try 의 코드 블록 실행 중에 Exception 이 발생했으니
print(age); 는 실행되지 않음
void printAge(int age) {
  if (age < 0) {
    throw Exception('나이는 0 이상이어야 해요 !');
  }
  print(age);
}

void main() {
  try {
    printAge(8);
  } on Exception catch (e) {
    print(e);
  }
}

/* printAge() 의 매개변수에 0 이상의 값을 전달하면
Exception 이 발생하지 않기 때문에 print(age); 로 8 출력