Annotating Javascript

download (4)Anyone who has written code knows that documenting code is important. They also know that it is a pain in the butt, is difficult to do well, and in most cases the comments that are added inline to code are more along the lines of:

// I have more work I need to do here

than of anything that may actually prove useful to another coder.

JavaScript in particular is somewhat notorious for trying to code well, especially once you start dealing with nested callback statements and objects that contain blocks of functions that also have parametric callback functions.

The costs of poorly documented code are high. If you don’t know what a function is supposed to do, what it’s arguments mean, what it is supposed to produce or how it is most commonly used, then if something goes wrong it is often easier to rewrite functions or even whole libraries from scratch, which means that one of the big benefits of code – it’s reusability – goes out the window.

Moreover, there is all kinds of other metadata that it would be nice to have about functions. For instance, suppose that you are working on a project with someone else and both of you are in and out of a particular set of functions. It would be useful to leave a note would indicate who worked on which function last, when they did it, and what version they were working on.

Finally, one of the real benefits of documentation is not for the developers of the code, but the users. This means that if libraries of functions could be queried automatically in order to put together user readable documentation pages, then you both have a tool that would allow someone to document while everyone remembers what the role of the functions are, rather than towards the end of a project when memories are fuzzier and people are more likely to have left a project.

Annotating Your Code

This is the role of annotations. An annotation can be thought of as something that gets attached to a function or an object that is a collection of named functions that provides a certain amount of metadata about that function. Annotations were first introduced a few years back in the Java world as part of what was at the time called Aspect programming. JavaScript has no “native” annotation capabilities, but it turns out that it’s actually fairly easy to add such annotations with a comparatively minimal amount of code.

While there are several annotation libraries available, I chose, as an experiment, to roll one of my own, located here. It can be used in a number of ways, such as

<script src=”http://mysource.com/annotations.js”>/**/</script>

if used from within HTML, or

var an = require(“/path/to/annotations.js”)

if used within node.js, or

var an = require(“/path/to/annotations.sjs”)

if called from MarkLogic as a server side javascript function.

In all cases, this produces a single global annotation object. The annotation object does not store any information about annotations – this is actually handled by the objects and functions themselves – but does serve to more readily access and manipulate this annotation information.

In its simplest case, you declare a function, then pass the function object itself as an argument to the annotation engine. For instance, suppose that you had a titleCase string function that took a string as input (a sentence with words separated by white space):

function titleCase(expr){
      var tokens = expr.split("\s+?");
      for (inde
x in tokens){
            var token = tokens[index];
            tokens[index] = token.substr(0,1).upperCase()+ token.substr(2).lowerCase();
            }
      return tokens.join(" ");
};

One salient point to notice. There is no immediate information about what expr should be or what the output is, so the only way that you can determine this information is to add it in as metadata. Once titleCase has been declared, then you can use annotations to do precisely this:

an.set(titleCase,"name","titleCase")
    .set(titleCase,"title","Title Case")
    .set(titleCase,"resultType","xs.string")
    .set(titleCase,"type","function")
.set(titleCase,"desc","This converts a string to title case, with the first letter \
of each word given as upper case and all subsequent letters lower case.")
    .set(titleCase,"params",[
          {name:"expr",type:"xs.string",desc:"This is the source string"}
          ]
    .set(titleCase,"usage","titleCase("this is a test.")\n\
=> "This Is A Test.");

What this does is assign to the titleCase function individual bits of metadata. The .set() function takes the function being annotated as an argument, along with the property name and value, places this on an annotation object attached to the function, then returns the annotation object, making it possible to chain multiple such statements.

Once these properties have been set, the Annotation get() function will retrieve an individual value:

an.get(titleCase,”desc”)
=> “This converts a string to title case, with the first letter of each word given as upper case and all subsequent letters lower case.”

You can also retrieve the keys used to set properties to these functions, check to see if a function has an annotation property, and remove properties:

an.set(titleCase,"newProperty","oops").
    .hasKey(titleCase,"newProperty");
=> true
an.keys(titleCase);
=> ["name","title","desc","resultType","params","desc","usage","newProperty"]
an.remove(titleCase,"newProperty")
    .hasKey(titleCase","newProperty")
=> false

Increasingly, most modern JavaScript has moved beyond creating functions in a global space. Instead, what is more common are objects that hold collections of functions, with each function in turn being bound to a particular hash tag. For instance, you may have a number of related string utility functions:

var StrUtil = {
      titleCase:function(expr){...},
      camelCase:function(expr){...},
      wordCount:function(expr){...},
      hyphenateJoin:function(expr,hyphenChar){...}
};

The same technique can be used to describe not only the individual functions, but also the enclosing object:

var strUtil = StrUtil;
an.set(strUtil,"name","StrUtil")
    .set(strUtil,"title","String Utility")
    .set(strUtil,"type","object")
    .set(strUtil,"desc","This is a collection of string utility functions")
    .set(strUtil,"methods",["titleCase","camelCase","wordCount","hypenateJoin"]);

an.set(strUtil.titleCase,"name","titleCase")
    .set(strUtil.titleCase,"title","Title Case")
    .set(strUtil.titleCase,"resultType","xs.string")
    .set(strUtil.titleCase,"type","function")
    .set(strUtil.titleCase,"desc","This converts a string to title case, with the first letter \
of each word given as upper case and all subsequent letters lower case.")
    .set(strUtil.titleCase,"params",[
          {name:"expr",type:"xs.string",desc:"This is the source string"}
          ]
    .set(strUtil.titleCase,"usage","titleCase("this is a test.")\n\
=> "This Is A Test.");

an.set(strUtil.camelCase,”name”,”titleCase”)
    .set(strUtil.titleCase,”title”,”Title Case”)
….;

Of these, the only required entries are the type and methods properties, which identify the public methods. Note that these can also be determined automatically, but that assumes that all methods within an object are public. This would normally be handled within an init() function of some sort.

One additional point is worth noting in this API. One of the principal reasons for going to the effort is to create a data structure that represents this data in a programmatic way. The two function toJSON and fromJSON are used on functions themselves to generate JSON data that represents the bundled metadata – the first to save all of the collected metadata into a structure that can be used to build documentation, form controls,, the second to load this json back into the functions and hence configure additional processing instructions.

What Annotations Give You

Annotations may seem a bit weird – they are metadata about functions. and modules (objects of functions). At their most basic level, they allow these modules to essentially describe themselves and provide a straightforward means of building documentation that is focused on the end user, rather than the developer. However, they can also serve other purposes as well.

One frequent problem that developers run into comes in logging errors. A common technique for logging is to set up some kind of global debugging flag and then have functions use this flag to indicate whether they should log errors or not. However, this can frequently mean the difference between getting no logging information and getting too much.

Suppose, for instance, that in the initial StrUtils module from above, the function is redefined slightly:

var StringUtils = {
titleCase:function(expr){
     var tokens = expr.split("\s+?");
if (an.get(this.titleCase,"log")){
console.log(tokens);
}
      for (index in tokens){
           var token = tokens[index];
tokens[index] = token.substr(0,1).upperCase()+ token.substr(2).lowerCase();
       }
     return tokens.join(" ");
},
..};

an.set(StringUtils.titleCase,"log",false);

This would normally cause the logging function to be disabled, because the value of the “log” annotation for the function has been set to false. However, when this function is imported, you could in fact turn on the logging just for that function:

var strUtils = require("StringUtils);
an.set(strUtils.titleCase,"log",true);
...
strUtils.titleCase("this is a test.");
..
an.set(strUtils.titleCase,"log",false);

In this case, the titleCase() function will output its specific logging output without any other function being affected. What’s as important here is that the function becomes responsible for its own logging, without the user of that function needing to know internal details about how that function is set up. This is often preferable to the situation where a developer inserts their own logging information into a function, then forgets that it is there. (This can also be replaced with providing different levels of logging based upon some kind of flag – logLevel:critical, for instance, might only log critical errors, while more thorough logging might be done with logLevel:warning).

In a similar manner, personal privacy information (PPI) can insure that information is only visible to people with the right permissions:

var BankingRecord = {
userId:"";
create:function(userId){
   ...
},
accountNumber:function(){
     var acctnum = db.getRecord(this.userId).acctNum;
      return (an.get(this.accountNumber,"ppiDisabled"))?
            "XXXXXXXXX":acctNum;
       }
};

an.set(BankingRecord.accountNumber,"ppiDisabled",false);

In this case, ordinarily, when the account number is requested, you’ll end up seeing a mask:

var bankRec = BankingRecord.create(userId);
print(bankRec.accountNumber());
=> "XXXXXXXXX"

However, if the output

is generated for the user, then they should be able to see their own account:

var bankRec = BankingRecord.create(userId);
an.set(bankRec.accountNumber,"ppiDisabled",true)
print(bankRec.accountNumber());
an.set(bankRec.accountNumber,"ppiDisabled",true)

=> "124819258"

This can even be extended to the whole object:

var BankingRecord = {
accountNumber:function(){
     var acctnum = db.getRecord(this.userId).acctNum;
      return (an.get(this,"ppiDisabled"))?
            "XXXXXXXXX":acctNum;
       }
}

var bankRef = BankingRecord.create("12345");
an.set(bankRec,"ppiDisabled",true);

In this regard, annotations act like global variables but with finer grained control of scope. They can be used to change the behavior of functions in consistent ways and can reduce the complexity of functions (fewer parameters), and of course they can be used to add documentation to Javascript library modules.

Annotations.js contains a gist of the Annotations script. This can also be used in the MarkLogic service side javascript engine by changing the name to Annotation.sjs then using the require() statement to make it available:

var an = require(“/path/to/Annotations.sjs”)

Summary

There are other annotation libraries available, including gquental and congajs. These use similar logic and the same kind of aspect based programming that is becoming increasingly popular in many computer languages.

Mentioning MarkLogic, I should also point out the MarkLogic XQuery library also supports annotations, though they use a different syntax and processing layer. I’ll dig more deeply into these annotations in a subsequent post.

There is also another form of annotations based upon work with the W3C that could have a profound impact upon the way that we annotate web content. That too is grist for an upcoming post.

Comments, as always, are welcome.

Kurt Cagle is Principal Evangelist for Avalon Consulting, LLC

Kurt Cagle About Kurt Cagle

Kurt Cagle is the Principal Evangelist for Semantic Technology with Avalon Consulting, LLC, and has designed information strategies for Fortune 500 companies, universities and Federal and State Agencies. He is currently completing a book on HTML5 Scalable Vector Graphics for O'Reilly Media.

Leave a Comment

*