Responsibly mid-size: What Matters, Suffers

March 4, 2023

In interviews, I'm sometimes asked what I think the biggest existential threat to Ramp is, and I often say "doing mid-size responsibly." We've raised a lot of money, hired a lot of people since the early days of Eric and Karim's apartments, and are now, solidly, mid-sized. What does it mean to hit mid-size responsibly? And how can we leverage experience from a decade in high-growth tech companies as we sustain flight here?

When describing "the game" of being mid-size day-to-day, I often reference the Serenity Prayer:

God, grant me the serenity
to accept the things I cannot change,
the courage to change the things I can,
and the wisdom to know the difference.

A mid-size company cannot do the same things they did when they were small. They will need to adopt what feels like "big company nonsense" in order to grow. But this needs to be piecemeal, you don't hit PMF and suddenly operate like a FAANG. Every day, people at the company will have to have the courage to adopt a new Process or Attitude when they need to, accept it when they shouldn't, and have the wisdom to know the difference.

What's an example of a "responsibly mid-size" attitude? I'm going to talk about (and challenge) the idea of "clean code" in any codebase that has real responsibilities. This is adapted from a presentation I gave internally; it's not in response to the Bad Thing Being Described (in my 7 companies, I've never felt more confident in my peers), it was more given as a preventative, and a bit of "what I wish I'd heard when I was in my late 20's working at other high-growth startups." I hope it's useful to someone.

"Bad code" is inevitable

These observations are summarized from a Sandi Metz talk, Polly Want A Message, which in turn references other writing. I highly encourage anyone to watch the full talk, read her blog, or check out the book on OOP she co-wrote. First I'll mention that "bad code" is effectively inevitable.

She links to Michael Feather's Getting Empirical About Refactoring, which introduces the idea of "churn" as how often a file changes. Considering your most-touched files are interesting, but if you plot them with semantic complexity within the file, you get an interesting picture (and she adds the green trend line):

A chart showing Churn vs. Complexity, with a green line sloped curved, concave, downward. Most are clustered at the bottom-left, a small cluster at the top-left, a small cluster at the bottom right, and one file in the top-right

Most files in a codebase live in at most one extreme: left and low (few changes, low complexity), left and high (complex but rarely touched, think that one gnarly feature that doesn't require much development after launch), or right and low (touched a lot, but not complex at all: think config files).

Except that one little buddy on the top right: Changes a lot and complex?! I hope it's not important.

A consultant of many, many software shops, Sandi says in the talk:

If I looked at the churn versus complexity chart for your own app, I can tell you what's up in that right-hand corner. It's a class that's big, much bigger than the average size of the classes in your system. It's complicated. It has a bunch of conditionals in it. And it's about something that's super important to your domain. [...] If you're doing contracts, it's Contract.

My first gig was Flash Player; the most churned file was also the biggest, splayer.cpp (the core player logic), which was around 22k lines when I was there. Most of it was one big switch statement.

She links a few graphics from Code Climate, a tool that measure code health on open source projects, and you can see almost every project has one of these:

A Code Climate chart showing Churn vs. Complexity for the AngularJS project; it has the same cluster with one problematic point on the top rightA Code Climate chart showing Churn vs. Complexity for the Discourse project; it has the same cluster with one problematic point on the top rightA Code Climate chart showing Churn vs. Complexity for the GitLab project; it has the same cluster with one problematic point on the top right

You can find your 30 most churned files by running

git log --name-only --pretty=format: | sort | uniq -c | sort -nr | head -n 30

Really, watch the talk, or read her blog post presenting this material (she includes a few more points on Object-Orientation vs. simple procedures). I summarize it here to tell you "nasty" code is emergent to the point of being inevitable, and right where you don't want it. If it's in all these successful, useful codebases, maybe seeing it yours doesn't mean something bad is happening.

"Bad code" might not be

Another example, namechecking a very famous axiom: should you rewrite your app from scratch? Almost every CTO would give an emphatic "NO!", and they'd be right, and they'd probably cite the original Joel on Software article about it. But when you have an ick reaction to code, think of what Joel said on that too:

[...] you can ask almost any programmer today about the code they are working on. "It’s a big hairy mess," they will tell you. "I’d like nothing better than to throw it out and start over."

Why is it a mess?

"Well," they say, "look at this function. It is two pages long! None of this stuff belongs in there! I don’t know what half of these API calls are for."

[...] Old code has been used. It has been tested. Lots of bugs have been found, and they’ve been fixed. There’s nothing wrong with it. [...]>

Back to that two page function. Yes, I know, it’s just a simple function to display a window, but it has grown little hairs and stuff on it and nobody knows why. Well, I’ll tell you why: those are bug fixes. One of them fixes that bug that Nancy had when she tried to install the thing on a computer that didn’t have Internet Explorer. Another one fixes that bug that occurs in low memory conditions. Another one fixes that bug that occurred when the file is on a floppy disk and the user yanks out the disk in the middle. That LoadLibrary call is ugly but it makes the code work on old versions of Windows 95.

This is to say: "nasty" code will probably do things you don't think you need until you lose them. You're not a bad person for wanting to hit your skull against a wall trying to understand old code, but if you let an ick reaction slow you down, you're playing yourself. It's a job! Do the work! Your customers are counting on it.

"Bad code," as a phrase, doesn't mean anything

Last namechecking comes from this wonderful thread by tef, who also wrote the hilarious (and short) Devil's Dictionary of Programming. He has the full thread here (and a related one here), I'm cherry-picking some relevant tweets:

The EULA example is a perfect illustration: real, working legal documents are structured the way they are for a reason, as are codebases you interact with.

A picture of a dam with text overlayed to look like a code comment saying Do Not Remove

Tech matters, but not as much as working well together

Mid-size companies are where you start turning features into systems. It's when new engineers get hired to augment a previous engineer's code, without the context of why it looks that way. It's where some people thrived when they could keep everything in their head at once but may hit friction when that's not possible. It's when every "single conversation" turns into a game of Telephone, where signal loss becomes an inevitability.

With all that happening, it's a ripe time for an engineer to look at something in front of them and think "ugh, this is garbage." I write this for folks in other mid-size companies: allowing this to fester and inform how you do your work day-to-day is not only immature engineering, but also a quick way to company ruin. From Sam Altman's Startup Playbook:

A quick word about competitors: competitors are a startup ghost story. First-time founders think they are what kill 99% of startups. But 99% of startups die from suicide, not murder. Worry instead about all of your internal problems. If you fail, it will very likely be because you failed to make a great product and/or failed to make a great company.

The hardest part of mid-size is keeping your team together and sailing the ship as one. When you hear someone saying "oh, we're getting shanked by the Widget team again!" or "Christ, why can't Marketing be clear about what they want?!" your company is already dead, the doctor just hasn't called it yet. Allowing yourself to be doomerish about a codebase is chasing a siren song: remember how that story ends.

I'm not saying "code quality is for chumps:" please think hard and carefully about what you design and deploy! Forming opinions on code quality is an important part of becoming a seasoned engineer, and some people have made a careers promising you to teach you what "good code" is (though the advice is often dubious). Invest in your craft and form opinions on what works and what doesn't.

But, whichever opinions you land on, don't let your gut reaction slow you down, or stray you from the fact that the game is about delivering value to customers. The code should work for your company, not the other way around; and I hope I've persuaded you that almost every lasting codebase does this by looking "ugly," often where it really matters. Develop taste, but not at the expense of delivering.

(if you liked this, you might also like my piece on the cultural baggage around the phrase "legacy code")

© 2024 Ramp Business Corporation. “Ramp,” "Ramp Financial" and the Ramp logo are trademarks of the company.
The Ramp Visa Commercial Card and the Ramp Visa Corporate Card are issued by Sutton Bank and Celtic Bank (Members FDIC), respectively. Please visit our Terms of Service for more details.