В таком случае, на мой взгляд, желательно сохранить информацию об изначальном исключении, но не давать к нему программного доступа — т.е. выводить только где-нибудь в стектрейсе. Как раз чтобы избежать ситуации, когда пользователь библиотеки привяжется к деталям реализации, но при этом позволить в ручном режиме диагностировать возможные проблемы с внешним миром.
Первый — это вывалить наружу "кишки", т.е. просто не обрабатывать ошибки типа ETIMEDOUT или какого-нибудь socket.gaierror, а просто передать их наружу вызывающей стороне. Минус такого подхода — если мы реализуем что-то посложнее http-клиента (например, какой-нибудь ORM, который может работать сразу с кучей разных БД), мы утекаем наружу детали реализации, и делаем эти детали реализации неявной частью контракта. В будущем мы не сможем просто взять и прозрачно перейти с BSD sockets на какой-нибудь WinSock, т.к. код вызывающей стороны уже рассчитывает получать исключения вполне определенного образца. На мой взгляд, это проблема.
Поэтому альтернативным решением может быть обработка того же таймаута внутри клиента. В самом простом случае "обработка" будет заключаться только в том, что мы перевыбросим исключение о таймауте, но уже обернув его в наш объект исключения (или вернув какой-нибудь наш собственный retval_t и т.д.). Это позволит авторам библиотеки менять реализацию с сохранением интерфейса.
В более сложном случае, http-клиент может иметь какую-нибудь условную настройку аля retriesOnConnectionFailure=5, и только затем выплевывать TimeoutException, если эти пять попыток не увенчались успехом.
Ах да, важный момент, который я упустил в своих объяснениях, касательно обработки.
1. Эти ошибки нельзя ловить и обрабатывать в пределах нашей зоны ответственности (приложения, библиотеки, домена консистентности и т.д.). Они предназначены для внешнего мира, и их обработка лежит на получателе.
2. Эти ошибки должны быть словлены и обработаны в пределах нашей зоны ответственности, т.е. не покидая пределов кода, который мы написали.
3. Эти ошибки нельзя ловить ни для каких целей, кроме протоколирования, и запрещено игнорировать. В идеале это один глобальный обработчик, который сохраняет требуемую диагностическую информацию, после чего с чистой совестью падает.
Я бы предложил разделение ошибок (не исключений) на три класса в зависимости от того, что можно с этой ошибкой сделать, и кто в ней виноват. Например,
1. Нарушение пред-условий — внешний мир попросил фигню.
Мы — библиотека с функцией `createUser`, и ее вызвали с недопустимыми знаками в имени пользователя. Мы — Calculator-as-a-Service, и нас попросили поделить на ноль. Мы — драйвер TCP, и нам прислали сегмент с seqnum за пределами окна приема.
Виноваты ли мы в этой ошибке? Нет. Может ли повторение операции проверки данных, кода, который обнаружил ошибку, привести к другому результату? Бесполезно, чистые функции на то и чистые.
Значит, нам нужно на некорректный запрос вернуть корректный ответ — выбросить исключение на неправильное имя пользователя, вернуть HTTP 400 Bad Request в ответ на деление на ноль, и отправить RST в ответ на запоздалый сегмент.
2. Нарушение пост-условий — внешний мир ответил фигню.
HTTP-запрос отвалился по таймауту. При попытке открыть файл нам говорят EAGAIN или «device is busy». Мы скачиваем страницу, а там неразбираемая белиберда вместо содержимого.
Виноваты ли мы в этой ошибке? Нет. Может ли повторение операции (запрос внешнего ресурса) привести к другому результату? Да.
Значит, нам нужно выбросить исключение, которое имеет общепринятый способ обработки, или повторить операцию энное количество раз с учетом rate limit-ов, или перепоставить операцию в очередь, или вывести интерактивное окно с вопросом «ну что, еще разок?».
3. Нарушение инварианта — мы сотворили фигню.
Программист допустил ошибку при реализации, реальный функционал программы отличается от запланированного. В какой-то момент времени новое состояние программы нарушает какой-то из внутренних инвариантов и переходит в невозможное/запрещенное/недопустимое.
Мы виноваты? Да. Может ли повторение операции привести к другому исходу? Нет, так как все поведение нечистых функций, работающих с внешним миром, закрыто обработкой ошибок из пп.1 и 2.
Это тот случай, когда самое время с грохотом упасть, потому что мы достигли ситуации, которой не может быть. Это и есть фатальная ошибка, и обработать ее в реалтайме невозможно.
У меня есть сильное-пресильное ощущение, что вы смешали бизнес-логику и представление воедино, поэтому уже сейчас, в программе, далекой от завершения, у вас царит лапшекошмар.
Почему бы всякие утилиты вида "отцентровать строку" не вынести в функцию? Почему бы не повыносить в функции кучу других утилит? Почему не оформить Chart или Plot как класс с методами для рисования?
Да даже с точки зрения UX, зачем спрашивать pace, когда его можно высчитать, исходя из ширины окна терминала?
Как быть с тем, что если вы захотите перейти на другой бэкенд, например, на ncurses, то вам потребуется переписать абсолютно все с нуля, потому что этот код не переносим?
Если бы вас посетила светлая идея использовать символы псевдографики для увеличения разрешения графика, то ведь тоже придется все выбросить. Зачем?
Вы парсите '%10.6g', просто беря символы с 1 по 3 невключительно. Это так не работает! Я хочу поле шириной 100 знаков, и что теперь? Или 9 знаков? Зачем в принципе использовать устаревшие %s, когда есть {}?
У меня слишком много вопросов к тому, что в этой статье написано абсолютно не так, как следовало бы.
Первый процессор, получивший действительно массовое распространение – это 8086 от компании Intel, разработанный в 1978 году. Тактовая частота работы 8086 составляла всего 8 МГц. Спустя несколько лет появились первые процессоры внутри которых было 2, 4 и даже 8 ядер.
А можно для тех, кто все проспал — что в 1980 году были за многоядерные процессоры с 2, 4 и даже 8 ядрами?
Тогда почему же в C++ не превратился C#, в котором перегрузка операторов есть с незапамятных времен, а еще есть unsafe, PInvoke и другие страшные вещи? ;)
Зачем? Это же неэффективно. Просто проверяют, установлен ли лицензионный Касперский. Если нет — запрет въезда на три года, чтобы поумнели. А там люди сами разберутся.
Пока все в посте обсуждают политическую компоненту данного решения, мне стало интересно — а каким таким образом MTProto "«маскируется» то под https, то под данные антивируса"? Я невнимательно читал спецификации MTProto? Промежуточные пакеты (после установления сессии) чем-то статистически отличаются от шума?
Ну я вижу два варианта решения данной ситуации.
Первый — это вывалить наружу "кишки", т.е. просто не обрабатывать ошибки типа ETIMEDOUT или какого-нибудь
socket.gaierror
, а просто передать их наружу вызывающей стороне. Минус такого подхода — если мы реализуем что-то посложнее http-клиента (например, какой-нибудь ORM, который может работать сразу с кучей разных БД), мы утекаем наружу детали реализации, и делаем эти детали реализации неявной частью контракта. В будущем мы не сможем просто взять и прозрачно перейти с BSD sockets на какой-нибудь WinSock, т.к. код вызывающей стороны уже рассчитывает получать исключения вполне определенного образца. На мой взгляд, это проблема.Поэтому альтернативным решением может быть обработка того же таймаута внутри клиента. В самом простом случае "обработка" будет заключаться только в том, что мы перевыбросим исключение о таймауте, но уже обернув его в наш объект исключения (или вернув какой-нибудь наш собственный
retval_t
и т.д.). Это позволит авторам библиотеки менять реализацию с сохранением интерфейса.В более сложном случае, http-клиент может иметь какую-нибудь условную настройку аля
retriesOnConnectionFailure=5
, и только затем выплевыватьTimeoutException
, если эти пять попыток не увенчались успехом.1. Эти ошибки нельзя ловить и обрабатывать в пределах нашей зоны ответственности (приложения, библиотеки, домена консистентности и т.д.). Они предназначены для внешнего мира, и их обработка лежит на получателе.
2. Эти ошибки должны быть словлены и обработаны в пределах нашей зоны ответственности, т.е. не покидая пределов кода, который мы написали.
3. Эти ошибки нельзя ловить ни для каких целей, кроме протоколирования, и запрещено игнорировать. В идеале это один глобальный обработчик, который сохраняет требуемую диагностическую информацию, после чего с чистой совестью падает.
1. Нарушение пред-условий — внешний мир попросил фигню.
Мы — библиотека с функцией `createUser`, и ее вызвали с недопустимыми знаками в имени пользователя. Мы — Calculator-as-a-Service, и нас попросили поделить на ноль. Мы — драйвер TCP, и нам прислали сегмент с seqnum за пределами окна приема.
Виноваты ли мы в этой ошибке? Нет. Может ли повторение операции проверки данных, кода, который обнаружил ошибку, привести к другому результату? Бесполезно, чистые функции на то и чистые.
Значит, нам нужно на некорректный запрос вернуть корректный ответ — выбросить исключение на неправильное имя пользователя, вернуть HTTP 400 Bad Request в ответ на деление на ноль, и отправить RST в ответ на запоздалый сегмент.
2. Нарушение пост-условий — внешний мир ответил фигню.
HTTP-запрос отвалился по таймауту. При попытке открыть файл нам говорят EAGAIN или «device is busy». Мы скачиваем страницу, а там неразбираемая белиберда вместо содержимого.
Виноваты ли мы в этой ошибке? Нет. Может ли повторение операции (запрос внешнего ресурса) привести к другому результату? Да.
Значит, нам нужно выбросить исключение, которое имеет общепринятый способ обработки, или повторить операцию энное количество раз с учетом rate limit-ов, или перепоставить операцию в очередь, или вывести интерактивное окно с вопросом «ну что, еще разок?».
3. Нарушение инварианта — мы сотворили фигню.
Программист допустил ошибку при реализации, реальный функционал программы отличается от запланированного. В какой-то момент времени новое состояние программы нарушает какой-то из внутренних инвариантов и переходит в невозможное/запрещенное/недопустимое.
Мы виноваты? Да. Может ли повторение операции привести к другому исходу? Нет, так как все поведение нечистых функций, работающих с внешним миром, закрыто обработкой ошибок из пп.1 и 2.
Это тот случай, когда самое время с грохотом упасть, потому что мы достигли ситуации, которой не может быть. Это и есть фатальная ошибка, и обработать ее в реалтайме невозможно.
У меня есть сильное-пресильное ощущение, что вы смешали бизнес-логику и представление воедино, поэтому уже сейчас, в программе, далекой от завершения, у вас царит лапшекошмар.
Почему бы всякие утилиты вида "отцентровать строку" не вынести в функцию? Почему бы не повыносить в функции кучу других утилит? Почему не оформить Chart или Plot как класс с методами для рисования?
Да даже с точки зрения UX, зачем спрашивать pace, когда его можно высчитать, исходя из ширины окна терминала?
Как быть с тем, что если вы захотите перейти на другой бэкенд, например, на ncurses, то вам потребуется переписать абсолютно все с нуля, потому что этот код не переносим?
Если бы вас посетила светлая идея использовать символы псевдографики для увеличения разрешения графика, то ведь тоже придется все выбросить. Зачем?
Вы парсите '%10.6g', просто беря символы с 1 по 3 невключительно. Это так не работает! Я хочу поле шириной 100 знаков, и что теперь? Или 9 знаков? Зачем в принципе использовать устаревшие
%s
, когда есть{}
?У меня слишком много вопросов к тому, что в этой статье написано абсолютно не так, как следовало бы.
Как быть с линуксами и маками? Как быть с теми, кто хочет купить чистый компьютер без операционной системы?
Спасибо за бдительность, гражданин, администрации уже направили запрос на удаление
https://habr.com/post/*
.