$ sudo teach IT
МОДУЛЬ 2 · УРОК 2.4

Условия: if / else / else if / switch

Учим программу принимать решения: проверяем условия, выбираем ветки выполнения и строим логику

В предыдущем уроке мы разобрались с операторами: научились складывать, сравнивать и комбинировать значения. Но до сих пор наши программы выполнялись строго сверху вниз, одна строка за другой, без каких-либо разветвлений. В реальной жизни так не бывает. Когда вы утром смотрите в окно и видите дождь, вы берёте зонт. Если солнечно, одеваетесь легче. Это и есть условие: если что-то истинно, делай одно; иначе делай другое. Сегодня мы научим нашу программу принимать решения с помощью конструкций if, else, else if и switch.

🔀 Базовый if — простейшее условие

Конструкция if — это самая базовая форма ветвления. Она говорит: «Если условие истинно, выполни код внутри фигурных скобок». Вот как это выглядит:

package main

import "fmt"

func main() {
    temperature := 35

    if temperature > 30 {
        fmt.Println("На улице жарко!")
    }
}

Разберём по частям. Переменная temperature хранит значение 35. Затем мы пишем if temperature > 30 — это условие. Если оно истинно (а 35 действительно больше 30), выполняется код внутри фигурных скобок: выводится строка «На улице жарко!». Если бы temperature была, скажем, 20, программа просто пропустила бы этот блок и ничего не напечатала.

Обратите внимание на важную особенность Go:

💡 Подсказка: в Go условие после if пишется без круглых скобок. Это отличает Go от многих других языков. Пишем if x > 10, а не if (x > 10). Если вы добавите скобки, программа скомпилируется, но это считается плохим стилем, и большинство Go-инструментов автоматически их уберут.

Ещё одна важная деталь: открывающая фигурная скобка { обязательно должна стоять на той же строке, что и if. Нельзя перенести её на следующую строку — Go этого не позволяет. Это не рекомендация, а требование языка.

// ТАК ПРАВИЛЬНО:
if temperature > 30 {
    fmt.Println("Жарко!")
}

// ТАК НЕПРАВИЛЬНО — ошибка компиляции:
// if temperature > 30
// {
//     fmt.Println("Жарко!")
// }

Фигурные скобки обязательны даже для одной строки внутри if. В отличие от некоторых языков, где можно написать if x > 0 doSomething() без скобок, в Go это невозможно. Это делает код более читаемым и предсказуемым.

↔️ if / else — два пути

Часто нужно не просто выполнить действие, если условие истинно, но и сделать что-то другое, если условие ложно. Для этого используется else:

package main

import "fmt"

func main() {
    age := 16

    if age >= 18 {
        fmt.Println("Вы совершеннолетний")
    } else {
        fmt.Println("Вы несовершеннолетний")
    }
}

Здесь age равен 16. Условие age >= 18 ложно, поэтому программа пропускает первый блок и выполняет блок else. На экране появится: «Вы несовершеннолетний».

Обратите внимание: else пишется на той же строке, что и закрывающая скобка предыдущего блока }. Это обязательно:

// ПРАВИЛЬНО — else на той же строке, что и }
if age >= 18 {
    fmt.Println("Совершеннолетний")
} else {
    fmt.Println("Несовершеннолетний")
}

// НЕПРАВИЛЬНО — ошибка компиляции:
// if age >= 18 {
//     fmt.Println("Совершеннолетний")
// }
// else {
//     fmt.Println("Несовершеннолетний")
// }

Рассмотрим ещё один пример — проверка чётности числа:

package main

import "fmt"

func main() {
    number := 7

    if number%2 == 0 {
        fmt.Println(number, "— чётное число")
    } else {
        fmt.Println(number, "— нечётное число")
    }
}

Оператор % (остаток от деления) мы изучали в прошлом уроке. Если остаток от деления на 2 равен нулю, число чётное. Иначе — нечётное. Программа выведет: «7 — нечётное число».

🔗 else if — цепочка условий

Иногда два варианта — «да» и «нет» — недостаточно. Например, вы хотите оценить результат экзамена: отлично, хорошо, удовлетворительно или неудовлетворительно. Для этого используется else if — промежуточные условия между if и else:

package main

import "fmt"

func main() {
    score := 82

    if score >= 90 {
        fmt.Println("Отлично!")
    } else if score >= 70 {
        fmt.Println("Хорошо")
    } else if score >= 50 {
        fmt.Println("Удовлетворительно")
    } else {
        fmt.Println("Неудовлетворительно")
    }
}

Давайте проследим, как программа думает. Переменная score равна 82.

  1. Проверяем: 82 >= 90? Нет. Переходим дальше.
  2. Проверяем: 82 >= 70? Да! Выполняем этот блок — выводим «Хорошо».
  3. Все остальные ветки пропускаются.

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

Блоков else if может быть сколько угодно. Финальный else необязателен, но полезен как «ловушка» для всех оставшихся случаев — на случай, если ни одно условие не подошло.

⚡ Короткий if с инициализацией

В Go есть уникальная возможность, которой нет во многих других языках: вы можете объявить переменную прямо внутри if перед условием. Это называется if с инициализацией (short statement). Выглядит это так:

package main

import "fmt"

func main() {
    if x := 10; x > 5 {
        fmt.Println("x больше 5, а именно:", x)
    }
}

Обратите внимание на точку с запятой ;. Она разделяет две части: сначала идёт инициализация (x := 10), потом условие (x > 5). Это позволяет объявить переменную и сразу проверить её значение в одной строке.

Зачем это нужно? Переменная x, объявленная таким образом, существует только внутри блока if (и связанных с ним else if / else). За пределами конструкции она недоступна. Это позволяет не засорять внешнюю область видимости временными переменными.

package main

import "fmt"

func main() {
    if length := len("Привет"); length > 3 {
        fmt.Println("Строка длинная:", length, "символов")
    } else {
        fmt.Println("Строка короткая:", length, "символов")
    }

    // fmt.Println(length) // ОШИБКА! Переменная length здесь не существует
}

В этом примере мы вычисляем длину строки и сразу проверяем, больше ли она 3. Переменная length доступна и в блоке if, и в блоке else, но после закрывающей скобки она перестаёт существовать.

💡 Подсказка: короткий if с инициализацией — это идиоматичный Go. Вы будете очень часто встречать его в реальном коде, особенно при обработке ошибок: if err := doSomething(); err != nil { ... }. Пока не обязательно запоминать этот паттерн — мы подробно разберём ошибки в будущих уроках. Главное, чтобы вы понимали механизм.

🪆 Вложенные условия

Условия можно вкладывать друг в друга. Например, сначала проверяем, есть ли у человека билет, а потом — какого он класса:

package main

import "fmt"

func main() {
    hasTicket := true
    class := "бизнес"

    if hasTicket {
        if class == "бизнес" {
            fmt.Println("Добро пожаловать в бизнес-класс!")
        } else {
            fmt.Println("Добро пожаловать в эконом-класс!")
        }
    } else {
        fmt.Println("Пожалуйста, приобретите билет")
    }
}

Программа сначала проверяет, есть ли билет (hasTicket — это булева переменная, поэтому можно писать просто if hasTicket, без == true). Если билет есть, внутри проверяется класс. Если билета нет, программа сразу переходит к else внешнего условия.

💡 Подсказка: вложенные условия работают, но старайтесь не злоупотреблять глубиной вложенности. Код с тремя-четырьмя уровнями вложенных if тяжело читать. Часто вложенные условия можно заменить цепочкой else if или логическими операторами && и ||.

🔄 switch — элегантная замена длинным if-else

Когда у вас много вариантов для одной переменной, цепочка else if становится громоздкой. Представьте, что вы проверяете день недели. С if / else if это выглядит так:

// Громоздко и трудно читать:
if day == "понедельник" {
    fmt.Println("Начало недели")
} else if day == "вторник" {
    fmt.Println("Второй день")
} else if day == "среда" {
    fmt.Println("Середина недели")
} else if day == "четверг" {
    fmt.Println("Почти пятница")
} else if day == "пятница" {
    fmt.Println("Ура, пятница!")
} else {
    fmt.Println("Выходной!")
}

Конструкция switch решает эту проблему — она делает код чистым и читаемым:

package main

import "fmt"

func main() {
    day := "среда"

    switch day {
    case "понедельник":
        fmt.Println("Начало недели")
    case "вторник":
        fmt.Println("Второй день")
    case "среда":
        fmt.Println("Середина недели")
    case "четверг":
        fmt.Println("Почти пятница")
    case "пятница":
        fmt.Println("Ура, пятница!")
    default:
        fmt.Println("Выходной!")
    }
}

Как это работает: после слова switch указывается выражение, которое мы проверяем (day). Затем идут блоки case с возможными значениями. Go сравнивает day с каждым case сверху вниз. Когда находит совпадение, выполняет код этого блока. Блок default выполняется, если ни один case не подошёл (как else в конструкции if).

Ключевое отличие Go: автоматический break

Если вы знакомы с другими языками (C, Java, JavaScript), то знаете, что в switch нужно писать break после каждого case, иначе выполнение «провалится» вниз в следующий блок. В Go break происходит автоматически! Каждый case сам по себе завершается после выполнения. Это избавляет от целого класса ошибок, связанных с забытым break.

📦 Несколько значений в одном case

Иногда несколько значений должны обрабатываться одинаково. В Go можно перечислить их через запятую:

package main

import "fmt"

func main() {
    day := "суббота"

    switch day {
    case "понедельник", "вторник", "среда", "четверг", "пятница":
        fmt.Println("Рабочий день")
    case "суббота", "воскресенье":
        fmt.Println("Выходной!")
    default:
        fmt.Println("Неизвестный день")
    }
}

Здесь все пять рабочих дней объединены в один case, и оба выходных — в другой. Это гораздо удобнее, чем писать пять отдельных блоков с одинаковым кодом. Программа выведет: «Выходной!».

🧩 switch без условия — чистая замена if/else

В Go есть особая форма switch — без выражения после ключевого слова. Такой switch эквивалентен switch true: он проверяет каждый case как самостоятельное условие. Это просто более чистый способ записать цепочку if / else if:

package main

import "fmt"

func main() {
    temperature := 25

    switch {
    case temperature < 0:
        fmt.Println("Мороз")
    case temperature < 10:
        fmt.Println("Холодно")
    case temperature < 20:
        fmt.Println("Прохладно")
    case temperature < 30:
        fmt.Println("Тепло")
    default:
        fmt.Println("Жарко!")
    }
}

Заметьте: после switch нет никакой переменной. Каждый case содержит собственное условие. Go проверяет их по порядку и выполняет первый истинный. Переменная temperature равна 25, поэтому temperature < 30 — первое истинное условие. Вывод: «Тепло».

Такой switch — любимый приём многих Go-разработчиков. Он читается проще, чем длинная цепочка if / else if / else if / else, и выглядит аккуратнее.

⬇️ fallthrough — принудительное проваливание

Мы говорили, что в Go каждый case автоматически завершается. Но иногда вам нужно, чтобы после одного case выполнился и следующий. Для этого существует ключевое слово fallthrough:

package main

import "fmt"

func main() {
    level := 1

    switch level {
    case 1:
        fmt.Println("Доступ: чтение")
        fallthrough
    case 2:
        fmt.Println("Доступ: запись")
        fallthrough
    case 3:
        fmt.Println("Доступ: администрирование")
    }
}

Вывод программы:

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

Несмотря на то что level равен 1, благодаря fallthrough выполнение «проваливается» в каждый следующий case. Это полезно в редких случаях, когда уровни доступа накапливаются: уровень 1 даёт все три права, уровень 2 — только запись и администрирование, уровень 3 — только администрирование.

💡 Подсказка: fallthrough используется в Go довольно редко. Он безусловный: переход в следующий case произойдёт всегда, без проверки условия. Поэтому используйте его только тогда, когда действительно нужно «провалиться» в следующий блок. В большинстве ситуаций перечисление через запятую (case "a", "b":) — более удобное решение.

🔧 switch с инициализацией

Как и if, конструкция switch поддерживает короткую инициализацию перед проверяемым выражением:

package main

import (
    "fmt"
    "time"
)

func main() {
    switch hour := time.Now().Hour(); {
    case hour < 6:
        fmt.Println("Ночь")
    case hour < 12:
        fmt.Println("Утро")
    case hour < 18:
        fmt.Println("День")
    default:
        fmt.Println("Вечер")
    }
}

Здесь hour получает текущий час, и мы сразу проверяем его в switch. Переменная hour существует только внутри конструкции switch, точно так же, как переменная в if с инициализацией. Функция time.Now().Hour() возвращает текущий час (от 0 до 23) — мы познакомимся с пакетом time подробнее позже, а пока просто запомните, что в Go можно легко получить текущее время.

🛠 Практика: определитель времени суток

Давайте соберём всё воедино и напишем программу, которая по заданному часу определяет время суток и выдаёт соответствующее приветствие. Мы используем и if, и switch, чтобы вы увидели оба подхода в деле.

Вариант 1: через if / else if / else

package main

import "fmt"

func main() {
    hour := 14 // Попробуйте менять это значение

    fmt.Println("Сейчас", hour, "часов.")

    if hour < 0 || hour > 23 {
        fmt.Println("Ошибка: час должен быть от 0 до 23")
    } else if hour < 6 {
        fmt.Println("Доброй ночи! Пора спать 🌙")
    } else if hour < 12 {
        fmt.Println("Доброе утро! Время для кофе ☕")
    } else if hour < 18 {
        fmt.Println("Добрый день! Продуктивного времени 💻")
    } else {
        fmt.Println("Добрый вечер! Время отдыхать 🌆")
    }
}

Обратите внимание: мы сначала проверяем, что час корректный (от 0 до 23). Это хорошая практика — всегда проверять входные данные. Оператор || означает «или»: если час меньше 0 или больше 23, выводится ошибка.

Вариант 2: через switch без условия

package main

import "fmt"

func main() {
    hour := 14

    fmt.Println("Сейчас", hour, "часов.")

    switch {
    case hour < 0 || hour > 23:
        fmt.Println("Ошибка: час должен быть от 0 до 23")
    case hour < 6:
        fmt.Println("Доброй ночи! Пора спать 🌙")
    case hour < 12:
        fmt.Println("Доброе утро! Время для кофе ☕")
    case hour < 18:
        fmt.Println("Добрый день! Продуктивного времени 💻")
    default:
        fmt.Println("Добрый вечер! Время отдыхать 🌆")
    }
}

Оба варианта делают одно и то же. Попробуйте поменять значение hour на разные числа — 3, 8, 15, 21, 25 — и посмотрите, что выведет программа. Запускайте так:

go run main.go

Вариант 3: используем реальное время

А теперь самое интересное — пусть программа сама определяет, сколько сейчас времени, и приветствует нас:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Получаем текущий час
    hour := time.Now().Hour()

    fmt.Printf("Сейчас %d часов. ", hour)

    switch {
    case hour < 6:
        fmt.Println("Доброй ночи!")
    case hour < 12:
        fmt.Println("Доброе утро!")
    case hour < 18:
        fmt.Println("Добрый день!")
    default:
        fmt.Println("Добрый вечер!")
    }

    // Бонус: определяем день недели
    switch day := time.Now().Weekday(); day {
    case time.Saturday, time.Sunday:
        fmt.Println("Сегодня выходной — отдыхайте!")
    default:
        fmt.Println("Сегодня рабочий день — за работу!")
    }
}

Эта программа использует пакет time, чтобы получить реальный текущий час и день недели. Каждый раз, когда вы её запустите, результат будет разным — в зависимости от того, когда именно вы это делаете. Обратите внимание на бонусную часть: switch с инициализацией и проверкой дня недели. Здесь time.Saturday и time.Sunday — это константы из пакета time.

Мы также впервые использовали fmt.Printf. Это функция форматированного вывода: %d — это подстановка для целого числа. То есть вместо %d подставится значение hour. О форматировании мы поговорим подробнее в одном из будущих уроков.

⚖️ Когда использовать if, а когда switch

Новички часто спрашивают: «А что лучше — if или switch?» Вот простое правило:

Используйте if, когда:

У вас одно-два условия. Или условие сложное, с несколькими логическими операторами. Или вам нужен if с инициализацией для обработки ошибок.

Используйте switch, когда:

У вас три и более вариантов для одной переменной. Или вы проверяете конкретные значения (строки, числа). Или хотите сделать код более читаемым и структурированным.

На практике обе конструкции взаимозаменяемы. Нет ничего, что можно сделать с switch и нельзя с if, и наоборот. Разница — в читаемости. Выбирайте то, что делает ваш код понятнее.

📋 Итоги урока

  • if в Go пишется без круглых скобок вокруг условия, а фигурные скобки обязательны
  • else и else if позволяют создавать ветвление с несколькими путями выполнения
  • if с инициализацией (if x := ...; x > 0) объявляет переменную, видимую только внутри блока
  • switch — чистая альтернатива длинным цепочкам else if; каждый case завершается автоматически, без break
  • В одном case можно перечислить несколько значений через запятую
  • switch без условия проверяет каждый case как отдельное булево выражение
  • fallthrough принудительно передаёт выполнение в следующий case (используется редко)
  • Мы написали программу-определитель времени суток, используя и if, и switch

В следующем уроке мы изучим циклы — конструкцию for во всех её формах. В Go for — единственный цикл, но он универсален: заменяет и while, и do-while, и foreach из других языков. Вы научитесь повторять действия заданное количество раз, перебирать элементы коллекций и создавать бесконечные циклы. После этого урока у вас будут все базовые инструменты для управления потоком программы!