STOP() should never be in production code, but sometimes happens. You’ll find this line in a popular library:
? stop('logic error in FindMatch') ! should never happen
The STOP window is ugly and confusing to end users.
Using PROP:StopHook it’s easy to replace that with a Message like below with these changes:
- Rename ABORT to “Close Application”, make second button
- Rename "IGNORE to “Continue”, make first button and the default
- Footer text to explain to End Users this message is “Unexpected”
In Debug Builds I add a “Stack Trace” button that does an ASSERT(). The Assertion window Call Stack makes it easy to find the “Stop code”, provided you used the Debug ClaRun.DLL which shows Procedure Names, “R$” for Routine names, and line numbers.
This is the code that makes it happen. At the bottom is a link to GitHub with the complete code.
StopBetter PROCEDURE(<STRING Message>)
SYSTEM{PROP:StopHook} = ADDRESS(StopBetter) !Tell RTL for STOP to call StopBetter()
!---------------------------------------------------------------------------
StopBetter PROCEDURE(<STRING pMessage>)
BlankMsg STRING('Exit Application?') !for STOP('')
StopMessage &STRING
FooterText STRING('<13,10>_{60}' & |
'<13,10>This message is displayed for an unexpected condition.' & |
'<13,10>Take a screen capture and note the steps you took.' & |
'<13,10>Please contact Technical Support for assistance.' )
AssertBtn PSTRING(24)
CODE
IF ~OMITTED(pMessage) AND pMessage THEN
StopMessage &= pMessage
ELSE
StopMessage &= BlankMsg !Called as STOP() or STOP('')
END
COMPILE('** debug **',_Debug_) !w/o Debug *No Assert() or Stack Trace
AssertBtn='|Stack Trace'
!** debug **
CASE MESSAGE(CLIP(StopMessage) & FooterText, | ! Message Text
'Stop - Unexpected Condition', | ! Caption
ICON:Hand, | ! Icon
'Continue|Close Application' & AssertBtn, |
1 , MSGMODE:CANCOPY )
OF 2 ; HALT() !Close Application
OF 3 ; ASSERT(0,'Stop Assert')
END
RETURN
If you use CapeSoft MessageBox, I’ll cover the changes to ds_Stop next time.
5 Likes
Hi Carl
that gave me a laugh as that is my code and it is there for a good reason.
if someone comes along and changes the code and inadvertantly breaks it I want it to yell out to the world about it and not go gentle into that good night.
you can argue about whether it is a good idea but I want to know if something fails.
of course you could also argue about whether these things should only be in debug mode (as it is here…) or on all the time. Asserts also only show up in debug mode so this is similar to an assert.
1 Like
My main point was if a STOP got into released code then which dialog below do you want a user to see?
Because it should never happen you coded it as a STOP, and I often do that too. Since the Abort is not needed this could have been a Message():
IF Message('Logic error in FindMatch: ' & pRegex,'ST',,'Ok|Stack Trace')=2 THEN ASSERT(0).
A favorite book Debugging Applications by John Robbins makes your point in the “Debugging During Coding” chapter. It has 2 points:
- Code should alert you to a problem with Assert, Trace, Stop, Message, etc.
- Code must handle the problem to prevent it causing further failure
I think of the #2 point often. Don’t just Assert, code to handle it! You did not do #2, after the STOP you Return. The LOOP pEnd = pStart TO endPos
will end with pEnd=EndPos+1
and pStart=StrPos(
. This likely returns with an invalid slice [pStart:pEnd] beyond the end of the &String. Failure should set pStart=0 ; pEnd=0
to indicate failure.
Again, I agree it should be impossible for this Stop to happen. But someone at Soft Velocity might break StrPos() and the problem only appear for certain RegEx, so it’s never seen in your testing.
1 Like
good points Carl.
I remember DAB did an article relating to this years ago entitled “Offensive Programming”.
I think it was originally published in Tom Moseley’s Clarion OnLine but is currently on David Bayliss’ dabhand site at:
http://www.dabhand.org/OldStuff/offensive.htm
It includes:
I was once sent a cartoon by fax, the picture was of an aeroplane flying over a busy city, the aeroplane had a speech bubble containing the words “Hello this is flight 932 from Sidney to Heathrow Central, what does ‘Numeric Exception at 000A:0089 – program halted’ mean?”
We can forgive the misspelling of Sydney and it does bring up the notion of when some form of tolerance/recovery is required although it also states:
The fundamental problem with fault tolerant systems is that they tolerate faults.
anyway well worth the read…
1 Like
I added a Halt Better replacement to the Repo. The template generated code will often HALT() when there is a file error, e.g… Legacy CheckOpen(FileXxx) when FileXxx does not exist.
The default message gives no hint to the User that OK is going to close the application. My replacement tells him “WILL CLOSE” so he can take a screen capture and notes. The “Stack Trace” button shows the Assert window to find the problem code (example above in STOP).
HALT() or HALT(’’) displays just a blank dialog with an OK button, so I display ‘HALT with Blank Reason’. Another way blank could happen is passing a blank string like HALT(ERROR()) when there is no error.
To use this set: SYSTEM{PROP:HaltHook} = ADDRESS(HaltBetter)
HaltBetter PROCEDURE(UNSIGNED pErrorLevel=0, <STRING pMessage>)
BlankMsg STRING('HALT with Blank Reason.') !for HALT('')
HaltMessage &STRING
FooterText STRING('<13,10>_{60}' & |
'<13,10>This message is displayed for an unexpected condition.' & |
'<13,10>The Application WILL CLOSE and NOT SAVE current data.' & |
'<13,10>Take a screen capture and note the steps you took.' & |
'<13,10>Please contact Technical Support for assistance.' )
AssertBtn PSTRING(24)
CODE
SYSTEM{PROP:HaltHook}=0 !So HALT() does RTL HALT()
IF OMITTED(pMessage) THEN
HALT(pErrorLevel)
ELSIF pMessage THEN
HaltMessage &= pMessage
ELSE
HaltMessage &= BlankMsg !Was HALT('') or HALT(Var) where Var=''
END
COMPILE('** debug **',_Debug_) !w/o Debug *No Assert() or Stack Trace
AssertBtn='|Stack Trace'
!** debug **
CASE MESSAGE(CLIP(HaltMessage) & FooterText, | ! Message Text
'HALT - Unexpected Condition', | ! Caption
ICON:Hand, | ! Icon
'Close Application'& AssertBtn, |
1, MSGMODE:CANCOPY)
OF 2 ; ASSERT(0,'HALT() Stack Trace Assert')
END
!IF ~BAND(KEYSTATE(),0300h) THEN
! ... could check for Ctrl+Shift down and Continue. Will need to Restore Prop:HaltHook
HALT(pErrorLevel)
RETURN
If you use CapeSoft MessageBox one of its options is to hook STOP and then it calls a ds_Stop procedure in one of your APPs. All of that code is generated to create a STOP just like the RTL using the Message() function.
There is an embed point for you to insert your own code ahead of the generated. Below is my code that works just like StopBetter:
ds_Stop PROCEDURE (<string StopText>)
!Data Generated by Capesoft
FooterText STRING('<13,10>_{60}' & |
'<13,10>This message is displayed for an unexpected condition.' & |
'<13,10>Take a screen capture and note the steps you took.' & |
'<13,10>Please contact Technical Support for assistance.' )
AssertBtn PSTRING(24)
CODE ! Begin processed code
if ~omitted(StopText) then Loc:StopText=StopText .
if ~Loc:StopText THEN Loc:StopText='Exit Application?'.
COMPILE('** debug **',_Debug_) !w/o Debug *No Assert() or Stack Trace
AssertBtn='|Stack Trace'
!** debug **
CASE Message(CLIP(Loc:StopText) & FooterText, |
'STOP - Unexpected Condition', ICON:Hand,|
'Continue|Close Application' & AssertBtn )
OF 1 !Continue
OF 2 ; HALT() !Was ABORT now button 2 named 'Close Application'
OF 3 ; ASSERT(0)
END
RETURN
!Generated code is NOT used that does ... TranslationFile and TimeOut
Note this short circuits some features like Translation and a Time Out counter. You can easily paste that code in to get the feature.
1 Like