Ну вот про C# то же самое говорили. Или про Java - что, кстати, почти оправдалось.
Реально встречаются Java-приложения, сложные - и работающие на любой платформе.
Вопрос же не в том, чтобы скомпилировать (этот вопрос давно закрыт с появлением llvm) - вопрос в том, чтобы предоставить разработке стандартное, универсальное API для работы. И чем оно более полное - тем сложнее и глючнее.
И да: если нужен "просто код" или "просто алгоритм" - так для этого можно или написать на интерпретируемом языке, или исходники предоставить. Все проблемы начинаются, когда хочется сделать "красиво". И интегрировано в нативное окружение.
Обращение по нулевому указателю С++ отлавливает, но в вашем примере его скорее всего просто не было. Параллельный доступ к дереву испортил его структуру, вероятно - часть памяти была утеряна, но обращения по нулевому указателю не было. "Испорченную" структуру данных типа зацикленного списка не отловит ничего, ну разве что кроме сторожевого таймера.
Рекомендую попробовать ваш пример для С++ со включённым анализатором времени исполнения - например, clang AddressSanitizer может отловить всякое.
Нет ни одной здравой причины перехватывать исключения, ну разве кроме случаев когда через исключение передают информацию о логической ошибке. Программа с ошибкой должна немедленно грохаться, чтобы эту ошибку можно было выявить и исправить. Максимум, что можно себе позволить: сохранить дополнительные данные в процессе завершения.
Нужна. Как минимум - математика в объеме до 1-2 курса хорошего вуза программисту нужна. Хотя бы для того, чтобы понимал, что такое сложность, почему экспоненциально-сложный или экспоненциальный по памяти алгоритм будет отлично работать на тестовых примерах, но завалится на реальных.
Лично я словил когнитивный диссонанс, когда обнаружил, что субподрядчик (фирма) состоит из программистов, не понимающих, как устроено кольцо целых чисел по модулю 65536.
То есть люди реально этого не понимали (то есть не понимали, что получится, если складывать uint16_t и что происходит при переполнении, и что в выражении из переменных этого типа можно раскрывать скобки и переносить слагаемые), и городили дополнительную сложность, лишь бы их алгоритм "не вызвал переполнения".
Ну и разумеется, математика отлично тренирует мозги: увеличивает сложность нейронных связей, и улучшает способность мыслить логически.
Ага, встречалось такое. Люди делают десятки классов, сложносочинённые конструкции. Спрашиваешь: "нахрена? Задача ведь сформулирована была вот ровно так?". Отвечают "а вдруг придётся делать всё через жопу в неопределённом будущем...".
Заодно убивают производительность.
Я не шучу: у меня один новый сотрудник написал замечательный класс транспорта (данные по резервированному TCP-соединению). Отдельный класс формирования заголовка, отдельный класс - вычисления контрольной суммы, отдельный класс упаковки и распаковки со ссылками на два других класса. И отдельный класс для записи данных: по одному байту через вызов виртуальной функции.
На тестах всё работало (с).
Потом "неожиданно" выяснилось, что передавать надо где-то мегабайт в секунду данных.
Знаете, сколько занимает 1 миллион вызовов виртуальной функции?
Вот именно то, что perl не меняется десятилетиями и при этом есть везде - и подкупает.
Да, изначальный синтаксис придумывали люди с более высоким IQ, чем у нынешних поколений - потому что это была эпоха, когда программист не мог назвать себя программистом, если не умел хоть немного писать в машинных кодах.
Но ведь не обязательно держать в голове все умолчания и сокращения, можно писать подробно.
И тут возникает вопрос: если ловить это исключение на самом высоком уровне (или позволить программе грохнуться) - контекст, объясняющий ЗАЧЕМ этот файл пытались открыть - будет потерян.
Мы его хотели создать? Прочитать? Что это вообще за файл, почему - возможно, не надо этот файл создавать, чтобы программа работала нормально - возможно, его имя указано неверно в конфигурации.
А если ловить на уровень выше - то получается точно то же самое, как обрабатывать возвращаемое значение.
В общем и целом: идея исключений была: "пишем код, не заморачиваясь обработкой ошибок по месту - которая резко снижает читаемость/понимаемость кода - и обрабатываем все ошибки уже после алгоритма, в блоке catch" - а по факту получилось, что выигрыша (для читабельности/удобства) - нет, если мы хотим более-менее вменяемые сообщения о ошибках.
Концепция исключений, как таковых - она изначально (когда её придумывали, наверно, уже лет 30 назад) казалась удобной и разумной - но на практике оказалась неудобной и неразумной, не считая одного или двух случаев.
Основная проблема с идеологией try/catch - она состоит в том, что к тому времени, как мы исключение обрабатываем - мы полностью потеряли контекст, в котором оно возникло.
Примеры с одним-двумя выражениями в блоке try() - они на самом деле бессмысленные, так как технически ничем не лучше "давайте обработаем ошибку прямо тут".
А когда у вас в блоке кода 3-5 мест, где может быть выброшено исключение одного и того же типа (например, мы открываем на чтение два файла, и третий - на запись, и каждый из них может не существовать или быть недоступен) - нам надо или принудительно сохранять контекст, или мы этот контекст потеряем.
Так что я в своё коде пришёл единственному разумному подходу: не генерируют исключений, формирую сообщение о ошибке и возвращаю "неудача".
Для С++ это выглядит как:
bool some_func(... , std::string & a_err);
В других языках - можно возвращать пару (реальный результат и сообщение о ошибке).
В моём коде прямо при выполнении каждого действий или проверке - в случае неудачи, формируется сообщение о ошибке, включающее весь нужный контекст. Потому что именно там этот контекст и есть.
Код уровнем выше может дополнить сообщение о ошибке. Например, функция проверки контрольной суммы файла может вернуть "файла нет/нет записи контрольной суммы/сумма неверна" - а уровнем выше, добавить - почему именно в этом файле должна быть контрольная сумма.
Чтобы пользователь не задавал мне идиотских вопросов "почему твоя программа валится с исключением, которое мне ничего не говорит?"
Да, можно кидать исключение со сформированным сообщением о ошибке - но это:
а) ничем не проще, чем сделать return false
b) если в коде несколько вызовов, чтобы добавить к ним контекст - придётся каждый окружать блоком try/catch. Это чисто физуально будет больше кода, чем просто:
if (! some_func(...,a_err))
{
a_error = "Additional context, failed: " + a_err;
return false;
}
Аж на две скобки меньше
======================
А когда использование исключений реально полезно? А в тех же (редких) случаях, когда раньше в языке С использовали longjump.
Лично я вижу только два применения
а) легитимно, в модульных тестах - вместо фатального завершения при срабатывании halt() или verify() (макросы, активно используемые в защитном программировании, штатно выдающие сообщение на консоль и завершающие с помощью abort()) - кинуть исключение и перехватить его в тесте. Удобно тестировать наличие/срабатывание проверок защитного программирования.
б) хак. Современные линуксы позволяют выполнять throw из сингальных хандлеров (хотя и не гарантируют). Можно выйти из зациклившегося алгоритма по SIGALARM.
Компилятор не удалял массив. Он сохранял его в исполняемом файле в компактном виде, в виде алгоритма, который генерировал его в оперативной памяти при старте программы.
Компилятор может не только код отомтимизировать, но и память.
Я в своё время сражался с llvm, пытаясь убедить его оставить в экзешнике крупный массив байт (в готовый экзешник в этот массив записывалась дополнительная информация).
gcc в такой ситуации достаточно было массив инициализировать, хотя бы не полностью - а llvm отследил варианты предсказуемой инициализации массива (например, константой отличной от нуля) и массив из экзешника выпиливал. Создавал динамически, на этапе инициализации.
Пришлось инициализировать массив сложной последовательностью, так чтобы компилятор не догадался, как её можно сгенерировать.
Вообще-то это ничего не значит. Ядро Линукс тоже на С написано. Что, из-за этого С++ не пользоваться?
Под современные микроконтроллеры спокойно можно писать на С++, т.к. clang и gcc пофиг, что преобразовывать в машинный код.
И да, при желании - с использованием урезанной стандартной библиотеки (без ввода/вывода), т.е. запросто можете пользоваться std::map и std::string. Если это удобно разработчику.
Да, надо иметь в виду, что использование динамической памяти на микроконтроллере - может привести к неожиданным результатам, но при грамотном использовании - более чем полезно.
А использовать проверенную десятилетиями связку flex+bison было слишком страшно?
Это ведь идеальная задача для парсера и грамматики.
Учитывая, что судя по вашему описанию - ваша утилита будет глючить на
комментариях, содержащих фигурные скобки
строковых литералах, содержащих фигурные скобки
символьных литералах, содержащих фигурные скобки
В общем, советую переделать по-взрослому.
Кстати, если немного подумаете - то сможете не просто проверочную утилиту реализовать, а реализовать утилиту, которая автоматом будет вставлять эти комментарии.
Ну да. Причём по его комментарию - ничего там особенного нет.
Фактически Столлмен возражал против того, что секс с 17-летней девкой, которая этим занимается профессионально (что тоже нарушает закон, и Столлмен это не отрицал) - описывали в терминах, создающих впечатление о изнасиловании несовершеннолетней лет так 12. Или 5.
Основная проблема с программированием в настоящее время - это то, что процентов 90, а может быть - 95 программистов занимаются полной хренью, которая по большому счёту нафиг никому не нужна и будет выкинута и забыта через полгода после окончания работы.
Миллионный раз делают те же самые окошки. Только полупрозрачные и с закруглёнными углами. Или формочки. Или тупую "бизнес-логику". Или очередной супердинамический сайт на модном фреймворке. Или очередную версию "офисного пакета".
В общем и целом - 90% усилий уходит в абсолютное НИ-КУ-ДА.
Не потому, что программисты такие глупыи или злонамеренные.
А потому, что современный бизнес, ориентированный на прибыль - требует именно этого.
Частично это - желание менеджеров найти оправдание существованию своих отделов. Ведь если вся работа сделана и осталась только поддержка и исправление редких багов - финансирование придётся сократить.
Частично - желание программистов, занимающихся тупой хренью типа впихивания контекстной рекламы - использовать какой-нибудь модный подход или новый язык. Или самим сделать новый фреймворк. Или перейти на новый фреймворк. Или переделать всё на микросервисы, при этом каждый - на своём языке.
Если так подумать - существенная часть умственного ресурса цивилизации сливается в унитаз. На разработку очередного "тапающего хомяка", например.
В общем, что и ожидалось. Вы можете сделать систему ИИ, которая демонстрирует сложное и вроде бы разумное поведение - но не можете реально оценить, не будет ли в этом поведении странных неожиданных сбоев.
Тут одно из двух: или простые, полностью понятные человеку алгоритмы - или вот так.
Ну вот про C# то же самое говорили. Или про Java - что, кстати, почти оправдалось.
Реально встречаются Java-приложения, сложные - и работающие на любой платформе.
Вопрос же не в том, чтобы скомпилировать (этот вопрос давно закрыт с появлением llvm) - вопрос в том, чтобы предоставить разработке стандартное, универсальное API для работы. И чем оно более полное - тем сложнее и глючнее.
И да: если нужен "просто код" или "просто алгоритм" - так для этого можно или написать на интерпретируемом языке, или исходники предоставить. Все проблемы начинаются, когда хочется сделать "красиво". И интегрировано в нативное окружение.
Обращение по нулевому указателю С++ отлавливает, но в вашем примере его скорее всего просто не было. Параллельный доступ к дереву испортил его структуру, вероятно - часть памяти была утеряна, но обращения по нулевому указателю не было. "Испорченную" структуру данных типа зацикленного списка не отловит ничего, ну разве что кроме сторожевого таймера.
Рекомендую попробовать ваш пример для С++ со включённым анализатором времени исполнения - например, clang AddressSanitizer может отловить всякое.
Нет ни одной здравой причины перехватывать исключения, ну разве кроме случаев когда через исключение передают информацию о логической ошибке. Программа с ошибкой должна немедленно грохаться, чтобы эту ошибку можно было выявить и исправить. Максимум, что можно себе позволить: сохранить дополнительные данные в процессе завершения.
Никто не мешает сделать это опцией, и собирать библиотеку по-разному для разных сценариев использования.
Нужна. Как минимум - математика в объеме до 1-2 курса хорошего вуза программисту нужна. Хотя бы для того, чтобы понимал, что такое сложность, почему экспоненциально-сложный или экспоненциальный по памяти алгоритм будет отлично работать на тестовых примерах, но завалится на реальных.
Лично я словил когнитивный диссонанс, когда обнаружил, что субподрядчик (фирма) состоит из программистов, не понимающих, как устроено кольцо целых чисел по модулю 65536.
То есть люди реально этого не понимали (то есть не понимали, что получится, если складывать uint16_t и что происходит при переполнении, и что в выражении из переменных этого типа можно раскрывать скобки и переносить слагаемые), и городили дополнительную сложность, лишь бы их алгоритм "не вызвал переполнения".
Ну и разумеется, математика отлично тренирует мозги: увеличивает сложность нейронных связей, и улучшает способность мыслить логически.
Удивлю я вас до невозможности ... но с помощью осциллографа люди до сих пор отлаживаются. И бывает, что других вариантов - нет.
Тактика диверсионных групп? "Это многое объясняет (тм)"
Какое главное отличие результата деятельности диверсионных групп от разработки ПО?
А диверсионная группа или достигает цели - причём всем пофиг побочный ущерб - или нет.
Мост взорван, штаб уничтожен. Получите медали и переходите к следующему заданию.
Или - "все умерли".
А вот при разработке ПО можно сделать говно - и потом с ним придётся маяться годами.
Ага, встречалось такое. Люди делают десятки классов, сложносочинённые конструкции. Спрашиваешь: "нахрена? Задача ведь сформулирована была вот ровно так?". Отвечают "а вдруг придётся делать всё через жопу в неопределённом будущем...".
Заодно убивают производительность.
Я не шучу: у меня один новый сотрудник написал замечательный класс транспорта (данные по резервированному TCP-соединению). Отдельный класс формирования заголовка, отдельный класс - вычисления контрольной суммы, отдельный класс упаковки и распаковки со ссылками на два других класса. И отдельный класс для записи данных: по одному байту через вызов виртуальной функции.
На тестах всё работало (с).
Потом "неожиданно" выяснилось, что передавать надо где-то мегабайт в секунду данных.
Знаете, сколько занимает 1 миллион вызовов виртуальной функции?
Вот именно то, что perl не меняется десятилетиями и при этом есть везде - и подкупает.
Да, изначальный синтаксис придумывали люди с более высоким IQ, чем у нынешних поколений - потому что это была эпоха, когда программист не мог назвать себя программистом, если не умел хоть немного писать в машинных кодах.
Но ведь не обязательно держать в голове все умолчания и сокращения, можно писать подробно.
Не беспокойтесь, проблема уже решена автоматически.
Большую часть контента в сети уже генерируют боты.
Соответственно, как бы не исхитрялись владельцы ИИ - обучаться им будет нечем, так как обучение на сгенерированном контенте приводит к маразму ИИ.
Разумеется, может! ИИ может делать обзорные статьи и выжимки.
Читать только их должен будет тоже ИИ, впрочем как и писать эти самые исходные статьи и выжимки.
Тогда круг успешно замкнётся, и человечество наконец-то сможет перестать заниматься ерундой.
И тут возникает вопрос: если ловить это исключение на самом высоком уровне (или позволить программе грохнуться) - контекст, объясняющий ЗАЧЕМ этот файл пытались открыть - будет потерян.
Мы его хотели создать? Прочитать? Что это вообще за файл, почему - возможно, не надо этот файл создавать, чтобы программа работала нормально - возможно, его имя указано неверно в конфигурации.
А если ловить на уровень выше - то получается точно то же самое, как обрабатывать возвращаемое значение.
В общем и целом: идея исключений была: "пишем код, не заморачиваясь обработкой ошибок по месту - которая резко снижает читаемость/понимаемость кода - и обрабатываем все ошибки уже после алгоритма, в блоке catch" - а по факту получилось, что выигрыша (для читабельности/удобства) - нет, если мы хотим более-менее вменяемые сообщения о ошибках.
Концепция исключений, как таковых - она изначально (когда её придумывали, наверно, уже лет 30 назад) казалась удобной и разумной - но на практике оказалась неудобной и неразумной, не считая одного или двух случаев.
Основная проблема с идеологией try/catch - она состоит в том, что к тому времени, как мы исключение обрабатываем - мы полностью потеряли контекст, в котором оно возникло.
Примеры с одним-двумя выражениями в блоке try() - они на самом деле бессмысленные, так как технически ничем не лучше "давайте обработаем ошибку прямо тут".
А когда у вас в блоке кода 3-5 мест, где может быть выброшено исключение одного и того же типа (например, мы открываем на чтение два файла, и третий - на запись, и каждый из них может не существовать или быть недоступен) - нам надо или принудительно сохранять контекст, или мы этот контекст потеряем.
Так что я в своё коде пришёл единственному разумному подходу: не генерируют исключений, формирую сообщение о ошибке и возвращаю "неудача".
Для С++ это выглядит как:
bool some_func(... , std::string & a_err);
В других языках - можно возвращать пару (реальный результат и сообщение о ошибке).
В моём коде прямо при выполнении каждого действий или проверке - в случае неудачи, формируется сообщение о ошибке, включающее весь нужный контекст. Потому что именно там этот контекст и есть.
Код уровнем выше может дополнить сообщение о ошибке. Например, функция проверки контрольной суммы файла может вернуть "файла нет/нет записи контрольной суммы/сумма неверна" - а уровнем выше, добавить - почему именно в этом файле должна быть контрольная сумма.
Чтобы пользователь не задавал мне идиотских вопросов "почему твоя программа валится с исключением, которое мне ничего не говорит?"
Да, можно кидать исключение со сформированным сообщением о ошибке - но это:
а) ничем не проще, чем сделать return false
b) если в коде несколько вызовов, чтобы добавить к ним контекст - придётся каждый окружать блоком try/catch. Это чисто физуально будет больше кода, чем просто:
if (! some_func(...,a_err))
{
a_error = "Additional context, failed: " + a_err;
return false;
}
Аж на две скобки меньше
======================
А когда использование исключений реально полезно? А в тех же (редких) случаях, когда раньше в языке С использовали longjump.
Лично я вижу только два применения
а) легитимно, в модульных тестах - вместо фатального завершения при срабатывании halt() или verify() (макросы, активно используемые в защитном программировании, штатно выдающие сообщение на консоль и завершающие с помощью abort()) - кинуть исключение и перехватить его в тесте. Удобно тестировать наличие/срабатывание проверок защитного программирования.
б) хак. Современные линуксы позволяют выполнять throw из сингальных хандлеров (хотя и не гарантируют). Можно выйти из зациклившегося алгоритма по SIGALARM.
Компилятор не удалял массив. Он сохранял его в исполняемом файле в компактном виде, в виде алгоритма, который генерировал его в оперативной памяти при старте программы.
Компилятор может не только код отомтимизировать, но и память.
Я в своё время сражался с llvm, пытаясь убедить его оставить в экзешнике крупный массив байт (в готовый экзешник в этот массив записывалась дополнительная информация).
gcc в такой ситуации достаточно было массив инициализировать, хотя бы не полностью - а llvm отследил варианты предсказуемой инициализации массива (например, константой отличной от нуля) и массив из экзешника выпиливал. Создавал динамически, на этапе инициализации.
Пришлось инициализировать массив сложной последовательностью, так чтобы компилятор не догадался, как её можно сгенерировать.
Вообще-то это ничего не значит. Ядро Линукс тоже на С написано.
Что, из-за этого С++ не пользоваться?
Под современные микроконтроллеры спокойно можно писать на С++, т.к. clang и gcc пофиг, что преобразовывать в машинный код.
И да, при желании - с использованием урезанной стандартной библиотеки (без ввода/вывода), т.е. запросто можете пользоваться std::map и std::string. Если это удобно разработчику.
Да, надо иметь в виду, что использование динамической памяти на микроконтроллере - может привести к неожиданным результатам, но при грамотном использовании - более чем полезно.
А использовать проверенную десятилетиями связку flex+bison было слишком страшно?
Это ведь идеальная задача для парсера и грамматики.
Учитывая, что судя по вашему описанию - ваша утилита будет глючить на
комментариях, содержащих фигурные скобки
строковых литералах, содержащих фигурные скобки
символьных литералах, содержащих фигурные скобки
В общем, советую переделать по-взрослому.
Кстати, если немного подумаете - то сможете не просто проверочную утилиту реализовать, а реализовать утилиту, которая автоматом будет вставлять эти комментарии.
Ну да. Причём по его комментарию - ничего там особенного нет.
Фактически Столлмен возражал против того, что секс с 17-летней девкой, которая этим занимается профессионально (что тоже нарушает закон, и Столлмен это не отрицал) - описывали в терминах, создающих впечатление о изнасиловании несовершеннолетней лет так 12. Или 5.
Зачем использовать эту технологию для уже выведенного в космос аппарата?
Реактор, который далее используется для питания ионных движков - эффективнее на два порядка.
Основная проблема с программированием в настоящее время - это то, что процентов 90, а может быть - 95 программистов занимаются полной хренью, которая по большому счёту нафиг никому не нужна и будет выкинута и забыта через полгода после окончания работы.
Миллионный раз делают те же самые окошки. Только полупрозрачные и с закруглёнными углами. Или формочки. Или тупую "бизнес-логику". Или очередной супердинамический сайт на модном фреймворке. Или очередную версию "офисного пакета".
В общем и целом - 90% усилий уходит в абсолютное НИ-КУ-ДА.
Не потому, что программисты такие глупыи или злонамеренные.
А потому, что современный бизнес, ориентированный на прибыль - требует именно этого.
Частично это - желание менеджеров найти оправдание существованию своих отделов. Ведь если вся работа сделана и осталась только поддержка и исправление редких багов - финансирование придётся сократить.
Частично - желание программистов, занимающихся тупой хренью типа впихивания контекстной рекламы - использовать какой-нибудь модный подход или новый язык. Или самим сделать новый фреймворк. Или перейти на новый фреймворк. Или переделать всё на микросервисы, при этом каждый - на своём языке.
Если так подумать - существенная часть умственного ресурса цивилизации сливается в унитаз. На разработку очередного "тапающего хомяка", например.
В общем, что и ожидалось. Вы можете сделать систему ИИ, которая демонстрирует сложное и вроде бы разумное поведение - но не можете реально оценить, не будет ли в этом поведении странных неожиданных сбоев.
Тут одно из двух: или простые, полностью понятные человеку алгоритмы - или вот так.