Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    Two types of complexity and their impact

    Continuing from You can't fix the wrong abstraction, this is the 2nd in a series of articles sharing insights from System Design and the Cost of Architectural Complexity, a 2013 PhD thesis from MIT.

    You can read my scribbled-over version at /pdfs/system-design-and-the-cost-of-architectural-complexity-with-swiz-notes.pdf. Now all the way through ✌️

    In this article we look at the difference between complicated code and complex systems.

    Complicated vs Complex

    Every engineer recognizes complexity when they see it. But the darn thing is hard to measure.

    You need a quantifiable metric to do science. Out of the many attempts that have been made, few consistently correlate with engineers saying "Yeah that looks hard".

    Two stand out:

    1. McCabe complexity
    2. Architectural complexity

    McCabe complexity, also known as cyclomatic complexity, measures how complicated a piece of code is to understand. The more control flows exist, the higher the score.

    Architectural complexity measures the complexity of a system or its parts. The more connections/dependencies, the higher the score.

    Both metrics think of your code as a graph.

    The complicated vs complex distinction is important. Complicated means that there are a lot of inter-connected moving pieces, but you can tease them apart by carefully following the execution flow. Complex means that your system is ruled by emergent effects that are hard or impossible to trace.

    Most modern systems are both complicated and complex 🙃

    McCabe complexity

    McCabe complexity has been around since the 1970's. Considered an industry standard these days, but ignored by working programmers.

    You probably learned about cyclomatic complexity through osmosis and by following advice of the Clean Code variety. Don't have too many variables in scope. Avoid large files. Use short functions. Avoid deeply nested code. One return per function. ...

    All those rules aim to lower the cyclomatic complexity of your code – how complicated it is to read. You're looking for code that reads start to finish like a story with few diversions.

    In theory, you measure the complicatedness of your code by turning it into a control flow diagram. Nodes are actions, arrows are control flow.

    Control flow diagram from Wikipedia
    Control flow diagram from Wikipedia

    The McCabe complexity score measures how many linear paths through the graph exist. You can count those by counting decision points (yes, there's a proof).

    Every "decision point" (an if condition) in your code adds 1 to the score. Code with less than 10 is considered "easy", code with 50+ is considered "untestable".

    Architectural complexity

    Architectural complexity is a new metric designed to measure the complexity of software as opposed to code. Based on visibility metrics developed in 2006 by MacCormack, Baldwin, and Rusnak.

    This measures true complexity – the systems are so large that no one person can understand the whole thing. Bugs come not from the code being complicated, but from interactions between parts becoming unpredictable.

    Managing this complexity is paramount.

    MacCormack et al. used network diagrams to show how visible different parts of a system are to one another. Visible meaning that there's a dependency of some sort – function call, shared variable, imported class, ...

    Network diagram before and after a rewrite
    Network diagram before and after a rewrite

    You're looking at a DSM diagram of Mozilla before and after a refactoring. Each dot in the matrix represents a dependency. Each row and column is a component, defined loosely.

    You can see that refactoring made the code more structured. Dependencies are less spread out and modules with tight internal coupling arise as dense areas on the graph. The modules are also smaller. Huge success 💪

    The diagonal comes from self-references. Every component depends at least on itself.

    Measuring architectural complexity

    Sturtevant, author of System Design and the Cost of Architectural Complexity, expands visibility metrics into architectural complexity by considering indirect connections: If A calls B which calls C, that means A and C are connected.

    This is important because:

    Components in a position to affect many other components, or that can be affected by many other components, have high levels of architectural complexity relative to their less well-connected peers.

    As part of his thesis, Sturtevant built software that can compute architectural complexity scores and draw DSM diagrams for C++ code based on files. Each file becomes a node, imports become arrows. Or rows/columns and dots in DSM.

    Turning code into diagrams
    Turning code into diagrams

    There might be commercial or opensource tooling available to do this by now, but I haven't seen anything popular. Extracting file dependencies sounds trivial so visualizations shouldn't be too hard to build 🤔

    A file's architecture complexity metric is then a count of these dependencies. Sturtevant classifies files into 4 categories based on in and out dependencies: peripheral, utility, control, core.

    4 categories of architectural complexity
    4 categories of architectural complexity

    Core files are the most complex.

    The impact of tough code vs tough systems

    Here's where it gets interesting. What do you think is worse? Complicated code or complex systems?

    The answer seems to depend on your years of experience.

    Sturtevant observed that more junior developers struggle with making changes to complicated code. It slows them down a lot. But more senior developers, it's like they don't even notice the code is complicated. Zero difference in productivity.

    He posits this is because senior developers "see" code as a network diagram and are able to work at a higher level of abstraction. The code as written is but an irrelevant detail, they think in algorithms and systems.

    You see a block of code and think "ah yes, loop". Whether it's written like this:

    ;[1, 2, 3].map((i) => console.log(i))
    

    Or like this:

    for (const i of [1, 2, 3]) {
      console.log(i)
    }
    

    Or like this:

    let i = 0
    while (i < 3) {
      console.log(i++)
    }
    

    An experienced engineer barely notices the difference between those. A less experienced engineer has to read carefully.

    But architectural complexity, where files depend on each other, that slows everyone down. Even familiarity with the system doesn't help much. In fact it seems that the more experienced you are, the more time you spend in highly complex parts of the system, and the overall slower you get. 🤔

    We'll talk about that next time.

    The lesson

    Don't worry about complicated code. As long as its encapsulated and has no spooky side-effects, you're good.

    Next article in series: Why taming architectural complexity is paramount

    Cheers,
    ~Swizec

    Published on May 26th, 2023 in Software Architecture, Software Engineering, Complexity, Papers

    Did you enjoy this article?

    Continue reading about Two types of complexity and their impact

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️