Что вы считаете «наилучшей практикой», когда речь идет об обработке ошибок согласованным способом в C-библиотеке.
Есть два способа, о которых я думал:
Всегда возвращайте код ошибки. Типичная функция будет выглядеть так:
MYAPI_ERROR getObjectSize(MYAPIHandle h, int* returnedSize);
Всегда предоставлять подход указатель ошибки:
int getObjectSize(MYAPIHandle h, MYAPI_ERROR* returnedError);
При использовании первого подхода можно написать код, подобный этому, где проверка обработки ошибок помещается непосредственно в вызов функции:
int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
// Error handling
}
Который выглядит лучше, чем код обработки ошибок здесь.
MYAPIError error;
int size;
size = getObjectSize(h, &error);
if(error != MYAPI_SUCCESS) {
// Error handling
}
Однако я думаю, что использование возвращаемого значения для возврата данных делает код более читабельным. Очевидно, что что-то было записано в переменную размера во втором примере.
Есть ли у вас какие-либо идеи о том, почему я должен предпочесть какой-либо из этих подходов или, возможно, смешать их или использовать что-то еще? Я не фанат глобальных состояний ошибок, так как это делает многопоточное использование библиотеки более болезненным.
РЕДАКТИРОВАТЬ: C ++ конкретные идеи по этому вопросу также было бы интересно услышать, если они не связаны с исключениями, так как на данный момент это не вариант для меня ...
источник
Ответы:
Мне нравится ошибка как способ возврата значения. Если вы разрабатываете API и хотите использовать свою библиотеку настолько безболезненно, насколько это возможно, подумайте об этих дополнениях:
сохраните все возможные состояния ошибок в одном перечислении typedef и используйте его в своей библиотеке lib. Не просто возвращайте целые числа или, что еще хуже, смешивайте целые числа или различные перечисления с кодами возврата.
обеспечить функцию, которая преобразует ошибки во что-то удобочитаемое человеком. Может быть просто. Просто enum in, const char * out.
Я знаю, что эта идея делает многопоточное использование немного сложным, но было бы неплохо, если бы прикладной программист мог установить глобальный обратный вызов ошибки. Таким образом, они смогут установить точку останова в обратном вызове во время сеансов поиска ошибок.
Надеюсь, поможет.
источник
Я использовал оба подхода, и они оба отлично работали для меня. Какой бы я ни использовал, я всегда стараюсь применять этот принцип:
Если единственными возможными ошибками являются ошибки программиста, не возвращайте код ошибки, используйте утверждения внутри функции.
Утверждение, которое проверяет входные данные, четко сообщает, чего ожидает функция, в то время как слишком большая проверка ошибок может затенить логику программы. Решение, что делать для всех различных случаев ошибок, может действительно усложнить проект. Зачем выяснять, как functionX должен обрабатывать нулевой указатель, если вместо этого вы можете настаивать на том, чтобы программист никогда не передавал его?
источник
assert(X)
где X - любое допустимое утверждение C, которое вы хотите быть истинным. см. stackoverflow.com/q/1571340/10396 .assert(X!=NULL);
илиassert(Y<enumtype_MAX);
? Посмотрите этот ответ для программистов и вопрос, на который он ссылается, для более подробной информации о том, почему я считаю, что это правильный путь.Есть хороший набор слайдов из CERT CMU с рекомендациями о том, когда использовать каждый из распространенных методов обработки ошибок C (и C ++). Одним из лучших слайдов является это дерево решений:
Я бы лично изменил две вещи в этой блок-схеме.
Во-первых, я хотел бы уточнить, что иногда объекты должны использовать возвращаемые значения для обозначения ошибок. Если функция только извлекает данные из объекта, но не изменяет объект, тогда целостность самого объекта не подвергается риску, и более подходящим является указание ошибок с использованием возвращаемого значения.
Во-вторых, не всегда целесообразно использовать исключения в C ++. Исключения хороши тем, что они могут уменьшить объем исходного кода, предназначенного для обработки ошибок, они в основном не влияют на сигнатуры функций, и они очень гибки в том, какие данные они могут передавать в стек вызовов. С другой стороны, исключения могут быть неправильным выбором по нескольким причинам:
Исключения в C ++ имеют особую семантику. Если вам не нужна эта семантика, то исключения C ++ - плохой выбор. Исключение должно быть обработано немедленно после того, как оно было сгенерировано, и дизайн благоприятствует случаю, когда из-за ошибки потребуется развернуть стэк вызова на несколько уровней.
Функции C ++, которые генерируют исключения, не могут быть позже обернуты, чтобы не генерировать исключения, по крайней мере, не оплачивая полную стоимость исключений в любом случае. Функции, которые возвращают коды ошибок, могут быть обернуты, чтобы генерировать исключения C ++, делая их более гибкими. С ++
new
получает это право, предоставляя вариант без броска.Исключения в C ++ относительно дороги, но этот недостаток в основном преувеличен для программ, которые разумно используют исключения. Программа просто не должна бросать исключения в путь кода, где важна производительность. Неважно, насколько быстро ваша программа может сообщить об ошибке и выйти.
Иногда исключения C ++ недоступны. Либо они буквально недоступны в реализации C ++, либо в руководящих принципах кода они запрещены.
Поскольку исходный вопрос был о многопоточном контексте, я думаю , что метод локального индикатора ошибки (что описано в SirDarius «s ответа ) был недооцененным в оригинальных ответах. Это потокобезопасно, не заставляет вызывающую ошибку немедленно обрабатывать ошибку и может связывать произвольные данные, описывающие ошибку. Недостатком является то, что он должен удерживаться объектом (или, я полагаю, каким-то образом связанным снаружи), и его легче игнорировать, чем код возврата.
источник
Я использую первый подход всякий раз, когда я создаю библиотеку. Есть несколько преимуществ использования перечисления typedef в качестве кода возврата.
Если функция возвращает более сложный вывод, такой как массив и его длина, вам не нужно создавать произвольные структуры для возврата.
Это позволяет для простой, стандартизированной обработки ошибок.
Это позволяет для простой обработки ошибок в библиотечной функции.
Использование перечисления typedef также позволяет отображать имя перечисления в отладчике. Это упрощает отладку без необходимости постоянно обращаться к заголовочному файлу. Полезно также иметь функцию для перевода этого перечисления в строку.
Самый важный вопрос, независимо от используемого подхода, должен быть последовательным. Это относится к именам функций и аргументов, упорядочению аргументов и обработке ошибок.
источник
Используйте setjmp .
http://en.wikipedia.org/wiki/Setjmp.h
http://aszt.inf.elte.hu/~gsd/halado_cpp/ch02s03.html
http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
источник
ETRY
Код был пересмотрен , так как этот ответ был написан.setjmp
это дорого, даже если не выдается никакой ошибки, это потребует немало времени процессора и места в стеке. При использовании gcc для Windows вы можете выбирать между различными методами обработки исключений для C ++, один из которых основан наsetjmp
и делает ваш код на 30% медленнее на практике.Я лично предпочитаю первый подход (возвращая индикатор ошибки).
При необходимости возвращаемый результат должен просто указывать на то, что произошла ошибка, а другая функция используется для определения точной ошибки.
В вашем примере getSize () я бы учел, что размеры всегда должны быть нулевыми или положительными, поэтому возвращение отрицательного результата может указывать на ошибку, как это делают системные вызовы UNIX.
Я не могу думать ни о какой библиотеке, которую я использовал, которая подходит для последнего подхода с объектом ошибки, переданным как указатель.
stdio
и т. д. все идут с возвращаемым значением.источник
Когда я пишу программы, во время инициализации я обычно раскручиваю поток для обработки ошибок и инициализирую специальную структуру для ошибок, включая блокировку. Затем, когда я обнаруживаю ошибку, через возвращаемые значения я вхожу в информацию из исключения в структуру и отправляю SIGIO в поток обработки исключений, а затем проверяю, не могу ли я продолжить выполнение. Если я не могу, я отправляю SIGURG в поток исключений, который корректно останавливает программу.
источник
Возвращение кода ошибки - это обычный подход к обработке ошибок в C.
Но недавно мы экспериментировали с подходом указателя исходящей ошибки.
У этого есть некоторые преимущества перед подходом возвращаемого значения:
Вы можете использовать возвращаемое значение для более значимых целей.
Необходимость записать этот параметр ошибки напоминает вам о необходимости обработки ошибки или ее распространения. (Вы никогда не забудете проверить возвращаемое значение
fclose
, не так ли?)Если вы используете указатель ошибки, вы можете передать его при вызове функций. Если какая-либо из функций установит его, значение не будет потеряно.
Установив точку останова данных в переменной error, вы можете узнать, где произошла ошибка. Установив условную точку останова, вы также можете обнаружить определенные ошибки.
Это упрощает автоматизацию проверки, обрабатываете ли вы все ошибки. Соглашение о коде может заставить вас вызвать указатель ошибки,
err
и это должен быть последний аргумент. Таким образом, скрипт может соответствовать строке, аerr);
затем проверить, следует ли за нейif (*err
. На самом деле на практике мы создали макрос, который называетсяCER
(проверка ошибки возврата) иCEG
(проверка ошибки возврата ). Поэтому вам не нужно вводить его всегда, когда мы просто хотим вернуться к ошибке, и это может уменьшить визуальный беспорядок.Однако не все функции в нашем коде имеют этот исходящий параметр. Этот исходящий параметр используется для случаев, когда вы обычно генерируете исключение.
источник
Я много занимался программированием на С в прошлом. И я действительно оценил возвращаемое значение кода ошибки. Но есть несколько возможных подводных камней:
источник
Подход UNIX больше всего похож на ваше второе предложение. Вернуть либо результат, либо одно значение «все пошло не так». Например, open вернет дескриптор файла в случае успеха или -1 при ошибке. В случае сбоя также устанавливается
errno
внешнее глобальное целое число, указывающее, какой сбой произошел.Для чего это стоит, Какао также принимает подобный подход. Ряд методов возвращает BOOL и принимает
NSError **
параметр, чтобы при ошибке они устанавливали ошибку и возвращали NO. Тогда обработка ошибок выглядит так:который находится где-то между вашими двумя вариантами :-).
источник
Недавно я тоже размышлял над этой проблемой и написал несколько макросов для C, которые имитируют семантику try-catch-finally с использованием чисто локальных возвращаемых значений. Надеюсь, что вы найдете ее полезной.
источник
Вот подход, который я считаю интересным, но требует некоторой дисциплины.
Это предполагает, что переменная типа дескриптора является экземпляром, в котором работают все функции API.
Идея состоит в том, что структура за дескриптором хранит предыдущую ошибку как структуру с необходимыми данными (код, сообщение ...), а пользователю предоставляется функция, которая возвращает указатель на этот объект ошибки. Каждая операция будет обновлять указанный объект, чтобы пользователь мог проверить его состояние, даже не вызывая функции. В отличие от шаблона errno, код ошибки не является глобальным, что делает подход ориентированным на многопотоковое исполнение при условии правильного использования каждого дескриптора.
Пример:
источник
Первый подход лучше ИМХО
источник
Я определенно предпочитаю первое решение:
Я бы немного изменил его, чтобы:
Кроме того, я никогда не буду смешивать допустимое возвращаемое значение с ошибкой, даже если в настоящее время объем функций, позволяющих вам сделать это, вы никогда не знаете, каким образом реализация функции пойдет в будущем.
И если мы уже говорим об обработке ошибок, я бы предложил в
goto Error;
качестве кода обработки ошибок, если только какая-тоundo
функция не может быть вызвана для правильной обработки ошибок.источник
Что вы могли бы сделать вместо того, чтобы возвращать свою ошибку и, таким образом, запретить вам возвращать данные с помощью вашей функции, это использовать оболочку для вашего возвращаемого типа:
Затем в вызываемой функции:
Обратите внимание, что при использовании следующего метода оболочка будет иметь размер MyType плюс один байт (на большинстве компиляторов), что весьма выгодно; и вам не придется помещать другой аргумент в стек при вызове вашей функции (
returnedSize
илиreturnedError
в обоих представленных вами методах).источник
Вот простая программа, чтобы продемонстрировать первые 2 пули ответа Нильса Пипенбринка здесь .
Его первые 2 пули:
Предположим, вы написали модуль с именем
mymodule
. Во-первых, в mymodule.h вы определяете свои коды ошибок на основе enum и пишете несколько строк ошибок, соответствующих этим кодам. Здесь я использую массив C strings (char *
), который хорошо работает только в том случае, если ваш первый код ошибки на основе enum имеет значение 0, и вы не манипулируете числами после этого.Если вы используете номера кодов ошибок с пробелами или другими начальными значениями, вам просто придется перейти от использования сопоставленного массива C-строк (как я делаю ниже) к использованию функции, которая использует оператор switch или операторы if / else if отобразить коды ошибок enum в печатные строки C (которые я не демонстрирую). Выбор за вами.mymodule.h
mymodule.c содержит мою функцию отображения для сопоставления кодов ошибок enum с печатными строками C:
mymodule.c
main.c содержит тестовую программу для демонстрации вызова некоторых функций и вывода из них некоторых кодов ошибок:
main.c
Вывод:
Ссылки:
Вы можете запустить этот код самостоятельно здесь: https://onlinegdb.com/ByEbKLupS .
источник
В дополнение к тому, что было сказано, перед возвратом кода ошибки выведите подтверждение или аналогичное диагностическое сообщение при возврате ошибки, поскольку это значительно упростит отслеживание. Способ, которым я делаю это, состоит в том, чтобы иметь настроенное утверждение, которое все еще компилируется при выпуске, но запускается только тогда, когда программное обеспечение находится в режиме диагностики, с возможностью молча сообщать в файл журнала или приостанавливать работу на экране.
Я лично возвращаю коды ошибок как отрицательные целые числа с no_error как ноль, но это оставляет вас с возможной следующей ошибкой
Альтернативный вариант - иметь ошибку, всегда возвращаемую как ноль, и использовать функцию LastError (), чтобы предоставить подробную информацию о фактической ошибке.
источник
Я несколько раз сталкивался с вопросами и ответами и хотел дать более полный ответ. Я думаю, что лучший способ думать об этом - как вернуть ошибки вызывающей стороне и что вы вернете.
Как
Есть 3 способа вернуть информацию из функции:
Возвращаемое значение
Вы можете вернуть значение только одного объекта, однако это может быть произвольный комплекс. Вот пример функции возврата ошибки:
Одним из преимуществ возвращаемых значений является то, что они позволяют объединять вызовы для менее навязчивой обработки ошибок:
Это не только о удобочитаемости, но также может позволить обрабатывать массив таких указателей на функции унифицированным способом.
Аргумент (ы)
Вы можете возвращать больше с помощью более чем одного объекта с помощью аргументов, но передовая практика предлагает сохранить общее количество аргументов низким (скажем, <= 4):
Из группы
С помощью setjmp () вы определяете место и то, как вы хотите обработать значение int, и вы передаете управление этому месту с помощью longjmp (). См. Практическое использование setjmp и longjmp в C .
какой
Показатель
Индикатор ошибки только говорит о том, что есть проблема, но ничего о природе указанной проблемы:
Однако это наименее эффективный способ для функции сообщать о состоянии ошибки, однако он идеально подходит, если вызывающий в любом случае не может ответить на ошибку постепенно.
Код
Код ошибки сообщает вызывающей стороне о природе проблемы, и может позволить подходящий ответ (из вышеупомянутого). Это может быть возвращаемое значение или как пример look_ma () над аргументом ошибки.
объект
При наличии объекта ошибки вызывающий может быть проинформирован о произвольных сложных проблемах. Например, код ошибки и подходящее удобочитаемое сообщение. Он также может сообщить вызывающей стороне, что несколько вещей пошли не так, или ошибка на элемент при обработке коллекции:
Вместо того, чтобы предварительно выделять массив ошибок, вы также можете (пере) распределять его динамически по мере необходимости.
Перезвони
Обратный вызов является наиболее эффективным способом обработки ошибок, поскольку вы можете сообщить функции, какое поведение вы хотели бы видеть, когда что-то идет не так. Аргумент обратного вызова может быть добавлен к каждой функции, или если настройка требуется только для экземпляра структуры, подобной этой:
Одним интересным преимуществом обратного вызова является то, что он может быть вызван несколько раз или вообще не вызываться при отсутствии ошибок, при которых нет никаких издержек на счастливом пути.
Существует, однако, инверсия контроля. Код вызова не знает, был ли вызван обратный вызов. Таким образом, может иметь смысл также использовать индикатор.
источник
РЕДАКТИРОВАТЬ: Если вам нужен доступ только к последней ошибке, и вы не работаете в многопоточной среде.
Вы можете возвратить только true / false (или что-то вроде #define, если вы работаете в C и не поддерживает переменные bool), и имеете глобальный буфер Error, который будет содержать последнюю ошибку:
источник
Второй подход позволяет компилятору создавать более оптимизированный код, потому что, когда адрес переменной передается функции, компилятор не может сохранять свое значение в регистре (ах) при последующих вызовах других функций. Код завершения обычно используется только один раз, сразу после вызова, тогда как «реальные» данные, возвращаемые из вызова, могут использоваться чаще
источник
Я предпочитаю обработку ошибок в C, используя следующую технику:
Источник: http://blog.staila.com/?p=114
источник
goto
's вместо повторногоif
' s. Рекомендации: раз , два .В дополнение к другим отличным ответам, я предлагаю вам попытаться разделить флаг ошибки и код ошибки, чтобы сохранить одну строку при каждом вызове, а именно:
Когда у вас много ошибок, это маленькое упрощение действительно помогает.
источник