Comments 179
def has_access(user) -> bool:
if not user.is_authenticated():
return False
# пока что все понятно, функция проверяет не надо ли пользователю доступ
# закрыть, и если все проверки провалятся, то она, видимо, вернет True
if user.is_superuser():
return True
# а нет, всё плохо. Чтобы понять что функия вернет, мне надо
# вчитываться в каждое условие и что при этом возвращается
if user.has_children():
return False
# окей, видимо, проверка на суперпользователя была исключением,
# сейчас все пойдет как надо
if not user.has_active_subscription():
return True
if user.is_baba_tanya():
return False
# ААА! Я запутался.
return True
# вроде False будет если не залогинен или
# не суперпользователь или с подпиской? Надо перечитать
Да вроде в этом коде всё читабельно (если комментарии убрать).
Сложность этого кода растёт из сложности бизнес-логики, и сильнее упростить его не меняя бизнес-логику не выйдет. Тут остается только трясти заказчика и аналитиков, почему это правило has_children приоритетнее чем !has_active_subscription, и так ли важна проверка is_baba_tanya.
Если у нас более сложные условия — то код можно упростить вынеся в отдельные функции эти самые условия.
В архитектуру нужно закладывать не только внешние интерфейсы модуля, но исключения которые оно будет генерить. Не все возможные исключения которые в нем есть, а именно та часть, которая может потребоваться наружному коду. Это такая же часть контракта как и интерфесы.
Поэтому проблемы возникают если такое проектирование и такие контракты не создаются.
Так себе идея — отвратительно скажется на производительности же. Ну а причины можно енумом (в java) или аналогами (в других языках) отдавать.
Если вы в своих приложения мониторите время ответа ваших бэкендов (вы ведь это делайте да?), то легко увидите, что ответы с ошибками отрабатывают быстрее чем нормальный медианный ответ для этого же ресурса.
Топик (статья) озаглавлена "про ёлсы", фактически — про фейл-фаст. Примеры — да на пхп. Комментарии — так же идут без жёсткой привязки к одному языку. Ну и да — я сомневаюсь, что реализация исключений в пыхе настолько уж другая, что внезапно мой комментарий превратится в тыкву.
Упали с исключением и отдали текст ошибки на клиент это нормальная ситуация с допустимой производительностью.Это правда нормальное поведение при обработке пользовательского ввода? Обработка исключений всё-таки довольно дорогая процедура.
А давайте спросим нагруженных ребят, которые даже собственные диалекты php пилят, чтобы набрать нужную производительность на имеющемся базисе — нормально ли это. Почему-то мне кажется что они с вами не согласятся.
Это тех, которые наговнокодили кучу кода, а потом вместо того, чтобы разбирать это дерьмо и, например, переписать на боле подходящем для перфоманса языке, пилят костыли к языку?
Решение излишней вложенности скорее относится к концепции раннего возврата, а не к защитным условиям
Например, использовать это вариант
foreach ($list as $element) {
if (!$condition) {
continue;
}
// логика
}
Вместо
foreach ($list as $element) {
if ($condition) {
// логика
}
}
Ну и те примеры, которые предложены автором
$flag = proverka1();
if(!$flag){ /* если не выполнилась проверка 1 */}
$flag2 = false;
if($flag) { /* делаем шаг 1 */ $flag2= proverk2();}
if($flag1 && !$flag2){ /* если должны были сделать шаг 1 , но проверка 2 не прошла */}
if($flag2){ /* делаем шаг 2, потому что проверка 2 успешная и потому что сделали шаг 1 */}
Ниже пример боевого кода, из которого убрана большая часть логики, но оставлен скелет из if-ов, как видите код без ветвлений совершенно и все проверки на флагах.
public function publishOne($identity)
{
$output = ['success' => false];
$response = CIBlockElement::GetByID($identity);
$isReadSuccess = BitrixOrm::isRequestSuccess($response);
if (!$isReadSuccess) {
$output['message'] = 'Fail request construction';
}
$construct = false;
if ($isReadSuccess) {
$construct = $response->Fetch();
}
$isConstructFound = BitrixOrm::isFetchSuccess($construct);
if ($isReadSuccess && !$isConstructFound) {
$output['message'] = 'Construction not found';
}
$isExistsChild = false;
if ($isConstructFound) {
$response = CIBlockElement::GetList([], $filter,
false, false, $select);
$isExistsChild = BitrixOrm::isRequestSuccess($response);
}
$child = false;
if ($isExistsChild) {
$child = $response->Fetch();
}
$gotChild = BitrixOrm::isFetchSuccess($child);
$childId = 0;
$childPermit = 0;
$gotPublicPermit = false;
if ($gotChild) {
$childId = $child['ID'];
$childPermit = $child['PROPERTY_PERMIT_OF_AD_VALUE'];
$gotPublicPermit = !empty($childPermit);
}
$isSuccessDelete = false;
if ($gotPublicPermit) {
$isSuccessDelete = CIBlockElement::Delete($childPermit);
}
if ($gotPublicPermit && !$isSuccessDelete) {
$output['message'] = 'Fail delete published permit;';
}
if ($gotPublicPermit && $isSuccessDelete) {
$output['message'] = 'Success delete published permit;';
}
if ($gotChild) {
$isSuccessDelete = CIBlockElement::Delete($childId);
}
if ($gotChild && !$isSuccessDelete) {
$output['message'] = 'Fail delete published permit;';
}
if ($gotChild && $isSuccessDelete) {
$output['message'] = 'Success delete published permit;';
}
$source = [];
$published = 0;
if ($isConstructFound) {
$source = $construct;
$published = (new CIBlockElement())->Add($construct);
}
$hasCopy = !empty($published);
if ($isConstructFound && !$hasCopy) {
$output['message'] = ' Fail copying of construction;';
}
$values = [];
if ($hasCopy) {
$output['success'] = true;
$output['message'] = ' Success copying of construction;';
CIBlockElement::GetPropertyValuesArray($values,
$source['IBLOCK_ID'], $filter, [],
['GET_RAW_DATA' => 'Y']);
}
$gotValues = !empty($values);
if ($hasCopy && !$gotValues) {
$output['message'] = ' Fail read construction properties';
}
$properties = [];
if ($gotValues) {
$values = current($values);
foreach ($values as $key => $value) {
if (!empty($value['VALUE'])) {
$properties[$key] = $value['VALUE'];
}
}
$properties['original'] = $identity;
}
$answer = null;
$hasPermit = !empty($properties)
&& !empty($properties['permit_of_ad']);
if ($hasPermit) {
$permitId = (int)$properties['permit_of_ad'];
$answer = CIBlockElement::GetByID($permitId);
}
$hasAnswer = !empty($answer) && $answer->result !== false;
if ($hasPermit && !$hasAnswer) {
$output['message'] = 'Fail read permit';
}
$permit = [];
if ($hasAnswer) {
$permit = $answer->Fetch();
}
$publishedPermit = 0;
$gotPermit = !empty($permit) && $permit !== false;
if ($gotPermit) {
$publishedPermit = static::copyElement($permit,
$this->pubPermits);
}
if ($gotPermit && empty($publishedPermit)) {
$output['success'] = false;
$output['message'] = 'Fail copying of permit';
}
if ($gotPermit && !empty($publishedPermit)) {
$output['message'] = ' Success copying of permit';
}
if ($gotPermit && !empty($publishedPermit)
&& !empty($properties)) {
$properties['permit_of_ad'] = $publishedPermit;
}
if (!empty($properties)) {
CIBlockElement::SetPropertyValuesEx($published,
$this->pubConstructs->getBlock(),
$properties, ['NewElement' => true]);
}
return $output;
}
Битрикс… Как вспомню так вздрогну.
И ты ещё такой в курсе что закупку уже один раз провели, но прошлый раз разработчики сделали без Битрикса, и у них работу просто не приняли как не соответствующую ТЗ, поэтому закупку провели повторно.
И ты такой директору говоришь «Битрикс это антипаттерн, не будем его использовать» :)
Работать программистом в госструктуре или выполнять гос заказ в нашей стране тоже является антипаттерном, к сожалению
Рабский труд запрещен Женевской конвенцией. Если вас заставляют этим заниматься, моргните два раза.
Кстати, очень интересно, что по вопросу госзакупок, навязывающих продукцию конкретного вендора, думает ФАС.
И конечно покрытие тестами в помощь.
Конкретно этому коду от силы две недели, при чём в прошлую пятницу заказчику показали релиз, и всё понравилось, а в четверг мы подписываем акт приёмки и ни какой дальнейшей разработки не будет.
Смысл тесты писать? для фирмы которая через *цать лет наш код будет дорабатывать? благородное конечно дело, но мой директор не одобрит.
При чём во втором строчек побольше чем в этом и логика поинтересней.
в четверг мы подписываем акт приёмки и ни какой дальнейшей разработки не будет.
Смысл тесты писать? для фирмы которая через *цать лет наш код будет дорабатывать?
Я б не смог работать в таких условиях… Но так или иначе, зачем приводить тут как образец кусок одноразового кода?
Понятно что его можно декомпозировать на 4 части, но я тут не учу SOLID-у, вот даже такой стиль написания кода он сам способствует декомпозиции, сделали — проверили, сделали — проверили, каждую пару выполнения шага и обработки результата можно в отдельный метод кидать. Но.
Началось всё с того, что товарищ попросил пример кода, я ему пример кода привёл, понабежали чудаки которые стали судить код вообще с других позиций, смотреть на него со своей колокольни, развели оффтоп, а минусов я отхватил :)
Если кто то не понимает что такое эффективность, что такое требования по качеству и требования по времени разработки, то успехов этим людям с производством ПО.
Учитывая что проверки на флагах, сделать это легко.
Скажите пожалуйста, что вас не устраивает в этом коде?
Количество строк в методе?
ок, код не декомпозирован и на то есть причины, описанные в моих коментах, что следующее?
Использование статических методов?
это методы Битрикса, мне свою обёртку на них написать надо?
Что вам ещё не по душе? давайте проведём код ревью, с учётом того что на метод и его полную отладку у нас одни сутки, выйти за этот бюджет времени нельзя.
Код предельно гранулирован, шаги можно легко менять местами, можно выкидывать, можно добавлять новые, для этого надо только подправить проверку до вставки кода и поправить проверку после вставки кода.
Только для вас, и только если вы помните этот код. В случае работы с другими людьми или если этот код придётся править через некоторое время это будет жуткий геморрой. Даже для джуна это ужасный код.
Всё просто, это спагетти код.
Которым местом? Что такое спаггети код? Это когда у нас переменные используются в неожиданных местах кодовой базы. Где тут такое?
Все переменные используются там где они объявлены, переменные хорошо локализованы.
приходит новый человек
Новый человек не придёт ближайший год, а скорее вообще ни когда не придёт. Это заказная разработка в очень сжатые сроки, у людей была задача и мы эту задачу решили, и процессы у них не поменяются ближние пять лет точно.
Какая причина менять код? Причина в том что в вашем опыте код переписывается переписывается и переписывается? не надо думать что везде как у вас, то есть думайте что хотите конечно, но когда я что то делаю, я делаю это под условия заказчика, а не под свои представления о прекрасном.
Откуда он узнает что какие-то переменные используются только в ограниченном месте
Я пользуюсь Штормом, я ставлю курсор на переменную и Шторм мне подсвечивает в боковой панели все строки кода где эта переменная используется, если вы так не можете то сделайте поиск, это умеют все редакторы, но например когда я копаюсь в исходниках Битрикса мне приходиться смотреть использование переменных поиском, потому что там весь скролл бар в варнингах и отметки использования переменной не видно.
и какие из них можно переиспользовать и не будет ли конфликтов?
То есть если пришёл человек и хочет поменять логику метода, то ему в логике разбираться не надо, он просто фигачит любой код в любом месте и всё начинает работать как он задумал?
Что вам помешало выделить отдельные куски в функции, это вообще никак не повлияет на скорость написания кода, зато многократно повысит читаемость, расширяемость, и поддержку.
Условия задачи заметили? время на написание и полную отладку одни сутки. При чём на старте у тебя просто «Нужен метод для Публикации одной рекламной конструкции», что это означает? а вот пойди разберись. В итоге выяснилось что надо сделать иногда 4 операции, а иногда 8. Тут с постановкой фиг пойми что и как, а вы хотите ещё и проектирование кода в шоколаде получить. Нет у меня лишнего часа на декомпозицию этой простыни на 4-8 методов.
Заработало и ура в бэклоге ещё тонны не реализованных требований.
Но вы же этого не знаете, и из своего опыта меня судите, адекватно это?
Не знаю броду, не суйся в воду, но вы конечно свободны сделать по своему :)
Которым местом? Что такое спаггети код?
Это запутанный код в котором трудно разобраться.
Новый человек не придёт ближайший год, а скорее вообще ни когда не придёт. Это заказная разработка в очень сжатые сроки, у людей была задача и мы эту задачу решили, и процессы у них не поменяются ближние пять лет точно.Как одноразовый код в принципе годится. Только с какой целью вы приводили его как пример тогда не понятно.
Я пользуюсь Штормом, я ставлю курсор на переменную и Шторм мне подсвечивает в боковой панели все строки кода где эта переменная используется
И в любом случае придётся разбираться для чего эта переменная используется прыгая по методу и держа при этом в памяти десяток других переменных.
То есть если пришёл человек и хочет поменять логику метода, то ему в логике разбираться не надо, он просто фигачит любой код в любом месте и всё начинает работать как он задумал?
Разобраться в логике метода из 10 строчек или из сотни разница как бы большая.
Нет у меня лишнего часа на декомпозицию этой простыни на 4-8 методов.
Ловите, у меня ушло на это 15 минут. А если потратить как вы говорите час, то можно было бы сделать ещё лучше. (надеюсь не напортачил, php c универа не юзал)
public function publishOne($identity)
{
$output = ['success' => false];
$construct = $this->get_construct($output, $response);
$child = $this->get_childs($response, $construct->isConstructFound());
$this->process_childs($output, $child);
$properties = $this->get_properties($output, $construct);
$permit = $this->get_permit($output, $properties);
$this->process_permit($output, $properties, $permit);
if (!empty($properties)) {
CIBlockElement::SetPropertyValuesEx($properties->get_published(),
$this->pubConstructs->getBlock(),
$properties, ['NewElement' => true]);
}
return $output;
}
Вообщем, как одноразовый код написанный за несколько минут который потом не будет поддерживаться или выбросится годится, но повторюсь зачем вы этот код выставили на обозрение, неужели вы не понимаете что он ужасен?
Хороший пример как можно писать без ветвлений.
В это коде очень много условий, но нет ни одного значимого ветвления.
С другой стороны если у вас на рефакторинг ушло 15 минут, то о чём речь? это не показатель того что это не спаггети код? Вы в каждый метод отдаёте всего две переменные, и только один раз приходиться отдать три, что ещё надо для счастья?
Из минусов: в результат вашего рефакторинга появилось состояние у класса, но бог с ним, если вы считаете это допустимым.
С другой стороны если у вас на рефакторинг ушло 15 минут, то о чём речь? это не показатель того что это не спаггети код?
Нет это не показатель того что это не спагетти код, просто мне приходилось работать с ещё более ужасными вещами например bitbucket.org/anderslanglands/alshaders/src/master/alSurface/alSurface.cpp
Из минусов: в результат вашего рефакторинга появилось состояние у класса, но бог с ним, если вы считаете это допустимым.
Это где?
$construct->isConstructFound()
$properties->get_published()
просто вместо переменных завели два класса с состоянием.
То что раньше было просто массивами, теперь стало массивом с флагом.
метод CIBlockElement::SetPropertyValuesEx() точно сможет обработать новые $properties, которые теперь умеют get_published()?
вообщем я же не говорю что тут не чего рефакторить, просто не за 5 минут это делается, поэтому я не стал на это время тратить. И сейчас время тратить не хочу.
Если прогнать ваш код через PHP Mess Detector, он выдает
src/Bitrix.php:12 The method publishOne() has a Cyclomatic Complexity of 43. The configured cyclomatic complexity threshold is 10.
src/Bitrix.php:12 The method publishOne() has an NPath complexity of 7739670528. The configured NPath complexity threshold is 200.
Банальное исправление блока
$gotChild = BitrixOrm::isFetchSuccess($child);
$childId = 0;
$childPermit = 0;
$gotPublicPermit = false;
if ($gotChild) {
$childId = $child['ID'];
$childPermit = $child['PROPERTY_PERMIT_OF_AD_VALUE'];
$gotPublicPermit = !empty($childPermit);
}
$isSuccessDelete = false;
if ($gotPublicPermit) {
$isSuccessDelete = CIBlockElement::Delete($childPermit);
}
if ($gotPublicPermit && !$isSuccessDelete) {
$output['message'] = 'Fail delete published permit;';
}
if ($gotPublicPermit && $isSuccessDelete) {
$output['message'] = 'Success delete published permit;';
}
if ($gotChild) {
$isSuccessDelete = CIBlockElement::Delete($childId);
}
if ($gotChild && !$isSuccessDelete) {
$output['message'] = 'Fail delete published permit;';
}
if ($gotChild && $isSuccessDelete) {
$output['message'] = 'Success delete published permit;';
}
на
if (BitrixOrm::isFetchSuccess($child)) {
$childId = $child['ID'];
$childPermit = $child['PROPERTY_PERMIT_OF_AD_VALUE'];
if (!empty($childPermit)) {
if (CIBlockElement::Delete($childPermit)) {
$output['message'] = 'Fail delete published permit;';
} else {
$output['message'] = 'Success delete published permit;';
}
}
if (CIBlockElement::Delete($childId)) {
$output['message'] = 'Success delete published permit;';
} else {
$output['message'] = 'Fail delete published permit;';
}
}
PHPMD уже выдает
src/Bitrix.php:12 The method publishOne() has a Cyclomatic Complexity of 36. The configured cyclomatic complexity threshold is 10.
rc/Bitrix.php:12 The method publishOne() has an NPath complexity of 83607552. The configured NPath complexity threshold is 200.
Тут и код получается короче раза в два и сразу понятно что он делает.
Ощущение, что ваш код перетерпел кучу рефакторинга, потому что выгялдит он ну очень неествественно.
Но вы же этого не знаете, и из своего опыта меня судите, адекватно это?
Вы понимаете, что вы говорите что "фигак-фигак и в продакшен" — самая лестная характеристика упомянутого вами подхода? Что он не применим в сколько-нибудь серьёзной разработке? Что это крайний случай для клиентов, которые хотят сэкономить даже на спичках? И что индустрия это не должно поддерживать, как минимум на уровне осуждения подхода? Вот об этом всём и говорят ваши собеседники. Да вы можете успешно говнокодить, если вам это не претит, но давайте хотя бы вы не будете этим хвастаться?!
У меня попросили пример кода без ветвлений, привёл пример кода, у меня просили пример идеального кода? нет. Какие ко мне претензии? вы за контекстом не следите, а я в контексте отвечаю.
Инвертировать проверки, где нужно, добавить ранние выходы — и всё будет уже не так плохо.
Ранние выходы можно сделать исключениями, но и простой `return` будет гораздо лучше тысячи флагов.
Останется немного подумать, над выделением методов из кусков этого кода по смыслу.
Ну и исключения тут вероятно подойдут лучше.
Этот комментарий не руководство к действию(прод всё таки), просто о том, как оно могло бы быть.
Я стреляюсь каждый раз когда мне приходиться отлаживать код, который вы приводите в качестве примера.
Вот комент под этой статьёй, который я сто процентов поддерживаю, человек знает о чём речь.
В функции должен быть один ретурн, любые выражения должны быть вычислены до того как результат вычисления будет передан в какой либо оператор или функцию, что бы во время дебага видеть вычисленные значения, а не только результат их применения. Надо понимать что ты передаёшь в if().
Надо понимать что ты передаёшь в функцию. Надо вообще понимать с какими значениями работает твой код, без дополнительных телодвижений, когда мы передаём в if() выражение во время отладки мне придётся делать eval() средствами IDE что бы понять что уйдёт в if(), для операций чтения из «стрима» это не идемпотентная операция.
Для highload это наверное не оптимальный подход, но я не highload программирую и не на асемблере пишу, для меня на первом месте моё удобство как разработчика.
PS
Вы в коде 100500 фэйлов расставили, но по бизнес логике это не фейлы, это «легковесное» логирование для отладки, если какой то кусок кода выполнять не надо, то это флагами разруливается.
Хорошо, если язык позволяет конструкции типа on-exit, а если нет?
Строго говоря, любая процедура должна иметь единственную точку входа и единственную точку выхода. Тогда ее логика понятна и прозрачна.
А уж как реализовать множественные проверки… Тут надо включить мозг и подумать.
Действия на выходе (если они вдруг нужны — на практике это редкость) выполняются функцией, в которую вкладывается fail-fast функция. Не надо городить антипаттерны из-за какой-то мифической фигни.
Мне вот интересно, вы на кристалле (МК) разработку ведёте или на платформах (веб/системное) у которых стек вполне себе приличный (мне вот ни разу без рекурсии его выбрать не удалось)? Себестоимость входа в ещё одну функцию так же заведомо на пару порядков ниже предложенных мельком в статье исключений.
Каждый продукт проходит 4 уровня тестирования — компонентное, интеграционное, нагрузочное и бизнес.
И на нагрузочном тестировании зачатую не проходят более безобидные на первый взгляд вещи чем лишний уровень стека — лишнее переоткрытие файла, лишнее чтение из dtaara, лишние операции с датой-временем, лишний запуск/остановка фонового процесса (submitted job)
То, что вы своим кодом не можете выбрать стек ничего не значит. Скорее всего, пока работает одна ваша задача все просто замечательно. До тех пор, пока ваша задача не становится одной из тысяч, работающих одновременно. А вот когда в пике загрузка сервера переваливает за 90% (а то и 95%), начинаются изыскания на тему где и как можно скроить и кто жрет лишнего.
В общем, мне лень спорить по пустому и рассказывать азы. Давно живу, повидал много на самых разных платформах.
более безобидные на первый взгляд вещи чем лишний уровень стека — лишнее переоткрытие файла, лишнее чтение из dtaara, лишние операции с датой-временем, лишний запуск/остановка фонового процесса (submitted job)
Вот вы сейчас прям страшно насмешили. Не знаю как в вашем языке, но в java работа со временем — очень дорогая операция. Не знаю, что такое dtaara, но переоткрытие файла — это работа с ФС ОС (которая даже буферизированная, без прямого обращения к диску — будет безумно дорогой). Что уж говорить про создание нитки или процесса (не знаю уж, эквиваленцией чего является ваш submitted job). И вы вот все эти тяжеловесные операции сравнили с уходом по стеку на один уровнь глубже! Да, разворачивать стек — тяжёлая, дорогая операция (именно этим плохи исключения — они буферизируют стек и могут ещё и разворачивать его для вывода ошибки). А вот войти в ещё одну функцию — что-то я сомневаюсь, что у вас настолько плотные математические алгоритмы. В общем — начните оптимизацию с чего-то более полезного (читайте частого) и ресурсоёмкого, а потом мне говорите бред про азы.
А можно комментировать так, чтобы не приходилось догадываться? Если вы считаете, что стектрейс тяжёлая операция, то я с вами согласен:
Да, разворачивать стек — тяжёлая, дорогая операция (именно этим плохи исключения — они буферизируют стек и могут ещё и разворачивать его для вывода ошибки).
Если нет, то поищити бенчмарки для вашего языка.
PS "дамп стектрейса" даже звучит ужасно. Есть стектрейс (трассировка стека, развёрный стек). Есть дамп памяти. Вывод развёрнутого стека в лог — не есть дамп.
Например. Настолькое приложение. Форма ввода с кнопкой ОК, выполняются какие-то проверки, вычисления, записи, чтения, и в итоге выполняется COMMIT в базу данных. Если происходит исключение где-то между ОК и COMMIT, то оно всплывает аж на уровень кнопки ОК, то есть обработчика событий GUI. И не факт, что перехватывается и обрабатывается должным образом. В результате, в программе проблема, но о ней могут не догадываться долгое время, а на поиск и исправление уйдет уйма времени.
А если внутренняя архитектура разбита на несколько коротих «конвееров» команд, то все намного проще и надежнее. Например, нажатие ОК не выполняет какой-то код, а только кладет команду и данные формы в очередь. Отдельный обработчик берет данные из очереди, проверяет и передает в следующую очередь. Затем следующий обработчик берет данные из очереди, производит вычисления, помещает результат в следующую очередь. Казалось бы, так все намного сложней и медленней. Но на деле, такая «микросервисная» архитектура намного проще в отладке, устойчивей и производительней на многозадачном железе. Исключение всегда всплывает на уровень консьюмера конкретного конвеера, значительно уменьшая размер контекста, сужая область и глубину поиска причины сбоя. Особенно ценно это в хардкорном продакшене, где нет доступа для отладки и приходится работать по продиктованному голосом адресу исключения. Даже не всегда есть возможность посмотреть логи и стектрейс.
Вы написали полную чушь.
Во-первых, для того, чтобы поймать исключение вовремя, совершенно нет необходимости делать конвейеры и очереди сообщений, достаточно написать try-catch!
Во-вторых, начиналось это обсуждение с того, что разбиение одной функции на две — якобы очень затратное изменение. Но всё, что вы написали, никоим образом не зависит от числа функций; ведь число точек, где ловятся исключения, от подобных рефакторингов-то не меняются!
Да с чего проще-то?
А как из одного следует другое?
Вот после таких деятелей, боящихся уровней стека, приходится дебажить методы на 15к строк кода с одним ретерном в конце.
Понимаете в чём дело… это от арихтектуры зависит. Возьмём стектрейс в java: он может быть суммарно и на 1000 и на большее количество строк, но
- точка происшествия на самом верху. Возможен последовательный проброс исключений (это когда более "хардовое" ловят и выкидывают более общее, в этом случае вываливается цепочка исключений)
- он хорошо структурирован и поиск по нему не вызывает проблем
- стектрейс и дебаг вот вообще никак не связаны. Стектрейс описывает проблему и выдаёт данные, которые догодался запросить программист (в виде сообщения исключения).
А вот дебаг на нескольких потоках — это знатное такое извращение. Не то, чтобы сложно, но как минимум неудобно. А в одном потоке — цепочку брекпоинтов, если очень надо — вотчеров натыкал и смотришь, что происходит в конкретных точках...
Если происходит исключение где-то между ОК и COMMIT, то оно всплывает аж на уровень кнопки ОК, то есть обработчика событий GUI. И не факт, что перехватывается и обрабатывается должным образом. В результате, в программе проблема, но о ней могут не догадываться долгое время, а на поиск и исправление уйдет уйма времени.
В java все исключения UI-ных приложений, вываливающиеся в поток управления UI роняют приложение и попадают в лог. У вас как-раз таки проблема, что UI отделён в отдельный поток и не узнаёт о проблеме корректно. Ну и вы предлагаете всё это ещё больше усугубить.
Но на деле, такая «микросервисная» архитектура намного проще в отладке, устойчивей и производительней на многозадачном железе.
Наивные мечтанья — синхронизация данных между несколькими потоками штука нифига не бесплатная. Распределённая архитектура (микросервисы) хороша при очень лимитированном общении сервисов друг с другом. В такой ситуации и ethernet не становится преградой.
Исключение всегда всплывает на уровень консьюмера конкретного конвеера, значительно уменьшая размер контекста, сужая область и глубину поиска причины сбоя. Особенно ценно это в хардкорном продакшене, где нет доступа для отладки и приходится работать по продиктованному голосом адресу исключения. Даже не всегда есть возможность посмотреть логи и стектрейс.
Пользуйтесь языками, где архитектура предполагает внятные стектрейсы и логи. Тогда не придётся гадать. И да я знаю, что подробная инорфмация о текущем состоянии энтерпрайзной системы будет закрыта от разработчиков (из соображения приватности продовых данных), но и неприватной информации можно извлечь достаточно, чтобы хотья бы примерно понимать, что же там произошло.
стектрейс и дебаг вот вообще никак не связаны. Стектрейс описывает проблему и выдаёт данные, которые догодался запросить программист (в виде сообщения исключения)
Стектрейс это часть дебага, важный инструмент. Без него все намного сложнее. Позволяет видеть истоки проблемы, откуда она пришла.
Насчет отладки распределенной архитектуры. Простой пример: отдельно сервис (или сервер) БД, сервис HTTP и сервис приложения. В этом случае не нужно забивать голову деталями БД и HTTP при отладке приложения, это отдельные «черные ящики». В случае сбоя в БД или HTTP достаточно перезапустить только отдельный сервис, и область поиска причины сбоя значительно сужается. А представьте, что будет, если HTTP и БД обрабатываются в одном приложении, в одной цепочке вызовов? Малейший сбой уронит всю систему. Какой бы хороший ни был язык программирования, проблема будет в дизайне системы.
А что вы называете архитектурой?
Пользуйтесь языками, где архитектура предполагает внятные стектрейсы и логи.
Вроде и так всё предельно ясно написал. Есть язык, у него есть "платформа" и "окружение". "Окружение" — фактически среда исполнения. для системных языков это ОС, для jvm-запускаемых это, собственно, jvm, для php — это интерпретатор php + сервис в котором он крутится (если он не самостоятельный). "Платформа" — доступный SDK, библиотеки и компилятор/транслятор. Насколько мне известно, получить вменяемый стектрейс (с указанием строки в файле, где произошло исключение) для того же c++ задача не самая тривиальная. А для java это — базовый функционал.
Стектрейс это часть дебага, важный инструмент. Без него все намного сложнее. Позволяет видеть истоки проблемы, откуда она пришла.
Стектрейсы это часть логов. Да это важный инструмент, но к дебагу он относится никак. При грамотно реализованном логировании исключений (и отсутствию проблем с логированием внутреннего состояния… что обязательно будет, если работаешь с приватными данными) дебаг может и не понадобиться. Дебаг нужен, когда не удаётся эмпирически понять "а что же тут происходит-то вообще".
Насчет отладки распределенной архитектуры. Простой пример: отдельно сервис (или сервер) БД, сервис HTTP и сервис приложения. В этом случае не нужно забивать голову деталями БД и HTTP при отладке приложения, это отдельные «черные ящики». В случае сбоя в БД или HTTP достаточно перезапустить только отдельный сервис, и область поиска причины сбоя значительно сужается. А представьте, что будет, если HTTP и БД обрабатываются в одном приложении, в одной цепочке вызовов? Малейший сбой уронит всю систему. Какой бы хороший ни был язык программирования, проблема будет в дизайне системы.
Ну вот а теперь следите за руками:
- 3 ваших сервиса хттп, основной и БД.
- ситуация — запись не попадает в БД.
- раз понадобился дебаг — очевидно логов нет вообще (ну для такой простой ситуации иначе не получается)
- включаемся дебагом в сервис БД — а там шквал разных запросов из разных мест сервиса с бизнес-логикой
- включаемся дебагом в сервис с бизнес-логикой, ловим в нужной нам точке — всё работает корректно!
- что делать дальше? А приходится снова ловить БЛ и перед точкой запроса включать дебаг на сервисе базы и тут же отпускать сервис БЛ… после чего среди пары десятков/сотен/… запросов в БД ловить нужный нам. (вы же сами ратовали за модель "очереди сообщений").
И вот это всё — вместо того чтобы просто пройтись в глубину за точку… Я никак не могу назвать это более удобным дебагом.
В случае же сбоя сервиса… ну у jvm всё просто — в большинстве случаев с перезапуском справится контейнер, в отдельных случая — софтверным watch dog-ом. Это если брать вебное приложение. С UI-ным сложнее, но тоже можно что-то попытаться сделать. В пыхе, насколько мне известно, ситуация не хуже — в большинстве случаев падение одного запроса не угробит весь сервис.
Вы приводите хороший пример, где запись не попадает в БД. Но зачем-то предлагаете тыкаться в БД дебагом. Это нужно делать в последнюю очередь. Для начала нужно глянуть, что поступает на вход сервиса БД, текст запросов. Да, это традиционно делается через логи. И включить запись логов намного проще, чем влезать в сервис дебагом. Кроме того, нужно еще глянуть, что приходило на вход сервиса бизнес-логики.
То есть, первый уровень отладки — это проверка входящих-выходящих данных каждого сервиса, чтобы определить, в каком сервисе проблема и на каких исходных данных. И уже потом лезть в конкретный сервис, ставя брейкпоинты и ассерты на конкретный тип данных.
В этом случае не придется изучать сотни запросов в дебагере. И не придется каждый раз перезапускать после сеанса отладки всю монолитную систему, достаточно перезапускать только конкретный сервис. Сфабриковать и подсунуть тестовые данные (mock) тоже проще в отдельный компонент системы, чем монолитную систему.
Напомню, что изначально речь шла о стеке и цепочке вызовов. И я на примере классического backend показал, что длинные цепочки вызовов усложняют отладку.
Попытались показать. Не получилось. Для java стек-трейсы легко могут быть в 100 и больше вызовов. При этом с ними вполне удобно работать.
Вы приводите хороший пример, где запись не попадает в БД. Но зачем-то предлагаете тыкаться в БД дебагом. Это нужно делать в последнюю очередь. Для начала нужно глянуть, что поступает на вход сервиса БД, текст запросов. Да, это традиционно делается через логи. И включить запись логов намного проще, чем влезать в сервис дебагом. Кроме того, нужно еще глянуть, что приходило на вход сервиса бизнес-логики.
Вы сами предолжили делать разными сервисами. Ну вот сервис работы с БД я и выделил в отдельный. Вот в него я и предлагал тыкаться дебагом "сначала". А если говорить про саму СУБД — то в зависимости от конкретной — разницы смотреть в логах или лезть в дебаг может и не быть, но всё же СУБД — это последняя точка, куда имеет смысл лезть за инфой, сперва лучше убедиться, что остальные корректно отрабатывают.
То есть, первый уровень отладки — это проверка входящих-выходящих данных каждого сервиса, чтобы определить, в каком сервисе проблема и на каких исходных данных. И уже потом лезть в конкретный сервис, ставя брейкпоинты и ассерты на конкретный тип данных.
И будете вы часами разгребать логи. Это нужно, чтобы определиться с изначальной проблемой (посмотреть на логи прода/теста). А дебаг уже должен быть спланирован по конкретным точкам, через которые бизнес-flaw тащит данные и запускает логику.
В этом случае не придется изучать сотни запросов в дебагере. И не придется каждый раз перезапускать после сеанса отладки всю монолитную систему, достаточно перезапускать только конкретный сервис. Сфабриковать и подсунуть тестовые данные (mock) тоже проще в отдельный компонент системы, чем монолитную систему.
В том и дело, что в монолите (хотя правильнее сказать "без конвееров сообщений) в принципе не придётся изучать сотни запросов — тыкаешься в начальную/среднюю точку сломанной бизнес-логики и идёшь дальше по вызовам. Быстро, удобно, комфортно. С конвеерами так не получится — там брекпоинты нужны условные, а они не со всякой платформой эффективно работают. Далее — зачем перезапускать после сеанса — я тоже не понял. А мок-данные и вовсе к дебагу никакого отношения не имеют — это банальный юнит-, компонентный и интеграционный тест (в принципе все уровни пригодятся, да).
Любопытства ради, как глубина стека зависит от нагрузки?
Вот только асинхронные вызовы помещаются в event loop (или его аналоги), а не лежат в стеке. Как результат — отказ эвент лупа, а не проблемы, связанные с глубиной вызовов. Проще говоря — процессор не успевает разгребать события и это понятно, но увеличение глубины стека — меньшее из зол, не имеющее отношение к эвент лупу. Ну понятно, что вызов функции — это сложить 4 (8, 16?) регистров в стек, поменять CP, это все помноженное на косвенную адресацию, что в итоге отъедает процессорное время, я сам лично, заинлайнив один метод в three.js, привел к его (очень незначительному) росту производительности, но речь идет про под-функцию внутри перемножения матриц, которая вызывается миллионы раз на каждый кадр, в остальных случаях в реальных приложениях цена вызова функции крайне мала и скорее будет проседать соединение с базой (я уже молчу про сам неоптимизированный запрос), чем проблемы с вызовом функции.
А в Андроиде, например, таймеры и activities стреляют не дожидаясь завершения предыдущего вызова, как будто в новом потоке.
Про таймеры не знаю, но весь UI там в одном потоке.
Да и JS вроде тоже.
В JS вообще нет потоков. Есть (Web)Workers, но они больше похожи на процессы, ибо обладают своей собственной памятью.
В винде проблемы возникали при банальном переключении визуальной иконки по таймеру. Вроде все нормально работает, а через час падает. А когда смотришь через профилировщик, то виден постоянный рост стека и поглощение какого-то ограниченного ресурса, например, дескрипторов в Windows.
Проблема утечки ресурсов не имеет никакого отношения к асинхронным таймерам. Её при желании можно и в простейшем цикле устроить, так же как и в таймере избежать.
Потому что где-то стоял WaitForMultipleObjects, который не блокировал event loop, а тупо ждал выхода из какой-то функции.
Но WaitForMultipleObjects, будучи вызван в UI потоке, не может не блокировать event loop!
Мне кажется, что вы говорите о сильно разных вещах. Так как запуск процесса ну никак не может быть безобиднее одного дополнительного вызова функции. Хотя бы потому, что сам подразумевает эти вызовы.
Другое удобство в том что бы не прожимать весь код, а нажать F5 и после этого пробежаться глазами по флагам и значениям переменных и быстро понять что к чему, успешно ли отработал код, или были ошибки и тут же посмотреть что функция возвращает.
Мне удобно когда ретурн только один, и меня не смущает использовать флаги, что бы не исполнять код который при «ошибках» исполнять не надо
Множественные точки выхода из процедуры не есть хорошо.
Почему? Где технические аргументы? Вы сейчас верующего напоминаете. По моему, так лапша из ифов в разы хуже.
Все повторяют как мантру фразу "У функции должна быть одна точка входа и одна точка выхода", хотя изначальный смысл у нее немного другой:
Одна точка входа — не начинать выполнять функцию с середины.
Одна точка выхода — функция должна вернуть управление туда откуда её вызвали.
Звучит логично. А где-нибудь об этом в книжках писалось, можете подсказать? Я вот про изначальный смысл что-то не помню, чтобы где-то читал.
Нашел только одно объяснение Single Entry, Single Exit
function getUser( id : any ) {
let error : string
checks : {
if( typeof id !== number ) { error = 'not a number' ; break checks }
if( id == val&0 ) { error = 'not an integer' ; break checks }
return DB.getNode( id ) as User
}
throw new Error( error )
}
Это все потому, что у них RAII нет
единственную точку входа и единственную точку выхода. Тогда ее логика понятна и прозрачна.
Эх, если бы. Но нет, единственность точек входа/выхода не является ни необходимым, ни достаточным условием понятности функции.
Если вам приходится в одном критичном месте использовать много уровней вложенности ради экономии одного уровня стека, это исключительный случай.
Любой, кто использует не си, уже тратит "впустую" намного больше ресурсов, чем можно сэкономить на подобных нанооптимизациях.
Любой, кто использует не си, уже тратит «впустую» намного больше ресурсов, чем можно сэкономить на подобных нанооптимизациях.
Ну… Есть еще Rust, который у Си может иногда выигрывать.
Приём, о котором я собираюсь рассказать, работает не всегда и иногда использовать else всё-таки придётся. В остальных случаях отказ от else поможет сделать код чище.
<?php
if (!$pattern->worked()) {
$code->useSometimes('else');
} else {
$code->doNotUse('else');
}
if ($user != null
&& time() >= strtotime('12 pm')
&& $user->hasAccess(UserType.PREMIUM)
&& $store->hasItemsInStock()) {
// OK
} else {
// The access is denied!
}
- Пользователь звонит в техподдержку: Техподдержка, что-то пошло не так
- Техподдержка сообщает в центр мониторинга: Мониторинг, что-то пошло не так!
- Мониторинг в эксплуатацию: Эксплуатация, что-то пошло не так!
- Эксплуатация сообщает программисту: Программист, что-то пошло не так!
- Программист: Сука, знал же, что нужно разделять условия и логировать разные варианты этого «что-то пошло не так»
Статьи с медиума как всегда «радуют» качеством содержимого. Надо отдать должное автору этой, что он по крайней мере не налил воды для пущего эффекта (если не считать картинок и врезок кода).
Переводчик, а что ж вы самую мякотку-то не перевели из последней части статьи?
It has personally caused me to entirely rewrite whole features from scratch because of how much technical debt I accumulated over time through numerous band-aid fixes.
Джун прозрел и теперь учит всех как правильно. Ну ничего, скоро узнает слово «валидатор» и снова напишет статью.
большинство из этих ошибок ввода обычно необходимо обработать/выдать пользователю одновременно
Это было актуально, когда пользователь взаимодействовал с сервером напрямую через формы. Сейчас он взаимодействует с клиентским приложением, так что серверных ошибок валидации в норме не видит — они лишь сигнализируют, где накосячил разработчик клиентского приложения.
То есть по-вашему если приложение пропустило невалидные данные на сервер, то пользователю выдается окошко «ой, что-то стряслось, попробуйте позже»?
А если сервер таки отдает приложению информативный фидбек (сервер-то в любом случае данные проверять обязан), то почему фидбеку не быть И информативным И удобным (в смысле вываливать все ошибки валидации, а не по одной)?
Потому, что не надо делать звездолёт, когда нужен трамвай (а звездолёт — это затраты времени, денег, баги и сложности с пониманием).
Потому, что появление больше одной ошибки в данных условиях крайне большая редкость (ибо все они должны были провериться еще на клиенте).
Потому, что часто все остальные ошибки являются следствием первой (например, передано неправильное число цифр кредитки, поэтому контрольная сумма не сошлась).
Потому, что остальные валидации часто невозможны, пока не прошла предыдущая (например, нельзя проверить, что число больше нуля, если передана строка, которую невозможно привести к числу).
Потому, что чинить ошибки разработчик всё-равно будет по очереди (ибо однозадачный).
Потому, что часто все остальные ошибки являются следствием первой (например, передано неправильное число цифр кредитки, поэтому контрольная сумма не сошлась)
А зачем дальше проверять валидность УЖЕ невалидного поля? Или у вас в коде именно так, как у автора статьи? Ну то есть:
if (!is_number($card_number))
return "card_number is wrong";// or throw or whatever
if (!is_visa($card_number))
return "we accept only visas";
if (!has_valid_checksum($card_number))
return "card number is not valid";
Такую лапшу же читать невозможно если у вас хотя бы с десяток полей и правила валидации чуть сложнее чем «должно содержать цифры»/«должно соответствовать маске».
По остальным пунктам — ну ок, согласен.
А клиентская валидация — все возможные ошибки.
Тоже не обязательно. Пример: https://mol.js.org/app/demo/-/#demo=mol_form_demo_bids
ошибках появившихся каскадно
Речь о другом шла. Естественно незачем проверять невалидное поле по дальнейшим критериям.
Я ратовал за то, чтобы параллельно проверять остальные поля в этой же итерации, а не дожидаться следующего ввода.
чтобы параллельно проверять остальные поля в этой же итерации, а не дожидаться следующего ввода
Давайте для начала определимся про какую валидацию идет речь: клиент, сервер?
Второе. Пользователь еще не дошел до ввода данных Х, мы уже "параллельно проверяем все остальные поля" и говорим ему "данные обязательны для ввода". Это так делать правильно?
еще не дошел до ввода данных Х, мы уже «параллельно проверяем все остальные поля»
Я так понял это Вы про клиент?
Если все-таки про сервер — поясните, почему и зачем клиент отправляет заведомо неполный набор данных на сервер?
Если речь идет о multi-step формах с участием сервера, то на сервере валидируется и обрабатывается текущий шаг, а не вся форма целиком.
Если все-таки про сервер — поясните, почему и зачем клиент отправляет заведомо неполный набор данных на сервер?
Да мало-ли, что произодйет с клиентом, что он решил отправить заведомо не полный набор данных. Или у вас валидация полноты набора данных только на клиенте?
Хотя подход выше и выполняет задачу, уже трудно понять к какому конкретно условному выражению относится каждое else, хотя их пока всего несколько штук.
Может просто начать по-человечески оформлять код, без идиотской привычки ставить открывающую скобочку в одной строке с if?
if ($user != null)
{
if (time() >= strtotime('12 pm'))
{
if ($user->hasAccess(UserType.PREMIUM))
{
if ($store->hasItemsInStock())
{
// Раздел доступен.
}
else
{
return 'We are completely sold out.';
}
} /* if ($user->hasAccess(UserType.PREMIUM)) */
else
{
return 'You do not have premium access to our website.';
}
}/* if (time() >= strtotime('12 pm')) */
else
{
return 'This section is not opened before 12PM';
}
} /* if ($user != null) */
else
{
return 'You are not signed in.';
}
Лучше не стало.
У автора всего 10 строк и уже
трудно понять к какому конкретно условному выражению относится каждое elseЗато по стандарту. Мыши плакали, кололись, но продолжали есть кактус. А мой пример читается легко и наглядно, сколько бы минусов комментарию не поставили.
Ну так в вашем варианте что, проще понять к какому условию else относится — или как?
Немного тренировки — и формат "по стандарту" читается легче и быстрее, чем ваш. Ну просто потому, что больше значащего текста помещается на экран, при этом не наплывая друг на друга. Да и в целом — вложенность больше 2х — много где считается code smell. Об этом автор и говорит (правда, почему-то, обвиняя бедные else).
На самом деле, имхо, как писать скобочку — вообще не важно. Стандарт — это в первую очередь для того, чтобы вся кодовая база была консистентна, и новичок на проекте не испытывал когнитивный диссонанс каждый раз, когда видит разные стили, и не тратил свой рабочий ресурс на то, чтобы определиться, как именно ему писать. Ну и для того, чтобы использовать fmt, который будут сами стилизовать код.
К примеру, в C# принят подход с переносом скобок на следующую строку во всех случаях — это правило языка. На JS, наоборот, нужно скобки оставлять на той же строке, даже для классов и методов — и это тоже правило языка. И используя эти языки, я буду использовать именно их правила, чтобы любой другой программист, прочитав код, обращал внимание на логику, а не на нестандартное оформление.
А в java есть несколько стандартов (от нескольких крупных игроков) и они различаются.
PS да я согласен, что стандарт это хорошо, просто я пользуюсь языком, где владельцам языка не удалось всех свести под гребёнку одного стандарта. Так что, личная оценка "что удобней" у меня есть. Другое дело, что кому-то действительно нехватает этих отступов, чтобы легче структурировать информацию… и в целом в java форматированием уже существующего кода стараются не увлекаться...
Немного тренировки — и
и Вы на крючке у когнитивного искажения, Вам кажется что так легче, из-за удобного стандарта, а на само деле Вам легче, потому что было
Немного тренировки
трудно понять к какому конкретно условному выражению относится каждое else
Это автор, простите, брешет. Там невооружённым глазом видно, к какому условию относится else. Тупо по отступам.
А если бы он переносил else на следующую строку — IDE помогла бы ему сворачивать код поблочно и видеть строки наподобие с
+if(...){...}
+else{...}
где + — иконка разворачивания.
Собственно из-за этого сугубо практического соображения я считаю, что открывающая фигурная скобка должна быть на одной строке с оператором, а закрывающая — на отдельной строке.
Мнение логичное, хотя и на это есть свои контраргументы.
Когда говорят "код читаем" — это значит, что его можно прочитать взглядом, это не значит, что нужно ещё что-то прокручивать, целиться то в первую раскрывашку, то во вторую или жать какие-то хоткеи.
Хотя, если кто-то умеет силой мысли раскрывать плюсики… </ irony> :)
Фишка в том, что нормальным IDE пофиг на форматирование — они умеют эти плюсики и в сплошном тексте делать. Но я почему-то предпочитаю, чтобы код читался без лишних телодвижений.
Разумеется у автора трудно понять. Он же специально сделал несколько вложенных условий. Это антипаттерн и так писать обычно не нужно, тогда и с читабельностью всё нормально будет.
А расставлять скобки в плохо написанном коде ради мифической читабельности — это как лечить мертвого. Тем более что ваш вариант многим людям читабельность ухудшает наоборот. Возможно даже, что почти всем, но верю, что конкретно вам чуть лучше.
Хотя подход выше и выполняет задачу, уже трудно понять к какому конкретно условному выражению относится каждое else
Я лично уволю тех, кто будет писать код как в вашем примере.
Теперь понятно, почему нынешний софт такой тормозной: все думают о стандартах, а не удобстве (читаемости в данном случае) для человека…
Эффективный менеджер, сразу видно. Сначала научитесь общению с людьми, без хамства, а потом уже увольняйте :)
И это пишет человек, сказавший в своём комментарии:
Может просто начать по-человечески оформлять код, без идиотской привычки ставить открывающую скобочку в одной строке с if?
В своём глазу бревна не видим? :D
Теперь понятно, почему нынешний софт такой тормозной: все думают о стандартах, а не удобстве (читаемости в данном случае) для человека…
Вы прям взаимоисключающие параграфы используете. Стандарты ведь придумали как раз для удобства человека.
А про "тормозной код" даже как-то и сказать нечего: как скорость исполнения кода зависит от стиля написания кода — решительно непонятно.
Просто главный разработчик, который имеет опыт в программировании более 20 лет.
В команде не потерплю людей который пишет быдлокод. Мне они не нужны, значит просто увольняем.
А делается это просто, говорю начальству, что данный товарищ не подходит.
Не знаю как тормозной, но продукт под моим началом спокойно держит 2000qps.
Есть PSR. Который обсуждается и принимает не один год.
Но есть люди, которые любят делать как им захочется. А затем почему то удивляются, почему их не берут на работу, хотя бы на 200т.р.
Мне кажется, что к правилам размещения скобочек это не относится. Например, в C# пишут скобочки как раз на отдельной строке, в отличие от php или Java.
Я думаю, что это вообще не важный и не принципиальный момент. Просто в пределах проекта (или группы проектов) надо придерживаться одинакового код стайла.
Я думаю, что это вообще не важный и не принципиальный момент. Просто в пределах проекта (или группы проектов) надо придерживаться одинакового код стайла.
Я бы сказал — в пределах языка. Чтобы, когда читаешь исходники библиотеки, не пришлось дополнительно думать о нестандартном оформлении.
Текущее обсуждение как раз это и показывает
Если больше углубляться в тему, то кроме удобства-читаемости есть еще инструменты и утилиты для языка, более низкоуровневые. Например, анализаторы коды, снифферы и т.д. Эти инструменты так же пишутся программистами, и им гораздо проще, когда язык стандартизирован. И возможно для них проблема переноса фигурной скобки является более принципиальной, чем для вас
Приём называется early return. Else тут вторичен. Выгода: меньше влодености, легче анализировать такой код, и как следствие снижение количества потенциальных багов.
Но улыбает, конечно, когда люди начинают заезжать и бездумно применять какой-то паттерн везде.
Смиритесь люди, иногда без пару else if не обойтись, не создавая какой-нибудь запутанной фигни :)
1. Нет подсветки синтаксиса
2. Нет поддержки рефакторингов в IDE
3.…
eval на пустом месте — это всегда плохо, хотя бы стрелочные функции используйте в таких случаях.
Но даже с ними мне такой код всё равно не нравится.
Реально — он нужен никогда. Но на него действительно могут пытаться завязать дивно-динамическую архитектуру… В принципе и в других языках встречается подобное (в js — прямой аналог и тоже начисто не рекомендуется к использованию, в java — можно на лету сваять или загрузить откуда-нибудь байт-код и загрузить его в jvm, что в принципе уже не является чистым антипаттерном — во-первых с кодом один фиг работают как с кодом, а не текстом, во-вторых случайно такое не сделаешь, там нужно специально постараться… в общем если правильно спроектировать — относительно безопасная вещь).
Прекратите использовать Else в ваших программах