Go 로 서버 만들기 · 에러를 값으로 다룬다 · 이론
if err != nil 이 지겨운 이유
한 줄 요약
Go 는 에러를 값으로 돌려준다. 그래서 에러 처리를 건너뛸 수 없고, 대신 코드가 길어진다. 그 길이는 비용이 아니라 명시성의 가격이다.
왜 이게 필요했나
예외가 있는 언어에서는 어느 줄이 던질 수 있는지 코드만 봐서는 모른다. 그래서 "여기서 죽을 수 있나" 를 확인하려면 호출 그래프를 따라가야 한다.
Go 는 그걸 시그니처에 적는다.
func ReadConfig(path string) (*Config, error)이 함수가 실패할 수 있다는 것이 타입에 있다. 호출하는 쪽은 그것을 무시할 수 없다 — 무시하려면 _ 라고 적어야 하고, 그건 의도가 드러난다.
감싸기
에러를 그대로 올려보내면 "어디서 났는지" 를 잃는다. %w 로 감싼다.
if err != nil { return fmt.Errorf("설정 읽기(%s): %w", path, err)}%w 는 원본을 안에 품는다. 그래서 위쪽에서 errors.Is / errors.As 로 원인을 물어볼 수 있다.
if errors.Is(err, os.ErrNotExist) { ... }var perr *os.PathErrorif errors.As(err, &perr) { fmt.Println(perr.Path) }%v 로 감싸면 문자열만 남고 이 질문이 불가능해진다. 감쌀 때는 항상 %w.
언제 panic 하나
거의 안 한다. panic 은 "프로그램이 계속 돌 이유가 없다" 일 때만이다 — 초기화 실패, 불변식 위반. 요청 처리 중의 panic 은 그 요청만 죽여야 하므로 HTTP 서버는 기본적으로 잡아 준다.
흔한 착각
"에러를 로그로 남기고 또 반환한다" — 그러면 같은 사고가 로그에 여러 번 찍힌다. 처리하거나 올려보내거나 하나만 한다. 로그는 최종적으로 처리하는 곳에서 한 번.
"nil 에러면 결과가 유효하다" — Go 관례상 맞지만 강제는 아니다. 라이브러리에 따라 둘 다 유효하거나 둘 다 nil 일 수 있다. 문서를 본다.
실무에서 진짜 중요한 것
에러 메시지는 위에서 아래로 읽었을 때 경로가 되도록 쓴다.
사용자 조회(id=42): 설정 읽기(/etc/app.yaml): open /etc/app.yaml: no such file각 층이 자기가 아는 맥락만 한 조각씩 붙이면 이렇게 된다. 대문자로 시작하지 않고 마침표로 끝내지 않는 것이 관례다 — 다른 메시지 안에 끼워 넣어지기 때문이다.