The biggest impediment to maintaining large software is how amenable it is to change. In coaching junior developers, the three biggest issues I encounter daily are:

  1. coupling,
  2. bad abstractions (leaky or concrete), and
  3. mixed concerns

Coupling

'Coupled' or 'complected' means two things are braided together, like a hairball. If you pull on something over here, it also pulls on something over there. The more things it tugs on, the more complected it is, and the harder it is to change.

For example, every time you write this or self in a function, you are tying it to an instance of that class. Consider adding a function argument instead and let the caller decide how it would like to regurgitate its hairballs. Better yet, make it static. Do you really need another class?

Abstraction

Concrete means the opposite of abstract. Just because you moved a function does not mean it is now magically abstract and reusable. You have simply stretched the hairball out and now I will knock over more lamps when I trip over it. Keep coupled things together, so I can see how big the hairball is. Good abstractions hide implementation details and do not leak.

Mixing of Concerns

Separate concerns. A unit of code should do as little as possible. The more things it does, the more concrete, coupled and leaky it tends to be. You can usually tell by overloading or the duality of its arguments.

Naming (honourable mention)

If I have to read your code to see what it actually does, rename it.