‘I’m not sure so much that’s “hard”, it’s more that most serious projects (with 30k+ LOC, 30+ developers, very complicated workflows, etc) are going to require something more than “pure functions” at the unit level.’
I developed my current approach while working on a Million+ LOC codebase with 100+ contributing developers. I have since mentored people on teams building apps with hundreds of millions of users and customers to reproduce those benefits at multiple fortune 500 companies.
The advantage of using more pure functions is that it introduces very important simplifying factors:
- Deterministic state reproduction
- Removal of state dependencies
- Easier refactoring, moving code around
- Easier maintenance
- Easier to extend code
For more on these topics and advantages, see:
This is not to say that functional programming is better than object-oriented programming, but it definitely is an assertion that imperative and object-oriented programmers can learn a lot from functional programming with regard to writing more loosely-coupled, deterministic code — especially to simplify very large codebases and ease coordination overhead on very large teams.
Shared-mutable state is just another form of spaghetti code, and spaghetti code is the last thing you want in a large enterprise app with lots of contributors. The advantage of purity is that it isolates and encapsulates complexity so that side-effects, network I/O, etc… do not bleed into other parts of the app. It has a radically simplifying effect on large apps.
That makes it much easier for a large group of developers to understand what’s going on in their part of the app, without needing to understand the full mutation history of every variable they touch.
“‘Most of your test value there is in integration land, not unit land, since your backend code is mostly just marshalling data to/from your database and likely also depending on logic that is applied from other services (microservices architecture). I don’t think the approach you are suggesting stands up to realistic demands in larger projects, especially on the backend.”
More reliance on pure functions and less reliance on shared mutable state and side-effects leads to a shift in balance between unit tests and integration tests, making it much easier to test much larger chunks of your apps using simple, pure functions and equational reasoning. The benefits are many:
- Pure code is synchronous, and generally much faster — unit tests finish in milliseconds, not minutes or hours
- Faster tests = more reliance on tests = better coverage = fewer bugs
- Faster tests = realtime developer feedback = enhanced productivity — developers spend less time manually checking their work building, loading up the whole app with the whole UI + networking layer, so they can spend more time just making their functions behave as expected instead of manually testing things.
You still do need integration & functional tests, of course, but they become more of a sanity check — “does everything fit together as expected?” rather than the primary check for application correctness.
For more on that topic, and the correct role of various types of tests, see “JavaScript Testing: Unit vs Functional vs Integration Tests”.
