Peter N Lewis wrote:
But I don't really understand the difference you make between turned off and compiled out. If assertions are turned off, no code is generated for them, unless they have side-effects, i.e. cases such as:
{ Returns True on success, False on failure. } function Foo (a: Integer): Boolean; [...]
Assert (Foo (42));
With assertions turned off, this is equivalent to:
Dummy := Foo (42);
(where Dummy is an otherwise unused Boolean variable).
That's the point. My assertions tend to be things like:
Assert( ValidDataStructure( x ) );
ValidDataStructure might be a very expensive call, it might parse through the entire data structure looking for holes in it.
This behaviour may be debatable. I think it would be more risky to discard side-effects as well (especially if most testing is done with assertions turned on).
But I'm not sure if that's actually what you're referring to.
Absolutely, it is definitely a case of tradeoffs. You have to be disciplined about your use of Asserts to ensure you never use any code in them that is required to be executed. It's all too easy to write things like:
Assert( OpenFile( f, name ) = noErr );
As I said, I fully understand why they are done the way they are done. However for me, the tradeoff with that is that it restricts the cases I would be willing to use Assert for. No one would want to write code like:
procedure AppendToList( var list: ListType; item: ItemType ); begin Assert( ValidList( list ) ); ...
if they new that in the production code their O(1) AppendToList function would actually be O(n) or worse.
It's a trade off of safety (in executing potentially side effect riddled code) with safety (in writing reliable well tested programs).
GPC also makes this more challenging because you can't define a macro in any given unit for asserting the validity of data structures because that macro is not propagated into the using units. For example, in the interface my hypothetical Lists unit above, I would add something like:
{$ifc not do_debug} {$definec AssertValidList( list )} {$elsec} {$definec AssertValidList( list ) AssertValidListCode( list, SrcFile, SrcLine )} {$endc}
{$ifc do_debug} procedure AssertValidListCode( list: ListType; source: String; line: Integer ); {$endc}
but that does not work in GPC. My solution has been to use a prefix file and copy all such macro definitions to the prefix file. This is less than ideal since it breaks the modularity. Perhaps someone has a better solution for me, but it's the best I can come up with so far.
Although it wouldn't be backward compatible with CW Pascal, you might want to look into using GPC's support for Extended Pascal's module multiple interface exports and export renaming. For example:
module MyLists;
export MyLists = (AppendToList); MyListsDebug = (AppendToListDebug => AppendToList); {insert const and type declarations needed for interface declarations} procedure AppendToList( var list: ListType; item: ItemType ); procedure AppendToListDebug( var list: ListType; item: ItemType ); end; {module heading}
function ValidList( protected var list: ListType): boolean; {insert list validation implementation code block}
procedure AppendToList( var list: ListType; item: ItemType ); {insert procedure's implementation code block}
procedure AppendToListDebug( var list: ListType; item: ItemType ); begin {pre-condition asserts go here} Assert( ValidList( list ) ); AppendToList( var list: ListType; item: ItemType ); {post-condition asserts go here} end; end. {module block}
Then in code using the MyLists module, use conditional compilation directives to select the debugging interface or the non-debugging interface. For example:
procedure DoSomethingWithLists; {$ifc not do_debug} import MyLists; {$elsec} import MyListsDebug in 'MyLists.pas'; {$endc} {insert other needed declarations} begin {...} {With do_debug = true, this ends up calling AppendToListDebug with the pre- and post-conditions assertion checks. Otherwise, it calls AppendToList which doesn't have the assertion overhead.} AppendToList(theList,anItem); end;
Then as long as you practice a discipline of side-effect free pre-condition and post-condition asserts, the effects of debugging/non-debugging versions will be transparent; will retain modularity; and will avoid the debugging code overhead in non-debug production code. If you're using a smart linker, the debugging code routines should also be dead stripped from the production executeable since the debugging related routines are never called from production code.