Нелокальные управляющие конструкции считать вредными
От: Tilir Россия http://tilir.livejournal.com
Дата: 22.03.13 09:42
Оценка: 48 (5) +3 -6 :))) :))
Дейкстра в классической статье [1] приводит несколько серьёзных доводов против нелокального goto. В частности, он пишет: "наши интеллектуальные возможности в основном направлены на анализ статических отношений и, напротив, наши способности представлять ход процессов, разворачивающихся во времени, развиты сравнительно слабо. Поэтому мы должны (как умные программисты, осознающие, собственные ограничения) делать всё от нас зависящее, чтобы уменьшать концептуальный зазор между статическим кодом программы и динамическим процессом её выполнения, добиваясь как можно более тривиального соответствия между программой (разворачивающейся в виде текста) и порождённым ей процессом (разворачивающимся во времени)".

Очень похожие слова мы можем встретить в современном документе Google C++ Style Guide [2] но там они касаются уже исключений (C++ exceptions): "исключения делают поток управления программы сложным для оценки при просмотре кода: функции могут вернуть управление в неожиданных местах. Вы можете минимизировать это, соблюдая некие правило относительно того как и где можно использовать исключения, но ценой этого будет то, что разработчик должен будет помнить и знать гораздо больше, чем ему нужно [для работы над его проблемой]". Такую же критику исключений приводит Джоэль Спольски [3] добавляя также "невидимость в исходном коде" и отмечая, что "даже тщательный анализ кода в этих случаях не помогает выявить проблемы".

Очень похожие аргументы о недетерминированном поведении кода приводит Edward A. Lee из Berkeley в своей работе [4], посвященной критике нитей исполнения (threads). Он пишет: "мы должны (и можем) строить детерминированные модели параллельного исполнения, в которые обоснованно и осторожно вносить недетермнизм там, где он реально нужен. Нити исполнения предлагают противоположный подход. Они делают программы абсурдно недетерминистичными и сильно полагаются на хороший стиль программирования, чтобы ограничить этот недетерминизм и достичь детерминированных целей"

Все эти три цитаты говорят об одном и том же -- о принципиальной вредности нелокальных управляющих конструкций. Какие вообще нелокальные управляющие конструкции мы знаем? Очень много, увы. Дейкстра был счастливчиком имея дело всего лишь с GOTO. Вот только некоторые источники головной боли в наше время:

* Нелокальный GOTO
* Continuations
* Исключения
* Сигналы
* Нотификации
* Сопрограммы
* Генераторы
* Нити исполнения
... имя им легион ...

Каждая нелокальная управляющая конструкция имеет локальный и детерминированный эквивалент. Нелокальный GOTO и необходимость использования continuations могут быть ликвидированы при хорошей структурированности программы, исключения и нотификации -- заменены обработкой кодов возврата, сопрограммы и генераторы -- вызовами функций, нити исполнения -- детерминированным параллелизмом на уровне компилятора, таким как OpenMP. Тем не менее они возникают снова и снова в разных видах из-за ложного чувства упрощения разработки, которое они дают.

Сложность жизненного цикла программы это не только сложность разработки, но и сложность поддержки. Большинство признанных сегодня методов разработки, таких как структурное программирование, ООП и паттерны проектирования, etc направлены на борьбу со сложностью и делают разрабатываемые системы более прозрачными, более соответствующими Principle Of Least Astonishment [5], и в конечном итоге более простыми в поддержке. В противовес этому, нелокальные управляющие конструкции дают прирост скорости разработки за счёт потери детерминированности и простоты разрабатываемого приложения.

Эрик Рэймонд в "Искусстве программирования под Unix" пишет: "Программную систему можно назвать прозрачной, если в ней нет тёмных углов и скрытых глубин. Прозрачность это пассивная характеристика. Программа прозрачна когда вы можете сформировать простую умственную модель её поведения, которая действительно предскажет все или большинство случаев работы с программой, потому что вы можете через всю сложность реализации, тем не менее понимать, что происходит".

Для программ со свободными функциями или с методами классов (а это абсолютное большинство работающего сегодня ПО), хорошим стилем является предпочитать явный контракт на метод неявному. Так, например, метод, модифицирующий одну глобальную переменную и сильно зависящий от состояния другой будет иметь два вхождения в своём неявном контракте. Но любая нелокальная конструкция в методе это часть неявного контракта на него. Безопасность относительно исключений или безопасность относительно многопоточности, которую должен обеспечить программист это такой же нонсенс, как "безопасность относительно глобальных переменных". Последнюю мы понимаем только в одном смысле -- надо выкинуть глобальные переменные.

В функциональных языках нелокальные конструкции, такие как pattern matching или ленивые списки, не вызывают особой боли, потому что являются естественной частью non-modifyable мира, где модификация загнана внутрь монад и интеллектуальные ресурсы разработчика не расходуются на поддержку консистентности модифицируемых структур данных при нелокальных переходах. Но по многим причинам (в частности из-за высокого входного порога и принципиально низкой производительности на существующих архитектурах -- представьте, например, что вместо модификации одного узла дерева, вы полностью уничтожаете и создаёте заново весь миллион его узлов), такие языки до сих пор представляют скорее академический интерес. Что касается нелокальных конструкций в модифицируемом мире, то мы должны отказаться либо от них, либо от оператора присваивания. К тому, же в мире чисто функционального программирования, детерминированности остаётся ещё меньше.

В плане производительности и детерминированности, следует также отметить, что очень часто их использование сопряжено с существенным и невидимым (или сложно оцениваемым) оверхедом. Например Glodstein, Rowe и Kesden из Карнеги-Меллон [6] особо предупреждают о существенном оверхеде при использовании сигналов Unix и переключении контекста процессов.

В отчёте С++ performance technical report [7], про исключения аккуратно сказано: "для некоторых программ проблему составляет сложность в предсказании времени, требующегося для передачи управления от throw до подходящего catch" и предлагается провести для каждой точки возможного выброса исключений сложный анализ, включающий анализ времени работы всех деструкторов объектов, которые могут быть вызваны при раскрутке стека, который позволит установить верхнюю и нижнюю границу времени выполнения. Это очень типичный аргумент для всех адептов нелокального счастья -- они советуют писать код с использованием всех их прекрасных возможностей, а уже потом при отладке многопоточного приложения с исключениями и сопрограммами искать узкие места и думать над тем, откуда это счастье выкинуть. И это явно не инженерный подход.

При проектировании надёжных, стабильно работающих приложений, мы должны, уже на этапе выбора архитектуры, инструментов и методологий, сводить к минимуму дорогостоящую отладку и поддержку на поздних этапах существования продукта. Речь не идёт о преждевременной оптимизации. Речь идёт о принципиальном отказе от недетеринизма, так же, как мы отказываемся от нелокальных переходов и убираем зависимости от глобальных переменных.

Именно поэтому я предлагаю все нелокальные управляющие конструкции считать вредными.

Литература

1. http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html
2. https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions
3. http://www.joelonsoftware.com/items/2003/10/13.html
4. http://www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.html
5. http://c2.com/cgi/wiki?PrincipleOfLeastAstonishment
6. http://www.cs.cmu.edu/~213/lectures/14-signals.pdf
7. http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf
Re: Нелокальные управляющие конструкции считать вредными
От: SergH Россия  
Дата: 22.03.13 09:58
Оценка: -1
Здравствуйте, Tilir, Вы писали:

T>Именно поэтому я предлагаю все нелокальные управляющие конструкции считать вредными.


Вот есть такая полезная штука как логирование.

Довольно удобно иметь возможность её включать, выключать и менять уровень из одного места. Через конфиг или параметры командной строки... Обычно это реализуется с использованием глобальных переменных/синглетонов или чего-то подобного.

Как ты предлагаешь сделать это иначе?
Делай что должно, и будь что будет
Re: Всё зависит от целей разработки. Может кто-то просто учится за счёт работада
От: Erop Россия  
Дата: 22.03.13 09:58
Оценка: :)
Здравствуйте, Tilir, Вы писали:

T>Именно поэтому я предлагаю все нелокальные управляющие конструкции считать вредными.



Люто, бешенно плюсую
Но с некоторыми уточнениями.
Если смотреть на мир с колокольни индустриальной разработки софта, типа делаем продукт за бюджет, когда, то нет чего-то однозначно вредного или там полезного, есть стоимость того или иного решения и риски с ними связанные.
Соответственно нелокальные управляющие конструкции обычно снижают стоимость разработки, во всяком случае если считать, что разработку ведут те же спецы за ту же зарплату, повышают стоимость поддержки и влекут малоконтролируемые дорогие риски.

так что их может и надо использовать, но ограниченно и обоснованно, в рамках каких-то точно оптсанных и довольно ограниченных подходов, которые экономически и технически обоснованны.

Например, исключения обычно вполне выгодно использовать для обработки таких событий, которые мы считаем полным провалом программы. То есть мы хотим после этого сохраниться и перезапуститься, а не восстановиться и продолжить исполнение

Либо в рамках каких-то крайне сильно изолирующих подходов.
Если с первым всё относительно ясно, то второе лучше пояснить примером.
Например, мы строим в буфере в памяти какую-то сложную структуру данных, имеющую много указателей внутрь себя самой. То есть она труднго перемещается в другой буфер и не может лежать в хранилище сегментированном на несколько буфров.
Тогда, например, можно аллокировать буфер, которого обычно хватает, а если не хватило, то кинуть исключение, всю структуру грохнуть и попробовать построить её ещё раз с буферов вдвое большего размера. Если структура собранна из POD узлов, то проблем с исключением тут будет мало, а профит будет заметным. но это большая редкость, тем не менее, и полностью укладывается в подход, что перед тем, как использовать нелокальную управляющую конструкцию, надо это экономически обосновать.

То есть, вопрос "как это повысит доходы фирмы" имеет то, волшебное свойстово, что позволяет легко обуздать страсть программистов к красоте и эесперементам со стилем разумными прагматическими рамками.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Нелокальные управляющие конструкции считать вредными
От: Erop Россия  
Дата: 22.03.13 10:04
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Как ты предлагаешь сделать это иначе?

1) Моё мнение состоит в том, что это удобнее делать через "почти глобальные переменные", и это тот редкий случай, когда подход оказывается выгодным. Тем не менее, в реальности часто хочется рулить логгированием как минимум по подсистемам, если не еёщ точнее, и на уровне приложения (глобальные переменные) многие тонкости, которыми хочется порулить при логгировании, просто неописуемы даже, а не то что бы удобно управляемы.

так что логично иметь в программе сервис, которыей вешается на что-то вроде сессии, или приложения, и в этом сервесе регистрировать логгеры, и опции логгирования по подсистемам и далее иерархически вплоть до отдельных сложных классов, или границ с другими компонентами, если надо. Например если у нас программа как-то работет и что-то делает, но иногда грузит плагин, который, например, часть работы перекладывает на СУБД, то этот плагин и должен динамически расширять систему логгирования для специфического логгирования работы с СУБД. Опять же у СУБД могут быть свои логи, которыми тоже можно рулить и вообще интегрировать в своё приложение таким образом.

2) Если же говорить об изведении статических переменных вообще, как идеи, то была, например, когда-то такая СО симбиан, в которой статических переменных не было. И там всюду надо было передавать контекст вызова, через который можно было получить доступ к таким вещам, как логи или аллокации системной памяти...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Нелокальные управляющие конструкции считать вредными
От: SergH Россия  
Дата: 22.03.13 10:23
Оценка:
Здравствуйте, Erop, Вы писали:

E>1) Моё мнение состоит в том, что это удобнее делать через "почти глобальные переменные", и это тот редкий случай, когда подход оказывается выгодным.


Таких случаев можно придумать ещё несколько Например, выделение памяти. Пул подключений к базе. И т.п. Это то, что обычно называют политиками. Обычно это что-то такое, о чём программист, реализующий конкретное поведение, беспокоиться не хочет и не должен.

E>так что логично иметь в программе сервис, которыей вешается на что-то вроде сессии, или приложения, и в этом сервесе регистрировать логгеры, и опции логгирования по подсистемам и далее иерархически вплоть до отдельных сложных классов, или границ с другими компонентами, если надо. Например если у нас программа как-то работет и что-то делает, но иногда грузит плагин, который, например, часть работы перекладывает на СУБД, то этот плагин и должен динамически расширять систему логгирования для специфического логгирования работы с СУБД. Опять же у СУБД могут быть свои логи, которыми тоже можно рулить и вообще интегрировать в своё приложение таким образом.


Это охрененно нелокальная конструкция Особенно с учётом того, что она будет управляться из конфига, и может быть даже перечитывать его налету.

E>2) Если же говорить об изведении статических переменных вообще, как идеи, то была, например, когда-то такая СО симбиан, в которой статических переменных не было. И там всюду надо было передавать контекст вызова, через который можно было получить доступ к таким вещам, как логи или аллокации системной памяти...


Да всё можно конечно, но это же неудобно ужасно. Повышается количество кода, который копипастится из функции в функцию, все вызовы становятся многословнее. Искать содержательные части кода в общем объеме становится сложнее.
Зачем же так бороться с собой ради какого-то странно понятого принципа.
Делай что должно, и будь что будет
Re[4]: Нелокальные управляющие конструкции считать вредными
От: Erop Россия  
Дата: 22.03.13 10:39
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Таких случаев можно придумать ещё несколько Например, выделение памяти. Пул подключений к базе. И т.п. Это то, что обычно называют политиками. Обычно это что-то такое, о чём программист, реализующий конкретное поведение, беспокоиться не хочет и не должен.


Ну да, но это надо ограниченно применять, ЭКОНОМИЧЕСКИ обосновав, не лепить направо и налево...
Ты мой ответ на стартовое сообщение читал?

SH>Это охрененно нелокальная конструкция Особенно с учётом того, что она будет управляться из конфига, и может быть даже перечитывать его налету.


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

SH>Да всё можно конечно, но это же неудобно ужасно. Повышается количество кода, который копипастится из функции в функцию, все вызовы становятся многословнее. Искать содержательные части кода в общем объеме становится сложнее.


Не знаю. Как-то я переносил довольно большую и сложную AI хреновину на симбиан. Не сказал бы, что она на симбиане была сильно сложнее, чем на Win CE, например...
А если учесть, что со статическими переменными всё равно есть куча заморочек, то косяки под симбианом были радикально прогнозируемее, к тому же...

SH>Зачем же так бороться с собой ради какого-то странно понятого принципа.

В смысле "бороться с собой"? Бороться надо не с собой, а за удешевление поддержки и разработки при сохранении удобства и функционала...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: Нелокальные управляющие конструкции считать вредными
От: SergH Россия  
Дата: 22.03.13 11:06
Оценка:
Здравствуйте, Erop, Вы писали:

E>Ну да, но это надо ограниченно применять, ЭКОНОМИЧЕСКИ обосновав, не лепить направо и налево...


Конечно ограниченно, не надо лепить всюду. Но и запрещать вообще это неоправданный экстремизм.
Я именно это имел ввиду под "борьбой с собой": мы видим, что так всем удобнее и проще, но из принципа будем делать иначе.

E>Ты мой ответ на стартовое сообщение читал?


Да, я согласен, что на исключениях не стоит делать сложную логику. Но иногда их использование полезно.
Можно конечно считать, что это очень опасный инструмент и не стоит давать его программисту в руки, порежется. Но это глупость, не нужно так делать. Как писал великий Страуструп в "Дизайн и эволюция С++"

Хорошие программы – это продукт хорошего образования, удачного проектирования, адекватного тестирования и т.д., а не наличия в языке средств, которые предположительно должны использоваться только «правильно». Употребить во вред можно любое средство, поэтому вопрос не в том, можно ли чем-то воспользоваться неправильно (можно!) или будут ли этим неправильно пользоваться (будут!). Вопрос в том, необходимо ли данное средство при условии его правильного употребления настолько, чтобы оправдать затраты на его реализацию, удастся ли смоделировать его с помощью других средств языка и реально ли с помощью обучения обратить во благо неправильное использование.

Делай что должно, и будь что будет
Re[6]: Нелокальные управляющие конструкции считать вредными
От: Erop Россия  
Дата: 22.03.13 11:11
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Я именно это имел ввиду под "борьбой с собой": мы видим, что так всем удобнее и проще, но из принципа будем делать иначе.


Часто всё-таки есть конфликт интересов между "всем удобнее и проще", и "выгоднее для бизнеса"...
Поэтому в подобных случаях ЭКОНОМИЧЕСКОЕ и ТЕХНИЧЕСКОЕ обоснования таки требуются, а по умолчанию, без обонований -- запретить.

SH>Можно конечно считать, что это очень опасный инструмент и не стоит давать его программисту в руки, порежется. Но это глупость, не нужно так делать. Как писал великий Страуструп в "Дизайн и эволюция С++"

Как много удачного и ДЕШЁВОГО в разработке коммерческого ПО наманагерил "великий Страуструп" в своей жизни?..

SH>

Хорошие программы – это продукт хорошего образования, удачного проектирования, адекватного тестирования и т.д., а не наличия в языке средств, которые предположительно должны использоваться только «правильно». Употребить во вред можно любое средство, поэтому вопрос не в том, можно ли чем-то воспользоваться неправильно (можно!) или будут ли этим неправильно пользоваться (будут!). Вопрос в том, необходимо ли данное средство при условии его правильного употребления настолько, чтобы оправдать затраты на его реализацию, удастся ли смоделировать его с помощью других средств языка и реально ли с помощью обучения обратить во благо неправильное использование.


С этим я и не спорю. Вопрос всего лишь о дефолтах и процедурах по отступлению от дефолтов...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Нелокальные управляющие конструкции считать вредными
От: Abyx Россия  
Дата: 22.03.13 11:19
Оценка: +4
Здравствуйте, Tilir, Вы писали:

нет. всё не так.
кроме сложности графа выполнения программы есть сложность самой программы — число строк (или например statements).
под "программой" следует понимать *корректную программу*, а не огрызок с кучей UB, который работает только на хороших тестовых данных.

т.е., вот это — не корректная программа (исключения не используются):
uint n = file.get_size();
void* buf = alloc(n);
file.read_to_buf(buf, n);


вот это — корректная программа без исключений, с макросами, как например в исходниках 7zip
#define RNOK(e) if (err_t err = e) return err

uint n;
RNOK(file.get_size(n));
void* buf;
RNOK(malloc(buf, n));
RNOK(file.read_to_buf(buf, n));


но макросы зло, а SEME "сложен для понимания", по этому вот SESE вариант:
err_t e = E_OK;
uint n;
e = file.get_size(n);
if (!e)
{
    void* buf;
    e = malloc(buf, n);
    if (!e)
    {
        e = file.read_to_buf(buf, n));
    }
}
return e;


такой код не имеет скрытых путей выполнения и потому удобен для понимания.




теперь насчет многопоточности.
многопоточность обычно обусловлена требованиями к программе,
по производительности — "пользозователь нажал кнопку — программа отработала за время <0.01 сек",
или по отзывчивости UI — "пользозователь нажал кнопку — UI не должен виснуть".

"параллелизм" это частный случай, который очень редко попадается в реальной жизни.
обычно задачи выглядят так:

void UI_Controller::validateFile()
{
    string localFileMd5, remoteFileMd5;
    thread th1([&]{ localFileMd5 = md5(fileName); });
    thread th2([&]{ remoteFileMd5 = http_get("http://site/md5/" + fileName); });
    th1.join();
    th2.join();

    ui.invoke([=]{ ui.checkbox.set(localFileMd5 == remoteFileMd5); });
}


никакой OpenMP тут не поможет, зато могут помочь continuations или сопрограммы (в т.ч. async/await).

без исключений тоже тяжело, надо же как-то передавать ошибки между потоками.
In Zen We Trust
Re[7]: Нелокальные управляющие конструкции считать вредными
От: SergH Россия  
Дата: 22.03.13 11:38
Оценка: 7 (1) +1 :)
Здравствуйте, Erop, Вы писали:

E>Часто всё-таки есть конфликт интересов между "всем удобнее и проще", и "выгоднее для бизнеса"...

E>Поэтому в подобных случаях ЭКОНОМИЧЕСКОЕ и ТЕХНИЧЕСКОЕ обоснования таки требуются, а по умолчанию, без обонований -- запретить.

Мне сложно представить себе экономическое обоснование на использование/запрет чего-либо. Это что-то типа

... как показывают исследование, А% программистов С++ с опытом от года понимают эту конструкцию правильно, так что если все теперешние программисты уволятся и придется набирать новых, проблемы с использованием этого возникнут с вероятностью Б%. Если повысить требования к набираемым программистам, проблемы возникнут с вероятностью В%, но при этом зарплаты придется повысить на Г% и риск не найти на рынке специалистов повысится до Д%. Уровень проблем оценивается как Ж-12-47.

При этом выгоды от использования данной конструкции составляют Е% уменьшения объёма кода и Ё% повышение его читабельности (для тех, кто понимает смысл конструкции правильно).

Оценки лежат в доверительном интервале +/- 2% с вероятностью 95%. Мы считаем выгоды значительными а риск приемлемым и рекомендуем разрешить.


Хотелось бы посмотреть на такие исследования.
Делай что должно, и будь что будет
Re: Нелокальные управляющие конструкции считать вредными
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 22.03.13 11:48
Оценка:
Здравствуйте, Tilir, Вы писали:

T>Сложность жизненного цикла программы это не только сложность разработки, но и сложность поддержки. Большинство признанных сегодня методов разработки, таких как структурное программирование, ООП и паттерны проектирования, etc направлены на борьбу со сложностью и делают разрабатываемые системы более прозрачными, более соответствующими Principle Of Least Astonishment [5], и в конечном итоге более простыми в поддержке. В противовес этому, нелокальные управляющие конструкции дают прирост скорости разработки за счёт потери детерминированности и простоты разрабатываемого приложения.


Не совсем понятно, что такое нелокальные конструкции.

T>В функциональных языках нелокальные конструкции, такие как pattern matching или ленивые списки, не вызывают особой боли, потому что являются естественной частью non-modifyable мира, где модификация загнана внутрь монад и интеллектуальные ресурсы разработчика не расходуются на поддержку консистентности модифицируемых структур данных при нелокальных переходах. Но по многим причинам (в частности из-за высокого входного порога и принципиально низкой производительности на существующих архитектурах -- представьте, например, что вместо модификации одного узла дерева, вы полностью уничтожаете и создаёте заново весь миллион его узлов), такие языки до сих пор представляют скорее академический интерес. Что касается нелокальных конструкций в модифицируемом мире, то мы должны отказаться либо от них, либо от оператора присваивания. К тому, же в мире чисто функционального программирования, детерминированности остаётся ещё меньше.


Почему PM это нелокальная конструкция ?

T>При проектировании надёжных, стабильно работающих приложений, мы должны, уже на этапе выбора архитектуры, инструментов и методологий, сводить к минимуму дорогостоящую отладку и поддержку на поздних этапах существования продукта. Речь не идёт о преждевременной оптимизации. Речь идёт о принципиальном отказе от недетеринизма, так же, как мы отказываемся от нелокальных переходов и убираем зависимости от глобальных переменных.


T>Именно поэтому я предлагаю все нелокальные управляющие конструкции считать вредными.


Это похоже камень в огород функциональщине и метапрограммированию ?
Re[8]: Нелокальные управляющие конструкции считать вредными
От: Erop Россия  
Дата: 22.03.13 11:49
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Мне сложно представить себе экономическое обоснование на использование/запрет чего-либо. Это что-то типа



Дык нет исследований -- нет и обоснования...
Либо выгода ОЧЕВИДНА и обоснование написать легко.

А так, конечно, принимать решения за которые отвечает кто-то другой не сложно
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Нелокальные управляющие конструкции считать вредными
От: AlexRK  
Дата: 22.03.13 12:58
Оценка:
Здравствуйте, Tilir, Вы писали:

В целом согласен. Я ненавижу исключения и прочие break-continue.

Но вот не знаю — как быть без исключений, если хочется малой кровью написать относительно живучую программу. К примеру, падение в каком-то методе вызывает откат до определенной точки "вверху" и пользователю отображается сообщение. Ведь даже банальное i := i + 1 может вызвать переполнение. И таких функций с ограниченной областью определения в программах море. Все это — скрытые баги. Если исключения убрать, то есть только два варианта, как поступить — либо любая ошибка вызывает останов программы (как в С или Oberon), либо мы должны статически доказать, что ошибки быть не может (что при текущем уровне развития технологий требует кучи дополнительного кода и является в общем случае алгоритмически неразрешимой задачей).
Re[2]: Нелокальные управляющие конструкции считать вредными
От: denis8158  
Дата: 22.03.13 14:43
Оценка: 1 (1) +1
Здравствуйте, Abyx, Вы писали:

A>Здравствуйте, Tilir, Вы писали:



A>но макросы зло, а SEME "сложен для понимания", по этому вот SESE вариант:

A>
A>err_t e = E_OK;
A>uint n;
A>e = file.get_size(n);
A>if (!e)
A>{
A>    void* buf;
A>    e = malloc(buf, n);
A>    if (!e)
A>    {
A>        e = file.read_to_buf(buf, n));
A>    }
A>}
A>return e;
A>


Для работы такого подхода, необходимо чтобы и все выше стоящие функции (например, бизнес логики) так же протаскивали через себя код ошибки. Вот мы поднимаемся через 10 функций выше. И что мы видим, что где-то не удалось открыть файл. Но даже не представляем какой файл, в каком месте, только код ошибки — локализация ошибок становится адом.

Но и это ещё не все, если мы этот код используем в достаточно сложной бизнес логике, которая и так сложная (сотни условий, разные циклы для подготовки данных, для расчетов и т. п. ). Так теперь ещё и после вызова каждой функции нам требуется проверять код её ошибки??? Даже в этом примере нам понадобилось два уровня if'ов. А уже в бизнес логике потребуются сотни и всё для того, чтобы не выполнить код, после возникновения ошибки. Мы тем самым вручную реализуем логику исключений и развертывания стека.

Типун Вам на язык, чтобы такое было в моём благоверном C#....
Re[3]: Немного шуток и прибауток AKA злостный офтоп ;)
От: Erop Россия  
Дата: 22.03.13 15:20
Оценка:
Здравствуйте, denis8158, Вы писали:

D>Типун Вам на язык, чтобы такое было в моём благоверном C#....


И тада прибор нам, как жена
И тада жена нам не нуна
И тада любой из нас не против
Дни и ночи проводить в работе?..
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Нелокальные управляющие конструкции считать вредными
От: maxkar  
Дата: 22.03.13 16:26
Оценка: 67 (9) +2
Здравствуйте, Tilir, Вы писали:

Дейкста и гугл говорят про языки с ручным управлением памятью. Не будем забывать об этом.

T>... в которые обоснованно и осторожно вносить недетермнизм там, где он реально нужен. Нити исполнения предлагают противоположный подход."

Нет. Нити не предлагают противоположный подход. Нити появляются там, где "недетерминизм реально нужен". Обработка данных на нескольких ядрах, параллельное выполнение блокирующих и неблокирующих операций (чтение данных и пользовательский UI) и т.п. А вот уже после того, как нити стали нужны, приходится бороться с недетерминизмом и всем остальным. Не нужно путать причину и следствие. Ну и совать многопоточность туда, где она не нужна — тоже.

T>Все эти три цитаты говорят об одном и том же -- о принципиальной вредности нелокальных управляющих конструкций.

Нет. Они говорят о принципиальной вредности нелокальных управляющих конструкций в определенном контексте. В том числе — в контексте проводимых операций, контексте управления ресурсами и т.п. Если сменить контекст, вредность может стать полезностью.

T>* Continuations

Полезны в определенных случаях. Какие-нибудь data flow строить, например. Еще лучше — динамически строить тот же data flow. Вручную перекладывание данных в "процедурном стиле" из одного источника в другой будет ничуть не лучше.

T>* Исключения

Исключительно удобны в "грязном" контексте. Работа с любыми внешними ресурсами. Там же любая операция с ресурсом может вернуть ошибку. И в этом случае на каждой операции нужно вручную(!) проверять код ошибки и при необходимости выполнять очистку ресурсов. Т.е. мы "сильно полагаемся на хороший стиль программирования" в вопросах о том, что ошибка будет замечена вовремя и правильно обработана. Ну и что на что мы поменяли? По мне проще помнить, что у нас грязный контекст, а не писать кучу монотонного ручного кода.

T>* Сопрограммы

T>* Генераторы
Это которые yield return? Так удобно же! Код "генератора вручную" читать и писать гораздо тяжелее, чем yield return. Это, конечно, касается только "чистого" контекста, в котором процедура-генератор не использует ручное управление ресурсами. Т.е., например, читать строки из файла — плохой тон (а вдруг кто-то после первой строки выбросит генератор, не дочитывая до конца и не дожидаясь выполнения закрытия файла).

T>* Нити исполнения

А что взамен? Выше я уже писал, что они используются только там, где по-другому нельзя. Межпроцессное взаимодействие (в сложном виде вроде shared memory и signals) обычно тоже только там, где совсем надо.

T>Каждая нелокальная управляющая конструкция имеет локальный и детерминированный эквивалент.

Да пожалуйста. Приведите аналог "нитей исполнения" в локальном и детерминированном эквиваленте. И желательно в контексте, когда в одном потоке мы читаем файл, а второй обрабатывает события пользовательского интерфейса (хотя бы только перерисовку, ладно уж).

T>Нелокальный GOTO и необходимость использования continuations могут быть ликвидированы при хорошей структурированности программы,

Да. Вопрос только — какой ценой. Вот, пожалуйста, пример. Async.applySync(someFunction, [loadDataA, loadDataB, preloadImages]). Эта страшная штука применяет someFunction после того, как будут загружены данные A, B, и предзагружены картинки. Вся загрузка — асинхронная (сетевая) и параллельная. Это не совсем continuations, (параметров многовато), ну да ладно. Для усложнения ситуации там до этого вполне может быть loadDataB = Async.applyAsync(loadDataB, [loadDataA, loadDataC]), т.е. данные B загружаются после загрузки данных A и C (мало ли что там, ссылки на другие данные, например).

T> исключения и нотификации -- заменены обработкой кодов возврата,

Угу. Обработкой кодов возврата в каждой строчке! Т.е. высокой дисциплиной.
T> сопрограммы и генераторы -- вызовами функций,
В смысле? Вызовами каких функций? Вы предлагаете все, что делает генератор, сначала сложить в память и потом вернуть? А в чем профит? Или как вы "вызовом функций" реализуете ту же банальную конкатенацию списков:
def concat(a, b):
  for elt in a:
    yield return elt;
  for elt in b:
    yield return elt;

Можно, конечно, явным состоянием. Но это будет уже далеко не банальный вызов функции.

T>нити исполнения -- детерминированным параллелизмом на уровне компилятора, таким как OpenMP.

А мы про какие контексты говорим? Для "чистых" программ — да, можно. А вот как быть, если программа с внешней средой взаимодействует? Ну там картинку из интернета загружает и ее в файл сохраняет? При этом пользователь загрузку и отменить может. Ну и OS еще во все это вмешивается снаружи. А еще мы какие-нибудь плагины добавим для обработки фотографии (подгружаемые только в рантайме). Это все компилятор сможет детерминированно отпараллелить? Или ему нужно будет подать на вход все, включая исходную операционную систему?

T>Тем не менее они возникают снова и снова в разных видах из-за ложного чувства упрощения разработки, которое они дают.

Они дают. Естественно, не везде. Но в своих контектсах — дают. Примеры выше.

T> В противовес этому, нелокальные управляющие конструкции дают прирост скорости разработки за счёт потери детерминированности и простоты разрабатываемого приложения.

Да нет, не только разработки. Чтения тоже (yield return читать проще, чем большой и страшный автомат). Кстати, вы от OOP и наследования от интерфейсов тоже предлагаете избавится? Они же недетерминизм вносят (как только мы dependency заинжектили — все, детерминизм кончился).

T>Программа прозрачна когда вы можете сформировать простую умственную модель её поведения, которая действительно предскажет все или большинство случаев работы с программой, потому что вы можете через всю сложность реализации, тем не менее понимать, что происходит".

И касается это в первую очередь внешнего API... Не кода, не того, что и куда там переходит. А внешнего API.

T>это часть неявного контракта на него. Безопасность относительно исключений или безопасность относительно многопоточности, которую должен обеспечить программист это такой же нонсенс, как "безопасность относительно глобальных переменных". Последнюю мы понимаем только в одном смысле -- надо выкинуть глобальные переменные.

А вот здесь вы ошибаетесь. Нельзя выкинуть глобальные переменные. И никто этого не делает. Обычно их инкапсулируют во что-нибудь менее страшное. Поэтому и работать с ними приходится. А давайте мы еще пару "безопасностей" добавим — безопасность относительно освобождения ресурсов и безопасность относительно гарантии обработки всех ошибок. Тоже откажемся? А ведь освобождение ресурсов это вообще частный случай "глобальных переменных"! Т.е. мы откажемся от работы с памятью (можно попробовать на GC поменять), работы с файлами и от всего остального. "гарантия обработки всех ошибок" — только через дисциплину программиста. Между прочим, "безопасность относительно исключений" — это в первую очередь та же самая "безопасность относительно освобождения ресурсов". И при налиции GC ее реализовывать достаточно просто. Вот с многопоточностью — сложнее...

Кстати, как вы хотите бороться с "глобальным состоянием" там, где оно существует исходя из контекста задачи? Банальный почтовый клиент возьмите. Вот пришло пользователю новое сообщение. Это глобальное или локальное состояние? Или нам весь мир нужно перестроить (да, уже там все будет достаточно сложно, потому что событие произошло далеко внизу и придется распутывать сплетения монад друг над другом). А самое интересное, что в процессе "перестроения мира" выяснится, что пользователь подвел курсор мышки к кнопочке и у нас анимация играется уже 0.2 секунды (и осталось еще 0.4 секунды проигрывания). Да и на экране нужно вывести что-нибудь, а это "глобальное состояние" ОС, монитора и пользователя, так что еще и все состояние OS нужно куда-нибудь передать и создать. Казалось бы, простейшая задача. А куда ни плюнь — везде глобальные переменные...

Я не спорю, что от всего "небезопасного" можно отказаться. Но только в достаточно специфичном классе приложений, которые работают практически без взаимодействия со внешним миром.

T>где модификация загнана внутрь монад и интеллектуальные ресурсы разработчика не расходуются на поддержку консистентности модифицируемых структур данных при нелокальных переходах.

Это сказки все про монады. К каждой монаде прилагатеся portable black hole в виде fail :: String -> m A. Это потрясающий недетерминизм в силу своего определения. Плюс он еще и нетипизированный. Если потом где-то откопаешь и выловишь этот fail, еще долго будешь разбираться, что же там произошло на самом деле. Уж лучше типизированные исключения (которые потом и заматчить можно, и все остальное)... Собственно, я поэтому для развлечений все-таки сменил haskell на scala. Мне нужны не "строковые" ошибки, мне нужны ошибки, которые я могу обрабатывать (например, локализовывать потом). Обратите внимание, что нет ни одного parser combinator library, у которой ошибки парсинга строго типизированы (они все строками через fail проползают)! И вряд ли появится в ближайшее время. В силу системы типов. А на исключениях я могу делать все, что нужно. Правда, компилятором они не проверяются (не умеют пока компиляторы нормально обрабатывать union types, нужные для нормальной композции функций с исключениями).

T>В отчёте С++ performance technical report [7], про исключения аккуратно сказано: "...

А оно так часто надо? Тем более, что и без исключений нельзя дать точную границу. Как вы будете оценивать время, занимаемое деструкторами объектов. Да и теми же банальнымы сишными free? (hint — что-нибудь могло засвопиться). Или какого-нибудь close(fd)? Основная проблема не в исключениях ведь. А в том, что там уже где-то образовалась достаточно сложная структура, которую и без исключений сложно детерминированно разобрать. Кстати, на кодах ошибок у вас может такая же структура образоваться со все тем же детерминизмом.

T>Это очень типичный аргумент для всех адептов нелокального счастья -- они советуют писать код с использованием всех их прекрасных возможностей, а уже потом при отладке многопоточного приложения с исключениями и сопрограммами искать узкие места и думать над тем, откуда это счастье выкинуть. И это явно не инженерный подход.

Это явно инженерный подход. Как показала практика, попытка предварительно все учесть в мелочах обычно кончается тем, что оптимизируется далеко не то, что на самом деле нужно. Вот в том, тчо это не математический подход соглашусь. Нет детерминизма, нет каких-то явно доказанных гарантий. Зато оно решает реальные задачи во вполне реальном физическом мире. И вот это то и есть вполне инжеренный подход — где можем, используем математику. Где не можем — не используем. Только математики комплексуют и обижаются иногда

T>При проектировании надёжных, стабильно работающих приложений, мы должны, уже на этапе выбора архитектуры, инструментов и методологий, сводить к минимуму дорогостоящую отладку и поддержку на поздних этапах существования продукта. ... Речь идёт о принципиальном отказе от недетеринизма, так же, как мы отказываемся от нелокальных переходов и убираем зависимости от глобальных переменных.


Там, где можно, от недетерминизма и отказываются. А там, где нельзя — не отказываются. Ну и так же, как с глобальными переменными — мы не отказываемся от них, мы их инкапсулируем. Сложные yield инкапсулированы в интерфейсе метода. Исключения — замена кодам ошибок. Иногда — достаточно хорошая. Потому что иногда приходится возвращать сложную структуру. Или там long long произвольный. Как вы его на кодах ошибок вернете? Через глобальные переменные (от которых мы отказались)? Или через выделение структуры? Очевидно, что структура будет выделяться на стороне вызывающего (потому что внутри функции память может не выделиться, а код ошибки мы вернуть не можем ). Отлаживать это все будет тоже "приятно"...

T>Именно поэтому я предлагаю все нелокальные управляющие конструкции считать вредными.

А локальные переменные (которые мутабельные) вы еще не предлагали запретить? Про них можно все те же аргументы привести, что и про глобальные переменные (только чуть в меньшем масштабе). У меня на них (т.е. на чем-то мутабельном) парсер кода (который в мозгу) раза в 3-4 сразу замедляется. Ну за исключением ситуации с fold/map/filter (это когда за переменной сразу специфический цикл идет). А это ведь сложность поддержки... Кстати, код с исключениями (checkd, ибо java) я читаю в среднем гораздо быстрее, чем код на кодах ошибок. Нужные контракты и гарантии обычно проверяются наличием правильного try/finally. На кодах ошибок там бы лесенки были...

P.S. Исключения — это локальный переход! Так же, как и break/continue/return. Типичная монада Either<AbruptCompletion, R>. Прекрасно разворачивается в последовательный код (с проверкой состояния на каждом шаге) и после этого ничем не отличается от возвратов на кодах ошибок.
Re[2]: Нелокальные управляющие конструкции считать вредными
От: LaptevVV Россия  
Дата: 22.03.13 16:33
Оценка: 8 (1)
Здравствуйте, Ikemefula, Вы писали:

I>Здравствуйте, Tilir, Вы писали:


T>>Сложность жизненного цикла программы это не только сложность разработки, но и сложность поддержки. Большинство признанных сегодня методов разработки, таких как структурное программирование, ООП и паттерны проектирования, etc направлены на борьбу со сложностью и делают разрабатываемые системы более прозрачными, более соответствующими Principle Of Least Astonishment [5], и в конечном итоге более простыми в поддержке. В противовес этому, нелокальные управляющие конструкции дают прирост скорости разработки за счёт потери детерминированности и простоты разрабатываемого приложения.


I>Не совсем понятно, что такое нелокальные конструкции.

Это — нелокальные переходы.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: Нелокальные управляющие конструкции считать вредными
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.03.13 12:54
Оценка: +1
Здравствуйте, Tilir, Вы писали:

T>Какие вообще нелокальные управляющие конструкции мы знаем? ... Вот только некоторые источники головной боли в наше время:


T>* Нелокальный GOTO

T>* Continuations
T>* Исключения
T>* Сигналы
T>* Нотификации
T>* Сопрограммы
T>* Генераторы
T>* Нити исполнения
T>... имя им легион ...

Нелокальный GOTO, исключения и потоки несомненно создают проблемы. Остальное перечисленное притянуто за уши. Бремя доказательства оставляю за тобой как за автором высказывания. Обоснуй, плиз, вред создаваемый хотя бы сопрограммами/генераторами.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Нелокальные управляющие конструкции считать вредными
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.03.13 13:03
Оценка: +2
Здравствуйте, Tilir, Вы писали:

T> Но по многим причинам (в частности из-за высокого входного порога и принципиально низкой производительности на существующих архитектурах -- представьте, например, что вместо модификации одного узла дерева, вы полностью уничтожаете и создаёте заново весь миллион его узлов),


Прежде чем судить о мирах тебе не известных ты бы потрудился разобраться в их устройстве. Слова про изменение миллионов узлов — это бред. В неизменяемом мире нет нужды пересоздвать все узлы дерева, если изменился один из них. При этом преподаются только узлы от текущего и до корня. А это обычно очень небольшое число узлов.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Нелокальные управляющие конструкции считать вредными
От: FR  
Дата: 23.03.13 16:34
Оценка:
Здравствуйте, VladD2, Вы писали:

T>>* Нелокальный GOTO

T>>* Continuations
T>>* Исключения
T>>* Сигналы
T>>* Нотификации
T>>* Сопрограммы
T>>* Генераторы
T>>* Нити исполнения
T>>... имя им легион ...

VD>Нелокальный GOTO, исключения и потоки несомненно создают проблемы.


Continuations тоже.
Во первых можно сломать мозг
Во вторых наломать дров на продолжениях без проблем.
Но как и с макросами по моему польза перевешивает вред.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.