JimmyVanVeen.com
Recently, I had a brief but thought-provoking exchange on X (formerly Twitter) with Aaron Francis, a well-known voice in the web development community, particularly within the Laravel and PostgreSQL ecosystems. Aaron posted a sentiment that I think many developers grapple with:
I'm constantly oscillating between "no one does hard things, so doing hard things is an advantage" and "you don't get bonus points just for doing it the hard way"
It’s a relatable feeling. On one hand, there's a pull towards tackling significant challenges, pushing boundaries, and knowing that real growth comes from facing difficulty head-on. On the other hand, there's the pragmatic realization that unnecessary complexity, just for the sake of it, is often unproductive and even detrimental.
My response was concise:
Don’t do things the hard way. Just do hard things
This might seem like a simple statement, but it encapsulates a core philosophy that I’ve developed over my years in software development. It’s a principle rooted in values about efficiency, user-centricity, and sustainable growth. Today, I want to unpack this idea, to delve deeper into what it means to "just do hard things" and why it's so crucial to avoid "doing things the hard way."
Values as a Compass in the Code
Before we dissect the nuances of "hard things" versus "hard ways," it's important to understand the foundation upon which this philosophy rests: values. As developers, we are not just code compilers; we are problem solvers, architects of digital experiences, and builders of tools that shape the world. Our work is deeply intertwined with choices, decisions, and trade-offs. And in these moments of decision, our values act as a compass, guiding us towards the best course of action.
Think about it. When faced with multiple approaches to a problem, how do you decide? Is it speed of implementation? Long-term maintainability? Scalability? Cost-effectiveness? User experience? The answer, often, is a blend of these, prioritized and weighted based on your core values.
For me, one of those core values is a deep-seated pragmatism. It’s a belief that efficiency and effectiveness are paramount, and that complexity should be embraced only when it directly serves a purpose – not for its own sake. This value directly informs my stance on "doing hard things" without "doing things the hard way."
No Virtue in Unnecessary Difficulty
Let’s get straight to the heart of it: there is no inherent virtue in making things harder than they need to be. This might sound counterintuitive in a culture that often glorifies struggle and "hustle." But in the context of software development, and frankly in many areas of life, it’s a crucial distinction.
When I say "no virtue in doing hard things for the sake of them being hard," I'm not advocating for laziness or cutting corners in quality. Far from it. What I mean is that the difficulty of the process should not be the primary metric of success or value. The value lies in the outcome, in the impact, in the problem solved, and in the experience created.
Let's dig a little deeper into this. If you choose to build a feature "the hard way," perhaps by reinventing a wheel that already exists, or by using overly complex technologies when simpler ones would suffice, what is the direct payoff? Often, the answer is: very little, if anything, for the user or the project itself.
You might argue that there's an indirect payoff. Perhaps you’re building skills by tackling a problem in a more challenging way. And that’s a valid point. Deliberately choosing a harder path for learning and skill development can be incredibly valuable for you as a developer. But it’s crucial to recognize this distinction: you are investing in yourself, not necessarily in the immediate outcome of the project.
And this is where the danger lies. If we conflate personal skill-building with project needs, we can easily fall into the trap of "doing things the hard way" under the guise of learning or rigor, while actually hindering progress and adding unnecessary complexity to the system. The feature you're building "the hard way" won't inherently be better, more performant, or more user-friendly simply because you chose the most convoluted approach.
"Doing Hard Things": The Essence of Growth
Now, let’s pivot to the other side of the coin: "doing hard things." This is not about shying away from challenges; it's about embracing them strategically and effectively. "Doing hard things" is the very essence of growth in any field, especially in the ever-evolving landscape of software development.
"Doing hard things" means tackling genuinely complex problems. It’s about diving into performance bottlenecks, architecting scalable systems, implementing intricate features, or refactoring legacy codebases. It’s about confronting the challenges that truly matter – the ones that push the boundaries of your skills and deliver real value to users.
When you focus on "doing hard things," you’re directing your energy and expertise towards meaningful endeavors. You're not getting bogged down in unnecessary complexity for its own sake. Instead, you're strategically choosing battles that will yield significant results, whether it's improved performance, enhanced user experience, or a more robust and maintainable codebase.
The Art of Sensible Shortcuts: Efficiency as a Virtue
One of the key aspects of "just doing hard things" is the willingness to take "sensible shortcuts" along the way. I want to emphasize the word "sensible" here. This is not about reckless abandon or cutting corners that compromise quality or security. Sensible shortcuts are about efficiency, about leveraging existing tools, patterns, and best practices to achieve the desired outcome without unnecessary toil.
Think about it. In web development, we have a vast ecosystem of libraries, frameworks, and pre-built components. Why reinvent the wheel when a well-maintained, robust library can handle a common task with ease? Using a battle-tested framework like Laravel for backend development or React for frontend development is a prime example of taking a sensible shortcut. These tools are built by communities of experts; they embody years of collective knowledge and best practices. Leveraging them is not "doing things the easy way"; it's "doing things the smart way." It’s about focusing your precious time and energy on the truly hard and unique challenges specific to your project.
Sensible shortcuts can also include:
- Following established design patterns: Instead of inventing a novel, untested architecture, adopting proven patterns can save time and reduce risk.
- Iterative development: Breaking down a large, complex task into smaller, manageable iterations allows for faster feedback, reduces the risk of major missteps, and delivers value incrementally.
- Prioritization: Focusing on the most critical features first and deferring less essential ones allows you to deliver core value quickly and adapt based on user feedback.
- Using boilerplate and generators: Tools that automate repetitive tasks, like setting up project structures or generating basic code, free you to focus on the more complex and creative aspects of development.
These "shortcuts" are not about being lazy; they are about being strategic. They are about recognizing that time and resources are finite and that focusing on the truly hard, impactful aspects of a project often requires streamlining or automating the less critical ones.
Deep Work and Strategic Focus
"Doing hard things" while avoiding "doing things the hard way" allows for a deeper, more focused approach to problem-solving. When you're not constantly battling self-imposed complexity, you can dedicate your cognitive resources to the genuinely challenging aspects of the task at hand. This fosters a state of "deep work," as Cal Newport describes it, where you can concentrate without distraction and produce high-quality results.
Imagine you're tasked with optimizing a slow-performing database query. If you’re someone who habitually "does things the hard way," you might start by trying to rewrite the entire query from scratch, experimenting with obscure SQL features, and delving into deep database internals without a clear plan. This approach is likely to be time-consuming, frustrating, and potentially ineffective.
On the other hand, if you approach this with the "just do hard things" philosophy, you'd start with a systematic analysis. You'd use database profiling tools to pinpoint the bottleneck. You'd examine the query execution plan to understand where the inefficiency lies. You'd explore indexing strategies, query optimization techniques, and potentially consider refactoring the data model. This strategic, data-driven approach is more likely to yield effective results, and it allows you to focus your "hard work" on the areas that truly matter.
Keeping the User in Focus: The Ultimate Goal
Ultimately, the software we build is for users. Whether they are customers, internal teams, or the wider public, our work serves a purpose for someone else. And this user-centric perspective is paramount when deciding between "doing hard things" and "doing things the hard way."
"Doing things the hard way" often becomes an inward-focused exercise. It's about the developer's ego, about proving something to oneself or others, or about getting lost in the technical details without considering the broader impact. This can lead to solutions that are technically impressive but practically cumbersome, slow, or difficult to use.
In contrast, "just doing hard things" keeps the focus firmly on the user. It's about solving their problems effectively, efficiently, and elegantly. It's about building software that is not just technically sound but also user-friendly, performant, and valuable in their lives or work.
When you prioritize the user, the choice between "the hard way" and "the smart way" becomes clearer. You ask yourself: "Which approach will ultimately deliver the best outcome for the user?" Often, the answer points towards sensible shortcuts, efficient solutions, and a focus on tackling the truly hard user-facing problems, rather than getting lost in unnecessary technical complexities.
Building Resilience Through Strategic Challenges
Finally, "doing hard things" – when approached strategically and efficiently – is a powerful way to build resilience as a developer. Resilience is the ability to bounce back from challenges, to learn from setbacks, and to persevere in the face of adversity. It's a crucial trait in the demanding and ever-changing world of software development.
When you consistently tackle genuinely hard problems, you build confidence in your abilities. You learn to navigate complexity, to break down large problems into smaller parts, and to find solutions even when the path forward isn't immediately clear. Each successfully overcome challenge strengthens your resilience and prepares you for future difficulties.
However, "doing things the hard way" can actually undermine resilience. When you consistently choose unnecessarily difficult paths, you risk burnout, frustration, and a sense of being constantly overwhelmed. The struggle becomes less about overcoming real challenges and more about battling self-imposed obstacles. This can erode confidence and make it harder to face future difficulties with optimism and determination.
"Just doing hard things," on the other hand, fosters a positive cycle of growth and resilience. You're constantly pushing your boundaries, but in a way that is strategic and sustainable. You're learning to differentiate between challenges that are worth tackling and complexities that are best avoided. And with each success, your resilience grows, making you a more effective and adaptable developer.
When "The Hard Way" Becomes Necessary
It's important to acknowledge that there are situations where "the hard way" might become necessary. Sometimes, performance constraints, security requirements, or specific technical limitations necessitate a more complex or difficult approach. For example, optimizing a critical system for extreme performance might require diving into low-level code, employing intricate algorithms, and pushing the limits of technology. Securing a highly sensitive application might demand layers of complex security measures and rigorous testing.
In these situations, "the hard way" isn't about gratuitous complexity; it's about necessity. It's about tackling a genuinely hard problem that requires a more intricate and challenging solution. The key is to approach these situations with intention and a clear understanding of why the more complex approach is necessary. It should be a deliberate choice driven by external factors, not a default preference for difficulty. Even in these cases, the goal remains to "do hard things" effectively, not to simply make things harder than they need to be if simpler alternatives could meet the requirements.
Embrace the Challenge, Choose Your Battles Wisely
So, let's bring it all back to the initial exchange. Aaron's oscillation between the allure of "doing hard things" and the pragmatism of avoiding unnecessary difficulty is a reflection of a common developer's dilemma. My response, "Don’t do things the hard way. Just do hard things," is not a dismissal of challenge; it's an invitation to embrace it strategically and effectively.
As developers, we should strive to tackle genuinely hard problems – the ones that push our skills, deliver real value, and contribute to meaningful outcomes. But we should also be mindful of efficiency, avoid unnecessary complexity, and always keep the user at the heart of our endeavors.
Let's choose our battles wisely. Let's focus our energy on the challenges that truly matter. And let's remember that the goal is not to make things harder for ourselves, but to build better software, solve real problems, and make a positive impact on the world. Don't do things the hard way – just do hard things. It's a philosophy that can guide us towards more effective, more fulfilling, and ultimately, more valuable development careers.