Опять про ошибки

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

Итак, возвращаемся к ошибкам и их обработке.

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

Начнем с самого начала. Алгоритм считается правильным, если на всем множестве допустимых данных он выдает правильный результат. Вопросам доказательства правильности алгоритма в классической литературе уделяется весьма значительное внимание. Порой, доказать правильность алгоритма сложнее, чем его разработать и отладить. Но, несмотря на сложность, данный вопрос один из ключевых в классическом IT-образовании. И, к сожалению, по моим наблюдениям, ему уделяется все меньше внимания при подготовке будущих программистов.

Почему я выделил слово «допустимых»? Да собственно, потому, что алгоритм — это своего рода функция, которая отображает множество X, называемое входными данными на множество Y, называемое результатом. Соответственно, скучные понятия, известные еще со школьной математики, такие как ОО и ОДЗ, справедливы и для алгоритмов. Думаю, немало баллов ЕГЭ потеряно из-за действий, расширяющих или сужающих ОДЗ… Но вот почему-то, в отношении алгоритмов, никто особо на этот счет не задумывается, и программы пишутся вообще без учета этих понятий.

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

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

Но, даже идеально вылизанный алгоритм не застрахован от ошибки. Дальнейшие ограничения накладывает архитектура. Аппаратная и программная. К примеру, молодой программист на Python, прогуливавший уроки информатики в школе (при условии еще, что их вел адекватный учитель, что возможно не всегда), может с удивлением узнать, что нельзя просто так взять и возвести 10 в 10-ю степень, используя язык Си. Или, вдруг, обнаружит, что нельзя посчитать биномиальный коэффициент 10 по 30 по формуле 30!/((30-10)!*10!) в лоб. Ну просто в силу того 30! не уместится ни в один из базовых типов данных C/C++, включая long long, хотя само по себе значение коэффициента вполне спокойно уместится в 32-битный int. И это, достаточно вульгарный, но показательный пример, когда архитектура влияет на работу алгоритма, и для простых, на первый взгляд, расчетов, приходится ухищряться и вспоминать математику, теорию чисел и множеств, и многое такое, что когда-то казалось ненужным, отвлекающим от «настоящего» программирования….

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

И я еще не упомянул пользователей…

В общем, ошибки всегда будут преследовать любой проект. Но значит ли, что надо опустить руки и смириться? Ни в коем случае. И тут, для программиста, есть множество средств, облегчающих поиск, идентификацию и локализацию ошибок.

И тут, я возвращаюсь к, недавно приобретенному, модулю на чипе ESP32-S3 и попыткам поднять на нем WiFi. В прошлой заметке я упомянул про API-шную функцию esp_netif_create_default_wifi_sta() и ее способность прибить приложение в случае какой-либо ошибки. Я не поленился и залез в описание данной функции:

esp_netif_t* esp_netif_create_default_wifi_sta(void)
{
    esp_netif_config_t cfg = ESP_NETIF_DEFAULT_WIFI_STA();
    esp_netif_t *netif = esp_netif_new(&cfg);
    assert(netif);
    ESP_ERROR_CHECK(esp_netif_attach_wifi_station(netif));
    ESP_ERROR_CHECK(esp_wifi_set_default_wifi_sta_handlers());
    return netif;
}

Вы серьезно? В функции API на 5 строк 3 ассерта? В смысле, что 1 assert и 2 подобных ему макроса? Я все понимаю, конечно, но на мой взгляд, это перебор. Еще ладно, нативный assert используется правильно. Ну в том плане, что он обрабатывает значение, возвращенной функцией, а не вызывает саму функцию. Но использование макроса ESP_ERROR_CHECK() за гранью добра и зла. Напомню, что ESP_ERROR_CHECK() — это assert на стероидах, то есть он обрабатывает int вместо bool и выдает расширенное описание ошибки. В остальном, его поведение полностью аналогично assert.

Да, assert — классная штука для обнаружения и локализации ошибок и здорово помогает программисту. Но! Его задача — обнаружение и локализация ошибок! В коде, который выпускается в продакшн их быть не должно!!! Тем более, в API-шном вызове! Тем более, при работе с периферией, которая сама по себе может работать нестабильно. Это априоре ставит крест на попытках написать отказоустойчивое приложение, работающее 24/7/366. Ну просто представьте ситуацию. Автопилот на автомобиле. Авто едет со скоростью 90 км в час. Впереди едет трактор с дисковой бороной в транспортном положении, которая выползает на половину соседних полос с каждой стороны. Нужно совершить обгон. Но предварительно, необходимо включить поворотник. И тут диагностика выдает, что одна из лампочек перегорела. Поворотник включить нельзя, значит нельзя совершать маневр, ведь правильно? Следовательно, это исключительная ситуация и нужно вызвать assert и убить приложение, отвечающее за управлением автомобилем. Ну а водителю, если так хочется жить, нужно успеть это сообразить и перезагрузить ЭБУ, чтобы вернуть контроль над машиной. Ведь классный сценарий, правда? Кажется, я начинаю понимать, что вызвало целую волну сообщений о том, что автомобили Tesla самопроизвольно выезжали на встречку или творили подобную дичь. Видимо, в их блоках управления было ПО, написанное подобно API ESP32…

Далее, я выскажу сугубо свое личное мнение.

Да, обнаружение ошибок важно. Да, локализация и диагностика ошибок нужна. Но! Обработка ошибок должна быть целиком и полностью на плечах программиста, так как он непосредственно отвечает за работу приложения. И только ему одному знать, что является критически важным, а что нет. Тем более, это касается функций API, пройти мимо которых нет никакой возможности.

Протоколирование ошибок обязано быть в функциях API. Причем, настраиваемое. От полного молчания, до пошагового сообщения итогов того или иного шага. Но гробить приложение по abort() по любому непонятному чиху функция API не имеет права!!!

В целом, на скору руку я бы переписал код многострадальной функции следующим образом:

esp_netif_t* esp_netif_create_default_wifi_sta(void)
{
    esp_netif_config_t cfg = ESP_NETIF_DEFAULT_WIFI_STA();
    esp_netif_t *netif = esp_netif_new(&cfg);
    if(!netif){
    	ESP_ERROR_CHECK_WITHOUT_ABORT(SMTH_ERR_CODE);
    	return netif;
    } else {
        int ret_code=esp_netif_attach_wifi_station(netif);
        if(ret_code!=ESP_OK){
        	ESP_ERROR_CHECK_WITHOUT_ABORT(ret_code);
        	return NULL;
        } else {
            	int ret_code=esp_wifi_set_default_wifi_sta_handlers();
            	if(ret_code!=ESP_OK){
            		ESP_ERROR_CHECK_WITHOUT_ABORT(ret_code);
            		return NULL;
            	}
        }
    }
    return netif;
}

Да, есть над чем подумать и что изменить, но в целом, сценарий именно такой. Да, выглядит громоздко. Но я решаю, что делать, если не удалось поднять WiFi. И для меня это совсем не повод, чтобы убивать приложение. Есть еще Bluetooth, IrDA и прочие способы достучаться до контроллера. Но это уже решать не авторам API.

P.S. Думаю, надо создать отдельную рубрику для ESP32.

PP.SS. Все, что я пишу — я пишу, в основном, для себя. Чтобы не забыть. Или, чтобы лучше осмыслить. Если кому-то это полезно, буду рад. Нет, так а я, собственно, ничего и не обещал никому…

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *