CUPID

  • An alternative to SOLID

  • Focus on properties rather than principles

    • Principles are like rules: you are either compliant or you are not. This gives rise to “bounded sets” of rule-followers and rule-enforcers

    • Where as properties

      • rely on qualities or characteristics of code rather than rules to follow.

      • define a goal or centre to move towards

Definitions:

Composable: plays well with others

  • Code that is easy to use, gets used, used and reused

  • pipes in unix

Small surface area

  • less to learn

  • less to go wrong

  • Less to conflict

  • Less inconsistency with other code you are using

  • Goldilocks just right, to getting it right between too fragmented and bloated

  • Issue

    • Has diminishing return

    • ie if your APIs are too narrow, you find yourself using groups of them together, and knowing “the right combination” for common use cases becomes tacit knowledge that can be a barrier to entry.

  • For example

    • narrow, opinionated API

Intention revealing name and purpose

  • easy to discover

  • easy to evaluate

    • to use it, or find something else, or adapt it

  • Knows what it does with out looking at internals

  • ie Having different levels of documentations or video tutorials (varying lengths, depths etc) that allows user to assess and get out when needs met

minimal dependencies

  • Avoid the whole wanted a banana but got a gorilla holding the banana ie node

  • Avoid adding dependencies which could conflict transitively with clients

    • ie version or library incompatibilities

    • ie avoid adding libraries in your libraries that will may conflict with the users of it

  • Less to worry about

Reviewing code

  • Does the class/method/function have any dependencies that aren't strictly necessary? If so, how can we remove them, or externalise them and make them optional?

  • Does the code have a small, opinionated interface or API? If not, how can we reduce its surface area?

Unix philosophy: does one thing well

  • Unix is one of the successful software, with a philosophy behind how it should do things

    • A lot of the cupid ideas stem from this philosophy

  • Have a consistent and simple model

    • predictable

  • Have components that work well together

    • composable

  • Functional paradigm

  • Make each program do one thing well and only one thing

    • "ls" list flie details, but no inspection of files

  • Together with composability there is nothing you cannot do

    • Expect the output of every program/method to become the input to another

    • pipes , streams api

  • Different to SRP

    • about what code does instead of how the code changes

    • The organsiation of code

    • inside out

    • Focus on having one reason to change, can lead to artificial seam

  • Instead of SRP do Single Purpose

    • Doing one thing well

    • outside in

  • Reviewing code

    • Does each part of the code do one and only one thing, and is it obvious what it doesn't do, and what the edge cases are? If it is doing too much, can we see where to introduce a seam and break things up?

Predictable: does what you expect

  • Should be consistent, reliable, no unexpected surprises

  • Behaves as expected with no surprises

    • passes all tests - only if doing tdd

    • Should be this way even with no tests

    • The intended behaviour should be obvious from it's naming and structure

    • Change can be obvious

  • Deterministic

    • does the same thign every time

    • Even for non deterministic systems (like random generators) should have operational or functional bounds that you can define

    • well understood operating characteristics

    • should be able to predict memory, network, storage, or processing boundaries, time boundaries, and expectations on other dependencies.

    • Should have

      • Robustness

        • is the breadth or completeness of situations that we cover.

        • Limitations and edge cases should be obvious.

      • Reliability

        • is acting as expected in situations that we cover.

        • should get the same results every time.

      • Resilience

        • is how well we handle situations that we do not cover;

        • unexpected perturbations in inputs or operating environment.

  • Observable

    • in the technical sense - internal state can be infered from outputs

    • Needs to be designed in

      • not added at the end, ie shoehorned in

    • why

      • Gain valuable data and insights about the runtime of the code

    • Types

      • Instrumentation

        • is your software saying what it is doing.

      • Telemetry

        • is making that information available, whether by pull—something asking—or push—sending messages; “measurement at a distance”.

      • Monitoring

        • is receiving instrumentation and making it visible.

      • Alerting

        • is reacting to the monitored data, or patterns in the data.

      • Predicting

        • is using this data to anticipate events before they happen.

      • Adapting

        • is changing the system dynamically, either to preempt or recover from a predicted perturbation.

  • Reviewing code

    • Is it obvious what this code does, even if it doesn't have any tests or code examples? (And if it doesn't, let's write some characterisation tests to get a feel for how it works.)

    • Does it have suitable instrumentation? (If we don't have a standard for this in the team, this is a good time to start those conversations, too.)

Idiomatic: feels natural

  • Everyone has their own coding styles, even if slightly different

    • working within code with different styles adds cognitive load

    • Makes code that should be easy to read for one makes it harder for another to understand

    • For example doing many ways of processing a sequence

      • use an iterator

      • use an indexed for-loop

      • use a conditional while-loop

      • use a function pipeline with a collector (“map-reduce”)

      • write a tail-recursive function

  • Instead, use common idioms and conventions either external or internal (team decided) and stick with it

    • might take time to learn, but eventually if stuck with, will be second nature

    • Being consistent in terms of style, shortens learning curve

  • This leads to and comes from empathy for your fellow coder

  • Not only applies to code, but also to domain

    • Domain terms/names/functions should be similar across similar businesses

      • ie shopping cart, wishlist, basket should be common terms across similar domains, implementations will be different but less cognitive load on learning or following the domain

  • The target for your code are those

    • familar with the language, its libraries, its toolchain, and its ecosystem

    • an experienced programmer who understands software development

    • trying to get work done!

  • uses language idioms

    • standard features, constructs, libraries ,frameworks, tools

    • feels natural to work with, goes with the grain

    • ie effective javat

  • USes local idioms

    • house style, coding standards, design standards, defacto

    • aligned with the project, dependencies, platforms, organsiations

    • Use of Architecture Decision Records define these and automated tests to enforce them

  • you can only write idiomatic code if you learn the idioms

  • Reviewing code

    • Is this how someone familiar with this technology would write this code? Are we finding it difficult to read, because someone has been "writing Java in Python", or using unnecessarily clever Scala tricks, or solving everything with C++ or Rust macros when simple object composition would be fine?

  • Areas of idioms occur at different levels of granuality, ie:

    • naming functions types, parameters, modules

    • layout of code

    • structure of modules

    • choice of tools

    • choice of dependencies

    • how you manage dependencies

Domain-based: the solution domain models the problem domain in language and structure

  • code should convey what it is doing in the language of the problem domain

    • reduce cognitive load

  • uses domain language

    • code in the language of the domain, not language of computer science

    • intention revealing, for easy of understanding

      • to articulate and navigate the solution space in code

    • helps catch bugs

    • remember there are multiple domains

    • Should be able to discuss code where a non coder can understand the code

  • use domain structure

    • code for the solution and not the framework

    • payements, loans and not models, views ,controllers

  • use domain boundaries

    • as module boundaries, unit of deployment

    • DDD

  • Reviewing code

    • Is it using the language of the business domain, or is it using maps, strings, tuples, even tri-state booleans, to represent domain concepts? Can we introduce types or change the names of variables to make it more intention-revealing?

    • Is all the code together that belongs together semantically, or are all the models in one big bag, and the views in another, because that's what the framework authors decided they like?

  • https://cupid.dev/

  • https://dannorth.net/2022/02/10/cupid-for-joyful-coding/

  • https://speakerdeck.com/tastapod/why-every-element-of-solid-is-wrong?slide=20

  • https://www.youtube.com/watch?v=knNaUSLhx-U

  • https://mozaicworks.com/blog/cupid-vs-solid

  • https://youtu.be/2QahGarHpXQ Daniel Terhorst-North - SOLID vs. CUPID

  • https://www.youtube.com/watch?v=cyZDLjLuQ9g CUPID — For Joyful Coding • Daniel Terhorst-North • YOW! 2022

Last updated