Writing software is all about mastering abstraction.When modelling a problem in software you are always ignoring some details and representing others. Hopefully capturing all the ones that influence the desired behaviour of the system.
eg. Does Github need the weight of each developer? Will it behave differently for people based on that data? no? then no.. don't capture that aspect of the world.When modelling a problem domain, we frequently see a way we can 'think about the problem' ... usually some sort of analogy. Typically we do this to remove duplication (which is one indication that a useful abstraction may exist).
eg. So... when the users save the reports we could think of those as "save-requests" and "queue" them and have a "worker" "process" them all.
When we introduce that analogy to the code, then when someone is reading the code they have to now understand the intended behaviour of the system, and the abstraction we have chosen to use to help solve the problem.Every step we take away from describing the actual problem in code is a mental step the reader has to take to understand the solution. This is extra work paid every time you reason about the code. So... the abstraction has to have enough value to be worth including. eg. it make the code easier to think about or much faster to execution or more composeable....When we pick the wrong abstraction, the code is harder to read, harder to think about, and the building blocks are hard to assemble into the behaviour we want. They tend to have unintended side effects, and do extra work in-case it's needed, the slows down performance.Refactoring verbose code into some sort of abstraction is fairly easy.
Refactoring abstract code from the wrong abstraction to a better one is usually much harder.So... what abstractions do we have in our code? What do they make easy? What do they make hard? Are they helping us or getting in the way?