
This is a Servoy Tutorial on how to write maintainable code. Maintainable code means that it can be easily read, understood, extended, and maintained. This Servoy tutorial builds upon what you learned in the two other tutorials at this site, Encapsulation and Event Driven Architecture. You should study those Servoy tutorials carefully, as they discuss how to design an application with good architecture. This tutorial focuses more on the coding conventions.
As you begin to build bigger applications, or you begin working with a team of developers, it becomes imperative to establish coding conventions early in the process. The reason for this is to ensure that all code is written in in a consistent manner. Consistency ensure that the code you write today, will still be understood tomorrow, either by yourself, or any one else. This is particularly important in today’s agile development environments, where features are continually added to the base code in short weekly sprints, building upon the existing structure. Establishing coding conventions also ensures that you build a product with a consistent user-interface and experience.
The ideas presented here are not new, nor are they gospel. Feel free to adopt and change them to suit your own needs. The important part, however, is that you establish your own conventions, and start implementing them in your project and with your team.
Style Sheet
You probably know that Servoy supports the use of cascading style sheets (CSS), and that individual style classes can be assigned to your forms and form elements. This ensures that you have a consistent user-interface. More importantly, it means that you can maintain your look-and-feel from a single location. In addition, you can create multiple style-sheets and switch them at application start-up, thereby allowing your application to use themes that users could choose from.
The important thing to do here is to create a Style Guide that documents what style classes to use for what elements and in what situations, which all developers on the team can refer to. Also, create a form in your base module that demonstrates every style class you define. Just by looking at this, developers will be able to quickly identify what style class they need to use for a particular element.
Its also a good idea to create sample base forms, that can be cloned as starting points for new forms. This allows you to make sure the proper style class is already applied, the record navigator has been removed, the proper form parts are there, and even consistent UI elements are present. This is particularly important for table views, which may have add/delete buttons, column headers, header divider, row selector, sort, delete icons, and even a few standard methods (public and private, right?). This saves you time in development and ensures that every form looks consistent.
In this real example, you can see that the base table view includes a header, quick search tab panel, header divider, row selector, and row sort icons. There is another version that also includes addNew/deleteAll buttons in the header and a delete button for each row. By cloning from these base templates, newly created forms maintain a consistent look, functionality, and creating new forms is greatly simplified.
Code Formatting
Code formatting is also part of your Style Guide, it dictates how the code will be written at a high level. It is extremely important, as it will ensure that any developer will be able to work on the code, regardless of who wrote it. As mentioned in other Servoy tutorials at this site, the code will be maintained by multiple developers over its lifetime.
Indentation
So the first thing is indentation. It doesn’t matter whether you choose to use spaces or tabs, just go into the Eclipse Preferences for Javascript, and configure a Formatter profile for your project(s). Just keep tweaking this until you and your team are happy with it, then export the format template and make everyone on the team use it. There is nothing more annoying then opening up a .js file and having to format the file first before you can read it. How would you like to start your Monday morning, before your first coffee, with something like this?
//Poorly formatted code - good luck reading this
function personProto() { function privateMember(){} this.fullName = function(){ return this.first + " " + this.last; }; this.fullNameTitle = function(){ return this.fullName() + ", " + this.title; }; /** * @protected */ this.protectedMember = function(){ return "You cannot see me!"; }; }
Here is the Eclipse code Formatter:
Here are the Edit settings for the code Formatter:
And here is the same code, properly formatted. Much easier to read and work on, right? Now go ahead and enjoy your coffee.
//Properly formatted code - much easier to read
function personProto() { function privateMember(){} this.fullName = function(){ return this.first + " " + this.last; }; this.fullNameTitle = function(){ return this.fullName() + ", " + this.title; }; /** * @protected */ this.protectedMember = function(){ return "You cannot see me!"; }; }
Line Termination
One of the odd things about Javascript, is that a line of code can be terminated either by a newline, or with a semicolon. This is possible because Javascript has a mechanism known as automatic semicolon insertion (ASI). In most cases, if you leave out the semi-colon, ASI will guess correctly, and automatically insert one when it runs your code. However, it does make mistakes occasionally, resulting in difficult to locate bugs. Consider the following code, which will return an object containing some data:
function getConsultant(){ return { first: Gary, last: Dotzlaw } }
The parser will see it as:
function getConsultant(){ return; { first: Gary, last: Dotzlaw }; }
The parser will incorrectly insert the semi-colon after the return, which will cause this function to return “undefined”.
Always terminate your lines with the semi-colon, that way you control where it ends, and not leave it to ASI to guess correctly. Your just asking for trouble if you don’t, and if you used JSLint or JSHint (Eclipse addins that check for proper code style), you would be scolded to death for the bad practice.
Also, the example we just used points out another important coding convention that should be enforced; placement and alignment of enclosing braces. You should always put your opening brace on the same as the block statement, thereby mitigating the ASI problem shown previously. So do this (example also demonstrates proper alignment):
//Brace alignment should be like this
function getConsultant(){ return{ first: Gary, last: Dotzlaw }; } if (oCustomer.country === "Canada"){ // Some code }else{ // Some other code }
And not this:
//Bad brace alignment - do not do this
function getResult() { return { first: Gary, last: Dotzlaw }; } if (oCustomer.country === "Canada") { // Some code } else { // Some other code }
In case you need to break a long string over multiple lines, like an SQL query, so that it is easier to read, do it like this:
oSQL.sql = "SELECT customer_id \ FROM customer \ WHERE company_name = ?";
Line Length
Keep your line length short and reasonable. It’s not a contest to see who can chain the most functions together or write the longest “if” statement. No developer wants to scroll the window horizontally just to read your code. In addition, source code commits are on a line by line basis, so the shorter your lines of code, the less likely you will have a difficult merge conflict to resolve.
Instead of this:
if (oCustomer.country === "Canada" && oCustomer.state === "Manitoba" && oCustomer.city === "Winnipeg" && oCustomer.company === "Dotzlaw Consulting"){ application.output("Hello"); }
Do this:
if (oCustomer.country === "Canada" && oCustomer.state === "Manitoba" && oCustomer.city === "Winnipeg" && oCustomer.company === "Dotzlaw Consulting"){ application.output("Hello"); }
Blank Lines
Your code should read like logical paragraphs, and not one continuous block of code. Blank lines should be used to separate related code from unrelated code. Also, when working with the Eclipse Formatter, you can tweak it to insert blank lines after block statements, and in various other situations, to make your code easier to read. Tweak the settings with your team and establish a standard; it will make the code consistent and easier to read for everyone.
Equality
I decided to mention this, because I was recently asked why I use “===” instead of “==” in my “if” statements.
Javascript uses something called “type coercion”, which automatically will try to convert a variable to a different type in an attempt to make a particular test succeed. This can lead to some unexpected results, so you need to be sure you know what you are doing. To illustrate the point, consider the following scenarios, and then make up your mind how you want to test for equality.
application.output(123.25 == "123.25"); // true application.output(1 == true); // true application.output (0 == false); // true application.output(123.25 === "123.25"); // false application.output(1 === true); // false application.output (0 === false); // false
I believe how you use equality should be part of your coding standard, and everyone should know what the difference is.
Variables
Everyone thinks they know how to use variables in Servoy, but I see a lot questionable practices, so its worth a short rant. You need to understand that Javascript is not like Java, or many other languages, when it comes to variables. I know we all learned in other languages to declare variables as close as possible to their first use in the code, but this is bad advice in Javascript. In Javascript, variables are hoisted to the top of the function, and will be initialized as “undefined”, regardless of where you define them. Also, in Javascript, variables declared in a function are accessible to everything in that function, including other functions (let’s ignore closures for now). Therefore, there are several problems with a typical block of code like this:
//Poor variable declaration and initialization
function myFunction(){ var iMax; var sCustomer; var bFlag; iMax = 10; for (var i = 1; i <= iMax; i++){ // some code } var rRec; // more code }
- Variables should be initialized properly with the expected data type, otherwise they are initialized as “undefined”.
- Variables should be defined at the top of the function; they will be hoisted to the top anyways.
- The expected variable type should be specified, when it is not obvious from the initialization. Use the jsDoc @type tag for this purpose.
- Variables should be defined together in a single statement, separated by commas, and properly aligned. Its less typing and easier to read.
- There is no such thing as a block variable, so the “var i” declared in the for loop, will be hoisted to the top of the function and initialized as “undefined”. This variable should be defined at the top of the function, just like the other variables. Change your Eclipse template to fix this problem; it comes standard like this with Servoy.
So here is the same code with proper variable formatting and initialization.
//Proper variable declaration and initialization
function myFunction(){ var i = 0, iMax = 10, sCustomer = "", bFlag = false, /***@type{JSRecord<db:/test/t_event>} **/ rRec; for (i = 1; i <= iMax; i++){ // some code } }
I guess I should also mention, that if you listen to the gospel (Javascript: The Good Parts), you should never use “i++”. This shortcut notation made it into the Javascript language from C++, a language so bad, that it inherited its name from this bad idea. It is confusing to new Javascript developers, and other more advanced developers who come from other languages where “i++” and “++i” are available and treated differently (++i
translates to “increment i, then evaluate” while i++
translates to “evaluate i, then increment“). Confusion leads to errors; excessive craftiness is what Crockford calls it.
for (i = 1; i <= iMax; i += 1){ // some code } // And don't do this to increment the pointer by two, please... i++; i++; // Instead, do this i += 2; // Same with decrement i -= 1;
Naming Conventions
In general, use camelCase for all naming, with the exception of object constructors and enums.
Objects
Prefix | Type | Example |
---|---|---|
fs | foundset | fsTime |
ds | dataset | dsTime |
r | record | rTime |
object constructor | Person |
Variables
Variable names should mostly be nouns. Use descriptive variable names, that shed light on what it is used for. For example, x, y and tt, are poor choices, and make your code hard to read and understand.
Prefix | Type | Example |
---|---|---|
b | boolean | bFlag |
i | integer | iCustomerID |
n | float | nHours |
s | string | sFirstName |
a | array | aReports |
d | date | dToday |
o | object | oContact |
_n | form variable | _variableName |
Design Elements
You do not need to name all fields and labels on a form. You need to name those that you need to access programmatically. Using the prefix will assist developers with code completion.
Prefix | Type | Example |
---|---|---|
btn | button | btnOkay |
fld | field | fldCustomerID |
lbl | label | lblName |
tab | tabpanel | tabMain |
Modules
You can prefix your module names to help identify the type of module.
Prefix | Type | Example |
---|---|---|
mod | module | modSales modUtils |
mob | mobile | mobCustomer |
Forms
Forms should be named with a prefix and a suffix. This will help to identify the different types of forms in the “Solution Explorer” and keep them neatly organized.
Prefix | Suffix | Type | Example |
---|---|---|---|
frm | _dtl | detail | frmCustomer_dtl |
frm | _tbl | table | frmCustomer_tbl |
frm | _dlg | dialog | frmCustomer_dlg |
frm | _base | base form | frmCustomer_base |
_base | developer/module base | _base |
Methods
Methods should begin with a verb and be followed by nouns.
Example |
---|
getPrice |
createCustomer |
deleteContact |
calcTimeExpense |
Relations
Servoy does a good job of automatically building the relation name, as you define a standard relation. Only if you are using a relation to pass a foundset (relation from one table to itself), or you are defining a global relation using a global variable or enum, should you have to edit the name.
Type | Example |
---|---|
standard | contact_to_customer |
standard + global using scopes.sales.customerId | contact_to_customer$sales_customerid |
global using scopes.sales.customerId | _to_customer$sales_customerid |
foundset | _to_customer |
Enums
Enums are constants, and should be named using all caps. Organize your enums into a scope, like “scopes.enums”, and then organize them into logical groupings as shown below. Doing so will make it easy for developers to locate the constant they need using code completion. Enums can also be used in relations.
/** * @enum */ var CONSTANTS = { _0: 0, _1: 1, ON: 1, OFF: 0, ACTIVE: 1, INACTIVE: 0, _NULL: null, ORD: "ORD", EST: "EST", JOB: "JOB" } /** * @enum */ var DB = { SERVER: "test", SUPPRESS_ERRORS: true, MAX_RECORDS: -1 }
Scopes
With the introduction of scopes, the restriction of having all your global variables or methods in a single node has been removed. In fact, the first thing you should do when you add a new module to your solution, is delete the scope that Servoy automatically adds called “globals”. You should create your own scopes that helps you with your overall architecture, and organize information into logical groupings. For example, create scopes called “scopes.enums”, “scopes.events”, “scopes.utils”, etc. This will help keep your code organized, easier for developers to use with code completion, and easier to maintain.
Conclusion
Well, there you have it, a Servoy tutorial on how to write maintainable code. It all comes down to developing good standards and enforcing them throughout the projects life cycle. The standards will allow new developers to adjust quickly, ensure everyone’s code looks the same, and make everyone’s code easier to maintain.
That concludes this Servoy tutorial. To learn more about good architecture, best practices, and other helpful tips, please check out the other Servoy tutorials in the Related Posts area below. I hope you enjoyed this tutorial, and I look forward to bringing you more Servoy tutorials in the future.
Please subscribe to receive notification when new articles are published, and share your comments below.