Compartilhar via


Error Handling in VBScript, Part Three

Apparently I've sparked a discussion amongst the super-geniuses of LtU on various innovative language constructs for handling errors. Fascinating stuff that I'd love to learn more about! But I'll be less highfalutin: no doubt about it, error handling in VBScript is a pain in the rear no matter how you slice it.

This series was inspired in party by an email from a reader who was interested in the philosophy of error handling. Here's an excerpt which contrasts two approaches: (my emphasis)

I think I'm old school in saying that error handling should be very tight. Handle errors where you expect to find them. Everything else is left to fail. I'd rather have a program end in a messy death than to blithely continue on in an unpredictable fashion. Some of my cohorts would rather do broad error handling (whole subroutines or sections of the script). They seem to assume that only the errors they expect will happen. And even if other errors do happen, it's better to have the script finish as best it can than to do nothing at all.

I've talked about this before (at the bottom of the post.) As a professional developer who writes complex C# and C++ code on a large team building a product that will be shipped to millions of people, I agree with the writer. Error handling should be built into the architecture from day one. And of course we have done so in VSTO2 -- we have developed a set of exception classes with error numbers and localizable error strings, and tried to engineer everything so that only the right errors get propagated up to the user.

But I'm working in C# and C++, languages specifically designed for implementing complex software written by large teams. VBScript is not such a language -- it was designed for simple administration and web scripts, where often "muddle on through" is exactly what you want it to do.

For example, I have a simple script much like this one that I use for doing quick-and-dirty regular expression searches on my hard disk. You’d better believe that On Error Resume Next is on for that script! If it encounters a directory or file that it cannot search because it is locked by another process, or that I don't have permission to read, or whatever, then I don't want my little twenty-line script to die horribly! I certainly don't want to be constantly maintaining it to add new error logic as I discover more special cases. Were I writing a user-grade bulletproof hard disk searching tool that was going to be shipped in the operating system, you'd better believe I'd have error handling all over that thing, but for my scripty purposes, doing its best and muddling through is almost always plenty good enough. Use the right tool for the job.

Finally, one more implementation detail that I forgot to mention the other day. When VBScript gets certain error numbers back from calls to IDispatch objects, it sometimes takes the error number and replaces it with the equivalent VBScript error number. I have never particularly liked this feature, but VB6 does it, so we're stuck with it for backwards compatibility reasons. This can be a little confusing if you're debugging a problem -- you see one error go out of the object, but a different error is reported to the host. On the off chance that someone finds this useful, I'll put the mapping table that VBScript uses below.

Coming up next time, part two of Riddle Me This, Google, where once more I dole out advice on subjects I know little about -- love, optics and primatology -- based on questions culled from my most recent 29950 Google hits. Stay tuned!

0x80004001 (E_NOTIMPL)                  --> 445 (ActionNotSupported)
0x80004002 (E_NOINTERFACE)              --> 430 (OLENotSupported)
0x80020001 (DISP_E_UNKNOWNINTERFACE)    --> 438 (OLENoPropOrMethod)
0x80020003 (DISP_E_MEMBERNOTFOUND)      --> 438 (OLENoPropOrMethod)
0x80020004 (DISP_E_PARAMNOTFOUND)       --> 448 (NamedParamNotFound)
0x80020005 (DISP_E_TYPEMISMATCH)        -->  13 (TypeMismatch)
0x80020006 (DISP_E_UNKNOWNNAME)         --> 438 (OLENoPropOrMethod)
0x80020007 (DISP_E_NONAMEDARGS)         --> 446 (NamedArgsNotSupported)
0x80020008 (DISP_E_BADVARTYPE)          --> 458 (InvalidTypeLibVariable)
0x8002000A (DISP_E_OVERFLOW)            -->   6 (Overflow)
0x8002000B (DISP_E_BADINDEX)            -->   9 (OutOfBounds)
0x8002000C (DISP_E_UNKNOWNLCID)         --> 447 (LocaleSettingNotSupported)
0x8002000D (DISP_E_ARRAYISLOCKED)       -->  10 (ArrayLocked)
0x8002000E (DISP_E_BADPARAMCOUNT)       --> 450 (FuncArityMismatch)
0x8002000F (DISP_E_PARAMNOTOPTIONAL)    --> 449 (ParameterNotOptional)
0x80020011 (DISP_E_NOTACOLLECTION)      --> 451 (NotEnum)
0x8002802F (TYPE_E_DLLFUNCTIONNOTFOUND) --> 453 (InvalidDllFunctionName)
0x80028CA0 (TYPE_E_TYPEMISMATCH)        -->  13 (TypeMismatch)
0x80028CA1 (TYPE_E_OUTOFBOUNDS)         -->   9 (OutOfBounds)
0x80028CA2 (TYPE_E_IOERROR)             -->  57 (IOError)
0x80028CA3 (TYPE_E_CANTCREATETMPFILE)   --> 322 (CantCreateTmpFile)
0x80030002 (STG_E_FILENOTFOUND)         --> 432 (OLEFileNotFound)
0x80030003 (STG_E_PATHNOTFOUND)         -->  76 (PathNotFound)
0x80030004 (STG_E_TOOMANYOPENFILES)     -->  67 (TooManyFiles)
0x80030005 (STG_E_ACCESSDENIED)         -->  70 (PermissionDenied)
0x80030008 (STG_E_INSUFFICIENTMEMORY)   -->   7 (OutOfMemory)
0x80030012 (STG_E_NOMOREFILES)          -->  67 (TooManyFiles)
0x80030013 (STG_E_DISKISWRITEPROTECTED) -->  70 (PermissionDenied)
0x8003001D (STG_E_WRITEFAULT)           -->  57 (IOError)
0x8003001E (STG_E_READFAULT)            -->  57 (IOError)
0x80030020 (STG_E_SHAREVIOLATION)       -->  75 (PathFileAccess)
0x80030021 (STG_E_LOCKVIOLATION)        -->  70 (PermissionDenied)
0x80030050 (STG_E_FILEALREADYEXISTS)    -->  58 (FileAlreadyExists)
0x80030070 (STG_E_MEDIUMFULL)           -->  61 (DiskFull)
0x800300FC (STG_E_INVALIDNAME)          --> 432 (FileNotFound)
0x80030100 (STG_E_INUSE)                -->  70 (PermissionDenied)
0x80030101 (STG_E_NOTCURRENT)           -->  70 (PermissionDenied)
0x80030103 (STG_E_CANTSAVE)             -->  57 (IOError)
0x80040154 (REGDB_E_CLASSNOTREG)        --> 429 (CantCreateObject)
0x800401E3 (MK_E_UNAVAILABLE)           --> 429 (CantCreateObject)
0x800401E6 (MK_E_INVALIDEXTENSION)      --> 432 (OLEFileNotFound)
0x800401EA (MK_E_CANTOPENFILE)          --> 432 (OLEFileNotFound)
0x800401F3 (CO_E_CLASSSTRING)           --> 429 (CantCreateObject)
0x800401F5 (CO_E_APPNOTFOUND)           --> 429 (CantCreateObject)
0x800401FE (CO_E_APPDIDNTREG)           --> 429 (CantCreateObject)
0x80070005 (E_ACCESSDENIED)             -->  70 (PermissionDenied)
0x8007000E (E_OUTOFMEMORY)              -->   7 (OutOfMemory)
0x80070057 (E_INVALIDARG)               -->   5 (IllegalFuncCall)
0x800706BA (RPC_S_SERVICE_UNAVAILABLE)  --> 462 (ServerNotFound)
0x80080005 (CO_E_SERVER_EXEC_FAILURE)   --> 429 (CantCreateObject)

Comments

  • Anonymous
    August 25, 2004
    I write complex scripts for the enterprise in cases where no existing tool (or at least one that meets all expectations) can do the job at hand. I agree that vbscript's error handling is a pain, but what are your recommendations enterprise-level error handling those kinds of situations? I'll take a guess and say .NET will be part of your initial response, but let's assume that vbscript will continue to be the language of choice.

  • Anonymous
    August 25, 2004
    The comment has been removed

  • Anonymous
    August 25, 2004
    I've been meaning to blog on WSCs for some time now, I just haven't gotten around to it yet.

  • Anonymous
    August 25, 2004
    The comment has been removed

  • Anonymous
    August 25, 2004
    8/25/2004 5:40 PM Eric Lippert

    > The language itself encourages the "Write
    > it. Run it. Tweak it.

    Fine so far.

    > Ship it."

    Oops.

    One attribute should be required on this kind of coding, during development. [Unsafe for shipping].

    The language should allow optional insertion of readable error handling, internationalization, etc. Optional for the first three steps and recursions and iterations thereof. But the attribute [Unsafe for shipping] should not be deleteable until these characteristics are present.

  • Anonymous
    August 25, 2004
    The REXX scripting language requires that every program begin with a descriptive comment that clearly and correctly documents the purpose of the program.

    However, what the compiler actually ENFORCES is simply that every program begin with a comment. Strangely enough, every REXX program I ever wrote begins with an empty comment.

    Who would have predicted that? :-)

    All kidding aside, you raise several good points. We have languages which recognize that functions, types, security settings, attributes, events and exceptions are first-class objects. I'd love to add "internationalization" to that list (among other things).

    We do have a way in .NET of marking a program "not safe for shipping" -- it's called "delay signing". Since users will typically be unable to use non-strong-named assemblies, it behooves you to not strong-name anything that isn't ready for prime time. Or, put another way, a strong name is your claim that a piece of code is worth putting your name on.

    I have a funny story -- ok, it wasn't funny at the time, as it caused a lot of user pain -- about how a "don't ship this" mark on a piece of code was insufficent to prevent it from being shipped. I'll blog about the internal details of The Great RegExp Debacle some time.

  • Anonymous
    August 25, 2004
    The comment has been removed

  • Anonymous
    August 25, 2004
    I've just seen this:
    http://support.microsoft.com/default.aspx?scid=kb;en-us;312116&Product=vsnet

    When scripting was blocked, Visual Studio falsely reported that it installed successfully.

    What would everyone's opinion be of a backup program reporting that it successfully made a backup when in fact it hadn't?

    How much fun was it when Internet Explorer used to happily report that it successfully downloading some file when in fact the download had died after 95% or 50% or 5%?

    When an error occurs and can't be corrected, there is something the program needs to do. It needs to inform the user. The user needs to know that they need to either retry the operation and/or get an upgrade and/or try another vendor's program. False reports of success are as malicious as viruses.

  • Anonymous
    August 25, 2004
    The comment has been removed

  • Anonymous
    August 26, 2004
    What bugs me about "on error resume next" is that it is way too easy to leave on, either by accident or by laziness. Since adding "on error goto 0" makes the program longer, there's an incentive to leave it out for brevity and readability. With scoped error handling such as try/catch you have to at least think about scope. Sure, you can wrap a whole program in try/catch, but at least it makes you think.


  • Anonymous
    August 26, 2004
    The comment has been removed

  • Anonymous
    August 27, 2004
    The comment has been removed

  • Anonymous
    August 30, 2004
    Your insights are (as usual) spot on Alex. Error handling is hard, and the difficulty grows quickly as the problem scales up. As I've said before and I'll say again, the VBScript language was not designed for programming-in-the-large, it was designed to facilitate rapid development of simple scripts by people who knew a little VB.

    Are there technical reasons why we didn't do global error handling? I wasn't there -- that was before my time -- but I could probably come up with some hand waves.

    Like, it would be quite tricky to make it work in the world where code starts running before all the code has been even downloaded.

    What happens when your handler is in another block? What happens when the engine is moved back to uninitialized state but the block with the error handler isn't marked as persistent? Yes, we have all these problems with subroutines too, but when you call a subroutine that doesn't exist, we raise an error. Error handlers should ideally not afford the possibility of themselves raising errors, because then you have a horrible mess no matter how you slice it.

  • Anonymous
    August 30, 2004
    8/30/2004 10:58 AM Eric Lippert

    > Error handlers should ideally not afford the
    > possibility of themselves raising errors,
    > because then you have a horrible mess no
    > matter how you slice it.

    Exactly the opposite. An error that was not planned for yields a horrible mess no matter how you slice it. Hiding the error yields a compounded horrible mess. An unplanned error in an error handler yields a different compounded horrible mess. Hiding an unplanned error in an error handler yields a multiply compounded horrible mess. Hiding does not make things better, it makes things worse.

    When an error arises in an error handler, do not pretend that the original error was handled, do not hide the error in the error handler, do not go directly to jail until you have first confessed to the user.

    Even if the user isn't a programmer, they deserve to know that their latest 10 hours did not create a reliable backup and they'd better get a different backup tool.

  • Anonymous
    August 30, 2004
    The comment has been removed

  • Anonymous
    September 02, 2004
    "As I've said before and I'll say again, the VBScript language was not designed for programming-in-the-large, it was designed to facilitate rapid development of simple scripts by people who knew a little VB."

    I jsut want to add a codicil to this. It is of course absolutely true from what I know. However, this is not the same as saying "VBScript should not be used for large projects". VBScript is a classic example of "successful software" - it not only is used in ways the developers never intended, it's occasionally used in ways the developers (and many users) never even imagined in their wildest dreams. :)

    In my opinion, tne design intent doesn't tell us what we should do with it. Obviously, it IS important because it points to the likely limits of what can be done.

    Batch convert thousands of documents from WordPerfect to Word? Sure, that's easy. Finite element analysis? Not unless they're very, very finite elements... ;)

  • Anonymous
    February 07, 2008
    Sometimes I end up rewriting my admin scripts in JScript just for the try/catch block!

  • Anonymous
    February 01, 2012
    excellent article.  thanks for sharing.