Devin.KR

Go 시작하기 - 모듈과 첫 프로그램

개발자KR 조회 0

이 장에서 배우는 것

이 책은 동네 공공 자전거 대여소의 대여·반납·정산 프로그램을 장마다 조금씩 키워 가는 방식으로 진행한다. 첫 장에서는 프로그램 기능을 거의 만들지 않는다. 대신 이후 모든 장에서 반복해서 쓸 작업 틀을 갖춘다. 폴더 하나를 Go 프로젝트로 만들고, 한 파일짜리 프로그램을 실행하고, 실행 파일로 빌드하고, 코드 모양과 흔한 실수를 도구로 점검하는 순서다.

프로그래밍 경험이 없어도 따라올 수 있도록 용어는 나올 때마다 풀어 쓴다. 명령은 터미널(명령어를 글자로 입력하는 창)에서 입력한다. macOS와 Linux를 기준으로 하고 Go는 1.25 이상을 쓴다.

  • 설치한 Go의 버전을 확인하고, 폴더 하나를 모듈(module)로 만든다.
  • go run과 go build의 차이를 알고 상황에 맞게 고른다.
  • 패키지(package)와 import가 무엇인지, main 패키지가 왜 특별한지 설명한다.
  • gofmt와 go vet으로 코드 모양과 의심스러운 코드를 점검한다.

문제 상황

동네 자전거 대여소를 관리하는 담당자가 있다고 하자. 지금은 종이 장부에 대여와 반납을 적는다. 이 담당자는 먼저 화면에 대여소 이름과 대여 가능한 자전거 수, 비어 있는 거치대 수를 보여 주는 아주 작은 프로그램을 만들려고 한다. 이 프로그램이 앞으로 대여 기록, 요금 정산, 웹 서비스까지 자라날 것이다.

처음 프로그래밍 도구를 마주하면 코드를 어디에 두는지, 어떤 명령으로 실행하는지, 다른 사람 컴퓨터에서도 같은 방식으로 돌아가는지 같은 질문이 먼저 나온다. 이 질문에 답이 없으면 코드를 한 줄 쓰기도 전에 막힌다. 그리고 팀에서 코드를 주고받을 때는 들여쓰기 취향이나 사소한 실수 때문에 리뷰 시간을 쓰게 된다.

Go는 이 질문들에 답하는 도구를 go라는 명령 하나에 모아 두었다. 프로젝트 만들기, 실행, 빌드, 형식 정리, 점검이 모두 같은 명령에서 시작한다. 이 장은 그 명령들을 한 번씩 사용해 보는 장이다.

도구 확인과 모듈 만들기

설치 확인

Go를 설치했다면 터미널에서 다음 명령으로 버전을 확인한다. 설치 방법은 Go 공식 설치 안내에 있다.

go version

go version go1.25.0 darwin/arm64처럼 버전, 운영체제, 프로세서 종류가 한 줄로 나온다. 뒤쪽 값은 컴퓨터마다 다르므로 이 책에서는 출력이 고정되는 명령만 결과를 싣는다. 버전 숫자가 1.25 이상이면 이 책의 예제를 그대로 실행할 수 있다.

모듈이란

Go에서 프로젝트의 단위는 모듈이다. 모듈은 함께 버전을 관리하는 코드 묶음이고, 폴더 최상단에 있는 go.mod 파일이 그 시작점을 표시한다. 이 파일에는 모듈의 이름과 필요한 Go 버전이 적힌다. 폴더를 만들고 안에서 다음 명령을 실행한다.

mkdir bikeshare
cd bikeshare
go mod init bikeshare

go mod init 뒤에 붙인 bikeshare가 모듈 이름이다. 코드를 인터넷에 공개할 계획이라면 저장소 주소를 이름으로 쓰지만, 이 책의 예제는 내려받아 실행하는 용도이므로 짧은 이름이면 충분하다. 명령을 실행하면 go.mod가 생기고 내용은 대략 다음과 같다.

module bikeshare

go 1.25.0

두 번째 줄의 버전 숫자는 설치한 Go에 따라 달라질 수 있다. 이 파일은 직접 고칠 일이 거의 없다. 다만 나중에 다른 장에서 코드를 여러 패키지로 나눌 때 이 모듈 이름이 이름표 역할을 하므로, 지금 정한 이름을 기억해 두자. 모듈에 대한 자세한 규칙은 Go 모듈 레퍼런스에서 확인할 수 있다.

같은 main.go에서 go run은 실행만 하고 go build는 실행 파일을 남긴다.

패키지와 import

모듈이 프로젝트 전체라면 패키지는 그 안에서 코드를 나누는 작은 단위다. 같은 폴더에 있는 .go 파일들은 하나의 패키지에 속하고, 모든 Go 파일은 첫 줄에 package 이름을 적어 자신이 속한 패키지를 밝힌다.

패키지 이름 중 main만 특별하다. main 패키지 안에 main이라는 이름의 함수가 있으면, 그 패키지는 실행 가능한 프로그램이 된다. 프로그램을 실행하면 Go는 이 main 함수부터 실행한다. 함수는 이름을 붙인 명령의 묶음이고, 자세한 내용은 뒤에서 다룬다. 지금은 func main() { ... }의 중괄호 안이 프로그램이 시작되는 자리라고만 알면 된다.

다른 패키지의 기능을 쓰려면 import로 가져온다. 이 책에서는 Go에 기본으로 들어 있는 표준 라이브러리만 쓴다. 이 장에서 쓰는 패키지는 두 가지다.

  • fmt: 화면에 글자를 출력하거나 값을 글자 모양으로 바꾸는 함수를 담고 있다. 이름은 format(형식)의 줄임말이다.
  • strings: 글자 묶음, 곧 문자열을 다루는 함수를 담고 있다.

가져온 패키지의 기능은 패키지이름.함수이름 꼴로 부른다. 예를 들어 fmt.Println은 fmt 패키지의 Println 함수다. 함수 이름이 대문자로 시작하는 것도 규칙이다. Go에서는 대문자로 시작하는 이름만 다른 패키지에서 쓸 수 있고, 소문자로 시작하는 이름은 그 패키지 안에서만 쓴다. 표준 라이브러리 함수의 사용법은 터미널에서 go doc fmt.Println으로 볼 수 있고, 전체 목록은 pkg.go.dev의 표준 라이브러리 문서에 있다.

Go 파일은 패키지 선언, import, main 함수 순서로 구성되고 실행은 main 함수에서 시작한다.

실행하고 빌드하고 점검하기

go run과 go build

코드를 실행하는 방법은 두 가지다. go run .은 현재 폴더의 프로그램을 컴파일(사람이 쓴 코드를 컴퓨터가 실행할 수 있는 형태로 번역하는 일)한 뒤 바로 실행한다. 번역 결과는 임시 위치에 만들어졌다가 정리되므로 폴더에 실행 파일이 남지 않는다. 코드를 고치고 결과를 보는 일이 잦은 학습 단계에서는 이 명령이 편하다.

go build는 번역해서 실행 파일을 폴더에 남긴다. 실행 파일 이름은 기본적으로 모듈 이름의 마지막 부분이 되므로, 이 장에서는 bikeshare가 생긴다. 이 파일은 Go가 설치되지 않은 같은 종류의 컴퓨터에서도 실행할 수 있다. 다른 사람에게 프로그램을 넘기거나 서버에 올릴 때는 이 방식을 쓴다.

go run과 go build는 결과물과 쓰임새가 다르다
명령하는 일폴더에 남는 것주로 쓰는 때
go run .번역 후 바로 실행없음코드를 고치며 확인할 때
go build번역해서 실행 파일 생성실행 파일 하나프로그램을 전달하거나 배포할 때
go build -o 이름실행 파일 이름을 지정지정한 이름의 파일파일 이름을 직접 정하고 싶을 때

gofmt: 코드 모양은 도구가 정한다

Go에는 공식 형식이 하나 있고, gofmt가 그 형식으로 코드를 고쳐 준다. 들여쓰기는 탭 문자 하나, 중괄호 위치, 공백 개수, import 정렬 같은 것이 모두 자동으로 정해진다. 그래서 Go 코드는 누가 썼든 비슷하게 생겼고, 코드 모양을 두고 논쟁할 일이 없다.

주로 쓰는 옵션은 세 가지다. gofmt -l .은 형식이 다른 파일의 이름만 나열하고, gofmt -d .는 무엇이 달라지는지 보여 주며, gofmt -w .는 파일을 직접 고쳐 쓴다. go fmt ./...는 폴더 아래의 모든 코드에 대해 gofmt -l -w를 실행하는 줄임 명령이다.

go vet: 컴파일은 되지만 수상한 코드

컴파일이 통과해도 실수가 남아 있을 수 있다. go vet은 컴파일러가 잡지 않는 의심스러운 코드를 찾아 알려 준다. 대표적인 것이 fmt.Printf의 형식 글자와 값의 종류가 맞지 않는 경우다. 문제가 없으면 아무것도 출력하지 않고 끝난다. 출력이 없다는 것이 통과했다는 뜻이다.

gofmt와 go vet은 점검하는 대상이 다르다
도구점검 대상문제가 있을 때고치는 방법
gofmt들여쓰기, 공백, 정렬 같은 코드 모양-l로 파일 이름이 나온다-w로 자동 수정
go vet컴파일은 되지만 의심스러운 코드파일 위치와 이유가 나온다사람이 코드를 직접 고친다

완성 코드

bikeshare 폴더 안에 main.go 파일을 만들고 다음 내용을 저장한다. 들여쓰기는 탭 문자다.

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println("동네 공공 자전거 대여소")
	fmt.Println(strings.Repeat("=", 24))
	fmt.Println("대여소: 느티나무 앞")
	fmt.Printf("대여 가능한 자전거: %d대\n", 12)
	fmt.Printf("비어 있는 거치대: %d칸\n", 8)
}

줄별 해설

  • package main: 이 파일이 main 패키지에 속한다고 밝힌다. 실행 가능한 프로그램이 되려면 이름이 반드시 main이어야 한다.
  • import ( ... ): 괄호로 여러 패키지를 한꺼번에 가져온다. 패키지 이름은 큰따옴표 안에 적는다. gofmt는 이 목록을 알파벳 순서로 정렬한다.
  • func main() {: 프로그램이 시작되는 함수를 선언한다. 여는 중괄호는 반드시 같은 줄 끝에 둔다.
  • fmt.Println("동네 공공 자전거 대여소"): 글자를 출력하고 줄을 바꾼다. Println은 print line(줄 출력)의 줄임말이다. 큰따옴표로 감싼 글자 묶음을 문자열이라고 하며, 한글도 그대로 쓸 수 있다.
  • strings.Repeat("=", 24): = 글자를 24번 이어 붙인 문자열을 돌려준다. 함수가 돌려준 값을 곧바로 Println에 넘겨 구분선을 출력한다.
  • fmt.Println("대여소: 느티나무 앞"): 대여소 이름을 한 줄로 출력한다.
  • fmt.Printf("대여 가능한 자전거: %d대\n", 12): Printf는 형식을 지정해 출력한다. 문자열 안의 %d는 정수가 들어갈 자리이고, 뒤에 넘긴 12가 그 자리를 채운다. \n은 줄바꿈을 뜻하는 글자다. Printf는 Println과 달리 줄을 자동으로 바꾸지 않으므로 직접 넣어야 한다.
  • fmt.Printf("비어 있는 거치대: %d칸\n", 8): 같은 방식으로 빈 거치대 수를 출력한다.
  • 마지막 }: main 함수의 끝이다. 여기까지 실행하면 프로그램이 끝난다.

숫자 12와 8을 코드에 직접 적었다. 값에 이름을 붙여 저장하는 변수는 다음 장에서 다루므로, 이 장에서는 값을 바로 쓴다.

실행 결과

bikeshare 폴더 안에서 다음 순서로 실행한다. go run .의 마침표는 현재 폴더를 뜻한다.

$ go run .
동네 공공 자전거 대여소
========================
대여소: 느티나무 앞
대여 가능한 자전거: 12대
비어 있는 거치대: 8칸

이어서 실행 파일을 만들고 직접 실행해 본다. 출력 내용은 go run과 같다.

$ go build
$ ./bikeshare
동네 공공 자전거 대여소
========================
대여소: 느티나무 앞
대여 가능한 자전거: 12대
비어 있는 거치대: 8칸

마지막으로 두 점검 도구를 실행한다. 둘 다 문제가 없으면 아무것도 출력하지 않는다.

$ gofmt -l .
$ go vet ./...
$

실무에서 자주 틀리는 것

1. 가져온 패키지를 쓰지 않는다

Go는 쓰지 않는 import를 오류로 취급한다. 나중에 쓸 생각으로 미리 적어 두면 컴파일이 멈춘다.

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println("대여소 열림")
}

다음과 비슷한 오류가 나온다.

./main.go:5:2: "os" imported and not used

쓰지 않는 줄을 지우면 된다.

package main

import "fmt"

func main() {
	fmt.Println("대여소 열림")
}

2. 여는 중괄호를 다음 줄에 둔다

다른 언어의 습관대로 중괄호를 줄 앞에 내리면 문법 오류가 난다. Go는 줄이 끝나는 자리에 문장이 끝난 것으로 보기 때문이다.

func main()
{
	fmt.Println("대여소 열림")
}
syntax error: unexpected semicolon or newline before {

여는 중괄호를 func main()과 같은 줄에 둔다.

func main() {
	fmt.Println("대여소 열림")
}

3. 함수 이름을 소문자로 쓴다

다른 패키지의 함수는 대문자로 시작해야 쓸 수 있다. fmt.println처럼 쓰면 컴파일 오류가 난다.

fmt.println("대여소 열림")
fmt.Println("대여소 열림")

4. Printf의 형식 글자와 값이 맞지 않는다

이 코드는 컴파일이 되고 실행도 된다. 그러나 %d는 정수 자리인데 문자열을 넘겼다.

fmt.Printf("대여 가능한 자전거: %d대\n", "열두")

실행하면 대여 가능한 자전거: %!d(string=열두)대처럼 깨진 글자가 나오고, 오류 없이 지나가 버리기 쉽다. go vet은 이것을 잡는다.

$ go vet ./...
./main.go:9:15: fmt.Printf format %d has arg "열두" of wrong type string

줄과 열 번호는 코드 위치에 따라 달라진다. 값을 정수로 고치면 통과한다. 커밋하거나 코드를 전달하기 전에 go vet ./...를 습관으로 실행해 두면 좋다.

fmt.Printf("대여 가능한 자전거: %d대\n", 12)

한눈에 보기

이 장에서 쓴 명령과 그 역할
명령역할결과물메모
go version설치한 Go 버전 확인한 줄 출력1.25 이상이면 된다
go mod init 이름폴더를 모듈로 만든다go.mod폴더당 한 번 실행한다
go run .번역 후 바로 실행프로그램 출력실행 파일은 남지 않는다
go build실행 파일 생성모듈 이름의 파일-o로 이름을 바꾼다
gofmt -l .형식이 다른 파일 나열파일 이름 목록-w로 자동 수정
go vet ./...의심스러운 코드 점검문제 위치와 이유출력이 없으면 통과
이 장에서 나온 용어와 규칙
용어뜻이 장의 예규칙
모듈프로젝트 단위, go.mod가 표시한다bikeshare폴더 최상단에 둔다
패키지같은 폴더의 코드 묶음main, fmt파일 첫 줄에 이름을 적는다
main 패키지실행 가능한 프로그램main.gomain 함수가 시작점이다
import다른 패키지 가져오기"fmt"쓰지 않으면 컴파일 오류다

연습 문제

  1. main.go의 대여소 이름을 "은행나무 사거리"로, 대여 가능한 자전거를 7대로, 빈 거치대를 13칸으로 바꾼 뒤 go run .으로 실행해 출력이 바뀌는지 확인하라.
  2. main.go에서 fmt.Println 줄 하나의 앞 탭을 지우고 공백 여러 개로 바꿔 저장하라. gofmt -l .과 gofmt -d .를 실행해 결과를 설명하고, gofmt -w .로 되돌려라.
  3. go build로 실행 파일 이름을 station으로 만들려면 어떤 명령을 쓰는가. 만든 파일을 실행하는 명령도 함께 적어라.
  4. 연습 문제 1의 코드에서 빈 거치대를 출력하는 줄의 %d 자리에 문자열 "열세"를 넘기도록 일부러 바꿔라. go run .과 go vet ./...의 결과를 비교하고, 고친 뒤 go vet이 조용해지는 것을 확인하라.

정답과 해설

  1. 바꾼 줄은 다음과 같다.
    fmt.Println("대여소: 은행나무 사거리")
    fmt.Printf("대여 가능한 자전거: %d대\n", 7)
    fmt.Printf("비어 있는 거치대: %d칸\n", 13)
    문자열 안의 글자와 %d 뒤에 넘긴 숫자만 바꾸면 된다. 저장하고 go run .을 다시 실행하면 새 값이 출력된다. 실행할 때마다 번역이 새로 일어나므로 빌드 단계를 따로 신경 쓸 필요가 없다.
  2. gofmt -l .은 main.go라는 파일 이름을 출력한다. 형식이 공식 규칙과 다르다는 뜻이다. gofmt -d .는 공백으로 들여쓴 줄이 탭으로 바뀌는 차이를 보여 준다. gofmt -w .를 실행하면 파일이 고쳐지고, 다시 gofmt -l .을 실행하면 출력이 없다. 프로그램의 동작은 들여쓰기와 상관없이 같지만, 형식을 통일해 두면 다른 사람이 읽기 쉽다.
  3. 다음 명령을 쓴다.
    go build -o station
    ./station
    -o 옵션 뒤에 파일 이름을 적는다. 이름을 지정하지 않으면 모듈 이름인 bikeshare가 쓰인다. 현재 폴더의 실행 파일은 이름 앞에 ./를 붙여 실행한다.
  4. go run .은 오류 없이 실행되고 빈 거치대 줄이 비어 있는 거치대: %!d(string=열세)칸처럼 깨져 나온다. 반면 go vet ./...은 fmt.Printf format %d has arg "열세" of wrong type string 형태의 메시지로 위치와 이유를 알려 준다. 값을 13으로 되돌리면 go vet은 아무것도 출력하지 않는다. 컴파일이 통과해도 실행 결과가 틀릴 수 있으므로, 실행 전후에 go vet을 함께 쓰는 것이 안전하다.

다음 장에서는 이 프로그램에 직접 적어 넣은 12와 8 같은 값에 이름을 붙여 저장하는 방법을 다룬다. 변수와 상수, 기본 타입을 익히면 대여소 상태를 코드에서 다루기 시작할 수 있다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.