Solving deep technical problems is fun. But by taking a step back and examining how we got here can be just as fun and useful.
We pride ourselves on our technical abilities. But an even more important skill we can have is not technical, it is a pragmatic skill: we can ask "why?".
Why is such a simple word but it helps us evaluate how we got this our current point. What decisions did we make? What influenced us? Has any of the information changed since then? Did we miss a perspective or an option?
I find that this skill, when used with empathy, respect, and patience, proves its value repeatedly. The value is in uncovering new options or in reinforcing the direction we already traversed.
Have you heard one of these before? "This component is just so complex it requires all 2000 lines of code." ... or how about ... "We can't write unit tests for this code."
Over the years I've heard these often. The interactions usually go something like this with a developer team. And sometimes I'm the one helping, and sometimes I am the one receiving the help :)
Me: Why don't you have tests for this component?
Team: This component is just so complex that we can't test it.
Me: Why is it so complex?
Team: The business rules have many variations and branches. Also, there are many rules we have to go get other data so we can make decisions.
Me: Why do the rules and data all have to be in this one component?
Team: Well, they don't but the code is very intertwined. Perhaps we could pull out the data calls.
Me: Good idea. Why do all the branches exist?
Team: The branches help decide the course of action.
Me: Do those decisions have to be made in other places too?
Team: Yes, in some cases. We can break those out into services too.
Me: How do you keep up with the complex logic?
Team: We write tons of comments.
Me: Are the comments all correct?
Team: No, the accuracy is difficult to maintain.
Me: Have you tried moving that complex logic to a function named for what it does?
Team: No, but let's try that now!
These types if interactions are trimmed down a bit, but usually they take anywhere from five to 20 minutes. Let me be clear ... these are all with very intelligent and accomplished teams. And sometimes I have been the one on the receiving end of this (and grateful for the help). We don't always see every angle. We sometimes make assumptions that cause us to miss things.
These types of retrospectives can help illuminate the options.
Open the Code and Refactor
Sometimes I open the code and start refactoring with them. One time in particular I recall a team pushing me out of the way about ten minutes into the conversation and refactoring the code themselves. This was the best result possible, IMO. This was a super bright team who just needed a gentle nudge in the right direction and then they were off and running.
I find that opening the code and doing it with a team via pair programming is a very productive way to help solve problems. The best part is when you hand over the keyboard and the net result is code that is:
- has less or no comments
And my favorite part is that often there are less lines of code overall. It is pretty darn cool when we can accomplish all of this by refactoring our code to be better and have deleted more lines than we write!
Smaller Bites of Shareable Code
Moving code into smaller and more digestible functions make it easier to read, share and test our code. Take any large function and we usually find several conditionals, guard logic, calculations, formatting, and then some final action which returns a value. Try this exercise and break down the function. We may have already done this if we see lots of comments explaining the steps the function takes. (more on comments in a moment).
Now take those individual steps inside the function and replace them with smaller functions. Now our function goes from being a long function doing many things to a series of smaller functions, each doing one specific job. We've now reduced our the responsibilities of each function and made it easier to test, share, and read.
Here is a great example of how we can split up a file that does many things into smaller units.
Replace Comments with Named Functions and Variables
We can take comments and replace them with a function. Why? Consider a comment on some logic that explains how to check for the customer's credit. Why not replace that comment with a function named
let credit = checkCustomerCredit(customer) ? Or better yet, let's move that logic to a customer service and do this
let credit = customer.checkCredit();. Now we move logic to a more testable and shareable state and we remove the need for comments.
OK, so making it shareable is an easy win, but I often run into remarks like "comments make my code more readable". Sometimes they do when used for things that are reporting consequences for things that are not obvious or for TODO's. But the vast majority of the time we can remove comments and replace with variables and functions that make sense and are readable.
This has the added advantage of making our code keep us honest. How? The comment is not a runnable line of code. It is easy for it to become incorrect. If we use a variable name or function name, it helps keep us honest.
Don't be afraid to step back and re-look at the problem. Why are we here?
It is easy to hide in the weeds of the problem, but it's an even better idea to step back and consider a few questions:
- are we addressing the right problem?
- is this going to help us achieve our story?
- is the priority of this important?
- have we considered all angles?
- why did we toss aside these other options?
- if someone else looked at my code, would it make sense to them?
It's OK to re-think. It's OK to make changes. It's OK to ask for a second set of eyes. Of course we all have deadlines, so we don't spend days on this ... but we can do ourselves and our team an injustice if we focus on the trees for the forest, instead of the other way around.
Every once in a while stop. Ask why.