Este documento discute a problemática de como reportar um erro de uma função de um determinado módulo para o módulo que chamou essa função.
Determinar exatamente qual é o erro que causou a falha de uma determinada função é fundamental para entender e posteriormente corrigir o problema.
Num sistema modular como este o problema fica maior já que uma função normalmente chama várias outras funções de outros módulos.
Pensando sobre esse problema pude verificar que existem basicamente dois tipos de solução: uma solução que usa vários códigos de erro (um para cada erro) e outra que usa códigos de erro mais genéricos e que têm um significado específico dependendo do módulo que gerou esse código de erro.
Neste documento vou apresentar as vantagens e desvantagens de cada uma das soluções e mostrar também as funções utilizadas pelo ModulOS para implementar a sua solução.
Esta solução é caracterizada fundamentalmente por utilizar muitos códigos de erro onde cada código de erro é um inteiro. Por exemplo, E_FILE_NOT_FOUND, E_DISK_FULL, E_NO_MEMORY, etc.
Por não termos como saber que módulo gerou o erro, o erro tem que ser muito específico para facilitar a determinação do problema. Por exemplo, nesse tipo de solução não seria interessante uma função retornar E_BUSY no caso do modem estar ocupado, mas sim E_MODEM_BUSY.
A grande vantagem desta solução é que é preciso guardar apenas um inteiro por linha de execução para o código de erro. E a desvantagem é ter que ficar criando mais e mais códigos de erro.
Nesta solução existem uns poucos códigos de erro genéricos e o contexto é que determinará o seu significado exato. Por exemplo, E_BUSY para um módulo pode significar que o modem está ocupado e para outro que um semáforo não pode ser obtido.
A desvantagem desta solução é que é preciso guardar um estado maior para cada linha de execução. No caso do ModulOS por exemplo, seria necessário guardar o número da função e o nome da interface. Além claro do próprio código de erro.
A vantagem é que temos menos códigos de erro e sabemos exatamente em que módulo o problema aconteceu.
O ModulOS usa a solução que utiliza códigos de erro genéricos pois ela facilita muito a determinação do problema.
Para suportar a utilização de códigos de erro para cada linha de execução, a interface TaskManager possui duas funções: SetLastError e GetLastError com a seguinte sintaxe:
long SetLastError(long error, const char *name, int function); long GetLastError(const char **name, int *function);
A função SetLastError recebe como parâmetro o código de erro, um nome e o número da função que gerou o erro. O nome pode ser o nome da interface ou o nome do módulo dependendo se o código de erro é da interface ou é específico daquela implementação da interface. Isso é determinado pelo bit mais alto do parâmetro error. Se for 1 é um código de error específico do módulo e se for 0 é um código de erro da interface.
Como se nota, a função SetLastError retorna um long para indicar se ocorreu sucesso na atribuição do código de erro.
A função GetLastError retorna o código de erro e recebe como parâmetro um ponteiro para um ponteiro que receberá o ponteiro para o nome da interface ou do módulo dependendo do código de erro e um ponteiro para um inteiro que receberá o número da função que gerou o erro.
O ponteiro para o nome vai conter o nome da interface/módulo até que outra função defina outro erro. Assim, esse nome deve ser copiado ou usado imediatamente após um erro ter ocorrido.
Para mais informações sobre essas funções consulte a especificação da interface TaskManager.