Go: Basiskennis voor Java/Python-ontwikkelaars in 2026

Leer Go snel door je Java- of Python-ervaring te benutten. Goroutines, channels, interfaces en essentiële patronen voor een vlotte overstap.

Go-gids voor Java- en Python-ontwikkelaars

Go (of Golang) heeft zich gevestigd als de taal bij uitstek voor microservices, CLI-tools en gedistribueerde systemen. In 2009 ontwikkeld door Google, combineert het de eenvoud van Python met de prestaties van C. Voor ontwikkelaars afkomstig uit Java of Python verloopt de overstap naar Go verrassend soepel zodra de kernconcepten op hun plek vallen.

Waarom Go in 2026?

Go vormt de basis van Docker, Kubernetes, Terraform en talloze cloud-infrastructuren. De snelle compilatie, native concurrency-ondersteuning en deployment als enkele binary maken het ideaal voor moderne backend-ontwikkeling.

Go installeren en configureren

Go installeren is eenvoudig en consistent op alle platformen. De go-tool beheert compilatie, afhankelijkheden en testing.

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

De projectstructuur in Go volgt strikte maar eenvoudige conventies. Het go.mod-bestand definieert de module en de bijbehorende afhankelijkheden.

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

Eerste Go-programma

Hieronder staat een eenvoudig programma dat de basissyntax van Go illustreert. De vergelijking met Java en Python helpt de verschillen te visualiseren.

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

Wat meteen opvalt: geen puntkomma's, geen haakjes rond condities en type-inferentie met :=. Go geeft prioriteit aan bondigheid zonder leesbaarheid op te offeren.

Variabelen en fundamentele types

Go is statisch getypeerd maar biedt uitstekende type-inferentie. De basistypen dekken de meeste gebruikssituaties.

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)
}
Zero Values in Go

Anders dan in Java of Python initialiseert Go variabelen automatisch naar hun "zero value": 0 voor getallen, "" voor strings, false voor bools, nil voor pointers en slices.

Functies en meervoudige returnwaarden

Go staat toe om meerdere waarden te retourneren, een eigenschap die uitgebreid wordt gebruikt voor foutafhandeling.

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

Structs en methoden

Structs zijn de bouwstenen voor aangepaste types in Go. Methoden worden via receivers aan types gekoppeld.

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

Gebruik een pointer receiver (*User) wanneer de methode de toestand wijzigt of wanneer het struct groot is. Gebruik een value receiver (User) voor read-only methoden op lichtgewicht structs.

Interfaces: impliciet polymorfisme

Go-interfaces worden impliciet geïmplementeerd. Een type voldoet aan een interface als het alle methoden implementeert, zonder expliciete declaratie.

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

Deze aanpak verschilt fundamenteel van Java, waar implements verplicht is. In Go is conformiteit structureel, niet nominaal.

Klaar om je Go gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Slices en maps: dynamische collecties

Slices zijn dynamische weergaven over arrays en maps zijn native hash-tabellen.

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

Idiomatische foutafhandeling

Go kent geen exceptions. Fouten zijn waarden die expliciet worden geretourneerd, wat een rigoureuze afhandeling afdwingt.

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 en errors.As

Sinds Go 1.13 wordt errors.Is() gebruikt om te vergelijken met sentinel-fouten en errors.As() om een specifiek fouttype te extraheren uit een keten van gewrapte fouten.

Goroutines: lichtgewicht concurrency

Goroutines zijn lichtgewicht threads beheerd door de Go-runtime. Het starten van een goroutine kost slechts enkele KB geheugen.

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

De sync.WaitGroup maakt het mogelijk om te wachten tot meerdere goroutines zijn voltooid. Dit is het basispatroon voor parallellisme in Go.

Channels: communicatie tussen goroutines

Channels zijn getypeerde conduits voor communicatie tussen goroutines. Ze maken veilige synchronisatie en gegevensuitwisseling mogelijk.

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

Gebufferde channels en 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-filosofie: CSP

Go volgt het CSP-model (Communicating Sequential Processes): "Communiceer niet door geheugen te delen; deel geheugen door te communiceren." Channels voorkomen race conditions.

Testen in Go

Go bevat een minimalistisch maar effectief testframework. Testbestanden eindigen op _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)
    }
}

Tests worden uitgevoerd met go test ./... en benchmarks met go test -bench=..

HTTP: minimalistische webserver

Go blinkt uit in het maken van krachtige HTTP-servers met de standaardbibliotheek.

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

Deze server verwerkt duizenden gelijktijdige verbindingen dankzij goroutines. Elk verzoek wordt automatisch in een eigen goroutine afgehandeld.

Conclusie

Go biedt een pragmatische benadering van backend-ontwikkeling: eenvoudige syntax, snelle compilatie, native concurrency en uitstekende tooling. Voor Java- of Python-ontwikkelaars vereist de overstap het accepteren van enkele andere conventies (expliciete foutafhandeling, beperkte generics vóór Go 1.18), maar de voordelen qua prestaties en onderhoudbaarheid zijn direct merkbaar.

Checklist om te beginnen

  • ✅ Go installeren via de officiële website of pakketbeheerder
  • ✅ De commando's go build, go run, go test, go fmt beheersen
  • ✅ Het verschil tussen slices en arrays begrijpen
  • ✅ Het if err != nil-patroon voor foutafhandeling toepassen
  • ✅ Goroutines en channels voor concurrency gebruiken
  • ✅ Table-driven tests schrijven met het testing-pakket

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Het Go-ecosysteem is volwassen, met populaire frameworks zoals Gin, Echo en Fiber voor webontwikkeling, en tools zoals Cobra voor CLI's. Met deze solide basis wordt het verkennen van geavanceerde onderwerpen zoals generics (Go 1.18+), het context-pakket en concurrency-patronen goed toegankelijk.

Tags

#go
#golang
#concurrency
#goroutines
#backend

Delen

Gerelateerde artikelen