The germans tried that in the 60s see https://en.wikipedia.org/wiki/Otto_Hahn_(ship).
It was uneconomical.
You need specialized engineers for that, you need special permissions for ports and the important canals are off limits due to risk.
We introduced MDM for our Mac boxes early this year. Over half(!) had outdated mac versions and missed multiple major updates. Before that - it was always really obvious that you needed to run the newest version ASAP (asap=All dev tools run on the newest version, which was not a given, so a few weeks delay was ok).
We have lots of linux boxes and I suspect their patch state is even worse - but how to check that? There are a dozen distros and a few self build systems...
Do those MDM solutions look into the Linux VMs? Because once I get one of those Rube Goldberg machine working-ish, I'm naturally going to do my best to never touch it/never update anything. Native Linux tends to Just Work and has easy rollbacks, so it's fine to update.
That was our experience, too. Sales people never update. They just don’t.
One day I asked our CFO something, and watched him log into his laptop with like 4 keypresses. And that’s how we got more complex password requirements deployed everywhere.
Having spent a few years as a CISO, I’m now understand much more about why we have all those pain in the neck controls. There’s a saying about OSHA regulations that each rule is written in blood. I don’t know what the SOC2 version of that is, but there should be one.
Yes, halfway decent security runs counter to most people's inclinations. Like osha or medecine rules. So enforcement is important, though it is annoying
I've gotten a lot of mileage out of explaining why we're enforcing controls. "OK, as an engineer, I'm not fond of this either, but here's why it's important..." goes a long way.
History shows that to simply not the case.
Individuals are bringing down democracies all the time. Especially presidential democracies are super vulnerable to this because the president has outsized power compared to the other branches of the government.
I studied political sciences twenty years ago - even then it was established consensus that presidential democracies are vulnerable to authoritarian takeover. The position has too much power, is easily abused and there are not enough checks on that position. The US escaped that problem for a long time due to strong cultural norms - but you abolished them (i.e. gatekeeping the presidential nominees and replacing that with a televised drama) and working checks (but again, now party in congress and president march in lockstep).
FPTP and gerrymandering just exacerbate that problem and entrench a very unhealthy "the winner takes it all without need for compromise" culture.
You need electoral reform post haste - but I do not seed even a start to that discussion, so I think you are hosed. Might not be Dictator Trump, but maybe Vance or some other guy who succeeds in this game.
And all who cry "if the democrats win everything will be ok again!!!!" - not it won't. The democrats are too slow to recognize the problem and even if they eventually do, there are no majorities to change the system. And finally: Democracy needs at least two parties - democrats cannot be expected to keep branches of the government forever. You need a sane and democratic second party. Republicans ain't it - but the current system gives them success, so why change?!
> I studied political sciences twenty years ago - even then it was established consensus that presidential democracies are vulnerable to authoritarian takeover.
Democracies are vulnerable to "authoritarian takeover" has been known and understood for 2500 years.
> The position has too much power, is easily abused and there are not enough checks on that position.
In most parliamentary democracies, the Prime Minister is much more powerful than the US President. This is particularly the case since the PM is PM by virtue of his party having the legislative majority.
> And all who cry "if the democrats win everything will be ok again!!!!" - not it won't.
A better argument would be that this isn't a partisan issue. The last President declared a Constitutional Amendment by fiat and attempted to do (good) things like student loan relief with blatantly illegal authoritarian methods due to the perpetual Congressional gridlock.
> In most parliamentary democracies, the Prime Minister is much more powerful than the US President. This is particularly the case since the PM is PM by virtue of his party having the legislative majority.
This is a grave misunderstanding. A legislative majority isn't a static historical fact like Trump's electoral majority, it's dynamic - those are identifiable people not just a statistic.
Liz Truss was the UK's Prime Minister for less than two months. What changed in two months? Probably most of the idiots who actually voted for her didn't change their minds, but that doesn't matter, her fellow Tory MPs feared the worst from the outset and were proven correct. If she hadn't left she'd have been kicked out, she's known to have actually asked if there's some way she can cling on and been told basically "No" because there isn't.
Ultimately, if they can't get rid of her any other way, her backbench only needs to affirm a simple motion, "That This House Has No Confidence In His Majesty's Government" and it's all over. It would never come to that, but that's the backstop.
Congress can also agree to remove the President. Indeed it would take only a few Rs to flip to do so.
We see PMs easily enacting massive legislative reforms and even Constitutional changes that are nigh impossible in the US, that was not a particularly controversial statement.
> We see PMs easily enacting massive legislative reforms and even Constitutional changes that are nigh impossible in the US
I'm responding to this part separately because it's a very different issue. The existence of "superior law" in the form of a written constitution, is very silly. There need be only a single law, the law of the land - and the legislature must be able to change it - and only them, otherwise why have a legislature at all?
These are only man's laws, they're no different than the laws of Football ("soccer") for example, they are not facts about the world like Mother Nature's Laws - and so to hold some of these laws superior to others is a waste of everybody's time. The resulting paralysis in the US is not something to be praised, it's just another rusted joint, a lost degree of flexibility and so a point of weakness.
In reality, the supposed "impossible" constitutional changes in the US simply enable learned helplessness. Democratic representatives weep that alas much as they wish otherwise they "cannot" fix obvious problems because change is "impossible" and then of course somebody who actually does want to change things just does and says (as we might expect remembering these are only man's laws) if you don't like it too fucking bad.
> There need be only a single law, the law of the land - and the legislature must be able to change it - and only them, otherwise why have a legislature at all?
The legislature can change the US Constitution. The federal Congress proposes an amendment with a 2/3rds yes-vote, then it must be ratified by legislatures of 3/4ths of the states.
The reason to make some laws harder to change than others is to protect civil rights. In the US, it is very difficult to legally infringe on the right to free speech, for example. In the UK, it is simply a matter of a majority vote in Parliament.
> Democratic representatives weep that alas much as they wish otherwise they "cannot" fix obvious problems because change is "impossible" and then of course somebody who actually does want to change things just does and says (as we might expect remembering these are only man's laws) if you don't like it too fucking bad.
Passing Constitutional amendments is perfectly feasible and has been done many times. It just can't be done without majority political support and the will to do so. They've been passed and "repealed" before, with Prohibition, for example.
A lot of kvetching in the US system (on both sides) comes from people whose ideas are simply not very popular and would like to change the rules so they win. In a democratic society, you need majorities of the population to agree. For larger changes, you preferable want larger majorities.
>We see PMs easily enacting massive legislative reforms and even Constitutional changes that are nigh impossible in the US
I don't know about UK but in Australia we need a Referendum (https://en.wikipedia.org/wiki/Referendums_in_Australia) to change the constitution and those have been historically extremely difficult to pass (only 8/45). The PM absolutely cannot alter the Constitution.
Yeah, it’s dangerous to generalize parliamentary systems too broadly. That isn’t the case in all of them. But as you can see in his comments, he thinks that having “constitutional” laws above other laws is also a bad idea.
Congress could, in theory, begin an arduous process (weeks? months?) in which eventually, if they succeed, again in theory it removes the President and... puts in his place his chosen replacement. It has never successfully done this, so from there we're in uncharted waters but it's hardly obvious that it is an effective procedure.
In contrast the Westminster Parliament routinely disposes of Prime Ministers who lose its confidence, it's already happened once in my lifetime and it's not some multi-week procedure in which there's some performance of a judicial process, just a simple question: Does this Government retain the Confidence of the House?
Margaret Thatcher decided on this course of action on a Monday, on Wednesday morning she rose to say, "Mr. Speaker, I beg to move, 'That this House has no confidence in Her Majesty's Government.'" and by the next morning the Callaghan minority government had fallen.
The length of time the process takes is entirely under the control of Congress. It could be done in a day if they wanted. The longer time periods seen with Clinton and Trump were to attempt to gin up the political support to follow through.
I was concerned with facts, whereas you seem focused on a fantasy about how you wish things were. But your fantasy doesn't matter at all. US-style Presidential Republics are a known bad design, the US nation building projects stopped doing this themselves because it doesn't work, the United States itself is just a slower decay, it's not an exception.
The problem wasn't the Crown, that's the big takeaway. Giving the same power to a guy who doesn't have a hat doesn't fix the problem. You need to hold this much power in commission, that's the lesson that gave us the present British arrangement - the Lord High Treasurer was much too powerful, so his power was given to a commission, today its First Lord though not nearly as powerful as the Lord Treasurer, is too powerful, that's the Prime Minister you gestured at - the formal office is "First Lord of the Treasury", with the Chancellor being Second Lord, and the whips taking subsidiary parts of the commission. If you ask me we should further re-divide this power.
But just giving all that power to one man (and in the US it has always been a man) is even worse. The US President has powers that a King had, which made sense in the 18th century but stands out today - that's why Trump can corruptly pardon people for example.
It's really baffling to see this take repeated, especially when we've seen European PMs rewrite their country's constitution. That's just not feasible in the US system. US Presidents are quite limited in their power. A lot of (justified) outrage occurs over the US President doing something that PMs can typically do with no issue.
You seem fixated on the practical process of removing one from power, which is of course irrelevant as long as their party backs them, which is the actual threat in both cases. In either case, if the legislature does not back them, they can be removed from power with little issue.
I see in a sibling comment you think this is actually a weakness of the US system...apparently the PM radically changing all the laws, norms, and unwritten constitution of his country is "not powerful", while the US President typically fighting a battle to get one single major piece of legislation through in his career is unitarian dictatorship?
> , the US nation building projects stopped doing this themselves because it doesn't work, the United States itself is just a slower decay, it's not an exception.
The US nation building projects felt that parliamentary democracies were easier to control, as direct election of Presidential executives sometimes leads to democracies electing leaders who are able to carry out policies that violate US interests.
We escaped them because the tenth amendment and judiciary constrained federal powers in non war time to activity summing up to like 2% of the GDP and they needed an amendment to do anything outside of a little box. POTUS was fairly low stakes office in peace time, lower stakes to most than their governor and state legislators.
We tossed that all aside in the 1930s via threatening to pack the Supreme court. Federal powers are now everything because interstate commerce is now everything and without a functional 10A and with delegation to executive agencies POTUS approaches God level.
Ravendb is trash. It can’t handle even 1/10 of this type of a load, it was trashed in jepsen testing too. I had to work with it 4 years and i disliked it.
Hey, do you think Gitlab should do anything except running after the next trend and develop shitty not-solutions for that?
Why, that could improve Gitlab. We cannot have that!
As a simple example : once upon a time a
We needed to generate a sort of heat map.
Doing it in pure python takes a few seconds at the desired size (few thousand cells where each cell needs a small formula). Dropping to numpy braucht that downs to hundreds of milliseconds. Pushing it to pure c got us to tens of milliseconds.
Yeah one of other beauties of numpy is you can pass data to/from native shared libraries compiled from C code with little overhead. This was more klidgy in Matlab last I checked
I don't think Inheritance is always bad - sometimes it's a useful tool. But it was definitely overused and composition, interfaces work much better for most problems.
Inheritance really shines when you want to encapsulate behaviour behind a common interface and also provide a standard implementation.
I.e: I once wrote a RN app which talked to ~10 vacuum robots. All of these robots behaved mostly the same, but each was different in a unique way.
E.g. 9 robots returned to station when the command "STOP" was send, one would just stop in place. Or some robots would rotate 90 degrees when a "LEFT" command was send, others only 30 degrees.
We wrote a base class which exposed all needed commands and each robot had an inherited class which overwrote the parts which needed adjustment (e.g. sending left three times so it's also 90 degrees or send "MOVE TO STATION" instead of "STOP").
> I don't think Inheritance is always bad - sometimes it's a useful tool.
I can only think of one or two instances where I've really been convinced that inheritance is the right tool. The only one that springs to mind is a View hierarchy in UI libraries. But even then, I notice React (& friends) have all moved away from this approach. Modern web development usually makes components be functions. (And yes, javascript supports many kinds of inheritance. Early versions of react even used them for components. But it proved to be a worse approach.)
I've been writing a lot of rust lately. Rust doesn't support inheritance, but it wouldn't be needed in your example. In rust, you'd implement that by having a trait with functions (+default behaviour). Then have each robot type implement the trait. Eg:
trait Robot {
fn stop(&mut self) { /* default behaviour */ }
}
struct BenderRobot;
impl Robot for BenderRobot {
// If this is missing, we default to Robot::stop above.
fn stop(&mut self) { /* custom behaviour */ }
}
> The only one that springs to mind is a View hierarchy in UI libraries.
I'd like to generalize that a little bit and say: graph structures in general. A view hierarchy is essentially a tree, where each node has a bunch of common bits (tree logic) and a bunch of custom bits (the actual view). There are tons of "graph structures" that fit that general pattern: for instance, if you have some sort of data pipeline DAG where data comes in on the left, goes out on the right, and in the middle has to pass through a bunch of transformations that are linked in some kind of DAG. Inheritance is great for this: you just have your nodes inherit from some kind of abstract "Node" class that handles the connection and data flow, and you can implement your complex custom behaviors however you want and makes it very easy to make new ones.
I'm very much in agreement that OOP inheritance has been horrendously overused in the 90s and 00s (especially in enterprise), but for some stuff, the model works really well. And works much better than e.g. sum types or composition or whatever for these kinds of things. Use the right tool for the right job, that's the central point. Nothing is one-size-fits-all.
> But what do those functions return? Oh look, it's DOM nodes, which are described by and implemented with inheritance.
Well of course. React builds on what the browser provides. And the DOM has been defined as a class hierarchy since forever. But react components don’t inherit from one another. If the react devs could reinvent the DOM, I think it would look very different than it looks today.
This is starting to look a lot like C++ class inheritance. Especially because traits can also inherit from one another. However, there are two important differences: First, traits don't define any fields. And second, BenderRobot is free to implement lots of other traits if it wants, too.
If you want a real world example of this, take a look at std::io::Write[1]. The write trait requires implementors to define 2 methods (write(data) and flush()). It then has default implementations of a bunch more methods, using write and flush. For example, write_all(). Implementers can use the default implementations, or override them as needed.
How does one handle cases where fields are useful? For example, imagine you have a functionality to go fetch a value and then cache it so that future calls to get that functionality are not required (resource heavy, etc).
// in Java because it's easier for me
public interface hasMetadata {
Metadata getMetadata() {
// this doesn't work because interfaces don't have fields
if (this.cachedMetadata == null) {
this.cachedMetadata = generateMetadata();
}
return this.cachedMetadata;
}
// relies on implementing class to provide
Metadata fetchMetadata();
}
But then you have the getters, setters, and field on every class that implements the functionality. It works, sure, it just feels off to me. This is code that will be the same everywhere, and you're pulling it out of the common class and implementing it everywhere.
But if there's a lot of classes that implement the same thing, then not duplicating code makes sense. And saying "it's an implementation detail" leads to having the same code in a bunch of different classes. It feels very similar to the idea of default implementations to me; when the implementation will be the same everywhere, it makes sense to have it in one place.
So to be clear about your example: You have a whole lot of different - totally distinct - types of things, which all need to have the same logic to cache HTTP requests? Can you give some examples of these different types you're creating? Why do you have lots of distinct types that need exactly the same caching logic?
It sounds like you could solve that problem in a lot of different ways. For example, you could make an HTTP client wrapper which internally cached responses. Or make a LazyResource struct which does the caching - and use that in all those different types you're making. Or make a generic struct which has the caching logic. The type parameter names the special individual behaviour. Or something else - I don't have enough information to know how I'd approach your problem.
Can you describe a more detailed example of the problem you're imagining? As it is, your requirements sound random and kind of arbitrary.
From a very modified version of something I was working on recently, but with the stuff I couldn't do actually done here (and non-functionality code because of that, but is shows the idea)
public interface MetadataSource {
Metadata metadata = null;
default Metadata getMetadata() {
if (metadata == null) {
metadata = fetchMetadata();
}
return metadata;
}
// This can be relatively costly
Metadata fetchMetadata();
}
public class Image implements MetadataSource {
public Metadata fetchMetadata() {
// goes to externally hosted image to fetch metadata
}
}
public class Video implements MetadataSource {
public Metadata fetchMetadata() {
// goes to video hosting service to get metadata
}
}
public class Document implements MetadataSource {
public Metadata fetchMetadata() {
// goes to database to fetch metadata
}
}
Each of the above have completely different ways to fetch their metadata (ex, Title and Creator), and of them has different characteristics related to the cost of getting that data. So, by default, we want the interface to cache the result so that the
1. The thing that _has_ the metadata only needs to know how to fetch it when it's asked for (implementation of fetchMetadata), and it doesn't need to worry about the cost of doing so (within limits of course)
2. The things that _use_ the metadata only need to know how to ask for it (getMetadata) and can assume it has minimal cost.
3. Neither one of those needs to know anything about it being cached.
I had a case recently where I needed to check "does this have metadata available" separate from "what is the metadata". And fetching it twice would add load.
Here's my take on implementing this in rust. I made a trait for fetching metadata, that can be implemented by Image, Video, Document, etc:
trait MetadataSource {
fn fetch_metadata(&self) -> Metadata;
}
impl MetadataSource for Image { ... }
impl MetadataSource for Video { ... }
impl MetadataSource for Document { ... }
And a separate object which stores an image / video / document alongside its cached metadata:
struct ThingWithMetadata<T> {
obj: T, // Assuming you need to store this too?
metadata: Option<Metadata>
}
impl<T: MetadataSource> ThingWithMetadata {
fn get_metadata(&self) -> &Metadata {
if self.metadata.is_none() {
self.metadata = Some(self.obj.fetch_metadata());
}
self.metadata.as_ref().unwrap()
}
}
Its not the most beautiful thing in the world, but it works. And it'd be easy enough to add more methods, behaviour and state to those metadata sources if you want. (Eg if you want Image to actually load / store an image or something.)
In this case, it might be even simpler if you made Image / Video / Document into an enum. Then fetch_metadata could be a regular function with a match expression (switch statement).
If you want to be tricky, you could even make struct ThingWithMetadata also implement MetadataSource. If you do that, you can mix and match cached and uncached metadata sources without the consumer needing to know the difference.
Isn't this essentially the generic typestate pattern in Rust? In my view there is a pretty obvious connection between that particular pattern and how other languages implement OO inheritance, though in all fairness I don't think that connection is generally acknowledged.
(For one thing, it's quite obvious to see that the pattern itself is rather anti-modular, and the ways generic typestate is used are also quite divergent from the usual style of inheritance-heavy OO design.)
In this example, ThingWithMetadata does the caching. image.fetch_metadata fetches the image and returns it. It’s up to the caller (in ThingWithMetadata) to cache the returned value.
But part of the goal is to not need the caller to cache it. Nor have the class that knows how to fetch it need to know how to cache it either. The responsibility of knowing how to cache the value is (desired to be) in the MetadataSource interface.
The rule is that you can't cache a value in an interface, because interfaces don't store data. You need to cache a value in a struct somewhere. This implementation wraps items (like images) in another struct which stores the image, and also caches the metadata. Thats the point of ThingWithMetadata. Maybe it should instead be called WithCachedMetadata. Eg, WithCachedMetadata<Image>.
You can pass WithCachedMetadata around, and consumers don't need to understand any of the implementation details. They just ask for the metadata and it'll fetch it lazily. But it is definitely more awkward than inheritance, because the image struct is wrapped.
As I said, there's other ways to approach it - but I suspect in this case, using inheritance as a stand-in for a class extension / mixin is probably going to always be your most favorite option. A better approach might be for each item to simply know the URL to their metadata. And then get your net code to handle caching on behalf of the whole program.
It sounds like you really want to use mixins for this - and you're proposing inheritance as a way to do it. The part of me which knows ruby, obj-c and swift agrees with you. I like this weird hacky use of inheritance to actually do class mixins / extensions.
The javascript / typescript programmer in me would do it using closures instead:
> The rule is that you can't cache a value in an interface, because interfaces don't store data.
Right, but the start of where I jumped into this thread was about the fact that there are places where fields would make things better (specifically in relation to traits, but interfaces, too). And then proceeding to discuss a specific use case for that.
> A better approach might be for each item to simply know the URL to their metadata.
Not everything is a coming from a url and, even when it is, it's not always a GET/REST fetch.
> but I suspect in this case, using inheritance as a stand-in for a class extension / mixin is probably going to always be your most favorite option
Honestly, I'd like to see Java implement something like a mixin that allows adding functionality to a class, so the class can say "I am a type of HasAuthor" and everything else just happens automatically.
I don't see how that solves the problem. It seems like Video will need to keep it's own copy of CachedMetadaSource, which points back to itself, and go through that access it's metadata in the getMetadata implementation it makes available to it's users. At that point, it might as well just cache the value itself without the extra hoops. The difficult part isn't caching the value, it's preventing every class that implements MetadataSource from having to do so.
It would be the other way around. You wouldn't pass around the underlying suppliers directly, you'd wrap them. But if you must have state _and_ behavior, then `abstract class` is your friend in Java (while in Scala traits can have fields and constructors, so there is no problem).
The commenter used inheritance and thought it was fine. Probably not necessary to re-write in Rust just to be able to say that it doesn't use inheritance while being functionally the same thing.
> And yes, javascript supports many kinds of inheritance
Funny you mention it, since JavaScript has absolutely no concept of contracts, which is one of the most important side-effects of inheritance. Especially not at compile time, but even at runtime you can compose objects willy-nilly, pass them anywhere, and the only way to test if they adhere to some kind of trait is calling a method and hoping for the best.
At least that had been the case till ES6 came around, but good luck finding anyone actually using classes in JavaScript. Mainly because it adds near-zero benefits, basically just the ability to overwrite method behavior without too much trickery.
Inheritance is not the only way to share behavior across different implementations — it'a just the only way available in the traditional 1990s crop of static OOP languages like C++, Java and C#.
There are many other ways to share an implementation of a common feature:
1. Another comment already mentioned default method implementations in an interface (or a trait, since the example was in Rust). This technique is even available in Java (since Java 8), so it's as mainstream as it gets.
The main disadvantage is that you can have just one default implementation for the stop() method. With inheritance you could use hierarchies to create multiple shared implementations and choose which one your object should adopt by inheriting from it. You also cannot associate any member fields with the implementation. On the bright side, this technique still avoids all the issues with hierarchies and single and multiple inheritance.
2. Another technique is implementation delegation. This is basically just like using composition and manually forwarding all methods to the embedded implementer object, but the language has syntax sugar that does that for you. Kotlin is probably the most well-known language that supports this feature[1]. Object Pascal (at least in Delphi and Free Pascal) supports this feature as well[2].
This method is slightly more verbose than inheritance (you need to define a member and initialize it). But unlike inheritance, it doesn't requires forwarding the class's constructors, so in many cases you might even end up with less boilerplate than using inheritance (e.g. if you have multiple overloaded constructors you need to forward).
The only real disadvantage of this method is that you need to be careful with hierarchies. For instance, if you have a Storage interface (with the load() and store() methods) you can create EncryptedStorage interface that wraps another Storage implementation and delegates to it, but not before encrypting everything it sends to the storage (and decrypting the content on load() calls). You can also create a LimitedStorage wrapper than enforces size quotas, and then combine both LimitedStorage and EncryptedStorage. Unlike traditional class hierarchies (where you'd have to implement LimitedStorage, EncryptedStorage and LimitedEncryptedStorage), you've got a lot more flexibility: you don't have to reimplement every combination of storage and you can combine storages dynamically and freely. But let's assume you want to create ParanoidStorage, which stores two copies of every object, just to be safe. The easiest way to do that is to make ParanoidStorage.store() calls wrapped.store() twice. The thing you have to keep in mind, is that this doesn't work like inheritance: For instance, if you wrap your objects in the order EncryptedStorage(ParanoidStorage(LimitedStorage(mainStorage))), ParanoidStorage will call LimitedStorage.store(). This is unlike the inheritance chain EncryptedStorage <- ParanoidStorage <- LimitedStorage <- BaseStorage, where ParanoidStorage.store() will call EncryptedStorage.store(). In our case this is a good thing (we can avoid a stack overflow), but it's important to keep this difference in mind.
3. Dynamic languages almost always have at least one mechanism that you can use to automatically implement delegation. For instance, Python developers can use metaclasses or __getattr__[3] while Ruby developers can use method_missing or Forwaradable[4].
4. Some languages (most famously Ruby[5]) have the concept of mixins, which let you include code from other classes (or modules in Ruby) inside your classes without inheritance. Mixins are also supported in D (mixin templates). PHP has traits.
5. Rust supports (and actively promotes) implementing traits using procedural macros, especially derive macros[6]. This is by far the most complex but also the most powerful approach. You can use it to create a simple solution for generic delegation[7], but you can go far beyond that. Using derive macros to automatically implement traits like Debug, Eq, Ord is something you can find in every codebase, and some of the most popular crates like serde, clap and thiserror rely on heavily on derive.
To my mind, the challenge is not "sharing behavior"; it is "sharing behavior in a way that capture human-understandable semantics and make code easier to reason about instead of harder."
I suspect part of the problem of inheritance is that it is a way to share behavior that some humans, especially visual thinkers who understand VMTs, find easy to reason about.
In my experience verbal thinkers struggle with inheritance, because it requires jumping between levels of abstraction and they aren't thinking in terms of semantic units. I have found that books like Refactoring can help bridge the gap, but we have to identify it as a gap to be bridged and people have to want to learn this new skill.
And then on the flip side you have people who try to use it just as a way to de-dupe code, even when it doesn't reflect a meaningful semantic unit.
> In my experience verbal thinkers struggle with inheritance, because it requires jumping between levels of abstraction and they aren't thinking in terms of semantic units.
This is too dismissive of the criticism. The problem with inheritance is it makes control flow harder to understand and it spreads your logic all over a bunch of classes. Ironically, inheritance violates encapsulation - since a base class is usually no longer self contained. Implementation details bleed into derived classes.
The problem isn’t “verbal thinkers”. I can think in OO just fine. I’ve worked in 1M+ line of code Java projects, and submitted code to chrome - which last time I checked is a 30M loc C++ project. My problem with OO is that thinking about where any given bit of code is distracts me from what the code is trying to do. That makes me less productive. And I’ve seen that same problem affect lots of very smart devs, who get distracted building a taxonomy in code instead of solving actual problems.
It’s not a skills problem. Programming is theory building. OO seduces you into thinking the best theory for your software is a bunch of classes which inherit from each other, and which reference each other in some tangled web of dependencies. With enough effort, you can make it work. But it almost always takes more effort than straightforward dataflow style programming to model the same thing.
I do not believe "it makes the control flow harder to understand" is as universal as you claim.
If used badly any flow tool (including if-statements) can be be confusing. But "it can be complicated" doesn't mean we shouldn't use the tool when it is appropriate. One of the reasons I like Java Enums is because they provide much more structured guidance on what communicative inheritance looks like.
But we may also disagree on what "productive" means in the context of writing software.
The "taxonomy of code" you are dismissing is I believe what Fred Brooks describes as the "essential tasks" of programming: "fashioning of the complex conceptual structures that compose the abstract software entity".
It's not that I don't sympathize with your concern: being explicit and clear about "what the code is trying to do" is why TDD is popular among OOP programmers. But the step after "green" is "refactor", where the programmer stops focusing on what the code is trying to do and refines the taxonomy of the system that implements those tasks.
To me (as a Java programmer) inheritance is very useful to reuse code and avoid copy paste. There many cases in which decorators or template methods are very useful and in general I find it "natural" in the sense that the concepts of abstraction and specialization can be found in plenty of real world examples (animals, plants, vehicles etc etc).
As usual there is no silver bullet, so it's just a tool and like any other tool you need to use it wisely, when it makes sense.
As a full stack developer who's current job is mostly Java on the backend - at least for the last 8 yrs: I'm not aware of anything you would lose by switching to interfaces with default implementations over inheritance... And that's the usual argument: use composition over inheritance.
But would switching to interfaces with default implementations fix any of the complaints that people have about inheritance? In my mind, they're pretty much equivalent, so it seems to me that anything you can do with inheritance that people complain about, you could also do with interfaces and complain about it in the same way.
1. A class can be composed out of multiple interfaces, making them more like mixins/traits etc vs inheritance, which is always a singular class
2. The implementation is flat and you do not have a tree of inheritance - which was what this discussion was about. This obviously comes with the caveat that you don't combine them, which would effectively make it inheritance again.
Yeah there can be a ton of derivative and convenience methods that would either have to be duplicated in all implementations or even worse duplicated at call sites.
Call them interfaces with default implementations or super classes, they are the same thing and very useful.