polterguy / phosphorusfive

A Full Stack RAD Web Application Development Framework
https://gaiasoul.com
GNU General Public License v3.0
178 stars 36 forks source link

ActiveEvents' Input / Output Documentation #9

Closed adebisi-fa closed 7 years ago

adebisi-fa commented 7 years ago

Hi @polterguy,

Really appreciate the initiative you took with p5, most importantly your courage to challenge the status quo (much more like provocation, though)!

A quick one, please:

Since ActiveEvents do not have interfaces (which allows it to reduce dependencies/coupling and increase cohesion), what suggestion do you have with respect to documenting the Inputs/Outputs of ActiveEvents in an ActiveEvent-based system?

This is even more important, as inputs and outputs are ordinary sub-nodes of an instance of type Node, with "string" names and "object" values.

Thanks.

polterguy commented 7 years ago

I have been thinking quite a lot about this myself. Thank you for asking a very good question :)

1. Auto generating dox from comments

The first obvious candidate, which only works for CLR Active Events, meaning those written in C#/VB.NET/F# etc - Is to use some sort of automated tool, to extract the comments. I have often used Doxygen myself for doing such things.

This probably wouldn't work for Hyperlambda Active Events, created with e.g. [create-event], at least not without making some effort for allowing Doxygen to parse Hyperlambda semantically, which probably would be quite a substantial job unfortunately ...

2. Literate events

Another approach, which I have considered myself, which would also work for Active Events written in Hyperlambda, is to create a "hidden argument" on your Active Events, to make the system become what Donald Knuth refers to as "literate". Such a variable could be called e.g. [inspect], and if it is passed into the event, the event will actually not execute, but rather return its own documentation, in a structured way, returning also example invocation, with example input, and example output. The problem with that approach, is that none of the core Active Events does currently support this, although this is something I have thought about implementing.

3. Documentation events

A third approach, which lends from the second approach above, is to create another Active Event, with a similar name as the one you are documenting, except you prepend or append something like e.g. "inspect" after it. E.g., if you have an Active Event called [foo.bar], you could create another called [foo.bar.inspect], which would return the documentation for it, and example input/output. This has the disadvantage of "polluting" your "namespace" with a lot of extra Active Events, which would make it more difficult to actually find stuff, using intellisense and such, in e.g. the "Apps/executor" thing, etc.

4. Common documentation event

A fourth approach, which lends from the third above, is to create your own "global Active Event" handler, which probably would be the most neat solution, which handles all Active Events. This could be done with something such as the following

// Notice the empty string
[ActiveEvent (Name = ""]
private void inspect (ApplicationContext context, ActiveEventArgs e)
{
    if (e.Args ["inspect"] != null) {
        // Return the documentation for e.Name, 
        // which is the Active Event being raised.
    }
}

The last approach has the advantage of not "polluting" the global namespace, or Active Event list, while also adding zero overhead to the runtime in any ways, during deployment, since the project that incorporate the above class, could be isolated into its own assembly, which could be simply removed when you deploy the project into a live production environment.

This is actually how raising Hyperlambda Active Events is implemented, which you can see here

Basically, when you create an Active Event in Hyperlambda, by using [create-event] (which you can see further up in that same file) - It will store your lambda object into a dictionary. Later, when you raise any Active Event (due to the empty Active Event name in the "_p5_core_null_active_event") - It will do a lookup into this dictionary, to see if that event exists as a "custom Active Event", and if it does, it will "eval" its lambda object.

If you start out with a copy of this logic, creating something similar for documenting your events, expecting the event to either end with/start with the word "inspect" for instance, or have the argument of "inspect", or something possibly even more unique, and if "inspect" is given, you return the documentation, which could mean loading some Hyperlambda file containing all dox for one or more projects, and returning the node matching your event name to caller.

Unfortunately, if you expect the argument called "inspect", this would still also execute the original Active Event - If you either prepend or append "inspect" after its name though, this would not occur, and only the documentation would be returned, and the original Active Event would never be executed.

So my suggestion is to go with the latter, something like the following.

// Notice the empty string
[ActiveEvent (Name = ""]
private void inspect (ApplicationContext context, ActiveEventArgs e)
{
    if (e.Name.StartsWith ("inspect.")) { // Or "dox", or "whatever" really
        // Return the documentation for e.Name, which is the Active Event being raised.
        // This could easily be achieved by loading a Hyperlambda file, 
        // containing the documentation in a "structured format" for the event.
    }
}

The above would technically pollute the main namespace, but since it's something never expected to be put into deployment, but only development, it is probably not a big problem ...

If you go with a "literate" solution, meaning "auto-dox", I would definitely choose the last from above.

5. The obvious solution

The one I have used so far though, although especially the fourth example above is probably very cunning, intelligent and smart - Is to simply document my events like the following.

/*
 * This event does "foo".
 *
 * It takes the following arguments.
 * - [foo] being the name of etc, etc, etc
 * - [bar] being a boolean, if true, then something, else something-else
 * - [something] is optional, and must be a string if given
 *
 * Returns.
 * - [x] being something
 * - [y] being something else
 */
create-event:foo-bar
  // Do stuff with "foo", "bar" on optionally "something" ...

Then combine this with the first approach from above, using Doxygen or something similar for C#/VB.NET/F# events. It might not be as cool and cunning as all of the above examples, but works surprisingly well in practice, since anyone interested in finding the documentation for the event called [foo-bar] can simply do a "Search for all ..." - SHIFT-CTRL-F in Visual Studio through the entire project, with the text "create-event:foo-bar", and if not found, search for '"foo-bar"', at which point it will find the C#/VB.NET code.

It depends upon the size and nature of your project. If you have huge development teams, with many developers, creating humongously large projects - Then creating some sort of "automated literate" type of documentation system, might be beneficial, and outweigh the costs. However, for smaller projects and teams, I suspect the simplest is the best, and that KISS (Keep It Simple and Stupid) is probably enough, and that you should go with the 5th solution.

One thing which I have created for myself as a "convention" though, is to always refer to nodes using bold text in my markdown/HTML, and put them in square brackets, which helps my readers to see that this is a node. Example, imagine node "foo", then I'd normally write it out as [foo], to give visual clues that this is a node indeed. In my comments, I write them out simply with square brackets surrounding their names, such as the following [foo].

Puuh, a long answer, but I anticipate it'll probably be the most important FAQ item, since possibly a lot of people will wonder about the same thing ...

Hope this helps you out :)

.t

adebisi-fa commented 7 years ago

Hi, @polterguy!

I so very well appreciate the time and thoughts that went into putting those LONG and USEFUL details down. YES, IT REALLY DOES HELP!

A Swiss Army's knife kind of solution won't cut it definitely; hence, the need for the not LONG, but DETAILED response.

Thank you very much! I'll mix and match your suggestions, going with whatever would be suitable for specific projects.

Thanks once again for p5.

polterguy commented 7 years ago

Thx, no problem :)