Most of the illegible code I've ever written was because I kept splitting functions up, thinking I was following good practice, but was really just making emotional/aesthetic decisions.
When you go back to edit your code, it's like calling a 1-800 number and getting re-routed to 15 different departments to finally find the person you need to talk to.
I see this all the time with "business rules" problems.
If you have a situation where you can't make levelled abstractions, you've got some thorny interconnections in your logic (and those may be fundamental!!!). The way I handle this is with a "gauntlet pattern." You still can split your logic up into parts, but you just do it by "rejecting" certain logical chunks at a time within a function and comment above each state of the gauntlet.
It looks something like this:
// marketing told me we should never do this ever under any circumstances
if (!a) {
return CASE_1;
}
// if the user enrolled like this, we should check if there's this bad property about that user
if (!b) {
return CASE_2;
}
// oh, you crazy people in finance
if (c > 4 && d < 0) {
return CASE_3;
}
return CASE_4;
The key thing is not to get hung up on duplication or having exact control flow like how the business thinks about it. You want 1) to return the largest percentage or simplest cases out first, 2) keep everything flat as possible without layers of branching, and 3) be able to associate tests to each one of those early-reject lines.
The nice thing about this is the reduction of cognitive load. By the time you get to the 3rd case or whatever, your mind can already know certain things are true. It makes reasoning about rules much easier in my experience.
When you go back to edit your code, it's like calling a 1-800 number and getting re-routed to 15 different departments to finally find the person you need to talk to.