Hacker News new | ask | show | jobs
by al2o3cr 1653 days ago
Re: using composition

    In the past, it was feared that this would lead to reduced
    performance but this is simply not the case.
Great to see the strong evidence here /s
3 comments

It might be more productive if you were to outright disagree with the statement instead of simply noting a claim that isn't strongly substantiated. If you have experiences that demonstrate to you that this claim is weak I'm sure everyone else would be interested in them (I know I would be!).
To add onto that, I tried several different hand rolled alternatives to a c++ vtable and the builtin functionality outperformed them all which was surprising. It’s entirely possible I didn’t know what I was doing but I think my point is that vtables are a language feature that compilers know how to reason about and apply optimizations to. Other concepts not so much.
Something to keep in mind when microbenchmarking virtual calls is that compilers will happily devirtualize them whenever possible.
Yeah I’m aware but I made sure the same devirtualization would apply in production too so the compiler doing any devirtualization was good. It was a bit surprising that vtables outperformed std::variant (and I had tried implementing my own hand rolled equivalent of that too).
"what may be asserted without evidence, may be dismissed without evidence"
Where is your evidence for that claim? Seems like I should dismiss it out of hand.
QED.
I mean, I'm not sure where this 'fear' even came from?

At its core, inheritence is a special case of composition anyways (looked at from the other perspective it's syntactic sugar over either static or dynamic delegation), so it can't really be "faster".

At any rate, there's no abstraction so powerful it can prevent a programmer from making it slow.

This is true for languages where most objects are not garbage-collected. In Java (modern days) this would add a level of indirection.
Which may be in certain cases trivially optimizable. Eg. an object available only inside a class can have its method calls inlined into the containing class’s methods.

(But I’m no JVM developer (unfortunately) so I’m not sure whether this exact optimization exists or not)

Language Design: Let's start with the admission that there's a lot of cargo culting in language design. Evidence is hard to come by and the best evidence is other languages that succeed with different choices. I remember the flame wars about how multiple inheritance would cause a language to fall apart. When will languages adopt the idea that encapsulation is not sacrosant, as the theoretical issue about the backdooring like _method in python or package level public in Go^ is, someone might abuse it? Howabout make testing a first class concept and lump it in there or just use the shortcuts that have been working.

What is inheritance? Inheritance (for any given language) is a language supported type of class composition (https://youtu.be/eEBOvqMfPoI?t=2874), as a closure. A class is a function and once you understand this, it opens up possibilities in how you design and test. This has nothing to do with performance, which is a nonsequitor. Is Rust less performant than Java because of how it does composition? No. Perhaps there's something in the JVM that makes mixins difficult to optimize for, but that would require some evidence (there's no general branch prediction in the JVM, last I checked) and is, ultimately, at the feet of the JVM implementation. Have a look at Go and Rust.

Naturally, because a specific kind of inheritance is a language feature, it gets overused and a language, (like Java) for backward compatibility's sake, overuses it in designing new features. Looking at other languages like Javascript, Lua, Erlang, PHP, Ruby, Rust, etc. saner heads have prevailed and even Java has resorted to using "Aspects" ...which are runtime traits for additional types of composition^^.

Regarding the rest of the article... His arguments for using final include: 1. Someone may forget to use super() and that's bad because what I want to happen trumps what they want to happen. 2. People can't subclass my class across package boundaries, because I don't know why handwaved JPMS (then covered in Should Inheritance Across Package Boundaries Ever be Used?). His reasoning is not compelling, in the least. I can say, without hesitation, that 'final' is harmful. Adding final to a class is such a violation of the concept of reusable software, I'm surprised the FSF doesn't boycott languages. In C++ there are performance benefits. In Java it's just to put up a roadblock. This was never a good idea and makes testing impossible in some cases (where final classes are injected into final classes). This is purely because of Java's design as a language, not because of some demonstrably helpful concept, implemented poorly^^.

^If you are a language designer, always allow backdooring of accessors for testing, at the very least. This conviction that they must always apply to protect developers from each other is misplaced and has hurt the reliability of software, badly.

^^Spring has a form tacked-on composition, which is both ugly from a conceptual standpoint and problematic from a testing standpoint. Java always seems 20+ years behind.

> Language Design: Let's start with the admission that there's a lot of cargo culting in language design. Evidence is hard to come by and the best evidence is other languages that succeed with different choices.

You are going to get a lot of false positives using success as a metric for good language design.

I cannot think of a single popular language, other than Python, in the last 40 years that did not have huge companies with sizable marketing budgets behind them.

Programming languages do not succeed based on merit. They may fail based on lack of it.

> Programming languages do not succeed based on merit. They may fail based on lack of it.

Merit is what I'm talking about, in a way. The ability to use more robust forms of composition is a positive quality for a language. So much so, even if not explicitly stated, most languages have adopted new mechanisms over time beyond simple rigid inheritance trees.

> Merit is what I'm talking about, in a way. The ability to use more robust forms of composition is a positive quality for a language.

Maybe so, but what I am saying is that merit cannot make a language popular, but marketing can.

People keep telling this about Python, always ignoring who was paying Guido's salary.
> People keep telling this about Python, always ignoring who was paying Guido's salary.

Well, reveal the spoilers and don't keep us all in suspense :-)

Who was paying GvR a salary[1] when Python took off circa 2001/2002?

[1] I don't think paying a single person a salary is the same as the millions of dollars poured into marketing and development as done with all the other popular languages.

Zope was, which was one of the best ways to do CMS back in those days.
Spring does (and perhaps even prefers where possible) constructor-based inheritance though — which is as pluggable as it gets, making testing easy.
> Spring does (and perhaps even prefers where possible) constructor-based inheritance though

OFC Spring does lots of things (too many things), but that varies project to project based on what series of Spring-related libraries are being used.

More likely I'll see non Spring-core annotations like @RestController + @RequestMapping-attributes and have to figure a standard way to mock up some of what Spring does just to assert the outputs. Perhaps there will be a series of full functional tests which requires a setup/teardown. Maybe the project just ignores endpoint tests and focus on the less Spring annotated business logic claiming "it's simple enough".

Spring has resulted in slow, complex, and incomplete (coverage) testing almost anywhere it's used because it's literally hiding functionality behind a runtime composition that you can't access casually by calling a routine.

It's problematic because it's complex and the versions

Testing REST-endpoints should be the job of integration tests, which are by definition more involved. Also, spring has really great test suits for these use cases.

Other classes/components should in the general case be written as POJOs. The dependent components can be mocked/injected simply by using the constructor.

> Testing REST-endpoints should be the job of integration tests, which are by definition more involved.

I would confidently say, this philosophy is dead wrong. Integration test are useful, but you still want the unit tests to ensure that the code paths and side effects are maintained. With runtime composition, this is much harder. Java sacrificed what we do know for a grand experiment of doing as much as possible in pre-processing, breaking (sacrificing) the known concept of code reliability in the hopes that someone else would figure out a way to handle the testing implications down the line. Java Testing went from a gold standard to an afterthought with annotations. This is how important getting additional composition turned out to be, but at what cost?

> spring has really great test suits for these use cases.

@SpringBootTest requires booting up Spring just to add in the runtime composition. It's both unnecessarily time consuming and problematic to have to predict composition rather than observing it directly. Now you have to memorize what Spring might do, given annotations that can be anywhere AND the code you are trying to test. Nightmare stuff.

How would you test a REST endpoint, if I may ask? Because in the end it will somehow reply to a request. But that response has quite a few things going on — if you give back the url of a templates string as a constant, is that meaningful to unit test that? For anything more complex you should be writing a service which can and should be unit tested. But I believe that the set of headers, security! and the like is not in the realm of the quite complex job of endpoints. By that you would be testing the Spring library, which presumably happens on spring’s side.