This will be the first of many articles dealing with code design. What is code design? In object oriented languages it sometimes stands for deciding on the object class and structures needed to elegantly complete a task. For us it will just be a set of abstract principles that will allow us to write readable, maintainable code.
Among the QTP programmers community, many think that designing code is a waste of time. This view is common for many reasons, especially because QTP uses VBScript, which is a degraded programming language, and doesn’t support many of the prerequisites for code-design. However, some code-design core principles can be done in VBScript, and are the only way to keep our QTP code readable and maintainable over time.
Talking about code design concepts can be very confusing. Many of the concepts overlap, and sometimes one concept will be referred to by different names. I’m going to make things worse by merging some concepts, degrading and chopping parts of others, in order to make them more relevant to VBScript and QTP.
So, if you have any programming experience (especially with OO languages), you’ve probably heard these terms being used differently. Just remember I’m trying to keep my focus on QTP and VBScript, sometimes at the expense of accuracy (and this is only one of many articles that will expand and elaborate).
Today we’re going to deal with three main concepts: encapsulation, black-box coding and separation of concerns (SoC). These concepts have many synonyms, and sometimes overlap and refer to the same basic things, so demonstrating each of them separately is quite impossible. I’ll do a quick run through all of them, and then demonstrate them with a single test-case.
Keeping these principles in mind while writing our code will have an enormous impact on our future maintenance, code readability, bugs, and ability to add more features and functionality to out scripts. So pay attention
Separation of concerns
Is the best place to begin – it is simple, natural, and chances are you’re already doing it (to some degree). Simply put, it’s about breaking the code into as many logical pieces as you can, with as little overlapping as possible. This is usually done by separating long pieces of code into small dedicated functions. Later, the code looks like a composed puzzle, or a Lego combination of the different logical building blocks.
By packing each logical “block” separately, we achieve several goals:
Our code is much more readable
We can later maintain each of the blocks separately
The process can help us locate other places that use the same logical block, and turn the duplicate code into function calls
And many more benefits…
Without SoC, you have many different logical tasks intertwined throughout the code. If one of these tasks will need to change in the future, you’ll be force to fix the code in many different places, while risking messing up the other tasks’ functionality. With SoC, you can touch each logical task on its own, knowing that there’s no risk bugs will raise in different parts of your code.
So to illustrate: Dealing with warnings and error messages can be broken into three parts (actually more, but let’s go with 3):
Dealing with the message container
Recognizing the message
Reacting to it
If your code doesn’t separate recognizing the message from the actions needed to react to it, things can get very ugly very soon. What will happen when a new error message is introduced? Are you sure you know ALL the places that need updating? Can we really change the reactions without affecting message recognition? What if dealing with the new message requires performing actions in a different window? How will you construct a maintainable if switch?
Typically, in order to cover all our bases, we must put a hefty douse of duplicate code, or work very hard to tackle every little change in the future. Either way, it sucks, and our code will look terrible: it will be hard to tell which commands react to the error and which deal with the pop-up; it won’t be clear how the if-structure plays out; and the whole thing will barely be readable.
Black Box Coding
AKA concealing, decoupling or implementation hiding – is a concept that helps us to better build the separate building blocks we produced in order to keep our separation of concerns. The concept further deepens the separation and independents of the blocks, ensuring we could radically change any one of them, without any impact on the rest of the script.
The name actually says it all – the pieces of the script should operate as black-boxes. No one outside of the function or the action should know HOW it achieves its goal (its implementation), only how to tell her to do so. In a certain way this means we should “dumb down” the script, so it just calls all the smart folks to do the actual work for it. Actually, even these folks are dumb, and call other folks to do some of the work for them.
Without back-box coding, changes in one function will require changes is many other places in the code (because they’re exposed to the function’s implementation). Because it’s impossible to remember all the places that are relevant for a code-fix, we will usually end up with many small bugs that will either fail, or mess around with our script.
Unfortunately, no matter how hard we try, VBScript forces us to changes our code when the function’s signature (parameters) change – more on that here. But other than that, you should strongly avoid any kind of dependencies between different black-boxes in the script.
For example: Writing to the log
Bad way: insert Reporter.ReportEvent commands straight into the code
This exposes the implementation of the log-reporting, which means it will be impossible to change the means of reporting without changing each and every reporting command throughout the entire script. This is what we call strong-coupling – the log-reporting functionality is coupled with other parts of the script in an inseparable way.
Good Way: wrap the Reporter command with a custom function, and call it from the script:
Here we’ve decoupled the logging into a black-box. No one knows HOW it logs, but whenever the script needs logging services, it just calls the black box, knowing that it will get the job done. If we’d like, we could switch to excel logging by changing this function only, and the rest of the script will require zero-maintenance.
Is all about gathering all the code that’s responsible for a specific task under one roof. The concept biggest benefit is eliminating any duplicate code in our scripts, but there are many welcomed side-effects. When there’s only one “parent” responsible for each task, the code becomes much clearer, and easier to maintain and expand.
When our logical functionality is properly encapsulated, there are no “I hope we fixed all the places that did X”, because X is done in one place only. When you’ll find a bug, it will usually take you less then two seconds to find the exact place to fix it. Expanding functionality is done decisively and quickly, in case where it would be considered impossible in the past.
I hope you’ve got the feeling that these concepts overlap and complete each other. When you separate concerns, you’ll be doing so in black-boxes. And when you’re building a black-box, it will be properly encapsulated. And you can’t encapsulate anything without separating it from other logical functionality (SoC), and making it into a black-box. Well, you got the point.
The bottom line is writing maintainable code, by reducing the dependencies between the different parts. This way we can expand, fix, and maintain each part, without having to worry about the rest of the script. These concepts are so basic; the almost any technological design has to take them into account – in order to make a car maintainable, you got to make sure that changing a light bulb won’t affect the steering. We’ll see how these and other concepts play their parts in the coming articles.
To make these concepts more tangible, here is an example of an error handling approach which utilizes these concepts. The example makes use of DP and dictionary objects, so make sure you know them well enough. Of course this isn’t the best way to deal with error messages, but it would do to demonstrate these concepts.
Download the example-app, cut and paste the code to QTP, and run it to see the results (don’t forget to change the AppPath constant at the top!).
The script will open the app, initiate an error window, and send it to the central function dealing with errors. The function will use its delegate-functions to extract the error from the window; analyze it and determine the needed actions to resolve it; and execute these actions.
Notice that if a new error message would be introduced (or an old one to be changed, we will only need to add a recognition block to the analyzer function (or edit an existing block), and within that block, add needed actions to be executed.
As with many of my examples, I try to introduce new ideas and concepts, beside those specifically discussed in the article. In this example you’ll find, among other things, using strings to represent action instructions, and using funcitons as factories that process these instructions and executes them.
About Yaron Assa
A leading automation expert, Yaron is the original founder of AdvancedQTP.com, and also ex co-founder and CTO of SOLMAR Knowledge Networks Ltd. Yaron has his own website, and also runs a Hebrew podcast on skeptical thinking and science (http://www.safeksavir.co.il).