Listen to this post
A software development philosophy guides every developer throughout their career. Some adhere to prevailing philosophies that are written down in books or written down in code. Some invent their own beliefs based on their view of the software world.
My philosophical influences come from the likes of lean Domain-Driven Design, Test Driven Development, the Agile Software Development Lifecycle, and many others.
The following prose is an account of my software development philosophy.
When using distinct technologies, I try not to get wrapped up in the details. Technology changes constantly; light strategic investments provide more long-term value. I invest lightly with my keystrokes, so I have less code to maintain down the road.
I avoid technical debt like the plague. A technical debt mountain is no laughing matter; it will threaten the longevity of your business. I recommend paying the debt now, so you don’t pay more later. It’s frighteningly common to see million-dollar software development projects get scrapped and built for millions again. It’s more often cheaper to pay technical debt than it is to rewrite software.
When building code, I assume change is coming and sooner than expected. I craft solutions that are easy to change in the future to more economically address new business requirements.
I create layers like bastion hosts to protect data rather than using software configuration files and leave configuration files permissive to reduce confusion about access rules. I often remind myself that the only secure computer is a dead computer, and you better be sure it’s dead.
Use the Right Tool
Languages are simply programming tools that exist to help us arrive at a solution. I think clearly about which tool will best handle any job.
In software development, many people use one tool and master it. It can be a great approach to master one tool; some developers will land a big payoff with this approach. I prefer to learn many tools and learn them enough to be productive to allow for full-stack development.
I follow the 80-20 rule when learning new tools to understand its use cases quickly.
Solutions Looking For a Problem
I avoid creating solutions before I know I’m solving a big enough problem. I ask myself a simple question before building anything.
Does solving the problem at hand add significant value?
If a solution to a problem isn’t providing significant value I save the idea for later and focus on high-value solutions.
It is of paramount importance to limit the use cases of software. Command-line utilities are easy to restrict use cases with while user interfaces are not. I do not attempt to handle every use case to avoid introducing a diverse collection of bugs. If a solution is fulfilling too many complex use cases, I consider creating a new solution.
Writing down a clear definition of use cases sets a clear contract between myself and users. When people use software outside of an intended use case, I use documentation to help determine if I have discovered a valid use case.
If I am creating general-purpose software, I limit the use cases as much as possible to general-purpose features that developers can extend.
If it’s flapping around, I nail it down. When tackling large problems, I identify and document constraints before I begin writing any code. Defining constraints upfront limits choices and helps narrow the path to a solution.
I pay particularly close attention to resource constraints and the price associated with those resources. Before implementing an architecture, I’ll go through the constraints to determine any blockers or bottlenecks.
I regularly validate the constraints I defined at the beginning of a solution. If something changes, a new path to a solution may have opened up while in the weeds. I look for opportunities to simplify business problems.
When building software, my philosophy is to version most things. I keep versions pinned, so upgrades don’t happen without a developer’s knowledge and seek a balance between keeping software up to date for security reasons and letting sleeping dogs lie. I abide by the rule; if it’s not broken, don’t fix it.
When naming variables, I name them in ways that make it easy to rename them. I call things exactly what they are and avoid overloading or overusing the same variable name.
I make heavy use of tracer bullets to punch a hole in the walls between myself and a solution. I’ll pick a requirement that exists far away from the end solution and meet it head-on. I’ll use scattershot shotgun methods to prove use cases, theories, and the viability of a solution before investing time and money in building robust solutions.
There Are No Big Problems
As Henry Ford once said, there are just a lot of small problems. Breaking down what looks like big problems into small manageable problems makes software developers look like wizards. 🧙♂️
If I am facing a complex problem, I find the smallest problem I can solve and work from there.
Driving Business Value
Everything I build has the end-user requirements placed front and center in my mind. Having a fancy contract with a lot of complexity won’t help sell more products than a simple one. If I can’t rationalize why a feature provides the highest value at a given time, I’ll raise my hand or simply change course on my own.
I keep software components isolated with a single responsibility and create user interfaces with a single responsibility to reduce complexity. I keep tools divided from one another in a way that is logical for users and other software developers, and I follow SOLID principles when creating object-oriented software solutions. I believe that many software problems are caused by not following this principle.
Development Operations (DevOps) has transformed software development. Modern software development practices have greatly benefited from the idea of DevOps. DevOps requires that software development teams take full responsibility for the products they create. The DevOps maturity model increases the quality of products and increases the speed of delivery to the market.
I am fully bought into this philosophy and work to push team members towards continuous improvement of build pipelines, testing methodologies, and the other DevOps best practices.
There Is Always A Design
A design always exists. An unplanned design is a bad design, but it is still a design. Designing a maintainable, malleable, and easy-to-understand system is of paramount importance. A poor design becomes a huge liability for a company and will slow a development team to a crawl.
I insist on a good design upfront. My primary goals include making a system sufficiently flexible, easy to understand, and low in long-term cost.
I work diligently to understand what people need to do with the software I build. I ask why five times to understand the real problems and keep the big picture in mind. Empathy for people is a cornerstone of a robust software development philosophy.
When dealing with difficult problems, I step slowly through the problem. I’ll make a single change then step through a debugger to see the results. Slowly stepping through problems helps me isolate them and solve them one at a time.
I automate tasks religiously. If it’s hard to automate something and it will cost a fortune to automate, I consider a different solution.
Test For Life
I like focusing on solving problems. Without robust integration tests, unit tests, and any other test, we spend time creating problems for ourselves.
I write tests because I don’t like manually running an app I’m working on after every change. An automated test suite helps me sleep at night, knowing that the products I build are top-notch. Time spent coding without testing increases the time spent working on QA errors, investigating production issues, and troubleshooting broken builds. Doing those things are prevented by testing; therefore, I test for life.
When tight couplings exist for no good reason, I will decouple them. I summarily dismiss software libraries that aren’t dependency injection-ready. I refactor tightly coupled code to reduce complexity and prepare it for change.
No Perfect Solutions
I know that picking the best solution, at the moment, with all the facts laid out, is the way to avoid analysis paralysis.
I know that solutions can’t be fast, cheap, and good at the same time, yet strive for the right balance based on functional requirements.
Conway’s Law Is True
Conway’s Law states that a system design is influenced by the organization’s communication structure building the system. If the communication flows are complex, the systems will also be complex. I look at the flow of information in a company to help me understand its systems. If the flow of company information is inefficient, I will propose a better solution.
Open Source is Life
I make use of open-source software whenever possible. I make sure the open-source software I use is well maintained, and I make safe bets on a project’s longevity. I’ll contribute to a project if it aligns with my development needs and enjoy the synergies that open-source software development models provide to businesses and software developers.
Can != Should
The software development world is full of many tools, methods, design patterns, and best practices. Oftentimes choice overload leads software developers to believe one tool can rule them all. 💍
Picking the wrong tools to solve your problem leads to technical debt and wasted time. Before tackling a problem, I will research what common methods are being used and choose the best tools for a specific problem.
A Software Development Philosophy Evolves
My software development philosophy is evergreen. It changes after learning new things. This account of my development philosophies is nowhere near complete, and it never will be. As always, a great magician never reveals all his tricks.
I refine my software development philosophy regularly in the spirit of continuous improvement.
Develop your own philosophy
Here are a few books that will help you develop your own software development philosophy.