Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Seems like kind of a meaningless gesture to pass in your own “this” just because you don’t like OOP. If you need a class, why not write it the idiomatic way?


I didn't down-vote you, but in my opinion it's not meaningless because it makes it inherently more testable. Try testing an opaque class without any accessibility into its state. It is much more difficult. If it takes in the state, performs an operation, and returns a new version of that state (ideally an immutable copy) then it becomes much easier to test and validate.

The reason information hiding is/was advocated was for reduced coupling. But in reality I find that this coupling becomes implicit which is arguably worse. But this is just my opinion.


I see your point, but I 80% disagree with your conclusion.

When you test opaque objects, you're generally not trying to test individual state transitions, you're trying to test that the class's interface adheres to the external promises it makes.

That said, many OOP languages have solutions specifically for when you actually do need to test those internal state transitions. C# for example has the "internal" keyword and allows you to declare friend assemblies, so you mostly get your cake and eat it too, at the cost of not hiding the code from yourself as the module implementer.


I interpreted the top-level comment as a list of extra external promises you could add to your class's interface (incremental search, save/restore, etc). If those are easier to test with functions and a state parameter, it might be better to skip the class.


Isn't how you pass things into a function separate from whether you are hiding information? If your state parameter was an opaque pointer then you wouldn't necessarily be able to do anything with it. Also if your class internals were public you would be able to change anything you wanted.


Exactly! You don't need classes to obtain information hiding. In general treating the state as an opaque pointer is a good thing. But the state is part of the public API whether you want it to be or not. Even if you try to "hide" it in the OOP sense, the result of the methods depends on the internal state so it is leaked through the behavior of those methods.

However, when it is hidden in the OOP sense it makes it much harder to reason about the behavior of a function without reading the source code. Because there is this internal "hidden" state that you do not know exists.

But a referentially transparent function is much easier to understand. For a given input, you get an exact output.


You're talking yourself in circles. Your final sentence contradicts the remainder of your point.

Testing is limited when you do not have the ability to fully control state, and understanding can be limited when information is hidden. The impacts of this are determined by the information being hidden, its relationship to the function's behavior, and the documentation of that behavior relative to the calling context.

Limitations on the ability to fully control state are orthogonal to the method of information hiding (opaque function parameters, internal object state, etc).

This is not an OOP/functional discussion, this is a discussion about the tradeoffs inherent in information hiding. And let's be perfectly clear here: These are tradeoffs, not black and white clear wins in either direction.


I think information hiding is the wrong term. It should be hidden for modification, open for inspection. Functional programming naturally promotes this. OO does not. That's my opinion.

It's been a useful discussion though so I appreciate everyone's input.


So are you saying you no longer think OO inherently makes things harder to test?

I feel like it's difficult to talk about this without examples. The library I like thinking about when I think of passing around in a state variable is the lua C api.

https://www.lua.org/pil/24.1.html here is an example.

https://pgl.yoyo.org/luai/i/lua_State here are docs for the state variable's type.

This is pretty object oriented except that it's not using C++'s syntax sugar. You can't really do much with L except pass it into other lua "methods". The discussion up until your comment seems to be about the difference between using the syntax sugar and passing around a state variable yourself.

Information hiding and these other implementation details are kind of seperate. In my example you can't just configure L exactly how you want by messing around with it or inspecting its state directly. You have to go through accessor "methods".

I think if the argument is that OO promotes information hiding I can agree with that. I'm not sure about the point about the internal state becoming a part of the API though. APIs are contracts that can be met with different implementations right? If your implementation details are public then your contract is huge and inflexible.


Not exactly, I'm saying OO still makes it hard to test because it is an opaque blob of state. It would be really hard to use/test the LUA API for example without knowing it is using a stack underneath the hood. That detail leaks through the API whether you want it to or not. If it was switched to a FIFO queue that would completely change the behavior.

Since using this API makes an implicit constraint that it is using a stack underneath the hood, why not just expose the stack for inspection? What advantage does keeping it an opaque blob have? I agree you don't want code manipulating this stack (although if it is immutable as in FP this isn't an issue), but since the external code already knows it is a stack and relies on that fact then it is part of the public API already.


The term information hiding used in this thread is very confusing (to me, at least).

I believe David Parnas introduced it in 1971 to mean that a program's design was sliced along shared units of concerns (things that vary together) rather than "steps in a flowchart".

https://prl.ccs.neu.edu/img/p-tr-1971.pdf

I believe what you are trying to convey is called "data abstraction", as for example used by Reynolds, 1975;

mentioned here: https://www.cs.utexas.edu/~wcook/papers/OOPvsADT/CookOOPvsAD...

explained here: https://link.springer.com/chapter/10.1007%2F978-1-4612-6315-...


The class will have to provide you with an option to get your answer back after a computation, so that's what you test. I don't see a reason to test how a class computes a result as long as the result is correct.


Well either you have internal state or you don't. If you don't have internal state (and it just returns the answer), then there is no difference between it and a function.

If it does have internal state, then the answer it returns depends on the value of that internal state which means it's hard to verify that the answer returned is correct since you have to know the internal state of the object.


The whole idea of internal state is that it is not accessible (so not relevant) to the outside world.

You test a class as a user by calling its methods in the desired order (per requirements and documentation) and you check that the results you get are correct. If the results are correct, the class fits your use case. If they are not, then you're either calling it wrong or it has a bug. Either way, no need to know the internal state.


That's a great ideal, and I understand the arguments in favor, but the reality is that sometimes in order to test all of the internal code paths, you have to go to extremes when only interacting via the public interface. If I'd have to write 50 lines of extra testing code (or worse, extend various classes to add fake hooks into external dependencies, etc., which is where testing tends to really get messy) to validate that some edge case is handled appropriately, it's sometimes worth skipping that and fiddling some internal state to jump straight to the edge case.


"Calling its methods in the desired order"

This is my point. The order of calling the methods matters, because it manipulates opaque internal state. Since this ordering matters, the caller is implicitly dependent on this internal state as it must know the order to call the methods in to get the desired result.

So it's very relevant to the outside world in my opinion and nothing is truly hidden. It becomes part of the public API whether you hide it or not.


It still depends.

Think of a List class. Let's say it has methods to add, remove and retrieve elements, and it has a method to retrieve the size.

Let's say the internal implementation is an array with a fill pointer.

Obviously, before you can usefully call list.get(7), you must have called list.add(T) at least 7 times (assuming no deletes).

Do you need to know the the state of the backing array and the value of the fill pointer? Do you need to know whether there even is a fill pointer, or a linked list underneath (obviously, the performance would be different so you would need to know at some point)?


You do not want to test private implementation. You should be testing the public contract only. Reaching in and making private things public crystallizes the implementation and reduces your ability to refactor.


If you’re asserting non-public internals of a class, your testing is wrong in the first place. Assert outcomes, not the process.


That's the whole point. You can't test outcomes if they are dependent on internal hidden state as that affects the outcome.

Whether you make it private or not hidden state leaks into the output since the output is dependent on it.


It sounds like you’re confusing side effect free code with OOP. You can use OOP while having side effect free stuff that never requires you to assert internals.


    Tests.h
    #define private public
    #define protected public


Maybe I'm mis-reading it, but it seems that your comment is just a re-statement of what your parent comment is replying to. It’s like, “Why A” “Because B” “But why A?”


Because it's like using a while loop to print out each character in a string when you can just do print to print the entire string.

I don't want to worry about external methods modifying state, I don't want to deal with constructors, I don't want to deal with instantiating state or modifying state before I call my function.

My function just takes a state and returns a new state. Simple.


In what way do you 'need' a class? It uses none of the features that a class provides over normal structs.


The idea is, if you need the functionality that classes give you (passing the implicit state between multiple calls), why not use the special syntax for that use case?


Because sometimes you don't need a class, or don't need the full-blown machinery that an object system entails.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: