2009-07-27 2 views
2

Основываясь на ответах на предыдущий question, вот конструкция простой системы отчетов об ошибках для моего встроенного приложения C. Я был бы признателен за некоторые отзывы.Является ли это хорошим решением для отчетов об ошибках для встроенного приложения C?

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

enum lib1ErrorCodes { 
    ERR_NO_ERROR, 
    ERR_NO_CONNECTION, 
    ... 
    ERR_MISC 
}; 

Это передается по цепи либо через глобальную переменную:

unsigned char lib1ErrNo; 

Следующий более высокий уровень содержит коды ошибок для библиотек, которые она использует:

enum lib2ErrorCodes { 
    ERR_NO_ERROR, 
    ERR_LIB1, 
    ERR_FILE_EXISTS, 
    ... 
    ERR_MISC 
} 

обнаружена ошибка Lib1 и попадает в переменной ошибки в этом уровне:

unsigned char lib2ErrNo = ERR_LIB1; 

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

if (lib3ErrNo == ERR_LIB2) 
    if (lib2ErrNo == ERR_LIB1) 
     printf("Error %d: %s", lib1ErrNo, lib1ErrDesc); 

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

Каков подходящий способ сделать это, если это не так?

Я хочу:

  • Unique распространения ошибки вплоть до верхнего уровня, так что я могу сообщить о ней.
  • Предпочтительно не проходить вокруг больших структур, так как это встроенное приложение.

ответ

3

Я думаю, что, имея такое же значение для разных ошибок - в вашем примере как ERR_NO_CONNECTION и ERR_LIB1 имеют значение 1 - это не разумно.

Я бы рекомендовал зарезервировать диапазоны значений для каждой из ваших библиотек и явно присвоить эти значения вашим кодам ошибок.

enum lib1ErrorCodes { 
    ERR_NO_ERROR = 0, 
    ERR_NO_CONNECTION = 1, 
    ... 
    ERR_MISC = ... 
}; 


enum lib2ErrorCodes { 
    ERR_NO_ERROR = 0, 
    ERR_LIB1 = 101, 
    ERR_FILE_EXISTS = 102, 
    ... 
    ERR_MISC = ... 
} 
+1

Я предполагаю, что в рамках этой системы мне нужно будет активно управлять, какие библиотеки получают, какие диапазоны ошибок, правильно? Что относительно сторонних библиотек, которыми я не контролирую? – jparker

+1

Вы догадываетесь правильно. Что касается сторонних библиотек, я полагаю, что каждая из них имеет свою собственную логику. – mouviciel

+0

Точка против этой стратегии заключается в том, что она создает скрытые зависимости между модулями. Я говорю, свести к минимуму сцепление. –

0

Я был бы более склонен хранить код ошибки вместе с битом флага, чтобы указать, из какой библиотеки он пришел.

2

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

typedef enum 
{ 
    SER_NO_ERR, 
    SER_TX_ERR, 
    SER_BAD_ARGS, 
    ... 
} SER_ERR; 

стек связи может иметь:

typedef enum 
{ 
    COMM_OK, 
    COMM_TIMEOUT, 
    COMM_TX_ERR, 
    ... 
} COMM_ERR; 

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

// lets assume you are using the global variables serErr and commErr 
// to store error codes for their respective libraries. 
serial_transmit(someDataPtr, someSize); 
switch(serErr) 
{ 
    case SER_NO_ERR: 
     commErr = COMM_OK; 
     break; 
    case SER_BAD_ARGS: 
    case SER_TX_ERR: 
     commErr = COMM_TX_ERR; 
     break; 
    ... 
} 

Я понимаю, что может привести к большому количеству кода, но мне нравится, что он поддерживает абстракцию между слоями. Кроме того, можно определить некоторые функции для обработки переводов, как:

void comm_handle_serial_error(SER_ERR err) 
{ 
    //copy in the switch statement from above 
} 

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

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

4

Во-первых, если у вас нет 8-битной архитектуры, я бы использовал большую переменную для хранения всех ошибок.

Во-вторых, я бы не сделал систему сложной. Я бы просто перечислил все ошибки и всякий раз, когда произошла ошибка, я бы назвал обработчик ошибок, который сообщает об этом. Часто, когда вы получаете одну ошибку, вы получаете много, и если у вас есть только один код ошибки для каждой библиотеки, вы рискуете потерять некоторые из них, возможно, один из них был самым важным (первым), если вы не очень бдительны в том, чтобы не допускать ничего иначе произойдет после возникновения ошибки. Это также может стать довольно сложным.

3

По моему опыту, чем больше информации вы можете сообщить об ошибке, тем лучше.

Один из способов решения этих проблем - создать центральный «диспетчер состояний», с которым могут взаимодействовать все пакеты, если они обнаружат проблему. На минимальном уровне диспетчер состояний может вызываться с перечислением, указывающим проблему, а также другое перечисление/целое число, чтобы помочь вам найти, где проблема. - В некоторых системах (как правило, 16-разрядных + системах с объемом памяти не менее 128 КБ) мне даже известно, что текстовые строки описывают проблему (проблемы).

Я также предполагаю, что вы можете установить интерфейс задней двери и/или фирменный интерфейс, который можно использовать для извлечения этих данных.

0

В зависимости от ограничений по программному пространству/ограничению скорости/скорости существуют рамки для C++, такие как обработка исключений в C. This is an excellent article, которые я использовал в прошлом, чтобы решить такую ​​проблему на очень низкой платформе, где распределение динамической памяти недоступен. Используемый надлежащим образом это может освободить вас от проблем, связанных с передачей кодов ошибок в дерево вызовов.

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

0

Используйте макросы __FILE__, __FUNCTION__ и __LINE__ для определения местоположения. Нет необходимости в уникальных кодах ошибок, так как модуль идентифицирован. Учитывайте следующее:

#define ERROR_PRINTF(code, fmt, ...) error_printf("\n%s::%s(%u) Error %u : " fmt "\n", __FILE__, __FUNCTION__, __LINE__, code, __VA_ARGS__) 

int error_printf(const char* fmt, ...) 
{ 
    va_list args ; 
    va_start (args, fmt) ; 
    vprintf (fmt, args) ; 
    va_end (args) ; 
} 

Тогда скажите, что в главном появится следующая строка.с, строка 20 в основной функции():

ERROR_PRINTF(ERR_NO_CONNECTION, "Connection failed on port %d", port) ; 

следующий текст появится:

main.c::main(20) : Error 1 : Connection failed on port 2 

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

Смежные вопросы