Go 오류 처리 기술 [닫기]


108

방금 Go를 시작했습니다. 내 코드에 다음이 많이 포함되기 시작했습니다.

   if err != nil {
      //handle err
   }

아니면 이거

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

Go에서 오류를 확인하고 처리하기위한 좋은 관용구 / 전략 / 모범 사례가 있습니까?

명확히하기 위해 편집 : 나는 Go 팀이 더 나은 것을 생각해 낼 것을 제안하거나 제안하지 않습니다. 내가 제대로하고 있는지, 아니면 커뮤니티가 생각 해낸 기술을 놓쳤는 지 묻고 있습니다. 모두 감사합니다.


4
아니, 실제로는 없습니다. 그것은 자주 논의되는 주제이며 현명한 주제입니다. 많은 진화 제안도있었습니다. 팀의 대답은 잘 작성된 코드에서는 문제가되지 않아야한다는 것입니다.
Denys Séguret 2013-06-06


이 관련 질문은이 질문과 실제로 동일하지 않습니다. 답변이 너무 구체적입니다.
Denys Séguret 2013-06-06

이 성가심에 대한 근거도 있습니다. 프로그램을 빨리 작성하는 것이 더 어렵지만 단순히 오류를 다시 발생시켜 버그를 생성하기 어렵게 만듭니다.
Denys Séguret 2013-06-06

당신은 앤드류 Gerrand 브래드 피츠 패트릭이 더 많거나 적은 비슷한 방식으로 이동에 HTTP / 2 클라이언트의 시작을 쓰기 찾을 수 있습니다 youtube.com/watch?v=yG-UaBJXZ80
Supreet 세티에게

답변:


61

귀하의 코드는 관용적이며 제 생각에는 사용 가능한 모범 사례입니다. 어떤 사람들은 확실히 동의하지 않을 것이지만 나는 이것이 Golang의 표준 라이브러리 전체에서 볼 수있는 스타일이라고 주장합니다 . 즉, Go 작성자는 이러한 방식으로 오류 처리를 작성합니다.


12
"Go 작성자는 이런 방식으로 오류 처리를 작성합니다." 나에게 좋은 소리.
gmoore

"어떤 사람들은 확실히 동의하지 않을 것입니다." : 누군가 이것이 오늘날 사용 가능한 모범 사례가 아니라고 말할 것입니다. 일부는 구문 설탕이나 기타 변경 사항을 요청하지만 오늘은 심각한 코더가 그렇지 않으면 오류를 확인하지 않을 것이라고 생각합니다.
Denys Séguret 2013-06-06

@dystroy : 좋아, 어떤 사람들은 " it sux " 라고 말하고 다른 사람들 은 "오류는 반환 값에서 처리됩니다. 70 년대 스타일"이라고합니다. 등 ;-)에
ZZZZ

2
@jnml 이런 방식으로 오류를 처리하는 것은 언어 설계의 문제이며 매우 논란이 많은 주제입니다. 다행스럽게도 선택할 수있는 언어는 수십 가지가 있습니다.
fuz

4
나를 죽이는 것은 모든 단일 함수 호출에 동일한 패턴이 사용되는 방법입니다. 이것은 어떤 곳에서 코드를 다소 시끄럽게 만들고 정보를 잃지 않고 코드를 단순화하기 위해 구문 설탕을 비명을 지르는 것입니다. 이것은 본질적으로 간결함의 정의입니다 (내가 장황함보다 우수한 속성이라고 생각하지만 논쟁의 여지가 있습니다 포인트). 원칙은 건전하지만 구문은 IMHO가 많이 필요합니다. 그러나 불평은 금지되어 있으므로 지금은 내 쿨 에이드를 마실 것입니다 ;-)
Thomas

30

이 질문을받은 지 6 개월 후 Rob Pike는 Errors are Values 라는 제목의 블로그 게시물을 작성했습니다 .

거기에서 그는 OP가 제시하는 방식으로 프로그래밍 할 필요가 없다고 주장하고 표준 라이브러리에서 다른 패턴을 사용하는 여러 위치를 언급합니다.

물론 오류 값과 관련된 일반적인 진술은 그것이 nil인지 여부를 테스트하는 것입니다.하지만 오류 값으로 할 수있는 다른 작업은 무수히 많으며 이러한 다른 항목을 적용하면 프로그램을 더 좋게 만들 수있어 상용구를 많이 제거 할 수 있습니다. 모든 오류가 rote if 문으로 확인되면 발생합니다.

...

언어를 사용하여 오류 처리를 단순화하십시오.

하지만 기억하세요 : 무엇을하든 항상 오류를 확인하세요!

좋은 읽기입니다.


감사! 확인하겠습니다.
gmoore

이 기사는 굉장합니다. 기본적으로 실패한 상태에있을 수있는 객체를 소개하며, 만약 그렇다면 그것으로하는 모든 것을 무시하고 실패한 상태에 머물 것입니다. 나에게 거의 모나드처럼 들린다.
Waterlink

@Waterlink 귀하의 진술은 의미가 없습니다. 상태를 가진 모든 것은 조금 곁눈질하면 거의 모나드입니다. en.wikipedia.org/wiki/Null_Object_pattern과 비교하는 것이 더 유용하다고 생각합니다.
user7610

@ user7610, 피드백 주셔서 감사합니다. 동의 만 할 수 있습니다.
Waterlink

2
Pike : "하지만 기억하세요 : 무엇을하든 항상 오류를 확인하세요!" -이것은 80 년대입니다. 오류는 어디에서나 발생할 수 있으며 프로그래머에게 부담을주지 않고 Pete를 위해 예외를 채택합니다.
Slawomir

22

나는 그들이 모두 관용적 코드라는 jnml의 대답에 동의하고 다음을 추가합니다.

첫 번째 예 :

if err != nil {
      //handle err
}

하나 이상의 반환 값을 다룰 때 더 관용적입니다. 예를 들면 :

val, err := someFunc()
if err != nil {
      //handle err
}
//do stuff with val

두 번째 예는 값만 다룰 때 좋은 속기 err입니다. 이는 함수가를 반환하는 경우에만 적용 error되거나 error. 예를 들어, 이것은 때때로 쓰여진 바이트 수 (때로는 불필요한 정보) 를 반환하는 ReaderWriter함수 와 함께 사용됩니다 .interror

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

두 번째 형식은 if 초기화 문 사용이라고 합니다 .

따라서 모범 사례와 관련하여 내가 아는 한 ( "errors" 패키지를 사용하여 필요할 때 새로운 오류를 생성 하는 것을 제외 하고) Go!의 오류에 대해 알아야 할 거의 모든 것을 다루었습니다.

편집 : 예외 없이는 정말 살 수 없다는 것을 알게된다면 defer, panic&recover .


4

Go 함수 대기열을 통해 간소화 된 오류 처리 및 파이핑을위한 라이브러리를 만들었습니다.

여기에서 찾을 수 있습니다 : https://github.com/go-on/queue

간결하고 장황한 구문 변형이 있습니다. 다음은 짧은 구문의 예입니다.

import "github.com/go-on/queue/q"

func SaveUser(w http.ResponseWriter, rq *http.Request) {
    u := &User{}
    err := q.Q(                      
        ioutil.ReadAll, rq.Body,  // read json (returns json and error)
    )(
        // q.V pipes the json from the previous function call
        json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
    )(
        u.Validate,               // validate the user (returns error)
    )(
        u.Save,                   // save the user (returns error)
    )(
        ok, w,                    // send the "ok" message (returns no error)
    ).Run()

    if err != nil {
       switch err {
         case *json.SyntaxError:
           ...
       }
    }
}

리플렉션을 사용하기 때문에 약간의 성능 오버 헤드가 있음을 유의하십시오.

또한 이것은 관용적 인 go 코드가 아니므로 자신의 프로젝트에서 사용하거나 팀이 사용에 동의하는 경우 사용하고 싶을 것입니다.


3
있다고해서 좋은 생각은 아닙니다. 이것은 아마도 읽기 어려운 점을 제외하고 는 책임사슬 패턴 처럼 보입니다 (의견). 나는 그것이 "특이한 바둑"이 아니라고 제안 할 것이다. 그래도 흥미 롭다.
Steven Soroka 2014

2

golang 및 다른 언어에서 오류를 처리하는 "전략"은 호출 스택에서 해당 오류를 처리 할 수있을만큼 충분히 높을 때까지 호출 스택에 오류를 지속적으로 전파하는 것입니다. 이 오류를 너무 일찍 처리하려고하면 코드가 반복 될 수 있습니다. 너무 늦게 처리하면 코드에서 무언가가 깨질 것입니다. Golang은 주어진 위치에서 오류를 처리하는지 또는 전파 하는지를 매우 명확하게 해주기 때문에이 프로세스를 매우 쉽게 만듭니다.

오류를 무시하려는 경우 간단한 _는이 사실을 매우 명확하게 나타냅니다. 처리하는 경우 if 문에서 확인할 수 있으므로 처리중인 오류의 정확한 경우가 명확합니다.

사람들이 위에서 말했듯이 오류는 실제로 정상적인 값입니다. 이것은 그것을 그렇게 취급합니다.


2

Go Gods는 Go 2에서 오류 처리를위한 "초안 설계"를 발표했습니다. 오류 관용구를 변경하는 것을 목표로합니다.

개요디자인

그들은 사용자의 피드백을 원합니다!

피드백 위키

간단히 말하면 다음과 같습니다.

func f() error {
   handle err { fmt.Println(err); return err }
   check mayFail()
   check canFail()
}

업데이트 : 초안 디자인은 많은 비판을 받았기 때문에 최종 솔루션을위한 가능성 메뉴와 함께 Go 2 오류 처리를 고려할 요구 사항 초안을 작성했습니다 .


1

업계에서 대부분은 golang 문서 오류 처리 및 Go에 언급 된 표준 규칙을 따릅니다 . 또한 프로젝트 문서 생성에도 도움이됩니다.


이것은 본질적으로 링크 전용 답변입니다. 링크가 유효하지 않은 경우에도 여전히 사용할 수 있도록 답변에 일부 콘텐츠를 추가하는 것이 좋습니다.
Neo

소중한 의견 감사합니다.
pschilakanti

0

다음은 Go에 대한 오류 처리를 줄이는 방법입니다. 샘플은 HTTP URL 매개 변수를 가져올 때입니다.

( https://blog.golang.org/errors-are-values 에서 파생 된 디자인 패턴 )

type HTTPAdapter struct {
    Error *common.AppError
}

func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
    requestUUID := uuid.Parse(mux.Vars(r)[param])
    if requestUUID == nil { 
        adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
            possibleError, http.StatusBadRequest)
    }
    return requestUUID
}

여러 가능한 매개 변수에 대해 호출하는 것은 다음과 같습니다.

    adapter := &httphelper.HTTPAdapter{}
    viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
    messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
    if adapter.Error != nil {
        return nil, adapter.Error
    }

이것은 은색 총알이 아닙니다. 단점은 여러 오류가있는 경우 마지막 오류 만 얻을 수 있다는 것입니다.

그러나이 경우 상대적으로 반복적이고 위험이 적으므로 가능한 마지막 오류를 얻을 수 있습니다.


-1

당신은 할 수 있습니다 (오류가 여기에 조심해야 값이기 때문에) 유사한 오류에 대한 오류 처리 코드를 정리하고 오류를 처리하는 전달 된 오류를 호출하는 함수를 작성합니다. 매번 "if err! = nil {}"을 쓸 필요가 없습니다. 다시 말하지만, 이것은 코드를 정리하는 결과를 가져 오지만 나는 그것이 일을하는 관용적 인 방법이라고 생각하지 않습니다.

다시 말하지만, 당신이 있기 때문에 할 수 있습니다 당신은 의미하지 않는다 해야한다 .


-1

goerr 는 함수로 오류를 처리 할 수 ​​있습니다.

package main

import "github.com/goerr/goerr"
import "fmt"

func ok(err error) {
    if err != nil {
        goerr.Return(err)
        // returns the error from do_somethingN() to main()
        // sequence() is terminated
    }
}

func sequence() error {
    ok(do_something1())
    ok(do_something2())
    ok(do_something3())

    return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
    fmt.Println("DOING 3")
    return nil
}

func main() {
    err_do_something := goerr.OR1(sequence)

    // handle errors

    fmt.Println(err_do_something)
}

Ick. 이와 같은 오류 처리 로직을 복잡하게 / 숨기는 것은 IMO가 좋은 생각이 아닙니다. 결과 코드 (goerr에 의한 소스 사전 처리가 필요함)는 관용적 인 Go 코드보다 읽기 / 추론하기가 더 어렵습니다.
Dave C

-4

정확한 오류 제어를 원한다면 이것이 해결책이 아닐 수도 있지만, 저에게는 대부분의 경우 모든 오류가 쇼 스토퍼입니다.

그래서 대신 함수를 사용합니다.

func Err(err error) {
    if err!=nil {
        fmt.Println("Oops", err)
        os.Exit(1)
    }
}

fi, err := os.Open("mmm.txt")
Err(err)

이러한 메시지는 stderr대신 로 이동해야 stdout하므로 log.Fatal(err)또는을 사용하십시오 log.Fatalln("some message:", err). main드문 경우지만 전체 프로그램을 종료하기 위해 (즉, 함수 / 메소드에서 오류를 반환하고, 중단하지 마십시오) 이러한 결정을 내리는 것 외에는 거의 아무것도하지 않기 때문에 이것은 여러분이 원하는 일입니다. 명시 적으로 수행하는 것이 더 깨끗하고 낫습니다. if err := someFunc(); err != nil { log.Fatal(err) }) 그것이 무엇을하는지에 대해 불분명 한 "도우미"함수를 통해서가 아니라 ( "Err"라는 이름은 좋지 않으며, 프로그램을 종료 할 수 있다는 표시를주지 않습니다).
Dave C

새로운 것을 배웠다! @DaveC 감사합니다
Gon
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.