詳細検索

골랑의 오류 처리에 대한 보다 우아한 대안

아바타
글쓴이 Chen Ziyu

골랑의 오류 처리에 대한 보다 우아한 대안
English에서 번역 • 원문 보기

최근에 저는 Colorkrew에서 Golang을 백엔드 언어로 사용하는 새로운 프로젝트에 합류했습니다. 팀의 거의 모든 사람이 저를 포함해 Golang 경험이 전혀 없어서 처음부터 시작해야 했습니다. Golang을 배우는 것은 지금까지 매우 보람 있는 경험이었습니다. 결국 현재 가장 수요가 많은 프로그래밍 언어 중 하나이니까요. 하지만 Golang으로 코드를 작성하는 것이 항상 순조로운 것은 아닙니다. 의견이 없지만, 여전히 불만이 꽤 있는데, 그중 가장 큰 불만은 Golang의 권장 오류 처리 방식입니다.

Golang 공식 튜토리얼에 따르면, 오류가 발생하면 함수들이 일반 반환 값과 함께 오류를 반환해야 한다고 합니다. 다음은 Golang 튜토리얼에서 약간의 조정을 한 예시입니다:

패키지 메인

임포트 ( 
    "오류" 
    "FMT" 
    "로그" 
)

헬로는 이름이 밝혀진 사람에게 인사를 건넨다. 
func Hello(이름 문자열) (*string, 오류) { 
    if name == "" { 
        반환 없음, 오류가 발생. New("empty name")
    }

    메시지 := fmt. 스프린트프("안녕하세요, %v. 환영합니다!", 이름)
    반환 및 메시지, 없음
}

'Hello' 함수는 'name'이라는 문자열을 매개변수로 받습니다. 주어진 'name'을 사용해 '메시지'를 생성하고 반환합니다. 'name'이 빈 문자열인 것은 용납될 수 없으며 오류로 간주됩니다. 'Hello' 함수는 먼저 이름이 빈 문자열인지 확인합니다. 문자열이 비어 있으면 'Hello'는 'nil'과 'empty name' 오류를 반환합니다. 그렇지 않으면 'name' 매개변수와 빈 오류('nil')로 구성된 실제 메시지를 반환합니다.

Golang은 함수 반환 값의 일부로 호출자에게 오류를 반환해야 하기 때문에, Golang 엔지니어들은 이를 처리할 때까지 계층별로 오류를 반환해야 합니다. 그 결과, Golang 코드는 필연적으로 다음과 같은 반복적인 오류 보일러플레이트를 갖게 됩니다.

만약 err != nil { 
    반환 0, 음
}

이러한 오류 처리 방식은 함수가 오류를 발생시켜 캐치 블록으로 처리하는 표준 프로그래밍 언어 관습과는 다릅니다. 주류 관례는 엔지니어가 오류를 계층별로 전달할 필요를 요구하지 않습니다. 엔지니어는 원하는 계층에서 오류를 자유롭게 포착하고 처리할 수 있어, 덜 장황하고 덜 반복적인 코드베이스를 만듭니다.

Golang의 오류 처리 철학은 엔지니어가 모든 오류를 어디서 어떻게 처리할지 결정하도록 요구합니다. 이로 인해 프로그래머의 감독으로 인한 시스템 실패 확률이 줄어듭니다. 또한, 오류를 함수의 반환 값의 일부로 취급하는 것은 엔지니어가 순수 함수를 작성하는 데 유리하며, 이는 불순수 함수보다 예측 가능성이 높습니다. 오류를 발생시키면 프로그램이 정상적인 흐름에서 벗어나 부작용이 발생하여 코드의 예측 가능성이 떨어집니다. 오류를 반환하면 이 문제를 해결하고 순수 함수 작성이 더 쉬워져 엔지니어가 쉽게 추론할 수 있는 코드를 만듭니다.

위의 오류 보일러플레이트를 복사해 붙여넣을수록 점점 더 궁금해졌습니다: 함수형 프로그래밍의 원칙을 위반하지 않고 오류를 더 우아하게 처리할 방법이 있을까요? 그러다 어느 날, 이 질문에 대한 답이 매우 간단하다는 것을 깨달았습니다: Scala 같은 함수형 프로그래밍 언어에서 오류 처리에 흔히 사용되는 데이터 타입인 'Either'를 사용하는 것입니다.

도대체 'Either'가 무엇인가요? 'Either'는 서로 서로가 아닌 합집합이며, 그 인스턴스는 'Left' 또는 'Right'일 수 있습니다. 관례적으로 오류 정보는 'Left'에, 유효한 값은 'Right'에 저장합니다. 즉, 'Either'가 'Left'일 때는 오류 메시지를 포함하는 오류입니다. 'Either'가 'Right'일 때는 유효한 값을 포함합니다.

위의 'Hello' 함수를 Scala에서 'Either'로 다시 작성해 봅시다.

def hello (이름: String): Either[String, String] = { 
    만약 (name.isEmpty()) { 
        return Left("empty name")
    }
    돌아다녀요(s"안, $name. 환영해요!")
}

Scala에서는 return 키워드를 생략하고 Hello 함수를 다음과 같이 다시 작성할 수도 있습니다: 
def hello (이름: String): Either[String, String] = { 
만약 (name.isEmpty()) { 
왼쪽("빈 이름")
} 그렇지 않으면 { 
맞아요(안녕하세요, $name. 환영합니다!)  
//     }
// }

첫 번째 눈에 띄는 차이점은 Scala 'hello' 함수는 단 하나의 반환 값만 가지고 있고, Golang 함수는 두 개라는 점입니다. 둘째, Scala 'hello' 함수는 두 가지 유형 중 하나를 반환할 수 있습니다: 'Left'(오류 메시지)와 'Right'(유효한 환영 메시지). Golang 'Hello' 함수를 호출할 때는 같은 확실성을 갖지 못합니다. 정의에 따르면 반환된 값의 조합은 '(nil, error)'와 '(message, nil)' 두 가지뿐입니다. 하지만 기술적으로는 타입 힌트에서 추론할 수 있는 두 가지 조합이 더 있습니다: '(nil, nil)'와 '(message, error)'입니다. Golang의 'Hello' 함수를 호출할 때는 네 가지 가능한 조합 모두를 추론해야 하는데, 이는 Scala의 'Either'보다 더 복잡합니다.

함수의 반환 값을 단순화하는 것 외에도, 'Either'는 오류 처리를 간소화합니다. 앞서 예시를 확장해 보겠습니다. 'name'이라는 매개변수를 받아 'Hello' 함수를 호출하는 새로운 함수 'VIPHello'를 만들고자 합니다. 'Hello' 함수가 오류를 반환하면, 'VIPHello'가 그 오류를 반환합니다. 'Hello' 함수가 유효한 메시지를 반환하면, 'VIPHello'가 메시지에 추가 텍스트를 추가하고 결과된 메시지를 반환합니다. Golang에서는 다음과 같이 구현할 것입니다.

패키지 메인

임포트 ( 
    "오류" 
    "FMT" 
    "로그" 
)

헬로는 이름이 밝혀진 사람에게 인사를 건넨다. 
func Hello(이름 문자열) (*string, 오류) { 
    if name == "" { 
        반환 없음, 오류가 발생. New("empty name")
    }

    메시지 := fmt. 스프린트프("안녕하세요, %v. 환영합니다!", 이름)
    반환 및 메시지, 없음
}

func VIPHello(이름 문자열) (*string, error) { 
    helloMessage, 음 := Hello(name)
    만약 err != nil { 
        반환 0, 음
    }

    메시지 := fmt. Sprintf("%v 진심 어린 감사를 받아주세요", *helloMessage)
    반환 및 메시지, 없음
}

보시다시피, 앞서 언급한 오류 보일러플레이트를 사용해야 합니다. 오류 함수를 호출할 때마다 오류 보일러플레이트를 복사해서 붙여넣어야 한다면 코드가 얼마나 장황해질지 상상해 보세요. 반면, 'Either'와 그 'map' 메서드 덕분에 Scala에서 같은 함수를 더 우아하게 구현할 수 있습니다.

def hello (이름: String): Either[String, String] = { 
    만약 (name.isEmpty()) { 
        return Left("empty name")
    }
    돌아다녀요(s"안, $name. 환영해요!")
}

def vipHello (이름: String): Either[String, String] = { 
    응답 hello(name).map((오른쪽) => s"$right 진심 어린 감사를 받아주세요")
}

간단하지만 스칼라 프로그래머들에게 읽기 쉬운 한 줄 문장입니다! 어떻게 작동하는지 설명하겠습니다. 'hello' 함수의 반환 값이 'Left'라면, 'map' 메서드는 단순히 반환합니다. Ether의 'map' 메서드의 마법은 'hello' 함수의 반환 값이 'Right'일 때 발생합니다. 'Right'에서 유효한 메시지를 추출하고, 전달된 함수에 따라 추가 메시지를 덧붙인 뒤, 반환하기 전에 'Right'로 감싸 줍니다. 이 함수는 Golang 함수가 하는 모든 것을 더 깔끔하고 우아하게 구현합니다. 더 중요한 것은, 오류 보일러플레이트를 어디서나 복사해서 붙여넣을 필요가 없다는 점입니다!

Golang의 오류 처리 스타일에 대한 제 불만을 들어주셔서 감사합니다! 저는 여전히 Golang의 단순함과 뛰어난 성능 같은 다른 많은 측면에 매료되어 있지만, 지긋지긋한 오류 보일러플레이트를 계속 복사해서 붙여넣거나, 유효한 값과 오류를 따로 반환하는 함수를 작성해야 하는 상황은 아쉽습니다.

이 글이 재능 있는 골랑 프로그래머들이 골랑의 오류 처리 방식을 재고하고, 'Anyther'를 골랑에 도입할 가능성을 탐색하는 데 영감을 주길 바랍니다.

Related Articles