День 06 · Условия: if, else, switch · страница 3 из 6

Цепочка else if и порядок проверок

if и else выбирают из двух вариантов. Вариантов часто больше: статус доставки, размер скидки, тип упаковки. Для этого проверки выстраивают в цепочку. Здесь же — главная ловушка дня: цепочка, в которой каждое условие верное, а результат неверный.


else if: несколько вариантов

Ситуация. Статус доставки по числу дней в пути: 0 — отгружен сегодня; от 1 до 3 — в пути; больше трёх — задерживается.

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func main() {
	days := 2
	if days == 0 {
		fmt.Println("Отгружен сегодня")
	} else if days <= 3 {
		fmt.Println("В пути, дней:", days)
	} else {
		fmt.Println("Задерживается, дней:", days)
	}
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day06/scratch$ go run .
В пути, дней: 2

Как это читать. Цепочка проверяется сверху вниз:

  1. Строка 7: days == 0? 2 == 0 — false. Блок пропущен, дальше.
  2. Строка 9: days <= 3? true. Выполняется строка 10.
  3. Всё, что ниже, пропускается: else на строке 11 не рассматривается, программа продолжает после последней }.

Правило цепочки: выполняется первая ветка, чьё условие истинно. Условия ниже неё не проверяются вообще. else в конце значит «ни одно условие не подошло». Его можно не писать; тогда при всех false не выполнится ни одна ветка.

Почему на строке 9 не нужно days >= 1 && days <= 3? Потому что до строки 9 программа доходит, только если days == 0 уже оказалось false. Каждое следующее условие цепочки проверяется при условии, что все предыдущие были ложными. Поэтому условия в цепочке короткие — и поэтому их порядок важен.

} else if days <= 3 { — одной строкой, как } else {.


Попробуйте сейчас: ветки цепочки по дням.

Цель: для каждого значения days назвать ветку до запуска и сверить с тем, что напечатала программа.

1. Наберите в scratch/main.go программу со статусом доставки:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func main() {
	days := 2
	if days == 0 {
		fmt.Println("Отгружен сегодня")
	} else if days <= 3 {
		fmt.Println("В пути, дней:", days)
	} else {
		fmt.Println("Задерживается, дней:", days)
	}
}

Запускайте с days равным 0, 1, 3, 4. Перед каждым запуском назовите ветку:

▶ Выполните
cd ~/gocourse/day06/scratch
go run .

2. Потом days := -1: какую ветку выберет программа и разумно ли это для склада?

Готово, когда: на 0, 1, 3 и 4 напечатанная ветка совпала с названной, а для -1 у вас есть ответ, какую ветку выбрала программа и годится ли это складу.


Порядок проверок

Ситуация. Скидка по сумме заказа: от 10 000 руб. — 10 %, от 5000 руб. — 5 %. Суммы в копейках.

Код для чтения · разбираем, набирать не нужно
	sum := 1250000
	pct := 0
	if sum >= 500000 {
		pct = 5
	} else if sum >= 1000000 {
		pct = 10
	}
	fmt.Println("Скидка, %:", pct)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day06/scratch$ go run .
Скидка, %: 5

Заказ на 12 500 руб. получил 5 % вместо 10. Компилятор молчит, go vet тоже: по правилам языка программа верная, по смыслу — нет.

Как это читать. Строка 8: 1250000 >= 500000 — true, pct = 5, остальная цепочка пропущена. Ветка pct = 10 не выполнится никогда: любая сумма от 10 000 руб. больше 5000 руб. и раньше попадает в первую ветку. Каждое условие по отдельности записано верно, ошибка — в порядке.

Порядок исправлен:

Код для чтения · разбираем, набирать не нужно
	sum := 1250000
	pct := 0
	if sum >= 1000000 {
		pct = 10
	} else if sum >= 500000 {
		pct = 5
	}
	fmt.Println("Скидка, %:", pct)
Вывод · это печатает программа, набирать не нужно
Скидка, %: 10

Правило порядка. Пороги с >= и > идут от большего к меньшему. Пороги с < и <= — от меньшего к большему. Общее правило: сначала самое узкое условие, потом шире.

Как проверить готовую цепочку: для каждой ветки придумайте число, которое должно в неё попасть, и пройдите цепочку сверху с этим числом. Если число «застряло» в ветке выше своей — порядок неверный.

Отдельные if вместо цепочки — другая ошибка:

Код для чтения · разбираем, набирать не нужно
	sum := 1250000
	if sum >= 1000000 {
		fmt.Println("Скидка 10%")
	}
	if sum >= 500000 {
		fmt.Println("Скидка 5%")
	}
Вывод · это печатает программа, набирать не нужно
Скидка 10%
Скидка 5%

Два отдельных if не связаны: каждый проверяется всегда. Когда из нескольких вариантов нужен ровно один, пишут цепочку else if. Отдельные if — для проверок, которые друг друга не исключают: заказ бывает одновременно тяжёлым и срочным.


Попробуйте сейчас: что выведет программа?

Цель: пройти цепочку с границей в голове и сдать ответ day06.q2.

1. Блок ниже только для чтения — пройдите его в голове:

Код для чтения · разбираем, набирать не нужно
	weight := 1000
	if weight > 1000 {
		fmt.Println("тяжёлый")
	} else if weight < 0 {
		fmt.Println("ошибка веса")
	} else if weight >= 1000 {
		fmt.Println("средний")
	} else {
		fmt.Println("лёгкий")
	}

Вес — ровно 1000, то есть ровно на пороге. Пройдите условия по очереди и помните, чем > отличается от >=.

2. Сдайте предсказание — до запуска:

▶ Выполните · выделенное замените своим
course answer day06.q2 ОТВЕТ

Вместо ОТВЕТ — одно слово, как его напечатает программа.

3. Теперь проверьте себя запуском. Наберите в scratch/main.go ту же программу целиком:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func main() {
	weight := 1000
	if weight > 1000 {
		fmt.Println("тяжёлый")
	} else if weight < 0 {
		fmt.Println("ошибка веса")
	} else if weight >= 1000 {
		fmt.Println("средний")
	} else {
		fmt.Println("лёгкий")
	}
}
▶ Выполните
cd ~/gocourse/day06/scratch
go run .

Готово, когда: ответ сдан до запуска, и запуск его подтвердил; в course check 06 пункт «Что выведет: цепочка else if с границей» (q2) отмечен ✔.


Границы

Ситуация. В условии написано «от 1000 г до 19 999 г — коробка». Какой знак ставить?

В условии В коде Само число входит?
от 1000 и больше, не меньше 1000 weight >= 1000 да
больше 1000, свыше 1000 weight > 1000 нет
до 1000 включительно, не больше 1000 weight <= 1000 да
меньше 1000 weight < 1000 нет

«До 1000» без уточнения понимают по-разному. В заданиях курса всегда сказано, входит ли граница.

Ошибка > вместо >= видна только на самой границе. На 999 и 1001 обе записи дают одно и то же, расходятся они ровно на 1000. Поэтому примеры по методу дня 05 для каждой границы — само число и соседнее: для «от 1000» — 999 и 1000. Проверка заданий подставляет в программу именно такие числа.

Для целых чисел weight >= 1000 и weight > 999 означают одно и то же. Пишите так, как сказано в условии: число из условия в коде проще сверить глазами.


Попробуйте сейчас: починка порядка в fix1.

Цель: fix1 выбирает упаковку по комментарию на всех границах.

1. Программа fix1 собирается, но выбирает не ту упаковку. Запустите:

▶ Выполните
cd ~/gocourse/day06/fix1
go run .

2. Пройдите цепочку сверху вниз с весом из комментария: какое условие сработало первым?

3. Почините порядок — поменяйте ветки местами, это до четырёх строк.

4. Проверьте все границы: поставьте в строку weight := по очереди 999, 1000, 19999, 20000, каждый раз go run ., и сверьте с комментарием. Потом верните исходный вес.

Готово, когда: на весах 999, 1000, 19999, 20000 и на исходном весе программа печатает упаковку из комментария, в файле снова исходный вес (пункт проверки fix1).


Что может пойти не так

Ошибки цепочек почти всегда логические: программа собирается и работает, но выбирает не ту ветку. Пример — особый случай, который не проверили:

Код для чтения · разбираем, набирать не нужно
	stock := -3
	status := ""
	if stock == 0 {
		status = "нет в наличии"
	} else if stock < 10 {
		status = "мало"
	} else {
		status = "достаточно"
	}
	fmt.Println(stock, status)
Вывод · это печатает программа, набирать не нужно
-3 мало

Отрицательный остаток — ошибка учёта, а программа назвала его «мало»: отдельной проверки для него нет, и первым подошло stock < 10. Особые случаи — отрицательное число, ноль — проверяют первыми.

Что видите Что это значит Что делать
большой заказ получил маленькую скидку, тяжёлый груз — тариф среднего широкое условие стоит выше узкого узкие условия выше; пройти цепочку с числом для каждой ветки
напечатались две ветки вместо одной отдельные if вместо else if } else if … {
на числе ровно на границе — не та ветка > вместо >= или < вместо <= сверить знак с условием, проверить число и соседнее
особое значение (ноль, минус) попало в обычную ветку для него нет своей проверки проверка особого случая — первой
ни одна ветка не выполнилась, у переменной начальное значение нет else, и ни одно условие не подошло добавить else для всего остального
syntax error: unexpected keyword else, expected } else if перенесён на новую строку после } } else if … { одной строкой
syntax error: unexpected newline, expected { after if clause на строке с else if { перенесена на следующую строку { в конце строки

Попробуйте сейчас: задание stock.

Цель: stock/main.go называет статус остатка по TASK.txt для любых чисел, числа у каждого свои.

1. Прочитайте условие:

▶ Выполните
cd ~/gocourse/day06/stock
cat TASK.txt

2. По методу дня 05: сначала выпишите примеры — по одному на каждый статус и по два на каждую границу. Решите, какой статус проверять первым.

3. Напишите main.go, запустите go run . и прогоните все свои примеры, меняя числа в первых строках.

4. Верните свои числа и проверьте формат:

▶ Выполните
gofmt -l .

Пустой вывод — код отформатирован.

Готово, когда: на каждом примере с бумаги программа печатает статус по условию, в первых строках снова ваши числа, gofmt -l . ничего не печатает (пункты проверки stock, t_gofmt, code_fmt).


Дальше: как трассировать программу с ветвлениями — course next