It definitely can be. I'm constantly trying to push our stack away from anti-patterns and towards patterns that work well, are robust, and reduce cognitive load.
It starts by watching Simple Made Easy by Rich Hickey. And then making every member of your team watch it. Seriously, it is the most important talk in software engineering.
https://www.infoq.com/presentations/Simple-Made-Easy/
Exhausting patterns:
- Mutable shared state
- distributed state
- distributed, mutable, shared state ;)
- opaque state
- nebulosity, soft boundaries
- dynamicism
- deep inheritance, big objects, wide interfaces
- objects/functions which mix IO/state with complex logic
- code than needs creds/secrets/config/state/AWS just to run tests
- CI/CD deploy systems that don't actually tell you if they successfully deployed or not. I've had AWS task deploys that time out but actually worked, and ones that seemingly take, but destabilize the system.
---
Things that help me stay sane(r):
- pure functions
- declarative APIs/datatypes
- "hexagonal architecture" - stateful shell, functional core
- type systems, linting, autoformatting, autocomplete, a good IDE
- code does primarily either IO, state management, or logic, but minimal of the other ops
- push for unit tests over integration/system tests wherever possible
- dependency injection
- ability to run as much of the stack locally (in docker-compose) as possible
- infrastructure-as-code (terraform as much as possible)
- observability, telemetry, tracing, metrics, structured logs
- immutable event streams and reducers (vs mutable tables)
- make sure your team takes time periodically to refactor, design deliberately, and pay down tech debt.