We worked on a project where a senior engineer was convinced that the select system call was broken on Solaris. No amount of persuasion or logic could change his mind (the fact that every other networking application on the box worked fine was irrelevant). He spent weeks writing workarounds, which, for some odd reason, didn't seem to fix the problem. When finally forced to sit down and read the documentation on select, he discovered the problem and corrected it in a matter of minutes. We now use the phrase "select is broken" as a gentle reminder whenever one of us starts blaming the system for a fault that is likely to be our own.
--The Pragmatic Programmer, “Select isn’t Broken”
Software developers encounter a variety of challenges throughout the course of a project. When problems arise, the existing processes are often blamed as the root cause. This is why Agile processes are often criticized. However, the reality is that Agile isn’t broken.
So why does Agile get the blame for project issues? One of the biggest mistakes that developers can make is thinking that Agile only requires a change in attitude, without acknowledging the shortcomings in their development process or skill set. In other words, writing code for an Agile project requires much more than a positive attitude towards change. It requires a fundamentally different set of technical skills than a waterfall approach.
Ample Passion, Modest Skills
One of the primary challenges with the Agile Manifesto is that it’s a philosophical statement, not a step-by-step guide for how to live out its virtues. Consider the following snippet:
Our highest priority is to satisfy the customer through early and continuous delivery of valuable software.
Welcome changing requirements, even late in development. Agile processes harness change for the customer's competitive advantage.
This is a bold vision for how software development can be. But how do we actually adopt those practices when we’re sitting in front of an empty Visual Studio or Sublime Text terminal? How do we write each line of code in a way that welcomes continuous delivery and ever-changing requirements? How do we deliver a small set of fully functional features at the end of a two week sprint, when we’re used to delivering a full product after 6 months?
Theory Meets Practice
To answer this question, I’ll dissect some of the interview questions that I like to give to candidates for entry-level or intermediate developer positions at Productive Edge.
What are some examples of designing to an interface instead of an implementation?
A surprising number of candidates, even senior-level ones, have difficulty articulating the importance of interfaces. When we think of software design only in terms of concrete classes, we often end up with tightly-coupled code. Without interfaces to abstract our code from other components in the system, we can end up entangling our code with the types and namespaces of all sorts of external libraries and projects.
Here’s a simple example. If your project leverages a framework that costs $5000 per license, and you haven’t properly abstracted that framework from the rest of your codebase, you’ve suddenly put yourself in a situation where you can’t meaningfully test your code without having the framework installed. Suddenly, even compiling the project becomes painful.
What are some examples of how you’ve used dependency injection in a project?
Dependency injection is an important foundational concept that allows developers to build truly testable, scaleable software. Whenever I take on a legacy codebase, I inevitably run into classes like this:
As I pointed out in the comments above, the logic in CommunicationsManager is tightly coupled to the AcmeBusinessLogicWorker and DbLogger classes. We probably can’t compile the CommunicationsManager class unless the Acme SDK or libraries are installed. And the code will probably throw exceptions or return unexpected errors if we try to test it without the actual runtime present.
If we’re going to architect our software in a way that can handle Agile’s ever-changing requirements, we need to consider a different approach. In the example below, we’ll leverage interfaces and dependency injection to untangle the code.
The new design for the CommunicationsManager class opens up a variety of possibilities. First, instead of dealing with concrete objects that we instantiate directly, we’re now using interfaces that we pass into the constructor. The IBusinessLogicWorker that we pass into the CommunicationsManager could be a simple wrapper for the “real” AcmeBusinessLogicWorker, but it could also be a mock class that just returns canned data (which allows us to test our code without the vendor’s libraries).
What are some examples of how you’ve used test-driven development in a project?
Test Driven Development, or TDD, is a powerful practice for not only maximizing the quality of code, but formalizing and validating that our code meets business requirements. We at PE are not TDD zealots, but we strongly value the use of effective, meaningful unit tests in our code.
An average developer, even one who is unfamiliar with interfaces or dependency injection, can usually look at a function like the following example, and think of a few inputs to validate its behavior:
But a “real world” unit test often requires us to test business logic, which rarely uses only primitive data types. Consider the following method prototype for evaluating service level agreement (SLA) violations. It accepts an IEnumerable and an interface for an object factory:
Properly testing a method like this requires not only supplying well-formed ticket objects, but also an ISLAEvaluatorFactory mock object that behaves in a realistic way. When building enterprise applications, we need to test logic like this in a repeatable, consistent, and fast way. We simply can’t leave this kind of validation up to a QA team.
What are some examples of mock classes? Do you have any preference about mocking frameworks?
Mocking frameworks allow developers to take a class or interface and instantiate a throwaway test object that has special behavior when its properties or methods are called. Mocking frameworks are tremendously powerful, and in some ways they represent a culmination of the practices of designing to interfaces, leveraging dependency injection, and TDD.
Here’s a trivial example. Let’s say that I have a social media web app which behaves in different ways depending on how many photos the user has taken. I’d like to write unit tests that test each of these scenarios.
So, what can I do to test this code?
If I have a mocking framework like Moq, it’s easy. These frameworks let me create an instance of IPhotoEngine that returns special behavior for its GetNumberOfPhotos() method:
I’ll leave the other scenarios as an exercise to the reader. But you get the idea.
These interview questions are just a small sample of the skills that form the backbone of Productive Edge’s Agile SDLC process. These skills aren’t supposed to be an exhaustive checklist--and you don’t have to be a master-level developer in order to be considered a viable candidate. What’s most important to us is that the candidate understands why these concepts are important, values Agile processes, and demonstrates an affinity for learning the necessary skills.
As demonstrated above, concepts like dependency injection, proper usage of interfaces, and other SOLID-related skills are fundamental building blocks to writing testable, maintainable software that can grow gracefully over time. A positive and progressive attitude is important, but matching technical prowess is vital to making the project succeed. Agile isn’t broken--teams just need to have the right skills and behaviors to make it succeed.
A full discussion of Agile-enabling technical skills goes far beyond the scope of this article. But there are some excellent books that can serve as a springboard:
● Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin
● Test Driven Development: By Example by Kent Beck
● Refactoring: Improving the Design of Existing Code by Martin Fowler
From those, you can leap into more platform-specific books, such as Agile Principles, Patterns, and Practices in C# by Robert C. Martin and Micah Martin.