On Wed, Nov 30, 2016 at 10:41 AM, Kirk Brooks <[email protected]> wrote:
> I'm writing an update procedure for a database that uses Execute formula a
> lot. Which got me looking at the benefits of Execute Method. This also got
> me thinking about ways to make some methods I might write for users to
> invoke and how I would go about making a little form for controlling which
> methods they see (easy) to how I could manage configuring the input params
> - if any.
>
...and you can automate the whole thing. If you define these properties in
the code of the method itself, you can then:
* Scan the code.
* Extract the parameter types and attributes (allowed inputs, etc.)
* Prepare formatted comments, as you suggest. You can use JSON or whatever
text-based scheme is easiest for you.
* Set the code procedurally.
* Compile & build.
So how to define the inputs, etc. in the method itself? There are several
ways to go. The first one that comes to mind for most people is probably
structured comments:
C_TEXT($1) // Inputs:Any string
C_LONGINT($2) // Inputs:Any positive integer
Something like that. That's not a bad way to go but, if you do this, make
sure that your method scanner validates the syntax of the statements so
that you automatically detect poorly-formed definitions. Personally, I'm
not taking this routine. Instead, I want the parameter definitions to be
more like real promises/contracts. Here's a snippet to give you an idea of
what I'm talking about:
C_LONGINT($0;$root_id)
C_TEXT($1;$object_type_name) // Pass a valid object name. Use constants to
make this easy to do correctly.
C_REAL($2;$version_number) // Use version numbers in case you need to
switch parsers during upgrades.
C_TEXT($3;$instance_name) // A header field to store a specific
name/description of an individual instance.
C_POINTER($4;$error_name_pointer) // Pass if you want errors returned.
(Recommended.)
If (Parameter_BlockCodeFromRunning )
Parameter_DefineResult ("Root ID";Abstract Type Is Object)
End if
$root_id:=0
C_TEXT($error_name)
$error_name:=Parameter_DefineCount (Count parameters;2;4)
If ($error_name="")
$error_name:=Parameter_IsAnyTextButNone ($1;"Object type name")
End if
If ($error_name="")
$error_name:=Parameter_IsARealRange ($2;"Version number";0.1;MAXLONG)
End if
If (($error_name="") & (Count parameters>=3))
$error_name:=Parameter_IsAnyText ($3;"Instance name")
End if
If (($error_name="") & (Count parameters>=4))
$error_name:=Parameter_DefineErrorPointer ($4;"Error name
pointer";Parameter Value Is Optional)
End if
You'll notice free-form comments at the top. Those are helpful, but they're
just documentation. The thing about documentation? It breaks your heart.
With the best of intentions, it starts out right...but then the code drifts
away. Better is a promise enforced in code that is then used as the basis
for automatic documentation. That's what the Parameter_ calls to in the
snipped above.
Parameter_DefineResult
Defines $0.
The type is drawn from the C_TEXT declaration.
The comment is drawn from the C_TEXT declaration line but is only used for
human consumption.
The parameter's name is "Root ID".
The parameter's 'abstract' type is Object. (In this case, an NTK JSON
reference)
Paraemeter_DefineCount
Defines the minimum and maximum range of parameters. From this, you can
automatically document optional and required parameters. Notice that this
routine takes a runtime argument and potentially sets an error name:
$error_name:=Parameter_DefineCount (Count parameters;2;4)
If this method is called with 1 parameter, an error is set. Same for 5.
Anything but the range 2-4.
The other parameter tests are similar. They establish what range or series
of values is accepted, if the parameter accepts empty values if passed,
labels for each parameter and, in some cases, defines an 'abstract' type.
Abstract types in 4D aren't a thing...but you can head in that direction a
little bit on your own. So, for example, I think of "error name" as a
special type, not just a random string. Error name should be defined in a
catalog of error names and there are a bunch of properties associated with
each error name (likely source, severity level, etc.) An easier to
understand example might be "Method Name". It's supposed to be an actual
method name and, if not, that's an error. Useful for something like an ON
ERR CALL handler. If you know that the type is method name, you can
validate that generically...if it's just a dumb string, then any string is
fine.
Anyway, I just turned off straight into the weeds without any warmup...but
I've been working on this very subject quite a bit lately.
Anyway:
* 4D methods can be treated as a bunch of text.
* If you set up a code scanner, you can read through methods and extract
information.
* Structured comments can work perfectly fine for this and have zero
runtime overhead.
* Live tests let you get some benefits from the compiler (for example, you
define a text parameter as a longint series...you pass a longint where a
text is expected & 4D complains.)
* Live tests make your documentation honest.
* Live tests are likely to make your code more reliable. Not mine, I always
write perfect code. Sure I do.
Avoiding over-testing is another subject, but all of the above can both
help or hinder the effort. At the least, it's helpful to be thinking
through when/how you're testing inputs.
* P.S. Anyone following the snipped above closely might wonder about
"Parameter Value Is Optional", a self-explanatory custom constant. There's
a distinction between a required *parameter* and a required *value*. You
may not be required to pass a parameter but, if you do, are then expected
to provide a meaningful value. In other cases, you can pass a blank (0, "",
!00/00/0000!, etc.) value to a required or optional parameter.
**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ: http://lists.4d.com/faqnug.html
Archive: http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub: mailto:[email protected]
**********************************************************************