1 - таблица с основными форматами Printf в Go

Шпаргалка по форматным строкам fmt.Printf

Шпаргалка по форматным строкам fmt.Printf

Формат Тип данных Пример вывода Описание С # (если применимо)
Общие
%v Любой {John 25} Универсальный формат %#vmain.Person{Name:"John"}
%+v Структуры {Name:John Age:25} С именами полей
%T Любой string Тип переменной
%% % Вывод знака %
Целые числа
%d int 42 Десятичное число
%#x int 0x2a Шестнадцатеричное с 0x Без #: 2a
%#o int 052 Восьмеричное с 0 Без #: 52
%b int 101010 Двоичное %#b0b101010 (редко)
Символы
%c rune A Символ по коду
%U rune U+0041 Код Unicode %#UU+0041 'A'
%q rune/string 'A' / "A" Кавычки вокруг значения %#q'A' (для рун)
Строки
%s string Hello Чистая строка
%x string/[]byte 48656c6c6f Шестнадцатеричный дамп %#x0x48656c6c6f
Плавающие
%f float64 3.141593 Обычный формат
%g float64 3.14159 Автовыбор (%f или %e) %#g → Без изменений
%e float64 3.141593e+00 Экспоненциальный формат
Указатели
%p Указатель 0xc0000180a8 Адрес памяти %#pc0000180a8 (без 0x)
Ширина/Точность
%5d int 42 Фиксированная ширина (5 символов)
%05d int 00042 Дополнение нулями
%.2f float64 3.14 Ограничение знаков после точки

Примеры использования

fmt.Printf("|%#v|%5d|%05d|\n", struct{ X int }{1}, 42, 42) 
// |struct { X int }{X:1}|   42|00042|

fmt.Printf("|%#x|%#U|\n", 42, 'A') 
// |0x2a|U+0041 'A'|

⚠️ Особенности

  • Флаг # работает только с %v, %x, %X, %o, %U, %p, %q.
  • Для %g, %s, %d и других — игнорируется.
  • %#q для строк оставляет двойные кавычки, для рун — одинарные.

2 - Closure

В этом примере на Go демонстрируется замыкание (closure) — функция, которая запоминает окружение, в котором она была создана.

1. Функция intSeq() создаёт замыкание

func intSeq() func() int {
    i := 0              // Локальная переменная внутри intSeq
    return func() int { // Возвращаемая анонимная функция
        i++            // Замыкание "видит" переменную i
        return i
    }
}
  • i := 0 — это локальная переменная внутри intSeq.
  • Возвращаемая функция func() int “замыкается” вокруг i, то есть запоминает её и может изменять.

2. Создаём экземпляр замыкания

nextInt := intSeq() // Переменная nextInt теперь — функция
  • При вызове intSeq():
    • Создаётся переменная i = 0.
    • Возвращается функция-замыкание, которая удерживает ссылку на i.

3. Вызов nextInt() изменяет состояние i

p(nextInt()) // Выведет: === 1 ===
p(nextInt()) // Выведет: === 2 ===
p(nextInt()) // Выведет: === 3 ===
  • Каждый вызов nextInt():
    • Увеличивает i на 1 (i++).
    • Возвращает новое значение.
  • i сохраняется между вызовами — это и есть “запомненное окружение”.

4. Новое замыкание — новый экземпляр i

newInts := intSeq() // Создаётся новый экземпляр замыкания
p(newInts())        // Выведет: === 1 === (не 4!)
  • newInts — это новое замыкание с собственной переменной i = 0.
  • Оно не связано с nextInt — их состояния (i) независимы.

Итог: как работает замыкание

  1. Запоминает окружение (переменные, например i), даже после выхода из внешней функции.
  2. Сохраняет состояние между вызовами (i увеличивается).
  3. Каждое новое замыкание создаёт свой экземпляр переменных.

Это полезно для:

  • Генераторов (как в примере).
  • Callback-функций с сохранением состояния.
  • Инкапсуляции данных (аналог private переменных в ООП).

Пример в JavaScript (для сравнения):

function intSeq() {
    let i = 0;
    return () => ++i;
}

Работает аналогично!

3 - Создание модуля в GO

Создание и подключение новых модулей локально

Создание проекта

Создать директорию с проектом

mkdir greetings
cd greetings

Инициализация

$ go mod init example.com/greetings
go: creating new go.mod: module example.com/greetings

Команда init создает файл go.mod с основными параметрами модуля:

module example.com/hello

go 1.24.3

Создать файл GO

В редакторе создаю файл: напрмер greetings.go

package greetings

import "fmt"

// Hello returns a greeting for the named person.
func Hello(name string) string {
    // Return a greeting that embeds the name in a message.
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}

важно указать в package название пакета для последующей ссылки на него

Создание основного модуля MAIN

Если проект многомодульный, то повторяем из корня проекта создание нового модуля:

cd ..
mkdir hello
cd hello

Выполняем инициализацию

$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello

Создаем файл основного проекта

имя файла должно быть таким, каким потом будет исполняемый файл, но в файле обязательно должен быть: package main

package main

import (
    "fmt"

    "example.com/greetings"
)

func main() {
    // Get a greeting message and print it.
    message := greetings.Hello("Gladys")
    fmt.Println(message)
}

Корректируем путь на зависимый модуль

go mod edit -replace example.com/greetings=../greetings

изменит в файле go.mod на фактический путь зависимого модуля

Получаем файл:

module example.com/hello

go 1.24.3

replace example.com/greetings => ../greetings

Выполняем go mod tidy

$ go mod tidy
go: found example.com/greetings in example.com/greetings v0.0.0-00010101000000-000000000000

для фактической связки только нужных пакетов и проверки go.mod

получаем go.mod

module example.com/hello

go 1.24.3

replace example.com/greetings => ../greetings

require example.com/greetings v0.0.0-00010101000000-000000000000

Запуск проекта

$ go run .
Hi, Gladys. Welcome!

4 - Создание тестов в GO

Создание и запуск тестов в GO + Emacs

Основные правила написания тестов в Go

  1. Именование файлов:

    • Тестовые файлы должны заканчиваться на _test.go.
    • Пример: main.gomain_test.go.
  2. Именование функций:

    • Тестовые функции должны начинаться с Test (для юнит-тестов) или Benchmark (для бенчмарков).
    • Пример:
      func TestAdd(t *testing.T) { ... }
      func BenchmarkAdd(b *testing.B) { ... }
      
  3. Параметры тестовых функций:

    • Юнит-тесты принимают *testing.T.
    • Бенчмарки принимают *testing.B.
    • Примеры тестов (t.Error, t.Fail, t.Run для подтестов).
  4. Табличные тесты (Table-Driven Tests):

    • Рекомендуемый подход для покрытия разных сценариев.
    • Пример:
      func TestAdd(t *testing.T) {
          cases := []struct {
              a, b, expected int
          }{
              {1, 2, 3},
              {0, 0, 0},
              {-1, 1, 0},
          }
          for _, tc := range cases {
              result := Add(tc.a, tc.b)
              if result != tc.expected {
                  t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, result, tc.expected)
              }
          }
      }
      
  5. Покрытие кода (Coverage):

    • Замер покрытия:
      go test -cover
      go test -coverprofile=coverage.out && go tool cover -html=coverage.out
      
  6. Моки и тестовые данные:

    • Используйте интерфейсы для замены зависимостей (например, sqlmock для тестирования SQL-запросов).

Автоматизация тестов в Emacs для Go

  1. go-mode:

    • Основной режим для работы с Go в Emacs.
    • Установка:
      (use-package go-mode
        :ensure t)
      
  2. go-test.el:

    • Пакет для запуска тестов (встроен в go-mode).
    • Команды:
      • M-x go-test-current-test — запуск текущего теста.
      • M-x go-test-current-file — тестирование текущего файла.
      • M-x go-test-current-project — тесты всего проекта.
      • M-x go-test-current-benchmark — запуск бенчмарка.
  3. flycheck / golangci-lint:

    • Проверка кода и тестов в реальном времени.
  4. dap-mode (Debug Adapter Protocol):

    • Отладка тестов (аналог VS Code).

Требования к тестовым файлам

  1. Расположение:

    • Тестовые файлы должны быть в том же пакете (package mathpackage math_test для black-box тестирования).
  2. Доступ к символам:

    • Если тестируемые функции/переменные должны быть экспортированы (UpperCase).
  3. Использование testing:

    • Обязателен импорт "testing".
  4. Тестовые данные:

    • Можно использовать testdata/ для дополнительных файлов (например, JSON-фикстур).

Основные команды для тестов

  1. Запуск всех тестов:

    go test ./...
    
  2. Запуск конкретного теста:

    go test -run TestAdd
    
  3. Покрытие кода:

    go test -coverprofile=cover.out && go tool cover -html=cover.out
    
  4. Бенчмарки:

    go test -bench .
    
  5. Пропуск кеширования:

    go test -count=1
    
  6. Вербозный вывод:

    go test -v
    
  7. Параллельные тесты:

    func TestParallel(t *testing.T) {
        t.Parallel()
        // ...
    }
    

Бенчмарки (Benchmarks) в Go

Бенчмарки в Go — это тесты производительности, которые измеряют скорость выполнения кода (время на операцию, аллокации памяти и т. д.). Они помогают находить узкие места в программе и сравнивать разные реализации алгоритмов.


Как работают бенчмарки?

  1. Именование функции:

    • Должно начинаться с Benchmark.
    • Принимает *testing.B (аналог *testing.T в обычных тестах).
    • Пример:
      func BenchmarkSum(b *testing.B) {
          for i := 0; i < b.N; i++ {
              Sum(1, 2) // Тестируемая функция
          }
      }
      
  2. Цикл b.N:

    • Go автоматически подбирает N (количество итераций), чтобы получить стабильные результаты.
    • Чем быстрее функция, тем больше итераций будет сделано.
  3. Запуск бенчмарка:

    go test -bench .
    
    • Флаг -bench принимает регулярное выражение для выбора бенчмарков (. — все).
    • Пример вывода:
      BenchmarkSum-8   	1000000000	         0.265 ns/op
      
      • BenchmarkSum-8 — название теста (-8 — количество CPU).
      • 1000000000 — количество итераций (b.N).
      • 0.265 ns/op — время на одну операцию (наносекунды).

Полезные флаги для бенчмарков

Флаг Описание Пример
-benchmem Показывать аллокации памяти go test -bench . -benchmem
-benchtime Установить время выполнения go test -bench . -benchtime=5s
-count Количество прогонов go test -bench . -count=3
-cpu Тестировать на разном числе CPU go test -bench . -cpu=1,2,4

Пример бенчмарка

Допустим, есть функция сложения:

// sum.go
package math

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

Тест и бенчмарк:

// sum_test.go
package math

import "testing"

func TestSum(t *testing.T) {
    if Sum(1, 2) != 3 {
        t.Error("Ожидается 3")
    }
}

func BenchmarkSum(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Sum(1, 2)
    }
}

Запуск:

go test -bench . -benchmem

Вывод:

BenchmarkSum-8   	1000000000	         0.265 ns/op	       0 B/op	       0 allocs/op
  • 0.265 ns/op — 0.265 наносекунд на операцию.
  • 0 B/op — 0 байт выделено за итерацию.
  • 0 allocs/op — 0 аллокаций памяти.

Сравнение производительности

Бенчмарки полезны для сравнения разных реализаций. Например:

func BenchmarkSlow(b *testing.B) {
    for i := 0; i < b.N; i++ {
        SlowFunction()
    }
}

func BenchmarkFast(b *testing.B) {
    for i := 0; i < b.N; i++ {
        FastFunction()
    }
}

Запуск:

go test -bench . -benchmem

Покажет, какая функция работает быстрее и использует меньше памяти.


Бенчмарки в Go — это тесты производительности.

  • Запускаются через go test -bench.
  • Показывают время на операцию (ns/op), аллокации памяти (B/op, allocs/op).

Если нужно углублённо анализировать производительность, можно использовать pprof:

go test -bench . -cpuprofile=cpu.out
go tool pprof cpu.out

5 - Работа с Workspace в Go: Полное руководство

Workspace (рабочее пространство) в Go — это мощный инструмент для управления несколькими модулями в одном проекте. Он появился в Go 1.18 и упрощает разработку, когда у вас есть взаимосвязанные модули.

1. Что такое Workspace и зачем он нужен?

Workspace позволяет:

  • Работать с несколькими модулями одновременно без необходимости публиковать их в GOPATH или go.mod replace.
  • Упрощать разработку в монолитных репозиториях (monorepo).
  • Управлять зависимостями между локальными модулями без replace в go.mod.

2. Создание Workspace

Шаг 1: Инициализация Workspace

go work init

Создается файл go.work в корне проекта.

Шаг 2: Добавление модулей в Workspace

go work use ./module1 ./module2

Теперь go.work выглядит так:

go 1.21

use (
    ./module1
    ./module2
)

3. Структура Workspace

Пример структуры проекта:

myproject/
├── go.work          # Workspace файл
├── module1/         # Первый модуль
│   ├── go.mod       # go.mod модуля 1
│   └── main.go
└── module2/         # Второй модуль
    ├── go.mod       # go.mod модуля 2
    └── utils.go

4. Основные команды для работы с Workspace

Команда Описание
go work init Создает go.work
go work use [dir] Добавляет модуль в Workspace
go work edit Редактирует go.work вручную
go work sync Синхронизирует зависимости

5. Пример: Разработка двух связанных модулей

Модуль 1 (module1/main.go)

package main

import (
    "fmt"
    "github.com/user/module2/utils"  // Локальный модуль
)

func main() {
    fmt.Println(utils.Add(1, 2)) // Используем функцию из module2
}

Модуль 2 (module2/utils.go)

package utils

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

Файл go.work

go 1.21

use (
    ./module1
    ./module2
)

Теперь module1 может использовать module2 без replace в go.mod!


6. Преимущества Workspace перед replace

Workspace replace в go.mod
Глобальная настройка для всех модулей Требует правки в каждом go.mod
Не нужно коммитить изменения в go.mod replace должен быть в go.mod
Удобен для монолитных репозиториев Подходит для временных изменений

7. Отключение Workspace

Если нужно временно отключить Workspace:

go env -w GOWORK=off

Вернуть обратно:

go env -w GOWORK=$(pwd)/go.work

8. Советы по работе с Workspace

  1. Не коммитьте go.work в общий репозиторий (если это не шаблон для команды).
  2. Используйте Workspace для локальной разработки, а go.mod — для продакшена.
  3. go work sync помогает при обновлении зависимостей.

6 - массивы (arrays) и срезы (slices)

В Go массивы (arrays) и срезы (slices) — это разные структуры данных, а len и cap — функции, которые работают со срезами (и массивами, но для массивов они всегда равны).

🔹 Массив vs Срез

Характеристика Массив ([n]T) Срез ([]T)
Размер Фиксированный (известен на этапе компиляции) Динамический (может расти и уменьшаться)
Передача в функцию По значению (копируется) По ссылке (указатель на массив)
Инициализация arr := [3]int{1, 2, 3} sl := []int{1, 2, 3} или make([]int, len, cap)
Гибкость Неизменяемый размер Можно изменять с помощью append

Примеры:

// Массив (фиксированная длина)
arr := [3]int{1, 2, 3}  // len=3, cap=3 (для массива len == cap)

// Срез (динамическая длина)
sl := []int{1, 2, 3}    // len=3, cap=3
sl = append(sl, 4)      // len=4, cap=6 (удвоился)

🔹 len vs cap

Обе функции применяются к срезам (и массивам), но имеют разный смысл:

  • len(s) — текущее количество элементов в срезе.
  • cap(s) — максимальное количество элементов, которое может вместить базовый массив без переаллокации.

Пример:

sl := make([]int, 2, 5)  // len=2, cap=5
fmt.Println(len(sl))     // 2 (элементы: [0, 0])
fmt.Println(cap(sl))     // 5 (может вместить ещё 3 элемента)

sl = append(sl, 1, 2, 3) // len=5, cap=5
sl = append(sl, 4)       // len=6, cap=10 (удваивается при переполнении)

Вывод:

  • Массивы — статические, срезы — динамические.
  • len — текущая длина, cap — ёмкость внутреннего массива.
  • append может увеличить len, а если места нет (len == cap), то Go выделит новый массив (обычно с удвоенной cap).

7 - Назначение пакетов net и net/http

В языке Go пакеты net и net/http тесно связаны, но имеют разные уровни абстракции и предназначены для разных задач.

1. Пакет net

Для чего используется:

  • Предоставляет низкоуровневые сетевые примитивы для работы с различными сетевыми протоколами (TCP, UDP, IP, Unix-сокеты, DNS и др.).
  • Позволяет создавать серверы и клиенты на уровне сокетов.
  • Используется, когда нужен полный контроль над сетевым взаимодействием.

Основные возможности:

  • Работа с TCP (net.TCPConn, net.ListenTCP)
  • Работа с UDP (net.UDPConn)
  • Разрешение DNS (net.LookupHost, net.ResolveIP)
  • Управление сетевыми интерфейсами (net.Interface)

Пример TCP-сервера:

package main

import (
	"net"
)

func main() {
	ln, _ := net.Listen("tcp", ":8080")
	for {
		conn, _ := ln.Accept()
		go func(c net.Conn) {
			c.Write([]byte("Hello from TCP server\n"))
			c.Close()
		}(conn)
	}
}

2. Пакет net/http

Для чего используется:

  • Реализует высокоуровневый HTTP-стек (клиент и сервер).
  • Подходит для создания веб-серверов, REST API, работы с HTTP-запросами.
  • Включает роутинг, middleware, куки, сессии и другие HTTP-специфичные функции.

Основные возможности:

  • HTTP-сервер (http.Server, http.HandleFunc)
  • HTTP-клиент (http.Client, http.Get, http.Post)
  • Работа с заголовками, куками, формами
  • Поддержка HTTPS (через http.ListenAndServeTLS)

Пример HTTP-сервера:

package main

import (
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello from HTTP server"))
	})
	http.ListenAndServe(":8080", nil)
}

Ключевые отличия

Критерий net net/http
Уровень Низкоуровневый (TCP, UDP, IP) Высокоуровневый (HTTP/HTTPS)
Использование Сокеты, DNS, RAW-соединения Веб-серверы, API, HTTP-клиенты
Сложность Требует больше кода Упрощает HTTP-взаимодействие
Примеры Чат-сервер, DNS-резолвер Веб-сайт, RESTful API

Когда что использовать?

  • net – если нужен контроль над сетевым уровнем (например, пишете свой протокол поверх TCP/UDP).
  • net/http – для стандартных HTTP-задач (веб-сервисы, API, скачивание данных).

Иногда их комбинируют: например, net/http использует net под капотом для работы с TCP-соединениями.