I don’t write code in the best way the first time. No-one does. Instead, if we want to get to a state of clean code (readable, maintainable etc.), we often have to put specific effort into it.
Martin Fowler’s Refactoring book summarises it nicely when talking about refactoring. You need two hats. One for adding functionality and the other for refactoring. Your process may have a nice point to change hats (I use TDD which does) – or you may have to be more deliberate. But at some point, you need to think about refactoring.
I’m not going to go into loads of detail on refactoring now. Instead, I want to focus on one type of refactoring that I use a lot – creating more subVIs.
Why Refactor Code to have more subVIs?
There are several reasons why code with more, smaller subVIs will tend to be easier to work with than a flat VI:
- The diagram is smaller – The simplest of them all – your diagram now fits on the screen!
- Improved readability through abstraction – This can be a contentious point. We are putting the detailed implementation another click of the mouse away which some people dislike. My experience is that this is outweighed by how much easier the code is to read. By creating a well-named, cohesive subVI, the calling VI is faster to read since you don’t have to worry about the details. You can worry about the problem it is solving and dig into the details if/when you have to. There are also testing and debugging advantages since the new subVI can be debugged in isolation.
- Clear and Obvious Coupling – Coupling is one of the primary concepts in software design that you need to grow to understand. A flat diagram can easily hide coupling in the noise, but once you create subVIs, it is obvious if a subVI has too many inputs or inputs that you wouldn’t expect it to have. These are both signs of coupling problems.
Where to start?
I signed up for a presentation at NI Week to talk about clean code and needed to talk about this topic. The day before planning to write the subVI section, I was working on a customer project. As I have a young baby at home, I try to leave at 5 pm every night now, but it was getting on for 5.40pm, and my Wife was messaging me to see where I was so I got the code running but never put on my refactoring hat.
When I came to write my presentation, I realised that stepping through the refactoring of this code was the perfect example! The code I abandoned that evening is the code in the steps below.
Look for Commented Sections
The first and simplest clue, is to look for sections of code with comments describing what they do. If it is enough of an important chunk to comment, it is probably cohesive enough to make a good subVI.
See the sections for formatting at the bottom? There are three types of data to be formatted for the table. One is already in a subVI but the other two are just labelled as doing the conversion. This is the classic sign of a subVI that hasn’t been created yet.
The first step here is to turn these all into subVIs.
I’ve created the subVIs, aligned them and given them a consistent naming convention.
Different Levels of Abstraction
I’m still looking for a better term for this, but there is still a smell in the code above for me.
Most of the code is now subVIs doing non-trivial functions. Loading from a log, generating tables. But there is a section on the left that is doing array manipulation. This is a problem because it is too much detail for what this code describes. This code is supposed to “say” load the data from the log, convert the sections to a table and combine them all. But instead this story includes handling memory allocation as well!
So my next step is to abstract this into a more descriptive subVI.
I hope you agree; this code is now much more straightforward to read. It is doing everything it was before (as efficiently as it was) but as developers, we can become faster at understanding the code.
Did you notice the coupling?
This highlighted a nice reason for this refactoring. Why is a build table function using an array of timestamps?
We sucked these in as part of the “create subVI” process, but they don’t belong. We can refactor the timestamp array out but using the rows in the time table instead to remove the input.
This is a somewhat trivial example, but by abstracting out the subVIs, we can now understand our code in a way that is clearer than before and better visualise the coupling between functional components.
Are we done?
Naturally there is no “perfect” state. This was the stage I stopped at since I had removed the immediate “smells” and the code worked well. Now the code structure is clearer though it actually highlights some other things we could consider:
- The build table code could be generalised for reuse by removing some of the labelling. If I need this same function elsewhere, I may come and grab this code.
- The program flow shown is that we generate each sub table before building a large table. It may have a higher performance to directly insert each of the subtables directly into the pre-allocated table. Right now the solution above is simpler, and I don’t have any performance concerns, so I haven’t done this.
I’m going to keep on about this – These potential changes are much more obvious in the new structure than the old – that is why those first steps are important. There is also a point where you have to say good enough, or there isn’t enough information to know what is the next best step (options 1 and 2 above may clash so we don’t do them until we know which we want).
A Note On Testing
Isn’t there a risk of breaking your code with refactoring? In theory refactoring should not make any functional changes to your code but we have all done things with unintended consequences!
Allowing low-risk refactoring is one of the ways that unit testing leads to better code. Because I have tests around this section of code, I could change it as much as I wanted with confidence that it still works. This is why we treat unit testing as a foundational principle at Wiresmith Technology.