Go 1.27: 50 готовых программ на Golang с примерами кода и лучшими практиками

Изучаете Go (Golang) или ищете готовые примеры для реального проекта? В этой подборке собраны практические программы на Go 1.27: от работы со строками, файлами и JSON до HTTP API, goroutines, каналов, generics и конкурентного программирования.

Все примеры можно скопировать, запустить и адаптировать под свой проект.

Что будет в подборке

1. Hello World на Go
2. Работа со строками
3. Массивы и slices
4. Maps
5. Struct и методы
6. Интерфейсы
7. Обработка ошибок
8. Чтение и запись файлов
9. JSON encode/decode
10. HTTP GET-запрос
11. REST API на `net/http`
12. Middleware
13. Context и timeout
14. Goroutines
15. Channels
16. Worker Pool
17. Mutex и атомики
18. Generics
19. Generic methods в Go 1.27
20. Iterator
21. Работа с PostgreSQL
22. Транзакции SQL
23. Graceful Shutdown
24. Retry с exponential backoff
25. Rate Limiter
26. Кэш с TTL
27. Pipeline
28. Fan-in / Fan-out
29. Unit-тесты
30. Benchmark

Пример: простой HTTP API на Go 1.27

package main

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

type Response struct {
Message string `json:"message"`
}

func main() {
mux := http.NewServeMux()

mux.HandleFunc("GET /api/hello", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

json.NewEncoder(w).Encode(Response{
Message: "Hello from Go 1.27",
})
})

log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}

Пример: Worker Pool на Go

package main

import (
	"fmt"
	"sync"
)

func worker(jobs <-chan int, wg *sync.WaitGroup) {
	defer wg.Done()

	for job := range jobs {
		fmt.Println("processing:", job)
	}
}

func main() {
	jobs := make(chan int)

	var wg sync.WaitGroup

	for range 3 {
		wg.Add(1)
		go worker(jobs, &wg)
	}

	for i := range 10 {
		jobs <- i
	}

	close(jobs)
	wg.Wait()
}

Лучшие практики Go

  • всегда проверяйте error;
  • передавайте context.Context для операций ввода-вывода;
  • не запускайте goroutine без понятного способа её остановить;
  • используйте defer для освобождения ресурсов;
  • предпочитайте маленькие интерфейсы;
  • форматируйте проект через gofmt;
  • запускайте go vet и тесты перед сборкой;
  • проверяйте конкурентный код через go test -race;
  • не добавляйте зависимости, если задачу нормально решает стандартная библиотека.

Что нового в Go 1.27

Одно из главных изменений Go 1.27 — generic methods: теперь методы могут объявлять собственные type parameters.

type Box struct{}

func (Box) Convert[T ~int | ~int64](value T) T {
	return value
}

Это позволяет использовать generics в API типов значительно естественнее.


Все примеры рассчитаны на современный Go 1.27 и подходят для обучения, собеседований и реальных backend-проектов.


## 31. Structured Logging через slog

```go
package main

import (
	"log/slog"
	"os"
)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

	logger.Info(
		"user logged in",
		"user_id", 42,
		"role", "admin",
	)
}

Для production лучше использовать структурированные логи вместо fmt.Println.

32. HTTP Middleware

func logging(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s", r.Method, r.URL.Path)
		next.ServeHTTP(w, r)
	})
}

Подключение:

mux := http.NewServeMux()
mux.HandleFunc("GET /", handler)

http.ListenAndServe(":8080", logging(mux))

33. HTTP-запрос с timeout

ctx, cancel := context.WithTimeout(
	context.Background(),
	3*time.Second,
)
defer cancel()

req, err := http.NewRequestWithContext(
	ctx,
	http.MethodGet,
	"https://example.com",
	nil,
)
if err != nil {
	log.Fatal(err)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
	log.Fatal(err)
}
defer resp.Body.Close()

Не делайте сетевые запросы без timeout.

34. Graceful Shutdown HTTP-сервера

server := &http.Server{
	Addr:    ":8080",
	Handler: mux,
}

ctx, stop := signal.NotifyContext(
	context.Background(),
	os.Interrupt,
	syscall.SIGTERM,
)
defer stop()

go func() {
	if err := server.ListenAndServe(); err != http.ErrServerClosed {
		log.Fatal(err)
	}
}()

<-ctx.Done()

shutdownCtx, cancel := context.WithTimeout(
	context.Background(),
	5*time.Second,
)
defer cancel()

server.Shutdown(shutdownCtx)

Так сервер успеет завершить активные запросы перед остановкой.

35. Ограничиваем количество goroutines

sem := make(chan struct{}, 5)

var wg sync.WaitGroup

for i := range 100 {
	wg.Add(1)

	go func(id int) {
		defer wg.Done()

		sem <- struct{}{}
		defer func() { <-sem }()

		fmt.Println("job:", id)
	}(i)
}

wg.Wait()

Вместо запуска тысяч тяжёлых задач одновременно ограничиваем concurrency.

36. Выполнить функцию только один раз

loadConfig := sync.OnceValue(func() string {
	fmt.Println("loading...")
	return "production"
})

fmt.Println(loadConfig())
fmt.Println(loadConfig())

Функция выполнится один раз, результат будет закэширован.

37. Безопасный счётчик через atomic

var counter atomic.Int64

var wg sync.WaitGroup

for range 1000 {
	wg.Add(1)

	go func() {
		defer wg.Done()
		counter.Add(1)
	}()
}

wg.Wait()

fmt.Println(counter.Load())

Для простых счётчиков atomic часто удобнее Mutex.

38. Правильная работа с errors.Is

var ErrNotFound = errors.New("not found")

func findUser() error {
	return fmt.Errorf("find user: %w", ErrNotFound)
}

func main() {
	err := findUser()

	if errors.Is(err, ErrNotFound) {
		fmt.Println("user not found")
	}
}

Не сравнивайте wrapped errors через обычный ==.

39. errors.As для собственных ошибок

type ValidationError struct {
	Field string
}

func (e *ValidationError) Error() string {
	return "invalid field: " + e.Field
}

func main() {
	err := error(&ValidationError{Field: "email"})

	var target *ValidationError

	if errors.As(err, &target) {
		fmt.Println(target.Field)
	}
}

40. Потоковое чтение большого JSON

decoder := json.NewDecoder(os.Stdin)

for decoder.More() {
	var item struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}

	if err := decoder.Decode(&item); err != nil {
		log.Fatal(err)
	}

	fmt.Println(item)
}

Для больших данных лучше использовать streaming, а не загружать всё в память.

41. Context отменяет всю цепочку задач

ctx, cancel := context.WithCancel(context.Background())

go func() {
	time.Sleep(time.Second)
	cancel()
}()

select {
case <-time.After(5 * time.Second):
	fmt.Println("finished")
case <-ctx.Done():
	fmt.Println("cancelled")
}

context.Context должен передавать отмену через всю цепочку операций.

42. Generic Methods в Go 1.27

Go 1.27 добавил generic methods.

type Converter struct{}

func (Converter) Convert[T ~int | ~int64](v T) T {
	return v
}

func main() {
	c := Converter{}

	fmt.Println(c.Convert(42))
	fmt.Println(c.Convert(int64(100)))
}

Теперь type parameters можно объявлять непосредственно у метода.

43. Собственный iterator

func Numbers(n int) iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := range n {
			if !yield(i) {
				return
			}
		}
	}
}

func main() {
	for n := range Numbers(5) {
		fmt.Println(n)
	}
}

Результат:

0
1
2
3
4

44. Сортировка структур через slices.SortFunc

users := []User{
	{Name: "Bob", Age: 31},
	{Name: "Alice", Age: 24},
}

slices.SortFunc(users, func(a, b User) int {
	return cmp.Compare(a.Age, b.Age)
})

Не обязательно писать собственную реализацию сортировки.

45. Retry с exponential backoff

func retry(ctx context.Context, fn func() error) error {
	delay := 100 * time.Millisecond

	for attempt := range 5 {
		if err := fn(); err == nil {
			return nil
		}

		if attempt == 4 {
			break
		}

		select {
		case <-time.After(delay):
			delay *= 2

		case <-ctx.Done():
			return ctx.Err()
		}
	}

	return errors.New("all attempts failed")
}

Retry должен иметь лимит попыток, backoff и возможность отмены через context.

46. Worker Pool с результатами

func worker(
	jobs <-chan int,
	results chan<- int,
) {
	for n := range jobs {
		results <- n * n
	}
}

Worker Pool полезен, когда нужно контролировать количество одновременно выполняемых задач.

47. Безопасная запись файла

tmp := "config.json.tmp"

if err := os.WriteFile(tmp, data, 0644); err != nil {
	log.Fatal(err)
}

if err := os.Rename(tmp, "config.json"); err != nil {
	log.Fatal(err)
}

Сначала записываем временный файл, затем заменяем основной.

48. Проверка нескольких условий через switch

switch {
case age < 18:
	fmt.Println("minor")

case age < 65:
	fmt.Println("adult")

default:
	fmt.Println("senior")
}

В Go switch без выражения часто читается лучше длинной цепочки if/else.

49. Безопасное преобразование типа

value := any("hello")

text, ok := value.(string)
if !ok {
	fmt.Println("not a string")
	return
}

fmt.Println(text)

Не используйте type assertion без ok, если тип значения заранее неизвестен.

50. Проверка программы на race condition

Запуск:

go test -race ./...

Race Detector помогает находить ситуации, когда несколько goroutines одновременно небезопасно обращаются к общей памяти.

Для concurrent Go-кода это одна из самых полезных проверок перед production.


Лучшие практики Go 1.27

  • используйте context.Context для timeout и cancellation;
  • ограничивайте количество goroutines;
  • оборачивайте ошибки через %w;
  • проверяйте ошибки через errors.Is и errors.As;
  • используйте log/slog для структурированных логов;
  • ставьте timeout на HTTP-клиенты и серверы;
  • реализуйте graceful shutdown;
  • используйте стандартную библиотеку, когда сторонняя зависимость не нужна;
  • регулярно запускайте go test ./...;
  • для concurrent-кода запускайте go test -race ./...;
  • форматируйте код через gofmt.

Что изучать дальше

После базового Go переходите к:

HTTP → PostgreSQL → concurrency → context → testing → profiling → observability → distributed systems.

Именно эти темы чаще всего встречаются в реальных backend-проектах на Golang.

Поделиться:

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *