It's similar. Commenting my code (which I do, almost religiously) helps. It still taxes my brain to follow it.
There's a certain kind of abstraction that is easier to write than read and understand. My current project is full of it, not least because a set of low-level predicates performing "primitive" operations on a foundational data structure are automatically generated and then everything else is built on top of them with a concentrated effort to avoid code duplication. There's a bunch of actions that move an agent around a map or look from the agent's position around the map, in discrete directions (currently) and the easiest way to implement these would be to implement one, say "step_north" or "look_north", and then copy/paste it with small changes however many times I need. Instead I opted to have parameterised "step" and "look" actions that I instantiate as I need. It's kind of the obvious thing to do, but starting from a "step" action, in my zeal to DRY (or NRM, I guess) I ended up creating a chain of predicates six or seven links deep that makes it harder to trace the execution of a top-level (step or look) action, just because I have to keep in mind each link in the chain and what exactly it does; and that's not obvious because some links compose new predicates from their arguments so I need to have a clear model of how that happens always in my mind. I could keep the chain shorter by using a higher level of abstraction but that would just make it even harder to debug.
Prolog makes it easy, even pleasant, to program like that, but it doesn't make it any easier to read and maintain that kind of code than any other language as far as I can tell.
Maybe the solution is not not do any metaprogramming and just copy/pasta and DRY and get it over with. But I find that this, too, makes it harder to debug because after a while all the instances of the copy/pasted code blur together into one smudgy fudge that has a downright hypnotic effect.