I wanna add to @xochi 's excellent answer. One common pattern in FM scripts that I see frequently, and use personally is a "single-pass loop" for early exits and error trapping. This pattern allows you to add multiple guard clauses or exit conditions, but still centralize the actual Exit Script step at the end so that you can do some boilerplate logging and script result formatting. This can make script flow easier to reason about.
Example
Loop
# Single-pass loop
# Put your main logic in here.
Exit Loop If [ If ( WrongContext() ; Let ( $error = "Wrong context!" ; True ) )]
Exit Loop If [ If ( not IsBrowseMode() ; Let ( $error = "Must be in browse mode" ; True ) )]
# If no records, exit *without* error (don't set $error)
Exit Loop If [ Get(FoundCount) = 0 ]
New Record
Exit Loop If [ If ( Get(LastError) ; Let ( $error = "New Record: " & Get(LastError) ; True ) )]
Set Field [ "Field1" ; "123" ]
Exit Loop If [ If ( Get(LastError) ; Let ( $error = "Set Field1: " & Get(LastError) ; True ) )]
Set Field [ "Field2" ; "987" ]
Exit Loop If [ If ( Get(LastError) ; Let ( $error = "Set Field2: " & Get(LastError) ; True ) )]
Commit Record
Exit Loop If [ If ( Get(LastError) ; Let ( $error = "Commit: " & Get(LastError) ; True ) )]
# End Single-pass loop
Exit Loop If [ True ]
End Loop
# Centralized error handling
If [ $error ]
Revert Record
Perform Script [ "Log" ; "Uh oh, an error occurred: " & $error ]
Show Custom Dialog [ "Error!" ; $error ]
End If
# Centralized Exit Script
Exit Script [ JSONSetElement ( "{}" ; [ "error" ; $error ; JSONString ] ) ]
NOTE: this is a purposely rudimentary example of error formatting, and a more sophisticated error trapping solution would use an error json object that always has the same properties (and a succinct custom function to create it). I just used strings for simplicity.
Thanks @jwilling for the shoutout. For anyone interested, the same demo file was used as the core material for a presentation I mentioned in this other thread: Error trapping advice - #7 by Bobino