Go: Java/Python 개발자를 위한 기초 2026

Java나 Python 경험을 활용하여 Go를 빠르게 배우세요. 고루틴, 채널, 인터페이스 및 원활한 전환을 위한 핵심 패턴을 설명합니다.

Java 및 Python 개발자를 위한 Go 가이드

Go(또는 Golang)는 마이크로서비스, CLI 도구, 분산 시스템을 위한 최고의 언어로 자리매김했습니다. 2009년 Google에서 만들어진 Go는 Python의 간결함과 C 수준의 성능을 결합합니다. Java나 Python에서 전환하는 개발자에게 Go로의 이전은 핵심 개념이 이해되면 놀라울 정도로 순조롭습니다.

2026년에 Go를 선택하는 이유

Go는 Docker, Kubernetes, Terraform 및 수많은 클라우드 인프라의 기반입니다. 빠른 컴파일, 네이티브 동시성 지원, 단일 바이너리 배포는 Go를 현대 백엔드 개발에 이상적으로 만듭니다.

Go 설치 및 설정

Go 설치는 간단하며 모든 플랫폼에서 일관됩니다. go 도구가 컴파일, 의존성 관리, 테스트를 처리합니다.

bash
# install.sh
# Installation on macOS with Homebrew
brew install go

# Installation on Linux (Ubuntu/Debian)
sudo apt update && sudo apt install golang-go

# Verify installation
go version
# go version go1.22.0 linux/amd64

Go 프로젝트 구조는 엄격하지만 간단한 규칙을 따릅니다. go.mod 파일이 모듈과 의존성을 정의합니다.

bash
# project-setup.sh
# Create a new project
mkdir my-project && cd my-project
go mod init github.com/user/my-project

# Generated structure:
# my-project/
# ├── go.mod    # Module manifest
# └── main.go   # Entry point

# Essential commands
go build          # Compile the project
go run main.go    # Compile and execute
go test ./...     # Run all tests
go fmt ./...      # Format code automatically

첫 번째 Go 프로그램

Go의 기본 문법을 보여주는 간단한 프로그램입니다. Java 및 Python과의 비교가 차이점을 이해하는 데 도움이 됩니다.

main.gogo
package main

import "fmt"

// Program entry point
func main() {
    // Declaration with type inference
    message := "Hello, Go!"
    fmt.Println(message)

    // Explicit declaration
    var count int = 42
    fmt.Printf("Count: %d\n", count)
}

즉시 눈에 띄는 점: 세미콜론 없음, 조건문 주변 괄호 없음, :=를 통한 타입 추론. Go는 가독성을 희생하지 않으면서 간결함을 우선시합니다.

변수와 기본 타입

Go는 정적 타입이지만 뛰어난 타입 추론을 제공합니다. 기본 타입이 대부분의 사용 사례를 커버합니다.

types.gogo
package main

import "fmt"

func main() {
    // Short declaration (inside functions only)
    name := "Alice"        // string
    age := 30              // int
    height := 1.75         // float64
    active := true         // bool

    // Explicit declaration
    var score int = 100
    var rate float64 = 3.14

    // Multiple declaration
    var (
        firstName string = "Bob"
        lastName  string = "Smith"
        points    int    = 0
    )

    // Zero values (default values)
    var count int      // 0
    var text string    // "" (empty string)
    var flag bool      // false
    var ptr *int       // nil

    fmt.Println(name, age, height, active)
}
Go의 제로 값

Java나 Python과 달리 Go는 변수를 자동으로 "제로 값"으로 초기화합니다: 숫자는 0, 문자열은 "", bool은 false, 포인터와 슬라이스는 nil입니다.

함수와 다중 반환값

Go는 여러 값을 반환할 수 있으며, 이 기능은 에러 처리에 광범위하게 사용됩니다.

functions.gogo
package main

import (
    "errors"
    "fmt"
)

// Simple function with typed parameters
func add(a, b int) int {
    return a + b
}

// Multiple returns (idiomatic pattern for errors)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Named returns
func getUser(id int) (name string, age int, err error) {
    if id <= 0 {
        err = errors.New("invalid ID")
        return
    }
    name = "Alice"
    age = 30
    return
}

// Variadic function
func sum(numbers ...int) int {
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}

func main() {
    // Simple call
    result := add(5, 3)
    fmt.Println("5 + 3 =", result)

    // Explicit error handling
    quotient, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Printf("10 / 3 = %.2f\n", quotient)

    // Ignore a returned value with _
    name, _, _ := getUser(1)
    fmt.Println("User:", name)

    // Variadic call
    total := sum(1, 2, 3, 4, 5)
    fmt.Println("Sum:", total)
}

구조체와 메서드

구조체는 Go에서 사용자 정의 타입을 만드는 기본 구성 요소입니다. 메서드는 리시버를 통해 타입에 연결됩니다.

structs.gogo
package main

import "fmt"

// Struct definition
type User struct {
    ID       int
    Username string
    Email    string
    Active   bool
}

// Constructor (convention: NewTypeName)
func NewUser(id int, username, email string) *User {
    return &User{
        ID:       id,
        Username: username,
        Email:    email,
        Active:   true,
    }
}

// Method with value receiver (copy)
func (u User) FullInfo() string {
    status := "inactive"
    if u.Active {
        status = "active"
    }
    return fmt.Sprintf("%s <%s> (%s)", u.Username, u.Email, status)
}

// Method with pointer receiver (modification possible)
func (u *User) Deactivate() {
    u.Active = false
}

// Method with pointer receiver for modification
func (u *User) UpdateEmail(newEmail string) {
    u.Email = newEmail
}

func main() {
    // Create with constructor
    user := NewUser(1, "alice", "alice@example.com")
    fmt.Println(user.FullInfo())

    // Modify via method
    user.Deactivate()
    fmt.Println(user.FullInfo())

    // Direct creation
    user2 := User{
        ID:       2,
        Username: "bob",
        Email:    "bob@example.com",
    }
    fmt.Println(user2.FullInfo())
}
값 리시버 vs 포인터 리시버

메서드가 상태를 변경하거나 구조체가 클 때는 포인터 리시버(*User)를 사용합니다. 가벼운 구조체의 읽기 전용 메서드에는 값 리시버(User)를 사용합니다.

인터페이스: 암시적 다형성

Go 인터페이스는 암시적으로 구현됩니다. 타입이 인터페이스의 모든 메서드를 구현하면 명시적 선언 없이 해당 인터페이스를 만족합니다.

interfaces.gogo
package main

import (
    "fmt"
    "math"
)

// Interface definition
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Rectangle implements Shape implicitly
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)
}

// Circle also implements Shape
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
}

// Function accepting the interface
func PrintShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}
    circle := Circle{Radius: 7}

    // Polymorphism via interface
    PrintShapeInfo(rect)
    PrintShapeInfo(circle)

    // Slice of interfaces
    shapes := []Shape{rect, circle}
    for _, shape := range shapes {
        PrintShapeInfo(shape)
    }
}

이 접근 방식은 implements가 필수인 Java와 근본적으로 다릅니다. Go에서 적합성은 구조적이며 명목적이지 않습니다.

Go 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

슬라이스와 맵: 동적 컬렉션

슬라이스는 배열에 대한 동적 뷰이고, 맵은 네이티브 해시 테이블입니다.

collections.gogo
package main

import "fmt"

func main() {
    // Slice: dynamic array
    numbers := []int{1, 2, 3, 4, 5}

    // Append elements
    numbers = append(numbers, 6, 7)

    // Slicing (similar to Python)
    subset := numbers[1:4]  // [2, 3, 4]
    fmt.Println("Subset:", subset)

    // Create slice with make
    scores := make([]int, 0, 10)  // len=0, cap=10
    scores = append(scores, 100, 95, 88)

    // Iteration with range
    for index, value := range numbers {
        fmt.Printf("numbers[%d] = %d\n", index, value)
    }

    // Map: hash table
    users := map[string]int{
        "alice": 30,
        "bob":   25,
    }

    // Add/Update
    users["charlie"] = 35

    // Check existence
    age, exists := users["alice"]
    if exists {
        fmt.Println("Alice's age:", age)
    }

    // Delete
    delete(users, "bob")

    // Map iteration
    for name, age := range users {
        fmt.Printf("%s is %d years old\n", name, age)
    }
}

관용적 에러 처리

Go에는 예외가 없습니다. 에러는 명시적으로 반환되는 값이며, 엄격한 처리를 강제합니다.

errors.gogo
package main

import (
    "errors"
    "fmt"
    "os"
)

// Sentinel error (for comparison)
var ErrNotFound = errors.New("resource not found")
var ErrInvalidInput = errors.New("invalid input")

// Custom error with context
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

// Function returning different error types
func GetUser(id int) (string, error) {
    if id <= 0 {
        return "", &ValidationError{
            Field:   "id",
            Message: "must be positive",
        }
    }
    if id > 1000 {
        return "", ErrNotFound
    }
    return "Alice", nil
}

// Error wrapping (Go 1.13+)
func ReadConfig(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("reading config %s: %w", path, err)
    }
    return data, nil
}

func main() {
    // Basic pattern
    user, err := GetUser(-1)
    if err != nil {
        fmt.Println("Error:", err)

        // Type assertion for custom error
        var valErr *ValidationError
        if errors.As(err, &valErr) {
            fmt.Printf("Field: %s\n", valErr.Field)
        }

        // Comparison with sentinel error
        if errors.Is(err, ErrNotFound) {
            fmt.Println("User not found")
        }
    } else {
        fmt.Println("User:", user)
    }
}
errors.Is와 errors.As

Go 1.13부터 errors.Is()는 센티넬 에러와의 비교에, errors.As()는 래핑된 에러 체인에서 특정 에러 타입을 추출하는 데 사용됩니다.

고루틴: 경량 동시성

고루틴은 Go 런타임이 관리하는 경량 스레드입니다. 고루틴을 시작하는 데 필요한 메모리는 몇 KB에 불과합니다.

goroutines.gogo
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()  // Decrement counter when done

    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    // Launch 5 goroutines
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)  // 'go' prefix launches the goroutine
    }

    // Wait for all goroutines to complete
    wg.Wait()
    fmt.Println("All workers completed")
}

sync.WaitGroup은 여러 고루틴의 완료를 기다리는 데 사용됩니다. 이것이 Go에서 병렬 처리의 기본 패턴입니다.

채널: 고루틴 간 통신

채널은 고루틴 간 통신을 위한 타입이 지정된 파이프입니다. 안전한 동기화와 데이터 교환을 가능하게 합니다.

channels.gogo
package main

import (
    "fmt"
    "time"
)

func producer(ch chan<- int) {
    for i := 1; i <= 5; i++ {
        fmt.Println("Producing:", i)
        ch <- i  // Send on channel
        time.Sleep(100 * time.Millisecond)
    }
    close(ch)  // Close channel when done
}

func consumer(ch <-chan int, done chan<- bool) {
    for value := range ch {  // Iterate until closed
        fmt.Println("Consuming:", value)
    }
    done <- true
}

func main() {
    ch := make(chan int)     // Unbuffered channel
    done := make(chan bool)

    go producer(ch)
    go consumer(ch, done)

    <-done  // Wait for consumer to finish
    fmt.Println("All done")
}

버퍼 채널과 select

channels_advanced.gogo
package main

import (
    "fmt"
    "time"
)

func main() {
    // Buffered channel (capacity 3)
    buffered := make(chan int, 3)
    buffered <- 1
    buffered <- 2
    buffered <- 3
    // buffered <- 4  // Would block since buffer is full

    fmt.Println(<-buffered)  // 1

    // Select: channel multiplexing
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // Wait for first available message
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received:", msg2)
        case <-time.After(500 * time.Millisecond):
            fmt.Println("Timeout!")
        }
    }
}
Go의 철학: CSP

Go는 CSP(Communicating Sequential Processes) 모델을 따릅니다: "메모리를 공유하여 통신하지 말고, 통신을 통해 메모리를 공유하라." 채널은 레이스 컨디션을 방지합니다.

Go 테스트

Go에는 미니멀하지만 효과적인 테스트 프레임워크가 내장되어 있습니다. 테스트 파일은 _test.go로 끝납니다.

calculator.gogo
package calculator

func Add(a, b int) int {
    return a + b
}

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}
calculator_test.gogo
package calculator

import (
    "testing"
)

// Basic test
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

// Table-driven tests (recommended pattern)
func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -2, -3, -5},
        {"zero", 0, 0, 0},
        {"mixed", -5, 10, 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)
            }
        })
    }
}

// Error test
func TestDivideByZero(t *testing.T) {
    _, err := Divide(10, 0)
    if err == nil {
        t.Error("Expected error for division by zero")
    }
}

// Benchmark
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(100, 200)
    }
}

테스트는 go test ./...로, 벤치마크는 go test -bench=.로 실행합니다.

HTTP: 미니멀 웹 서버

Go는 표준 라이브러리를 사용하여 고성능 HTTP 서버를 만드는 데 탁월합니다.

server.gogo
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    // Simple route
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, Go!"))
    })

    // JSON route
    http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
        users := []User{
            {ID: 1, Name: "Alice"},
            {ID: 2, Name: "Bob"},
        }

        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(users)
    })

    // Route with method
    http.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case "GET":
            w.Write([]byte("Get user"))
        case "POST":
            w.Write([]byte("Create user"))
        default:
            http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        }
    })

    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

이 서버는 고루틴 덕분에 수천 개의 동시 연결을 처리할 수 있습니다. 각 요청은 자동으로 자체 고루틴에서 처리됩니다.

결론

Go는 백엔드 개발에 실용적인 접근 방식을 제공합니다: 간단한 문법, 빠른 컴파일, 네이티브 동시성, 우수한 도구. Java나 Python 개발자에게 전환은 몇 가지 다른 규칙의 수용을 요구하지만(명시적 에러 처리, Go 1.18 이전의 제한된 제네릭), 성능과 유지보수성의 이점은 즉시 체감할 수 있습니다.

시작 체크리스트

  • ✅ 공식 사이트 또는 패키지 매니저에서 Go 설치
  • go build, go run, go test, go fmt 명령어 마스터
  • ✅ 슬라이스와 배열의 차이점 이해
  • ✅ 에러 처리를 위한 if err != nil 패턴 채택
  • ✅ 동시성을 위해 고루틴과 채널 활용
  • testing 패키지로 테이블 기반 테스트 작성

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

Go 생태계는 성숙하며, 웹 개발용 Gin, Echo, Fiber 같은 인기 프레임워크와 CLI용 Cobra 같은 도구가 갖춰져 있습니다. 이러한 견고한 기반 위에서 제네릭(Go 1.18+), context 패키지, 동시성 패턴 같은 고급 주제 탐구가 용이해집니다.

태그

#go
#golang
#concurrency
#goroutines
#backend

공유

관련 기사