1. What Is Go?
Go는 어떤 언어이고 무엇으로 개발하나
- Haram Lee
- 2026-10-03
- studies / Topics / Go
Python과 Scala를 쓰던 입장에서 Go를 처음 볼 때 가장 먼저 궁금한 것은 “왜 이렇게 기능이 적은가"다. 클래스도, 상속도, 예외도 없다. 이 글은 그 이유와, 코드를 읽고 짜기 전에 알아야 하는 모듈, 패키지, 도구를 정리한다.
1.1 Why Go Has So Few Features
Go는 2009년 Google에서 만들었다. 여러 사람이 대규모 서버 코드를 오랫동안 고쳐 나가는 상황을 염두에 둔 언어다. 설계의 중심은 “읽기 쉬운 코드가 쓰기 쉬운 코드보다 중요하다"는 판단이다.
- 기능이 적으면 코드 모양이 비슷해진다. 같은 일을 하는 방법이 하나둘로 줄어들어서 남이 쓴 코드도 금방 읽힌다. Scala처럼 같은 로직을 여러 스타일로 쓸 수 있는 언어와 정반대다.
- 컴파일이 빠르고 결과물은 단일 바이너리다. 의존성을 모두 정적으로 묶은 실행 파일 하나가 나와서 컨테이너 이미지가 작고 배포가 단순하다. JVM이 없으니 시작도 빠르다.
- 동시성이 언어에 들어 있다.
go키워드로 가벼운 실행 단위(goroutine)를 띄우고 channel로 통신한다. 요청마다 동시에 많은 일을 하는 네트워크 서버에 잘 맞는다. 이 부분은 4편에서 다룬다. - 가비지 컬렉션이 있다. C처럼 메모리를 직접 해제하지 않는다. 대신 GC 일시 정지를 짧게 유지하는 쪽으로 설계됐다.
이벤트 수집 서버나 알림 서버처럼 “요청을 많이 받아 빠르게 처리하고 다른 시스템으로 넘기는” 서버가 Go로 많이 만들어지는 이유가 여기에 있다.
1.2 Modules and Packages
Go 코드의 단위는 세 단계다.
- 모듈(module): 버전이 붙는 배포 단위다. 레포 루트의
go.mod파일이 모듈 경로(예:github.com/<user>/<repo>)와 Go 버전, 의존성을 적는다.go.sum에는 의존성의 체크섬을 기록한다. - 패키지(package): 디렉터리 하나가 패키지 하나다. 같은 디렉터리의
.go파일은 모두 첫 줄에 같은package 이름을 쓴다. 실행 파일은package main에func main()이 있어야 한다. - 파일: 파일 단위의 구분은 거의 의미가 없다. 같은 패키지 안이면 다른 파일의 함수와 변수도 바로 쓸 수 있다.
공개 범위는 이름의 첫 글자로 정한다. 대문자로 시작하면 패키지 밖에서 쓸 수 있고(Handle), 소문자면 패키지 안에서만 쓴다(handle). public, private 같은 키워드는 없다.
package collector
// 패키지 밖에서 collector.NewServer로 쓸 수 있다
func NewServer() *Server { ... }
// 패키지 안에서만 쓴다
func validate(e Event) error { ... }internal/ 디렉터리 아래 패키지는 그 모듈 안에서만 import할 수 있다. 서버 레포에서는 흔히 cmd/<바이너리 이름>/main.go에 진입점을 두고 실제 로직은 internal/ 아래에 둔다.
1.3 The go Command
| 명령 | 하는 일 |
|---|---|
go mod init <모듈 경로> | 새 모듈을 만든다 |
go get <패키지>@<버전> | 의존성을 추가하거나 버전을 바꾼다 |
go mod tidy | 쓰지 않는 의존성을 지우고 빠진 것을 채운다 |
go run ./cmd/server | 컴파일하고 바로 실행한다 |
go build ./... | 모든 패키지를 컴파일한다 |
go test ./... | 모든 패키지의 테스트를 돌린다 |
go vet ./... | 흔한 실수를 정적으로 찾는다 |
gofmt -w . | 코드를 표준 형식으로 맞춘다 |
gofmt가 있어서 Go에는 들여쓰기나 괄호 위치를 두고 하는 논쟁이 없다. 에디터가 저장할 때마다 돌리도록 설정해 두는 것이 보통이다. 에디터 지원은 공식 언어 서버 gopls가 맡는다.
테스트는 xxx_test.go 파일에 func TestXxx(t *testing.T)를 쓰면 된다. 별도 프레임워크 없이 표준 라이브러리 testing으로 충분하다. Go 코드에서는 입력과 기대값을 표로 늘어놓고 반복문으로 돌리는 “table-driven test"를 자주 볼 수 있다.
1.4 Coming from Scala or Python
| 처음 보면 낯선 것 | Go에서는 |
|---|---|
| 클래스와 상속 | 없다. struct와 메서드, 인터페이스로 대신한다 |
예외(try/catch) | 없다. 함수가 error를 마지막 반환값으로 돌려준다 |
| 제네릭 | 1.18부터 있지만 컬렉션 유틸리티 정도에만 쓴다 |
map, filter 같은 고차 함수 | 표준 라이브러리에 거의 없다. 대부분 for 반복문으로 쓴다 |
| 쓰지 않는 import나 변수 | 컴파일 에러다 |
null | 포인터, 슬라이스, 맵, 인터페이스의 0값이 nil이다 |
이 가운데 마지막 줄이 중요하다. Go의 모든 타입에는 “0값"이 있다. 선언만 하고 값을 넣지 않으면 숫자는 0, 문자열은 "", 불리언은 false, 포인터와 슬라이스는 nil이다. 그래서 “초기화하지 않은 변수"라는 개념이 없고, 0값이 그대로 쓸모 있게 설계된 타입이 많다. 예를 들어 sync.Mutex는 선언만 하면 바로 쓸 수 있다.
1.5 Resources
- A Tour of Go: 브라우저에서 바로 실행하며 문법을 훑는다. 첫날 여기서 시작한다.
- Effective Go: 관용적인 Go 코드를 쓰는 법이다. Tour 다음에 읽는다.
- Go by Example: 주제별 짧은 예제다. 필요할 때 찾아본다.
- Go Code Review Comments: 리뷰에서 자주 지적되는 것들이다. 남의 Go 코드를 읽기 전에 한 번 본다.