Go: Основи для Java/Python-розробників у 2026 році

Швидке вивчення Go з використанням досвіду Java або Python. Goroutines, channels, інтерфейси та основні патерни для плавного переходу.

Посібник з Go для Java та Python розробників

Go (або Golang) утвердилася як мова першого вибору для мікросервісів, CLI-інструментів та розподілених систем. Створена Google у 2009 році, вона поєднує простоту Python із продуктивністю C. Для розробників, які переходять з Java або Python, перехід на Go виявляється напрочуд плавним, коли ключові концепції стають зрозумілими.

Чому Go у 2026 році?

Go лежить в основі Docker, Kubernetes, Terraform та незліченної кількості хмарних інфраструктур. Швидка компіляція, нативна підтримка конкурентності та розгортання одним бінарним файлом роблять Go ідеальним для сучасної backend-розробки.

Встановлення та налаштування 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 для чисел, "" для рядків, false для bool, 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())
}
Value vs Pointer Receiver

Pointer receiver (*User) використовується, коли метод змінює стан або коли структура є великою. Value receiver (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)
    }
}

Цей підхід радикально відрізняється від Java, де implements є обов'язковим. У 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() для вилучення конкретного типу помилки з ланцюжка обгорнутих помилок.

Goroutines: легковісна конкурентність

Goroutines — це легковісні потоки, якими керує середовище виконання Go. Запуск goroutine коштує лише кілька КБ пам'яті.

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 дозволяє чекати завершення кількох goroutines. Це базовий патерн паралелізму в Go.

Channels: комунікація між goroutines

Channels — це типізовані канали для комунікації між goroutines. Вони забезпечують безпечну синхронізацію та обмін даними.

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")
}

Буферизовані channels та 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): "Не комунікуйте, поділяючи пам'ять; поділяйте пам'ять, комунікуючи." Channels запобігають перегонам даних.

Тестування в 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))
}

Цей сервер обробляє тисячі одночасних з'єднань завдяки goroutines. Кожен запит автоматично обробляється у власній goroutine.

Висновок

Go пропонує прагматичний підхід до backend-розробки: проста синтаксис, швидка компіляція, нативна конкурентність та чудові інструменти. Для Java- або Python-розробників перехід потребує прийняття деяких відмінних конвенцій (явна обробка помилок, обмежені дженерики до Go 1.18), але переваги у продуктивності та підтримуваності відчуваються одразу.

Контрольний список для початку

  • ✅ Встановити Go з офіційного сайту або менеджера пакетів
  • ✅ Опанувати команди go build, go run, go test, go fmt
  • ✅ Зрозуміти різницю між слайсами та масивами
  • ✅ Прийняти патерн if err != nil для обробки помилок
  • ✅ Використовувати goroutines та channels для конкурентності
  • ✅ Писати table-driven тести з пакетом testing

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Екосистема Go є зрілою, з популярними фреймворками на кшталт Gin, Echo та Fiber для веб-розробки, та інструментами на кшталт Cobra для CLI. З цими міцними основами вивчення просунутих тем, таких як дженерики (Go 1.18+), пакет context та патерни конкурентності, стає доступним.

Теги

#go
#golang
#concurrency
#goroutines
#backend

Поділитися

Пов'язані статті