Software Engineering Principles

Contributor: Christopher White

Software Engineering Principles

Software engineering is an incredibly complex topic. At our company The Sneakers Agency, we simultaneously work on a lot of different projects for all kinds of clients. These are the principles and tactics that we follow to keep ourselves grounded and insure that we’re making reasonable technical choices given requirements, budgets, timelines, and expectations.


Principle: Build on Solid Foundations

One of your biggest goals is to make extending and improving a system/application as easy and safe as possible given timing and budget.


Tactic: Start With a System Diagram

We oftentimes don’t get incredibly detailed feature requirements, but we still have to make early architectural decisions. The main qualities you’ll want to aim for are the same timeless classics in every project: solidity, maintainability, extensibility and scalability.


Tactic: Verify Your Decisions

It can be difficult to anticipate all of the eventualities associated with certain architectural decisions. Consult with your peers, team leaders, and senior engineers to get a second opinion. For large systems, obtain official sign-off.


Principle: Choose the Boring Solution

Software development is complex enough by nature. When in doubt, go with the boring solution.


Tactic: Recognize Inherent Complexity

We’re all guilty from time-to-time of downplaying complexity. We want to believe that we’ve stumbled on a simple, elegant solution to a problem. Take the time to review your approach and think about edge cases that could cause problems. Also, look out for the interests of your teammates by choosing a straightforward approach that is easy to maintain and understand.


Principle: Modularity

No matter which framework, language, or programming principle you prefer: always shoot for modularity in your architecture and code.


Tactic: Think Extensibility, Maintainability, and Longevity

No one can plan 100% for how their code or system will be used in the future, but that doesn’t mean that some upfront planning isn’t needed. When designing a module, class, or even a function, think about inputs and outputs and singularity of purpose. Design the interfaces to make it as difficult as possible for someone to use it incorrectly, but if they do use it incorrectly to let them know via well thought out error cases.


Principle: Aim for Simplicity

As much as possible, avoid making things more complex than they need to be.


Tactic: Keep It Simple, Stupid (KISS)

A simple solution will always beat the overengineered solution. When a simpler solution seems sufficient right now, you should always aim for it.


For instance, complex code should be avoided for the following reasons:

  • Complex code is a perfect hiding place for mistakes.
  • Complex code is hard to understand, for your coworkers and yourself.
  • Complex code cannot easily be extended.
  • Complex code cannot be reused. And, last but certainly not least, writing complex code will cause your teammates to brand you as an outlaw.
Tactic: You Ain’t Gonna Need It (YAGNI)

Stay modest when planning the volume and scope of your implementations. This requires close coordinate with product teams/stakeholders. Will users really need this feature? Will they need this option within a feature? These questions of course will translate to our code: will we really need that class / module / routine?


Principle: Shoot For Low Cost of Ownership

The systems that we design, build and deploy have real world costs and consequences associated with them.


Tactic: Understand Who Will Maintain the System+Hosting

A lot of times when we are designing and building systems, we may think that there will be an information technology (IT) team available to support it. While this can often be the case for mid-size and larger enterprises, this is rarely the case for smaller clients. Further, even for clients with their own IT teams, they may not be familiar with the platforms and tools you’ve chosen to use or not have the time to maintain it. Always think with the end client in mind.


Tactic: Estimate Costs

Hosting and serving content like photos and especially videos can get expensive over time. For platforms with heavy content requirements, estimate the costs of storing and delivering the content. Check with the product owner to estimate the overall needs for the system. Where possible, make use of systems like Content Delivery Networks (CDNs) to help keep delivery costs to a minimum. Also estimate the web and database server usage and costs. Making the right choices here can save clients hundreds if not thousands of dollars a month.


Principle: Create Good Application Programming Interfaces (APIs)

An easy, thoughtful API is probably the part of your software where quality matters the most. Your colleagues might forgive you a little sloppiness in the internals of this or that method. But they won’t (and shouldn’t) forgive you for creating a bad API.


Tactic: Focus on Features, Inputs and Outputs

As the saying goes: “Garbage In, Garbage Out.” APIs are no different. Create a simple spreadsheet listing out what you know about the current feature requirements that the API needs to support. For each requirement, list out the inputs and outputs. This will help lay the foundation for your API and help you find opportunities to design the API as cleanly as possible.


Tactic: Use Good Names

Use verbs and action words to help someone understand what is being performed. Don’t assume someone knows what you mean and so therefore try to avoid abbreviations.


Principle: Design Patterns Are Your Friend

Many have come before you. Take some time to see if there is something to be learned from others and their solutions.


Tactic: Requirements First

Programming paradigms should be used where they fit and not be enforced. Your components should be designed with your application’s requirements in mind — not with a beautiful design pattern.


Tactic: Anticipate Consequences

In cases where you’ve indeed found a helpful pattern for your current problem, there’s only one more thing: be sure to really understand the pattern and its consequences on your coding.


Principle: Stand-Alone Environments Are Worth the Investment

Once a system is available to the outside world, it’s off limits to daily development and testing.


Tactic: Two-Environment Minimum: Staging and Production

When you think in terms of distinct environments, you ultimately change the way you think about how you architect systems, how you manage configurations, and how you construct your code. Every system must have stand alone staging and production environments so that we can fix and test issues on staging before we are ready to promote them to production.


Tactic: Always Be Deployment Ready

Take the time to set up your two environments as early in the development process as possible. Also insure that you are able to easily and repeatedly deploy to either environment whenever you like. This will allow you to focus on feature development instead of environment configuration and setup and will allow you to be production ready in theory from day one.


Principle: Embrace Best Practices

“Best practices” — in the form of proven concepts, conventions, patterns, and high-quality libraries — should always be your first point of reference. After carefully verifying that those best practices aren’t suitable for your special case, you’re free to go your own way.


Tactic: Don’t Reinvent The Wheel

Some software engineers follow the “roll your own” philosophy when it’s time to build something. Unfortunately, this approach has many pitfalls. It’s usually the case that your first assumptions about a component or system are overly simplified and not entirely thought through. You’re also discounting all of the work and bug fixing and hardening that someone else put into to building a library or framework. Further, once you “roll your own” you’ll have to take time supporting it and fixing bugs. Take the time to understand if an off-the-shelf solution will get the job done well enough. It will more than likely save time and money.


Principle: Fashion-Driven Development

Just because it’s new and shiny doesn’t mean it works well.


Tactic: Embrace What’s New When It’s Ready for Production

A new framework or library that you heard about might look incredible on the surface, but there are often dangers lurking beneath. We have a duty to our clients to develop solutions that are production ready which means that any dependencies we rely on should be production ready as well (unless a client is asking for an incredibly bleeding edge feature for example). Do your research and insure that something is ready for prime time. It takes time to build robust libraries and platforms that don’t have major bugs.


Principle: Stack-Overflow-Driven Development Has Its Problems

Embrace Stack Overflow as a good source of guidance for certain problems. But also take the time to thoroughly and honestly evaluate if you’ve found a real, solid solution.


Tactic: Don’t Take Things at Face Value

You have to watch out and resist the temptation to take code that works for code that’s good. What you’ve found on Stack Overflow is — in all but the rarest cases — not a solution but rather a clue. It can certainly make for a great pointer, but it was not written with your exact problem / requirements / constraints /code base / application in mind. And sometimes, it might simply be a dirty hack.


Principle: Source Control Management Is Your Friend

The importance of always using source control management software like Git can’t be overstated. There’s absolutely no excuse for not using source control management on any project. We’ve heard all the excuses, trust us.


Tactic: Always Think Worst Case

When creating software, we depend on hard drives to store/save our code. This could be on your local machine. This could be on a server. Unfortunately, the reality is that hard drives fail for various reasons. Your code is never 100% backed up unless it’s been pushed to a managed source control repository.


Tactic: Think About Those Who Will Come After You

Think about the end. All of us eventually part ways with a software project and you should anticipate and plan for your own eventual departure. By ensuring that you are backing up your code to a source control repository you are making it possible for those who come after you continue where you and others have left off as efficiently as possible. We oftentimes take over projects from other agencies and there is nothing worse than not being given a source control repository to start working from.


Tactic: Management of Intellectual Property is Essential

Our clients depend on us to be good managers of what is one of their most valuable assets which is their intellectual property (IP). This IP, in most cases, is all of their software code. If we were to lose some of this code due to something like a hard drive failure on an engineers machine this would not only be damaging to our client but damaging to us as a software agency.


Tactic: Branches Are Your Friend

We use git for source control management. Given the design of git, branches cost nothing to create. When in doubt, save the code you are working on in a branch and push that branch to the main code repository. There’s no harm in doing this and it will ensure that you or anyone else can access this code later.


Principle: Trust, But Verify

In the rush to get things done, don’t be overconfident.


Tactic: Take The Time to Double-Check Your Work

If there’s one thing we’ve seen from most software engineers, ourselves included, it’s that we’re oftentimes overconfident in our work. Avoid the temptation to set it and forget it. After you’ve completed a task, take the few minutes to double-check your work. Did you leave some test configuration values in your code? Did you test edge cases? Did you do a quick high-level test of the functionality with the rest of the system? By doing some quick verification and catching bugs early, you save everyone time and money.


Principle: Easy Onboarding

Engineers aren’t mind readers, make it easy for them to get going.


Tactic: Clear, Concise Readme’s

When a new engineer is starting on a project there’s a lot to come up to speed on. They need to get their local development environment configured. They need to know about the architecture, how to make builds or perform deployments and any number of other things. Create a Readme for your project and ideally place it in your source code repository so that someone has all of the information they need to get started and make informed contributions to the project.


Principle: You Do Not Own Your Code

Avoid the tendency to feel some level of personal ownership over your code.


Tactic: Learn to Give and Receive Constructive Criticism

When you’ve worked hard on something, you start to develop a personal relationship to it. This is natural and at times can be beneficial, but it can become an issue when you start to feel that the code is yours. Ultimately, the code belongs to your company or your client or to a community in the case of open source projects. Take the opportunity to learn when someone gives you constructive feedback and practice giving constructive feedback to others.



We can’t take 100% credit for coming up with something like this.


Our Development Philosophy (1): Architecture, Design Patterns and Programming Principles