А как выглядит код "прочитать столбец чисел из текстового файла, и записать во второй файл их сумму"?
Так что ли:
Проверить нет ли ошибки при открытии файла на чтение, В цикле: Проверить нет ли ошибки при чтении строки, Проверить нет ли ошибки при конвертации строки в число
Проверить нет ли ошибки при открытии второго файла Проверить нет ли ошибки при записи строки во второй файл Проверить нет ли ошибки при flush/close
Ну, я лично считаю что это неправильно (мешает читать и исправлять код), и всё равно не работает.
Не работает, во-первых, из-за человеческого фактора (и это основная проблема),
во-вторых, система виртуальной памяти Linux всё равно "кидает исключения" при ленивом выделении страничек памяти.
Прочитал значение из массива или рекурсию глубокую сделал - и всё, "прямолинейное исполнение" заканчивается, запускается обработчик "исключения" в вызываеющем bash-скрипте
Подумаешь! А в ядре переполнение стека не обработать без специальной задачи. :) И вообще там чёрным по белому пишут, что оно хотя и обрабатывает чего-то, состояние процессора не гарантируется, и после этого нужно делать сброс и перезагрузку.
Для случаев, когда ошибка по логике вещей быть не должна, можно использовать wrapper макро, который проверяет код возврата, и логает + крэшает, если была ошибка. Для случаев, когда ошибка предусмотрена, ну, проверяешь статусы и делаешь retry. Это не намного сложнее, чем бросаться exception-ами, зато clear и нет целого класса возможных issues с exception'ами.
Ну если мы имеем, скажем, математика, который не хочет вникать в C++ исключения - то они так и работают, "залогать и крешнуть". Его все равно не заставить дисциплировано использовать коды возврата. В таком случае его код будет от опечатки во входных файлах будет выдавать мусор, вместо "залогать и крешать". Я очень много этого наелся, от atoi и atof в чужом коде. Миня аж трисет от atoi
( ... )
Вы хотите меня убедить, что exceptions лучше? Я хорошо понимаю плюсы и минусы обоих подходов. Там примерно поровну как радостей, так и говна с обеих сторон. Я чуть больше склонен в сторону кодов возврата, поэтому гугловский style guide мне и понравился.
Отдельно возражу по поводу п.2. (передача дальше) и ментальной энергии. Это будет одно возражение сразу на два аргумента. Ментальная энергия очень хорошо экономится, когда можно заниматься дизайном и написанием кода, а не сидением в дебаггере, разбирая exceptions, которые по три раза за-wrapp'ились и поменялись в процессе своего путешествия.
> "goto из макроса" - бррр. Если есть RAII, и поэтому вместо "goto из макроса" мы можем "return из макроса", то у нас уже всё готово для использования исключений.
> разбирая exceptions, которые по три раза за-wrapp'ились и поменялись в процессе своего путешествия. Если исключения - это "3 раза поменялись", то коды возврата - это "29 раз из 30 поменялись, и 1 раз забыли проверить".
Ну вы таки хотите мериться потенциальными gotchas, кто больше? Ладно, подыграю. Вот, например, дам такой: есть catch all или catch слишком-многого, и он жрёт валидный error case, но благодаря разному другому resiliency всё тихо, просто в 100 раз медленнее.
Я предпочитаю считать, что если я написал throw или catch, то значит я готов на тормоза, эквивалетные открытию файла, break отладчика, останову процесса и тп.
Если я на такие последствия не готов - значит нужно использовать
MaybeOptional MaybeParseInt() ( bool MaybeParseInt(int *out) ) а не int ParseInt()
Это красивая теория. На практике, если exceptions используются в принципе, то они часто используются в more or less excepted case (вроде ParseInt вместо MaybeParseInt), и тормоза становятся понятны только когда-т кто-то основательно посидит в профайлере. Потом будет зачистка, потом в команду придёт ещё несколько новых человек, и начнётся по новой. Стандартный attrition rate 15%. За 5 лет команда может поменяться почти целиком. Если речь идёт о сопровождении большой системы, готовы ли вы рефакторить весь код постоянно?
> Ментальная энергия очень хорошо экономится, когда можно заниматься дизайном и написанием кода
Именно! Ментальная энергия экономится, когда можно писать
ui64 sum = 0; FileInput in(input_fileiname, "r"); while(строка l из in) sum += ToInt(l.Strip()); FileOutput(output_fileiname, "w").Write(ToString(sum)).Flush();
А не обрабатывать случаи "при конвертации числа в строку кончилась память".
Reply
Reply
Так что ли:
Проверить нет ли ошибки при открытии файла на чтение,
В цикле:
Проверить нет ли ошибки при чтении строки,
Проверить нет ли ошибки при конвертации строки в число
Проверить нет ли ошибки при открытии второго файла
Проверить нет ли ошибки при записи строки во второй файл
Проверить нет ли ошибки при flush/close
Reply
Reply
Не работает,
во-первых, из-за человеческого фактора (и это основная проблема),
во-вторых, система виртуальной памяти Linux всё равно "кидает исключения" при ленивом выделении страничек памяти.
Прочитал значение из массива или рекурсию глубокую сделал - и всё, "прямолинейное исполнение" заканчивается, запускается обработчик "исключения" в вызываеющем bash-скрипте
Reply
А человеческий фактор всегда и везде проявляется, не так, так иначе. Все ошибки не найдёшь и не исправишь.
Reply
Это лайфхак для 64-битных машин - выделяешь терабайт через malloc, и используешь как бесконечный стек, не думая какой .reserve()/.capacity() выбрать.
Ну а в windows - C код не может обработать например stack overflow без SEH.
Reply
Reply
Reply
Reply
Отдельно возражу по поводу п.2. (передача дальше) и ментальной энергии. Это будет одно возражение сразу на два аргумента. Ментальная энергия очень хорошо экономится, когда можно заниматься дизайном и написанием кода, а не сидением в дебаггере, разбирая exceptions, которые по три раза за-wrapp'ились и поменялись в процессе своего путешествия.
"goto из макроса" - бррр.
Reply
Если есть RAII, и поэтому вместо "goto из макроса" мы можем "return из макроса", то у нас уже всё готово для использования исключений.
> разбирая exceptions, которые по три раза за-wrapp'ились и поменялись в процессе своего путешествия.
Если исключения - это "3 раза поменялись", то коды возврата - это
"29 раз из 30 поменялись, и 1 раз забыли проверить".
Reply
Ладно, подыграю. Вот, например, дам такой: есть catch all или catch слишком-многого, и он жрёт валидный error case, но благодаря разному другому resiliency всё тихо, просто в 100 раз медленнее.
Reply
Я предпочитаю считать, что если я написал throw или catch, то значит я готов на тормоза, эквивалетные открытию файла, break отладчика, останову процесса и тп.
Если я на такие последствия не готов - значит нужно использовать
MaybeOptional MaybeParseInt() ( bool MaybeParseInt(int *out) )
а не
int ParseInt()
Reply
Reply
Именно! Ментальная энергия экономится, когда можно писать
ui64 sum = 0;
FileInput in(input_fileiname, "r");
while(строка l из in)
sum += ToInt(l.Strip());
FileOutput(output_fileiname, "w").Write(ToString(sum)).Flush();
А не обрабатывать случаи "при конвертации числа в строку кончилась память".
Reply
Leave a comment