백고등어 개발 블로그
왜 Google, Netflix, Uber가 모두 Go로 갈아탔을까? 초보자도 7일만에 Go 마스터하기 본문
728x90
반응형
"Go 언어 배워야 하나요?" 이런 질문을 받으면 저는 항상 이렇게 답합니다.
"지금 안 배우면 5년 후에 후회할 겁니다."
구글이 만든 Go 언어가 왜 Docker, Kubernetes, Prometheus를 비롯한 모든 클라우드 네이티브 도구들의 표준이 되었는지 아시나요?
대기업들이 Go를 선택하는 진짜 이유
성능: 컴파일 언어의 속도 + 스크립트 언어의 편의성
# Python 서버
$ time curl http://localhost:8000/api/users
real 0m0.145s
# Go 서버
$ time curl http://localhost:8080/api/users
real 0m0.008s
18배 빠른 응답속도! 이것이 바로 Uber가 Node.js에서 Go로 갈아탄 이유입니다.
동시성: 고루틴의 마법
// 1만 개의 동시 작업을 가볍게 처리
func main() {
for i := 0; i < 10000; i++ {
go func(id int) {
fmt.Printf("Goroutine %d is running\n", id)
time.Sleep(time.Second)
}(i)
}
time.Sleep(2 * time.Second)
}
자바에서 1만 개의 스레드를 만들면? 메모리 부족으로 서버 다운
Go에서 1만 개의 고루틴을 만들면? 아무것도 아님
배포: 단일 바이너리의 혁명
# 빌드
$ go build -o myapp main.go
# 배포 (의존성 걱정 없음!)
$ scp myapp user@server:/usr/local/bin/
$ ssh user@server "myapp &"
Docker, JVM, Python 런타임? 필요 없습니다. 파일 하나면 끝.
Day 1-2: Go의 첫인상, "이렇게 단순할 수가?"
설치부터 Hello World까지
# 설치 (Mac)
brew install go
# 설치 (Linux)
wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz
sudo tar -xvf go1.21.0.linux-amd64.tar.gz -C /usr/local
# 설치 확인
go version
// main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
# 실행
$ go run main.go
Hello, Go!
# 빌드
$ go build main.go
$ ./main
Hello, Go!
변수와 타입 - 명시적이면서도 간결한
package main
import "fmt"
func main() {
// 변수 선언 방법들
var name string = "홍길동"
var age int = 25
// 타입 추론
var city = "서울"
// 짧은 선언 (함수 내에서만 가능)
country := "한국"
// 여러 변수 한번에
var (
firstName = "길동"
lastName = "홍"
isActive = true
)
fmt.Printf("이름: %s, 나이: %d, 도시: %s, 국가: %s\n",
name, age, city, country)
fmt.Printf("성: %s, 이름: %s, 활성화: %t\n",
lastName, firstName, isActive)
}
함수 - 다중 반환값의 혁신
package main
import (
"fmt"
"errors"
)
// 기본 함수
func add(a, b int) int {
return a + b
}
// 다중 반환값 - Go의 핵심 특징
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("0으로 나눌 수 없습니다")
}
return a / b, nil
}
// 명명된 반환값
func getUserInfo(id int) (name string, age int, err error) {
if id <= 0 {
err = errors.New("잘못된 사용자 ID")
return // name, age는 제로값으로 자동 설정
}
name = "홍길동"
age = 25
return // 명시적으로 값을 써도 되고 안 써도 됨
}
func main() {
// 기본 함수 사용
result := add(10, 20)
fmt.Printf("덧셈 결과: %d\n", result)
// 다중 반환값 처리
quotient, err := divide(10, 3)
if err != nil {
fmt.Printf("에러: %s\n", err)
} else {
fmt.Printf("나눗셈 결과: %.2f\n", quotient)
}
// 에러 무시하기
quotient2, _ := divide(10, 2)
fmt.Printf("나눗셈 결과: %.2f\n", quotient2)
// 명명된 반환값 사용
name, age, err := getUserInfo(1)
if err != nil {
fmt.Printf("에러: %s\n", err)
} else {
fmt.Printf("사용자: %s, 나이: %d\n", name, age)
}
}
Day 3-4: 구조체와 메서드 - 객체지향의 Go 방식
구조체 정의와 사용
package main
import (
"fmt"
"time"
)
// 구조체 정의
type User struct {
ID int
Name string
Email string
CreatedAt time.Time
isActive bool // 소문자 시작 = private
}
// 생성자 함수 (관례)
func NewUser(name, email string) *User {
return &User{
ID: generateID(),
Name: name,
Email: email,
CreatedAt: time.Now(),
isActive: true,
}
}
// 메서드 정의 - 리시버(receiver) 사용
func (u *User) Activate() {
u.isActive = true
}
func (u *User) Deactivate() {
u.isActive = false
}
func (u User) IsActive() bool { // 값 리시버 (읽기 전용)
return u.isActive
}
func (u User) String() string { // Stringer 인터페이스 구현
return fmt.Sprintf("User{ID: %d, Name: %s, Email: %s}",
u.ID, u.Name, u.Email)
}
// 임베딩 (상속 대신)
type Admin struct {
User // User 구조체 임베딩
Permissions []string
}
func (a *Admin) AddPermission(permission string) {
a.Permissions = append(a.Permissions, permission)
}
var idCounter = 1
func generateID() int {
idCounter++
return idCounter
}
func main() {
// 구조체 생성 방법들
user1 := User{
ID: 1,
Name: "홍길동",
Email: "hong@example.com",
}
user2 := NewUser("김영희", "kim@example.com")
// 메서드 호출
fmt.Println("활성화 상태:", user2.IsActive())
user2.Deactivate()
fmt.Println("비활성화 후:", user2.IsActive())
// String() 메서드 자동 호출
fmt.Println(user1)
fmt.Println(user2)
// 임베딩 사용
admin := &Admin{
User: *NewUser("관리자", "admin@example.com"),
}
admin.AddPermission("READ")
admin.AddPermission("WRITE")
fmt.Printf("관리자: %s, 권한: %v\n", admin.Name, admin.Permissions)
}
인터페이스 - Go의 핵심 철학
package main
import (
"fmt"
"math"
)
// 인터페이스 정의 - 덕 타이핑
type Shape interface {
Area() float64
Perimeter() float64
}
// 빈 인터페이스 - 모든 타입
type Printer interface {
Print()
}
// Circle 구조체
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
func (c Circle) Print() {
fmt.Printf("원 - 반지름: %.2f\n", c.Radius)
}
// Rectangle 구조체
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func (r Rectangle) Print() {
fmt.Printf("사각형 - 너비: %.2f, 높이: %.2f\n", r.Width, r.Height)
}
// 인터페이스를 매개변수로 받는 함수
func printShapeInfo(s Shape) {
fmt.Printf("넓이: %.2f, 둘레: %.2f\n", s.Area(), s.Perimeter())
}
func printAny(p Printer) {
p.Print()
}
func main() {
circle := Circle{Radius: 5}
rectangle := Rectangle{Width: 10, Height: 6}
// 암시적 인터페이스 구현
printShapeInfo(circle)
printShapeInfo(rectangle)
printAny(circle)
printAny(rectangle)
// 인터페이스 슬라이스
shapes := []Shape{circle, rectangle}
for i, shape := range shapes {
fmt.Printf("도형 %d: ", i+1)
printShapeInfo(shape)
}
}
Day 5-6: 동시성 프로그래밍 - Go의 진짜 파워
고루틴과 채널
package main
import (
"fmt"
"sync"
"time"
)
// 기본 고루틴
func sayHello(name string) {
for i := 0; i < 5; i++ {
fmt.Printf("Hello %s! (%d)\n", name, i)
time.Sleep(100 * time.Millisecond)
}
}
// 채널을 이용한 통신
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d started job %d\n", id, job)
time.Sleep(time.Second) // 작업 시뮬레이션
fmt.Printf("Worker %d finished job %d\n", id, job)
results <- job * 2
}
}
func main() {
// 1. 기본 고루틴
fmt.Println("=== 기본 고루틴 ===")
go sayHello("Alice")
go sayHello("Bob")
time.Sleep(600 * time.Millisecond)
// 2. WaitGroup 사용
fmt.Println("\n=== WaitGroup 사용 ===")
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d is working...\n", id)
time.Sleep(time.Second)
fmt.Printf("Goroutine %d is done\n", id)
}(i)
}
wg.Wait()
fmt.Println("All goroutines finished!")
// 3. 채널을 이용한 작업자 풀
fmt.Println("\n=== Worker Pool ===")
const numJobs = 5
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// 워커 고루틴 시작
for w := 1; w <= numWorkers; w++ {
go worker(w, jobs, results)
}
// 작업 전송
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// 결과 수집
for a := 1; a <= numJobs; a++ {
result := <-results
fmt.Printf("Result: %d\n", result)
}
}
실전 예제: 웹 크롤러
package main
import (
"fmt"
"io"
"net/http"
"sync"
"time"
)
type Result struct {
URL string
Status int
Size int
Error error
}
func fetchURL(url string, results chan<- Result) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
results <- Result{URL: url, Error: err}
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
results <- Result{URL: url, Status: resp.StatusCode, Error: err}
return
}
duration := time.Since(start)
fmt.Printf("Fetched %s (%d bytes) in %v\n", url, len(body), duration)
results <- Result{
URL: url,
Status: resp.StatusCode,
Size: len(body),
Error: nil,
}
}
func main() {
urls := []string{
"https://google.com",
"https://github.com",
"https://stackoverflow.com",
"https://golang.org",
"https://reddit.com",
}
results := make(chan Result, len(urls))
// 모든 URL을 동시에 처리
for _, url := range urls {
go fetchURL(url, results)
}
// 결과 수집
for i := 0; i < len(urls); i++ {
result := <-results
if result.Error != nil {
fmt.Printf("ERROR %s: %v\n", result.URL, result.Error)
} else {
fmt.Printf("SUCCESS %s: %d (%d bytes)\n",
result.URL, result.Status, result.Size)
}
}
}
Day 7: 실전 프로젝트 - REST API 서버
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
// User 모델
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
// UserStore - 메모리 기반 저장소
type UserStore struct {
mu sync.RWMutex
users map[int]*User
nextID int
}
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[int]*User),
nextID: 1,
}
}
func (s *UserStore) Create(name, email string) *User {
s.mu.Lock()
defer s.mu.Unlock()
user := &User{
ID: s.nextID,
Name: name,
Email: email,
CreatedAt: time.Now(),
}
s.users[s.nextID] = user
s.nextID++
return user
}
func (s *UserStore) GetByID(id int) (*User, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
user, exists := s.users[id]
return user, exists
}
func (s *UserStore) GetAll() []*User {
s.mu.RLock()
defer s.mu.RUnlock()
users := make([]*User, 0, len(s.users))
for _, user := range s.users {
users = append(users, user)
}
return users
}
func (s *UserStore) Update(id int, name, email string) (*User, bool) {
s.mu.Lock()
defer s.mu.Unlock()
user, exists := s.users[id]
if !exists {
return nil, false
}
user.Name = name
user.Email = email
return user, true
}
func (s *UserStore) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
_, exists := s.users[id]
if exists {
delete(s.users, id)
}
return exists
}
// HTTP 핸들러들
type UserHandler struct {
store *UserStore
}
func NewUserHandler(store *UserStore) *UserHandler {
return &UserHandler{store: store}
}
func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/users")
switch {
case path == "" || path == "/":
h.handleUsers(w, r)
case strings.HasPrefix(path, "/"):
h.handleUser(w, r, path[1:]) // ID 부분 추출
default:
http.NotFound(w, r)
}
}
func (h *UserHandler) handleUsers(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.getUsers(w, r)
case http.MethodPost:
h.createUser(w, r)
default:
w.Header().Set("Allow", "GET, POST")
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *UserHandler) handleUser(w http.ResponseWriter, r *http.Request, idStr string) {
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
h.getUser(w, r, id)
case http.MethodPut:
h.updateUser(w, r, id)
case http.MethodDelete:
h.deleteUser(w, r, id)
default:
w.Header().Set("Allow", "GET, PUT, DELETE")
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *UserHandler) getUsers(w http.ResponseWriter, r *http.Request) {
users := h.store.GetAll()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func (h *UserHandler) createUser(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
if req.Name == "" || req.Email == "" {
http.Error(w, "Name and email are required", http.StatusBadRequest)
return
}
user := h.store.Create(req.Name, req.Email)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
func (h *UserHandler) getUser(w http.ResponseWriter, r *http.Request, id int) {
user, exists := h.store.GetByID(id)
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func (h *UserHandler) updateUser(w http.ResponseWriter, r *http.Request, id int) {
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
user, exists := h.store.Update(id, req.Name, req.Email)
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func (h *UserHandler) deleteUser(w http.ResponseWriter, r *http.Request, id int) {
if !h.store.Delete(id) {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
}
// 미들웨어
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 다음 핸들러 호출
next.ServeHTTP(w, r)
duration := time.Since(start)
log.Printf("%s %s %v", r.Method, r.URL.Path, duration)
})
}
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
store := NewUserStore()
// 테스트 데이터 추가
store.Create("홍길동", "hong@example.com")
store.Create("김영희", "kim@example.com")
store.Create("박철수", "park@example.com")
userHandler := NewUserHandler(store)
// 라우팅 설정
mux := http.NewServeMux()
mux.Handle("/api/users", userHandler)
mux.Handle("/api/users/", userHandler)
// Health check 엔드포인트
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "healthy",
"time": time.Now().Format(time.RFC3339),
})
})
// 미들웨어 적용
handler := corsMiddleware(loggingMiddleware(mux))
fmt.Println("서버가 :8080 포트에서 시작됩니다...")
log.Fatal(http.ListenAndServe(":8080", handler))
}
이제 서버를 실행하고 테스트해보세요:
# 서버 실행
$ go run main.go
# 다른 터미널에서 테스트
$ curl http://localhost:8080/health
$ curl http://localhost:8080/api/users
$ curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name":"새사용자","email":"new@example.com"}'
고급 Go 패턴들
1. 에러 처리 패턴
package main
import (
"errors"
"fmt"
)
// 커스텀 에러 타입
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Message)
}
// 에러 래핑
func validateUser(name, email string) error {
if name == "" {
return ValidationError{Field: "name", Message: "cannot be empty"}
}
if !strings.Contains(email, "@") {
return ValidationError{Field: "email", Message: "invalid format"}
}
return nil
}
func createUser(name, email string) (*User, error) {
if err := validateUser(name, email); err != nil {
return nil, fmt.Errorf("user creation failed: %w", err)
}
// 사용자 생성 로직...
return &User{Name: name, Email: email}, nil
}
func main() {
_, err := createUser("", "invalid-email")
if err != nil {
fmt.Printf("Error: %v\n", err)
// 특정 에러 타입 확인
var validationErr ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("Validation error in field: %s\n", validationErr.Field)
}
}
}
2. 컨텍스트 패턴
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func longRunningTask(ctx context.Context) error {
select {
case <-time.After(5 * time.Second):
fmt.Println("작업 완료!")
return nil
case <-ctx.Done():
fmt.Println("작업이 취소되었습니다:", ctx.Err())
return ctx.Err()
}
}
func handlerWithTimeout(w http.ResponseWriter, r *http.Request) {
// 3초 타임아웃 컨텍스트
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
if err := longRunningTask(ctx); err != nil {
http.Error(w, "Request timeout", http.StatusRequestTimeout)
return
}
w.Write([]byte("작업 완료!"))
}
func main() {
http.HandleFunc("/task", handlerWithTimeout)
fmt.Println("서버 시작: http://localhost:8080/task")
http.ListenAndServe(":8080", nil)
}
3. 제네릭 (Go 1.18+)
package main
import "fmt"
// 제네릭 스택
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
index := len(s.items) - 1
item := s.items[index]
s.items = s.items[:index]
return item, true
}
func (s *Stack[T]) IsEmpty() bool {
return len(s.items) == 0
}
// 제네릭 함수
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func main() {
// 정수 스택
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
for !intStack.IsEmpty() {
if value, ok := intStack.Pop(); ok {
fmt.Println("Pop:", value)
}
}
// 문자열 스택
stringStack := &Stack[string]{}
stringStack.Push("Hello")
stringStack.Push("World")
// 제네릭 함수 사용
numbers := []int{1, 2, 3, 4, 5}
doubled := Map(numbers, func(n int) int { return n * 2 })
strings := Map(numbers, func(n int) string { return fmt.Sprintf("Number: %d", n) })
fmt.Println("Doubled:", doubled)
fmt.Println("Strings:", strings)
}
성능 최적화 팁들
1. 메모리 최적화
// 좋지 않은 예
func badSliceUsage() {
data := make([]int, 0) // 초기 용량 없음
for i := 0; i < 10000; i++ {
data = append(data, i) // 계속 재할당
}
}
// 좋은 예
func goodSliceUsage() {
data := make([]int, 0, 10000) // 용량 미리 할당
for i := 0; i < 10000; i++ {
data = append(data, i)
}
}
// 문자열 빌더 사용
func buildString(parts []string) string {
var builder strings.Builder
builder.Grow(len(parts) * 10) // 예상 크기만큼 미리 할당
for _, part := range parts {
builder.WriteString(part)
}
return builder.String()
}
2. 고루틴 풀 패턴
package main
import (
"fmt"
"sync"
"time"
)
type WorkerPool struct {
workerCount int
jobQueue chan Job
wg sync.WaitGroup
}
type Job struct {
ID int
Data string
}
func NewWorkerPool(workerCount, queueSize int) *WorkerPool {
return &WorkerPool{
workerCount: workerCount,
jobQueue: make(chan Job, queueSize),
}
}
func (wp *WorkerPool) Start() {
for i := 0; i < wp.workerCount; i++ {
wp.wg.Add(1)
go wp.worker(i)
}
}
func (wp *WorkerPool) worker(id int) {
defer wp.wg.Done()
for job := range wp.jobQueue {
fmt.Printf("Worker %d processing job %d: %s\n", id, job.ID, job.Data)
time.Sleep(time.Millisecond * 100) // 작업 시뮬레이션
}
}
func (wp *WorkerPool) Submit(job Job) {
wp.jobQueue <- job
}
func (wp *WorkerPool) Stop() {
close(wp.jobQueue)
wp.wg.Wait()
}
func main() {
pool := NewWorkerPool(3, 10)
pool.Start()
// 작업 제출
for i := 0; i < 20; i++ {
pool.Submit(Job{
ID: i,
Data: fmt.Sprintf("Task %d", i),
})
}
pool.Stop()
fmt.Println("모든 작업 완료!")
}
실무에서 자주 쓰이는 도구들
1. 환경 설정 관리
package main
import (
"os"
"strconv"
)
type Config struct {
Port int `json:"port"`
DatabaseURL string `json:"database_url"`
Debug bool `json:"debug"`
}
func LoadConfig() *Config {
return &Config{
Port: getEnvAsInt("PORT", 8080),
DatabaseURL: getEnvAsString("DATABASE_URL", "localhost:5432"),
Debug: getEnvAsBool("DEBUG", false),
}
}
func getEnvAsString(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvAsInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}
}
return defaultValue
}
func getEnvAsBool(key string, defaultValue bool) bool {
if value := os.Getenv(key); value != "" {
if boolValue, err := strconv.ParseBool(value); err == nil {
return boolValue
}
}
return defaultValue
}
2. 로깅
package main
import (
"log"
"log/slog"
"os"
)
func setupLogger() *slog.Logger {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
return logger
}
func main() {
logger := setupLogger()
logger.Info("서버 시작", "port", 8080)
logger.Error("데이터베이스 연결 실패", "error", "connection timeout")
logger.Debug("요청 처리", "method", "GET", "path", "/api/users", "duration", "45ms")
}
테스트 작성하기
package main
import (
"testing"
)
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"mixed", -1, 2, 1},
{"zero", 0, 5, 5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
}
})
}
}
// 벤치마크 테스트
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
add(1, 2)
}
}
func add(a, b int) int {
return a + b
}
배포와 운영
Dockerfile 예제
# 멀티 스테이지 빌드
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
# 실행 환경
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
Makefile
.PHONY: build run test clean docker
build:
go build -o bin/app main.go
run:
go run main.go
test:
go test -v ./...
bench:
go test -bench=. ./...
clean:
rm -rf bin/
docker:
docker build -t myapp .
docker-run:
docker run -p 8080:8080 myapp
결론: Go는 선택이 아닌 필수
왜 Go를 배워야 하는가?
- 산업 표준: 쿠버네티스, 도커, 프로메테우스 등 모든 클라우드 도구들이 Go
- 성능: C/C++ 급의 성능과 Python 급의 개발 속도
- 동시성: 고루틴으로 쉽게 처리하는 대규모 동시 처리
- 생산성: 컴파일 속도, 배포 편의성, 코드 가독성
지금 바로 시작하세요:
# Go 설치
go version
# 첫 프로젝트
mkdir my-go-app
cd my-go-app
go mod init my-go-app
echo 'package main\nimport "fmt"\nfunc main() { fmt.Println("Hello, Go!") }' > main.go
go run main.go
7일 후, 여러분은 "왜 진작 Go를 배우지 않았을까?"라고 후회하게 될 겁니다.
구글, 넷플릭스, 우버가 선택한 언어, 이제 여러분 차례입니다!
728x90
반응형