Ervin Turai is a software engineer at Tecknoworks. Some of Ervin's specialties include C#, VB.NET, WPF and SQL Server.
Throughout the development phase of a software product, there comes a time when the source code needs maintaining. Think of it like a nice living fence that needs trimming every other week, to make sure it has just the right shape and aesthetics. That's right—code can, and should be aesthetic, easy to read and comprehend.
The process through which one achieves this is refactoring. In a nutshell, the code is rearranged, rewritten, regrouped and reorganised in such a way that it fits the required specifications.
The need to refactor might arise because of the following reasons:
And the list of reasons can go on. As it can be seen, pretty much all paths in the development cycle lead to the need to refactor. The extent of the action can of course, vary.
Back in the days, as a junior developer, I used to believe that by thinking well ahead and writing good code from the very beginning exempts me from refactoring. While a good design of the architecture and backbone of the solution is a positive and desirable approach, it cannot prevent refactoring.
There are mainly two strong reasons why I believe refactoring cannot be prevented:
However, refactoring has its disadvantages too. The risk of breaking things is the most imminent and it usually happens. When a piece of code is designed to cover one or more features and the code is refactored, chances are that the developer overlooks one or two, or simply doesn't understand why the code does that one, apparently useless thing. Then naturally, the features may start failing.
Not refactoring the code and instead just adding more and more lines to accommodate all changes and bugs, inevitably leads to technical debt. As all debts, if not 'payed', it'll come chasing you. Needless to say, the later in the development cycle refactoring happens, the more risky, difficult, error prone and lengthy it is. So one good advice is: don't be lazy, polish the code, especially if working in a team and do not let code smells stay in there.
At the end of the day, the benefits of keeping the code clean greatly outweigh the risks. One simple yet very strong positive outcome is when thinking about the maintainability of the code. Either when new members join the dev team, post-release maintenance, or in an unforeseen next phase of the project, having a well readable code, is a huge advantage. Even if the original team is re-assigned to the project as little as 6 months later, they will have forgotten the subtle nuances and edge cases their code caters for. Then, having readability is their main advantage.
My top 3 tricks I recommend you to follow when refactoring:
It is quite amazing in how many places repeated code can be found and in how many shapes it can be.
Imagine the simplest example: let us have a team of several developers, working on a project. Two or more of them work on features that need displaying dates in a certain format. Depending on the order in which they'll encounter the need to display the dates, they will start implementing in various ways: through .ToString() and a constant, through .ToString() and an inline string pattern, through an extension method or through a helper class. And the examples can continue.
The programmers may communicate and learn that there is a 'standard' way in the application to display dates, or they may be thorough enough to start searching for a similar case and go by the approach they find. But odds are not always on the team's side and they may end up in several implementations of the same problem. When it comes to refactoring, these things need to be consistent. And even that does not guarantee that, when new team members come along and need to display dates, they won't 'reinvent the wheel' and come up with their own way of doing it.
Example:
1. Dim targetPdf = String.Format("{0}/{1}_{2}.pdf", Server.MapPath("~/Content/reports"), PostedReportName, timeTag)
2. Try
3. If IO.File.Exists(targetPdf) Then IO.File.Delete(targetPdf)
4. Catch ex As Exception
5. targetPdf = String.Format("{0}/{1}_{2}_{3}.pdf", Server.MapPath("~/Content/reports"), PostedReportName, Guid.NewGuid().ToString(), timeTag)
6. End Try
7. repDoc.ExportToDisk(ExportFormatType.PortableDocFormat, targetPdf)
8. repDoc.Close()
9. repDoc.Dispose()
10.reportPdfViewer.Src = String.Format("{0}/{1}_{2}.pdf", "../content/reports", PostedReportName, timeTag)
Basically, what it says is this: we want to export a report into the ~/Content/reports folder and then bind it to a report viewer’s source. For this, in line 1 a filename is set up. Line 3 makes sure that another file with the same name does not exist in the target location by attempting to delete it. However, if deletion fails, line 5 makes sure that the file name is chosen in such a way that it is certainly unique. But then look at line 10: it recomposes the file name from its various pieces, thus breaking DRY. What if line 5 actually got executed? We would bind a different file to the report viewer than the one we just created. And it’d be a problematic file, because line 3 could not delete it. And it’s all such an innocent looking, one line repetition. Imagine what effects a longer repetition could have!
2. The Boy Scout principle
As the scouts that keep the forest neat and clean as they stride through, the programmer should also leave the place in which he worked neater and cleaner than it was before.
The occasions when you'll need to work on the code of others will be numerous and you might find that, besides implementing the change at hand, you can enhance or beautify the code too. For example:
This is one of the most useful principles that can be applied when refactoring. It can be done in small, unrisky runs, let the new code settle in, let your teammates find and explore your solution. A simple example:
The code is full of statements like: if (element != null) list. Add(element);
It is not too pleasant to read all those repeated IF statements. Instead, why not create an extension method to your List <T> class, calling it AddIfNotNull, and just call that. It will even vertically arrange the text, rendering the inserted objects to start at the same position, thus making it easier to visually scan, top-to-bottom.
3. Mitigating Risks - Use unit tests or TDD.
The easiest way to cater for the risk of breaking things are the unit tests. TDD is the most important safety belt one can have against crashing features. During refactoring, the unit test can be run and they will immediately signal which feature needs more attention. When extending the team, unit tests are especially helpful—they cover features at a high level, endowing them with the power to run the tests and explore all paths in the code that the execution takes. Having this possibility is a time-saver for the existing team, because they won’t have to explain the solution in meetings. Rather, they’ll just focus on the really important key decisions, or mechanisms that are in place.
Another way to reduce the risks of regression is code reviews. The more eyes looking at the code, the greater the coverage will be. Moreover, pair programming can be a useful technique while refactoring, not just because two brains will yield greater coverage, but also because thinking aloud helps each of the programmers individually.