Это многостраничный печатный вид этого раздела. Нажмите что бы печатать.
Памятка по примерам GO
- 1: таблица с основными форматами Printf в Go
- 2: Closure
- 3: Создание модуля в GO
- 4: Создание тестов в GO
- 5: Работа с Workspace в Go: Полное руководство
- 6: массивы (arrays) и срезы (slices)
- 7: Назначение пакетов net и net/http
1 - таблица с основными форматами Printf в Go
Шпаргалка по форматным строкам fmt.Printf
Формат | Тип данных | Пример вывода | Описание | С # (если применимо) |
---|---|---|---|---|
Общие | ||||
%v |
Любой | {John 25} |
Универсальный формат | %#v → main.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 |
Двоичное | %#b → 0b101010 (редко) |
Символы | ||||
%c |
rune |
A |
Символ по коду | — |
%U |
rune |
U+0041 |
Код Unicode | %#U → U+0041 'A' |
%q |
rune /string |
'A' / "A" |
Кавычки вокруг значения | %#q → 'A' (для рун) |
Строки | ||||
%s |
string |
Hello |
Чистая строка | — |
%x |
string /[]byte |
48656c6c6f |
Шестнадцатеричный дамп | %#x → 0x48656c6c6f |
Плавающие | ||||
%f |
float64 |
3.141593 |
Обычный формат | — |
%g |
float64 |
3.14159 |
Автовыбор (%f или %e ) |
%#g → Без изменений |
%e |
float64 |
3.141593e+00 |
Экспоненциальный формат | — |
Указатели | ||||
%p |
Указатель | 0xc0000180a8 |
Адрес памяти | %#p → c0000180a8 (без 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
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
) независимы.
Итог: как работает замыкание
- Запоминает окружение (переменные, например
i
), даже после выхода из внешней функции. - Сохраняет состояние между вызовами (
i
увеличивается). - Каждое новое замыкание создаёт свой экземпляр переменных.
Это полезно для:
- Генераторов (как в примере).
- 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
-
Именование файлов:
- Тестовые файлы должны заканчиваться на
_test.go
. - Пример:
main.go
→main_test.go
.
- Тестовые файлы должны заканчиваться на
-
Именование функций:
- Тестовые функции должны начинаться с
Test
(для юнит-тестов) илиBenchmark
(для бенчмарков). - Пример:
func TestAdd(t *testing.T) { ... } func BenchmarkAdd(b *testing.B) { ... }
- Тестовые функции должны начинаться с
-
Параметры тестовых функций:
- Юнит-тесты принимают
*testing.T
. - Бенчмарки принимают
*testing.B
. - Примеры тестов (
t.Error
,t.Fail
,t.Run
для подтестов).
- Юнит-тесты принимают
-
Табличные тесты (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) } } }
-
Покрытие кода (Coverage):
- Замер покрытия:
go test -cover go test -coverprofile=coverage.out && go tool cover -html=coverage.out
- Замер покрытия:
-
Моки и тестовые данные:
- Используйте интерфейсы для замены зависимостей (например,
sqlmock
для тестирования SQL-запросов).
- Используйте интерфейсы для замены зависимостей (например,
Автоматизация тестов в Emacs для Go
-
go-mode:
- Основной режим для работы с Go в Emacs.
- Установка:
(use-package go-mode :ensure t)
-
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
— запуск бенчмарка.
- Пакет для запуска тестов (встроен в
-
flycheck / golangci-lint:
- Проверка кода и тестов в реальном времени.
-
dap-mode (Debug Adapter Protocol):
- Отладка тестов (аналог VS Code).
Требования к тестовым файлам
-
Расположение:
- Тестовые файлы должны быть в том же пакете (
package math
→package math_test
для black-box тестирования).
- Тестовые файлы должны быть в том же пакете (
-
Доступ к символам:
- Если тестируемые функции/переменные должны быть экспортированы (
UpperCase
).
- Если тестируемые функции/переменные должны быть экспортированы (
-
Использование
testing
:- Обязателен импорт
"testing"
.
- Обязателен импорт
-
Тестовые данные:
- Можно использовать
testdata/
для дополнительных файлов (например, JSON-фикстур).
- Можно использовать
Основные команды для тестов
-
Запуск всех тестов:
go test ./...
-
Запуск конкретного теста:
go test -run TestAdd
-
Покрытие кода:
go test -coverprofile=cover.out && go tool cover -html=cover.out
-
Бенчмарки:
go test -bench .
-
Пропуск кеширования:
go test -count=1
-
Вербозный вывод:
go test -v
-
Параллельные тесты:
func TestParallel(t *testing.T) { t.Parallel() // ... }
Бенчмарки (Benchmarks) в Go
Бенчмарки в Go — это тесты производительности, которые измеряют скорость выполнения кода (время на операцию, аллокации памяти и т. д.). Они помогают находить узкие места в программе и сравнивать разные реализации алгоритмов.
Как работают бенчмарки?
-
Именование функции:
- Должно начинаться с
Benchmark
. - Принимает
*testing.B
(аналог*testing.T
в обычных тестах). - Пример:
func BenchmarkSum(b *testing.B) { for i := 0; i < b.N; i++ { Sum(1, 2) // Тестируемая функция } }
- Должно начинаться с
-
Цикл
b.N
:- Go автоматически подбирает
N
(количество итераций), чтобы получить стабильные результаты. - Чем быстрее функция, тем больше итераций будет сделано.
- Go автоматически подбирает
-
Запуск бенчмарка:
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: Полное руководство
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
- Не коммитьте
go.work
в общий репозиторий (если это не шаблон для команды). - Используйте Workspace для локальной разработки, а
go.mod
— для продакшена. go work sync
помогает при обновлении зависимостей.
6 - массивы (arrays) и срезы (slices)
🔹 Массив 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
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-соединениями.