예외 처리 1

이동건 (불꽃냥펀치)·2024년 11월 20일
0

예외 처리가 필요한 이유

  • NetworkClient: 외부 서버와 연결하고, 데이터를 전송 및 연결을 종료하는 기능을 제공
  • NetworkService: NetworkClient를 사용해서 데이터를 전송한다. NetworkClient를 사용하ㅁ려면 연결, 전송 , 연결 종료와 같이 복잡한 흐름을 제어해야 하는데 이런 부분은 서비스가 담당함
  • Main: 사용자의 입력을 받는다.
  • 전체흐름: Main을 통해 사용자의 입력을 받으면 사용자의 입력을 서비스에 전달한다.NetworkServiceNetworkClient를 사용해서 외부 서버에 연결하고, 데이터를 전송하고, 전송이 완료되면 연결을 종료한다.

NetworkServiceV0

 package exception.ex0;
 public class NetworkClientV0 {
     private final String address;
     
     public NetworkClientV0(String address) {
         this.address = address;
         }
         
	public String connect() { //연결 성공
		System.out.println(address + " 서버 연결 성공");
         return "success";
     }
     
	public String send(String data) { //전송 성공
 		 system.out.println(address + " 서버에 데이터 전송: " + data);
         return "success";
     }
	public void disconnect() { 
    	System.out.println(address + " 서버 연결 해제");
	} 	
}
  • String address: 접속할 외부 서버 주소
  • connect(): 외부 서버에 연결한다
  • send(String data): 연결한 외부 서버에 데이터를 전송한다.
  • disconnect(): 외부 서버와 연결을 해제한다.
package exception.ex0;
import java.util.Scanner;
public class MainV0 {
    public static void main(String[] args) {
        
        NetworkServiceV0 networkService = new NetworkServiceV0();
        Scanner scanner = new Scanner(System.in);
       
       while (true) {

			System.out.print("전송할 문자: ");
            String input = scanner.nextLine(); 
            if (input.equals("exit")) {
				break; 
            }
            networkService.sendMessage(input);
            System.out.println();
        }
		
        System.out.println("프로그램을 정상 종료합니다."); }
}
  • 전송할 문자를 Scanner를 통해서 입력받는다.
  • NetworkService.sendMessage()를 통해 메시지를 전달한다.
  • exit를 입력하면 프로그램을 정상 종료한다.
  • 여기서 서버와 오류상황을 일으켜 보자
  • 연결 실패: 사용자가 입력하는 문자에 error1 단어가 있으면 연결에 실패한다.
  • 전송 실패: 사용자가 입력하는 문자에 error2 단어가 있으면 데이터 전송에 실패한다.

NetworkServiceV1

package exception.ex1;
 public class NetworkClientV1 {
     private final String address;
     public boolean connectError;
     public boolean sendError;
     
     public NetworkClientV1(String address) {
         this.address = address;
	}
     
     public String connect() {
         if (connectError) {
			System.out.println(address + " 서버 연결 실패");
             return "connectError";
         }
         //연결 성공 System.out.println(address + " 서버연결 성공");
         return "success";
	}
	
    public String send(String data) {
    	if (sendError) {
        	System.out.println(address +"서버에 데이터 전송 실패: " + data);
        	return "sendError";
    	}
		 System.out.println(address + "서버에 데이타 전송: " + data);
         return "success";
	}
	
    public void disconnect() {
		System.out.println(address + "서버 연결해제");
	}
    
    public void initError(String data) {
        if (data.contains("error1")) {
            connectError = true;
        }
        if (data.contains("error2")) {
            sendError = true;
		} 
	}
}
  • sendError: 이 필드의 값이 true가 되면 연결에 실패하고 connectError 오류 코드를 반환한다.
  • connectError: 이 필드의 값이 true가 되면 데이터 전송에 실패한다. sendError오류 코드를 반환한다.
  • 문제가 없으면 success 코드를 반환한다.
  • initError(String data)
    • 이 메서드를 통해서 connectError,sendError 필드의 값을 true로 설정할 수 있다.
    • 사용자 입력 값에 "error1"이 있으면 connectError 오류가 발생하고 "error2"가 있으면 sendError 오류가 발생한다.
package exception.ex1;
public class NetworkServiceV1_1 {
	public void sendMessage(String data) {
		
        String address = "http://example.com";
		NetworkClientV1 client = new NetworkClientV1(address); 		
        client.initError(data); //추가
        client.connect();
        client.send(data);
        client.disconnect();
	} 
}
package exception.ex1;
 import java.util.Scanner;
 public class MainV1 {
         public static void main(String[] args) {
         
         NetworkServiceV1_1 networkService = new NetworkServiceV1_1();
         Scanner scanner = new Scanner(System.in);
         while (true) {
		System.out.print("전송할 문자: "); 
        String input = scanner.nextLine(); 
        if (input.equals("exit")) {
			break; 
            }
             networkService.sendMessage(input);
             System.out.println();
         }
		System.out.println("프로그램을 정상 종료합니다."); 
        }
}
  • error1, error2를 입력하면 사용자는 각각 서버연결, 데이터 전송에 실패한다.
  • 여기서 문제는 연결이 실패하면 데이터를 전송하지 않아야 하는데, 여기서는 각각 error1, error2 문자를 데이터로 전송한다.
public class NetworkServiceV1_3 {
    
    public void sendMessage(String data) {
        
        NetworkClientV1 client = new NetworkClientV1("http://example.com");
        client.initError(data);
        String connectResult = client.connect();
        if (isError(connectResult)) {
			System.out.println("[네트워크 오류 발생] 오류 코드: " + connectResult); 
            } 
        else {
            String sendResult = client.send(data);
            if (isError(sendResult)) {
				System.out.println("[네트워크 오류 발생] 오류 코드: " + sendResult); 
                }
			}
        client.disconnect();
    }
   
   private static boolean isError(String resultCode) {
        return !resultCode.equals("success");
	} 
}
  • 코드를 수정한 결과 연결에 실패해도 disconnect()를 호출
  • 데이터 전송에 실패해도 disconnect()를 호출
  • 하지만 위 코드를 보면 정상 흐름과 예외 흐름이 분리되지 않아 어떤 부분이 정상인지 파악하기가 힘들다.
  • 그뿐만 아니라 코드의 양이 많아져 처음 보는 사람들이 이해하기 어렵다.
  • 이런 점을 보완하기 위해 예외 처리를 도입해보겠다.

예외 계층

자바는 프로그램 실행 중에 발생 할 수 있는 예상치 못한 상황, 예외를 처리하기 위한 메카니즘을 제공한다.

  • Object: 자바에서 기본형을 제외한 모든 것은 객체다.
  • Throwable: 최상위 예외이다.
  • Error: 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구가 불가능한 시스템 예외이다. 애플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.
  • Exception: 체크예외
    • 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외이다.
    • Exception과 그 하위예외는 모두 컴파일러가 체크하는 체크 예외이다. 단 RuntimeException은 예외로 한다.
  • RuntimeException:언체크 예외
    • 컴파일러가 체크하지 않는 언체크 예외이다.
    • RuntimeException과 그 자식 예외는 모두 언체크 예외이다.
    • RuntimeException의 이름을 따라서 RuntimeException과 그 하위 언체크 예외를 런타임 예외라고 많이 부른다.

체크 예외 VS 언체크 예외

체크 예외는 발생한 예외를 개발자가 명시적으로 처리해야한다. 그렇지않으면 컴파일 오류가 발생한다. 언체크 예외는 개발자가 예외를 명시적으로 처리하지 않아도 된다.

예외 기본 규칙

  • 예외는 잡아서 처리하거나 밖으로 던져야 한다.
  • 예외를 잡거나 던질 떄 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리할 수 있다.
    • 예를 들어 Exceptioncatch로 잡으면 그 예외의 자식들도 함께 처리할 수 있다.
    • 예를 들어 Exceptionthrows로 던지면 그 하위 예외들도 모두 던질 수 있다.


체크 예외

  • 체크는 잡아서 처리하거나 또는 그 밖에서 던지도록 선언해야한다. 그렇지 않으면 컴파일 오류가 발생한다.
package exception.basic.checked;
/**
* Exception을 상속받은 예외는 체크 예외가 된다. */
 public class MyCheckedException extends Exception {
     public MyCheckedException(String message) {
           super(message);
    }
}
  • 예외 클래스를 만들려면 예외를 상속 받으면 된다.
  • Exception을 상속받은 예외는 체크 예외가 된다.
package exception.basic.checked;
public class Client {
    public void call() throws MyCheckedException {
        throw new MyCheckedException("ex");
    }
}
  • throw 예외라고 하면 새로운 예외를 발생 시킬 수 있다. 예외는 객체이기 때문에 먼저 new로 생성하고 예외를 발생시켜야 한다.
  • throws 예외는 발생시킨 예외를 메서드 밖으로 던질 때 사용하는 키워드이다.
package exception.basic.checked;
/**
* Checked 예외는
* 예외를 잡아서 처리하거나, 던지거나 둘중 하나를 필수로 선택해야 한다. */
public class Service {
    Client client = new Client();
/**
* 예외를 잡아서 처리하는 코드 */
    public void callCatch() {
        try {
            client.call();
        } catch (MyCheckedException e) {
		//예외 처리 로직
		System.out.println("예외 처리, message=" + e.getMessage()); }
		System.out.println("정상 흐름"); 
        }
/**
* 체크 예외를 밖으로 던지는 코드
* 체크 예외는 예외를 잡지 않고 밖으로 던지려면 throws 예외를 메서드에 필수로 선언해야한다. */
     public void callThrow() throws MyCheckedException {
         client.call();
		} 
}
  • callCatch() : Client에서 던진 오류를 try-catch로 잡는 메서드
    • 만약 try에서 잡은 예외가 catch의 대상에 없다면 예외를 잡을 수 없다.
    • 이때는 예외를 밖으로 던져야한다.
package exception.basic.checked;
 public class CheckedThrowMain {
	public static void main(String[] args) throws MyCheckedException {
		Service service = new Service();
		service.callThrow();
		System.out.println("정상 종료");
	} 
}
  • callThrow() : Client에서 던진 오류를 잡지 않고 메서드 밖으로 던짐

    • 예외를 처리하지 않고 밖으로 던져서 나중에 예외가 main() 메서드까지 올라갈 수 있다.
    • 예외를 잡아서 처리하지 못했기 때문에 여기서 예외가 main() 밖으로 던져져, service.callThrow() 메서드 다음에 있는 "정상 종료"가 출력되지 않는다.


    체크 예외의 장단점

  • 장점: 개발자가 실수로 예외를 누락해도 컴파일러를 통해 문제를 잡을 수 있는 훌륭한 안전장치이다.

  • 단점: 모든 체크 예외를 반드시 잡거나 던지도록 처리해야한다.


언체크 예외

체크 예외 VS 언체크 예외

  • 체크 예외: 예외를 잡아서 처리하지 않으면 항상 throws 키워드를 사용해서 던지는 예외를 선언해야 한다.
  • 언체크 예외: 예외를 잡아서 처리하지 않아도 throws키워드를 생략할수 있다.
 package exception.basic.unchecked;
/**
* RuntimeException을 상속받은 예외는 언체크 예외가 된다. */
 public class MyUncheckedException extends RuntimeException {
     public MyUncheckedException(String message) {
         super(message);
     }
}
package exception.basic.unchecked;
 public class Client {
     public void call() {
         throw new MyUncheckedException("ex");
     }
}
package exception.basic.unchecked;
/**
* UnChecked 예외는
* 예외를 잡거나, 던지지 않아도 된다.
* 예외를 잡지 않으면 자동으로 밖으로 던진다. */
 public class Service {
     Client client = new Client();
/**
* 필요한 경우 예외를 잡아서 처리하면 된다. */
     public void callCatch() {
         try {
             client.call();
         } catch (MyUncheckedException e) {
		//예외 처리 로직
			System.out.println("예외 처리, message=" + e.getMessage()); 
         }
		 System.out.println("정상 로직"); 
    }
/**
* 예외를 잡지 않아도 된다. 자연스럽게 상위로 넘어간다.
* 체크 예외와 다르게 throws 예외 선언을 하지 않아도 된다. */
      public void callThrow() {
         client.call();
	} 
}

언체크 예외의 장단점

  • 장점: throws의 생략이 가능하다.
  • 단점: 개발자가 실수로 예외를 누락할 수 있으며,이때 컴파일러가 오류를 잡지 못한다.







출처: https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EC%A4%91%EA%B8%89-1/dashboard

profile
자바를 사랑합니다

0개의 댓글

관련 채용 정보