[자바로 배우는 리팩토링 입문] 10장 에러 코드를 예외로 치환

2022. 6. 8. 18:35·Major/Java
728x90

* 예외 처리 

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) 실패 원자성

- 예외를 던질 때 객체가 이도저도 아닌 상태에 빠지지 않게 하라 

 

 

 

728x90
저작자표시 비영리 (새창열림)

'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
'Major/Java' 카테고리의 다른 글
  • [자바로 배우는 리팩토링 입문] 9장 분류 코드를 상태/전략 패턴 치환
  • [자바로 배우는 리팩토링 입문] 8장 분류 코드를 하위 클래스로 치환
  • [자바로 배우는 리팩토링 입문] 7장 분류 코드를 클래스로 치환
  • [자바로 배우는 리팩토링 입문] 6장 클래스 추출
BeNI
BeNI
코딩하는 블로그
  • BeNI
    코딩못하는컴공
    BeNI
  • 전체
    오늘
    어제
    • Menu (254)
      • My profile (1)
      • 회고 | 후기 (8)
      • Frontend (66)
        • Article (11)
        • Study (36)
        • 프로그래머스 FE 데브코스 (19)
      • Backend (0)
      • Algorithm (58)
        • Solution (46)
        • Study (12)
      • Major (111)
        • C&C++ (23)
        • Java (20)
        • Data Structure (14)
        • Computer Network (12)
        • Database (15)
        • Linux (6)
        • Architecture (3)
        • Lisp (15)
        • OS (1)
        • Security (2)
      • etc (2)
  • 링크

    • 깃허브
    • 방명록
  • 인기 글

  • 최근 댓글

  • 최근 글

  • 태그

    리팩토링
    react
    데브코스
    C++
    백준
    프로그래머스
    lisp
    Algorithm
    파일처리
    자료구조
  • hELLO· Designed By정상우.v4.10.2
BeNI
[자바로 배우는 리팩토링 입문] 10장 에러 코드를 예외로 치환
상단으로

티스토리툴바