You know about spaghetti code. Code so confusing it looks like a bowl of spaghetti.
But have you heard of ravioli and lasagna code?
I don't know why we name bad practices after delicious Italian foods, but a recent conversation about splitting up a monolith inspired me to add another – minestrone software.
Spaghetti code – unstructured
— Swizec Teller (@Swizec) May 20, 2022
Ravioli code – too structured
Lasagna code – layered wrong
Adding Minestrone software – unclear domains pic.twitter.com/b0VZYl3RYn
Here's how different Italian foods capture bad software practices. Growing up next to Northern Italy, I have cooked all of these from scratch which makes me an expert 👨🍳
Spaghetti code
Spaghetti code is the easiest to spot. When you have to jump around to understand how something works, that's spaghetti code.
Commonly produced when you take clean code rules too seriously.
The easiest way to write unreadable code is by splitting your functionality across 20 clean small functions.
— Swizec Teller (@Swizec) November 12, 2021
I asked twitter for examples. Here's my favorite
Credit: https://t.co/q1TvQQUERg pic.twitter.com/vifav10Sqc
— @ericniebler.bsky.social (@ericniebler) May 22, 2022
From an article about why GOTO is bad – because it jumps around. Arrows show the flow of execution in that code.
You can achieve similar results with function calls.
Like this terrifying suggestion:
How about a react class component that in the render function create component on the fly that access and modify the state of the patent component? And thoes components create also components within them that modify the same state
— Nicolas Beaussart (@beaussan) May 22, 2022
You can do that with React hooks. But let's not ...
Ravioli code
Ravioli code happens when your code makes sense in isolation, but the system as a whole is confusing. Think emergent behaviors.
We can use Conway's Game of Life as an analogy. A code example would be too long.
3 rules govern the next state of each cell:
- Any live cell with two or three live neighbours survives.
- Any dead cell with three live neighbours becomes a live cell.
- All other live cells die in the next generation. Similarly, all other dead cells stay dead.
You can think of each cell as a well-defined class. Follows sensible rules and makes sense in isolation.
Put them all together and you get ... an infinitely looping glider gun? Even a breeder that creates guns?? 🤨
Yeah, confusing.
Lasagna code
Lasagna code happens when you structure your code in layers and forget to create abstractions. Common in module-view-controller architectures, React components, and typical approaches to GraphQL and RESTful API design.
Here's an example I've seen time and again.
Storage layer
The storage layer keeps your data. A filesystem or database. Secretly it also holds your domain model, but we rarely talk about that.
Article {
id: uuid
title: text
content: text
created_at: timestamp
author_id: fkey(User)
}
Articles have title
, content
, are created_at
a time, and belong to an author.
Service layer
The service layer builds on top of your storage layer and adds the business logic. This is where task-based functions go.
function getLatestArticlesByAuthor(author_id, count) {
return query(
`select * from Articles
where author_id=${author_id}
order by created_at
limit ${count}`
)
}
Returns the latest count
articles from an author. Production code would add error handling and more complex business logic.
Controller layer
The controller layer translates API requests to service layer function calls. Modern frameworks hide all the complexity of parsing requests and constructing responses.
You write the glue:
exports.latestArticles = async (req, res) => {
const { author_id } = req.params
const articles = ArticleService.getLatestArticlesByAuthor(author_id, 10)
return res.send(200).json(articles)
}
We snuck in some business logic! We're saying that "latest" means 10. Does that go in the controller? 🤔
API layer
The API layer specifies what your communication between client and server looks like. The shape of requests and responses. For RESTful APIs you put this in an OpenAPI spec, for GraphQL this whole layer is "POST a query".
{
"/articles/:author_id/latest": {
get: {
description: "Returns latest articles by author"
parameters: {
author_id: {
in: "path",
required: true,
schema: {
type: "integer"
}
}
},
responses: {
200: {
description: "OK"
content: {
"application/json": {
schema: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "string", format: "uuid" },
title: { type: "string" },
content: { type: "string" },
created_at: { type: "string", format: "date-time" },
author_id: { type: "integer" }
}
}
}
}
}
}
}
}
}
}
Yes, that's a lot of work to say "You'll get the full unchanged Article object from the database". It's more useful in production code.
Client render layer
On the client you then have to render these objects.
const LatestArticles = ({ articles, author }) => {
const sorted = articles.sort(
(a, b) => new Date(b.created_at) - new Date(a.created_at)
)
return (
<div>
<h2>Latest from {author.name}</h2>
{sorted.map((article) => (
<ArticleListing {...article} />
))}
</div>
)
}
Sort articles by created_at
and render an <ArticleListing>
component for each. We pass the whole article object as individual props.
const ArticleListing = ({ id, created_at, title, content }) => {
return (
<div>
<h3>{title}</h3>
<label className="created_at">Created at: {created_at}</label>
<p>{content.substr(100)}</p>
</div>
)
}
Each article listing shows a title
, the created_at
timestamp, and the first 100 characters of the content
as a lazy summary.
Client style layer
We want the created_at
label to have a specific style and have chosen to use CSS because styling inside JavaScript is dirty or whatever. There are people who think like that 🤷♂️
label.created_at {
font-size: 0.6em;
font-style: italic;
}
The Lasagna collapses
Okay dear reader, we want to distinguish between when an article was created and when it was published. How many layers do you have to change?
I count ... all except the controller. All this work to create well-structured layers and just one has a meaningful abstraction 🙃
See also: A feature based approach to React development
Minestrone software
Minestrone software happens when you have unclear domains.
You won't see this in tutorials because it requires a sizeable software project. You may never find a project big enough for minestrone to be a problem.
How you cook mom's minestrone:
- take big pot
- see what's in fridge
- throw in a few things that look good
- add water
- boil for a while
- taste minestrone
- add more ingredients to taste
- wait more
- adjust spices (salt and such) to taste
Then you eat that big pot of minestrone all week. There is no other food in the house. If you don't like the minestrone you are given, feel free to add more ingredients.
How you make minestrone software
Replace the pot with software or company and ingredients with features 👉 Minestrone software!
Why make a new module for a new function when there's already a fine module right there? It's even imported and everything! How bad can adding more be.
Why make a new database table when an extra column will do? Who cares if your tables now mean multiple things.
Why make a new (micro)service when you have all this working boilerplate in The Big One? Adding a little extra won't hurt ...
How you recognize minestrone software
Fast forward a few years, a few iterations, a few teams, a few team members, and you're us. Figuring out how to split a monolith into microservices, struggling to structure yourself into functional teams, stepping on each other's toes, and can't even agree on the meaning of your database tables.
Overlapping team responsibilities is a symptom of minestrone software. Or when you need multiple teams to build 1 feature.
At smaller scale, you've got minestrone when you can't decide what file a function goes into. That means your files (or modules) aren't well-defined. Or even when you're not sure which function or component to add to.
Unclear business domains mean you're not sure where code goes, who owns what, what new teams you need, or which microservice makes sense.
Writing well-structured software without a good domain model is impossible. No matter how clean your code.
If your data model is good, the code is easy.
— Swizec Teller (@Swizec) November 3, 2021
If your data model is bad, the code is spaghetti.
Your software is one big minestrone. The secret ingredient is love.
Cheers,
~Swizec
PS: anyone who tells you there is a recipe for minestrone is lying and has never been to Italy. Every mom and grandma makes it different
Continue reading about The Italian foods theory of bad software design 🍝
Semantically similar articles hand-picked by GPT-4
- What microservices are for
- Why even care about code structure
- Finding modules in a big ball of mud
- You can't stop the business, or why rewrites fail
- Why other people's code looks weird
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
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 ❤️