On the face of it, software development seems a simple and straightforward job – develop the logic, conjure up the code, compile it and it’s good to go! However, the reality is different. Software developers and architects face innumerable challenges on a regular basis during the course of software development.
There are solutions and best practices to nip each specific issue in the bud, and even prevent such issues from occurring in the first place. However, such methods have proven ineffective when it comes to practical implementation over time.
A far better approach that would pre-empt almost all of the above stated issues is embracing test-driven development (TTD). TDD is a software development process that relies on the repetition of a very short development cycle. The developer first writes an automated test case that defines the new function or a desired development, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards. It follows a four-step approach of writing tests -> running all tests -> writing the implementation code -> refactoring.
Here are a few common challenges that software developers face and how TDD can offer a long-lasting and effective solution:
1. Code re-usability:
Since many programs would have similar basic functionality, it is a standard practice to reuse code from an earlier developed application, to avoid reinventing the wheel and save precious time and effort.
However, most developers remain under the impression that their code is 100% reusable. The reality is that reusing code is fraught with many challenges. The code would develop serious glitches outside their intended modules. For instance, the presence of too many functions may render an implementation class virtually impossible to reuse, and a single function interface can lead to too many classes. Code reusability works only when a balance is struck somewhere in between with medium sized interfaces or fewer functions.
The code produced using TDD is of high quality and good for reuse. Even otherwise, the tests, which constitute an inherent part of the TDD development framework serve as a feedback mechanism to pre-empt any issues that may flare up on using reused code.
2. Documentation:
Comprehensive documentation is important for making updates, maintaining the code, knowledge transfer to new developers, and for making further developments. However, most software development comes with poor documentation. Many programmers, rightly obsessed with removing the glitches and getting the software working perfectly neglect documenting the fine-tunings that they made. Others may have done a good job of documentation upfront, but later, as changes are made to the code, the updates may not have been documented in detail.
With TDD, documentation becomes easier. All it requires for understanding a class or operation is documenting the sample code, as all classes or operation basically uses the same logic and calls on it. Thus, unit tests effectively become a significant portion of the technical documentation and acceptance tests form an important part of the requirements documentation. The developers can then add user, system overview, operations, and support documentation around this basic core.
3. Code Refactoring:
Code refactoring is the process of restructuring existing computer code without changing the external behavior. This would make the code structure much simple, improve code readability, make maintenance easy and create a more expressive internal architecture, to improve extensibility. However, many developers remain skeptical to refactor some code as they are scared of breaking some existing functionality. However, if refactoring does break some existing functionality, then it is an indication of “code smell” or symptom in the source code indicating a deeper problem.
Refactoring is an integral element of the TDD development process. TDD makes refactoring easier as the unit tests will fail if any change has broken the code. The approach of having unit tests upfront and the practice of continuous integration help agile teams effect a lean design and refactor continuously.
4. Broken functionality:
Software development is never static. However, changes and updates to a code, especially at a later stage in the lifecycle may result in the update breaking down some functionality, even when the intended changes are met successfully. Such side effects may not manifest itself at that point, for the develop to rectify and when it is eventually detected later in the lifecycle, it costs a lot of debugging hours just to identify the problem, and even more hours to effect a fix.
The TDD approach makes it considerably easier to check for broken functionality and make amends. The TDD approach requires a cycle of new tests and refactoring every time changes are made. If any test fails, it means that the refactoring effected on introducing a new test case (for a new functionality) has broken an existing functionality. It then requires changing only the new test to get the refactoring right, before adding the actual features later.
5. Code Quality
Endemic code quality is vital to any business, more so when the business depends on the software. The cumulative impact of even small errors and wrong assumptions can be dire and impact customer-facing deployments negatively. While most developers start with maintaining the required quality standards, it becomes difficult to keep tab on code quality as the module grows and the code becomes large and unwieldy. As the project advances the pressures of deadlines may also cause quality standards to dip.
Empirical Studies Show that TDD improves quality, and this is because the tests catch bugs before they are shipped, effectively pre-empting bugs and quality issues rather than run the risk of ego-issues, boredom, lack of time, and other issues impede the quality checks later down the lane.
6. Unused Code:
Dead code elimination, or removing code that does not affect the program results is important to optimize the software, to shrink its size. This not just reduces memory requirements, but since it avoids running the program excluding irrelevant operations, running time gets reduced.
While there are best practices on eliminating unused code, in practice, unused code tend to accumulate unnoticed as the project grows, as the developers are busy with other priorities.
Apart from making the program considerably large and slow, accumulating dead code increases the administrative burden of maintaining the code, takes it longer for a new programmer to make sense of the code, creates misunderstandings, and also opens up the danger of a someone making changes which inadvertently involve the ‘dormant’ code and introduce bugs.
The entire TDD approach is of writing the minimum amount of code required to make the software run and then refactoring the code to make it clean. Thus, the chances of dead code or unused code is pre-empted by design itself.
7. Recurring bugs:
It is common in software development for bug that is fixed few releases back reappearing. This usually takes place when new code is added to a project as a special case, and this happens due to improper analysis in the first place. The TDD approach of testing and refactoring upfront negates such a possibility in the first place.
TDD is a powerful tool in the hands of agile software developers, helping development teams overcome all the traditional challenges and stumbling-blocks associated with traditional development, and improving productivity and quality drastically.
Image Credit: CodeProject