Не совсем понял вопрос. В любом случае, этот вариант меня так же устраивает (за исключением опечаток)
Дело именно в «опечатках». Статья, напомню, о психологии читабельности, и Ваш комментарий о более лёгком сопровождении.
Плюс, в таком стиле пишут крайне редко, т.к. он противоречит естественному (позитивному) ходу мыслей. В if обычно пишут позитивное условие, в else — негативное, а не наоборот. Начинать if с отрицания, при наличии else, часто считается code smell.
А это, тогда что такое?
if not user:
print('no user')
return None
От того, что в таком случае else неявный, делает только хуже в контексте статьи.
В этом и суть. Статья о чём? О читабельности. Что читабельней: наличие всех return очевидный поток исполнения?
В коде func1() надо тщательно читать каждое условие, чтобы понимать где success path
Не более внимательно, чем в раннем выходе.
плюс следить за значением results
Питон это отдельная песня, но при использовании более ошибкоустойчивых языков(+анализатор), корректность работы с result отслеживается. Попробуйте отследить пропущенный return.
В приведенном вами коде Apple задача «очистить память перед выходом» стояла изначально
А если я скажу, что изначально шла работа с локальными буферами и освобождать не нужно было, но в процессе обновления библиотеки понадобилось, то это что-то изменит? Если не стоит запрет на преждевременный выход и я привык его применять, это не увеличит нагрузку на распознавание ситуации, что дальше есть необходимые действия и так поступать нельзя?
Читать и поддерживать такой код гораздо проще.
Вот я из большинства поверил Вам, взял этот код и в процессе его более простого сопровождения он стал таким.
def func2():
user = get_user()
if not user:
print('no user')
orders = get_orders(user)
if not orders:
return 'no orders', None
transactions = get_transactions(orders)
if not transactions:
return 'no transactions', None
return 'OK', [execute(t) for t in transactions]
Уменьшилась ли от этого ментальная нагрузка на работу с этой функцией по сравнению со структурным потоком исполнения?
И, кстати, почему 1-й вариант Вы записали не так?
def func1():
results = None
user = get_user()
if not user:
print('no user')
else
orders = get_orders(user)
if not orders:
print('no orders')
else
transactions = get_transactions(orders):
if not transactions:
print('no transactions')
else:
results = [execute(t) for t in transactions]
return results
Ведь он же ближе ко 2-му? И что значит прыгать по if/else? Вы действительно прыгаете, затрачивая энергию, а не читаете наглядное ветвление? Может, с непривычки?
Про «else if» пример был для самых простых случаях. Обычно это что-то типа
Тогда нужно разбивать на шаги. Ибо иначе огромные функции так и растут. Из-за усеянности неструктурными переходами разбивать функцию становится всё сложней.
Как-то, читая новость об исправлении уязвимости в NGINX, случайно обнаружил в нём другую ошибку переполнения. Оцените исправление этой ошибки hg.nginx.org/nginx/rev/e3723f2a11b7 Она была продублирована в большом количестве мест, и общий код не мог быть так просто вынесен в отдельную оттестированную функцию, а был повсюду в большущих функциях, потому что использовалась логика неструктурных переходов, усложняющая декомпозицию.
Не поверю, что Вы не можете придумать примеры сами. Я же не буду придумывать, а покажу функцию от Apple, где есть код, который должен выполнится в конце habr.com/post/213525. Кстати, эта конкретная ошибка могла произойти и с return.
Добавить код может понадобиться, когда, к примеру, расширяются коды проверки или изначально что-то забыто, или происходит рефакторинг или ещё по тысяче причин.
Задача «добавить код» сама по себе в принципе не возникает.
Как и задача писать код сама по себе в принципе не возникает. Но мы же и пишем, и добавляем?
Несколько раз встречал ошибки что в начинают добавлять код после закрывающего блока для else, а на самом деле этот код должен быть использован внутри else.
К сожалению, не понял, что имеется ввиду.
А пример хорошо переписывается в многоветочный if, Java не имеет встроенной поддержки, но благодаря статическим анализаторам это не так критично
public void SomeFunction(int age)
{
if (age < 0) {
System.out.println("Не верный возраст");
} else if (age > 100) {
System.out.println("Подумай ещё");
} else if (age < 18) {
System.out.println("Не продаём малолетним");
} else {
// сделать что-то
}
}
С преждевременным выходом же появляется дополнительная возможность ошибки пропуска return и диагностировать это сложней в общем случае
Да, абстрактен. Я это называю проблемой восприятия маленьких примеров, когда выводы делаются на основе заведомо суженных рамок, за которыми не видно проблем промышленного кода, которые можно наблюдать, например, регулярно анализируя сообщения об уязвимостях.
думаю автор подразумевает что код самодостаточен и завершен: функция решает только одну задачу, и задачи «добавить код выполняющийся в конце функции» попросту возникнуть не может.
А это ещё одна нагрузка мозг — нужно воспринимать не только текущий, но и предсказывать будущий контекст, что ещё хуже.
Одним из примеров может служать концепция раннего возврата («early return»):
…
public void SomeFunction(int age)
{
if (age < 0){
System.out.println("Не верный возраст");
return;
}
// сделать что-то
}
В контексте статьи это плохой пример, так как если после «сделать что-то» понадобится добавить код, обязательно выполняющийся в конце функции, то нужно будет учитывать, что где-то ранее может быть сделан преждевременный выход из функции — нужно учитывать больше контекста. С точки зрения читаемости такой код лучше:
public void SomeFunction(int age)
{
if (age < 0){
System.out.println("Неправильный возраст");
} else {
// сделать что-то
}
}
Структурность и форматирование кода делает поток вычислений более очевидным в противовес выравненным неструктурным переходам.
Дело не только в статистических уравнениях или, скорее, статистика — это следствие. Возьмите производную пространственной координаты по времени и производную одной пространственной координаты по другой. С точки зрения физики Вы получете одно и то же?
Нельзя в одном абзаце писать, что любое уравнение симметрично относительно времени, и что есть уравнения с несимметричным временем. Тут где-то опять пожертвовали здравым смыслом ради «красивой концепции».
Необходимость разнесение понятия «протокол» на роли (тип, шаблон, типаж, маркер), которые имеют свои собственные ограничения относительно других, говорит о том, что под единое слово поместили разные по своей сути понятия. Надо разбираться, возможно, они имеют достаточно совместных свойств, чтобы объединение было оправданным, но пока после прочтения статьи создаётся впечатления, что авторы языка снова принесли практический смысл в жертву «красивой концепции», как это было не раз в истории языков программирования.
Если главная цель показать концепцию LR, то да, имело смысл. Не уверен, что получилось так уж просто, но я не знаю как сделать лучше.
Но я понял изначальный посыл, как желание разобраться с синтаксическим разбором в принципе, а не конкретно с LR. А тогда лучше бы начать с более простых вещей.
грамматика, которую вы нарисуете «из головы», с некоторой вероятностью окажется пригодна для LR
Полагаю, что если рисовать действительно из головы без согласования с ограничениями методов разбора, то и под LR она не подойдёт. Всё-таки прагматический подход к создание языков состоит в том, чтобы рисовать не от балды, а с оглядкой на выбранный метод разбора, и сознательного отказа от излишеств.
А что до рекурсивного спуска, кстати, то LL (да и LR) строятся по грамматике полностью автоматически, а если рекурсивный спуск ручками писать — вы не гарантированы от ошибок
Рекурсивный спуск — это метод разбора текста по LL грамматике, но это не означает, что его обязательно писать вручную. Впрочем, в ручном создании парсера тоже есть смысл по совокупным качествам.
Напоследок упомяну про LL-анализ (рекурсивный спуск). Исторически он появился раньше, чем LR, более или менее общим мнением является то, что LL-анализаторы сравнительно просто писать вручную
По-моему, их куда проще написать и понять, чем приведённый Вами код. Я как-то написал в экспериментальных целях вычислитель схожих выражений expression-parser на Go. Сравните, стоило ли обращаться к «пузырьковому вычислителю» вместо рекурсивного спуска?
Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.
Функциональное программирование не просто избегает общего изменяемого состояния, а не содержит никакого изменяемого состояния — ни общего, ни частного. Если же, все-таки, изменяемое состояние есть, то речь идёт о смеси функционального и императивного прогаммирования.
Может определение и любимое, но оно, как минимум, неточное и ведёт к неверным выводам.
Если же учесть, что функциональная и императивная парадигмы на базовом уровне — это вопрос интерпретации, то можно прийти к заключению, что статья является упражнением в софистике. Ведь если идти до конца, то любую императивную программу можно интерпретировать как функциональную и запрет на функциональное программирование будет обозначать запрет на программирование вообще.
Но обычно имеют ввиду не это.
А программировать надо так, как навязывает язык и его расширения-библиотеки, иначе всё время придётся бороться с ветряными мельницами. Только космических выводов из этого делать, пожалуй, не стоит. Большинство ЯВУ, начиная с Фортрана, были синтетическими.
Идея, действительно, стоящая в ряде случаев, но есть и проблема, заключающаяся в том, что постепенная эволюция может существенно влиять на полученное решение, не позволяя доуточнить позже пропущенное вначале. Та же опциональная типизация возможно тогда, когда мы с самого начала подразумевали некий тип, но пропустили его объявление для увеличения скорости набора программы. Если же мы закладываем динамику в саму логику, исходя из того, что это возможно, а не из того, что это нужно, то возможна ситуация когда от изначальных неудачных решений уже никуда не деться, если только не переписать всё заново. При этом изначальное написание программы в более строгом языке не всегда приводит к более сложной программе. И если наша конечная задача нужна именно в более строгом виде, то произвольное «постепенное программирование» может оказаться во вред.
А это, тогда что такое?
От того, что в таком случае else неявный, делает только хуже в контексте статьи.
Не более внимательно, чем в раннем выходе.
Питон это отдельная песня, но при использовании более ошибкоустойчивых языков(+анализатор), корректность работы с result отслеживается. Попробуйте отследить пропущенный return.
Вот я из большинства поверил Вам, взял этот код и в процессе его более простого сопровождения он стал таким.
Уменьшилась ли от этого ментальная нагрузка на работу с этой функцией по сравнению со структурным потоком исполнения?
И, кстати, почему 1-й вариант Вы записали не так?
Ведь он же ближе ко 2-му? И что значит прыгать по if/else? Вы действительно прыгаете, затрачивая энергию, а не читаете наглядное ветвление? Может, с непривычки?
Про неидеальный мир согласен, но в контекст статьи это не вписывается. Тут, всё-таки, о правильном подходе даже с учётом психологии.
Как-то, читая новость об исправлении уязвимости в NGINX, случайно обнаружил в нём другую ошибку переполнения. Оцените исправление этой ошибки hg.nginx.org/nginx/rev/e3723f2a11b7 Она была продублирована в большом количестве мест, и общий код не мог быть так просто вынесен в отдельную оттестированную функцию, а был повсюду в большущих функциях, потому что использовалась логика неструктурных переходов, усложняющая декомпозицию.
Добавить код может понадобиться, когда, к примеру, расширяются коды проверки или изначально что-то забыто, или происходит рефакторинг или ещё по тысяче причин.
Как и задача писать код сама по себе в принципе не возникает. Но мы же и пишем, и добавляем?
А пример хорошо переписывается в многоветочный if, Java не имеет встроенной поддержки, но благодаря статическим анализаторам это не так критично
С преждевременным выходом же появляется дополнительная возможность ошибки пропуска return и диагностировать это сложней в общем случае
А это ещё одна нагрузка мозг — нужно воспринимать не только текущий, но и предсказывать будущий контекст, что ещё хуже.
В контексте статьи это плохой пример, так как если после «сделать что-то» понадобится добавить код, обязательно выполняющийся в конце функции, то нужно будет учитывать, что где-то ранее может быть сделан преждевременный выход из функции — нужно учитывать больше контекста. С точки зрения читаемости такой код лучше:
Структурность и форматирование кода делает поток вычислений более очевидным в противовес выравненным неструктурным переходам.
Но я понял изначальный посыл, как желание разобраться с синтаксическим разбором в принципе, а не конкретно с LR. А тогда лучше бы начать с более простых вещей.
Рекурсивный спуск — это метод разбора текста по LL грамматике, но это не означает, что его обязательно писать вручную. Впрочем, в ручном создании парсера тоже есть смысл по совокупным качествам.
Может определение и любимое, но оно, как минимум, неточное и ведёт к неверным выводам.
Если же учесть, что функциональная и императивная парадигмы на базовом уровне — это вопрос интерпретации, то можно прийти к заключению, что статья является упражнением в софистике. Ведь если идти до конца, то любую императивную программу можно интерпретировать как функциональную и запрет на функциональное программирование будет обозначать запрет на программирование вообще.
Но обычно имеют ввиду не это.
А программировать надо так, как навязывает язык и его расширения-библиотеки, иначе всё время придётся бороться с ветряными мельницами. Только космических выводов из этого делать, пожалуй, не стоит. Большинство ЯВУ, начиная с Фортрана, были синтетическими.
Смысл — удовлетворение интереса.