5 minutes
Locality of Behaviour, over the Single Responsibility Principle
Bob: This is where I keep all the notes I wrote in red pen, and likewise for all my blue notes, my green notes, and my purple notes
Alice: Doesn’t this split your chemistry notes up into 3 different piles? Why not keep those together?
Bob: gasp – and break the Single Color Principle?
Disclaimer The shitty part of becoming a more senior engineer is your opinions become a mystifying web of “it depends” – a pile of vague intuitions and bag of exceptions, the kind that drove proponents of expert systems insane. You don’t get to have simple “this thing bad” opinions anymore. So either I get wrapped up in that and never write down my opinions at all, or I write a dissertation. Here’s a very lossily-compressed version of my thoughts instead, optimized more for enjoyment to write/read, than Truth. “Useful, not true” as Derek Sivers says.
You’ve been given an essential task by your manager, a P0 which the whole company’s future depends on. He needs a button which increments a shared value, and he wants to be able to see that value.
This is your moment to shine. You fire up the AI editor of the month, and prompt your way to victory. Company saved.
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1
},
},
})
export const { increment } = counterSlice.actions
export default counterSlice.reducer
const CounterButton = styled.beutton`
padding: 16px;
background: blue;
`
export const Component = () => {
const count = useSelector(store, (s) => s.count)
const dispatch = useDispatch()
const onClick = () => {
dispatch(increment())
}
return (
<CounterButton onClick={onClick}>
{count}
</CounterButton>
)
}
The constant need to shuttle away the “real code” here, is befuddling. But lots of people advocate for this. The redux example here is pretty much lifted straight from their docs, and I worked in React codebases like this for years. People see the clean markup in the body of the component and go “such clean code, much literate programming”, conveniently ignoring that all the incidental complexity of the problem has been sprinkled around instead, like shifting the food around on your plate to make it seem like you’ve eaten more than you have.
I cannot overstate how much I like being able to do things in the same file
— @jacobparis.com ❖ (@jacobmparis) August 22, 2024
When the sales people promise new functionality to a big client – a button to decrement the value – you’ll need to go add it to the reducer, export that action, create a new styled button, call useSelector again to fetch that bit of state… And look I generally care more about readability of code anyway, so I’m not just complaining that you have to write code in a bunch of places – it’s hard to read too. You have to go on a journey of self-discovery to figure out where things actually get done.
This isn’t the worst it gets, since a lot of people are still using action creators + actions model, which makes an amazing trade-off: more boilerplate and less locality of behavior, and in exchange you also get a total lack of type safety.
It may seem I’m getting off-track here, but Redux-bashing largely reduces (heh) to criticizing the single responsibility principle, because Redux is like the platonic ideal of this misguided thinking that you have to divide up your code by categories.
Styles there, state updates there, markup there, actions there, action creators there, db operations there, cache updates there… It’s like we’re collectively suffering from a very specific brand of OCD.
The sane way, for the record
const [store, setStore] = createStore({ count: 0 });
function Component() {
return (
<button
class="p-4 bg-blue"
onClick={() => {
setStore("count", store.count + 1);
}}
>
{store.count}
</button>
);
}
The styling is there, the state update is there. Fetching from the store can be done right in the markup too. Hell, if I had a network call to make, I might do it right there too. I’m crazy like that. If in the future you want to reuse the styling, or call increment from more places, you can extract stuff then. Don’t pay the cost of extracting that code upfront, before you have any reason to suspect that you’ll need it from other places.
Hamiltonian vs Jeffersonian interpretations of the constitution single responsibility principle.
When I’m arguing, against others or myself, about satisfying this principle, it can resemble a supreme court hearing where we’re trying to debate whether this or that separation of concerns satisfies the letter of the law or not. “You see, if you interpret the SRP in this way, one could argue that we are dealing with a single responsibility, because user authentication is one responsibility. So if we partition by business logic instead of programming categories…”
Stop. We’re not on the supreme court, and we’re not religious scholars arguing over this or that interpretation of the Hadiths. We can just ditch this principle entirely, and get to what we care about instead. Are we writing code that is easy to read, write, and maintain, or are we not?
If you play games, my PSN is mbuffett, always looking for fun people to play with.
If you're into chess, I've made a repertoire builder. It uses statistics from hundreds of millions of games at your level to find the gaps in your repertoire, and uses spaced repetition to quiz you on them.
If you want to support me, you can buy me a coffee.