1P by GN⁺ | ★ favorite | 댓글 1개
  • Go의 if err!= nil 반복은 수년간 사용자 설문에서 큰 불만으로 남아 있었지만, Go 팀은 오류 처리 문법 변경을 당분간 추진하지 않기로 함
  • 2018년 check/handle, 2019년 try, 2024년 ? 제안은 모두 충분한 합의를 얻지 못했고, 특히 try숨겨진 제어 흐름 때문에 강한 반발을 받음
  • Go 제안 절차상 일반적 합의가 없으면 제안은 보통 거절되며, Google의 Go 팀 고참 구성원들 사이에서도 현재 최선의 방향에 대한 만장일치가 없음
  • 현상 유지 쪽은 Go가 이미 작동하는 오류 처리 방식을 갖고 있고, 새 문법이 코드 스타일·디버깅·문서·도구·기존 코드에 큰 비용을 만든다고 봄
  • Go 팀은 오류 처리 구문을 주된 대상으로 하는 열린 제안과 신규 제안을 추가 조사 없이 닫고, 더 명확한 문제 인식이 생기기 전까지 다른 개선 기회에 집중함

if err != nil이 만든 오래된 불만

  • Go에서 가장 오래 지속된 불만 중 하나는 오류 처리 코드의 장황함
  • 대표 패턴은 다음 형태임
x, err := call()
if err != nil {
    // handle err
}
  • API 호출이 많고 오류를 단순히 반환하는 프로그램에서는 if err != nil이 코드의 나머지 부분을 압도할 수 있음
  • 예시 함수 printSum에서는 함수 본문 10줄 중 실제 작업처럼 보이는 줄이 호출·출력·반환을 포함해 4줄이고, 나머지 6줄은 잡음처럼 보임
  • Go 연례 사용자 설문에서 오류 처리는 수년간 최상위 불만이었고, 한동안 제네릭 부재가 이를 앞섰지만 Go가 제네릭을 지원한 뒤 다시 오류 처리가 최상위 불만으로 돌아옴

세 차례의 주요 문법 제안

  • Go 팀의 첫 명시적 시도는 2018년 Go 2 노력의 일부로 Russ Cox가 문제를 공식 정리하면서 시작됨
  • Marcel van Lohuizen의 초안 설계checkhandle 메커니즘을 기반으로 했고, 다른 언어 접근과 대안 분석도 포함함
func printSum(a, b string) error {
    handle err { return err }
    x := check strconv.Atoi(a)
    y := check strconv.Atoi(b)
    fmt.Println("result:", x + y)
    return nil
}
  • check/handle 접근은 너무 복잡하다고 판단됐고, 2019년에는 더 단순한 try 제안이 나옴
    • check 유사 키워드는 try 내장 함수가 됨
    • handle 부분은 빠짐
    • 기존 오류 처리 코드를 try 방식으로 바꾸는 도구 tryhard가 만들어짐
    • 관련 GitHub 이슈는 900개에 가까운 댓글로 격렬하게 논의됨
func printSum(a, b string) error {
    // use a defer statement to augment errors before returning
    x := try(strconv.Atoi(a))
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x + y)
    return nil
}
  • try는 오류가 나면 둘러싼 함수에서 반환하는 방식으로 제어 흐름에 영향을 줬고, 깊게 중첩된 표현식 안에서도 반환이 일어날 수 있어 많은 사용자에게 받아들이기 어려웠음
  • 당시에는 새 키워드를 도입하는 편이 나았을 수 있으며, 현재는 go.mod 파일과 파일별 지시어로 언어 버전을 세밀하게 제어할 수 있음
  • Jimmy Frasche의 최근 제안은 원래 check/handle 설계로 돌아가면서 일부 단점을 다루는 방향을 따름

try 이후의 절차 반성과 ? 제안

  • try 제안 이후 Russ Cox는 “Thinking about the Go Proposal Process” 연재를 통해 제안 절차를 되돌아봄
  • “Go Proposal Process: Large Changes”try가 구현 일정이 붙은 제안이 아니라 두 번째 초안 설계였어야 했다고 평가함
  • 이후 몇 년간 Go 팀은 오류 처리 문법 변경을 추진하지 않았고, 커뮤니티에서는 유사하거나 흥미롭거나 이해하기 어렵거나 실현 불가능한 제안들이 계속 들어옴
  • Ian Lance Taylor는 오류 처리 개선 제안 현황을 정리하는 umbrella issue를 만들었고, 관련 피드백과 논의를 모으는 Go Wiki도 생김
  • Sean K. H. Liao의 “go error handling proposals”는 수년간의 많은 오류 처리 제안을 추적함
  • 불만이 계속되자 Ian Lance Taylor는 2024년에 ?를 사용해 오류 처리 보일러플레이트를 줄이는 제안을 발표함
    • Rust의 ? 연산자에서 빌린 표기였음
    • 소규모 비공식 사용자 연구에서 대부분의 참가자는 ?를 사용한 Go 코드의 의미를 올바르게 추측함
    • 일반 Go 코드를 새 문법으로 변환하는 도구와 컴파일러 프로토타입도 만들어짐
func printSum(a, b string) error {
    x := strconv.Atoi(a) ?
    y := strconv.Atoi(b) ?
    fmt.Println("result:", x + y)
    return nil
}
  • 이 제안도 빠르게 많은 댓글과 선호 기반의 세부 수정 제안으로 넘쳐났고, Ian은 제안을 닫은 뒤 내용을 discussion으로 옮김
  • 약간 수정된 버전은 조금 더 긍정적인 반응을 얻었지만 폭넓은 지지는 얻지 못함

왜 지금은 멈추려 하는가

  • Go 팀은 예측 가능한 미래에는 오류 처리의 구문 문제를 해결하려는 시도를 멈춰야 한다고 판단함
  • 제안 절차는 이 결정을 뒷받침함
    • 제안 절차의 목표는 적시에 결과에 대한 일반적 합의에 도달하는 것임
    • 이슈 트래커 논의에서 일반적 합의를 찾지 못하면 보통 제안은 거절됨
    • 합의도 다음 단계도 찾지 못하면 Go architects가 논의를 검토하고 내부 합의를 시도함
  • 어떤 오류 처리 제안도 합의에 가까운 지지를 얻지 못했고, 모두 거절됨
  • Google의 Go 팀 고참 구성원들조차 현재 최선의 진행 방향에 대해 만장일치가 아니며, 강한 합의 없이는 합리적으로 진행할 수 없음

현상 유지와 변화의 논거

  • 현상 유지 쪽에는 Go의 성숙도와 생태계 비용이라는 현실적인 이유가 있음
    • Go가 초기에 오류 처리 전용 문법 설탕을 도입했다면 지금은 논쟁이 적었겠지만, Go는 15년이 지났고 이미 작동하는 오류 처리 방식을 갖고 있음
    • 지금 완벽한 해결책을 찾더라도 변화 지지자 대신 현상 유지 선호자가 불만을 갖는 상황으로 바뀔 수 있음
    • 제네릭은 사용자가 반드시 직접 쓰지 않아도 되지만, 오류 처리 새 문법은 쓰지 않으면 코드가 비관용적으로 보일 수 있어 사실상 대부분이 사용해야 함
    • 같은 일을 하는 여러 방법을 제공하지 않는다는 Go 설계 규칙과도 새 문법 추가는 충돌할 수 있음
  • := 짧은 변수 선언의 재선언 기능은 오류 처리 때문에 생긴 문제를 해결하기 위해 도입됐음
    • 재선언이 없었다면 연속 오류 검사마다 서로 다른 err 이름이나 별도 변수 선언이 필요했음
    • 당시 오류 처리에 더 나은 문법 지원이 있었다면 재선언 규칙과 관련 복잡성도 필요 없었을 수 있음
  • 오류를 제대로 보강해 처리하면 단순 반복의 비중은 줄어듦
    • 사용자 설문에는 오류에 스택 추적이 없다는 반복 의견이 있음
    • 보조 함수로 보강된 오류를 만들어 반환하는 방식이 가능함
    • fmt.Errorf("invalid integer: %q", a)처럼 입력값 정보를 붙이면 보일러플레이트의 상대적 비중이 작아짐
  • 표준 라이브러리 기능도 오류 처리 보일러플레이트를 줄일 수 있음
    • Rob Pike의 “Errors are values”와 같은 방향임
    • 일부 경우 cmp.Or로 여러 오류를 한 번에 다룰 수 있음
  • 쓰기, 읽기, 디버깅은 서로 다른 활동임
    • 반복 오류 검사를 작성하는 일은 지루하지만, IDE와 LLM 보조 코드 완성이 기본 오류 검사를 쉽게 생성할 수 있음
    • 읽을 때 장황함이 더 두드러지며, IDE가 오류 처리 코드를 숨기는 토글을 제공할 수 있음
    • 디버깅할 때는 이미 별도 if 문이 있으면 println 추가나 중단점 설정이 쉬움
    • check, try, ? 뒤에 오류 처리가 숨으면 보통 if 문으로 되돌려야 할 수 있고, 이 과정이 디버깅을 복잡하게 하거나 미묘한 버그를 만들 수 있음
  • 언어 변경에는 설계·구현뿐 아니라 기존 코드 변경, 문서 업데이트, 도구 조정 비용이 뒤따름
    • Go 팀은 상대적으로 작고, 처리해야 할 다른 우선순위도 많음
    • 우선순위와 팀 규모는 바뀔 수 있음
  • Google Cloud Next 2025에서 Go 팀이 만난 일부 Go 사용자들은 더 나은 오류 처리를 위해 언어를 바꾸지 말아야 한다고 강하게 말함
    • 이들은 다른 언어에서 막 넘어올 때 Go의 전용 오류 처리 문법 부재가 가장 눈에 띄지만, 더 관용적인 Go 코드를 쓰게 되면 덜 중요해진다고 말함
    • 이 표본은 대표성을 갖기에는 충분히 크지 않지만, GitHub에서 보는 사람들과 다른 집단일 수 있음
  • 변화 지지 논거도 여전히 유효함
    • 더 나은 오류 처리 지원의 부재는 사용자 설문의 최상위 불만으로 남아 있음
    • 문자 수를 줄이는 데만 집중하는 접근은 잘못된 방향일 수 있음
    • 기본 오류 처리를 키워드로 눈에 잘 띄게 만들면서 err != nil 보일러플레이트를 제거하면 코드 리뷰에서 오류 처리 여부를 더 쉽게 확인할 수 있음
    • 문제의 핵심이 단순 문법 장황함인지, API와 개발자·최종 사용자에게 의미 있는 오류를 구성하는 좋은 오류 처리의 장황함인지는 아직 충분히 알지 못함

Go 팀의 결정

  • 지금까지 오류 처리를 다루려는 어떤 시도도 충분한 추진력을 얻지 못함
  • Go 팀은 공유된 문제 이해가 부족하고, 애초에 문제가 있는지에 대해서도 모두가 동의하지 않는다고 판단함
  • 예측 가능한 미래에는 오류 처리를 위한 문법적 언어 변경을 추진하지 않음
  • 오류 처리 문법을 주된 대상으로 하는 열린 제안과 앞으로 들어오는 제안은 추가 조사 없이 닫을 예정임
  • 커뮤니티의 탐색과 토론은 오류 처리 문법 변경으로 이어지지는 않았지만, Go 언어와 절차의 여러 개선으로 이어졌음

댓글과 토론

Hacker News 의견들
  • Go 팀이 “이렇게 했으면 됐다”는 식의 제안을 가볍게 던지고 싶다면, 먼저 글에 링크된 위키 페이지 https://go.dev/wiki/Go2ErrorHandlingFeedback와 GitHub 이슈 검색 https://github.com/golang/go/issues?q=+is%3Aissue+label%3Aerror-handling를 봤으면 함
    지금 제안하려는 건 거의 틀림없이 처음 나온 게 아니고, 상당수는 이미 깊게 검토됐을 가능성이 큼
    Go 팀의 이런 솔직한 접근을 좋게 보고, 여전히 업무에서 매일 Go를 즐겁게 쓰고 있음

    • 피드백의 기반이 된 설계 초안에는 C++, Rust, Swift가 나오지만, 링크한 방대한 피드백 문서에서는 Haskell/Scala/OCaml에서 쓰는 do 표기법, for-comprehension, monadic-let 같은 접근을 찾지 못했음
      가장 댓글이 많은 GitHub 이슈 몇 페이지에서도 비슷한 건 보이지 않았고, Go 팀이 언어 설계의 마법사라서 여기 사람들이 가볍게 던지는 해법은 당연히 검토했을 거라고 보기엔 무리임
      Go 팀은 Java와 같은 실수, 즉 정적 타입이면서 매개변수 다형성이 없던 문제를 저질렀고, 이 오류 처리 문제의 뿌리도 거기에 있는데 손을 들고 고치지 않는 모습으로 보임
    • 정말 똑똑하고 경험 많은 사람들이 그 페이지를 쓰고 수년간 논의했는데도, Haskell식 해법인 Maybe/Either 모나드와 bind 연산자를 쓰는 do 표기법이 어디에도 안 보이는 게 신기함
      거창하고 위협적으로 들릴 수 있지만, 오류를 처리 가능한 곳까지 전파하면서도 잊히지 않게 만드는 우아하고 함수적으로 순수한 방식임
      Haskell 코드를 쓰는 사람들에게는 너무 깊이 자리 잡은 방식이라, Go 커뮤니티에 이를 알고 좋아하는 사람이 없었다는 게 이해되지 않음
      페이지 자체와 링크는 고맙지만, 자기 언어를 그렇게 신경 쓰는 사람들이 이렇게 잘 정립된 해법을 건너뛴 건 혼란스럽게 느껴짐
    • 이미 어딘가 답이 있겠지만, 왜 Go에서만 특히 어려운 문제인지 궁금함
      거의 모든 언어가 각자 더 나은 방식을 갖고 있는데, 단지 결정을 못 하거나 모두를 만족시키지 못해서인지, 아니면 Go 언어 자체에 다른 언어의 해법이 안 맞는 구체적인 이유가 있는지 알고 싶음
    • Go 비판에서 자주 보이는 패턴은, 상대적으로 아마추어인 사람들이 Go를 만드는 사람들이 프로그래밍 언어를 자기들보다 모른다고 가정하는 것임
      실제로는 거의 모든 경우 그들이 훨씬 많이 알고 있음
      아마추어는 기능을 가장 많이 욱여넣은 언어가 최고라고 순진하게 생각하고, 특히 자기 취향의 기능이 들어가면 더 그렇다고 봄
      칼 만들기에 막 입문한 사람이 일본식 셰프 나이프를 보고 부족하다고 느낀 뒤, 손가락 홈, 비밀 수납공간, 라이터, Bluetooth 스피커가 달린 3D 프린트 손잡이를 붙이면 더 낫겠다고 생각하는 것과 비슷함
    • 이제는 수정하려면 승인을 받아야 하는데도 Wiki라고 부르는 건 이상함
  • 체크박스 목록을 만들고, 각 항목을 논의해서 채운 뒤, 치명적인 의미론 오류나 건전성 구멍이 발견되지 않는 한 다시 빼지 않으면 됨
    다 채워지면 구현하고, .await로 쓸지 /await로 쓸지 .await!()로 쓸지 떠들던 사람들은 다시 사라짐
    Rust는 이런 식으로 굴러가며, 어떤 이슈는 10년 넘게 지연되기도 하지만 결국 항목들이 채워지고 최신 nightly에서 안정화됨
    Go가 모두가 즉시 부딪히는 단일 문제를, 완성도 높은 제안이 여러 개 있는데도 그중 하나를 못 골라 bikeshedding이 멈추길 기다리느라 해결 못 한다면 그 절차는 희극임

    • “완성된 완벽한 제안이 여러 개 있다” 같은 건 존재하지 않음
    • 그게 바로 Rust가 위원회식 설계로 인해 읽기 못생기고 문법이 일관되지 않은 언어라는 평판을 얻는 방식임
    • 프로그래밍 언어는 설계된 시스템이라 전체적으로 말이 되어야 함
      체크박스 요구사항을 만족하면 그냥 추가되는 기능 모음이 아님
    • 25년 뒤 알림 걸고 싶음
      Rust는 C++처럼 엉망이 됐을까? Go는 출시 당시처럼 시간을 타지 않는 언어로 남았을까?
    • “Go가 모두가 즉시 겪는 단일 문제를 해결하지 못한다”는 건 이상함
      설문에서는 오류 처리를 언급한 비율이 13%였고, 지금 방식 그대로를 선호하는 사람들도 있음
      https://go.dev/blog/survey2024-h1-results
  • 예전에 내부 함수가 오류를 반환하길 기대하는 특이한 Go 함수를 작성한 적이 있음
    그래서 내부 함수가 오류를 반환하지 않으면 바깥 함수가 오류를 반환하고 다른 처리를 해야 했고, 내부 함수가 오류를 반환하면 nil을 반환해야 했음
    요약하면 if err != nil { ... }이 아니라 if err == nil { // return an error }를 써야 했는데, 습관적으로 전자를 써버렸고 디버깅에 꽤 오래 걸렸음
    if err != nil에 너무 둔감해져 있어서 뇌가 그 구문이 들어가면 안 된다는 가능성을 아예 고려하지 않았기 때문임
    그래서 흔한 표현에는 문법적 설탕이 필요하다고 봄. 극히 흔한 if err != nil과 드문 if err == nil 사이의 차이가 더 두드러졌다면 실제로 도움이 됐을 것임

    • if err == nil을 쓸 때마다 // inverted라고 주석을 달아 눈에 띄게 만듦
      언어 차원에서 처리되면 좋겠지만, 최소한 더 잘 보이게 하는 방법으로 공유함
    • 물론 if fruit != "Apple" { ... }도 똑같은 상황을 만들 수 있음
      이를 개선할 일반적인 해법이 있는지 궁금하고, 이걸 오류 처리만의 문제로 보는 건 다소 빗나간 듯함
      오류에는 특별하거나 고유한 점이 없고, 다른 것과 같은 상태일 뿐임
    • 이건 오히려 문법 변경에 반대하는 근거가 됨
      흔한 if err == nil { return ... } 패턴이 생기면, 이번에는 그게 코드에 널려 있게 되기 때문임
      현재 해법은 괜찮고, 주로 Go를 막 시작했거나 초급인 사람들이 싫어하는 듯함
      주변에서는 명시적이고 분명하며 읽기 쉬운 “장황한” 오류 처리를 좋아함
    • 악마의 변호를 하자면, IDE와 글꼴이 Go 문법 모드에서만 if err != nil을 단일하고 작은 합자 기호처럼 강조하거나 배경으로 흐리게 렌더링할 수 있음
      그러면 if err == nil처럼 정확히 그 문자열과 다른 형태는 오히려 눈에 띄게 됨
    • 좋은 지점임. 에디터에서 if err … {처럼 접힌 표기법으로 해결할 수도 있어 보임
  • Go의 명시적 오류 처리가 마음에 듦
    함수는 항상 성공하거나, 성공 또는 실패할 수 있음. 항상 성공하는 함수는 단순하고, 실패할 수 있는 함수가 실패하면 바깥 코드는 실패 상태로 진행할 수 없으니 처리해야 함
    여기서 언어들이 갈라짐. 많은 언어는 예외를 던져 누군가 명시적으로 잡을 때까지 올리고 일종의 스택 추적을 제공함
    Go에서는 코드를 쓸 때 항상 선택해야 하는 선택지가 있다는 점이 좋음: 오류를 무시하고 진행하기(foo, _ := doSomething()), 의미 있는 정보 없이 조기 반환하기(return nil, err), 유용한 맥락을 붙여 조기 반환하기, 받은 오류를 해석해 분기하기
    예를 들어 데이터베이스에서 수정할 행을 찾지 못하면 서비스 계층이 not found 오류를 반환하고 API에서는 404가 되거나, 멱등 삭제 함수가 not found를 성공으로 해석할 수 있음
    Go 2나 다른 언어라면, nil 가능 튜플 대신 Rust/Swift식 Result 타입과, 항상 error를 직접 쓰는 대신 더 잘 타입화되고 열거 가능한 오류 타입이 있으면 좋겠음
    다만 Go 1의 관용적인 튜플 반환 위에 Result를 추가하면 같은 일을 하는 방법이 여러 개 생겨 혼란과 분열을 만들기 때문에, Go 2나 새 언어에 더 어울림

    • 오류 처리 정책은 호출자에게 위임되어야 한다는 게 내 경험임
      스택의 낮은 계층은 대체로 무엇을 해야 할지 모르므로 오류를 처리하면 안 됨
      오류를 처리한다는 정책은 결국 오류를 감싸서 스택 위로 다시 반환하는 정책이 되기 쉽고, 상당한 잡일이 됨
    • “함수가 실패하면 그 실패를 처리해야 한다”가 바로 Go가 실패하는 지점임
      Go는 오류를 완전히 무시할 수 있게 하고, 그 결과 충돌로 이어질 수 있음
      견고한 소프트웨어를 만들기 위한 요구사항을 정확히 짚고도 Go의 오류 처리 방식을 좋아한다는 게 좀 이해되지 않음
    • borgo[1] 문법이 그대로 Go 2 언어가 되면 좋겠음. 꿈은 꿀 수 있음
      [1]: https://borgo-lang.github.io/ | https://github.com/borgo-lang/borgo
    • 이 속도라면 Go2는 절대 출시되지 않을 아이디어 실험실처럼 보임
    • 무엇과 비교해서 좋다는 건지 묻고 싶음
      모든 함수형 언어, Rust 같은 많은 현대 언어, 심지어 checked exception을 가진 Java도 이걸 제공함
      제네릭이 있는 언어라면 Go식 “오류 처리”를 대체로 복제할 수 있고, 아마 더 나은 코드가 나올 것임
      답이 JavaScript나 Python이라면, 그건 흔한 비교 패턴이긴 함
  • Go에는 이게 맞는 결정임. 처음 Go를 접했을 때는 오류 처리가 싫었지만, 지금은 정말 좋아하게 됨
    바뀐 계기는 두 가지임. https://go.dev/blog/errors-are-values 글을 읽고 “오류는 값”이라는 관점을 제대로 받아들였고, 그 기반으로 어느 정도 인기 있는 패키지 [https://github.com/stytchauth/sqx](https://github.com/stytchauth/sqx도 만들었음
    또 정말 말이 안 되는 잘못된 상태에는 panic(err)를 조금씩 쓰는 데 익숙해졌음
    부모 코드가 처리할 감각도 없는 엉뚱한 상태를 억지로 모두 처리하게 할 이유는 없고, 잘 배치된 panic 한두 개로 코드베이스의 오류 검사 수백 개를 제거할 수 있음
    예를 들어 ctx 안에 기본 로거가 있는지 같은 문제를 생각해볼 수 있음

    • 안타까운 얘기임. 든 근거 둘 다 Go 오류 처리가 얼마나 형편없는지와는 관련이 없고, 오류 처리를 더 좋게 만든다고 나빠질 것도 없음
      오히려 개선될 가능성이 큼
    • PHP조차 오류 수준과 호출 지점에서 오류를 억제하는 @ 연산자 덕분에 더 나은 오류 처리를 갖고 있음
      bash에도 -e가 있음
    • 나도 Go 방식이 좋음. 무슨 일이 벌어지는지 더 확실히 알 수 있다면 더 많은 코드 줄 수를 감수하겠음
      예전 C#을 처음 쓸 때는 try/catch/finally 흐름, using, 중첩, catch에서 오류가 나면 어떻게 되는지, finally에서 나면 어떻게 되는지 이해하는 게 똑똑하다고 생각했음
      이제는 그런 걸 생각하지 않는 편이 좋음
    • Rust식 합 타입 오류도 값임
  • 이 글이 Go 오류 처리의 주된 문제가 문법이 너무 장황한 것이라고 말하는 방식이 마음에 들지 않음. 그건 별로 신경 쓰지 않음
    더 중요한 건 오류가 조용히 버려지거나 실수로 무시될 수 있고, 함수 호출 결과가 값이 아니라 쉽게 저장하거나 전달할 수 없으며, errors.Is가 필요하고 “중첩” 오류 전체가 타입 시스템과 잘 맞지 않는 이상한 런타임 장치라는 점임
    오류에 대한 switch도 어렵고, 표준 라이브러리는 sentinel 값을 쓰며, 제네릭과의 상호작용이 좋지 않아 errgroup 같은 패키지가 필요해짐
    빠뜨린 게 또 있을까?

    • Go로 전문적으로 일하는 시간의 90%는 각 오류 반환 분기를 문장 커버리지로 덮기 위해 테스트 케이스를 억지로 만드는 일
      예외가 있는 언어였다면 아무도 그렇게 하지 않았을 것임
    • 이 글 어디에서도 “주된 문제가 문법이 너무 장황하다”는 주장을 하지 않는다고 봄
      예측 가능한 미래에는 오류 처리 문법을 바꾸려는 시도를 더 하지 않기로 했고, 그 덕분에 오류든 다른 주제든 다른 문제를 살펴볼 주의가 생김
    • Go가 어떤 형태의 제네릭을 지원하기까지도 엄청나게 오래 걸렸다는 걸 기억해야 함
      Go의 진화는 빙하처럼 느리고, 많은 사람에게 그건 버그가 아니라 기능임
    • 100% 동의함. 둘 다 Googler인데 Go 팀에게 또 실망하게 되어 너무 아쉬움
    • 1번에는 동의하지만, errcheck 같은 개발 도구로 어느 정도 완화할 수 있음: https://github.com/kisielk/errcheck?tab=readme-ov-file
  • “오류에 스택 추적이 없다는 설문 의견은 보조 함수가 보강된 오류를 만들어 반환하게 해서 해결할 수 있다”는 식의 설명이 우스움
    if err != nil { return fmt.Errorf("invalid integer: %q", a) }처럼 스택 추적을 수동으로 제공하는 것을 “오류 처리”라고 부르는 게 재미있음
    Go 팀의 정의대로라면 예외는 오류를 자동으로 처리해주는 셈임. 물론 C++을 제외한 언어들에서임

    • 사람들이 화면 가득한 스택 추적을 보고 명확하고 유용하다고 말하는 게 재미있음
      그럴 수도 있지만 정말 그 모든 게 필요한가? 로그 비용은 어떻고?
      프레임워크와 런타임 소음을 잘라내는 한 줄짜리 감싼 오류가 훨씬 낫다고 봄
      잘 감싸면 검색하기도 매우 쉽고, 보통은 스택 추적보다 더 효과적으로 추적할 수 있음
      Go를 10년 넘게 풀타임으로 쓰면서 런타임 함수나 호출 스택의 장황한 잡음이 필요했던 적은 한 번도 없음
  • Elixir 개발자 관점에서는 이건 미친 일처럼 보임
    Erlang/Elixir에서는 함수가 보통 {:ok, result} 또는 {:error, description_or_struct} 튜플을 반환하면서 해결됨
    여기에 Elixir의 with 문을 쓰면 오류 처리를 아래쪽에 모을 수 있어 훨씬 읽기 좋아짐
    Go도 with 절에 해당하는 걸 추가해서 오류가 nil인 동안 함수들을 계속 진행하고, 아래쪽에 오류 처리 절을 두면 됨

    • 이용 가능한 증거만 보면 Go가 with 문을 채택할 가능성은 전혀 없어 보임
      Go는 제네릭, 오류 처리, 패키지 관리처럼 기본적이고 명백히 가치 있는 구조를 커뮤니티 합의 부족 때문에 아주 오래 미루는 점이 흥미로움
      제네릭은 오픈소스 공개 이후 13년이 걸렸고, 16년이 지난 지금도 오류 처리는 없으며, 패키지 관리는 약 9년이 걸렸음
      숙고에도 가치가 있지만 출시에도 가치가 있음. GitHub 댓글 900개를 쓰는 사람들도 결국 Go를 계속 쓸 테고, 언어에 뭔가 들어가는 편이 계속 미루는 것보다 나았을 가능성이 큼
    • Go의 다중 반환 자체가 내 관점에서는 이상함
      반환 타입이 여러 개인 함수는 변수에 대입하는 것 말고는 할 수 있는 게 없음
    • Haskell 사용자와 Rust 팬들은 합 타입을 자기들 것처럼 여기고, 사람들이 그들의 댓글과 글을 읽고 믿은 뒤 무서운 Hindley-Milner 토끼굴로 빠지고 싶지 않아서 합 타입을 꺼리게 됨
      하지만 Erlang과 Elixir에서는 아무런 짐 없이 완전히 관용적인 방식임
      사실 ML 계열보다 훨씬 강력하기도 한데, 그쪽의 합 타입은 열려 있기 때문임
  • 이 논의를 자세히 따라가진 않았지만, 왜 Rust식 방식을 그냥 채택하지 않는지 모르겠음
    Go에 제네릭이 생긴 뒤 내가 바로 추가하게 되는 방식도 그거임
    링크된 글에서는 “Rust에는 handle에 해당하는 것이 없고, ? 연산자의 편의성은 적절한 처리를 생략하게 만들 가능성이 있다”는 설명만 보임
    하지만 편리하다는 게 오류를 무시한다는 뜻인지는 이해되지 않음
    Go 방식의 문제 절반은 결과에 대해 아무것도 강제하지 않고 오류 검사도 최소한만 강제한다는 점임
    x, err := strconv.Atoi("123"); fmt.Println("result:", x)declared and not used: err가 나지만, 두 번째 변환 뒤 err를 확인하지 않아도 y의 기본값 0 때문에 문제를 모른 채 잘 실행될 수 있음
    if err != nil { }처럼 비워둬도 컴파일되고 실행되며, 뭔가 잘못됐다는 걸 알 수 없음
    반환값을 Result로 만들면 결정을 내려야 한다는 점이 강제됨. 누가 !를 막 쓰거나 ?로 편하게 올리면서 오류 케이스를 처리하지 않는다고 해도, 그럼 panic도 금지할 건가?

    • Go에는 합 타입이 없어서 Result를 가질 수 없음
      그리고 모든 타입에 지정된 영값이 있어야 한다는 이상한 집착 때문에 합 타입도 추가하지 못함
    • ?를 쓰기 편하면 아무도 더 이상 오류를 감싸지 않을 거라는 뜻으로 이해함
      매우 의심스러운 논리임
      애초에 ?가 오류 감싸기를 장려하도록 설계하면 됨
    • Rust식을 Go에 넣을 때 정확히 무엇이 동등한 형태인지 분명하지 않기 때문임
      예를 들어 Rust의 From에 해당하는 것은 Go에서 어떤 모습이어야 할까?
    • ?는 가시성이 낮고, 제어 흐름 분기를 하나의 문장이나 표현식 안에 숨김
      Go가 삼항 연산자를 없애고 각 분기가 별도 줄에 있는 if 문을 택한 이유 중 하나도 그것임
      중단점 걸기도 쉽지 않고, 오류를 보강하거나 처리하기보다 그대로 위로 올리는 쪽을 선호하게 만듦
    • :=가 단일 문장의 선언 및 대입이라고 생각했는데, 예시의 5번째 줄에서 err를 다시 선언하고 새 err가 기존 err를 가리는 것 아닌가?
      그렇다면 새 err 변수가 사용되지 않았으니 declared and not used: err로 실패해야 할 것 같음
      아니면 변수가 이미 있으면 :=가 그냥 일반 대입처럼 동작하는 건가?
  • “오류에 스택 추적이 없다는 점은 보조 함수가 보강된 오류를 만들어 반환하게 해서 해결할 수 있다”는 말은 현실을 너무 낙관함
    스택 추적이 있는 언어는 이걸 공짜로 주지만, Go에서는 매번 구현해야 함
    본인은 항상 세부 정보를 붙이는 규율 있는 개발자일 수 있어도, 팀원 모두가 같은 규율을 갖지는 않음
    스택 추적의 가장 좋은 점은 오류까지의 호출 경로를 준다는 것임
    여러 곳에서 호출되는 메서드에서 오류가 발생하면, 스택 추적으로 어느 경로를 탔는지 즉시 알 수 있음
    여러 해 동안 sysadmin/SRE 비슷한 일을 하며 많은 문제를 해결했는데, 스택 추적이 있을 때 쉬운 문제는 원인이 뻔해서 1~2분이면 끝났음
    Go에서는 누군가 오류를 보강하지 않거나 같은 오류 메시지를 재사용하면 쉬운 문제도 추리 작업이 되어 더 오래 걸림