Hacker News new | ask | show | jobs
by devmunchies 1121 days ago
The worst part of OOP is that all the properties of an object can be a mishmash of values and are mutable. In any method, you never know if the object is in some undesirable state without checking properties within the method itself. Multiply that headache across all methods and all other classes and it becomes a mutable mess. It makes it weird that we pass around objects as types when they encapsulate so much state and logic. They aren’t really a concrete data types, they are an entire living village.

With functional languages, it tries to enforce some explicit type signatures in the function arguments so things are cleaner within the functions themselves.

4 comments

This isn't a property of OOP. This is a property of poor class design. You absolutely should be designing classes such that every possible sequencing of their public methods leaves them in a valid state and maintains their invariants.

Structs have the issue you describe and they aren't really OOP.

Yes, if the first thing you do when you write a class is make a setter method for each field then you will have problems. That's not really a property of OOP.

Poor class design IS a property of OOP.

All of these logical errors that are easy to commit are terrible because they are usually runtime bugs, not compile time.

As I think of it, I think a neat feature of OOP would be conditional methods that are only callable under specific circumstances. For example, the “Customer.SendPasswordResetEmail()” method couldn’t be called (or didn’t even exist) until I verify that the “Customer.IsEmailVerified” property is true.

Being able to add these type of annotations to methods for expected object state would help catch some logic bugs at compile time.

> The worst part of OOP is that all the properties of an object can be a mishmash of values and are mutable.

Const-ness is one of the things I really miss from C++. I could look at an object and be reasonably sure I wasn't mutating it by calling foo.length() for example.

IMHO that is of such little help and the drawbacks weigh much heavier: const-correctness spreads like a cancer (try making one thing const without having to fix a hundred other things), and often requires annoying boilerplate -- I'm no C++ expert but if these are still the best available solutions...: https://stackoverflow.com/a/123995/1073695

Sometimes I want to add one little extra thing that gets mutated in an otherwise "const" method, e.g. for debugging purposes. If the compiler doesn't let me do that because I valued the ideal of const-correctness higher than practical concerns, I know I've done something wrong.

Perhaps it depends on the code-base. I worked on a medium complexity C++ project (~300kLOC) but which used multi-threading quite heavily with shared data structures, and there was only a couple of instances where I felt it got in the way.

In the vast majority of cases it reduced my cognitive load significantly because I could just look at the method declaration and see that my code would be fine.

Yes, as always it all depends on the context and how features are used. Maybe I was just bitten too often in situation where const is particular gnarly to use. I know for sure that in many cases, such as when calling small helper functions for copying a shallow array and such, one can easily pass pointer as pointers-to-const.

However, IME there is a big problem with const for more database-y, more stationary in-memory data. This is the kind of data that is almost always going to be mutated by at least some part of the code at some point in time. There is a fundamental problem of communication between mutating code and non-mutating code (the "strstr()" function, that has to apply a const-cast hack internally to implement its interface, is a trivial example here).

As said there are certainly situations where such "communication" isn't needed, but I'm anxious about precluding the possibility in the name of const-correctness.

I feel that instead of const (or whatever static formalized description of what a function is doing), good naming is most helpful to intuit broadly what that one function was doing again.

In C at least, I've ended up leaving const almost exclusively for the cases where the data is truly const - i.e. in the .ro section of the binary, and I know for sure it won't ever have to be modified, and basically I have to apply the const qualifier lest it puts the data in the wrong section / it needs an awful cast to remove the const. The majority of those are string literals typed as "const char *".

Lisp derived languages, with dynamic gradual typing, and dynamic scopes, say hello.
Erlang and Elixir store state in arguments of recursively called functions, usually running in their own processes separate from the rest of the application. There is nothing in the language to enforce correctness of the state. They are generally regarded as functional languages even if they are somewhat object oriented if one thinks about their message passing as method calls to the object / process storing the state.