Software Development Principles

Bernie Durfee
The Startup
Published in
5 min readJan 4, 2019

Whenever I embark on a project, I always like to define a set of fundamental guiding principles. These principles can and should be revisited often as the project grows to ensure that the basic tenets of the original vision are not lost.

The principles below are basic blocking and tackling that help to reinforce the structure of a software component or system. Unfortunately, there are many, many projects that go forward without properly executing the fundamentals. If you don’t have a set of core principles for your software, these should provide a good starting point.

Separate concerns

No single component should do ‘too much’. Always be mindful of opportunities to refactor large components into smaller ones. Doing so will enhance modularity and provide new opportunities for reuse.

Why?
Flexibility! By componentizing software, whether at the code level or the network service level, the architecture can be more easily refactored in order to adapt to changing requirements.

Use the network to separate components

The SOA and microservices architecture styles are all about decomposing large processes into smaller processes and linking them together using network infrastructure. Segmenting components using the network often increases flexibility, reliability and scalability.

Why?
This principle is intended to push an architecture toward network segmentation of components vs running components in a single process space. In general, distributing application components across a network adds resilience, as compute nodes will fail, but clusters are forever. While there are added costs inherent in a network segmented design, often in performance and complexity, the added resilience of a distributed application is often worth the investment.

Don’t save state locally

Maintaining state within a process typically binds interactions to that specific process. This makes that process special and when that process fails, as processes do, an entire session or workflow may break.

Why?
This principle again guides our designs to decrease dependence on individual compute nodes. Application state should exist within the cluster and should be persisted on shared disk when feasible. This adds further resilience to the application, even when the lights go out!

Communicate asynchronously

Message-based and event-driven systems are typically highly scalable and robust. Synchronous communication consumes resources during long running transactions, leading to blocked and unresponsive interfaces. Synchronous communication also creates brittle systems that often cannot survive partial failures.

Why?
The longer a process runs, the more likely it is to fail. If component A calls component B, which then calls component C, you have A waiting for C to finish. If A doesn’t absolutely need to wait for C to finish, you’re tying up resources and increasing the odds of a failure. If A doesn’t absolutely need to wait for B or C, which is often the case, let A be done as soon as it calls B. Synchronous communication should be the exception rather than the rule!

Security first

Securing distributed systems is difficult, especially when security is not baked into the core architecture. Establish secure design patterns early and reuse those patterns to ensure consistently secure components.

Why?
Because you can’t trust anyone or anything… ever. Unfortunately, human nature being what it is, someone will always attempt to compromise your software the moment it’s out in the wild. Every line of code is a potential vulnerability and only systematic security will ever give you a fighting chance to keep your customers safe. You should always be thinking of ways to hack your software, because you can be certain someone else is!

Log everything always

Distributed systems are complex and troubleshooting can be impossible without enough information and context. All components should allow for detailed trace logging to be easily enabled and shipped to a centralized log service.

Why?
Because we’ve all spent enough time on the troubleshooting end of a bug to know that replicating an issue in a controlled environment is often impossible. Quality logging in production gives the troubleshooters some much needed information to help diagnose or at least isolate when, where and how the issue originated. You can never log enough, but more logging almost always helps during troubleshooting.

Fail gracefully

Commit to taking the time to handle, log and respond to error conditions. User and operator experience is crucial to a successful product.

Why?
Software naturally has many more failure modes than success conditions. Every process in your application walks a tightrope that usually works out for the best, but often does not. Failure modes are as important to consider and handle as successful logic. Unhandled exceptions are critical bugs that disrupt the end user experience and take a heavy toll on the perception of your application. To your customers, one nasty bug can often be more relevant than a thousand great features.

Self describe

All interfaces should be self-documenting. Judicious use of documentation generation tooling like Swagger, JavaDoc, JSDoc, etc. should be in place at the start of a project.

Why?
It’s simply not acceptable to hand someone a Word document with half-finished out-of-date API documentation. If you have an API that you expect someone else to use, especially someone outside of your immediate team, it needs to be self-documenting. Only you think your API is intuitive, everyone else needs instructions!

Try serverless first

Servers are a burden and a liability. Unless you absolutely cannot, you should always eliminate servers by utilizing managed cloud services. If you can’t go serverless, containerize. If you can’t containerize, use a VM. If you can’t use a VM, you really need to think hard before you rack up a server.

Why?
Because operating servers is difficult and you will fail. Your code is complex enough to support without adding the complexity of an operating system into the mix. Delegate that burden to someone else entirely and build your application to adapt and survive in a virtual space that hovers well above servers and operating systems.

Build infrastructure from code

All infrastructure provisioning and configuration should be using code. Never build anything by hand, always code it up.

Why?
Building things by hand can be fun and personally satisfying, but is no way to scale a business. Your infrastructure should be written as code, which will allow it to be provisioned, deprovisioned, reprovisioned and modified consistently. It will also allow people other than you to easily understand and manage the infrastructure.

YMMV

You’ve probably noticed that the principles above have been decorated with words like ‘often’ and ‘generally’. This is because, of course, not every rule applies to every scenario. In fact, these are not rules, they are guiding principles. They can be freely ignored when the situation warrants, but they should be at least considered and the decision to ignore should be deliberate.

--

--

Bernie Durfee
The Startup

IT Professional; Software Developer; Software Architect; Habitual Musician