object orientated programming is the wrong idiom for almost all problems, and even in the few cases where it makes sense, you have to be very careful or it'll hurt you
I have been trying to be more functional but I still use classes for things like loading/modeling configs. What are some common situations where using an object is a good solution?
What are some common situations where using an object is a good solution?
It depends on what you mean by "object"
Some kind of structured data?
Some named type which fulfills an interface?
When you have some kind of structured data, having a class to represent it is fine. If you're able to give it type annotations, that's much better than passing around random dictionaries.
When you need polymorphism and have an interface where some method on an object needs to exist (e.g. car.honk()), that's also fine as long as you avoid creating subclasses and using inheritance. If you need some car that can honk like a truck and drive like a racecar, use composition.
What I would consider a good use of classes (more specifically, nominal types) is refinement types. The Wikipedia page is lacking, but the idea is that you use the type system to enforce invariants for data.
For example, suppose you have a string for a user email. It might be a valid email string, or it might be garbage like "z#%@("=))??". You have a function for updating the user email in a database, and it requires the email string to be valid.
One approach is to validate the email string after receiving it from the user. That works, but what if your coworker creates a new form and forgets to validate the email string there? Bad data gets passed downstream to functions that expect well-formed data.
Another approach is to validate the email string at the top of every function that expects well-formed data. That also works, but now you're validating the same string multiple times and pasting validate_email(email) everywhere.
With a refinement type, you have a ValidatedEmail type and a constructor for it. The constructor will return an instance of the ValidatedEmail if and only if the email string is valid. Any function that expects a valid email will only accept a ValidatedEmail, and not a string. If your coworker creates a new form and forgets to validate the email, the type system will complain about a string being passed instead of a ValidatedEmail. You also shift the responsibility of validating the email to wherever there is a boundary between validated and unvalidated data, avoid having to validate the same email multiple times, since you know a ValidatedEmail is already valid.
Stuff like enemies, bullets, loot, items, etc. are constantly changing and affecting each other. This makes it difficult to program in fixed functions that run through and crosscheck a bunch of constantly changing arrays.
It makes logical sense to have objects with their own functions stored in dynamic lists instead of arrays.
A lot of the properties are used by several different objecs, like coordinates or health points, so it also makes sense to describe them in classes.
So, I'd say that it's a useful way to classify data that is very dynamic, or when different functions need to run depending on that data.
I don't like putting all code into classes, because a lot of code doesn't need that kind of flexibility.
Idk. Maybe it's because I learned OOP first that it makes more sense to me; but OOP is a good way to break down complex problems and encapsulate them into easily understable modules. Languages like Java almost force everyone on the project to use similar paradigms and styles, so it's easier for everyone to understand the code base. Whenever I've worked on large non-OOP projects, it was a hard-to-maintain mess. I've never worked on projects such as the Linux kernel, and I'm hoping it's not an unmaintainable mess, so I'm pretty sure it's possible to not use OOP on large projects and still be maintainable. I am curious if they still use OOP concepts, even though they are not using strictly OOP.
I also like procedural python for quick small scripts. And although Rust isn't strictly OOP, it obviously borrows heavily from it. Haskell is neat, but I haven't used it enough to be proficient or develop good sense of application architecture.
I've done production work in C, but still used largely OOP concepts; and the code looks much different than code I've seen that was written before C++ was popular.
The Linux kernel actually uses quite a bit of OOP ideas. You have modules that are supposed to have a clear interface with the rest of the world, and they (ab)use structs to basically work like objects. If you try hard enough, you can even do "inheritance" with them, like with their struct kobject. It is actually somewhat well-thought-out, imo. No need to go full OOP, just pick some of the good parts, and avoid the MappingModelFactoryServiceImpl hell or the madness that is C++.