Инжинерность
Я сейчас случайно увидел, что делает powershell, если указать несуществующую команду.
Это выглядит неприятно. Но, кроме "неприятно", что ещё можно сказать? Оно не хорошо спланировано.
Один из принципов инженерности, это правильная обработка sad path (vs bad path).
Сама идея: в программе есть три пути: happy path (что сказали, то сделали, получили результат), sad path (поведение при невозможности получить результат) и bad path (то, что случается, если результат нельзя получить, но никто не предусмотрел что делать).
Десятилетиями операционные системы и языки программирования давали свою интерпретацию "что делать если всё плохо". Первые версии просто игнорировали "всё плохо" (вот этот мем немного утрирует, но справа показано полное отсутствие обработки ошибок). В реальности, полное отсутствие обработки ошибок, это UB (undefined behavior) и процессор продолжает исполнять что-то, что уже не оригинальная программа. На практике это всегда заканчивается зависанием.
Потом добавили рудиментарные проверки на чтение по некорректному указателю (сама концепция "некорректный указатель" - это уже конструкция поверх возможности читать всё и всегда), сообщение от операционной системы (segmentation fault), etc.
Сверху добавили трейсы и т.д.
Это работа по превращению bad path в sad path с точки зрения ОС и языка программирования (точнее, среды исполнения, которую предоставляет компилятор).
Однако, это не отменяет необходимости делать такую же работу со стороны программиста. Программа на питоне, которая выдаёт трейс "не могу открыть файл" (вместо сообщения об ошибке "не могу открыть файл") - это пример bad path (который sad path с точки зрения питона). Полагаться на обработчик ошибок уровнем ниже - это неинжинерно.
В тот момент, когда программа отдаёт ошибку "наверх" (трейс, exception), она отказывается от своего домена и проблема перефомулируется на языке уровнем ниже.
Не "input file not found" (программа знает, что это input file), а "file not found exception at line xxx". В этот момент язык программирования не знает, является ли ошибка ожидаемой или нет, ошибка программы это (например, опечатка в имени файла) или ошибка отсутствия файла как параметра.
Язык программирования не знает, bad path это для программы или sad path. Отсутствие обработки ошибки, стирает её смысл.
К чему я это всё? На самом деле я не про sad/bad path. Я про инжинерность. Обработка ошибок - это один из признаков инжинерности, когда программа сохраняет свой понятийный домен (язык, на котором описывается, что делает программа) во всех ситуациях.
Сохранение семантического домена - это сохранение инварианта. Если программа А работает, то она всегда взаимодействует в рамках этого домена.
Плохая программа нарушает инвариант, давая вариативность: "иногда я делаю что сказали, в своём определении 'сказали', а иногда нет, и тогда проблема описывается в других терминах, операционной системы/рантайма".
Чем большая вариативность программы в семантических доменах, тем труднее с ней работать, тем хуже она написана. Иногда эта вариантивность оправдана (например, в любом языке программирования де-факто, в какой-то момент заканчивается язык программирования и начинаются регэкспы - у этого есть причины и польза). Но если вариантивность появляется "потому что так меньше писать", это признак плохой программы, то есть неинжинерной программы.
Это выглядит неприятно. Но, кроме "неприятно", что ещё можно сказать? Оно не хорошо спланировано.
Один из принципов инженерности, это правильная обработка sad path (vs bad path).
Сама идея: в программе есть три пути: happy path (что сказали, то сделали, получили результат), sad path (поведение при невозможности получить результат) и bad path (то, что случается, если результат нельзя получить, но никто не предусмотрел что делать).
Десятилетиями операционные системы и языки программирования давали свою интерпретацию "что делать если всё плохо". Первые версии просто игнорировали "всё плохо" (вот этот мем немного утрирует, но справа показано полное отсутствие обработки ошибок). В реальности, полное отсутствие обработки ошибок, это UB (undefined behavior) и процессор продолжает исполнять что-то, что уже не оригинальная программа. На практике это всегда заканчивается зависанием.
Потом добавили рудиментарные проверки на чтение по некорректному указателю (сама концепция "некорректный указатель" - это уже конструкция поверх возможности читать всё и всегда), сообщение от операционной системы (segmentation fault), etc.
Сверху добавили трейсы и т.д.
Это работа по превращению bad path в sad path с точки зрения ОС и языка программирования (точнее, среды исполнения, которую предоставляет компилятор).
Однако, это не отменяет необходимости делать такую же работу со стороны программиста. Программа на питоне, которая выдаёт трейс "не могу открыть файл" (вместо сообщения об ошибке "не могу открыть файл") - это пример bad path (который sad path с точки зрения питона). Полагаться на обработчик ошибок уровнем ниже - это неинжинерно.
В тот момент, когда программа отдаёт ошибку "наверх" (трейс, exception), она отказывается от своего домена и проблема перефомулируется на языке уровнем ниже.
Не "input file not found" (программа знает, что это input file), а "file not found exception at line xxx". В этот момент язык программирования не знает, является ли ошибка ожидаемой или нет, ошибка программы это (например, опечатка в имени файла) или ошибка отсутствия файла как параметра.
Язык программирования не знает, bad path это для программы или sad path. Отсутствие обработки ошибки, стирает её смысл.
К чему я это всё? На самом деле я не про sad/bad path. Я про инжинерность. Обработка ошибок - это один из признаков инжинерности, когда программа сохраняет свой понятийный домен (язык, на котором описывается, что делает программа) во всех ситуациях.
Сохранение семантического домена - это сохранение инварианта. Если программа А работает, то она всегда взаимодействует в рамках этого домена.
Плохая программа нарушает инвариант, давая вариативность: "иногда я делаю что сказали, в своём определении 'сказали', а иногда нет, и тогда проблема описывается в других терминах, операционной системы/рантайма".
Чем большая вариативность программы в семантических доменах, тем труднее с ней работать, тем хуже она написана. Иногда эта вариантивность оправдана (например, в любом языке программирования де-факто, в какой-то момент заканчивается язык программирования и начинаются регэкспы - у этого есть причины и польза). Но если вариантивность появляется "потому что так меньше писать", это признак плохой программы, то есть неинжинерной программы.
no subject
И, собсно, не вижу причин делать дихотомию "программирование - регекспі". Регексп, вопреки бітующему мнению - єто НЕ паттерн :) Єто код, транслируемій в FSA, которій віполняет сравнение с неким паттерном. И в єтом коде есть макросі, бекреференсі, условніе операторі и прочее разное, что слабо укладівается в понятие паттерна. Регекспі - єто еще одна из разновидностей программизьма.
no subject
Бывают неоправданные (например, тот же powershell, который выкидывает пользователя в .net по любому чиху).
no subject
no subject
no subject
no subject
1. Сервер не использует примонтированный по сети sqlite, а использует базу данных с гарантиями по таймаутам и fallable операции.
2. Клиент, при общении с сервером, использует локальное кеширование (для local first работы)
3. Если сервер не выполняет операцию за ожидаемое время, клиенту не важно по какой причине сервер не выполнил операцию, это таймаут операции. Если операция в бэкграунде, то она отправляется на retry с exponential fallback, а серверу набирают баллы проблем (чтобы показать, что это проблема сервера).
4. Если это интерактивная операция (отправка письма), задача становится в очередь, и по таймауту клиенту дают опцию: попробовать снова, отложить операцию, сохранить черновик локально, отменить полностью.
5. Трекинг проблем с операциями - пометка на сервер, мол, сервер испытывает проблемы.
6. Recoverable локальный стейт, закрытие клиента в любой момент времени позволяет восстановить состояние клиента (включая полу-набранное письмо).
7. Для race conditions между recoverable state и состоянием сервера - at least once. Лучше дважды отправить письмо, чем потерять его.
no subject
Причем тут сервер, когда я спросил о почтовом клиенте?
Вот есть клиент, типа Thunderbird, который хранит скачанные письма и все прочие данные пользователя в локальной sqlite базе. Так сложилось, что в конкретной системе раздел с sqlite базой лежит на разделе, смонтированном по сети. Этот раздел отвалился. Что программа должна сказать пользователю? Конкретное сообщение. Причем все слова про раздел, базу, настройки - говорить нельзя, потому что это не входит в понятийный домен почтового клиента.
>Примерно так же, как и любая другая ситуация с нехваткой ресурса.
1) Пользователь почтовой программы не знает таких слов.
2) Здесь нет проблемы нехватки ресурса.
no subject
Проблема недоступности nfs (или что за шайтан технология со стороны сервера - хоть лямбды поверх S3) - это всё "недоступность почтового сервера".
Лучше, более точно:
1. "работает медленно" (высокое latency, но ниже таймаутов)
2. "Частично недоступен" (часть операций проходит, часть с таймаутами)
3. "Не доступен" (все операции с таймаутами)
Я не понимаю, почему автор почтового клиента должен как-то различать 99% потерю пакетов по сети от сломавшейся шары.
Если тебя интересует sad path, то он:
1. Не потерять локальные изменения
2. Дать visibility проблемы клиенту
3. Дать recovery path (когда возможно)
4. Не блокировать работу клиента из-за застрявшей операции.
no subject
Ты путаешь почтовый сервер и сервер, раздающий файловую систему. В данном примере это РАЗНЫЕ машины. Почтовый сервер - это, например, машина где-то в облаке гугла, клиент обращается к ней по сетевому протоколу IMAP. Сервер с файловой системой - это машина, например, в офисе организации, о сущестсвовании которой почтовый клиент вообще не знает - он обращается к локальной с его т.з. файловой системе (для простоты предположим, что по сети смонтирован /home). Почтовый клиент скачивает письма с почтового сервера и сохраняет в своей локальной базе, которая в данном случае лежит на смонтированном по сети разделе /home в директории, например, ~/.thunderbird. Что должен сказать пользователю клиент, если при попытке прочитать письмо из локальной базы он получает ошибку EHOSTDOWN ?
И, кстати, твои затруднения в понимании - это показатель того, что в общем случае желаемое тобой невозможно.
no subject
Вот как проблему решает well-engineered браузер.
http://bad.url/
(или ты ожидал, что я расскажу как сделать из sad path хороший такой happy path? Нет.)
no subject
В том, кто виноват и куда идти в попытке её пофиксить. Почтовый сервер в данном случае не виноват.
>Про нюансы я написал.
Не про те.
>Вот как проблему решает well-engineered браузер.
Современные браузеры ни в каком месте не well-engineered. Нельзя сделать что-то well-engineered, когда у тебя не только проект, но еще и стандарт делается по agile.
Впрочем, мой браузер (файрфокс), действительно, в данном случае делает разумную вещь... казалось бы. Он при этом выходит за пределы штатного домена смыслов пользователя - понятий сети и файрвола там нет, только страница и сайт.
no subject
Просто представь себе на секунду браузер, в котором только happy path а bad path обрабатывается рантаймом. Допустим, даже, не падением всего браузера, а трейсом в сендбоксе для вкладки.
Вот это будет пример плохого инжиниринга.
no subject
no subject
Отчёт про ошибку ECC (номер банка памяти, номер сервера, описание сервера, который это заметил (через netconsole, например) - 200кб.
Супервизор базы данных, который это нашёл - 300кб. (данные супервизора, аудит-трейл получения данных и т.д.)
Приложение - ещё 200кб (потому что трейс глубокий и есть контекст)
Это приложение вернуло ошибку фрод-системе (лог ещё 1Мб, потому что много обстоятельств).
Это приложение вернуло ошибку в код обработки транзации. (+500кб)
Это приложение вернуло ошибку банку-процессору (+2Мб, потому что чужая система и нам нужны полные данные)
Это приложение вернуло ошибку терминалу, к которому ты приложил карточку.
Терминал должен дать тебе квиток с ошибкой. Нам написно Rejected и ID транзакции.
Надо ли в терминале печатать эти 5 мегабайтов ошибки, из которой ты узнаешь, в какой планке памяти на каком сервере битая память.
Я утрирую, но идея именно та. Глубина трейса должна быть ограничена, иначе обработки ошибок не будет.
no subject
- простой rejected - подошедшему клиенту.
- "сервер вернул ошибку при обработке транзакции" + описание транзакции - оператору терминала
- детали ошибки - администратору терминала
- Детальные отчет об ошибке супервизора базы + железячной ошибке - администратору базы данных.
Можно обсуждать что и кому показывать, но потери информации быть не должно. В идеале. На практике, понятно, возможны варианты, когда приходится скрипя зубами что-то обрезать.
no subject
Представь себе трейс на обработку нехватки ресурсов (sad path), вызванный избыточным количеством трейсов.
В современном observability есть сэмплинг (в т.ч. ошибок) и backpressure.
no subject
no subject
Сколько такой сайт простоит и как легко его завалить? http://bad.url/get404 в цикле, и сайт сам себя ухайдокает.
А такое получить очень просто. Трейс надо написать. Куча интерполяции. Трейс надо отправить. Разумеется, по SSL. Минус энтропия, плюс установка соединения. Если там нет connection pool или socket reuse в систему логгинга, одно это сломает сервер, даже без доп. ухищрений.
Если же у тебя там observability в потолок, то 404 - это 300 байт, а трейс о нём - пара мегабайт. Вот у тебя самоDoS и образовался, с мультипликатором over 10k.
no subject
no subject
no subject
no subject
Примерно то же, что она говорит при ошибке нахождения сектора дискеты из-за частичного размагничивания сервометок. Critical error reading drive A: — Retry/Abort/Ignore/Fail?
no subject
И я не уверен в ignore как sound решении.
no subject
Я слышал про решения, когда поверх большого, но медленного HDD ставится маленький SSD-кэш. Возможно, аналогично можно ставить маленький локальный надёжный диск поверх большой ненадёжной сети.
no subject