TestForge | Aidevops | 📊 Plogger ✍️ Blog 📚 Docs
plogger

AI DevOps Korea

AI 서비스 개발, 운영, 성능개선을 하나의 루프로 연결합니다

aidevops.kr에서 LLMOps, RAG, AI Agent, 관측성, 평가, 비용-성능 최적화를 실전 운영 관점으로 정리합니다.

Go 언어 기초 실전 가이드

· 수정 4월 21일
Go 언어 기초 실전 가이드 다이어그램
이 글에서 다루는 핵심 흐름, 아키텍처 구조, 주요 판단 포인트를 한눈에 이해할 수 있도록 정리한 그림입니다.
Go는 종종 배우기 쉬운 작은 언어로 소개됩니다. 맞는 말이지만 핵심은 아닙니다. Go의 진짜 가치는 기능이 적다는 데 있지 않고, 제어 흐름과 에러 처리, 동시성을 코드에서 더 잘 보이게 만든다는 데 있습니다.

그래서 Go가 인프라와 서비스 코드에서 강한 이유도 “문법이 간단해서”보다 복잡성을 추상화 뒤에 숨기기 어렵게 만들기 때문에 가깝습니다.

Go를 많이 오해하는 지점

입문 단계에서는 흔히 “문법이 단순하니 시스템도 단순해질 것”이라고 생각합니다. 하지만 실무는 다릅니다.

  • 문법은 작아도 운영 규율은 여전히 필요합니다
  • 동시성은 쉽게 시작되지만 소유권 규칙은 따로 정해야 합니다
  • 인터페이스는 가볍지만 남용하면 경계가 흐려집니다
  • 명시적 에러 반환도 팀이 의미 있게 다루지 않으면 금방 잡음이 됩니다

즉 Go는 문법 암기보다, 언어가 어떤 엔지니어링 습관을 강제하는지 이해하는 편이 훨씬 중요합니다.

Go가 실무에서 다르게 느껴지는 이유

Go 코드는 Java, Kotlin, TypeScript와 비교하면 유난히 평평하고 직접적으로 보일 때가 많습니다. 그 이유는 언어의 편향이 분명하기 때문입니다.

  • 제어 흐름을 드러내기
  • 의존성 표면을 좁게 유지하기
  • 에러를 일반 실행 경로의 일부로 다루기
  • 동시성을 쉽게 허용하되 눈에 띄게 만들기

이 조합 때문에 Go는 읽기 쉬운 언어이면서도, 설계를 자동으로 안전하게 만들어 주지는 않습니다. 오히려 단순한 언어일수록 더 많은 판단을 요구합니다.

goroutine은 시작은 쉽지만 운영은 어렵다

goroutine은 Go의 가장 큰 장점 중 하나지만, 동시에 코드베이스를 가장 빠르게 흐리게 만드는 지점이기도 합니다.

goroutine이 가벼우니 쉽게 쓰게 되지만, 가볍다는 것이 공짜를 뜻하지는 않습니다.

실무에서 중요한 질문은 아래입니다.

  • 이 goroutine의 생명주기는 누가 책임지는가
  • cancellation은 어떻게 전파되는가
  • channel receiver가 읽지 않으면 무슨 일이 생기는가
  • 무한 background 작업을 무엇이 막는가

Go는 동시성을 쉽게 시작하게 해주지만, 동시성 아키텍처를 대신 설계해 주지는 않습니다.

channel은 마법 파이프가 아니라 조정 도구다

channel은 Go를 대표하는 기능처럼 보이지만, 실제로는 여러 조정 수단 중 하나로 읽는 편이 맞습니다.

channel이 잘 맞는 경우:

  • 소유권 이전이 명확해야 할 때
  • 작은 경계를 통해 작업을 직렬화해야 할 때
  • producer/consumer 관계가 설계 핵심일 때

channel이 과한 경우:

  • mutex가 상태 경계를 더 직접적으로 표현할 때
  • 통신 그래프가 너무 복잡해질 때
  • 버퍼링 규칙이 불분명할 때
  • 그냥 Go답게 보이려고 쓸 때

좋은 Go 코드는 channel을 많이 쓰는 코드가 아니라, 필요한 곳에만 쓰는 코드입니다.

interface는 작을수록 강하다

Go의 interface는 호출자가 실제로 필요한 능력만 표현할 때 가장 좋습니다.

그래서 건강한 Go 코드베이스에서는 interface를 구현체 쪽이 아니라 소비자 쪽 근처에서 정의하는 경우가 많습니다. 객체 계층을 만들기 위한 것이 아니라, 최소한의 의존성 계약을 표현하기 위한 도구이기 때문입니다.

자주 실패하는 패턴은 이렇습니다.

  • 메서드가 많은 넓은 인터페이스
  • 구현체가 하나도 없는데 미리 추상화부터 만드는 경우
  • 패키지 전체를 추상화로 포장하는 경우

Go는 좁은 경계를 잘 보상합니다. 추상화 자체를 보상하지는 않습니다.

에러 처리는 문법이 아니라 설계 표면이다

Go의 명시적 에러 반환은 장황하다고 자주 말해지지만, 실무에서는 오히려 강력한 설계 도구입니다.

에러 처리가 강제하는 질문은 다음과 같습니다.

  • 어떤 실패가 정상적인 운영 흐름인가
  • retry와 fallback은 어디에 둘 것인가
  • 어떤 문맥을 붙여야 추적이 쉬운가
  • 어떤 에러가 패키지 경계를 넘어가야 하는가

에러 처리가 시끄러워지는 이유는 대개 언어가 아니라, 팀이 raw error를 구조 없이 반환하거나 래핑 규칙을 일관되게 잡지 못했기 때문입니다.

예시: context 소유권이 보이는 동시성 코드

func FetchAll(ctx context.Context, urls []string, client *http.Client) ([]string, error) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    type result struct {
        body string
        err  error
    }

    ch := make(chan result, len(urls))

    for _, url := range urls {
        url := url
        go func() {
            req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
            if err != nil {
                ch <- result{err: err}
                return
            }

            resp, err := client.Do(req)
            if err != nil {
                ch <- result{err: err}
                return
            }
            defer resp.Body.Close()

            body, err := io.ReadAll(resp.Body)
            if err != nil {
                ch <- result{err: err}
                return
            }

            ch <- result{body: string(body)}
        }()
    }

    out := make([]string, 0, len(urls))
    for range urls {
        res := <-ch
        if res.err != nil {
            cancel()
            return nil, res.err
        }
        out = append(out, res.body)
    }

    return out, nil
}

이 예시가 좋은 이유는 goroutine을 썼기 때문이 아니라, 아래가 모두 보이기 때문입니다.

  • cancellation 경계
  • 결과 수집 방식
  • 실패 시 전체 작업 중단 규칙

좋은 Go 패턴은 대개 이런 식으로 소유권과 종료 조건이 드러나는 코드입니다.

자주 나오는 안티패턴

  • cancellation 없이 goroutine만 띄우는 경우
  • mutex가 더 나은데도 channel을 쓰는 경우
  • 넓은 interface를 너무 일찍 만드는 경우
  • 문맥 없는 에러를 던지는 경우
  • 문법이 단순하니 설계 리스크도 낮을 것이라 착각하는 경우

Go 코드베이스가 어려워지는 이유는 언어가 복잡해서가 아니라, 언어가 단순하니 설계도 저절로 단순할 것이라고 믿기 때문입니다.

리뷰 체크리스트

  • 동시성이 제한되고 취소 가능한 구조인가
  • interface가 실제 소비자 요구만 표현하는가
  • channel이 조정 도구로만 쓰이고 있는가
  • 에러에 운영 추적에 필요한 문맥이 붙어 있는가
  • 코드가 짧아서 단순한가, 아니면 설계가 명확해서 단순한가

마무리 판단

Go는 제어 흐름이 잘 보이고, 경계를 좁게 잡고, 동시성을 다루기 쉬운 언어를 원하는 팀에 특히 유용합니다. 하지만 Go의 단순함은 안전장치가 아니라 명료함을 요구하는 압력에 가깝습니다. 그 점을 이해할수록 Go 코드는 오래 버팁니다.

Continue Reading

다음으로 읽기 좋은 글

다음 탐색

이 주제를 시스템 관점으로 더 이어서 보기