2. Don't be eager to concretize your abstractions. e.g. It may be the case that you need to use a state machine (SM). If you already have state types/objects, you can add what's necessary to clearly document and enforce transitions. What isn't always necessary is extracting the state machine portion to an abstract set of nodes/edges. Such a thing is useful if you have either dynamic SMs or multiple similar ones that are hard to maintain 'in place'. You can easily tell which path you're on if your class names don't have any domain terms in them and instead layer mechanism names. i.e. A class/object can implement a mechanism without having it extracted to being its own class/object.
The above could be summarized to saying write "clear code" (domain level thinking) rather than the shallower "clean code" mechanical refactoring.
That being said, I find it helpful for top-level scripts to be "about a page long" at most
e.g. often a 300 line main.py file is overkill and becomes easier to understand if compartmentalized down to a few high level components like connect(...), load(...), parse(...), etc
Build abstractions at the right time. Base classes and common structs should hold completely common information, not be a bag of data that may or may not be there. Be tight with your reasoning about inheritance.
Write tests, useful ones that exercise the logic. (Setters and getters aren't worth it usually.)
Try for no warnings with your static code analyzer / linter / compiler?
Make tests for it
Add logging
Put it on gitlab pipeline, make a CI/CD for it
Make it as a standalone docker container (see above point)
Update or make the readme.md
So don't be lazy and try to put that new functionality into a new class instead of trying to squeeze it into existing classes and functions - that makes it more testable, separated and uses the "composition instead of inheritance" dogma. It will make the code somewhat cleaner and a thousand time more refactorable.
#header { background: grey; color: white; display: block; font-size: 12px; }