Software Rules

I started keeping this list about two years into my career. It’s ended up becoming a nice running summary of things I’ve learned while writing software.

I definitely do not always follow these rules, but when I do I find things generally go more smoothly. Hope you find at least a couple tid-bits in here that are helpful to you :)

Simplicity

Most items in this doc are in alphabetical order, but simplicity is such a first order concern for writing software that it deserves to be at the top. In an amusing twist of events, simplicity can actually get pretty complicated.

  • Simple software is Easy To Think About (ETTA). Simple software places low cognitive strain on its owners/users/etc.

  • Simple software is Easy To Change (ETC).

  • Components are a key element of simplicity. A component is a unit within a system that is independently replaceable and upgradeable. Clear boundaries make components easy to think about.

  • Orthogonality is a key element of simplicity. Simple software is composed of orthogonal components. Their components are not intertwined. You can change A without worrying about B.

  • Choose simple constructs over complexity-generating constructs (example: functional interfaces vs methods/side-effects, value/data/immutability vs changing state, etc.)

    • Constantly re-evaluate your toolkit to use fewer complexity-generating constructs and more simple constructs.

  • Simplifying a problem space before you start working on the problem is a powerful technique.

  • Having few pieces is not the same as being simple. It can be part of simplicity, but it’s not the same thing. In fact, simple systems tend to be composed of many obvious/transparent components instead of fewer opaque pieces.

Business

  • Embrace your role as a software business owner. Understand your business, its goals, and its opportunities.

  • Reject the notion that developers can operate effectively as siloed code monkeys. You must understand your business to be effective.

  • Practice Genchi Genbutsu - Go and see for yourself. Value primary sources over hearsay.

  • Practice Kaizen - the process of continuous improvement. For your products, your tools, your processes, etc.

  • Average teams “See something, say something”. Great teams “See something, do something”.

Clean Code

  • Clean Code is Easy To Think About (ETTA). A clean codebase places low cognitive strain on its developers.

  • Clean Code is Easy To Change (ETC).

  • Bad Code is hard to think about. A bad codebase places high cognitive strain on its developers. It requires constant consideration of state, side effects, tech debt, and more.

  • Clean Code favors composability over inheritance (analogy: unix tools vs some highly task specific GUI)

  • When Clean Code uses inheritance, it keeps a shallow inheritance structure because this helps us maintain Orthogonality (deep inheritance violates Orthogonality)

  • Clean Code and Bad Code are self-perpetuating. The more Clean Code you have, the more Clean Code will be added in the future. The more Bad Code you have, the more Bad Code will be added in the future.

  • Premature optimization leads to Bad Code. It is hard to anticipate future requirements. Embrace WET (write everything twice) and YAGNI (You Aren’t Going to Need It - e.g. don’t write it until you literally need the feature).

  • Understand and utilize dependency injection - it will help you write more testable code, while also reducing the amount of undifferentiated boilerplate factory code you have to write.

Clean APIs

  • Bad APIs just “hooks things up”.

  • Clean APIs support your business goals more readily. They are Easy to Think About (ETTA) and Easy to Change (ETC) and flexible enough to support future use cases.

  • Usually, RESTful API design is a good approach to achieving this.

    • Sometimes other paradigms like RPC make sense (Ex: you have tightly coupled components where performance/integration is better served by a tailored behavior-oriented API than an entity-oriented one).

  • RESTful design basically means exposing an API that uses the semantics of HTTP to work with Resources.

  • This has many pros:

    • HTTP is everywhere. Clients are common. If semantics are followed, behavior will not surprise users and they can pick up a totally unfamiliar API with relative ease (see AWS APIs across all our services).

    • Resource-first APIs are flexible. They will support many unanticipated use cases that a tailored API will not.

    • Most tailored use cases can still be represented with a tailored Resource, so you don’t miss out on much.

  • This does not just mean exposing everything and the kitchen sink from your data store! Your Resources are your interface and should be kept clean and limited. There is good and bad entity design. For example:

    • There are good resource ids (/pets/12345) and bad ones (12345). Notice the ambiguity in the second - the client has no way to know this is a pet. The client will need special information to make use of the identifier.

    • There are good ways to represent collections (unopinionated as to how said collection will be used) and bad ways (oriented around/anticipating specialized use-cases).

Clean Architecture

  • Clean Architecture is Easy To Think About (ETTA). A clean system places low cognitive strain on its owners.

  • Bad Architecture is hard to think about. A poorly architected system places high cognitive strain on its owners. It requires extreme caution to operate. It requires constant consideration of state, side effects, tech debt, and more.

  • Clean Architecture is Easy To Change. If we decide to use a different database technology, we can execute that migration without causing ripples across our architecture.

  • A monolith can be successful but often becomes Bad Architecture. Some reasons include:

    • Needing to scale the entire monolith instead of just individual components that need more resources.

    • Needing to deploy the entire monolith for every change (more risk of breaking unrelated parts)

    • Needing to understand the monolith’s monolithic codebase to successfully operate (violates Clean Code rules).

  • Clean microservice architectures address many of the weaknesses of monolithic architecture:

    • Microservices are independently deployable and scalable.

    • Clear boundaries and separate codebases tend to drive cleaner code - we must define and respect interfaces.

    • Can even be split up and operated by separate teams, allow for modular consumption of common needs, etc.

      • But be careful of this as it can lead to changes needing to go through many many teams.

  • Bad microservice architecture however, is often worse than monolithic architecture because it fails to accrue the benefits and adds network, operational, and organizational complexity.

    • Probably the worst case scenario is a Networked Monolith, e.g. many tightly coupled services that need to be deployed together in a coordinated fashion.

  • Organize your teams around your business needs, not your technology.

    • Conway’s Law: Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.

    • If unmitigated, you’ll end up with:

      • Products that mirror your org structure rather than what customers actually need.

      • “UI teams”, “middleware teams”, and “backend teams”. Every piece about your software you want to change will go through 3X the planning, 3X the politics, and 10X the miscommunication.

Learning

  • One of the best ways to improve at writing software is to write lots and lots of software.

  • Make time to study software every day.

  • Collect perspectives from those outside your org on topics outside your daily work.

  • It is worth your time to be intentional about what you are learning - think about what you need to learn to get where you want to go.

Operations

  • Great OPS come from Ownership and Prioritization. Own your software and know what’s important to customers.

  • You won’t be able to do everything and that’s okay. The important thing is to prioritize the work and chop it down.

  • You build it, you run it, is a simple rule for incentivizing Clean Systems, Clean Code, Clean Architecture and solid operations. No throwing software over the wall to another team.

Productivity

  • To write good software, you must make time for deep, undistracted work.

  • If you consider software a craft, then writing it will produce more satisfaction and happiness.

  • Productivity is about Leverage. Leverage = Impact / Time. Not all work is equal.

  • Focus your work on Leverage Points - work that produces vastly greater impact relative to time invested.

  • There will always be more work than you can do. The important thing to do is to prioritize it and chop it down.

  • Work on ONE big thing at a time. Having a lot of Work In Progress (WIP) causes context switching and stress

  • Productivity questions to ask yourself:

    • How can I reduce the time required to complete this activity?

    • How can I increase the value of this activity?

    • Is there something higher leverage I could be doing?

    • Which task will most reduce my current Work In Progress? (WIP is your enemy)

    • Is what I’m currently doing important or just urgent?

    • Is what I’m doing moving us towards our Big Ass Goal?

  • Good internal platforms lead to high business productivity.

    • Good Platforms are self-service: A good platform reduces the amount of backlog coupling across teams. It lets users provision, configure, manage, and operate resources created via it.

    • Good Platforms are compelling to use, not mandated. They bring real value to users:

      • The platform is self-service for the overwhelming majority of use cases.

      • The platform is composable, containing discrete services that can be used independently.

      • The platform does not force an inflexible way of working upon the delivery team.

      • The platform is quick and cheap to start using, with an easy on-ramp (e.g. Quick start guides, documentation, code samples)

      • The platform has a rich internal user community for sharing

      • The platform is secure and compliant by default

      • The platform is up to date

    • Good Platforms act as a “Paved Road” - team members can move off of them, but there are benefits to staying on them.

Product

  • Great product comes from Customer Obsession.

  • It’s hard to build a great product when you don’t use your own product.

  • Listen closely to customer problems, take proposed solutions with a grain of salt. The right solution may not be exactly what the customer is asking for.

Politics

  • Every company unavoidably has its own political system. You need to understand how that system works to effectively deliver software within it.

  • Highly political environments tend to be inefficient and reward bad behaviors. They are bad for the product and bad for customers. Great teams minimize politics.

Planning

  • Things usually take twice as long as you expect them to take.

  • Accurately estimating how long it will take you to deliver valuable software to customers will help you plan and operate a successful software business.

  • Plan ahead but embrace change - plans will change as we reflect and learn during a software project.

  • Decisions/requirements are reversible. They can and will change out from under you. Treat them as such and plan accordingly.

  • Sometimes you need to take a big bite. Agile principles do not mean “never ever attempt to take on a large project”. Sometimes, the most critical work that needs to be done in an organization is a large project. In these cases, instead of fighting tiny fires forever, take a big bite to address the problem at its core.

Projects

  • A Project is a large body of work to complete.

  • Projects have costs and benefits. Good Projects generate positive ROI for your business.

  • Projects works better when single threaded. A Project, should have a single, knowledgable owner.

  • Projects should have a Source of Truth that indicates how they are going.

  • Milestones are one part of the Source of Truth. Milestones should reflect working code in production when possible.

  • Risks are another part of the Source of Truth. Risks have owners, status, and dates by which they must be addressed.

  • Status Updates are mechanisms of pressure and coordination to ensure Projects move forward.

Success

This document is about rules for writing software, but rules for success are relevant to all areas of our life.

To keep this concise, here are 5 rules for success from Arnold Schwarzenegger, a very successful individual and leader.

  1. Have a clear vision of what you want to achieve. Otherwise you will just drift around.

  2. Think big. Thinking small is a self-perpetuating practice. We want to achieve big results in software.

  3. Ignore the naysayers. There will be naysayers for any pursuit worth pursuing. Ignore these people and succeed.

    1. Clarification: this does not mean ignore people giving you technical guidance that will help you succeed :)

  4. Work your butt off. Succeeding is hard and tends to require high volumes of work.

  5. Give something back. Remember to make time to give back to your team, your org, and your community because this is what you will ultimately be judged by in life.

Teams

  • Writing software is a human activity. Great software is written by great teams of good people with strong bonds.

  • Great people and a strong team are the first-order driver of great software. Processes and tools are important, but they are second-order influences.

  • Do not be a narcissist. Narcissists create unnecessary pain for others and prioritize their career over peers and customers.