Hacker News new | past | comments | ask | show | jobs | submit login

It is very interesting to see in such projects how they expose the weakness of certain Java idioms. The mentioned JavaBeans getters and setters are obsolete pattern, for which in most cases there’s no good reason to keep it in the code. Java ecosystem is probably the richest one on the design patterns, some of which receive „anti“ prefix over the years (e.g. self-contained singleton). At the same time new languages explore and verify the ideas and coding styles that might be worth to borrow and make mainstream. Porting exercise can be such source of new best practices pushing us to rethink what is worth keeping and what is pointless overengineering.



I was struck by the lack of inheritance in go, that to me is brilliant.

I’ve worked with C# for a decade, and I’ve yet to see a use of inheritance that wouldn’t have been easier to maintain in the long run without using inheritance. We’ve limited our own useage to override methods in the standard library, but even then it’s often used to implement things that are really just terrible practices. Like adding search functions for AD extension fields or increasing the timeout in one of the older web clients.


Embedding does seem like one of the decisions Go got very right.

Many languages (thinking c#, Java, c++) say they promote composition over inheritance but yet make inheritance so mmuch easier to use.


Until 1.8 Java’s composition over inheritance story was weak. 1.8 introduced default functions on interfaces, which finally allows shared functionality without inheritance. In some senses Java’s patterns in this area have been reset, and many old code bases need to catch up (and probably never will).


Well, from where I sit, interfaces are another kind of inheritance - inheritance of API rather than implementation. But once you can have default functions, that to me looks exactly like inheritance of a base class, except that the base is called "interface" instead of "class".

Am I missing something?


It's a difference of inheriting data vs. functionality, so yes, the API is what's being inherited.

Looking at the history of inheritence a few languages I know:

- C++: multiple inheritance super confusing expressions and understanding of things like diamond inheritance. Initialization becoming a really complex thing to understand.

- Java: learning from the mistake of C++, determines multiple inheritance is bad, only allows inheriting data from one super class and then API (and now in 1.8 functionality) from any number of interfaces.

- Rust: realizes inheritance of any data is bad, and only allows for any number of Trait's (interfaces) to be implemented on types.

There's a difference, and ultimately it's that you're not carrying data via the inheritance chain, only functions. The benefit here being that it's much easier to reason about a type and what data it has that determines how it works in the broader system. This allows for very explicit inclusion of data from other types, as opposed to implicit inclusion with C++ and/or Java. In the end moving away from data inheritance leads to fewer mistakes and easier understanding of the code.


Q: Who uses inheritance in C++, anymore?

A: Java coders.

Inheritance defines a mechanism that is sometimes useful. It is useless as an interface design tool.


With Java 1.8 you can ask the same thing about Java and the answer would be the same.

I didn’t mean to suggest that it’s idiomatic in C++ to create strange inheritance graphs, anyone who’s done it once realizes it’s a bad pattern. The issue is more that the language -allows- it.


Except that a class can inherit from multiple interfaces.


Composition over inheritance solves only certain language problems where limitations of expressive power make the code less transparent or flexible, but it’s not a solution for decomposition. The problems with properly used inheritance arise only in sufficiently old and evolved code, because decomposition of one domain does not necessarily describe well another (evolved) domain. Programmers tend to think that every problem in the world should be addressed by better coding, so they try to change their coding practices. In fact, solution here is managerial - throw away the old code and do the decomposition once again, for the new circumstances and new requirements. OOD and inheritance work well, so let’s not blame them for the use cases which they cannot solve.


100% this. We're never going to built a perfect sand castle.

IMHO - There are many managerial short comings in software development that lead to abuse of legacy design. OOP by design allows you to abstract and ignore not only original implementation decisions but reflections of those decisions in the architecture. Polymorphism makes sense when it's the classic ICat: IAnimal example but so often it _becomes_ IHouseFly : IAnimal because all the contracts expect IAnimal and the deadline is.....tomorrow.

I personally don't have a good solution given the pragmatic counter argument that the ROI on a system which is cheap to develop but is patched over 5 years may be equal to or cheaper than one that is expensive to develop and _still_ needs to be patched over it's 5 year lifetime. Let's call this the used car problem. A new car is more reliable but now that used cars are reliable _enough_ it's harder to convince people _not_ to gamble with a lower upfront cost.

I absolutely _love_ Rust and the code I'm written feels bullet proof. No idea how long it would take a team of my C# peers to be even 1/10th as productive in Rust/Go as they are in Visual Studio and C#.


Indeed! This has always mystified me. Why is it so easy to implement what is now considered to be an anti-pattern (inheritance) when it's so boilerplate and annoying to implement composition? Why does C# not have language support for delegating members? Why do I have to buy and use ReSharper just to generate all that boilerplate? It's a constant battle talking to "just get it done" developers about why inheritance is bad.


Inheritance is only considered an anti-pattern by some.

Since the 90's, any good CS book about OOP paradigms had discussions about is-a and has-a and how to make the best use of each, depending on the desired application architecture.

The thing is, such books usually aren't taught on bootcamps.


Inheritance isn’t an anti-pattern in academia. I do part time work as an examiners for CS students, and I see their car/animal examples everywhere in introductionary courses.

When we have interns, they’ll sometimes build things with inheritance. So it’s certainly still a thing.

I’ve yet to see a real world use of it, where you wouldn’t have been better of not using it though. My real world is the relatively boring world of enterprise public sector software, however, and maybe I’m simply oblivious to where inheritance might be worthwhile.


That is the thing, the architect should have a proper understanding of is-a and has-a relations and apply them appropriately.

Initially, VBX only allowed for composition as well, COM introduced interface inheritance with delegation, when one wanted to override a couple of methods, but not the remaining several ones.

And now UWP offers mechanisms to do implementation inheritance in COM, because everyone got tired to write delegating code for is-a relations.

Inheritance and composition are both tools, it is up to each one to learn how to use them appropriately.


Inheritance is an anti-pattern for some. Not for others (eg me).


As with everything it depends how its used.


Sounds like you would find the Trait system in Rust very interesting.

https://play.rust-lang.org/?version=stable&mode=debug&editio...


Traits are okay in principle but, as a Rust beginner, the huge number of trait implementations make browsing the api docs overwhelming. For example, the list of implementations for String don't even fit on the screen at once. [1] It seems very repetitive and I wish there were an easier way to get an overview of what functionality is available and find the right method to call.

[1] https://doc.rust-lang.org/std/string/struct.String.html


It's not a great solution, but I've found it beneficial to lean heavily on the Rust docs search bar when looking for specific behavior. It's very fast, and fuzzy enough that it finds what I'm looking for most of the time.


I'm also a c# dev but with less experience. Could you elaborate? Does the mean you copy paste shared code to all of the derived classes? I think I know why you say it's a maintenance nightmare because you start off with a method or properties that make sense in all of the derived classes but overtime the classes become less cohearant and start to develope warts and it would have been easier to modify the duplicated code.


It’s more that there are other ways to reuse code. Search for “composition over inheritance” in your favourite search engine for more information.


I think you formulate it nicely. Sometimes the things it made sense to share stop making sense.

Something that means rework, sometimes you end up with a parent method which is overridden in every child, possibly because each override was spread out over a long time period and no one bothered to look outside the child.

Inheritance makes sense with interfaces in C# but for the rest I think composition is just a better way of sharing.


Watch out for hype that golang authors and supporters claim without properly backing it up, there's quite a bit of it.

There are other languages that have better support for composition, while still maintaining support for inheritance (e.g. Kotlin). Inheritance has its uses, as evident by the fact that Rust is considering adding support for inheritance (e.g. writing GUIs).


I’m not sure what you mean by “rust is considering adding support for inheritance”; as far as I know that’s not true. Did I forget something? Do you have a link to the proposal?


I came across it a while ago, I think it was mentioned by pcwalton or someone if I'm not mistaken, specifically citing writing GUI code as a rationale. That being said, Rust (and Java, Kotlin, Scala, C#, etc) have default interface method implementations which might alleviate some of the need for full blown inheritance, unlike golang's interfaces.


Yes indeed. Porting code is also a great way to learn a language. And you learn a lot about idioms and patterns on both sides. Go innovated quite a bit in terms of simplicity. However, they also did a few things that they are now trying to fix like having generics and exceptions. Lack of overloading is something that I miss in other languages as well. E.g. in Javascript you often end up with these type checks to sort of fake things. These often get added defensively because the lack of typing just causes people to call things with the wrong types and it is common for that kind of code to not be noticed (fixed a few such issues when introducing typescript recently).

A lot of stuff in Java is just because (by now) it has a lot of history and baggage. JavaBeans were kind of cool back in the late nineties but mostly it's a really annoying convention by now. The notion of accessors is not obsolete but unfortunately was not part of the language design when they created Java. Since Java has reflection, they came up with naming conventions that allow code to inspect object instances and do things with properties based on naming conventions. This has been key to a lot of the Java enterprise stuff that happened after this. And it also facilitated creating UI builders that came with IDEs like jbuilder, netbeans, visual cafe, etc. when having a UI builder was still a base requirement for an IDE. Eclipse sort of broke that tradition by not including one (initially).

These days UI builders are basically very uncommon; which makes javascript frontend work particularly tedious and repetitive. Because there are no conventions, no typing, and quite often no meaningful test coverage in JS, it is basically impossible to deal with that in a UI tool. So, accessors matter and are not obsolete at all.

Most modern languages have more elegant ways to do essentially the same type of accessor mechanisms in the language. And also languages like Smalltalk had this (as well as UI building tools, refactoring, and a lot of other stuff that is still science fiction in the javascript world).

If you look at Kotlin, they fixed this while retaining the ability to expose code back to Java.

For example kotlin has properties with accessors that you can optionally override. Normally you just type val foo="bar" and you have a string property with an inferred type of String. The setters/getters generated under the hood and used automatically when you assign or use the variable. If you want you can customise the accessors or use something called delegated properties that e.g. turn a property getter into a function or use lazy intialization. Once compiled, a java class that uses that code would see the normal setters and getters as if it was a normal Java class. Likewise when accessing Java code from Kotlin you use java properties as if they were normal kotlin properties (i.e. without using setFoo(foo)/getFoo()). This makes Kotlin a really nice way of using legacy Java code.


I’ve been diving deep into Kotlin lately and I am now convinced that Kotlin is the next evolution for Java codebases and the JVM. It fixes a lot of the quirks in Java with great interop support so we can still use all the jvm/Java knowledge,libraries,performance in a more modern, more ergonomic and safer language.


I agree. I wonder how well the transpiler in IntelliJ works. I'm using Kotlin in my spare time a bit in a maven sub-module of a project, but used it from scratch (because of the great interoperability with Existing Java Code). I'd love to switch to it in other modules as well (still Java code), but wonder if it generates idiomatic Kotlin code out of the box, but maybe not. That said I'm a Kotlin beginner, but it seems everything from Effective Java is so much easier with Kotlin and the defaults are simply better.


It’s not bad. It picks up some common javaisms and converts those to the more idiomatic Kotlin version.

The cool thing is that you can convert to Kotlin on a file by file basis.


Yes, the converter gets you eighty percent of the way there. Usually it is a bit too conservative and e.g. biased towards making all types nullable because it is hard to reason about nullability in Java. In the same way it attempts to turn any method starting with the prefix get into a property, which does not always make sense when it is clearly a bit of business logic instead of a property. Finally, it seems to slab a lot of redundant generic type hints on things, which don't always work. As soon as you remove them, Kotlin's type inference usually gets it. So I typically spent a minute cleaning up after converting a file.

After that you get to the idiomatic stuff like e.g. making properties read only getting rid of multiple constructors by introducing default parameters. Getting rid of the builder pattern (mostly redundant in kotlin), introducing data classes where that makes sense, using lateinit vars to make nullable vals nonnullable, etc. Technically you are at that point improving things.


They're not going to add exceptions.


if the verbosity of javabeans bothers you, you can just use the lombok compiler extension to get similar concepts to what Go provides.


Lombok is a syntactic sugar for bad design decisions: there should not exist operation like setName - either there’s additional semantics deserving a method with a descriptive name, or the class is just a structure with writable state. I also agree with the comment about unnecessary magic.


ugh, don't use lombok. it's terrible in so many ways. Either switch to kotlin or use the AutoValue or Immutables libraries.


can you iterate the terribleness?


Lombok brings huge amounts of unnecessary magic to the codebase.

A new coder diving in to the code will need to either know all the Lombok annotations by heart or look up what every single one of them does.

Also it makes debugging kinda crappy for the same reason.


huge? pretty trivial.. the point of a javabean is you don't need to understand anything. As for annotations, it's not like developers don't need to know a crapton of other annotations as well. It's just the way it is, so a few more aren't a big deal. If you need to debug java beans, you are doing something wrong.

you can also delombok if you ever get tired of it, and now your back to manual.

I personally don't use lombok, as i'm not offended by the verbosity, given that ides since forever have done all the work, but if it's something you are bothered by, well. that.

If an argument is that javabeans are the wrong pattern to use, that's a different argument, and unrelated to lombok.




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

Search: