Go 함수는 문법보다 무엇을 반환하고, 실패를 어디서 드러내는가를 먼저 보면 읽기가 빨라집니다.
숏컷 코드
func ping() {}
func add(a int, b int) int {
return a + b
}
func readUser(id string) (User, error) {
// ...
}문법
함수 형태는 매개변수와 반환값을 같이 본다
func ping() {}
func greet(name string) string {
return "hello, " + name
}매개변수가 없으면 "호출만 하면 되는 동작"에 가깝고, 반환값이 있으면 호출자가 결과를 이어서 써야 하는 계약이 생깁니다.
Go는 다중 반환으로 결과와 에러를 같이 돌려준다
Go 함수에서 가장 자주 다시 찾는 표면은 (value, error) 형태입니다.
func parsePort(raw string) (int, error) {
// ...
}이 패턴 덕분에 예외 대신 반환값으로 실패를 드러내는 흐름이 기본이 됩니다.
port, err := parsePort("8080")
if err != nil {
return err
}named return은 가능하지만 기본값은 아니다
func split(sum int) (x int, y int) {
x = sum * 4 / 9
y = sum - x
return
}named return은 짧은 함수나 defer와 함께 반환값을 조정할 때 유용할 수 있습니다. 다만 길어진 함수에서는 오히려 흐름을 흐리기 쉬워서, 보통은 명시적 return value, err가 더 읽기 쉽습니다.
defer
defer는 함수가 끝날 때 실행된다
defer는 "지금 바로"가 아니라, 현재 함수가 반환되기 직전 실행됩니다.
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
// f 사용
return nil
}이 패턴 덕분에 자원 해제 코드를 여러 return 경로마다 반복하지 않아도 됩니다.
인자는 defer를 선언하는 시점에 평가된다
func demo() {
i := 1
defer fmt.Println(i)
i = 2
}위 코드는 2가 아니라 1을 출력합니다. defer에 넘긴 인자가 선언 시점에 평가되기 때문입니다.
선택 기준
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 결과만 반환 | func f(...) T |
| 결과와 실패를 함께 반환 | func f(...) (T, error) |
| 자원 정리 | defer |
| 반환값 이름이 흐름을 더 선명하게 할 때 | named return 제한적으로 사용 |
| 함수가 길고 반환 지점이 많음 | 명시적 return 우선 |
주의할 점
Go에서 defer는 자원 정리에 매우 자주 쓰이지만, 루프 안에서 무심코 쌓거나 반환값을 흐리게 만드는 named return과 섞으면 읽기가 급격히 나빠질 수 있습니다. 먼저 함수가 무엇을 돌려주고, 실패를 어떤 순서로 검사하는지를 선명하게 두고 그 위에 defer를 올리는 편이 안전합니다.
참고 링크
3 sources