We already talked about how to make your forms and views easier to maintain. But in most Notes/Domino applications, a majority of the code is written in Lotusscript (at least for traditional applications, XPages are outside the scope of this discussion for now).
It is not hard to write easy to read Lotusscript code. Just follow some simple rules. As always, there are more than one way to do things, but this is how I do it (most of the time). I found that this works for me.
Variables
- Always use Option Declare (or Option Explicit) in all your code. This will prevent you from getting errors later when (not if!) you spell a variable wrong. Option Declare forces you to declare all variables before you use them. It also helps you by warning if a function or method is called with a variable of the wrong data type. Most of your bugs can be avoided using Option Declare. Use it!
- Give your variables meaningful names. There is no need to abbreviate variables and/or give them cryptical names, or you will just spell them wrong later. Use a naming system that makes sense to you. As I mentioned in my previous post, I avoid Hungarian notation, it just makes the variables hard to read. Since Lotusscript is a strongly typed language, the compiler is taking care of data types. I however do use some indicators for certain variables. For the few global variables I use, I do prefix them with g_, as I then can give local variables a similar name. By giving your variables meaningful names, the code also becomes largely self-documenting.
- Be consistent in how you capitalize your variables. I normally write all variables in lower case only, but that is my personal preference. That way they are easy to distinguish from built-in Lotusscript functions and properties of objects. Some programmers prefer some version of CamelCase, which is fine. I occasionally use that, especially in Javascript.
- Be consistent in how you name related variables. If you have a variable named startpos to indicate the beginning position of a string within another string, name the other variable endpos, not posend. Think through all variables you need so you give them good names. The variables posstart and posend are not as easy to read and understand as startpos and endpos. Of course, using CamelCase, posStart and posEnd are much easier to decypher.
- Use the same/similar naming convention for the Domino objects as IBM uses in the online help. Use ws, uidoc, session, db, doc, etc. If you need more than one object of any type, e.g. NotesDatabase, name them in a consistent way. I always use thisdb for the database the code is executing in, nabdb for names.nsf, archivedb for (you guessed it!) an archive database, etc.
- Declare the variables at the beginning of the code/subroutine/function. Don’t declare them throughout the code, this makes it much harder to find them later, when you perhaps need to see what data type they are.
- Declare the variables in a logical order. I always group the variables by type. First the UI classes, then backend classes, then any custom classes/data types, and finally other variables. Within each group, I declare them in the order I am using them. The benefit of this is that you get an idea of where to look for the variables in your function/subroutine.
- Use Option Declare. Yes, this needs to be repeated. Way too many (especially new) programmers forget this, or don’t know about this.
Here is a typical example of how I would declare variables in one of my applications:
Dim ws As New NotesUIWorkspace Dim uidoc As NotesUIDocument Dim session As New NotesSession Dim thisdb As NotesDatabase Dim nabdb As NotesDatabase Dim nabview As NotesView Dim persondoc As NotesDocument Dim doc As NotesDocument Dim username As String Dim phonenumber As String Dim employeenumber As Integer
As you can see, this function will do some kind of lookup in names.nsf and retrieve the name, phone number and employee number from the person document. It is then (probably) storing the values into the backend document of the currently open document. Just by declaring and naming variables in a logical way you can often figure out what the code will do further down.
If you declare variables that you end up not using, remove them. Since you now are a good programmer who is using Option Declare, you can very easily test if a variable is used by commenting it out. If you don’t get an error, you can remove it.
Comments
This brings us to the art of commenting. Yes, it is a little bit of an art, and I have seen way too much code written where the code is either overly commented (not very common), or hardly at all (very common). The art is to add just the right amount of comments.
There is an old saying among programmers: “if the code was hard to write, it should be hard to read”. But it may not be another programmer that have to look at the code and find a bug in it or update it with new functionality, it may very well be you. And if you have commented your code, you will not have to analyze every line of code, trying to figure out hwo you were thinking a few months or even years ago.
You want the comments to explay “why”, not “what” the goal of the code, not explain the individual lines of code. What I mean with that is that it does not make sense to have code like this:
'*** Set key variable to current user's name key = session.CommonUsername '*** Get the "People" view in database Set view = db.GetView("People") '*** Get person document in view based on key Set doc = view.GetDocumentByKey(key) '*** Get the email address from the field "InternetAddress" emailaddress = doc.GetItemValue("InternetAddress")
The comments in the example above does not add anything to the code. This code below is much better, and also shorter:
'*** Get email address for current user from person document key = session.CommonUsername Set view = db.GetView("People") Set doc = view.GetDocumentByKey(key) emailaddress = doc.GetItemValue("InternetAddress")
This comment explains what we actually are attempting to do. In real life, you would probably break out this code into a separate function with a descriptive name. The example above is just for illustrative purposes. I will cover functions in a future article.
Comments are crucial when you use some kind of work-around, or when your variables or field names have been named something that is not clear. I have cases where a field was initially named for the data it would hold, but later it was changed to something totally different. Make a note of that in your code. Or if you have to use some more or less obscure work-around to get the code to work, it would be very annoying if you (or another programmer) later removes that line. A typical example is to perform a db.FTSearch() with a query that will not return anything to get an empty document collection for later use.
I use the following system for comments:
- Comments explaining one line of code or a command are put on the same line, with no stars, just an apostrophe.
- Comments explaining a section of code are put on their own line, and I start them with three stars to make them more visible and make it clear they are comments and not code.
Remember, comments are there to help you remember (or tell someone else) what is happening in the code.
Bringing it together
Let’s look at a real life example. This one is taken from one of the IBM developerWorks forums, where a user had some issues attaching files to a rich text field. After getting some help, he posted the code he finally got to work:
Sub Initialize Dim s As New NotesSession Dim ws As New NotesUIWorkspace Dim uidoc As NotesUIDocument Dim filesys As String Dim FullPath As String Dim OUName As String Dim FileName As String Dim Path As string Dim db As NotesDatabase Dim view As NotesView Dim doc As NotesDocument Dim Object As NotesEmbeddedObject Dim IDFile As string Set uidoc = ws.CurrentDocument OUName = uidoc.FieldGetText( "OUName" ) FileName = uidoc.FieldGetText( "FileName" ) IDFile = uidoc.FieldGetText( "IDFile" ) Set doc = uidoc.document Call uidoc.Save Dim FullPath1 As Variant FullPath = doc.GetItemValue("FullPath")(0) Dim rtitem As New NotesRichTextItem (doc, "FileName") Set Object = rtitem.EmbedObject(EMBED_ATTACHMENT,"",IDFile,OUName) Call doc.Save(True,True) End Sub
As you can see, variables are declared all over the place, some declared but never used, inconsistent named, and of course no comments.
Here is my cleaned up version of the same code, with comments added as well:
Sub Initialize Dim ws As New NotesUIWorkspace Dim uidoc As NotesUIDocument Dim doc As NotesDocument Dim rtitem As NotesRichTextItem Dim filepath As String Dim filename As String '*** Save currenly open document Set uidoc = ws.CurrentDocument Call uidoc.Save ' Save UI doc so backend doc get updated '*** Get updated backend document and read field values Set doc = uidoc.document filepath = doc.GetItemValue("FullPath")(0) ' Path must end with / filename = doc.GetItemValue("FileName")(0) '*** Create a new rich text field called 'Attachment' and save doc '*** Use GetFirstItem() if the field already exists in document Set rtitem = New NotesRichTextItem(doc, "Attachment") Call rtitem.EmbedObject(EMBED_ATTACHMENT,"", filepath + filename) Call doc.Save(True,True) End Sub
I think this code is much easier to follow, cleaner and easier to maintain. This is the kind of code I would like to see if I came in to update or modify an existing Notes application. Wouldn’t you?
Update: Made some small changes and clarifications, and fixed some typos.
For commenting you should/could consider to make it consistent with LSDoc, so they can be useful beyond the source code
Before Code: {You want the comments to explay “why”, not “what”.}
After Code. {This comment explains what we actually are attempting to do.}
Sorry… ;)
Some developers would argue that it is better coding to dim the variables closer to the point they’re used which makes them easier to understand while sifting thru masses of code:
‘*** Get email address for current user from person document
Dim key as String
key = session.CommonUsername
Dim view as NotesView
Set view = db.GetView(“People”)
Dim Doc as NotesDocument
Set doc = view.GetDocumentByKey(key)
I agree with most what You said here, but I disagree with one thing: Commenting should be done only at rare occasions. Usually comments are redundant, unnecessary and/or just simply don’t add anything. For instance, in Your example:
‘*** Get email address for current user from person document
key = session.CommonUsername
Set view = db.GetView(“People”)
Set doc = view.GetDocumentByKey(key)
emailaddress = doc.GetItemValue(“InternetAddress”)
This comment should be removed and the code should be moved into its own function and the function name should be named so that You understand what the function does, and thus makes the comment redundant:
Public Function getEmailAddresForCurrentUser() As String
Set view = db.GetView(“People”)
Set doc = view.GetDocumentByKey(session.CommonUsername)
getEmailAddressForCurrentUser = doc.GetItemValue(“InternetAddress”)
End Function
A comment will make the code more cluttered and therefore actually make the code LESS readable.because the reader has to stop at every comment at think for a moment.
A comment should be present only when there is no other way to make the code readable, like if there is an outside dependency that isn’t clear in the code, or there is a logical assumption that the reader has to be aware of.
It is really a shame that LotusScript hasn’t the good refactoring tools that we have in Java despite that we are coding in the same IDE! Eclipse can help us Java coders soooo much but leaves us LotusScript coders way behind.
That was just an example, to show the principle. In real life, you are right, one would break out code like that into a function. But breaking out code into functions is covered in upcoming articles. :-)
I’m one of the odd people that *does* like to use a form of Hungarian Notation in LotusScript. Not in item/field names (ugh!) but yes in scripting.
In the end, there’s little difference between
docPerson
and
persondoc
It’s just a matter of personal preference. And using CamelCase (which I *always* use, in every language) means you look past the prefix to the name anyway, so in practice the end result is just as readable – again, all a matter of taste.
When it comes to breaking down the code into a lot of small functions …. there’s a happy middle ground. The antiquated LotusScript editing environment mitigates against that to some extent, because the code becomes harder to navigate. However, if you wrap things up in custom LotusScript classes, it gets a whole load better, because the editor gives you typeahead for your custom classes including the properties and methods. It’s not always practical/sensible to do that, but it’s worthwhile effort particularly for new code.
How about declaring variables just before you use them? This reduces (greatly) variable scope and therefore debugging effort..
http://www.hadsl.com/HADSL.nsf/Documents/Developer2007+-+30+Proven+Tips+to+Optimize+Your+LotusScript+Development!OpenDocument
I actually find it harder to read the code that way. Since I prefer to declare the variables in the order they are being used, they do show up in the same order in the debugger. The end result is the same as doing what you and Per Gref suggests, putting the declarations mixed in with the code.
Agree with most of what you’ve said. It all makes perfect sense and I do most of it without thinking. I use camelCase religiously and, like Julian, I must admit that I am a little odd … ie I do use hungarian notation all the time (even in field names, which must make me even odder).
I also agree that there is a happy middle ground when breaking code down into functions. I cringe every time I see single line functions like this …
Function CreateDocument (db as NotesDatabase) as NotesDocument
Set CreateDocument = db.CreateDocument
End Function
which then gets called like this…
Dim session as New NotesSession
Dim dbThis as NotesDatabase
Dim docNew as NotesDocument
Set dbThis = session.CurrentDatabase
Set docNew = CreateDocument(dbThis)
when you could achieve exactly the same thing as follows [with no function]
Dim session as New NotesSession
Dim dbThis as NotesDatabase
Dim docNew as NotesDocument
Set dbThis = session.CurrentDatabase
Set docNew = dbThis.CreateDocument
or even
Dim session as New NotesSession
Dim docNew as NotesDocument
Set docNew = session.CurrentDatabase.CreateDocument
CamelCaser and SomeTime Hungarian ;-)
I find using this notation makes code easier to read while debugging as dbNames and vwNamesUsers already signify what you’re working with meaning less commenting, I also use vFullName wherever I use a Variable so I don’t confuse them with Fields I’m CRUDing within documents.
“and the function name should be named so that You understand what the function does, and thus makes the comment redundant:”. Am amazed to read this and some other statements in the posting.
“Commenting should be done only at rare occasions”? Wow.
You actually write as the comments and naming is for ‘You’ and You only. I say they are also for others, now and in a future when ‘You’ are not available any longer and thus is part of the problem, not the solution. I think a point here is too see outside oneself. Sure it is a balance, but having a single line of commenting and then remove it ‘as it ‘make the code more cluttered’ and the reader somehow forces all readers to ‘stop and think’ seem stretching it too far.
Yes, breaking out certain code into separate pieces such as functions make sense,
@Bob: When you say “Am amazed to read this and some other statements in the posting”, are you referring to my blog post or Kenneth’s comment?
Sorry for not being clearer on that. His comment only,
Let me add I think your example of code cleaning is good and I always have thought having variable declares like that (together at the start) is best practice. It make great sense.
Bob, I ask for everyone that comment here to use a valid email address. I know that your address is bogus (gmail does not allow 3-letter addresses), but I decided not to leave your comments up. I would also prefer that you (as well as everyone else) uses a real name, but I understand that some prefer a pseduonym or an online handle. But a valid email is required.
ok – the “You” was incorrectly written in haste; Of course I ment that You should name functions in such manner so that ANYONE easily can understand what the function does, but I think that everyone else got the message. But You maybe like to mark words.
If a function name is clearly understood by You and everyone else on Your worksite – then commenting is not necessary. That’s the basic philosophy.
I strongly recommend reading “Clean Code” by Robert C Martin – there is a whole chapter dedicated about the commenting thing and it describes perfectly exactly what I mean and strive for. Take a look here: http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_1?ie=UTF8&qid=1362505362&sr=8-1&keywords=Clean+Code
It is not a question of marking words. I believe you are still talking about a pretty narrow space and time. Why not invest so little more effort to increase the odds that someone else, perhaps a few years in the future, will have an easier time to understand your intentions?
How can you so assuredly assume that some unknown person in the future will have no trouble and not perceive your logic in another way than yourselves or your current team mates in your work site? One can never be so sure.
The commenting is increase the odds I would say. They put another perspective to your intentions in the code (the code itself being the main perspective).
I have never seen code from a professional, seasoned, developer not having some level of commenting.
Obviously having a good name standard on the functions make great sense, but I cannot see how limiting oneself to that cover more than the most basic aspects.
Well wirtten code is self explanatory and comments will be redundant. As soon as You find Yourself writing a comment, You should focus on rewriting Your code so that is abundantly clear in all its logic and functions. It can be done by following SOLID principles of OOP.
I feel sorry for You to not have had the experience of good solid developers writing such code, but the existance of good programmers are hard to find. I’m extremely lucky to have worked with not only one but two such good programmers and mentors and have seen the most beautiful clean code that is absolutely clear; their code was like reading a book. Even though the business logic was documented in a wiki, the logic was in the code and easy to understand that even the wiki felt unnecessary at times. And not a single line of commenting.
Such code will be understood for years and years for any good programmer that will lay his/her hands on it.
So, NO – I’m NOT limiting myself to talk about a narrow space and time. Good code will stand the test of time. So with my experience of seeing, doing and working with such coding/coders – I know for a fact that this can be done without commenting.
I noticed in your example that although you append object type to your variable names, you don’t do this for primitives. I think you should.
For example, instead of this: Dim phonenumber As String
I would write this; Dim phonenumberStr As String
That way I don’t have to find the declaration to see that it is a string
In the same manner, instead of this: Dim username As String
I would use this: Dim usernameStr As String
Otherwise, since LotusScript isn’t case sensitive, you wouldn’t be able to also do this:
Dim userName As NotesName
BTW, an important thing to remember and something that many experienced LotusScript writers don’t know is that this:
Dim xStr, yStr, zStr As String
is the same as writing this:
Dim xStr As Variant, yStr As Variant, zStr As String
when they meant for it to be this:
Dim xStr As String, yStr As String, zStr As String
Great post!
I avoid appending the data type to primitives, in my opinion it makes the code harder to read.
If I need to know what data type a variable is, I just hover over the variable name and Domino Designer will display the declaration in a pop-up.
I am developing more in Javascript/jQuery and also some in C# these days, and because of that I have started using camelCase more. So in my example above I would probably today write like this:
Dim filePath As String
Dim fileName As String
So today you would write this: Dim employeeName As String
Without referencing the Dim statement, how would you know that it is a string instead of a NotesName?