* 예외 처리
1. 예외란
- 예외 : 잘못된 코드, 부정확한 데이터, 예외적인 상황에 의하여 발생하는 오류
2. 예외 처리기 (try-catch, throws)
try{
// 예외가 발생할 수 있는 코드
} catch{
// 예외 처리
} finally {
// try 블록이 끝나면 무조건 실행된다
}
public void writeList() throws IOException
{
...
}
3. 예외의 종류
1) Error
- 자바 가상 기계 안에서 치명적인 오류 발생
- H/W 문제 등으로 파일을 읽을 수 없는 경우
- 컴파일러가 체크하지 않음!
2) RuntimeException
- 프로그래밍 버그나 논리 오류에 기인한다
- 컴파일러가 체크하지 않음!
3) 기타 예외
- 회복할 수 있는 예외로 반드시 처리해야 함
- 컴파일러가 체크함!
1. 리팩토링
1) 에러 코드를 예외로 치환 : 에러 발생 사실을 에러 코드로 표현
2) 리팩토링 카탈로그
이름 | 에러 코드를 예외로 치환 |
상황 | 에러 발생 사실을 에러 코드로 표현함 |
문제 | 정상 처리와 에러 처리가 혼재함 에러 코드 전파 처리가 넓은 범위에 있음 |
해법 | 에러 코드 대신에 예외를 사용함 |
결과 | o 정창 처리와 에러 처리를 명확하게 분리 가능 o 에러 코드를 반환해서 전파하지 않아도 됨 o 에러 관련 정보를 예외 객체 저장 가능 x 에러 발생 부분과 에러 처리 부분이 분리되기 때문에 알기 어려워지는 경우도 있음 |
2. 예제 프로그램
클래스명 | 역할 |
Robot | 명령어를 실행하는 로봇을 나타내는 클래스 |
Command | 로봇 제어 명령어를 나타내는 클래스 |
Main | 로봇 움직임을 확인하는 클래스 |
Position | 로봇 위치를 나타내는 클래스 |
Directon | 로봇 방향을 나타내는 클래스 |
1) 리팩토링 전
package before;
import java.util.*;
public class Robot {
private final String _name;
private final Position _position = new Position(0, 0);
private final Direction _direction = new Direction(0, 1);
public Robot(String name) {
_name = name;
}
public void execute(String commandSequence) {
StringTokenizer tokenizer = new StringTokenizer(commandSequence);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (!executeCommand(token)) {
System.out.println("Invalid command: " + token);
break;
}
}
}
public boolean executeCommand(String commandString) {
Command command = Command.parseCommand(commandString);
if (command == null) {
return false;
}
return executeCommand(command);
}
public boolean executeCommand(Command command) {
if (command == Command.FORWARD) {
_position.relativeMove(_direction._x, _direction._y);
} else if (command == Command.BACKWARD) {
_position.relativeMove(-_direction._x, -_direction._y);
} else if (command == Command.TURN_RIGHT) {
_direction.setDirection(_direction._y, -_direction._x);
} else if (command == Command.TURN_LEFT) {
_direction.setDirection(-_direction._y, _direction._x);
} else {
return false;
}
return true;
}
public String toString() {
return "[ Robot: " + _name + " "
+ "position(" + _position._x + ", " + _position._y + "), "
+ "direction(" + _direction._x + ", " + _direction._y + ") ]";
}
}
package before;
import java.util.*;
public class Command {
public static final Command FORWARD = new Command("forward");
public static final Command BACKWARD = new Command("backward");
public static final Command TURN_RIGHT = new Command("right");
public static final Command TURN_LEFT = new Command("left");
private static final Map<String,Command> _commandNameMap = new HashMap<String,Command>();
static {
_commandNameMap.put(FORWARD._name, FORWARD);
_commandNameMap.put(BACKWARD._name, BACKWARD);
_commandNameMap.put(TURN_RIGHT._name, TURN_RIGHT);
_commandNameMap.put(TURN_LEFT._name, TURN_LEFT);
}
private final String _name;
private Command(String name) {
_name = name;
}
public String getName() {
return _name;
}
public static Command parseCommand(String name) {
if (!_commandNameMap.containsKey(name)) {
return null;
}
return _commandNameMap.get(name);
}
}
package before;
public class Position {
public int _x;
public int _y;
public Position(int x, int y) {
_x = x;
_y = y;
}
public void relativeMove(int dx, int dy) {
_x += dx;
_y += dy;
}
}
package before;
public class Direction {
public int _x;
public int _y;
public Direction(int x, int y) {
_x = x;
_y = y;
}
public void setDirection(int x, int y) {
_x = x;
_y = y;
}
}
package before;
public class Main {
public static void main(String[] args) {
Robot robot = new Robot("Andrew");
System.out.println(robot.toString());
robot.execute("forward right forward");
System.out.println(robot.toString());
robot.execute("left backward left forward");
System.out.println(robot.toString());
robot.execute("right forward forward farvard");
System.out.println(robot.toString());
}
}
2) 리팩토링 실행
① 에러 종류에 맞는 적절한 예외 작성
⑴ 예외 상태가 아니라면 예외를 사용하지 않음
⑵ 복구 가능한 에러라면 검사 예외 선택
⑶ 복구 불가능한 에러 또는 프로그래머 실수로 인한 에러라면 비검사 예외 선택
⑷ 컴파일
② 메서드를 호출하는 쪽 변경(검사 예외)
⑴ 호출하는 쪽에서 에러를 처리한다면 try~catch 추가
⑵ 호출하는 쪽에서 에러를 처리하지 않는다면 throws 절 추가
⑶ 컴파일해서 테스트
③ 메서드를 호출하는 쪽 변경(비검사 예외)
* 검사 예외와 비검사 예외
3) 리팩토링 후
import java.util.*;
public class Robot {
private final String _name;
private final Position _position = new Position(0, 0);
private final Direction _direction = new Direction(0, 1);
public Robot(String name) {
_name = name;
}
public void execute(String commandSequence) {
StringTokenizer tokenizer = new StringTokenizer(commandSequence);
try {
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
executeCommand(token);
}
} catch (InvalidCommandException e) {
System.out.println("Invalid command: " + e.getMessage());
}
}
public void executeCommand(String commandString) throws InvalidCommandException {
Command command = Command.parseCommand(commandString);
executeCommand(command);
}
public void executeCommand(Command command) throws InvalidCommandException {
if (command == Command.FORWARD) {
_position.relativeMove(_direction._x, _direction._y);
} else if (command == Command.BACKWARD) {
_position.relativeMove(-_direction._x, -_direction._y);
} else if (command == Command.TURN_RIGHT) {
_direction.setDirection(_direction._y, -_direction._x);
} else if (command == Command.TURN_LEFT) {
_direction.setDirection(-_direction._y, _direction._x);
} else {
throw new InvalidCommandException();
}
}
public String toString() {
return "[ Robot: " + _name + " "
+ "position(" + _position._x + ", " + _position._y + "), "
+ "direction(" + _direction._x + ", " + _direction._y + ") ]";
}
}
import java.util.*;
public class Command {
public static final Command FORWARD = new Command("forward");
public static final Command BACKWARD = new Command("backward");
public static final Command TURN_RIGHT = new Command("right");
public static final Command TURN_LEFT = new Command("left");
private static final Map<String,Command> _commandNameMap = new HashMap<String,Command>();
static {
_commandNameMap.put(FORWARD._name, FORWARD);
_commandNameMap.put(BACKWARD._name, BACKWARD);
_commandNameMap.put(TURN_RIGHT._name, TURN_RIGHT);
_commandNameMap.put(TURN_LEFT._name, TURN_LEFT);
}
private final String _name;
private Command(String name) {
_name = name;
}
public String getName() {
return _name;
}
public static Command parseCommand(String name) throws InvalidCommandException {
if (!_commandNameMap.containsKey(name)) {
throw new InvalidCommandException(name);
}
return _commandNameMap.get(name);
}
}
3. 코드 추가 수정
1) 분류 코드를 상태/전략 패턴으로 치환
- if문의 연쇄는 switch문과 마찬가지로 악취를 풍긴다
- 따라서 forward, backward, turn_right, turn_left는 상태/전략 패턴으로 리팩토링한다.
import java.util.*;
public abstract class Command {
public static final Command FORWARD = new Forward();
public static final Command BACKWARD = new Backward();
public static final Command TURN_RIGHT = new Right();
public static final Command TURN_LEFT = new Left();
private static final Map<String,Command> _commandNameMap = new HashMap<String,Command>();
static {
_commandNameMap.put(FORWARD._name, FORWARD);
_commandNameMap.put(BACKWARD._name, BACKWARD);
_commandNameMap.put(TURN_RIGHT._name, TURN_RIGHT);
_commandNameMap.put(TURN_LEFT._name, TURN_LEFT);
}
private final String _name;
protected Command(String name) {
_name = name;
}
public String getName() {
return _name;
}
public static Command parseCommand(String name) throws InvalidCommandException {
if (!_commandNameMap.containsKey(name)) {
throw new InvalidCommandException(name);
}
return _commandNameMap.get(name);
}
public abstract void execute(Robot robot);
// 중첩 클래스
private static class Forward extends Command {
public Forward() {
super("forward");
}
@Override public void execute(Robot robot) {
robot.forward();
}
}
private static class Backward extends Command {
public Backward() {
super("backward");
}
@Override public void execute(Robot robot) {
robot.backward();
}
}
private static class Right extends Command {
public Right() {
super("right");
}
@Override public void execute(Robot robot) {
robot.right();
}
}
private static class Left extends Command {
public Left() {
super("left");
}
@Override public void execute(Robot robot) {
robot.left();
}
}
}
import java.util.*;
public class Robot {
private final String _name;
private final Position _position = new Position(0, 0);
private final Direction _direction = new Direction(0, 1);
public Robot(String name) {
_name = name;
}
public void execute(String commandSequence) {
StringTokenizer tokenizer = new StringTokenizer(commandSequence);
try {
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
executeCommand(token);
}
} catch (InvalidCommandException e) {
System.out.println("Invalid command: " + e.getMessage());
}
}
public void executeCommand(String commandString) throws InvalidCommandException {
Command command = Command.parseCommand(commandString);
command.execute(this);
}
public void forward() {
_position.relativeMove(_direction._x, _direction._y);
}
public void backward() {
_position.relativeMove(-_direction._x, -_direction._y);
}
public void right() {
_direction.setDirection(_direction._y, -_direction._x);
}
public void left() {
_direction.setDirection(-_direction._y, _direction._x);
}
public String toString() {
return "[ Robot: " + _name + " "
+ "position(" + _position._x + ", " + _position._y + "), "
+ "direction(" + _direction._x + ", " + _direction._y + ") ]";
}
}
4. 한 걸음 더 나아가기
1) 검사 예외와 비검사 예외
- 검사 예외는 보통 java.lang.Exception의 하위 클래스로 선언
해당 구문을 try-catch문으로 감싸거나 throws 절을 붙인다.
- 비검사 예외는 java.lang.RuntimeException의 하위 클래스로 선언한다.
2) 예외 계층
- 세세한 에러 처리를 하고 싶으면 계층 아래쪽에 있는 클래스를 예외로 잡는다.
- 여러 에러를 한꺼번에 처리하고 싶다면 계층 위쪽에 있는 클래스로 예외를 잡는다.
3) java.io.EOFException에 대해
- 데이터를 읽으려고 했지만 더는 데이터가 없을 때 발생하는 예외!
4) 비검사 예외와 사전 확인용 메서드
- 비검사 예외는 사전 확인으로 예외 발생을 회피 가능한 상황에 사용하는게 좋다.
5) 실패 원자성
- 예외를 던질 때 객체가 이도저도 아닌 상태에 빠지지 않게 하라
'Major > Java' 카테고리의 다른 글
[자바로 배우는 리팩토링 입문] 9장 분류 코드를 상태/전략 패턴 치환 (0) | 2022.06.08 |
---|---|
[자바로 배우는 리팩토링 입문] 8장 분류 코드를 하위 클래스로 치환 (0) | 2022.06.07 |
[자바로 배우는 리팩토링 입문] 7장 분류 코드를 클래스로 치환 (0) | 2022.06.04 |
[자바로 배우는 리팩토링 입문] 6장 클래스 추출 (0) | 2022.06.02 |
[자바로 배우는 리팩토링 입문] 5장 메서드 추출 (0) | 2022.06.01 |