In this post, I articulate my perspective on the nature of what I call a software development do-gooder. I then talk about how to minimize the negative effects of the software development do-gooder while maintaining the positive aspects.
At a high level, the classic software development do-gooder is a person who wants to do "good" things above and beyond his or her normal tasks to improve the overall product. Because the do-gooder implements these well-intentioned actions outside of his or her own regularly assigned tasks, they are often done hurriedly and almost as a side thought rather than being well-planned and thought out. Negative consequences most commonly follow a do-gooder's actions when the do-gooder failed to consider all ramifications of a change that seemed simpler and less involved than it really is.
Examples of Software Development Do-Gooder Actions
There are numerous examples of actions that fit the this idea of a software development do-gooder. A small set of these are listed next.
"Fixing" code that is not well understood. It is easy as a software development do-gooder to see some code that appears to be blatantly incorrect and decide to fix it. If it turns out the code was actually (surprisingly) correct, then the "fix" might actually break it. Even if this mistake is quickly realized, there is wasted effort.
Applying best practices and standards hurriedly to another developer's code. I am a strong believer in best practices as evidenced by my articles on JSP Best Practices, More JSP Best Practices, and Basic JPA Best Practices. These are best used with engineering judgment (knowing when it is most appropriate to apply them) and are best used throughout development. The problem I have seen in this case is when an "improvement" is being made to meet a standard or best practice and working code is broken in the process. There are many specific examples in this context such as cleaning up seemingly dead code that is actually being used somewhere, improving an API that is actually used somewhere not expected, starting to make code changes that ripple much deeper into the code than expected (typically because of too tightly coupled code), and so forth.
Blurring Personal Preferences with Real Value It is sometimes difficult to distinguish between do-gooder actions intended to truly improve software from actions simply intended to employ one's personal coding and stylistic preferences. While the negative consequences of a well-meaning do-gooder's changes are not particularly welcome, they are easier to take than negative effects of changes that are merely opinionated and stylistic. An example of this is changing names of variables to meet one's own preferences and then requiring others to spend extra effort merging code with those trivial changes. It is easier to think that the code is better because of the name changes, but is it really or is it just better in one person's opinion?
Why the Do-Gooder Runs into Problems
In general, the do-gooder negative consequences seem to come out when the developer means well, but actually causes more trouble than good because he or she made one of these classic mistakes: underestimated the effort required for the fix or new effort; lacked understanding of code, technology, or domain; failed to realize effects of change on all stakeholders; addressed an issue more rapidly than the task really required; or failed to communicate with developers who could explain why something was done the way it was or what other unforeseen effects a given change would have.
Reducing the Negative Impact of the Do-Gooder
A Do-Gooder often actually ends up doing good things as intended. However, when the do-gooder does mess up, it is nice to have approaches to undo the bad.
Use Configuration Management Because many of the most costly results of a well-intentioned, but ultimately flawed, action are in code or other documentation and are often easily identifiable, it is helpful to have a decent configuration management (CM) system that allows changes to be rolled back to the previous known and correct state. With CM tools almost as prevalent as programming languages, there are many reasonable alternatives from which to choose an appropriate CM solution.
Unit Tests and Regression Tests Perhaps the best way to know if a seemingly innocent "improvement" has not broken a working system is to have unit tests and regression tests that prove the code still works as desired. Compiler checking will catch some errors, but the really costly ones are the subtle runtime errors introduced by do-gooder's seemingly minor change. Unit tests and regression tests can improve one's chances of catching these before committing them.
Clear Code A configuration management system and unit and regression tests are good tools for determining that a particular change has not had a bad effect and undoing an improper change. A good preventive step to avoid making unnecessary changes in the first place is to have clear code. Clear code is more likely to not tempt the do-gooder to try to tweak it or "improve" it unnecessarily. Clean code that works properly does not offer a lot of incentive to the do-gooder looking to do good.
Comments on Unorthodox Code Many developers believe strongly in letting the code speak for itself and reducing comments. I have no objection to this as long as the code is actually clear and clean as described in the last item. However, there are times when circumstances out of our control may dictate less clear code than we'd normally like. In such cases, especially when things are done that seem counter intuitive to traditional software development practices, it is important to comment on why the unorthodox approach is being used. At the very least, the comments will help the do-gooder know what was intended and perhaps the do-gooder's improvement will still be a good idea. On the other hand, the do-gooder may realize from the comment that there is no "easy" or "quick" way to remedy the convoluted code and won't even try until he or she is prepared to spend the required effort on the task. Code like this should be refactored with serious thought and not in a fly-by do-gooder approach.
As with many problems, the negative consequences of a do-gooder action can be reduced with better communication. Face-to-face discussions, e-mail, instant messaging, and the telephone all provide a quick way to find out if a do-gooder's proposed change is free of negative consequences. The do-gooder can also communicate after a change is made to let others know what it is and why it was made. Furthermore, steps such as using Java
@Deprecatedannotation and Javadoc tag
@deprecatedto more slowly introduce and communicate a particular change (such as removal of seemingly dead code) can also be advantageous.
Consider Real Value and Cost
The do-gooder should also consider what the actual value of a particular change is and compare that to known and potentially unknown costs and negative consequences.
In general, the software do-gooder often does many useful and truly good things. However, when the do-gooder is not careful, he or she can cause more problems and more trouble than the benefits of his or her action are worth. We don't want people to stop trying to do extra and we don't want to document and describe every little thing in intricate detail. However, we also want to take preventive measures to prevent and minimize the inadvertent negative consequences of a do-gooder's actions.
The do-gooder can minimize the frequency and severity of negative consequences by reducing naivety via better communication, through spending more time understanding the problem and its effects, by using configuration management tools, and by building and using unit and regression tests.