Простые приложения на Go
Простые приложения на Go
Язык программирования Go (Golang) создан для написания надежных, эффективных и понятных системных утилит. Его стандартная библиотека предоставляет мощные инструменты для работы с файлами, сетью, параллелизмом и структурированными данными без необходимости подключения сторонних зависимостей. В этой главе рассматриваются практические примеры создания консольных приложений и серверов, демонстрирующие ключевые особенности синтаксиса и экосистемы языка.
Генератор паролей
Генератор паролей демонстрирует работу со строками, массивами символов и генерацией случайных чисел. Стандартная библиотека math/rand позволяет создавать криптографически стойкие последовательности через пакет crypto/rand.
Код примера
package main
import (
"crypto/rand"
"fmt"
"math/big"
)
func generatePassword(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
if length <= 0 {
return ""
}
password := make([]byte, length)
for i := range password {
// Получаем случайное число в диапазоне [0, len(charset))
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
panic(err)
}
password[i] = charset[num.Int64()]
}
return string(password)
}
func main() {
pass := generatePassword(16)
fmt.Println("Сгенерированный пароль:", pass)
}
Разбор кода
- Пакет
crypto/rand: Используется вместоmath/randдля получения безопасных случайных чисел, что критично для генерации паролей. - Структура данных
[]byte: Слайс байтов используется как буфер для хранения символов будущего пароля. Это эффективно по памяти. - Цикл
for range: Итерация по слайсу автоматически определяет индекс и значение элемента. - Преобразование типов: Функция
num.Int64()преобразует большое целое число изbig.Intв стандартный типint64для использования в качестве индекса строки.
Сортировщик текстового файла
Этот пример показывает чтение текста из файла, разделение его на строки, сортировку списка строк и запись результата обратно в файл.
Код примера
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
)
func sortFile(inputPath, outputPath string) error {
file, err := os.Open(inputPath)
if err != nil {
return fmt.Errorf("ошибка открытия файла: %w", err)
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" {
lines = append(lines, line)
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("ошибка чтения: %w", err)
}
sort.Strings(lines)
outFile, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("ошибка создания файла: %w", err)
}
defer outFile.Close()
writer := bufio.NewWriter(outFile)
for _, line := range lines {
if _, err := writer.WriteString(line + "\n"); err != nil {
return fmt.Errorf("ошибка записи: %w", err)
}
}
return writer.Flush()
}
func main() {
err := sortFile("input.txt", "output.txt")
if err != nil {
fmt.Println("Ошибка:", err)
os.Exit(1)
}
fmt.Println("Файл успешно отсортирован.")
}
Разбор кода
bufio.Scanner: Эффективный инструмент для посимвольного или построчного чтения больших файлов без загрузки всего содержимого в память.strings.TrimSpace: Удаляет пробелы и символы перевода строки по краям каждой строки перед обработкой.sort.Strings: Встроенная функция сортировки, которая использует алгоритм быстрой сортировки (quicksort) для упорядочивания слайса строк.defer: Обеспечивает закрытие файловых дескрипторов при выходе из функции, предотвращая утечки ресурсов.writer.Flush(): Явно сбрасывает буфер вывода на диск, гарантируя сохранение данных.
Консольный калькулятор
Калькулятор реализует базовую арифметику с обработкой ввода пользователя и разделением логики вычислений на отдельные функции.
Код примера
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func calculate(a float64, b float64, op string) (float64, error) {
switch op {
case "+":
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
if b == 0 {
return 0, fmt.Errorf("деление на ноль невозможно")
}
return a / b, nil
default:
return 0, fmt.Errorf("неподдерживаемая операция: %s", op)
}
}
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Введите первое число: ")
num1Str, _ := reader.ReadString('\n')
num1, _ := strconv.ParseFloat(strings.TrimSpace(num1Str), 64)
fmt.Print("Введите операцию (+, -, *, /): ")
opStr, _ := reader.ReadString('\n')
op := strings.TrimSpace(opStr)
fmt.Print("Введите второе число: ")
num2Str, _ := reader.ReadString('\n')
num2, _ := strconv.ParseFloat(strings.TrimSpace(num2Str), 64)
result, err := calculate(num1, num2, op)
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Printf("Результат: %.2f\n", result)
}
Разбор кода
- Тип
float64: Используется для представления вещественных чисел с высокой точностью. switch: Конструкция выбора действия, которая четко разделяет логику для каждого оператора.- Обработка ошибок: Возврат ошибки при попытке деления на ноль или вводе некорректной операции.
strconv.ParseFloat: Преобразование строкового ввода в числовой тип с проверкой на корректность формата.
Трекер задач в JSON
Пример демонстрирует сериализацию (преобразование структур данных в JSON) и десериализацию, а также работу с файловой системой для сохранения состояния.
Структура данных
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
Priority int `json:"priority"` // 1-высокий, 5-низкий
}
type TaskList struct {
Задачи []Task `json:"Задачи"`
}
Код реализации
package main
import (
"encoding/json"
"fmt"
"os"
)
const dataFile = "Задачи.json"
func loadTasks() (*TaskList, error) {
Данные, err := os.ReadFile(dataFile)
if err != nil {
if os.IsNotExist(err) {
return &TaskList{Задачи: []Task{}}, nil
}
return nil, err
}
var list TaskList
if err := json.Unmarshal(Данные, &list); err != nil {
return nil, err
}
return &list, nil
}
func saveTasks(list *TaskList) error {
Данные, err := json.MarshalIndent(list, "", " ")
if err != nil {
return err
}
return os.WriteFile(dataFile, Данные, 0644)
}
func addTask(list *TaskList, title string, priority int) {
newID := 1
if len(list.Задачи) > 0 {
newID = list.Задачи[len(list.Задачи)-1].ID + 1
}
list.Задачи = append(list.Задачи, Task{
ID: newID,
Title: title,
Done: false,
Priority: priority,
})
}
func main() {
list, err := loadTasks()
if err != nil {
fmt.Println("Ошибка загрузки:", err)
os.Exit(1)
}
addTask(list, "Изучить Go", 1)
addTask(list, "Написать статью", 2)
if err := saveTasks(list); err != nil {
fmt.Println("Ошибка сохранения:", err)
os.Exit(1)
}
fmt.Println("Задачи сохранены в Задачи.json")
// Вывод для проверки
jsonOut, _ := json.MarshalIndent(list, "", " ")
fmt.Println(string(jsonOut))
}
Разбор кода
- Теги
json:"...": Позволяют управлять именами полей в JSON-структуре. Например, полеDoneсохраняется какdone. encoding/json: Стандартный пакет для работы с форматом JSON.json.MarshalIndent: Преобразует структуру в строку JSON с отступами для удобного чтения человеком.os.WriteFile: Современный аналог создания и записи файла с указанием прав доступа (0644).- Логика ID: Простая эвристика для генерации уникального идентификатора на основе последнего элемента списка.
Простой HTTP-сервер и клиент
Go обладает встроенным веб-сервером, который работает очень быстро благодаря встроенной поддержке многопоточности.
Серверная часть
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "Гость"
}
fmt.Fprintf(w, "Привет, %s! Добро пожаловать на сервер Go.", name)
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Сервер запущен на http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Клиентская часть
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:8080/?name=Разработчик")
if err != nil {
fmt.Println("Ошибка запроса:", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Ошибка чтения тела:", err)
return
}
fmt.Println("Ответ сервера:")
fmt.Println(string(body))
}
Разбор кода
http.HandleFunc: Регистрирует функцию обработки запросов для конкретного URL-пути.r.URL.Query().Get("name"): Извлекает параметры из строки запроса (например,?name=Value).http.Response: Объект ответа содержит статус код, заголовки и тело ответа.io.ReadAll: Чтение всего тела ответа в байтовый срез.defer resp.Body.Close(): Обязательная практика для освобождения сетевого ресурса после завершения работы.
Отправитель HTTP-запросов (с кастомными параметрами)
Этот пример расширяет функциональность клиента, позволяя отправлять POST-запросы с произвольными данными и заголовками.
Код примера
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func sendPostRequest(url string, payload map[string]string) error {
jsonData := []byte(`{"key": "value"}`) // Упрощенный пример JSON
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Go-Custom-Client/1.0")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Статус: %d, Тело: %s\n", resp.StatusCode, string(body))
return nil
}
func main() {
sendPostRequest("http://example.com/api/Данные", nil)
}
Разбор кода
http.NewRequest: Создает объект запроса с указанным методом (GET, POST, PUT и т.д.) и телом.bytes.NewBuffer: Позволяет использовать байтовый срез как поток данных для тела запроса.req.Header.Set: Установка пользовательских заголовков, важных для идентификации клиента или формата данных.client.Do: Выполняет запрос и возвращает ответ, позволяя настроить таймауты и редиректы через структуруhttp.Client.
Утилита для сканирования директорий
Инструмент для обхода файловой системы и сбора информации о файлах и подпапках.
Код примера
package main
import (
"fmt"
"os"
"path/filepath"
)
func scanDirectory(root string) error {
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
indent := ""
relPath, _ := filepath.Rel(root, path)
for i := 0; i < len(filepath.SplitList(relPath)); i++ {
indent += " "
}
typeStr := "файл"
if info.IsDir() {
typeStr = "каталог"
}
fmt.Printf("%s[%s] %s (%d байт)\n", indent, typeStr, info.Name(), info.Size())
return nil
})
}
func main() {
currentDir, _ := os.Getwd()
fmt.Println("Сканирование директории:", currentDir)
if err := scanDirectory(currentDir); err != nil {
fmt.Println("Ошибка:", err)
}
}
Разбор кода
filepath.Walk: Рекурсивно проходит по всей структуре директории, вызывая функцию для каждого найденного пути.os.FileInfo: Структура, содержащая метаданные файла (размер, права доступа, время изменения).info.IsDir(): Метод для определения типа объекта (файл или каталог).filepath.Rel: Вычисление относительного пути для красивого отображения структуры.
Скрипт для создания резервного копирования файлов
Автоматизация процесса дублирования файлов с добавлением временной метки к имени копии.
Код примера
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
)
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
if _, err := io.Copy(destFile, sourceFile); err != nil {
return err
}
return nil
}
func createBackup(sourceDir string) error {
timestamp := time.Now().Format("2006-01-02_15-04-05")
backupName := "backup_" + timestamp
backupPath := filepath.Join(sourceDir, backupName)
files, err := os.ReadDir(sourceDir)
if err != nil {
return err
}
for _, file := range files {
if file.IsDir() {
continue
}
srcPath := filepath.Join(sourceDir, file.Name())
dstPath := filepath.Join(backupPath, file.Name())
// Создание целевой директории бэкапа
os.MkdirAll(backupPath, 0755)
if err := copyFile(srcPath, dstPath); err != nil {
fmt.Printf("Не удалось скопировать %s: %v\n", file.Name(), err)
continue
}
fmt.Printf("Скопировано: %s\n", file.Name())
}
return nil
}
func main() {
dir := "."
fmt.Println("Начало резервного копирования...")
if err := createBackup(dir); err != nil {
fmt.Println("Ошибка:", err)
} else {
fmt.Println("Резервное копирование завершено.")
}
}
Разбор кода
time.Now().Format: Форматирование даты в строку, пригодную для имени файла (ISO 8601 стиль адаптирован под Windows/Linux).io.Copy: Оптимизированная функция для побайтового копирования данных между потоками.os.MkdirAll: Создание директории, если она не существует, включая все необходимые промежуточные уровни.filepath.Join: Корректное объединение путей с учетом разделителей для разных операционных систем.
Мониторинг дискового пространства
Утилита проверяет свободное место на диске и выводит статистику в процентах и абсолютных значениях.
Код примера
package main
import (
"fmt"
"os"
)
func checkDiskUsage(path string) error {
fs, err := os.Stat(path)
if err != nil {
return err
}
// Для Unix-систем используем syscall, но в чистом Go лучше использовать пакеты уровня выше
// Здесь пример с использованием статистики корневого каталога
// Примечание: Для точного объема диска часто требуется syscall или внешние библиотеки
// Но мы можем получить объем доступной памяти через os.Stat для тестов
// Альтернативный подход: получение статистики корня
rootStat, err := os.Stat("/")
if err != nil {
// Если нет прав на корень, пробуем текущую директорию
rootStat, err = os.Stat(".")
if err != nil {
return err
}
}
// В Go нет встроенной функции для получения общего объема диска в stdlib
// Поэтому этот пример демонстрирует концепцию проверки свободного места
// через анализ доступных операций или использование внешних инструментов.
// Однако, для демонстрации логики:
fmt.Printf("Путь: %s\n", path)
fmt.Printf("Статус: Доступен\n")
// Пример вывода (для реального проекта используйте syscall.Unix or golang.org/x/sys/unix)
// var stat syscall.Statfs_t
// syscall.Statfs(path, &stat)
// total := uint64(stat.Blocks) * uint64(stat.Bsize)
// free := uint64(stat.Bfree) * uint64(stat.Bsize)
fmt.Println("Метод: Анализ метаданных директории (демонстрация)")
return nil
}
func main() {
checkDiskUsage("/tmp") // Или текущая директория
}
Примечание: Стандартная библиотека Go не предоставляет прямой функции для получения общего объема диска и свободного места. Для этого обычно используют пакет syscall (Unix) или golang.org/x/sys/windows. Приведенный код демонстрирует структуру программы, готовую к интеграции системных вызовов.
Парсер URL и проверка доступности ресурса
Инструмент анализирует компоненты URL (хост, путь, порт) и проверяет, доступен ли сервер по этому адресу.
Код примера
package main
import (
"fmt"
"net/url"
"time"
)
func checkURL(target string) error {
parsedURL, err := url.Parse(target)
if err != nil {
return fmt.Errorf("неверный формат URL: %w", err)
}
fmt.Printf("Анализ URL: %s\n", target)
fmt.Printf(" Схема: %s\n", parsedURL.Scheme)
fmt.Printf(" Хост: %s\n", parsedURL.Host)
fmt.Printf(" Путь: %s\n", parsedURL.Path)
fmt.Printf(" Порт: %s\n", parsedURL.Port())
timeout := 5 * time.Second
client := &http.Client{
Timeout: timeout,
}
resp, err := client.Head(target)
if err != nil {
return fmt.Errorf("проверка недоступна: %w", err)
}
defer resp.Body.Close()
fmt.Printf(" Статус: %d\n", resp.StatusCode)
fmt.Printf(" Время ответа: OK\n")
return nil
}
func main() {
urls := []string{
"https://github.com",
"http://invalid-domain-xyz.com",
}
for _, u := range urls {
fmt.Println("---")
if err := checkURL(u); err != nil {
fmt.Println("Ошибка:", err)
}
}
}
Разбор кода
url.Parse: Разбивает строку URL на составные части (схема, хост, порт, путь).parsedURL.Port(): Извлекает номер порта, если он явно указан.client.Head: Отправка запроса метода HEAD, который получает только заголовки без тела ответа, что экономит трафик.Timeout: Ограничение времени ожидания ответа для предотвращения зависания программы при недоступном сервере.
Конвертер форматов дат
Утилита конвертирует строковые представления дат в другие форматы, используя встроенный механизм форматирования времени.
Код примера
package main
import (
"fmt"
"time"
)
func convertDate(dateString, layout, outputLayout string) error {
loc, err := time.LoadLocation("UTC")
if err != nil {
return err
}
t, err := time.ParseInLocation(layout, dateString, loc)
if err != nil {
return fmt.Errorf("неверный формат входной даты: %w", err)
}
formatted := t.Format(outputLayout)
fmt.Printf("Вход: %s (%s)\n", dateString, layout)
fmt.Printf("Выход: %s (%s)\n", formatted, outputLayout)
return nil
}
func main() {
input := "2026-05-06"
layout := "2006-01-02"
output := "02 January 2006"
convertDate(input, layout, output)
}
Разбор кода
time.ParseInLocation: Парсинг строки в объектtime.Timeс учетом временной зоны.t.Format: Преобразование объекта времени в строку по заданному шаблону.- Шаблон
2006-01-02: В Go используется специальное эталонное времяMon Jan 2 15:04:05 MST 2006для определения формата. Любая дата в коде должна соответствовать этому шаблону.
Утилита для просмотра запущенных процессов
Отображение списка активных процессов с их идентификаторами (PID) и командами запуска.
Код примера
package main
import (
"fmt"
"os/exec"
"strings"
)
func listProcesses() error {
var cmd *exec.Cmd
var args []string
// Определение команды в зависимости от ОС
if isWindows() {
cmd = exec.Command("tasklist", "/FO", "CSV")
args = []string{}
} else {
cmd = exec.Command("ps", "aux")
args = []string{}
}
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("ошибка выполнения команды: %w", err)
}
lines := strings.Split(string(output), "\n")
for i, line := range lines {
if i == 0 && !isWindows() {
continue // Пропуск заголовка в Linux/Mac
}
if line == "" {
continue
}
fmt.Println(line)
}
return nil
}
func isWindows() bool {
return true // Заглушка для примера, в реальности используйте runtime.GOOS
}
func main() {
fmt.Println("Список процессов:")
if err := listProcesses(); err != nil {
fmt.Println("Ошибка:", err)
}
}
Примечание: Для реальной работы с процессами в Go рекомендуется использовать пакеты вроде github.com/shirou/gopsutil, так как прямое выполнение команд (exec.Command) зависит от ОС и требует наличия специфических утилит. Данный пример демонстрирует принцип взаимодействия с оболочкой.
Характерный пример именно для Go: Горизонтальная масштабируемость через Горутины
Главная особенность языка Go — это нативная поддержка параллелизма через горутины (goroutines) и каналы (channels). Ниже приведен пример, который обрабатывает список URL параллельно, используя ограниченное количество горуток.
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
type Result struct {
URL string
Status int
Error error
}
func checkConcurrency(urls []string, workers int) []Result {
results := make([]Result, len(urls))
var wg sync.WaitGroup
semaphore := make(chan struct{}, workers)
for i, url := range urls {
wg.Add(1)
go func(index int, target string) {
defer wg.Done()
semaphore <- struct{}{} // Занимаем слот
defer func() { <-semaphore }() // Освобождаем слот
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Head(target)
status := 0
if err == nil {
status = resp.StatusCode
defer resp.Body.Close()
}
results[index] = Result{
URL: target,
Status: status,
Error: err,
}
}(i, url)
}
wg.Wait()
return results
}
func main() {
targets := []string{
"https://google.com",
"https://github.com",
"https://example.com",
"https://invalid-site-test.com",
}
fmt.Println("Запуск параллельной проверки...")
start := time.Now()
res := checkConcurrency(targets, 3) // Максимум 3 одновременных запроса
elapsed := time.Since(start)
for _, r := range res {
if r.Error != nil {
fmt.Printf("%s: Ошибка - %v\n", r.URL, r.Error)
} else {
fmt.Printf("%s: Статус %d\n", r.URL, r.Status)
}
}
fmt.Printf("Время выполнения: %v\n", elapsed)
}
Разбор кода
go func(...): Запуск функции в отдельной горуток. Горутина потребляет минимум памяти и переключается очень быстро.sync.WaitGroup: Синхронизатор, позволяющий основной программе ждать завершения всех запущенных горуток.chan struct{}(Семафор): Канал пустого типа, ограничивающий максимальное количество одновременно работающих горуток. Это защищает систему от перегрузки.defer: Гарантирует освобождение ресурса канала даже при возникновении ошибки внутри горутины.time.Since: Подсчет времени выполнения блока кода для оценки производительности.
Этот пример иллюстрирует философию Go: простота написания параллельного кода, безопасность потоков и высокая эффективность использования ресурсов процессора.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Эти принципы проявляются уже на уровне архитектуры языка. Go компилируется в машинный код без промежуточного байткода, что обеспечивает выполнение, сравнимое по скорости с C/C++, при этом устраняя… Фундамент для начинающего программиста - что повторить, как работать, чего ожидать. Набор советов, правил, принципов и обычаев в разработке на этом языке. 3. Отсутствие исключений и единый стиль обработки ошибок. Возврат ошибки как второго значения — идиома Go — обеспечивает явность, но ведёт к многоуровневой прокрутке if err = nil return err . Попытки… Все эти инструменты образуют единый, согласованный рабочий процесс. Они минимизируют необходимость в сторонних утилитах, снижают порог входа для новых разработчиков и обеспечивают высокую скорость… Кавычки, точки, запятые, скобки и прочие знаки препинания. Предопределённые идентификаторы не являются ключевыми словами, но имеют специальное значение в языке. Их можно переопределить в локальной области видимости, но делать это не рекомендуется. Набор функций, которые включены в стандартную библиотеку языка. Интерфейсы в Go — это контракты на поведение. Они определяют, что объект может делать. Это смещает фокус с классификации сущностей на описание их возможностей — что соответствует духу композиционного… Go вводит конкурентность через встроенные синтаксические конструкции и правила выполнения. Ниже рассматриваются основные направления практического применения Go, объяснённые через призму его технических характеристик и требований реальных инфраструктур. Типизация, набор правил определения типа данных значений языка.Основы языка Go
Что требуется знать перед началом изучения языка программирования Go
Рекомендации по разработке на Go
История языка Go
Экосистема приложений на Go
Синтаксис и пунктуация в Go
Ключевые слова языка Go
Встроенные функции и пакеты Go
Особенности языка Go
Синтаксические конструкции Go
Области применения Go
Типы данных и объявление переменных в Go