Skip to content

Commentary:CupidForJoyfulCoding

Ben Christel edited this page Sep 3, 2022 · 3 revisions

This page contains my commentary on Dan North's blog post "CUPID - for joyful coding"

Properties over principles

  • Dan North says principles are rules that you're either following or not. Properties are a direction to optimize in
    • I don't really see this -- doesn't seem to align with common usage of these words. To me, principles are about values and properties are about structure. Properties are objective. You can have properties that serve principles. Maybe Bob Martin erred in calling the SOLID principles "principles"—to me they seem like the same sort of thing as Dan North's "properties".

Composable

Code with a narrow, opinionated API has less for you to learn, less to go wrong, and less chance of conflict or inconsistency with other code you are using. This has a diminishing return; 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.

He's talking about orthogonal interface design here. A possible way to get around both horns of the dilemma and eat your cake too: design orthogonal APIs, but create convenience wrappers for the most common use cases.

Code with minimal dependencies

I wish he'd gone into more detail about what he means by "dependency". Is he just talking about third-party code? Can a pure function be a "dependency"?

More than once I have started writing a class by giving it an intention-revealing name, only for the IDE to pop up a suggested import with the same name.

To achieve this serendipity, you must understand KranzsLaw. "Intention-revealing" presumably means naming things after their inherent properties and effects—not what they're used for.

Unix Philosophy

Dan equates the "Unix Philosophy" with "many small programs that each do one thing well". But the Unix Philosophy actually covers many more topics—at least the C, U, P, and I of CUPID. See http://www.catb.org/esr/writings/taoup/html/ch01s06.html

(AFAIK the Unix Philosophy is largely silent on whether code should be domain-based—probably because it is the brainchild of systems programmers, not app programmers.)

“doing one thing well” is an outside-in perspective; it is the property of having a specific, well-defined, and comprehensive purpose

"purpose" is a tricky word. I wouldn't say cat has one "purpose". It gets used for different things in different contexts. E.g. you can use it to print a file, or concatenate several files, or force another program to format its output for machines instead of humans. It often gets used to load a file into a stream that can be piped to another command (some people say you should prefer the special shell syntax for this).

Likewise, echo doesn't have one "purpose". You use it for logging, to print output, and to get a string into a stream.

Perhaps the distinction is between "each piece should have one and only one reason to change" and "each change should affect one and only one piece". As Dan North writes:

the most common case is where the content and format of the data change together; a new field, for instance, or a change to the source of some data that impacts both its content and the way you want to display it.

Bob Martin's "clean architecture" involves splitting up a single vertical feature slice into a bewildering number of components that each munge the data. I think Dan is reacting to that.

As a developer, having these living in different places leads to an administrative chore of chaining identical fields together

I'd maybe question whether this is such a big problem. As programmers, we sometimes feel like "administrative chores" are beneath our dignity, but isn't part of our job to reduce mountains to a lot of little molehills? The alternative to solving a lot of trivial problems is to solve one big, risky problem.

Predictable

Code should do what it looks like it does, consistently and reliably, with no unpleasant surprises. It should be not only possible but easy to confirm this. In this sense, predictability is a generalisation of testability.

100%.

I once worked on a complex algorithmic trading application that had around 7% “test coverage”. These tests were not evenly distributed! Much of the code had no automated tests at all, and some had crazy amounts of sophisticated tests, checking for subtle bugs and edge cases. I was confident making changes to most of the codebase, because each of the components did one thing, and its behaviour was straightforward and predictable, so the change was usually obvious.

This ties in with another thing I've been wondering about recently: how much of a typical codebase is "glue"? That is, what's the ratio between the core domain models, business logic, algorithms, etc. and the infrastructure that binds it all together? Glue is often less testable and less critical to test (because with the right design, it can be made self-evidently correct). It seems that in Dan's case the logic:glue ratio was about 1:13.

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

It sounds like he's ruling out non-deterministic algorithms here, but even those algorithms have a describable probability distribution of resource usage. E.g. consider the following algorithm for picking 10 unique elements from a large list:

while we don't have 10 elements picked yet:
  pick an element from the list uniformly at random
  if the element is not already in the list of picked elements, add it

This algorithm is not deterministic; you might get unlucky and pick the same element over and over, an unbounded number of times. However, that's very unlikely to happen, and we can describe the time this algorithm takes to run as a probability distribution which is a function of the input size.

Code should be observable in the control theory sense

This seems like an aspect of Predictability to me. We can only predict what code will do if we can infer facts about its state and observe what it does!

Idiomatic

As well as understanding the problem domain and the solution space, you have to interpret what someone else meant, and whether their decisions were deliberate and contextual, or arbitrary and habitual.

Katrina Owen says "make similar things look similar, so the important differences stand out". We need consistency so we can separate signal from noise (or, I guess, signal from carrier wave?)

In a sense, this is the predictability principle applied to software developers instead of software. By observing a programmer's output, you should be able to infer their internal state—what they were thinking.

In this context, your target audience is:

  • familar with the language, its libraries, its toolchain, and its ecosystem
  • an experienced programmer who understands software development
  • trying to get work done!

This answers a question that has often troubled me: should one design code to flatten the learning curve for people who are less experienced with the tools or with programming in general? Dan seems to be saying "no" here. I've gotten pushback from other devs who insist that this is exclusionary: that TDD'd code is too complicated for junior devs to understand, that I shouldn't assume that people know about JavaScript this binding, etc. I find this stance disturbingly anti-intellectual, but also relatable: how would you like to be the new programmer on a team who was constantly preventing you from getting work done because their code was incomprehensible to you? I am not sure Dan's answer to this question is sufficient.

At the other end of the spectrum are languages like Scala, Ruby5, JavaScript, and the venerable Perl. [...] Perl coined the acronym TIMTOWTDI—“there is more than one way to do it” [....] This means that in any non-trivial size of code, you will likely find examples of [several different implementation strategies], often in combination with each other. Again, this all adds cognitive load, impacting your capacity to think about the problem at hand, increasing uncertainty, and reducing joy.

Interesting that Ruby programmers love how many different ways Ruby gives them to do things. I wonder if Dan is just not "familiar with the language, its libraries, its toolchain, and its ecosystem".

Architecture Decision Records6, or ADRs, are a great way to document your choices about style and idioms.

I feel like a mention of pattern languages is owed here. IMO the reason patterns have fallen by the wayside in software is that software engineers misunderstood the point of Christopher Alexander's original pattern language—the patterns truly do form a language, which is a medium of communication between practitioners. I have seen teams struggle to produce working code in a consistent style because their pattern language only had a couple simplistic "words" that couldn't adequately express the solutions they needed.

Idioms are as much about what you can say as how you say it.

Domain-Based

defining a Surname type

Obligatory link to https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/

While I like the idea of domain-driven design, I think it's often politically difficult to implement. Product managers may request features for which the users have no existing words, then insist that the terms the engineers use for the resulting concepts not be revealed to the user anywhere (if this sounds bizarre, ask me for details sometime—I swear this has actually happened). I call this "postmodern design" since it creates simulacra—virtual systems that bear no resemblance to any preexisting or underlying reality.

Instead, the layout of code—the directory names, the relationships of child and sibling folders, the grouping and naming of related files—should mirror the problem domain as closely as possible.

Yes. Though I also think every project should have a lib folder for app- and domain- agnostic code.

The app framework Ruby on Rails popularised this approach in the early 2000s by building it in to its tooling

I suspect some paragraphs got reordered during editing—the "this approach" that Dan refers to is the code-centric rather than domain-centric approach. Rails organizes code into architectural layers rather than domain concepts.

The likelihood is that any non-trivial change to patient record management will involve code scattered all over the codebase. The SOLID principle of Single Responsibility says that view code should be separate from controller code,

I agree with this, but I don't think the solution is just to lump all the code related to a domain concept into a single place. There still do need to be architectural boundaries; otherwise you end up with the old PHP anti-pattern of SQL mixed in with HTML. I think where frameworks like Rails err is in not allowing sufficiently abstract infrastructure. E.g. every database table has to correspond to a class that inherits from ActiveRecord::Model, or you can't query it. Why? Why do we have to write multiple units of code that all fundamentally do similar things (read from a database)? I know that since Rails uses inheritance, we don't literally have to rewrite the database access code for every class, but the Rails way still violates the Unix philosophy: from the caller's perspective, every model knows how to talk to the database; there is no single database client that does its one job well.

Clone this wiki locally