Это довольно старый вопрос, но я думаю, что это довольно важный вопрос, поскольку он касается некоторых фундаментальных вопросов, касающихся того, как лучше всего использовать MATLAB Coder и спроектировать свой код.
Хорошая система обработки ошибок очень важна для любой кодовой базы среднего размера, поэтому важно, чтобы она была структурирована. В коде, предназначенном для MATLAB Coder, вы не можете использовать какие-либо обычные функции исключения (try, catch, error и т. д.), и даже «утверждение» приобретает в Coder особое значение. Это означает, что вы сами должны создать хорошую систему обработки ошибок. Это печально, но не непреодолимо.
Вы можете просто создать перечисляемый тип с разными кодами ошибок и вернуть соответствующее перечисление. Это очень стандартный способ работы с C, и он явно будет очень чисто переведен из MATLAB в C. У него есть несколько минусов:
- Вы должны поддерживать класс перечисления для добавления каждого состояния ошибки.
- Это не облегчает трассировку стека напрямую.
- Вполне вероятно, что одна и та же ошибка будет повторно использоваться в разных местах, что не позволяет точно отследить местонахождение ошибки.
- Он не предоставляет удобочитаемых сообщений об ошибках.
- Он не позволяет возвращать данные времени выполнения, например значения переменных, которые использовались в функции нижнего уровня, что могло вызвать ошибку.
Это все, что предоставляет встроенная система MATLAB, поэтому было бы неплохо иметь что-то из этого.
В моей кодовой базе я разработал систему ошибок, которая сослужила мне хорошую службу. Он использует некоторые мощные инструменты в Coder, чтобы создать чистую, простую в использовании систему ошибок, которая немного похожа на собственную систему ошибок MATLAB. Это также обеспечивает хорошую связь с обработкой ошибок в программе, которая в конечном итоге вызывает библиотеку, созданную Coder.
Первая функция — контейнер ошибок. Это функция, в которой хранятся постоянные переменные, в которых хранятся ошибки времени выполнения.
function [ idnum, errorid, errorstring, inneridnum ] = errorcontainer(functionname, varargin ) %#codegen
%ERRORCONTAINER The container function for error processing
% This function contains the error ID table, and can assign new error
% codes or look up error codes.
%
% Examples:
% To register a new error and retrieve a unique error ID:
% idnum = errorcontainter( 'register', ID, MESSAGE )
% where ID and MESSAGE are strings. ID is a short machine readable
% string, and MESSAGE is human-readable.
%
% Optionally, an INNERIDNUM can be provided to indicate an error code for
% a inner (lower-level) error related to this. This will allow
% chaining of error messages:
%
% idnum = errorcontainter( 'register', ID, MESSAGE, inneridnum )
%
% ID and MESSAGE can have maximum lengths of 2048 characters. Anything
% longer will be truncated.
%
% e.g. idnum = errorcontainer('register', ...
% 'FOO_INVALIDTYPE', ...
% 'First input must be an int.');
% idnum will be a negative int32 returned.
%
% If the ID matches an existing ID, the message will be overwritten
% and the same idnum will be returned (the database does not grow).
%
% To lookup an existing error code:
% [idnum, errorid, errorstring] = errorcontainer('lookup', idnum);
%
% See also: errorcode2string, registererror
persistent IDLIST;
persistent ERRORSTRINGLIST;
persistent INNERID;
width = 2048;
if isempty(IDLIST)
IDLIST = char(zeros(1,width));
tempstr = 'ERRORCONTAINER_UNKNOWNERRORID';
IDLIST(1,1:numel(tempstr)) = tempstr;
end
if isempty(ERRORSTRINGLIST)
ERRORSTRINGLIST = char(zeros(1,width));
tempstr = 'Unknown Error';
ERRORSTRINGLIST(1,1:numel(tempstr)) = tempstr;
end
if isempty(INNERID)
INNERID = zeros(1,1,'int32');
end
coder.varsize('IDLIST', 'ERRORSTRINGLIST', 'INNERID', [], [1,1]);
coder.varsize('errorstring', 'errorid');
switch lower(functionname)
case 'register'
% First see if the listed ID matches any in the database.
errorid = varargin{1};
if numel(errorid) > width
errorid = errorid(1:width);
end
if (nargin == 4)
inneridnum = int32(varargin{3});
else
inneridnum = int32(0);
end
errorstring = varargin{2};
if numel(errorstring) > width
errorstring = errorstring(1:width);
end
matchindex = 0;
for i = 1:size(IDLIST,1)
if ( strcmpi(errorid, deblank(IDLIST(i,:))) && (inneridnum == INNERID(i) ) )
matchindex = i;
end
end
if (matchindex > 0)
idnum = int32(-matchindex);
else
idnum = int32(-(size(IDLIST,1)+1));
tempstr = char(zeros(1,width));
tempstr(1:numel(errorid)) = errorid;
IDLIST = [IDLIST ; tempstr]; % In Coder, cannot grow with indexing. Have to concatinte and reassign.
tempstr = char(zeros(1,width));
tempstr(1:numel(errorstring)) = errorstring;
ERRORSTRINGLIST = [ERRORSTRINGLIST ; tempstr];
INNERID = [INNERID; inneridnum];
end
case 'lookup'
idnum = varargin{1};
tidnum = idnum;
if ((-tidnum > size(IDLIST,1)) || (-tidnum <= 0 ))
tidnum = int32(-1);
end
errorid = deblank(IDLIST(-tidnum,:));
errorstring = deblank(ERRORSTRINGLIST(-tidnum,:));
inneridnum = INNERID(-tidnum);
otherwise
idnum = int32(-1);
errorid = deblank(IDLIST(-idnum,:));
errorstring = deblank(ERRORSTRINGLIST(-idnum,:));
inneridnum = int32(0);
end
end
Сердцем этой функции является большой оператор switch, который позволяет выбрать выполняемую задачу. Функция контейнера ошибок предназначена для использования в качестве внутреннего хранилища и служебной процедуры для системы обработки ошибок.
Основная функция, используемая для «генерирования» ошибок, — это функция registererror
, показанная здесь:
function idnum = registererror(messageid, errorstring, varargin) %#codegen
%REGISTERERROR Registers a message id and error string with error system
% Given a message ID string and an error code string (human-readable),
% will register the strings with the error subsystem and generate a
% negative ID number to return.
%
% Example:
% idnum = registererror('FOO_INVALIDINPUT', 'Invalid input to function foo');
%
% Optionally, an inner id number can be handed to the registration to
% assocaite this error with a lower-level error previously registered.
% idnuminner = registererror('BAR_INTERNALERROR', 'Internal error in bar');
% idnum = registererror('FOO_INVALIDINPUT', 'Invalid input to function foo', idnuminner);
%
% See Also: errorcode2string, errorcontainer
coder.inline('never')
switch nargin
case 2
idnum = errorcontainer('register', messageid, errorstring);
case 3
idnum = errorcontainer('register', messageid, errorstring, varargin{1});
end
end
Функция registererror
принимает строку тега ошибки (обычно это имя функции, в которой произошла ошибка, за которым следует двоеточие, за которым следует некоторый идентификатор того, что произошло) и удобочитаемое сообщение об ошибке. Он регистрирует ошибку в глобальном хранилище и возвращает идентификатор ошибки в виде int32. Правило для моего кода состоит в том, что значение int32(0) означает отсутствие ошибки, а отрицательное значение является одним из этих зарегистрированных идентификаторов ошибки.
Значения, возвращаемые registererror, не являются уникальными для каждого состояния ошибки. Они являются дескрипторами ссылок в контейнер ошибок и могут использоваться для извлечения строки тега ошибки и удобочитаемых строк.
В моем коде использование registererror обычно происходит примерно так:
...
output = someFunctionThatShouldReturnZeroIfSuccessful(filename);
if (output ~= 0)
result = registerror('MYFUNC:THESOMEFUNCTIONFAILED', ['The function that should return zero returned something else for filename: ', filename]);
return;
end
...
В удобочитаемой строке я могу добавить строковые данные из фактического выполнения. В этом примере я добавил имя файла, который не выполнил мою теоретическую функцию. Я делаю возвращаемые значения всех своих функций int32, и все они могут исходить от вызова registererror.
Существует дополнительный необязательный вызов registererror. Одна вещь, которую удобно сделать, это создать стек ошибок. Иногда проблема вызывалась ошибкой в более глубокой функции, и нам нужна полная трассировка стека. Это легко сделать, так как внутренние идентификаторы ошибок могут быть дополнительно переданы в registererror. Давайте посмотрим на пример этого:
...
result = someFunctionThatShouldReturnZeroIfSuccessful(filename);
if (result ~= 0)
result = registerror('MYFUNC:THESOMEFUNCTIONFAILED', ['The function that should return zero returned something else for filename: ', filename], result);
return;
end
...
В данном случае функция someFunction... вернула значение, которое было сгенерировано вызовом registererror, и это результирующее значение было добавлено в качестве третьего аргумента к вызову registererror здесь, который возвращает другое значение ошибки. Что мы можем сделать с этим значением?
Что ж, нам нужна третья функция, которую часто можно вызвать из кода, использующего нашу библиотеку. Эта функция называется errorcode2string
, и, как следует из названия, она может принимать один из наших кодов ошибок и возвращать две строки, а также любой внутренний код ошибки, связанный с ошибкой. Вот errorcode2string:
function [errorid, errorstring, innercode] = errorcode2string(errorcode) %#codegen
%ERRORCODE2STRING Return strings given an error code
% Given an error code returned by any of the library functions, will
% return a string with human-readable information about the error. The
% error codes are, in some cases, dynamically generated, so the codes
% themselves should not be used for programmatic flow control except that
% a negative value in a return always indicates an error occurred.
%
% Example:
% [errorid, errorstring, innercode] = errorcode2string(errorcode);
%
% - errorcode is an int32 value.
%
% - errorid is a string that is machine-readable and can be used to trap
% specific error codes.
%
% - errorstring is the returned 1 by N string of the human-readable error
% information.
%
% - innercode is the int32 error code of an inner error message if any.
% It is a negative value of an error code if present, 0 if this is the
% innermost error.
%
% See Also: registererror
[~, errorid, errorstring, innercode] = errorcontainer('lookup', errorcode);
end
Как вы можете видеть, эта функция на самом деле является просто оболочкой для контейнера ошибок, но это неплохо сделать, чтобы сохранить порядок для любого, кто вызывает библиотеку.
Пример:
Итак, давайте запустим быстрый пример. Представьте, что внутренняя функция не удалась, и функция registererror была вызвана следующим образом:
result = registererror('SOMEFUNC:SOMETASK', 'Something terrible happened.')
result = -2
Вызывающая функция замечает, что результатом является не 0, а отрицательный код ошибки, и сама выдает ошибку:
result = registererror('CALLINGFUNC:TOPTASK', 'Trying to do some high level thing failed.', result);
result = -3
Это значение -3 возвращается нашей библиотекой в вызывающий код, поэтому он знает, что произошла ошибка просто потому, что результат был отрицательным. Затем он может вызвать errorcode2string, чтобы получить дополнительную информацию об ошибке.
[errorid, errorstring, innercode] = errorcode2string(int32(-3))
errorid = CALLINGFUNC:TOPTASK
errorstring = Trying to do some high level thing failed.
innercode = -2
Поскольку внутренний код по-прежнему отрицательный, если вызывающая программа склонна к этому, она может вызвать errorcode2string, чтобы узнать больше информации по функциональному стеку. На самом деле, именно так вы узнаете, что произошло с корневой ошибкой.
[errorid, errorstring, innercode] = errorcode2string(int32(-2))
errorid = SOMEFUNC:SOMETASK
errorstring = Something terrible happened.
innercode = 0
Теперь внутренний код равен 0, поэтому мы знаем, что можем остановиться.
Эта система отлично сработала для моего проекта, и я надеюсь, что она окажется полезной. Это была одна из первых вещей, которые мне пришлось понять, чтобы использовать Coder, и она научила меня многому о хороших практиках архитектуры в MATLAB Coder, в том числе о том, как сделать структуру данных доступной глобально.
person
Tony
schedule
16.04.2015