Howard Bullock
(KiX Supporter)
2002-04-15 02:58 AM
UDF Design

Discussion Issue.

I have been internally debating how a properly constructed UDF should return data including errorcodes and would like input from the board.

Is it as simple as always using "Exit x"?

In some of my UDF's I return an array where the first array element is the value expected to be returned by the UDF and the second and third are the errorcode and error text that may have been encountered by an function or COM call internal to the UDF. For the sake of consistent development, is there a methodology that is preferred over others?

Should a UDF define custom error conditions and use Exit to set @error to a non Microsoft OS error number?

Any and all thoughts are expected.


Schuliebug
(Hey THIS is FUN)
2002-04-15 11:56 AM
Re: UDF Design

Howard,

I try to use exit codes just as Ruud does in the standard Kix functions. E.g. when creating a function InGroups which checks if the user is member of all the groups given, i return the same @error as the InGroup function does.

If it is a completely new UDF, there's no standard. I prefer however to set the @error only if an error occurs (@error=0; no error, @error>0; error !).


ShawnAdministrator
(KiX Supporter)
2002-04-15 02:34 PM
Re: UDF Design

My two cents:

I think your right, but there are some tough decisions one must make. I`ll make the statement that one should always set the value of @ERROR using EXIT(n). Having said that, here`s some scenarios:

If your function returns data, then you really (should) have no choice. Always set the value of @ERROR using EXIT(n). I would NOT recommend returning an error code as part of the data itself. Why ? Because its not really the standard (kixtart) way of doing things.

If your function doesn`t return any data. Then you got some decisions to make. Should you return the error code ? Should you return nothing and set @ERROR ? Should you do both ?

Looking to Kixtart for guidance isn`t much help because some kixtart functions (like commands) just set the value of @ERROR, while some return @ERROR and set @ERROR.

The nice thing about returning @ERROR is that folks can use your UDF in conditionals, kinda like the OPEN function, eg:

if open(1,"filename") = 0
...
endif

The "problem" with returning @ERROR is that folks will always have to "silence" your UDF if they are not interested in its return code:

$= open(1,"filename")

Sometimes its nice not to have to "worry" about silencing a function. But given that silencing is a quirk unique only to kixtart, and that UDFs are functions and NOT commands, I would suggest that you bite the bullet and do it the kixtart way, and that is to return @ERROR and set @ERROR.

The only exception to this might be highy specialized functions, functions that are more like procedures than anything else. Or for functions that rarely if ever fail (ie console writting functions) or has good error checking, applies lots of defaults and has flawless error recovery.

-Shawn

[ 15 April 2002, 14:39: Message edited by: Shawn ]


Richard H.Administrator
(KiX Supporter)
2002-04-15 03:37 PM
Re: UDF Design

I agree with Shawn with regard to @ERROR. There is no really compelling reason not to set it. One of the minor criticisms of KiXtart is that the use of @ERROR is inconsistant.

You should be able to rely on @ERROR to report the success/failure of a function call in every case, whether it can be intuited from the data returned or not.

Howard, if you are going to use an array to return data, I suggest you re-order it slightly so that the error value is always in the same place. My personal preference would be:
quote:
Element 0 = Error Value
Element 1 = Error String
Element 2 = Number of data returned
Element 3 = Datum 1
Element 4 = Datum 2
...
Element n+2 = Datum n



Jack Lothian
(MM club member)
2002-04-16 02:31 AM
Re: UDF Design

I don't think there is any obvious standard that is an optimal solution in all cases. Returning arrays, no matter how you order them, makes using the function rather complicated. Maybe it is something that should be left open.

Richard H.Administrator
(KiX Supporter)
2002-04-16 10:30 AM
Re: UDF Design

The problem is that there is no clean standard way to return multiple values. This thread is proposing a way of defining a standard method to achieve it. It's not about enforcing a method but rather producing quidelines for a standard way of doing it if you want to, the "KiXtart Result Array Passing" HOWTO if you like. Perhaps someone can think up an acronym for it?

The purpose of ordering the values in a particular way is so that it makes sense for the error value to be in the same place every time.

I don't believe that the process adds significant complexity. Here is a trivial example:
code:
Function udfSquare($iNumber)
Dim $Result[4]
;
$Result[0]=0 ; Success
$Result[1]="Operation completed successfully"
;
$Result[3]=$iNumber * $iNumber
$Result[2]=1 ; One data value returned
;
$udfSquare=$Result
Exit $Result[0] ; Set ERROR on exit
EndFunction
;
;
$avResult=udfSquare(1024)
If @ERROR
udfErrorHandler($avResult)
Else
"Result: " $avResult[3]
EndIf

There is very little extra code in the function, and no extra code in the calling script.

You can immediatley see another benefit. If an error occurs I hand off the processing to a generic error handler routine. This can display the error number and message, and can even display the returned value(s) for debugging. This error handling will work for every function which is coded with an array returned in the suggested format.

[ 16 April 2002, 10:31: Message edited by: Richard Howarth ]


Howard Bullock
(KiX Supporter)
2002-04-16 01:39 PM
Re: UDF Design

Richard, with your latest post this thread is headed right where I hoped it would. My goal was to spur discussion and possibly develop some level of consensus about UDF design.

My issue started with the TranslateName UDF. I have four COM calls and wanted to know the original error text which indicated which call failed. This detail was lost if I only set @error with Exit(@error). You are correct the main reason for pursuing this direction is the enhanced ability to return sufficient information for properly reporting the failure and start debugging or even just understanding the issue is the code itself is sound.

The TranslateName UDF was failing on specific computers from time to time. It was a real pain. The additional data returned help isolate the problem to the NameTranslate.Init call which in turn was traced to a DNS issue that kept the client from finding the GC:.


Jack Lothian
(MM club member)
2002-04-16 06:10 PM
Re: UDF Design

The point I was trying to make was not about enforcing standards rather it was a question about where should one should take kixtart. In my opinion, one of kixtart greatest advantages was its simplicity. Novices could leap right in and use it. You didn’t need to be a programmer or think like one to use it. In a previous life, I was a programmer and I understand good programming practices but I also know that everyone in the world can not think like a programmer.

As a example, EXCEL use to be a wonderful programming tool used by hundreds of thousands of non programmers. Many years ago even secretaries made word & excel macros. Today, visual basic for applications replaces those old macros & all those people stopped making “programs”. Only hard core programmers think this is an improvement. Unfortunately, at MS the teckies dominate & programming for the masses is not an option.

In my mind, I always thought Ruud swam against this flow. He was a teckie who tried to talk to the masses.

I was just posing a question about balance. Don’t forget the masses.


ShawnAdministrator
(KiX Supporter)
2002-04-16 07:21 PM
Re: UDF Design

Jack, et al

[good to see you back by the way, Jack]

I agree with Jack. Making a UDF more complex than it needs to be just doesn't make sense. After all, the whole purpose of the UDF in first place was to hide some sort of complexity. Im not saying that returning structured arrays is complex, just that its overkill for the job at hand.

My strategy has always been to "recycle" existing Microsoft @ERROR codes (and ideally their asscociated @SERROR string descriptions). For example, if one builds a UDF to search an array for a string, and one wanted an error code to represent string not found, simply recycle ERROR 2 (file not found). If an internal UDF function or command failed, one might just blindy return that to the user, then abort the UDF.

The other strategy one may use is to simply define your own error codes, starting at a base (say 1) and working-up to however many you need. Or I think theres an upper limit to the number of win32 error codes anyways, so maybe you could start your numbering at that level (i think if you go above 6000 your safe). But, one may critize this strategy by saying that the std @SERROR descriptions won't "line-up" with the meaning.

I know things get can get tricky with larger UDF's. A UDF that has many steps with function calls that can potentially return the same ERROR code (how would the user know which "step" failed), but the given the ideals behind encapsulation, the question becomes:

"Should the user care which step failed" ?

I've rambled.

-Shawn

[ 16 April 2002, 19:28: Message edited by: Shawn ]


Howard Bullock
(KiX Supporter)
2002-04-16 09:30 PM
Re: UDF Design

Rambling is good as long as its close to the topic.

New Mexico Mark
(Hey THIS is FUN)
2002-04-16 11:26 PM
Re: UDF Design

A couple of additional thoughts here:

1. Shawn has a good point about recycling MS error codes, where possible. However, a UDF that uses hundreds or thousands of possible error codes is far exceeding the scope of a function. If you limit (and therefore identify) a subset of MS errors, you might as well make your own system.

2. There are two ways to represent errors. If only one error will ever be returned, a 32-bit integer can represent over billions of distinct errors. However, in most cases, there is less need for this many error codes and more need for one error number that can represent zero, one, or more than one simultaneous errors. In this case, a 32-bit number can represent 32 distinct errors. Of course, there are still billions of possible combinations of errors, but one only need parse the 32 bits and use error handling as necessary from there.

For example, we have a pretty extensive logon script, as it is doing much of the job in lieu of SMS while the USAF is experimenting with brain-dead Tivoli [motto - "We do one-quarter of the job of SMS at only twenty times the cost!"]. First, I set the values of the errors, with the array element number corresponding to the bit number of the error.

code:
; General structure is:
; Bits 0 - 7 General messages and minor change reporting
; Bits 8 - 15 Major change reporting / non-fatal errors
; Bits 16 - 23 Fatal errors and security violations
; Bits 24 - 30 Internal script errors
for $intCtr = 0 to ubound($aryMsgCat)
$aryMsgCat[$intCtr] = ""
next
$aryMsgCat[0] = "INF" ; 1
$aryMsgCat[1] = "MND" ; 2
$aryMsgCat[2] = "UCC" ; 4
$aryMsgCat[3] = "SCC" ; 8
$aryMsgCat[4] = "VSF" ; 16
$aryMsgCat[5] = "MIF" ; 32
$aryMsgCat[6] = "RAS" ; 64
$aryMsgCat[7] = "LCF" ; 128
$aryMsgCat[8] = "CDF" ; 256
$aryMsgCat[9] = "NFE" ; 512
$aryMsgCat[10] = "NLA" ; 1024
$aryMsgCat[11] = "NSS" ; 2048
$aryMsgCat[12] = "NSD" ; 4096
$aryMsgCat[16] = "ERR" ; 65536
$aryMsgCat[17] = "SEC" ; 131072
$aryMsgCat[18] = "NOS" ; 262144
for $intCtr = 0 to ubound($aryMsgTxt)
$aryMsgTxt[$intCtr] = ""
next
$aryMsgTxt[0] = "Debugging and status INFormation"
$aryMsgTxt[1] = "Mapped Network Drive"
$aryMsgTxt[2] = "User Configuration Change"
$aryMsgTxt[3] = "System Configuration Change"
$aryMsgTxt[4] = "Virus Signature File update"
$aryMsgTxt[5] = "MIF File creation or update"
$aryMsgTxt[6] = "RAS connection"
$aryMsgTxt[7] = "Local Configuration Flag"
$aryMsgTxt[8] = "unable to Create Directory or File"
$aryMsgTxt[9] = "Non-Fatal Error"
$aryMsgTxt[10] = "Not in Local Administrators group"
$aryMsgTxt[11] = "Non-Standard Script program or version"
$aryMsgTxt[12] = "Non-Standard Domain"
$aryMsgTxt[16] = "external or internal ERRor"
$aryMsgTxt[17] = "SECurity problem or error"
$aryMsgTxt[18] = "Non-Standard Operating system or server"

I echo these values to each user's log to help our Help Desk folks read the codes in the logs:

code:
  
Write(@CRLF + 'The following is a list of active error codes used in the script.',1)
Write('Spaces inserted in acronyms here to prevent false hits in searches.',1)
Write('CATEGORY BIT VALUE DESCRIPTION',1)
FOR $iCtr = 0 to UBound($aMsgCat)
IF $aMsgCat[$iCtr] <> ''
$iTmp = Exp(2,$iCtr)
$sTmp = SubStr($aMsgCat[$iCtr],1,1) + ' ' +
SubStr($aMsgCat[$iCtr],2,1) + ' ' +
SubStr($aMsgCat[$iCtr],3,1) + ' ' +
SubStr(' ',1,2 - Len($iCtr)) + $iCtr + ' ' +
SubStr(' ',1,9 - Len($iTmp)) + $iTmp + ' ' +
$aMsgTxt[$iCtr]
Write($sTmp,1)
ENDIF
NEXT

During script execution, I log errors and information. Then, at the end of the script, I do a summary of the error codes set during script execution:

code:
  
Write('Exit code: ' + $iExCode,1)
Write('This can be decoded as follows...',1)
FOR $iCtr = 0 to 30
$Exp = Exp(2,$iCtr)
IF $iExCode & $Exp
Write($aMsgCat[$iCtr] + '- ' + $Exp + ' ' + $aMsgTxt[$iCtr],1)
ENDIF
NEXT
ELSE
Write('INF - TGLOGON.KIX completed with no significant actions or errors.',1)
ENDIF

Similarly, my UDF's have bitwise error codes. If the UDF is more complex, I might set a single script-level error code by evaluating one or more UDF error codes returned in @error.

Finally, always returning an array in order to include error codes and information seems excessive and cumbersome to me. I liked the comments about VBA vs. the old Excel macro language. VBA is MUCH more powerful... so almost no one uses it. [Wink]

In this case, we are in a gray area, as most of the people writing KiXtart script are assumed to have some level of proficiency with KiXtart. But I'd still try to apply the following rules to a returned value:

1. Return nothing (since a UDF acts as both a function and a procedure in KiXtart) or a single value wherever possible.
2. Return it in the format expected. (I.e. I would expect Exp(x,y) to return a number, not an array of strings)
3. Keep the default operation of the function as simple as possible. By putting errors in the error value of the function, the user has the option of evaluating that error code or not. Forcing the user to deal with this every time is not necessarily a kindness.

My .02.

Thanks,

New Mexico Mark


Jack Lothian
(MM club member)
2002-04-16 11:44 PM
Re: UDF Design

Hi Shawn,

In real life, I am a mathematician. I took up this "hobby" because the local middle school had buckets of money invested in computers but none invested in people. Nothing worked, not even the new equipment. I and 3 other parents decided to build a system for the school. We finished our work about 2 years ago.

For the last 2 years, I have been out of it, but the school is looking into putting in Win2000 & we were asked to help in the upgrade. So, I was just snooping around & I couldn't help it, I had to open my mouth. I am not sure if my return to the board is permanent or not. It depends on a lot of factors at the school. For now, I am just trying to get current.

I am impressed with this board. All the old folks are still here plus thousands of new ones. (With the exception of Davidson - where is he? I would miss him since he was one of the few school related people that I encountered regularly on this site.)

Howard,

Sorry - I think these ramblings might be off topic but your intro does say discussions. I took liberties.


Howard Bullock
(KiX Supporter)
2002-04-17 01:02 AM
Re: UDF Design

Jack, No problem. [Smile]

Mark,
quote:
(I.e. I would expect Exp(x,y) to return a number, not an array of strings)
That does not have to be the case. Try this code:
{edit} By no means is this suppose to be a valid EXP function.
code:
function EXP($x,$y)
dim $x, $y, $z
? "X = " + $x + " of type " + vartypename($x)
? "Y = " + $y + " of type " + vartypename($y)
$z = $x
for $i=2 to $y
$z = $z * $x
next
$err = @Error
$errtext = @Serror
? "Z = " + $z + " of type " + vartypename($z)
$EXP = $z, $err, $errtext
endfunction
$result = EXP(2,3)
? ? "Results:"
for $i=0 to Ubound($result)
? "element $i = '" + $result[$i] + "' is type " + vartypename($result[$i])
next



[ 17 April 2002, 01:04: Message edited by: Howard Bullock ]


NTDOCAdministrator
(KiX Master)
2002-04-17 08:58 AM
Re: UDF Design

In my opinion I don't want or need error codes for UDFs. I want or assume that the code is solid enough to work on 99% of all of my systems from the get go. If I only have 5 machines out of 5,000 having a problem with the code, I would blame those 5 machines and not the code. If on the other hand I am using the error code as a part of the decision process of the script, then it may be a different matter.

I would only want or need error codes typically to help me track down why my UDF is not working, or for debugging someone else’s code.

I don't disagree with the idea of "advanced" programmers wanting a "standard". I agree that one should be used by those so inclined, as it can be helpful. However, to attempt to coerce or indicate that a UDF is not valid or not good because it either does not have error routines built-in or because it does not conform to a "standard" is also wrong in my opinion. KiXtart is a logon script first and foremost. Yes, we can and do have it do so much more now days... but a simple/complex UDF that is written well and performs well should not need to be debugged by other members that download the code and use it.

At the risk of sounding off base, (in my opinion again - from most of the posting I see, I would have to say there "appear" to be less then a dozen "regularly posting" members that I would put in this ADVANCED PROGRAMMER category...of which I do not include myself). I would say that the vast majority of us are at varying degrees of understanding "advanced" code samples, and we USE it more so then CREATE it.

Howard, if I'm missing the boat here please let me know. Maybe you're meaning or wanting something else based on your original question then what I'm replying with. It's late and maybe I'm just not reading the thread and request properly.


Richard H.Administrator
(KiX Supporter)
2002-04-17 10:45 AM
Re: UDF Design

quote:
It's not about enforcing a method but rather producing quidelines for a standard way of doing it if you want to
{EDIT} Pre-coffee rant deleted {/EDIT}

The ideal solution would be if a function returned an object whose properties are (say):
quote:
$oResult.ERROR
$oResult.SERROR
$oResult.Value

If the default property/method (I'm not that familiar with objects) is the $oResult.Value, then the functions would continue to work as they currently do, i.e.
code:
Function udfSetString()
$udfSetString="A String"
EndFunction
;
; Call function
udfSetString() ?

When called the function will return "A String" as you'd expect.
However if you wanted you could use the other properties:
code:
Function udfDivide($iDividend,$iDivisor)
If Val($iDivisor)=0
$udfDivide.ERROR=1
$udfDivide.SERROR="Whoa! caught a divide-by-zero error in udfDivide()"
Else
$udfDivide=Val($iDividend) / Val($iDivisor)
EndIf
Exit $udfDivide.ERROR
EndFunction

This way if someone else uses your function and doesn't care about the extra information they can still assign the return value and get the expected "natural" result.

[ 17 April 2002, 11:13: Message edited by: Richard Howarth ]


Will Hetrick
(Hey THIS is FUN)
2002-04-18 12:55 AM
Re: UDF Design

As I was reading this Discussion and where it was heading, I decided to input some thought.

As a former School teacher, I understand the notion that its better to teach someone to fish and they will eat for a lifetime.

Now with that out of my head, The thought of adding a return code to the UDF other than the microsoft simple error code is a good idea from the programmers point of view so that he can debug it, but How many people come on to this board as programmers?

Most of the people I see that want to use the UDF's and who we make the UDF's available for are the ones who can't program, need a quick fix for a problem and do not know what to do with the error codes to begin with.

Why make it harder for the people we are trying to help make it easier for in the first place?

[ 17 April 2002, 12:56: Message edited by: Will Hetrick ]


New Mexico Mark
(Hey THIS is FUN)
2002-04-17 07:43 PM
Re: UDF Design

Howard:

That was an interesting example. I'm surprised it works... I guess this is an example of an array of variants? Either that or an array can contain mixed types. I wouldn't have expected that, but then again, I haven't dug into the internals of KiXtart nearly as much as a few others here. Some of these folks probably get coding projects handed off to them on the sly by Ruud. [Wink]

Yet, the problem still remains that if I expect (for example) an integer to be returned and I get an array, it adds to the complexity of how I deal with the result. Admitted, this is a trivial addition, and you may find the added information returned to be very useful. However, it is non-intuitive and could be problematic to those not used to working with arrays.

Richard's idea of object properties is very intriguing. I like it, but have no idea how that could be made to work.

As for those arguing 'simpler is always better', I would have to disagree. Often external simplicity and power is at the cost of great internal complexity. So long as the end user doesn't have to understand the internal complexity, and that complexity doesn't cost too much in performance / resources, I'm for it. A GUI is incredibly complex, but my four-year-old is able to open programs, enter data (her name), and play games involving selection and manipulation of on-screen objects after only minimal instruction. Behind that 'simplicity' are millions of lines of code and many, many standards of coding and inter-process communication.

Programming is 10 percent core logic and 90 percent interface logic. Obviously, this isn't nearly so much of an issue with UDF's, but I appreciate the following 'best practices' in some of the coolest UDF's I've seen:

1. They are pretty much unbreakable. They prevent or handle internal errors to the greatest extent possible. They still return a value of the type expected if they break -- thus helping prevent type mismatches -- but the value is zero, empty, or null. If the values supplied should never return this data, the data itself can act as the error. Otherwise, the function should set an error number on exit to be tested.

2. They are independent. All internal variables are declared so they will not be confused with global variables. (Side note -- Howard, I notice you tend to 'Dim' your argument variables as well. Unless I'm misunderstanding the process, this happens automatically simply because they are arguments.) Also, files are opened and closed in a way that they can't interfere with file numbers in the calling script.

3. They return what I intuitively expect. This way I don't have to dig through the code to see what is going on. In other words, most numeric functions returns a number. Most string functions return a string. There are obvious exceptions, of course. I would expect instr() to return a number. But I would be very surprised if it returned an array!

4. They are well documented and tested. Even better if extensive testing code is available along with expected results so I can verify those results on my particular combination of HW/OS/KiX versions before implementing the function.

5. They are readable so I can customize, if so desired. (This is strictly a personal preference. Most people probably only want to use a UDF, not tinker with it.)

I don't give a rip about 'function police' [motto - "Badges? We don' need no steenking badges!"] , and I don't think that is where this is going.

However, I welcome ideas and feedback on how to write better, cleaner, more robust code. And the more the most active KiXtart developers can agree on 'best practices', the more the entire KiXtart community benefits -- not only from better scripts and functions, but also from the respect of other scripting communities.

Cheers from a crazy idealist,

New Mexico Mark


Howard Bullock
(KiX Supporter)
2002-04-17 08:11 PM
Re: UDF Design

Mark, I just used "dim" out of habit trying to keep local vars local. I did check and verified that you are correct parameters are local to the function and do not need to be "dim"'ed. Thanks for educating me.

Your comments on the file handles are a different matter as I interpret your statment. File handles do appear to be global. the following test code illustrates the point.
code:
function writelog ($x)
$rc = open (1, "Junk2.txt", 5)
? "function open: $rc"
$rc = writeline (1, $x)
$rc = close(1)
? "function close: $rc"
endfunction

IF Open(1, "junk.txt", 5) = 0
? "File handle 1 opened to junk.txt"
endif
writelog ("stuff for junk2")
$rc=writeline (1, "more stuff")
$rc = close(1)
? "Script close: $rc"
exit

I like Richard's object oriented ideas as well.
Everyone so far has made valuable contributions to this discussion. It may be that the best thing I learn here is to provide code in format and function that can be useful and similiar to the exisiting base of UDF's out there.

I think that for simple UDF's I will return a single value and set @error while attempting to prevent any fatal script errors with minimal code.

I would still like to here more opinions from those that want to post them.


BrianTX
(Korg Regular)
2002-04-17 08:46 PM
Re: UDF Design

A lot depends on the complexity of UDFs we are talking about. If I were to write a UDF that performed a square root function to a given number of decimal places, the only possible errors would be that:

1. My UDF couldn't handle a number that large.
2. My initial number input is negative.

As such, I don't really see the need for a complex array to report errors. The best practice is to keep things simple so that the person using the UDF doesn't have to do a lot of extra programming to handle errors.

If I were to create a more complex UDF that were to open one file, and find all the instances of words from another file, and output their line numbers to a third file, I can see there being many more possibilities for errors. So, there might be a use for more descriptive error codes in such a case -- provided I am using someone else's UDF. If I wrote it myself, then I can customize my own error code system to suit my own needs.

I'm not sure how it would be possible to take both scenarios and mesh them together in one "standard" system of error coding. Some disadvantages for doing so are: (I know I'm repeating what has been said)

1. Making simple, straightforward UDF's harder to use.

2. Lengthening code unecessarily.

On the other hand, some UDFs will have enough complexity to warrant extended error codes. In that situation, what standard should be used?

I believe that it would be simplest to just return errorcodes in the same manner that KIXtart returns them (Microsoft system error codes). This way prevents proprietary schemes competing that confuse everyone. Although using an array to return errors sounds like a good idea, in my opinion its benefits are outweighed by increased complexity.

So, in summary, my thoughts are:

1. NO extended error codes for most UDFs
2. Use the Microsoft system error codes if extra codes are needed.
3. Do not use arrays as this increases complexity.

.... It's interesting how this seems fairly identical to the way KIXtart has been programmed.

Just my two cents...

Brian


Bryce
(KiX Supporter)
2002-04-17 09:38 PM
Re: UDF Design

Great stuff yall!

My input on returning an array from a UDF call.

I am guilty of this, several of my UDF's return an array.

In all cases the array that is returned is 100% pure data, and using an array seemed like the natural way to present the data being requested.

With the addition of the enhanced array tools in Kix 4.x, working with array's is much simpler.

take for example my GroupMembers() UDF.

This UDF returns an array, and the array size will always be unknown.

All groupmembers() really is, is the "(ADSI Group Object).members" interface wrapped up into an easy to use UDF with some simple filter capabilities.

The "(ADSI Group Object).members" object is an array of "(ASDI Groups Objects)" that the given user is a member of.

So in this case, returning an Array just seemed logical.

Bryce


ShawnAdministrator
(KiX Supporter)
2002-04-17 10:09 PM
Re: UDF Design

Bryce,

Your not guilty of anything ! Returning arrays of data is great stuff. Very usefull in fact - one can use them as collections in FOR EACH IN statements and count them with UBOUND.

Returning other things like extended error codes along with the data, i think, is not a good idea. In fact, it would make the UDF in-flexible in that it couldn't [easily] be plugged into collection-aware contructs.

-Shawn

[im thinking Howard wishes he never started this thread [Wink] ]

[ 17 April 2002, 22:10: Message edited by: Shawn ]


BrianTX
(Korg Regular)
2002-04-17 10:44 PM
Re: UDF Design

This brings me to some other thoughts/questions...

Should error codes extend functionality beyond that of basic error processing? How extensively should error trapping be used?

I've seen many pieces of code that almost exclusively use error codes to make branch decisions. This code can be very cumbersome to understand or debug unless the error codes are well known and standardized or labelled well throughout the whole program/function/macro/etc. This problem seems to be more frequent when the same variable or macro is used to return the error code each time, like @error.

This may be a bit off-topic, and perhaps displays my inexperience at programming, but wouldn't it be nice to have each error returned in a variable like:

Function: SayHello
ErrorDescription: $error.a.SayHello.b
Error: error.a.SayHello.b
{a} would be the line number or some other identification internal if it's a UDF.
{b) would be the line number of the function call. Obviously, "a" wouldn't be present for standard functions.

Perhaps this is a little too complex for what KIXtart is meant for, but most good error trapping systems give standard error codes AND line numbers.

Just something for everyone to chew on... If error trapping was like that, would it be easier or harder to use? It could still be evaluated just like @error. Would it make code simpler or more complex?

Brian


BrianTX
(Korg Regular)
2002-04-17 10:50 PM
Re: UDF Design

I messed up a bit on that.. was thinking too abstractly i suppose:

Function: SayHello
ErrorDescription: $error.a.SayHello.b
Error: error.a.SayHello.b
{a} would be the line number or some other identification internal if it's a UDF.
{b) would be the line number of the function call. Obviously, "a" wouldn't be present for standard functions.

That doesn't make sense. lol
This is better:

Function: SayHello
ErrorDescription: $Error.SayHello
Error: Error.SayHello
Line Number of error: LError.SayHello
Line Number of function call: FError.SayHello
...

Ok.. I think that works better. lol

Brian (I feel like a dummy now.)


Howard Bullock
(KiX Supporter)
2002-04-17 11:15 PM
Re: UDF Design

This thread is going great guns. [Smile]

Shawn, I have wondered how many worms are going to crawl out of the can opened by this thread but don't regret it one bit.

I know that I will be re-visiting some of my code after this duscussion dies down.


New Mexico Mark
(Hey THIS is FUN)
2002-04-17 11:34 PM
Re: UDF Design

On the file handles thing:

Unless Ruud sees fit to provide FileNumOpen() and / or FreeFileNum() functions, this will be an issue.

However, all is not lost. If a function needs file system access, there are ways to do this with KiX functionality like Redirect, SHELL, or with File System Objects, thus bypassing the file number limitations.

It is a pain to have to bypass KiXtart file numbers in a function, but with only ten choices, I have a pretty good chance of trying to use an already open file number and causing the function to fail.

Here is an example using KiXtart file numbers to avoid this:

code:
FUNCTION Unique(OPTIONAL $sDS)
; Create a unique-named file in a given (or current) directory. Supports up to
; 32767 unique files in one directory.
; Syntax: Unique(DirectorySpec)
; Returns: Directory and file spec of created file or empty string if no
; file was created.
; Error level returned is that of the Open() return value
DIM $RC, $iFN
SRnd(@MSECS)
IF $sDS = '' $sDS = @CURDIR ENDIF
IF SubStr($sDS,Len($sDS),1) <> '\' $sDS=$sDS + '\' ENDIF
IF GetFileAttr($sDS) & 16 ; If sDS is a directory
DO
$Unique=DecToHex(Rnd())
$Unique=$sDS+'~KIX'+Right('0000'+$Unique,4)+'.TMP'
UNTIL NOT Exist($Unique)
$iFN=1
$RC=Open($iFN,$Unique,1)
WHILE $RC = -3 AND $iFN <= 10
$iFN=$iFN+1 $RC=Open($iFN,$Unique,1)
LOOP
IF $RC
$Unique=''
ELSE
IF Close($iFN) $Unique='' ENDIF
ENDIF
ENDIF
EXIT $RC
ENDFUNCTION

Here is the same function, but using the file system object to avoid the problem entirely.
code:
FUNCTION Unique(OPTIONAL $sDS)
; Create a unique-named file in a given (or current) directory. Supports up to
; 32767 unique files in one directory.
; Syntax: Unique(DirectorySpec)
; Returns: Directory and file spec of created file or empty string if no
; file was created.
; Error level returned is that of CreateObject() or OpenTextFile()
DIM $RC, $iFN
SRnd(@MSECS)
IF $sDS = '' $sDS = @CURDIR ENDIF
IF SubStr($sDS,Len($sDS),1) <> '\' $sDS=$sDS + '\' ENDIF
IF GetFileAttr($sDS) & 16 ; If sDS is a directory
DO
$Unique=DecToHex(Rnd())
$Unique=$sDS+'~KIX'+Right('0000'+$Unique,4)+'.TMP'
UNTIL NOT Exist($Unique)
$iFN=1
$RC=Open($iFN,$Unique,1)
WHILE $RC = -3 AND $iFN <= 10
$iFN=$iFN+1 $RC=Open($iFN,$Unique,1)
LOOP
$oFS = CreateObject("Scripting.FileSystemObject")
IF @ERROR EXIT @ERROR ENDIF
$oFileOpen = $oFS.OpenTextFile($Unique,2,1,0)
$RC=@ERROR
ENDIF

New Mexico Mark


Richard H.Administrator
(KiX Supporter)
2002-04-18 09:21 AM
Re: UDF Design

I'll pick up a few things based on the "object" return I proposed earlier.

If there are any object aware gurus listening, please
  • Shoot this idea in the head if it is simply not possible
  • Come up with a working object to demonstrate it if it is, heh [Wink]
BrianTX
quote:
I'm not sure how it would be possible to take both scenarios and mesh them together in one "standard" system of error coding. Some disadvantages for doing so are: (I know I'm repeating what has been said)

1. Making simple, straightforward UDF's harder to use.

2. Lengthening code unecessarily.

The point of meshing them using the object actually addresses both of these issues. If you use the return value as you currently do it will continue to work regardless of how it was assigned in the UDF. This means that there is no change in how you use the UDF. Use it the simple way or the advanced way, both will work transparently.
This of course also answers your second point. You can have the "extended" code both in your calling script and in the UDF, in just one of them, or in neither of them. There is no more code unless you specifically want to include it.

Shawn
quote:
Returning other things like extended error codes along with the data, i think, is not a good idea. In fact, it would make the UDF in-flexible in that it couldn't [easily] be plugged into collection-aware contructs
Yeah, this is what started me thinking about abstracting the return value using objects. The well constructed object would accept an array as the value assignment, so:
code:
$udfReturn=$avMyDataArray

would continue to work, and the dataset would be kept clean.


Howard Bullock
(KiX Supporter)
2002-04-18 01:29 PM
Re: UDF Design

Hey Mark, a little like to WriteLog2(). [Wink] As long as you do not keep opening file handles and keeping them open for the duration of your script should be able to avoid the the limitation

Bryce
(KiX Supporter)
2002-04-18 02:51 PM
Re: UDF Design

how about the ability to set the exit error code like this.

code:
seterror("1 2 56")

for $i = 0 to ubound(@error)

? @error[$i]
? @serror[$i]

next


function seterror($error)
$error = split($error," ")
exit($error)
endfunction

Bryce


Howard Bullock
(KiX Supporter)
2002-04-18 03:09 PM
Re: UDF Design

Hey guys, I have to apologize for stirring up this hornets nest. The example that cause the origination of thread can correctly return the COM error text in @serror if I exit(@error) immediately at the point or error. Originally I was storing @error in a variable and permitted other lines to execute which reset @serror. I then went down the path of string both @error and @serror in variable to return in an array. [Embarrassed]

I do believe though that my programmatic shortcoming has opened a great discussion and potential new paths for KiXtart programming. I just wish that my initial motivation had a deeper philosophical base than it did. [Frown]


Richard H.Administrator
(KiX Supporter)
2002-04-18 04:08 PM
Re: UDF Design

Howard,
No apologies required. This type of thread is how some of the neatest KiXtart tricks have evolved.

In fact the SetError() trick came about because a very similar thread way back in May 2001. Anyone else remember this thread?

KiXcrypt only happened because of a long thread about securing scripts.

The KiXtart forums are great places for brain-storming ideas, and you never know when someone will surprise you with a simple way of achieving something you thought was impossible.

And personally I find these tricky "how can I" threads entertaining and educational. It sure beats working.


BrianTX
(Korg Regular)
2002-04-18 04:10 PM
Re: UDF Design

So, are you trying to close off the discussion? lol

Maybe we should be requesting that KIXtart have an optional error queue that will add functionality to the @error and @serror macros.

Maybe like this:

First activate:

ERRORTRAP=ON

Then have additional error functions that had a queue of errors:

$error1 = ERROR[a]
$lineerror1 = LERROR[a]
$error1string = SERROR[a]
[a] represents the nth @error code returned.

$error1 would return the errorcode.
$lineerror1 would return the line number of or function generating the errorcode.
$error1string would return text of the error.

In addition, have a command called:

ERRORLIST that would return a list of return codes and functions in column form:

errornum linenum textmessage
0 1
0 2
1 3 This is an error 1 message

.....
Yeah, this is totally off topic, but wouldn't it be cool? Maybe i should drop this in the suggestion box.

Brian


Howard Bullock
(KiX Supporter)
2002-04-18 04:18 PM
Re: UDF Design

This topic is a juggernaut. I don't know if I could close it if I tried. [Smile]

ShawnAdministrator
(KiX Supporter)
2002-04-23 06:41 PM
Re: UDF Design

Guys,

Just wanted to resurect this juggernaut thread because I think its important.

Richard,

imho you are spot on in terms of how com objects should return error codes. but there is no need to write a proof-of-concept object because your ideas are already being implemented. take for example MS ADO objects. correct me if im wrong but if one does a recordset query against a database - and the SQL is bad - one will still get a recordset object returned (returns an expected object) but an error will be flagged internally. to get at the error, you query an error object that has all the extended error information.

I guess to implement something similar in Kixtart, one could declare a global $ERROR variable in ones UDF library. then for example, if a function that is supposed to return a string fails, it will still return a string, but would set @ERROR to something meaningfull. then the person using the UDF could get the extended error info either directly with the global var, or through a library return-last-error type function, like the Windows GetLastError().

And lastly to my mind, I think Mark laid-down some darn good best-pratices for UDFs. Here they are again:

1. They are pretty much unbreakable. They prevent or handle internal errors to the greatest extent possible. They still return a value of the type expected if they break -- thus helping prevent type mismatches -- but the value is zero, empty, or null. If the values supplied should never return this data, the data itself can act as the error. Otherwise, the function should set an error number on exit to be tested.

2. They are independent. All internal variables are declared so they will not be confused with global variables. (Side note -- Howard, I notice you tend to 'Dim' your argument variables as well. Unless I'm misunderstanding the process, this happens automatically simply because they are arguments.) Also, files are opened and closed in a way that they can't interfere with file numbers in the calling script.

3. They return what I intuitively expect. This way I don't have to dig through the code to see what is going on. In other words, most numeric functions returns a number. Most string functions return a string. There are obvious exceptions, of course. I would expect instr() to return a number. But I would be very surprised if it returned an array!

4. They are well documented and tested. Even better if extensive testing code is available along with expected results so I can verify those results on my particular combination of HW/OS/KiX versions before implementing the function.

5. They are readable so I can customize, if so desired. (This is strictly a personal preference. Most people probably only want to use a UDF, not tinker with it.)

I would also add some words in terms of using (what ill call) global script resources. The only one I can think of off-hand are file handles. Might propose that main scripts use file handles starting at the number 1 and working upwards. And that UDF`s use file handles starting at number 10 and work downwards.

-Shawn


New Mexico Mark
(Hey THIS is FUN)
2002-04-23 09:35 PM
Re: UDF Design

I removed the personal comments, refined the language a little, and added Shawn's comments. If this meets general approval, maybe it can go in the FAQ area.

KiXtart UDF "Ten Suggestions"

1. A function should be pretty much unbreakable. It should prevent or gracefully handle internal errors to the greatest extent possible. An end user should never have to waste their time troubleshooting a poorly written UDF.

2. A function should still return a value of the type expected even if it cannot finish normally -- thus helping prevent type mismatches -- but the value should be zero, empty, or null. If the values supplied should never be zero, empty, or null (i.e. MyBodyTempNowCelcius() should never return a zero under normal circumstances), the data itself can act as the error. Otherwise, the function should set an error number on exit for testing from the calling script.

3. A function should be independent. All internal variables should be declared to prevent confusion / conflict with global variables. (Variables in function arguments are automatically dimensioned by KiXtart.)

4. Global resources should be used with great caution. KiXtart settings changed within the function need to be reset to their previous state before exiting the function. Files opened by a function should be opened in a way that they won't interfere with file numbers in the calling script. One possible way to handle (no pun intended) file numbers when using the KiXtart open() function is to reserve file numbers 1-5 for main script files handles and 10-6 for UDF file handles.

5. A function should return what the average user would intuitively expect. In other words, most numeric functions return a number. Most string functions return a string. There are obvious exceptions, of course. Instr() *should* return a number. But it would be odd if that function returned an array.

6. Try to make the arguments and return types of functions as intuitive as possible. Ideally, any end user should be able to copy a UDF into any script and expect it to work with no modifications and without having to dig into the code to figure out why it returns a value of more than one type or works right when MyFunc(10) is supplied, but breaks when MyFunc('10') is supplied.

7. UDF's should be well documented and tested. Known limitations of the function should be documented. For instance, it may be OK if a function like Divide(A,B) can not return a correct value when B=0, but this should be documented. A real plus is to include extensive testing code along with expected results so the end user can verify the results on their particular combination of HW/OS/KiX versions before implementing the function. Ideally, include abbreviated documentation within the function itself to briefly describe general syntax, expected arguments, returned value/type, error handling, and point of contact for questions/problems.

8. A function should be readable enough for the end user to customize it, if so desired. (This is strictly a personal preference. Most people probably only want to use a UDF, not tinker with it.)

9. Function names should be as intuitive as possible. ReadProfileString() or GetFileSize() is preferable to mydatnumfunc(). (Note the action-object-type structure of most function names.) If you are aware of another UDF with the name you prefer, be graceful and give yours a different name to avoid confusion. Be cautious with function names you anticipate may be added to KiXtart as internal functions supercede UDF functions of the same name.

10. The KiXtart community is highly libertarian in coding practices. The previous suggestions are just that -- suggestions. Please do not use these suggestions to harrass, indimidate, or bully anyone writing KiXtart code, no matter how bad that code is or how much it makes you whimper when trying to decipher it. Instead, encourage one another. Make suggestions (like these). Show someone a better or faster way to do something. We will all benefit.

finis

[ 24 April 2002, 16:36: Message edited by: New Mexico Mark ]


ShawnAdministrator
(KiX Supporter)
2002-04-23 10:49 PM
Re: UDF Design

I'll just add another 2 cents. Function naming conventions can go a long way in making a UDF easy and intuitive to use. This may go without saying, but sometimes its helpful to embed a variable type name within ones UDF name, for example:

ReadProfileString - returns a string
CreateObject - returns an object
SortArray - sorts an array and returns an array

And sometimes certain keywords conventions can be used as a tipoff as to what the function does and what it returns.

InGroup - the keyword "In" usually denotes that this function returns a boolean.

IsUpper - same as "In" - returns a boolean

AsLower - "As" meaning that this function transforms the input parameter or returns a variable of a different type (eg, AsNumber("3"))

GetFileSize - "Size" meaning that this function returns a number

I`ve always liked the old "action-object-type" naming convention for functions, you know, function names that start with an action (get,is,check,read) then an object name (File,String,Profile) then optionally, an attribute of that object (Size,Handle,Upper), for example:

GetFileHandle()
IsStringUpper()
ReadProfileString()

Anyway, im just rambling again, anyone have anything to add about function naming conventions ?

-Shawn

[ 23 April 2002, 22:52: Message edited by: Shawn ]


New Mexico Mark
(Hey THIS is FUN)
2002-04-24 04:32 PM
Re: UDF Design

Good point. I'd never seen the action-object-type thing formally presented before, but it makes sense. I'll just edit the previous post to include this suggestion.

NMM

[ 24 April 2002, 16:34: Message edited by: New Mexico Mark ]


**DONOTDELETE**
(Lurker)
2002-06-07 12:40 AM
Re: UDF Design

Just saying Hi to Jack and all, I am about I just don't have the spare time I used to [Smile] My head hurts after reading this topic though :lol

Jack Lothian
(MM club member)
2002-06-06 03:32 PM
Re: UDF Design

Hi Don [Smile]