Error management in OOP framework.

1 view (last 30 days)
Cedric
Cedric on 1 Dec 2017
Edited: Cedric on 3 Dec 2017
Dear all,
1. I cannot use a global/outer try/catch statement, because the MException caught doesn't contain any reference/handle to the object that threw it. Concretely, if the main script is:
try
model = Model() ;
project = model.addProject( 'Test' ) ;
project.addScenario( NaN ) ;
catch ME
...
end
with
classdef Project < handle
methods
function addScenario( obj, name )
assert( ischar( name ), ... ) ;
...
there is no way to get a handle on project from ME, which stores only text (msgID, message), a stack (struct array of names and line numbers), and causes (cell array of other MException objects).
2. If I extend a little this mechanism with:
classdef Core < handle
methods
function assert( obj, test, varargin )
...
...
classdef Project < Core
methods
function addScenario( obj, name )
obj.assert( ischar( name ), ... ) ;
...
I could update the MException object in Core.assert (adding clauses), but again, there is no way to add a Project handle to a MException object, so there is no point in having the outer try/catch statement.
3. Accounting for this, I can extend a bit the mechanism and implement something along the following line:
classdef Core < handle
methods
function assert( obj, test, varargin )
if ~test
obj.errorManager( varargin ) ;
...
classdef Project < Core
methods
function addScenario( obj, name )
obj.assert( ischar( name ), ... ) ;
end
function errorManager( obj, varargin )
...
end
...
which is somehow satisfactory, because I can add an errorManager method to each sub-class of Core, as a separate file in each class folder, and implement as much error management/analysis/reporting as I want without "loading" the rest of the code in the class definition.
Yet, this is not completely satisfactory. I can easily truncate the (DB) stack from the report that I generate, i.e. the first two entries ( Project.errorManager and Core.assert) that should not be displayed, to avoid confusing users who should see Project.addScenario at the top as the source of the error.
But when I throw an exception ultimately from the error manager or elsewhere, it becomes difficult to prevent MATLAB from displaying these first entries. I looked for a way to pass or define a start stack depth for MATLAB to truncate the stack and I didn't find any. The stack property of MException objects is read-only so I am unable to (re)define it.
There are still a few messy alternatives, e.g. throw an exception, post-process the report (easily done using REGEXP and the output of getReport(ME)), print it to stderr, and generate an "empty enough" error, but these tricks seem to be ugly.
Hence my questions:
  • Did I miss a fundamental mechanism for managing these situations properly?
  • Do you have any cleaner alternative approach? (Keeping in mind that adding clauses to a MException object is not flexible enough).
Thank you,
Cedric

Accepted Answer

Philip Borghesani
Philip Borghesani on 1 Dec 2017
Edited: Philip Borghesani on 1 Dec 2017
I am not sure if this code helps you or not but have you tried subclassing MException to add an object?
Things to look for in code:
  • Use of ThrowAsCaller to hide helper functions.
  • This code assumes object has name better to use evalc and disp of object in portable code.
  • Any number of types of exception are possible.
  • From the command line MException.last can be used to retrieve the exception object.
classdef ObjException < MException
methods
function obj=ObjException(src,varargin)
obj=obj@MException(varargin{:});
obj.sourceObj=src;
end
function rep=getReport(obj,varargin)
rep=sprintf('Object Error on object %s\n%s', obj.sourceObj.name, getReport@MException(obj,varargin{:}));
end
end
properties
sourceObj
end
end
classdef testobj
methods
function meth(obj,arg)
assert(obj,arg);
end
function assert(obj,cond,varargin)
if ~cond
if nargin>2
ex=ObjException(obj,varargin{:});
else
ex=ObjException(obj,'testobj:AssertFailed','assertion failed');
end
throwAsCaller(ex);
end
end
function error(obj,varargin)
ex=ObjException(obj,varargin{:});
throwAsCaller(ex);
end
end
properties
name
end
end
>> to=testobj
>> to.name='foo'
>> to.meth(true)
>> to.meth(false)
Object Error on object foo
Error using testobj/meth (line 4)
assertion failed
  2 Comments
Cedric
Cedric on 2 Dec 2017
Hello Philip and thank you very much for your answer!
I've been testing your approach for a few hours and (after some fight because a base class method can only be called from the same method in a subclass), I think that I could adapt it to my case. I will go on tomorrow and update the thread accordingly.
Thanks again!
Cedric
Cedric on 3 Dec 2017
Edited: Cedric on 3 Dec 2017
I finished migrating to an approach based on your answer and it works well; thank you! In short,
  • ErrorManager built in Core.assert/error calls obj.errorManager where obj in an instance of a subclass of Core. Method errorManager is defined for each subclass in its own M-File in each class folder, so it can be as long as needed without "loading" and messing up the rest of the code.
  • PException<MException built in Core.assert/error, stores handle on current object and redefines method getReport.
  • PException.throwAsCaller (inherited from MException) is called at the end of Core.assert/error. It calls the overloaded getReport, which produces an extended report.

Sign in to comment.

More Answers (0)

Categories

Find more on Software Development Tools in Help Center and File Exchange

Products

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!