Create your own
Lesson illustration

Основы программирования на Go: переменные, типы, условия и циклы

Здравствуйте. В прошлом уроке вы подготовили модуль task-service и восстановили базовый цикл работы: форматирование, запуск, сборка, тесты и управление зависимостями. Теперь наполним эту заготовку логикой: научимся хранить данные в переменных, выбирать подходящие базовые типы, принимать решения через условия и повторять действия в циклах.

Это небольшой, но важный шаг к backend-коду: практически любой обработчик запроса сначала получает значения, затем проверяет их и обрабатывает набор действий по определённым правилам.


Переменные: имя, тип и значение

Переменная связывает имя, тип и текущее значение. Тип определяет, какие операции допустимы: числа можно складывать, строки сравнивать и объединять, а bool использовать в условиях.

В Go есть два основных способа объявить переменную.

var projectName string
var taskLimit int = 10
var isArchived = false

В форме с var тип стоит после имени. Если значение задано сразу, компилятор часто способен вывести тип самостоятельно:

var taskLimit = 10      // int
var projectName = "API" // string

Внутри функций чаще используют короткое объявление:

projectName := "API"
taskLimit := 10
isArchived := false

Оператор := одновременно объявляет переменную и присваивает значение. Он разрешён только внутри функций. После объявления переменной для изменения значения применяется обычное присваивание =:

taskLimit := 10
taskLimit = 20

Нельзя написать :=, если в текущей области видимости нет хотя бы одной новой переменной:

limit := 10
limit = 20 // правильно

// limit := 20 // ошибка: новой переменной здесь нет

Нулевые значения

Go не оставляет обычные переменные «неинициализированными». Если объявить переменную через var без значения, она получит нулевое значение своего типа:

ТипНулевое значение
int, int64, float640
string""
boolfalse
var title string
var taskCount int
var ready bool

fmt.Printf("title=%q tasks=%d ready=%t\n", title, taskCount, ready)

Вывод будет таким:

title="" tasks=0 ready=false

Нулевые значения удобны, но в backend-разработке нельзя автоматически считать их корректными бизнес-данными. Например, пустое название проекта, идентификатор 0 или лимит 0 могут означать, что клиент не передал поле, а не то, что это осмысленное значение. Позже вы будете явно валидировать такие случаи в HTTP-обработчиках.

A Tour of Go

Прочитайте короткий раздел «Variables» из официального интерактивного курса A Tour of Go. Он закрепит синтаксис var и покажет, что переменные можно объявлять как на уровне пакета, так и внутри функции.

В разделе Variables прочитайте текст и пример целиком. Обратите внимание на правило объявления: тип пишется после имени, а var допустим и вне функций, и внутри них. В текущем проекте почти все переменные пока будут локальными, внутри main.


Базовые типы: выбирать по смыслу, а не «на всякий случай»

Для большинства прикладного Go-кода вам постоянно будут встречаться несколько типов:

title := "Prepare interview" // string
completed := 3               // int
ownerID := int64(42)         // int64
isArchived := false          // bool
progress := 60.0             // float64

string

string хранит текст. Строковые литералы обычно записываются в двойных кавычках:

status := "open"
emptyStatus := ""

Строки удобно использовать для названий, описаний, email-адресов и внешних идентификаторов. В доменной модели статус задачи позже тоже можно будет представить строкой, но только с проверкой допустимых значений.

Целые числа: int и int64

int — стандартный выбор для локальных счётчиков, индексов и размеров:

attempts := 3

Размер int зависит от архитектуры: обычно это 64 бита на современных серверных системах. Когда нужен конкретный размер, используйте тип с явной разрядностью, например int64.

var projectID int64 = 42

На практике int64 часто появляется на границе с PostgreSQL, если столбец имеет тип BIGINT. Не стоит выбирать int64 для каждой переменной без причины: для обычного счётчика задач в цикле достаточно int.

Беззнаковые типы uint, uint64 и другие существуют, но использовать их для бизнес-значений «потому что число не бывает отрицательным» обычно не нужно. У них есть неприятное свойство: при вычитании из нуля возникает переполнение. В прикладном backend-коде отрицательное значение int часто полезнее заметить и отклонить валидацией.

bool

bool принимает только два значения:

isOwner := true
isArchived := false

Булевы значения обычно получаются из сравнений:

hasAccess := ownerID == currentUserID
hasValidCount := completed >= 0 && completed <= total

float64

float64 хранит числа с дробной частью:

ratio := 3.5

Он подходит, например, для отображения процента выполнения. Но не используйте float64 для денег: двоичная плавающая точка может неточно представить десятичные дроби. Денежные суммы в сервисах обычно хранят в минимальных единицах целым числом: например, 19990 копеек вместо 199.90.

byte — это псевдоним uint8, а rune — псевдоним int32, представляющий одну Unicode-кодовую точку. Они понадобятся при более детальной работе с байтами и текстом; пока достаточно знать, что строка и отдельный Unicode-символ — не одно и то же.

Basic types

Официальный раздел A Tour of Go даёт компактную карту базовых типов Go. Используйте его как справочник: не нужно заучивать все размеры чисел, важнее понимать, почему по умолчанию обычно выбирают int.

В разделе Basic types сначала просмотрите полный список типов, затем прочитайте пояснение под ним. Особенно запомните выбор int: берите int, пока конкретная разрядность или беззнаковое представление действительно не требуются.

Изучаем Go. Урок №2. Переменные, константы. Типы. Области видимости. Преобразование типов #golang

Посмотрите фрагмент видео «Изучаем Go. Урок №2. Переменные, константы. Типы. Области видимости. Преобразование типов» от ThisIsIT. Он наглядно повторяет базовые типы, способы объявления переменных и их нулевые значения.

Начните с типов данных: обратите внимание на различие между bool, string, целыми числами, byte и rune. Затем посмотрите объявления и нулевые значения. Сопоставляйте примеры с правилом: var подходит и для объявления без значения, а := удобен для локальных переменных с немедленной инициализацией.


Явное преобразование типов

Go не выполняет неявные преобразования между числовыми типами. Это защищает от незаметной потери точности и ошибок в контракте данных.

var dbID int64 = 42
count := 3

// total := dbID + count // ошибка: int64 и int — разные типы

total := dbID + int64(count)
fmt.Println(total)

Преобразование записывается как имя целевого типа и значение в скобках:

int64(count)
float64(completed)

Особенно часто это встречается при вычислении дробного процента. Деление двух int даёт целое число:

completed := 2
total := 3

ratio := completed / total
fmt.Println(ratio) // 0

Чтобы получить дробный результат, преобразуйте хотя бы один операнд до деления:

progress := float64(completed) / float64(total) * 100
fmt.Printf("%.1f%%\n", progress) // 66.7%

Но сначала необходимо удостовериться, что total не равен нулю. Это уже задача условия.

Константы для неизменяемых правил

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

const maxTasksPerProject = 100

Константы подходят для лимитов, строковых кодов и иных стабильных правил. Имя должно объяснять смысл числа: maxTasksPerProject лучше, чем просто 100, разбросанное по нескольким местам кода.


Условия: превращаем правила в код

Условие выбирает ветку выполнения на основании выражения типа bool.

if completed == total {
	fmt.Println("All tasks are completed")
}

В Go условие не оборачивают в круглые скобки, но тело всегда заключают в фигурные скобки. Это правило уменьшает риск ошибок при последующем добавлении строк в ветку.

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

ОператорЗначение
==равно
!=не равно
<, >меньше, больше
<=, >=меньше или равно, больше или равно

Важно не путать присваивание и сравнение:

isArchived = true  // присвоить значение
isArchived == true // сравнить значения

Для булевой переменной обычно читается лучше короткая форма:

if isArchived {
	fmt.Println("Project is archived")
}

if !isArchived {
	fmt.Println("Project is active")
}

В Go нет неявной «истинности» чисел или строк. Следующий код некорректен:

// if completed {
// }

Нужно явно сформулировать правило:

if completed > 0 {
	fmt.Println("There are completed tasks")
}

Несколько ветвей: if, else if, else

Когда сценарии взаимоисключающие, используйте одну цепочку:

if completed == total {
	fmt.Println("Project is complete")
} else if completed == 0 {
	fmt.Println("No tasks completed yet")
} else {
	fmt.Println("Project is in progress")
}

Выполнится ровно одна ветка. else пишется на той же строке, где закрывается предыдущий блок: } else {. После go fmt код будет приведён к этому стандартному стилю автоматически.

Логические операторы

Практические правила обычно состоят из нескольких проверок:

validCounts := completed >= 0 && completed <= total
  • &&И: всё выражение истинно, только если истинны обе части.
  • ||ИЛИ: достаточно, чтобы истинной была хотя бы одна часть.
  • !НЕ: меняет true на false и наоборот.
isEmptyProject := total == 0 || completed == 0
canEdit := isOwner && !isArchived

Go проверяет части логического выражения слева направо и использует короткое вычисление. В выражении с && правая часть не будет вычислена, если левая уже false; в выражении с || правая часть не нужна, если левая уже true. Это позволяет безопасно строить проверки, которые защищают дальнейшее вычисление:

if total > 0 && float64(completed)/float64(total) >= 0.8 {
	fmt.Println("Progress is at least 80%")
}

Здесь деление произойдёт только при total > 0.

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

Go позволяет создать переменную прямо перед условием:

if isComplete := completed == total; isComplete {
	fmt.Println("Project is complete")
}

Переменная isComplete существует только внутри этой конструкции if, включая возможный блок else. Это полезно, когда промежуточный результат не нужен дальше. Если проверка начинает выглядеть громоздко, лучше объявить переменную отдельной строкой: читаемость важнее компактности.

Изучаем Go. Урок №5. Условный оператор (if/else). Логические операторы #golang

Фрагменты видео «Изучаем Go. Урок №5. Условный оператор (if/else). Логические операторы» от ThisIsIT помогут быстро восстановить синтаксис условий и логику составных проверок.

Сначала посмотрите сравнения и if, затем после короткого пропуска перейдите к ветке else. Закончите логическими операторами: соотнесите &&, || и ! с проверками валидности количества задач и прав доступа.


Цикл for: повторяем работу без копирования кода

В Go есть один ключевой оператор цикла — for. Его формы покрывают привычные циклы while, классический счётчик и бесконечное повторение.

Цикл с условием

Эта форма выполняется, пока условие истинно:

retriesLeft := 3

for retriesLeft > 0 {
	fmt.Println("Trying again")
	retriesLeft--
}

Не забывайте изменять значение, от которого зависит условие. Иначе цикл может стать бесконечным.

Цикл со счётчиком

Для известного числа повторений используйте три части: начальное значение, условие продолжения и действие после каждой итерации.

for attempt := 1; attempt <= 3; attempt++ {
	fmt.Printf("Attempt %d\n", attempt)
}

Здесь:

  1. attempt := 1 выполняется один раз перед началом цикла.
  2. attempt <= 3 проверяется перед каждой итерацией.
  3. attempt++ увеличивает счётчик после выполнения тела.

attempt++ — сокращение для attempt = attempt + 1. В Go это отдельная инструкция: его нельзя использовать внутри выражения.

break и continue

break немедленно завершает цикл:

for attempt := 1; attempt <= 3; attempt++ {
	fmt.Printf("Attempt %d\n", attempt)

	if attempt == 2 {
		break
	}
}

continue пропускает оставшуюся часть текущей итерации и начинает следующую:

for number := 1; number <= 5; number++ {
	if number <= 2 {
		continue
	}

	fmt.Printf("Task %d needs attention\n", number)
}

В реальном коде break и continue полезны, но чрезмерное количество таких переходов усложняет чтение. Держите тело цикла коротким и формулируйте условие так, чтобы было ясно, почему обработка прекращается или пропускается.

В следующем уроке о массивах и срезах появится форма for range, которая обычно применяется для обхода реальных коллекций задач. Пока достаточно уверенно владеть for с условием и счётчиком.


Собираем мини-программу для проверки проекта

Замените содержимое main.go на программу ниже. Она не моделирует полноценные задачи — для этого пока нет коллекций и структур, — но уже использует типичные элементы прикладной логики: входные значения, инварианты, ветвления и обработку серии условных задач.

package main

import "fmt"

const maxTasksPerProject = 100

func main() {
	title := "Interview preparation"
	ownerID := int64(42)
	completed := 2
	total := 5
	isArchived := false

	validCounts := total >= 0 && completed >= 0 && completed <= total

	if title == "" {
		fmt.Println("Project title is required")
		return
	}

	if !validCounts {
		fmt.Println("Invalid task counts")
		return
	}

	if total > maxTasksPerProject {
		fmt.Println("Too many tasks in project")
		return
	}

	if isArchived {
		fmt.Println("Project is archived")
		return
	}

	fmt.Printf("Project %q, owner %d\n", title, ownerID)

	if completed == total {
		fmt.Println("Project is complete")
	} else if completed == 0 {
		fmt.Println("No tasks completed yet")
	} else {
		progress := float64(completed) / float64(total) * 100
		fmt.Printf("Progress: %.0f%%\n", progress)
	}

	// В этой учебной модели первые completed задач считаются завершёнными.
	for taskNumber := 1; taskNumber <= total; taskNumber++ {
		if taskNumber <= completed {
			continue
		}

		fmt.Printf("Task %d needs attention\n", taskNumber)
	}
}

Обратите внимание на несколько инженерных решений в этом коде:

  • ownerID имеет тип int64, поскольку идентификаторы нередко приходят из БД в таком виде.
  • Проверка validCounts защищает инвариант: завершённых задач не может быть меньше нуля или больше общего числа.
  • Ранние return не дают программе перейти к вычислению процента и циклу при некорректных данных.
  • Преобразование в float64 происходит только после проверки корректности счётчиков; при total == 0 код попадёт в ветку completed == total, не выполняя деление.
  • continue не печатает завершённые задачи и оставляет в выводе только требующие внимания.

Запустите привычный цикл проверки из корня модуля:

go fmt ./...
go test ./...
go run .

Ожидаемый результат для исходных значений:

Project "Interview preparation", owner 42
Progress: 40%
Task 3 needs attention
Task 4 needs attention
Task 5 needs attention

Для самостоятельной ручной проверки меняйте значения по одному:

  • установите completed := 5, чтобы попасть в ветку полного завершения;
  • установите completed := 0, чтобы увидеть ветку отсутствия прогресса;
  • установите completed := 6, чтобы сработала проверка инварианта;
  • установите isArchived := true, чтобы программа завершилась до обработки задач.

После изменений каждый раз запускайте go fmt ./... и go test ./...: даже пока тест содержит только smoke-проверку, эта команда также компилирует весь пакет и ловит ошибки типов и синтаксиса.


Итоги

Теперь у вас восстановлена базовая логика Go-программы:

  • var объявляет переменную, а := кратко объявляет и инициализирует её внутри функции;
  • у базовых типов есть предсказуемые нулевые значения;
  • для обычных целых счётчиков подходит int, а int64 нужен при явной необходимости;
  • Go требует явного преобразования между числовыми типами;
  • if, else if и else выражают взаимоисключающие правила;
  • &&, || и ! позволяют собирать несколько проверок в одно условие;
  • for покрывает циклы с условием и со счётчиком, а break и continue управляют ходом обработки.

В следующем уроке вы перейдёте к функциям: выделите проверки и вычисления из main, научитесь возвращать несколько значений, включая привычную для Go пару «результат и ошибка».

Can't find a good explanation? Sign up and we'll make it for you

Sign up