Well hello there. It’s been a minute.
It’s a new year (kinda?) which makes it a great time to revisit old commitments like, say, actually writing again.
So I have a morning standup every day, a quick 15 minute call to touch base with my team, just like many other developers. It’s B/E Standup 2.0, and we’ve had it every single workday since probably June 2021. The first year or so it was B/E Standup 1.0, only we just called it B/E Standup because we didn’t know there’d be a 2.0. Much like World War I used to be called the Great War before anyone knew there’d be another one that was even bigger.
Anyway, what makes this standup a little different is that most of us don’t work at the company we used to anymore. What started as a simple way to get our stories straight before the real standup with the front-end team and product guys right after ours has turned into a combination support group/kaffeeklatsch/walk down memory lane, but we do occasionally talk about technology still. During these calls, it has been discovered that I’m not a huge fan of React for a whole host of Old Man Yells at Cloud reasons as well as a bunch of good ones, and the team sometimes sends me articles or videos explaining why I’m wrong.
One of these videos is from Theo at T3.gg, entitled Separation of Concerns is a Lie.
I suspect that Theo was hyperbolic in his naming of the video (controversy invites attention, after all) but as someone who was raised in the Olde Days of web programming I was interested to see the viewpoint of someone who went against that old conventional wisdom. I won’t go point by point through the entire video (it’s worth watching), but I think the core of his argument is in the following extended quote:
Separation of concerns is almost always sold as a solution to complexity. By separating these things into these boxes you’ve reduced the potential complexity of those things.
What you’ve also done is increase the complexity of any given feature because every feature is going to touch all of these boxes you’ve split up. That might be good if those boxes are the same shape as your teams but that’s not the case for most people because teams are now being built around features not around locations in the stack and if I’m building a new feature on Twitch or on UploadThing and I’m on the front-end team and we need back-end changes now we have this whole process with back and forth with making changes that hopefully are backwards compatible with realizing the API is slightly off from what we need and bugging them to make additional changes. All of this separation makes it so much slower to move and takes away a lot of the confidence you normally would have when you make these changes.
If the distance between your infrastructure and your UI code wasn’t multiple teams, files, code bases, and deployment infrastructure and instead the differences between these was two lines of code it makes maintenance so much easier. And that’s why I’m getting frustrated.
There is a lot of merit to what he says here, but I do think he’s conflating a few different grievances into one meta grievance which ends up muddying the point a bit.
The chief complaint seems to be that having separate teams for front-end and back-end slows things down and is unnecessary, but that (strictly speaking) isn’t about using an MVC framework; it’s more a byproduct of hiring practices and how teams traditionally use different technologies (which require different specialists) on different parts of the stack. It happens that someone who is masterful at optimizing SQL may be less adept at doing transition animations in CSS, and companies/organizations that have larger teams can afford to find specialists in each category.
If we can assume teams have specialists and that they’ll need to coordinate, Theo doesn’t really explain here why MVC frameworks are bad on either the front-end or back-end code bases. It’s perfectly reasonable to have your back-end endpoints grouped as controllers in separate files with helpful names, for instance. I also tend to think the problem set that a) requires intense coordination between front-end and back-end teams and b) can otherwise be solved with a quick two line change is very small. To say that using an MVC framework is why YOUR team is stuck in meeting hell trying to roll out a new feature while a more enlightened team is knocking it out with two lines of code and a git hook pushing it live in two minutes seems to set the bar a little high, in my opinion.
I also think he’s mischaracterizing why people like MVC as a model. It’s not exactly that it reduces complexity, but more because it organizes it. Things can only be as simple as they can be. You can always make a function seem simpler by taking all of the hard parts out, putting them in another function, and then calling that function from within yours. It’s only one line of code now! So simple! But with MVC it’s more about knowing where to look to figure things out. In Rails, for instance, you can look at the URL structure and immediately (assuming the developer followed the convention) zero in on the controller that is doing stuff at that URL. From there you see it calls this model, or brings in this library. Assuming the language is the same, an MVC app and non-MVC app can do the same things, in many cases using the same libraries. They are just as complex. But one is easier to figure out for someone looking at the code for the first time.
Rather than focus on the specific points in the video, I’d instead like to go over a few reasons why separation of concerns (and MVC specifically) are still pretty rad.
1. Separating Concerns Correctly Makes Maintenance Easier
One of my issues with React (I don’t have the entire diatribe queued up, but it’s going to be great. Promise.) is that it has overloaded the idea of the Component. “Component” is an extremely useful word in that it can mean anything while also meaning nothing specific, and I’ve noticed that developers raised on React tend to use it to mean whatever they want in the moment. A UI widget? That’s a component. The router that determines the way the URL path turns into a running application? Also a component. It’s maddening. Words have meanings!
In Theo’s video he starts off with the dictionary definitions of the parts of a Model-View-Controller application, which went something like this:
- Model: The application’s dynamic data structure
- View: The visual representation of information
- Controller: Accepts commands and input and coordinates with the other two layers
Each of these is a very specific function with specific requirements, and there is nothing wrong with separating your code along those lines. It’s just awkward to do it in React.
With JSX, React likes developers to create Components in self-contained files with all of the different parts of the component present at the same time. The styling, the markup, the event handlers, various hooks that control its life cycle. I’d even go so far as to say this is totally reasonable for a View layer component, which could benefit from being self contained as you don’t have to hunt around to different places to figure out why something is misbehaving.
The problem is that React applications just end up being the byproduct of the interactions between their various components.
Due to the lack of proper layers, the application just has its state living in various different components depending on the taste or preferences of whichever developer was working on it that day. Which is great if you are that developer, but less good if you were brought in to the project 3 years on after the original team left for greener pastures, the second team was fired, and the third team was balancing 2 other higher priority projects. And Steve, the guy who was supposed to “show you the ropes,” can’t get you a database dump that is newer than November 2022, and it seems like a bunch of columns in the code aren’t in the database, and you get the picture.
MVC solves this problem because you (as a developer familiar with the framework you’re using) know where to look to get to what you need. Things are much more discoverable.
In properly engineered applications (they exist, I assume) you don’t need to change code in 15 places to add a new feature. You may need to, say, add a new status field to a model, and then update your model or controller to properly validate for that new field, and add a new dropdown to your form for it or whatever, but having to touch 4 files doesn’t create “complexity” assuming everything is where it should be. To Theo’s point, as a new developer on a project, it feels complex if you are unfamiliar with how the application is laid out, but once you internalize the structure (and the team doing the code reviews also internalizes it) you almost automatically know where everything belongs.
2. Keep It Simple, Friend
Theo and many people who watch his videos are very good developers. Anyone who can speak confidently about it on Youtube or elsewhere has strong opinions and the skills to back them up, and I’m sure that the choices he makes in how he organizes his code are smart and well-considered.
Most developers are not like Theo.
As an actual greybeard (ok, saltandpepperbeard) I’ve worked on many teams of varying quality, and that is in no way a knock on any of the individuals comprising those teams. We all come from different experiences, and it’s a simple fact that we aren’t all sprung from the loin of Donald Knuth fully formed knowing the correct pattern to apply to any situation (and the mathematical way to prove it). We come together at different times at different points in our personal evolution and are tasked with creating software. In unguarded moments, many of us admit that we don’t even like what we do for a living. One guy I worked with many years ago left the industry to become a lawyer, and is now a successful politician with lots of good ideas. Another told me in a Zoom call that his real passion is making dog food for dogs with dietary restrictions. I love dogs. None of this is bad. It’s just to say that we aren’t all totally into crafting software and there is a near 100% chance that if you stick in the industry long enough you’re going to end up on teams with people who are just passing through on the way to bigger and better things.
If you have a Theo (or someone like him. I’m not singling him out) leading your team and guiding you towards the correct way to organize your code, you’ve hit the mother lode.
If you have someone who thinks they are Theo and through some quirk of your company and their promotion process has somehow managed to get themselves into a leadership position while having none of the requisite technical skill (but lots of strong opinions), it can be less good.
Falling back on reliable and repeatable patterns can help teams flourish despite the environment around them. Like bacteria living on volcanic vents 5 miles under the ocean in water acidic and hot enough to melt iron, they are able to keep following the patterns and pumping out software despite all of nature trying to stop them.
It’s also worth mentioning that the rules for lone wolf developers and teams are vastly different. On projects that only I see, I get real experimental with it. Like writing-domain-specific-languages-to-generate-code experimental. And the thing is, it works. Eventually. I see this as part of my process to better understand my craft and the problem domains I work within, and to give me ideas for the next version or product or whatever.
If you (like Theo) have a way that you want to organize your code that makes you more productive, do it! That’s how you learn. There aren’t really any rules to this, and you can accomplish the same result any number of ways. Maybe you’ll even learn something, good or bad.
There is no Platonic Ideal of “Best Code.” There is only good code in this specific environment.
Most of my support of MVC comes from the team environment where we all need a common language with which to interact. If your work primarily consists of solo- or small-team projects and you can find efficiencies in breaking out of patterns (or into different patterns) have at it. That’s where a lot of innovation happens.
3. The Persistence of Time and Deadlines
There’s sort of two points here and I’m going to try to thread a needle to fit them under one headline. History will tell if I succeed.
- Point 1: Things aren’t really as urgent as we think.
- Point 2: Deadlines drive most technical decisions and, consequently, debt.
So we live in the age of Agile Development, that mythical time where the CEO or product owner comes up with an idea and, due to our agility, we get it working in 2 weeks somehow, despite all the other things we also need to fix in those same 2 weeks. I’ll have other posts about that, so watch out.
A lot of Theo’s criticism of Separation of Concerns seems geared towards the Complexity it introduces, and the resulting delay as features need to involve conversations with other people. I’m here to offer a counterpoint:
It isn’t bad to plan before you code, and the bigger the team is the more important it can be.
When a new project starts, the original developers gather some basic specifications, or have conversations with customers to get a rough idea of the requirements, and then get to work. They start building an edifice, one brick or pipe a time, and that’s when the problems begin. Because putting a brick there, or running a pipe through that hole you had to make by necessity means you committed to a certain structure, which is now real. If you found out later that the small mid-level apartment building ackchyually needs to be a hardened bomb shelter because the Secret Service wants to use it to protect the Secretary of the Interior (#8 in the line of succession!) in the case of a nuclear war, it’s safe to say that the assumptions you started out with need to be re-examined.
And that takes time to do correctly.
For any non-trivial project with teams of 10 plus people, that will involve conversations and analysis of tradeoffs to get from where we are to where we need to be, and that’s ok. Obviously everyone wants it to go faster, but it generally isn’t the end of the world if breaking changes come down the pike that you now need to account for.
For long-lived applications, there are essentially two types of changes:
- Compatible Changes: where the possibility for such a change was built into the design of the application, and implementing it is easy within the application’s structure. Sometimes these can be bigger like adding a new set of screens with supporting data structures, but they are net additions rather than changes.
- Breaking Changes: where the change involves restructuring the application to make a different set of assumptions.
In any reasonably well-designed application, the first type of change should be accomplished within a sprint. The latter will (and should) take a bit longer as you should revisit assumptions, check for regressions in the new revised code, etc. This is true for both MVC and non-MVC applications, but with the non-MVC code it will be more up to the individual developer making the changes how to best organize them. With 20 developers and 5 years of code, this can get weird fast if the team isn’t diligent about revisiting and updating old code to keep it conformant with new styles and paradigms as the codebase evolves. Which brings us to the second part…
Deadlines (artificial or otherwise) are very real.
They act similarly on both MVC and non-MVC projects (where choices are made for expediency rather than correctness) but I submit that non-MVC projects (or projects that don’t follow well-known patterns consistently) are impacted worse due to less enforced consistency. Every project has a few files that haven’t been touched in years. What is the likelihood that a new developer who just joined a project will be able to make sense of how it was done if the original developer was just trying to jam something out to hit a deadline, just inlining everything, using random React syntax that’s 3 versions out of date?
Enforcing a structure at a framework level (which, in my experience, I haven’t seen much in React applications) makes it easier to pump out consistent code when it’s crunch time.
So there you have it: The idea that everything is pants-on-fire urgent (and therefore you should reject rigid structure to move faster!) isn’t correct, but when it is urgent, having an MVC makes sure that you’ll produce code with more consistency that won’t bite you in the butt in 5 years.
I think that came together?
As someone who started programming back in the days when Java Applets were considered the next big thing, I was definitely raised under the cult of MVC. Remember AWT, and then Swing, and then (my personal favorite) SWT? And then when the first Rails video came out and they were pumping out a functional blog in (seemingly. It was a long time ago) 5 very well organized files, I was really hooked. I admit it.
Single Page Applications and particularly React kinda turned that on its head. But I’m still not convinced it’s “better.”
Like everything else in our industry, our job is to navigate trade-offs, and there are always trade-offs. And they change over time! Sometimes a new version of a library or a new browser version will upend the old trade-offs and create a new calculus, and all of the things you told yourself for the last 2 years no longer apply.
So maybe I am wrong, and MVC really is the wrong way to build applications in January of 2024. I guess I’ll live with that and keep tilting at that particular windmill.
There’s always February to look forward to!
Til next time.