Первая часть:
Рассмотрим такую ситуацию: вы хотите сварить кашу. Нужно сходить в магазин за крупой.
Тогда вы можете разбить процесс на составные части так: сначала у вас есть намерение купить крупу, затем у вас есть намерение сварить кашу.
Когда вы что-то делаете, вы находитесь в процессе выполнения своего намерения. Ожидая зелёного сигнала светофора, вы осознаёте, что в этот момент вы исполняете своё намерение дойти до магазина.
Сейчас ваши намерения синхронизированы. Чтобы осуществить намерение сварить кашу, надо сначала осуществить намерение сходить в магазин.
Можно поступить и по-другому. Так как вы всё равно будете варить кашу, можно поставить кастрюлю с водой на огонь и уйти в магазин. Пока вы будете покупать крупу, вода закипит, и каша приготовится быстрее.
Теперь, стоя у светофора, вы осуществляете два намерения параллельно: вы уже и в процессе варки каши, и в процессе похода в магазин.
В случае синхронного осуществления намерений вы могли бы задержаться в магазине на несколько часов, спасти котёнка и тому подобное. Потом вы бы вернулись и сварили кашу.
В случае параллельного выполнения намерений вы уже ограничены в вариантах развития событий. Вы должны успеть вернуться домой, пока вода не выкипела. И даже если это вам удастся, уже у двери может оказаться, что вы потеряли ключи.
Очевидно, чем больше намерений вы хотите осуществить за один раз, в целях оптимизации своего времени и усилий, тем больше вероятность, что что-то пойдёт не так, и тем труднее становится за всем этим следить. Появляются зависимости одних событий от других. Давайте запомним это, чтобы вспомнить позже.
Намерения в программировании
Читая код, очень важно понимать – какое намерение здесь осуществляется? Понимание намерения гораздо важнее, чем синтаксис кода.
Вот пример одного и того же кода, но с разными комментариями:
- a += 5; // прибавить 5 к переменной a
- a += 5; // пополнить счёт на 5 рублей
В общем-то "прибавить 5 к переменной а" это тоже намерение, но оно формальное, оно не отражает сути того, что мы хотим сделать, и его описание не несёт никакой пользы.
Хорошо, когда истинное, а не формальное намерение видно просто по коду, без необходимости в комментариях.
А что, если в коде, как и в жизни, реализуются сразу несколько параллельных намерений? Опять же в целях оптимизации: раз уж мы в этом месте что-то делаем, то можно сделать и что-то ещё.
Никаких чудес не случится. Такой код закономерно окажется сложнее в понимании, и вариантов что-то в нём сломать будет больше.
Перейдём теперь к коду из игры "5 букв", который вызвал критику. Я убрал оттуда лишние детали и добавил комментарии.
Я также обозначил два намерения, которые осуществляются в коде. Это опрос игрока, а затем проверка результата. Как можно видеть, они осуществляются синхронно. Сначала одно, потом другое.
Это позволяет рассматривать их как два самостоятельных блока. Например, можно каждый из них заключить в функцию:
И каждую функцию отдать на реализацию своему программисту. Программисты будут работать, не завися друг от друга, и будучи связаны только типами передаваемых и возвращаемых параметров.
Но можно заметить, что проверка "если слово отгадано правильно" присутствует в обоих намерениях. В первом, чтобы прекратить цикл опроса, а во втором, чтобы обработать результат.
Это одна и та же проверка, но используется она для разных намерений. Это не нравится комментатору, и он пишет следующее:
Хм... Давайте зафиксируем:
- учим плохому
- двойная проверка ok_cnt это неэффективно
- нам предлагается вывод сообщения о победе внести внутрь цикла, это будет, соответственно, эффективно
Сделаю как сказано:
Теперь я обрабатываю победу игрока прямо там, где происходит выход из цикла. Но... вторая проверка никуда не делась, потому что кроме победы надо обработать и проигрыш тоже. То есть эффективность кода не повысилась, а намерение 2 теперь просочилось в намерение 1. Мы имеем параллельную обработку намерений. Я теперь не могу разбить их на две функции и каждую функцию отдать своему программисту.
Но у комментатора есть ещё замечание:
Оказывается, оператор break, который специально придуман, чтобы быть внутри for, внутри этого самого for смотрится коряво. Почему? Какие-то личные заморочки или навязанное мнение от какого-то ютуб-гуру. Конечно, он не смотрится коряво, с чего ему вообще смотреться коряво? Но ладно, мне не сложно поменять:
Итак, теперь нет корявого break, но зато появилась корявая (я так решил, я ведь так тоже могу) проверочная часть цикла for:
try_cnt < 7 && ws.ok_cnt < LETTER_CNT
В остальном ничего не изменилось – все проверки по-прежнему на месте. А бонусом внимательные читатели заметят, что их стало даже на одну больше, чем было.
Но у комментатора есть решение:
Охохох... давайте писать реализацию.
Вот функции:
Вот как изменился сам цикл:
Сделано всё по рекомендациям!
Давайте посчитаем, сколько теперь нужно проверять условий:
- Функция check_guessed() проверяет одно условие, а цикл проверяет результат, который вернула функция – это 2 условия
- Функция check_tries() проверяет 1 условие, а цикл проверяет результат, который вернула функция – это ещё 2 условия
Итого 4 условия. Их стало меньше? Нет! Их стало больше в два раза в каждом шаге цикла! Не говоря уж о том, что появились два вызова функций. Эффективность кода просто зашкаливает.
Но это ещё не всё. А что там с нашими намерениями, с нашими бедными несчастными намерениями?
Как я вообще собираюсь кому-то объяснять, что обработка результата делается в двух разных функциях, которые вообще используются для организации цикла? Что это за адская дичь, господи?
Но у комментатора есть ещё идеи:
Что ж, сгорел сарай, гори и хата. Сделаем:
И цикл с дальнейшей проверкой:
Все условия остались как и были – то есть цель повышения эффективности путём сокращения условий не достигнута. Появился дополнительный массив строк и индекс, появилась передача в функцию параметра для изменения по указателю (морщится).
Хорошего тут ровно одно: обратно появилось хоть какое-то подобие намерения 2. Однако оно уже не выглядит как "обработка результата". Оно выглядит как "вывод строки из массива по индексу". То есть только это, и всё. Это не то намерение, которое было изначально.
Если мы хотим обработать результат – выдать игроку приз, сохранить его рекорд и т.п. – нам всё равно придётся проверять, победил он или проиграл.
Но у комментатора есть ещё одна идея:
Это нам, значит, для самостоятельного изучения :)))
То есть, как строки хранятся в массиве и выбираются по индексу, который неявным образом меняется по указателю в какой-то функции, так же предлагается хранить указатели на функции и выбирать их по индексу. Одна из функций будет обрабатывать победу, другая проигрыш.
Всё это из-за одного (одного, единичного, не в цикле) якобы лишнего условия.
С вашего позволения, я не буду это делать.