Denial
I think that all of us can recall the feeling three months into our first Software Engineering job, coming up with a solution that includes every advanced language feature we could possibly think of. That desire doesn’t come from a bad place – maybe we think it’s the most generalizable, abstract solution to potentially changing requirements. Maybe a small part of it is just compensating as we struggle to acclimate to the idea of being the most ignorant person in the room.
# This is brilliant…
class LoggerMeta(type):
def __new__(cls, name, bases, dct):
for attr, val in dct.items():
if callable(val):
dct[attr] = cls.log_decorator(val)
return super().__new__(cls, name, bases, dct)
@staticmethod
def log_decorator(func):
def wrapper(*args, **kwargs):
print(
f"Calling {func.__name__} with {args} "
f"and {kwargs}")
return func(*args, **kwargs)
return wrapper
class Calculator(metaclass=LoggerMeta):
def add(self, x, y):
return x + y
# But I like this
def add(x, y):
print(f"Calling add with {x} and {y}")
return x + y
Despair
This goes on until you’re on the other side of that mess, until you maintain a product and see the evils of complexity – bugs, operational cost, difficult troubleshooting. I assure you, when you’ve just been paged in the middle of the night, sniffling with used tissue paper littering your desk, having not slept well the night before because you’d just had a fight with your partner, and the first clue leads you to code that’s abstracted seven levels of hell deep, you will regret the pristine, polymorphic, meta-decorated masterpiece you wrote three years ago.
Anger
Then, one day, you’re in a planning meeting with your Engineering and Product Managers, discussing the prioritization of the last item above the line – and are disappointed when the decision is made to prioritize a customer-facing feature over that juicy chunk of tech debt you’ve been championing to pay off for four quarters now. At this point it’s not tech debt, it’s a campfire story the team nervously laughs at. That’s when the realization hits – simplification never happens. It’s hard to justify a time investment that doesn’t drive revenue. Code will only ever get more complex. I know that for me, it’s harder to review code than it is to write it. If I write the most sophisticated code I can muster, then by definition I won’t be clever enough to understand it years into the future.
Acceptance
The most pragmatic thing we can do is to slow down the rate of ever-increasing complexity. Championing simplicity, even if it makes us look like a troglodyte, because simplicity is the ultimate sophistication. It requires making our own mistakes to get there. Change the code when requirements change, but not before. Simplicity is when the design does not surprise new team members. Perfection is not when you’ve added all that you could; it’s when there’s nothing left to remove. Come up with a design that others won’t even notice is there. Code like you drive: try to be the least surprising driver on the road. Consider finding creative outlets that don’t involve architecture diagrams. Boring is good. Keep your curiosity, keep your creativity, but consider channeling it toward problem solving rather than the minutia of design.
Wisdom
Finding the balance. Harmony comes when you develop the judgment to know when to design for resilience. There are, in fact, times when adaptability and associated complexity is Good (™). Understand when certainty is low, and no amount of data gathering will raise it. Knowing that requirements might change with a probability that’s just high enough to warrant sophisticated design. It’s rare, but it happens.