5 Most Common Anti-Patterns in Enterprise Software
Every codebase has room for improvement. Here are the most common mistakes I've observed and how to fix them! Some are low hanging fruit, while others are larger effort problems that might take you months to address. Good luck!
5. Magic Numbers
Problem: Have you wondered why the value is -1 or 0? So does everyone else. These non-values just confuse both people and downstream code. Bugs magically appear when this anti-pattern is used.
Solution: The easiest way to get out of this is by naming all those numbers.
if (variable == null) { variable = 0; }
Just give it a name! Make it obvious what 0 number means in this context.
final Integer start = 0; if (variable == null) { variable = start; }
BONUS: One step up is to use functions to pass these numbers around, so they are friendly with dependency injection. Which brings us to the next sin:
4. The New Keyword
Problem: New instances of objects are created throughout the code so there is no way to control what exactly the code inside a method is acting on. The main problem is it makes testing very hard. That should be enough for everyone to tackle this one.
Solution: If you are practicing dependency injection, you should have very few or no usages of the new keyword. You don't need anything fancy to fix these, just pass your dependencies as parameters.
String format() { final DateTimeFormatter formatter = new DateTimeFormatterBuilder() .appendMonthOfYearText() .appendLiteral(' ') .appendYear(4, 4) .toFormatter(); return DateTime.now().toString(formatter); }
This locks us into using this particular formatter and you're always formatting now. Living in the now is great, but not so good when you want to format dates.
String format(final DateTimeFormatter formatter, final DateTime dateTime) { return dateTime.toString(formatter); }
Yes, this function is almost worthless, but now you can actually tell that!
3. Copy and Paste
Problem: I think just about everyone who codes knows this one, but it can still be prevalent. One problem is solved, but now the same kind of problem comes up again. Just copy those five lines over to here - done. At first, it is pretty benign. Years go by and that pattern has been repeated ten plus times. Someone new touching the code wants to update the logic due to a business ask. Only one or two of the copies are fixed. Bug(s) introduced!
Solution: Follow this rule to get out of this one: Don't Repeat Yourself. Of course, don't go crazy, but if you find at least three instances of one piece of logic, make it something you can reuse.
To fix it, use a fancy feature in your IDE to refactor the logic out to a new function or class, or simply cut it out and do it yourself. Just make sure you have tests in place to make sure you don't break anything in the process.
2. Not Invented Here
Problem: Developing an internal, custom solution to an already solved, common, difficult problem.
Solution: I think this one gets overlooked a lot in bigger companies, but I doubt it is a problem in smaller ones. Remember, just because you can recreate something that already exists doesn't mean you should. Be very questioning when someone wants to build a new authentication system or a dependency injection framework (actually, you should never use one!).
You're good at coming up with something new to code, of course. You love coding. BUT, just because you can doesn't mean you should. Think of all the lines you (or some unsuspecting engineer in a few years) will be responsible for maintaining. Pay an expert in the job you need done and focus on your unique business requirements.
1. Untested Spaghetti Code
Problem: Yes, I'm lumping two things together here, but I need to make a point about these two. Untested code, bad. Spaghetti code, bad. Both together, REALLY BAD. It makes for nearly impossible to change code without breaking something.
Solution: Requires multiple steps.
- Find a way to test it. This could be end-to-end tests that exercise the business rules at the highest level or component tests that hit the controller level. Whatever your app looks like, get to test coverage that you or your team is comfortable with. It will save you when you start making major changes to this messy codebase.
- Address the previous four anti-patterns. As well as anything else you find difficult to work with. You have the freedom to make the changes needed with the testing safety net.
- Create modularity. Chop up that spaghetti into bite-size pieces that can be tested individually. Establish a comfortable level of test coverage. Bonus points for mutation test coverage and property-based testing.
- Slice it up logically. Choose a pattern and go with it. Hexagonal. Feature-based. This keeps your logic from intertwining again. Keep testing!
- Convert to functional. If you aren't already doing so, use functional programming where it makes sense. Most applications these days have large pieces that work well functionally over objectly. Your code is likely manipulating and reacting to data as it comes into software, so the pattern usually fits. This really helps you keep your logic small, reusable, testable, modular, and simple.
Oh, there are more...
I have no doubt you've seen more anti-patterns daily. Share which you've seen and how you fixed them.
Not currently looking at None
5 年Before you decide to refactor some code or rewrite it, make sure you know what the code does..otherwise you can recreate problems that have already been fixed but come up again during the refactor/rewrite.
Product & Technology Leader
5 年Totally agree with not invented here syndrome - scary stuff. My two, somewhat related?pet peeves: #1?- Designing / engineering for imagined requirements that could potentially come up in the future. Nothing slows down a product more than weeks of code reviews because "what if it had to do this in the future?" Build for what we know now, refactoring later is healthy!?#2?- Over-abstraction. Nothing more frustrating than looking at a codebase that is so abstract as to be unreadable because the author didn't want to commit to calling something what it is.