What Makes Good Code?
It’s been a while since I’ve had a programming oriented post, and I figured this would be a great topic to write about. It’s been a topic I’ve been thinking about more and more over the last year and I’ve been experimenting with certain patterns and practices to see if certain things actually make code “better”. A lot of the information presented in this series will be completely based on my opinion, but I’ll try to back up my opinion with as many concrete examples as I can. If you have a differing opinion, I’d love to hear it in the comments.
I’d also like to call out that much of what I’ll be discussing is in the context of object oriented programming. To be specific, there may be mostly C# examples used. If this isn’t something you’re actively doing, then don’t worry! It would be great to hear if you see parallels in the work you’re doing.
What Is “Good”?
So let’s start by defining what “good” or “better” means (and I’ll leave this high level and we can dive in afterwards)…
- Extensible: Re-writing of code is minimized when adding more functionality. It feels straight forward to extend the code. Developers won’t do “the wrong thing” when trying to extend the code.
- Maintainable: Many of the same qualities that go with extensible. Fixing bugs or making tweaks involves touching few places in the code base.
- Testable: It’s straight forward to write coded tests that can exercise functionality of the code under test.
- Readable**: Developers should be able to read code and understand what it’s doing. The flow of execution within and between different modules of code shouldn’t cause anyone a headache,
All of these describe qualities of the code. I could argue that you could write very maintainable, extensible, and testable code and it would be awful if it didn’t actually solve the customers’ needs. I’d like to try and leave this aspect out of the discussion, and focus on the actual patterns/practices that we can implement in code. I’d love to write something separate about writing code that actually solves a problem versus code that may potentially solve some possible problem at some potential point in the future (and yes, all of the uncertainty in that sentence was on purpose).
Why Should We Care?
It’s kind of a funny question, I guess, but I think it’s a fair one to ask. Why should we care what good code is? If we agree on the definition of good code, so what? Maybe those criteria for good code are obvious to some people. Maybe they weren’t so obvious to some people, but they still don’t really care. So why the fuss about what good code is?
If you’ve read other posts on DevLeader or you know me personally, you may know that I started work at a digital forensics startup a few years back in Waterloo Ontario. What you may not know is that that startup has grown significantly, and based on the accolades we’ve received as an entire organization, we’re actually one of the fastest growing software companies in North America in terms of revenue. I’m not trying to do the horn tooting without a reason though… I think one of the reasons we’ve been able to have such great success on the development side of things is because we’re always trying to improve.
We didn’t always write “good” code, and we certainly don’t always write “good” code right now. However, we’re always trying to figure out how we can get better. So why might WE care as developers at our office? I think it comes down to trade-offs.
In the real world of software development, you’re often faced with trade-offs. You can get a product out faster if you don’t test it. Or you can get a product out and test it, but maybe you had to take a lot of shortcuts in the code. Or maybe you can get all the features in the product and tested, but you can’t hit the deadline. There’s countless more combinations of trade-offs that we make in real software development every day. I think that by understanding what “good” code means allows a team to recognize just what kinds of corners they’re cutting sometimes. When teams talk about introducing “tech debt”, there’s a better grasp around what type of debt you’re introducing. If you need to get some extra features and bug fixes in but they’re getting added with some tech debt, what could that end up meaning?
Even if you don’t agree with what my criteria are for good code, I think it’s important that you establish this within your team. If everyone can agree on what good code is, it makes constructive conversations about different implementations much easier. Just because some new code is different doesn’t instantaneously make it scary and/or wrong… Maybe it’s a new way that emphasizes one of the criteria for good code a bit more than another implementation might emphasize. Perhaps it doesn’t… You can at least refer back to a reference point for what “good” is.
It’s also important to recognize that the criteria for “good” may change over time. Revisiting the definition periodically might allow you to recognize when your team is redefining what “good” means to them.
Enough Rambling! Where’s This Going?
Right. Okay. I want to write some follow up posts that will focus on a few of the following items:
- Does every class need an interface?
- Does every class need a factory that can create it?
- Unit tests versus functional tests
- Is there a benefit to only passing interfaces to objects around?
- Is there a way to enforce that interfaces HAVE to be passed around?
- Is the single responsibility principle even helpful?
- Mutability and immutability
- Is it always good to follow patterns and practices in all scenarios?
And after I feel that I’ve covered enough on these topics, I’d like to circle back and revisit what “good” code is. It’ll be cool to see if the definition changes at all!
**Readability was an after thought and I’m not sure how… I started writing the first example post for this series and QUICKLY realized I had omitted this.