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

The important thing is restricting your public interface, hiding implementation details, and thinking about how easy your code (and code that uses it) will be to change later. It's not an OO vs anything thing.

When you want a value from a module/object/function/whatever, whether or not it's fetched from a location in memory is an implementation detail. Java and co provide a short syntax for exposing that implementation detail. Python doesn't: o.x does not necessarily mean accessing an x slot, and you aren't locking yourself into any implementation by exposing that interface as the way to get that value. It's more complicated than Java or whatever, here, but it hides that complexity behind a nice syntax that encourages you to do the right thing.

Some languages provide short syntax for something you shouldn't do and make you write things by hand that could be easily generated in the common case. Reducing coupling is still a good idea.



> The important thing is restricting your public interface

That is the important thing sometimes. At other times the important thing is to provide a flexible, fluent public interface that can be used in ways you didn't intend.

It really depends on what you're building and what properties of a codebase are most valuable to you. Encapsulation always comes at a cost. The current swing back towards strong typing and "bondage and discipline" languages tends to forget this in favour of it's benefits.


> At other times the important thing is to provide a flexible, fluent public interface that can be used in ways you didn't intend.

That scares me. How do you maintain and extend software used in ways you didn't intend?

Quality assurance should be challenging.


> That scares me.

It scares you because you're making some assumptions:

1. You assume that I'm writing software that I expect to use for a long period of time.

2. Even if I plan to use my software for an extended period of time, you're assuming that I want future updates from you.

Let me give you an example of my present experience where neither of these things are true. I'm writing some code to create visual effects based on an API provided by a 3rd party. Potentially - once I render the effects (or for interactive applications - once I create a build) my software has done it's job. Even if I want to archive the code for future reuse - I can pin it to a specific version of the API. I don't care if future changes cause breakage.

And going even further - if neither of these conditions apply the worst that happens is that I have to update my code. That's a much less onerous outcome than "I couldn't do what I wanted in the first place because the API had the smallest possible surface area".

I'll happily trade future breakage in return for power and flexibility right now.


Maybe instead of "restrict" it would be better to say "be cognizant of." If you want to expose a get/set interface, that's fine, but doing it with a public property in Java additionally says "and it's stored in this slot, and it always will be, and it will never do anything else, ever." I don't see what value that gives in making easy changes for anyone. I don't see why that additional declaration should be the default in a language.

You get into the same issue with eg making your interface be that you return an array, instead of a higher-level sequence abstraction like "something that responds to #each". By keeping a minimal interface that clearly expresses your intent, you can easily hook into modules specialised on providing functionality around that intent, and get power and flexibility right now in a way that doesn't hamstring you later. Other code can use that broad interface with your minimal implementation. Think about what you actually mean by the code you write, and try to be aware when you write code that says more than that.

I think it's interesting that you associate that interface-conscious viewpoint with bondage and discipline languages. I mostly think of it in terms of Lisp and Python and languages like that where interfaces are mostly conceptual and access control is mostly conventional. If anything, I think stricter type systems let you be more lax with exposing implementations. In a highly dynamic language, you don't have that guard rail protecting you from changing implementations falling out of sync with interfaces they used to provide, so writing good interfaces and being aware of what implementation details you're exposing becomes even more crucial to writing maintainable code, even if you don't have clients you care about breaking.

Of course all this stuff goes out the window if you're planning to ditch the codebase in a week.


I don’t think I’ve ever seen a useful “Getter” abstraction...


  getArea() {
    return this.width * this.height;  
  }

  getIcon() {
    // if icon hasn't been loaded, load it
    return this.icon;
  }


Those aren’t abstractions... Also, I’m not arguing that you can’t contrive an abstraction around a getter, I’m arguing that it’s useful to do so (so please spare me contrived examples!).


You're always using a getter. It's just a question of what syntax your language provides for different ways of getting values, and how much they say about your implementation.

Most people don't have a problem with getters and setters, they have a problem with writing pure boilerplate by hand. Languages like Python and Lisp save you from the boilerplate and don't provide a nicer syntax for the implementation-exposing way, so people don't generally complain about getters and setters in those languages, only in Java and C++ and things.


You misunderstood my post. I said I haven’t seen a useful getter abstraction. Not all data access is via a method nor is it always abstract.

I specifically object to the useless abstraction, not the boilerplate (boilerplate is cheap).


I think we're coming at it from different angles. My point is that there shouldn't be any abstraction to write, and it should just be the way the language works. Primitive slot access in Java is not just a get/set interface, it's a get/set interface that also specifies implementation characteristics and what the code will be capable of in the future. It should be in the language so that you can have primitive slots, but it shouldn't be part of the interface you expose for your own modules, because adding pointless coupling to your code does nothing but restrict future changes. Languages should not provide an easy shortcut for writing interfaces like that.

I don't view it as a useless abstraction, because I view it as the natural way of things. I view specifying that your get/set implementation is and always will be implemented as slot access to be uselessly sharing implementation details that does nothing but freeze your current implementation strategy.

I think a better question is when that abstraction gets in your way. When does it bother you that nullary functions aren't reading memory locations? Why do you feel that's an essential thing to specify in your public interface, as a default? There's nothing stopping you from writing code in Python and mentally modelling o.x as slot access, because it follows the interface you want from it.

If you only care because it's something extra you have to do, then that's what I meant by boilerplate. I think it's a misfeature of Java's that it presents a model where that's something extra you have to do.


> My point is that there shouldn't be any abstraction to write, and it should just be the way the language works.

I understand your point, but I think you misunderstand what "abstraction" means. "abstraction" doesn't mean "function" (although functions are frequently used to build abstractions), and if you have "dynamic properties" (or whatever you'd like to call them) a la Python, then you're still abstracting. My point is that abstracting over property access (regardless of property-vs-function syntax) is not useful, or rather, I'm skeptical that it's useful.

> I think a better question is when that abstraction gets in your way. When does it bother you that nullary functions aren't reading memory locations? Why do you feel that's an essential thing to specify in your public interface, as a default? There's nothing stopping you from writing code in Python and mentally modelling o.x as slot access, because it follows the interface you want from it.

I think this is a good question, because it illustrates a philosophical difference--if I understand your position correctly, you'd prefer to be as abstract as possible until it's problematic; I prefer to be as concrete as possible until abstraction is necessary. There's a lot of mathematical elegance in your position, and when I'm programming for fun I sometimes try to be maximally abstract; however, when I'm building something and _working with people_, experience and conventional wisdom tells me that I should be as concrete and flat-footed as possible (needless abstraction only makes it harder to understand).

To answer your question, that abstraction gets in your way all the time. The performance difference between a memory access (especially a cache-hit) and an HTTP request is several orders of magnitude. If you're doing that property access in a tight loop, you're wasting time on human-perceivable timescales. While you can "just be aware that any given property access could incur a network call", that really sucks for developers, and I see them miss this all the time (I work in a Python shop). We moved away from this kind of "smart object" pattern in our latest product, and I think everyone would agree that our code is much cleaner as a result (obviously this is subjective).

TL;DR: It's useful to have semantics for "this is a memory access", but that's unrelated to my original point :)


It's frustrating to read this thread and your comment kind of crystallized this for me so I'll respond to you.

Using an array without having to (manually) calculate the size of the objects contained within is like the major triumph of OO. This is a getter that you almost certainly use constantly.

Please try to consider your statements and potential counter factuals before spraying nonsense into the void


> Using an array without having to (manually) calculate the size of the objects contained within is like the major triumph of OO.

Er, aside from C and ASM, few non-OO languages require that kind of manual effort. That's not a triumph of OO, it's a triumph of using just about any language that has an approach to memory management above the level of assembly.


> Please try to consider your statements and potential counter factuals before spraying nonsense into the void

My claim was that getter abstractions as described by the GP (abstracting over the “accessed from memory” implementation detail) are not useful. Why do you imagine that your array length example is a reasonable rebuttal?


Its not the length of the array. Its using things like array[20]. Yes that exists pre-OO and outside of OO, but its the foundational aspect of OO and one of the strongest use cases.

Sorry for the way I communicated- I was tired and should have reconsidered.


> Sorry for the way I communicated- I was tired and should have reconsidered.

No worries, it happens. :)

> Its not the length of the array. Its using things like array[20]. Yes that exists pre-OO and outside of OO, but its the foundational aspect of OO and one of the strongest use cases.

I'm not sure what you're getting at then. Indexing into an array? Are you making a more general point than arrays? I'm not following at all, I'm afraid.


I think my argument is basically that arrays are effectively object oriented abstractions in most languages.

You aren't responsible for maintaining any of the internal details, it just works like you want it to. My example was with the getter for the item at index 21 (since you had specifically called out useless getters), but equally well applies to inserting, deleting, capacity changes, etc.


> I think my argument is basically that arrays are effectively object oriented abstractions in most languages.

I think I see what you mean, although I think it's worth being precise here--arrays can be operated on via functions/methods. This isn't special to OO; you can do the same in C (the reason it's tedious in C is that it lacks generics, not because it lacks some OO feature) or Go or Rust or lisp.

These functions aren't even abstractions, but rather they're concrete implementations; however, they can implement abstractions as evidenced by Java's `ArrayList<T> implements List<T>`.

And to the extent that an abstract container item access is a "getter", you're right that it's a useful abstraction; however, I don't think that's what most people think of when they think of "getter" and it falls outside the intended scope of my original claim.


Watch your tone!


> Using an array without having to (manually) calculate the size of the objects contained within is like the major triumph of OO.

I've used arrays in countless OO and non-OO programming languages, and I do not recall ever having to manually calculate the size of objects contained therein – what are you talking about? Only C requires crap like that, but precisely because it doesn't have first class arrays.


Downvoters, care to elaborate what you think is wrong with the above? Literally even fortran can do better than

   size_t len_a = sizeof(a)/sizeof(a[0]);
or

   my_pseudo_foo_array = (foo*) malloc(len * sizeof(foo));


You're not wrong. Even BASIC was better than this.


HTTP GET :-)


> Python doesn't: o.x does not necessarily mean accessing an x slot

C# also 'fixes' that. o.x could be a slot or it could be a getter/setter.


Initially seen in languages like Eiffel and Delphi.


I have basically no experience with Java. But in C# I think the above is whats behind stuff like

fooobj.events += my_eventhandler;


It is, but those languages did it about 6 years before C# came into existence.

Which isn't surprising, given that Delphi took the idea from Eiffel, which share the same Pascal influence, and was designed by Anders.


Not to mention Anders and C#.




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

Search: