728x90

프로그래밍을 하다보면 에러를 만난다. 요즘에는 예외와 에러를 따로부르는 경향도 있는 것 같다.

오랜 기간동안 자바가 강세였고 자바로 인해서 생긴 try-catch(자바가 처음이라고 안했습니다. 자바가 영향력이 컸다는 말입니다.)

문화는 대부분의 언어로 퍼져나갔고 현재 대부분의 언어에는 이 구문이 에러를 처리하는 대표적인 구문으로 정해지게 됬다.

하지만 Go에는 이런 try-catch가 없다. 그걸 염두하고 밑의 글들을 읽기 바란다.


예외처리를 배우기 앞서서 반드시 알아야할것은 에러와 예외이다.

사실 두녀석은 어찌보면 비슷하지만 결정적 차이가 있다.

전통적인 프로그래밍에서 에러와 예외는 사실상 거의 같다고 볼 수있다.

두 녀석의 공통점은 일단 발생하면 프로그램의 정상진행이 불가피하므로 프로그램을 종료해야한다는데 있다.

그리고 과거 예외처리가 존재하지 않던 C나 Basic같은 언어의 경우 이 둘은 그냥 뭉뚱그려서 에러라고 불렀다.

하지만 에러와 예외의 결정적인 차이점은 회피할수 있냐 아니냐로 분류된다.


자바 포스팅 24강


Go를 아는데 다른 언어가 필요하나 싶긴한데 사실 에러처리부분은 초보자들보다는 프로그래밍 경험이 있는 사람들이 많이 볼것 같기 때문에

부득이하게 기존 개념을 소개해야할 것 같다.


과거의 방법 => 에러상태를 나오고 해당값이 나오면 에러, error는 if문으로 잡아내자

var ERROR = 10000000000

func div(num1 float32, num2 float32) float32 {
if (num2 != 0) {
return num1 / num2
} else {
return float32(ERROR)
}
}

정말 과거의 방법인데 에러를 잡아내는 방식이 if문인 것이다.

일단 div는 나누는 함수인데 나눔은 0이되선 안된다.(컴퓨터는 0으로 나눌수 없다. 무한루프에 빠지게된다.)

그리고 에러시에 에러를 나타내는 코드를 동봉한다.

반환값이 float이지만 매크로로 미리 정해둔다.(go는 매크로가 없으므로 그냥 상수로 정하면된다.)


var num1 float32 = 3
var num2 float32 = 2
var result float32 = div(num1,num2)
if int(result) == ERROR{
fmt.Println("에러남")
}else{
fmt.Println(result)
}

그리고 div를 호출한되서는 에러를 if문으로 잡아내는 것이다.


위 방식은 구식이지만 아직도 여러코드에서 남아있다.

대표적으로 C와 C++에서는 아직도 이방식으로 에러를 검출해내는 코드가 많다.

하지만 이방식에는 치명적인 단점이 있다.

바로 에러처리 구문인지 분기문인지 겉보기에는 모른다는 것이다.


프로그래밍은 사람들과의 대화인데 div함수에 대한 에러처리구문인가?

아니면 그냥 분기문인가? 이는 프로그래머가 코드를 해석해야만 알 수 있다는 딜레마에 봉착하게 된다.


그럼 에러가 일어날 가능성이 있으면 try에, 처리는 catch로 처리하는 try-catch를 만들자

그래서 만들어 진게 다른 언어의 try-catch문이다.

사실 원리는 if문과 크게 다를게 없지만 일반 로직과 에러 로직을 분리할 수 있게된다.


package com.jiharu.main;

public class Main {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;
try{
System.out.println(num1 / num2);
}
catch (ArithmeticException e){
System.out.println("0으로 나눌 수 없다.");
}
}
}

위 코드는 자바 코드이다. try-catch문 자체가 자바를 쓰는 사람에게는 "아 예외(에러)가 일어났구나"라는걸 상기시킨다.
그래서 위의 코드에서 에러와 로직이 구분 가능해질...줄 알았지만...

try (PrintWriter out = asyncContext.getResponse().getWriter()) {
asyncContext.getResponse().setContentType("application/json");
out.println(data);
out.flush();
asyncContext.complete();
} catch (Exception e) {
/*pass*/
}


위의 코드는 jsp에서 long polling구현하는 예제에서 나온 코드의 일부이다.
보면 알겠지만 저 코드가 어디서부터 어디까지가 에러인지 명확한가?
try문의 의미는 "여기서 에러가 일어날 수 있어"라고 알려주는건데 그 길이가 길어진다면...
필연적으로 로직이 에러구문에 들어가게된다.

과거에는 로직에 에러가 들어갔는데 이제는 에러에 로직이 들어가는 본말 전도 현상이 일어나게된다.

그래서... Go에서는 또다시 완전 다른 방법으로 사용하게 된다.

그럼 Go에서는 어떻게 하는데?
func div(num1 float32, num2 float32) (float32, error) {
if (num2 != 0) {
return num1 / num2, nil
} else {
return 0, &MyError{time.Now(), "You can't divide by zero."}
}
}
go에서는 error를 함께 반환한다. 항상.
보통 대부분의 함수들은 로직 뒤에 에러도 반환하게 된다.
만약 에러가 안일어난다면 nil을 일어난다면 에러를 반환한다.


func main() {
var num1 float32 = 3
var num2 float32 = 2
result, err := div(num1, num2)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
}

그리고 main에서는 err를 체크해서 처리해주는 로직을 달아주면 에러 처리구문은 끝난다.

위의 구문으로 인해서 if문은 에러가 일어나지 않을 경우, else문은 에러가 일어날때의 처리를 넣는다.


unc main() {
var num1 float32 = 3
var num2 float32 = 2
result, err := div(num1, num2)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result)
}

사실 위 같은 형태보다 이런 형태로 많이 쓰게된다.

이게 go의 철학과도 연결되 있다.

즉 에러가 일어나면 거기서 진행을 안하는 것이다.

코드의 종류에 따라서 다르겠지만 에러가 일어나면 아래 부분들을 진행할 수 없는 경우가 더 많다.

가령 위의 코드에서 error가 난다면 앞으로 result의 값을 쓸 수 없으므로 진행해봤자 별 의미가 없다.

그래서 그냥 에러가 나면 끝내버리는 것이다.


물론 그렇게 짜지 않는것도 가능하고 그런 경우도 많다.

경우에 따라서 다르지만 위와 같이 쓰는게 Go의 일종의 문화라는 것이다.

필자의 go예제 포스팅을 보아도 이러한 루틴은 쉽게 확인할 수 있다.


이로써 여러분은 에러를 처리할 수 있게 되었다.

물론 초보단계에서 에러 처리를 적극활용하는 경우는 드물지만 앞으로 계속해서 해나가다보면 에러를 본인이 직접 만들어야하는 경우가 빈번할 것이다.


+ Recent posts