Quite recently me and a coworker had a lively discussion on what constitutes a great software developer, or, in more blunt terms, what separates the professional from the amateur. This got me thinking about the things I currently think are crucial in any professional software developer. Here’s my opinion on this.
Triggered by this excellent article by Nicolai Parlog, the most annoying thing that can happen if you’re using somebody else’s API is that you have to dive into its implementation to understand how that API is supposed to behave. So properly document the expected behavior of all your public and protected classes and members. Don’t repeat the name of the class or member but explain why it exists, what problem it is supposed to solve and what the boundaries of its responsibilities are. If a class has a strong relationship with another, document how these two work together.
The quality of software can only go two ways. It can improve or it can rot. The easiest way to improve your code base is to make sure each and every change you do improves it. Whether it is the length of a method, the refinement of a method name or by improving the API documentation, it doesn’t matter. As long as it improves the quality a notch or two. If you’re already practicing Test Driven Development, then continuously refactoring your code is already part of your mantra. If you’re fixing a bug, make sure you first add a unit test to proof the bug. If that’s difficult to do, make it easier by refactoring the code so to improve testability. And if you don’t have to time right now to fix a design smell or apply a potential refactoring, consider using the Natural Refactoring workflow to mark code as such.
A clean source control history is one that has the following characteristics:
- Changes are grouped in commits (in Git) of related changes where large-scale renames or file movements are grouped in a separate commits.
- Irrelevant changes such as commits with a message like ‘Oops, I’ve fixed a unit tests’ or ‘Processed review comments’ should not exist.
- The commit should clearly explain why something was changed. The what should be visible from the code changes themselves.
Such a clean history not only benefits those that want to understand why something was changed during a historically analysis, but should also make it much easier for other people to branch and merge from. Especially those that use Git’s rebasefeature will be very sad if they have to resolve conflicts with a messy code base. So, if you’re using Git (and you should), learn how to use its powerful features such as interactive rebases, squashing, fixing up commits and such.
Each component, sub-system or product can have their own level of adherence of the stuff mentioned before. Some need less (spikes, temporary components, proofs-of-concepts), some need more. Boundaries should define interactions and contracts and can help prevent ending up with monolithic code bases. Consider using an anti-corruption layer to protect those boundaries. Each boundary can (should) have their own Ubiquitous Language, so the same term can mean different concepts between two boundaries. Boundaries can also help preventing monoliths, so identifying boundaries is an essential principle that should not be underestimated. It doesn’t happen magically, but modern tooling like those facilitated by Git, Github and NuGet can help set-up your organization to acknowledge boundaries as a first-class citizen.
Even though our clients might think they need it, we’re not all building Formula 1 cars. So don’t jeopardize maintainability by tuning, tweaking and optimizing your code until it performs at top speed but becomes utterly unreadable. That doesn’t excuse you from using the wrong data structures, algorithms and common sense though. Don’t use a List for an index if you can also use an Dictionary. In the unlikely case that you really need to build the software equivalent of a Formula 1 car that can only be maintained by the best, consider extracting the critical components into separate source control repositories ownedby those special people. Again, control your boundaries.
They consider the reversibility of their decisions
Not every decision has to be made right now. Always consider the cost of postponing a decision to its last responsible moment. If it doesn’t matter whether you do it later rather than now, do it later. Following YAGNI properly can really help you in this decision process and prevent analysis paralysis. I’ve seen too many senior developers getting all warm and fuzzy while building the ultimate future-proof solution and completely forgetting that they are working for a firm that at the end of the day needs to make money. I’m not saying that you should postpone all decisions here. I’m just saying that you should understand when you should think tactically and when strategically.
As soon as somebody doesn’t feel confident about changing some code because they fearside-effects, you’re lost. If you detect that kind of fear, immediately analyze the reasons behind it. Is it insufficient unit test coverage? Then try to add the missing part or consider writing a couple of characteristics tests. Is it the lack of user interface testing? Invest in UI testing using Seleniumand BrowserStack (and read this post first). Is it because the deployment involves lots of moving parts? Consider introducing OWIN to easily host all your components in an unit test. Is it an occasionally failing unit test? And warning in a log file that should not really happen? Just analyze the problem and fix it! The worst thing you can do is ignore these kind of problems because of project pressure. The longer you wait, they’ll get more painful and definitely more expensive to fix.
One aspect of avoiding fear of change is to make sure the feedback loop between making a change and getting confirmation that your changes have not caused any side-effects as short as possible. If your unit tests are taking too much time to complete, consider switching to XUnitto benefit from its concurrent execution. If they are just too slow, use a profiler to find the bottlenecks. Overusing IoC containers and mocking frameworks was a big issue for us. Ideally you’ll find some product issues that will benefit the end-users along the way. If your build process is pretty extensive, consider using something like build chainingand artifact dependencies (provided your build engine supports that). Is NuGet to slow for you? Consider using an (on-premise) MyGetcache. Is your Git repository too big? Consider purging a part of its history. Obviously these are just examples. The point is that you should make an effort to solve these problems as soon as possible, and if possible, account for this in your architecture design.
A quality I don’t see that often in developers is the ability to second guess their own ideas. I lost count of how many times an idea of mine got either bunked because it was way too naïve, or got much better with the input of my team members. If you’re so unfortunate to not have anybody around you to challenge your ideas, visit local user groups, events or conferences. They are great for sparring with peers or gaining new ideas and concepts, especially if they offer Open Spaces or Chalk-and-Talk sessions. Blogging about your ideas is similar to asking for feedback, especially if you support Disquss or you’re on Twiter. Presenting about your ideas is the next level, something I occasionally do. Some people are too afraid to go on-stage, but I get a lot of confidence from the fact that I’m presenting my own experiences. I might be wrong about something, but in the worst possible case you learn something new from it.
I see myself as an all-round professional with a wide range of skills covering .NET, architecture styles, design patterns, agile development, application lifecycle management with a strong focus on building maintainable software. I’m nowhere an expert in any of those fields, but I do know who to talk to if I really need in-depth knowledge and experience on a certain topic. Twitter, StackOverflow and Gitter are my friends here and will never let me down.