Do yourself a favor and wear yourself off all this SOLID, Uncle Bob, Object Oriented, Clean Code crap.
Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives.
Example: imagine you have a school system where there are student users and there are employee users, and some features like grading that should only be available for employees.
Instead of making Student and Employee inherit from User, just have a User class/record/object/whatever you want to call it that constitutes the account
data class User (id: Int, name: String, email: String)
and for those Users who are students, create a Student that points to the user
data class Student (userId: Id, blabla student specific attributes)
and vice versa for the Employees
data class Employee (userId: Id, blabla employee specific attributes)
then, your types can simply and strongly prevent Students from being sent into functions that are supposed to operate on Employees etc etc (but for those cases where you really want functions that operate on Users, just send in each of their Users! nothing's preventing you from that flexibility if that's what you want)
and for those users who really are both (after all, students can graduate and become employees, and employees can enroll to study), THE SAME USER can be BOTH a Student and an Employee! (this is one of the biggest footguns with inheritance: in the inheritance world, a Student can never be an Employee, even though that's just an accident of using inheritance and in the real world there's actually nothing that calls for that kind of artificial, hard segregation)
Once you see it you can't unsee it. The emperor has no clothes. Type-wise functional programmers have had solutions for all these made up problems for decades. That's why these days even sane Object Oriented language designers like Josh Bloch, Brian Goetz, the Kotlin devs etc are taking their languages in that direction.
Never using inheritance is pushing the dogma to the other extreme imo. The whole prefer composition over inheritance is meant to help people avoid the typical OO over use of inheritance. It doesn't mean Is-A relation doesn't/shouldn't exist. It just means when there is data, prefer using composition to give access to that data.
There will be times when you want to represent Is-A relationship - especially when you want to guarantee a specific interface/set of functions your object will have - irrespective of the under the hood details.
Notifier n = GetNotifier(backend_name);
Here what you care about is notifier providing a set of functions (sendNotification, updateNotification, removeNotification) - irrespective of what the implementation details are - whether you're using desktop notifications or SMS notifications.
I see no need for inheritance there, that can and should be done using interfaces
eg in the contrived example I gave, any of all three of the User, Student and Employee can implement the interface (and if needed Student could simply delegate to its internal User while Employee could "override" by providing its own, different implementation)
Inheriting from a class creates dispatch problems, and there's instance variables/fields/whatever to deal with. Never mind multiple inheritance, as that gets hairy real fast. With interfaces there is no hierarchy, and all there is is a type and a data, and you either pass them around together, you monomorphize to avoid the need to pass two pointers instead of one, or you have the data point to its type in a generic way. With class hierarchies you have to have operations to cast data up and down the hierarchy, meaning trade one dispatch table for another.
Interfaces manifest the object's responsibility. Functions accepting objects as parameters should work with interfaces, not type instances. That way the responsibilities and capabilities are clear to the user of the interface and the implementor.
As far as how the implementor may fulfill its interface obligation, it may use inheritance, if it truly has an IS-A or subtype relationship with the base object.
If you have a sufficiently statically typed language then the is-a concern goes away -- certainly in the example you gave, since the compiler/linker knows to look for a `GetNotifier()` that returns a `Notifier`. Now, you might still want to know whether the notifier you got satisfies other traits than just those of `Notifier`, but you do that by using a type that has those traits rather than `Notifier`, and now you have little need for `instanceof` or similar operators. (You still might want to know what kind of thing you got because you might care about semantics that are not expressed in the interface. For example you might care to know whether a logger is "fast" as in local or "slow" as in remote, as that might cause you to log less verbosely to avoid drops or blocking or whatever. But the need for this `instanceof` goes down dramatically if you have interfaces and traits.)
Never is a good starting point as a guide but of course there are cases where is-a makes sense too.
Composition generally is not good enough to model all cases without resulting to some AOP or meta programming also, but in that case the is-a or a base class would arguably be the simpler approach, at least it can be reasoned about and debugged, as opposed to some AOP/Meta spaghetti that can only probably be understood from docs
Just because you see OO languages starting to favor composition over inheritance does not mean inheritance has no place, and indeed, interfaces as a form of composition have existed in many bog-standard OO languages for decades.
Your example dosn't compute, at least in most languages, because derived objects would not have the same shape as one another, only the shape of the base class. I.e. functions expecting a User object would of course accept either an Employee or a Student (both subclasses of a User), but functions expecting a Student object or an Employee object would not accept the other object type just because they share a base class. Indeed, that's the whole point. And as another poster mentioned, you are introducing a burden by now having no way to determine whether a User is an Employee or a Student without having to pass additional information.
Listen, I'll be the first to admit that the oo paradigm went overboard with inheritance and object classification to the n-th degree by inventing ridiculous object hierarchies etc, but inheritance (even multiple inheritance) has a place- not just when reasoning about and organizing code, but for programmer ergonomics. And with the trend for composition to disallow data members (like traits in Rust), it can seriously limit the expressiveness of code.
Sometimes inheritance is better, and if used properly, there's nothing wrong with that. The alternative is that you wind up implementing the same 5-10 interfaces repeatedly for every different object you create.
It should never be all or nothing. Inheritance has its place. Composition has its place.
And if you squint just right they're two sides of the same coin. "Is A" vs "Can Do" or "Has".
> The alternative is that you wind up implementing the same 5-10 interfaces repeatedly for every different object you create.
if both Student and Employee need to implement those interfaces, it's probably User that should have and implement them, not Student and Employee (and if they truly do need to have an implement them, they can simply delegate to their internal User = "no override" or provide a unique implementation = "override") (let alone that unless I'm misremembering in Kotlin interfaces can have default implementations)
Inheritance has no place in production codebases—unless there is strict discipline, enforced by tooling, ensuring calls only go in one direction. This Liskov stuff has zero bearing.
You are conflating two completely separate things.
The Liskov principle basically defines when you want inheritance versus when you don't. If your classes don't respect the Liskov principle, then they must not use inheritance.
The problems from your story relate to the implementation of some classes that really needed inheritance. The spaghetti you allude to was not caused by inheritance itself, if was caused by people creating spaghetti. The fact that child classes were calling parent class methods and then the parent class would call child class methods and so on is symptomatic of code that does too much, and of people taking code reuse to the extreme.
I've seen the same things happen with procedural code - a bunch of functions calling each other with all sorts of control parameters, and people adding just one more bool and one more if, because an existing function already does most of what I want, it just needs to do it slightly different at the seventh step. And after a few rounds of this, you end up with a function that can technically do 32 different things to the same data, depending on the combination of flags you pass in. And everyone deeply suspects that only 7 of those things are really used, but they're still afraid to break any of the other 25 possible cases with a change.
People piggybacking on existing code and changing it just a little bit is the root of most spaghetti. Whether that happens through flags or unprincipled inheritance or a mass of callbacks, it will always happen if enough people are trying to do this and enough time passes.
I don't believe in free will. People (including me) are going to do the easiest thing possible given the constraints. I don't think that's bad in any way -- it's best to embrace it. In this view, the tools should optimize for right thing to do aligning with the easy thing.
So if you have two options, one better than the other, then the better way to do things should be easier than the worse way. With inheritance as traditionally implemented, the worse way is easier than the better way.
And this can be fixed! You just need to make sure all the calls go one way (making the worse outcome harder), and then very carefully consider and work through the consequences of that. Maybe this fix ends up being unworkable due to the downstream consequences, but has anyone tried?
> I've seen the same things happen with procedural code - a bunch of functions calling each other with all sorts of control parameters, and people adding just one more bool and one more if, because an existing function already does most of what I want, it just needs to do it slightly different at the seventh step. And after a few rounds of this, you end up with a function that can technically do 32 different things to the same data, depending on the combination of flags you pass in. And everyone deeply suspects that only 7 of those things are really used, but they're still afraid to break any of the other 25 possible cases with a change.
Completely agree, that's a really bad way to do polymorphism too. A great way to do this kind of closed-universe polymorphism is through full-fledged sum types.
> People piggybacking on existing code and changing it just a little bit is the root of most spaghetti. Whether that happens through flags or unprincipled inheritance or a mass of callbacks, it will always happen if enough people are trying to do this and enough time passes.
You're absolutely right. Where I go further is that I think it's possible to avoid this, via the carrot of making the better thing be easy to do, and rigorous discipline against doing worse things enforced by automation. I think Rust, for all its many imperfections, does a really good job at this.
edit: or, if not avoid it, at least stem the decline. This aligns perfectly with not believing in free will -- if it's all genetic and environmental luck all the way down, then the easiest point of leverage is to change the environment such that people are lucky more often than before.
Respectfully, I think you're throwing the baby out with the bathwater.
I read your story, and I can certainly empathize.
But just because someone has made a tangled web using inheritance doesn't mean inheritance itself is to blame. Show me someone doing something stupid with inheritance and I can demonstrate the same stupidity with composition.
I mean, base classes should not be operating on derived class objects outside of the base class interface, like ever. That's just poorly architected code no matter which way you slice it. But just like Dijkstra railing against goto, there is a time and a place for (measured, intelligent & appropriate) use.
Even the Linux kernel uses subclassing extensively. Sometimes Struct B really is a Struct A, just with some extra bits. You shouldn't have to duplicate code or nest structures to attain those ergonomics.
Anything can lead to a rube-goldberg mess if not handled with some common sense.
I believe the problem is structural to all traditional class-based inheritance models.
The sort of situation I described is almost impossible with trait/typeclass-based polymorphism. You have to go very far out of the idiom with a weird mix of required and provided methods to achieve it, and I have never seen anyone do this in practice. The idiomatic way is for whatever consumes the trait to pass in a context type. There is a clear separation between the call-forward and the callback interfaces. In Rust, & and &mut mean that there's an upper limit to how tangled the interface can get.
I'm fine with inheritance if there's rigorous enforcement of one-directional calls in place (which makes it roughly equivalent to trait-based composition). The Liskov stuff is a terrible distraction from this far more serious issue. Does anyone do this?
> You shouldn't have to duplicate code or nest structures to attain those ergonomics.
What's wrong with nesting structures? I like nesting structures.
> Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives.
Bah. There are completely legitimate uses of inheritance where it's a really great fit. I think you'll find that being dogmatic about avoiding a programming pattern will eventually get you twisted up in other ways.
Inheritance can be used in a couple of ways that achieve a very-specific kind of code reuse. While I went through the early 2000's Java hype cycle with interfaces and factories and builders and double-dispatch and visitors everywhere, I went through a period where I hated most of that crap and swore to never use the visitor pattern again.
But hey, within the past two years I found an unbeatable use case where the visitor pattern absolutely rocks (it's there: https://github.com/titzer/wizard-engine/blob/master/src/util...). If you can come up with another way by which you can deal with 550 different kinds of animals (the Wasm instructions) and inherit the logic for 545 and just override the logic for 5 of them, then be my guest. (And yes, you can use ADTs and pattern-matching, which I do, liberally--but the specifics of how immediates and their types are encoded and decoded just simply cannot be replicated with as little code as the visitor pattern).
So don't completely swear off inheritance. It's like saying you'll never use a butter knife because you only do pocket knives. After all, butter knives are dull and not good for anything but butter.
If you can use functions in the same way objects are used, there’s no need for visitor objects.
There’s a reason why everything is a Lisp. All of the patterns are obvious with its primitives, while higher level primitives like classes, interfaces hide that there’s data and there’s behavior/effects.
Visitor objects are needed when you want, at runtime, to decide what code to execute based on the types of two parameters of a function (regular OOP virtual dispatch can only do this based on the type of one argument, the one before the dot). While you can model this in different ways, there is nothing in "plain" Lisp (say, R7RS Scheme) that makes this particularly simple.
Common Lisp does have a nicer solution to this, in the form of CLOS generic functions. In CLOS, methods are defined based on the classes of all arguments, not just the first one like in traditional object systems. Combined with inheritance, you can implement the whole thing with the minimal amount of code. But it's still an OOP system designed specifically for this.
The Visitor Pattern is one of the ones that actually does not go away when you have CLOS. That is to say the traversal and visitation part of it doesn't go away, just all the boiler plate around simulating the double dispatch, like needing two methods: accept and visit and whatnot.
Like say we want to visit the elements of a list, which are objects, and involve them with a visiting object:
We write all the method specializations of generic-fun for all combinations of visitor and element type we need and that's it.
Importantly, the traversal function doesn't have to know anything about the visitor stuff. Here we have mapcar, which dates back to before object orientation.
The traversal is not really part of the visitor pattern. The element.accept(visitor) function together with the visitor.visitElementType(element) are the identifying part of the visitor pattern, and they completely disappear with CLOS.
A classic example is different parsers for the same set of expression types. The expressions likely form a tree, you may not need a list of expressions at all, so no mapcar.
The motivating scenario for the Visitor pattern is processing an AST that has polymorphic nodes, to achieve different kinds of processing based on the visiting object, where special cases in that processing are based on the AST node kind.
Even if we have multiple dispatch, the methods we have to write for all the combinations do not disappear.
Additionally, there may actually be a method analogous to accept which performs the recursion.
Suppose that the AST node is so abstract that only it knows where/what its children are. Then you have some:
;; accept renamed to recurse; visitor to fun
;; visit is funcall
(defmethod recurse ((node additive-expr) fun)
(recurse (additive-left-child node))
(recurse (additive-right-child node))
(funcall fun node))
(We might want recurse-bottom-up and recurse-top-down.)
If all the AST classes derive from a base that uniformly maintains a list of n children, then this would just
be in the base: (for-each-child ch (recurse ch fun)) or whatever.
Suppose we don't want to use a function, but an object (and not to use that object as funcallable). then we need (let's integrate the base class idea also):
I happily admit it's more than possible to come up with examples that make inheritance shine. After all, that's what the authors of these books and articles do.
But most of them put the cart before the horse (deliberately design a "problem" that inheritance "solves") and don't seriously evaluate pros and cons or even consider alternatives.
Even then, some of the examples might be legitimate, and what you're referring to might be a case of one. (though I doubt there's no equally elegant and succinct way to do it without inheritance)
But none of that changes the fact that inheritance absolutely shouldn't be the default goto solution for modeling any domain it has become (and we are taught to understand it as)
or that it's exceedingly uncommon to come across situations like yours where you have 500+ shared cases of behavior and you only want to "override" 5
or that inheritance is overwhelmingly used NOT for such niche edge cases but as a default tool to model even the most trivial relationships, with zero justification or consideration
I agree that examples matter a lot, and for some reason a lot of introductory OO stuff has really bad examples. Like the whole Person/Employee/Employer/Manager dark pattern. In no sane world would a person's current role be tied to their identity--how do you model a person being promoted? They suddenly move from being an employee to a manager...or maybe they start their own business and lose their job? And who's modeling these people and what for? That's never shown. Are we the bank? The IRS? An insurance company? Because all of these have a lot of other data modeling to do, and how you represent the identities of people will be wrapped up in that. E.g.--maybe a person is both an employee and a client at the same time? It's all bonkers to try to use inheritance and subtyping and interfaces for that.
Algebraic data types excel at data modeling. It's like their killer app. And then OO people trot out these atrocious data modeling examples which functional languages can do way better. It's a lot of confusion all around.
You gotta program in a lot of different paradigms to see this.
Yeah, there are some real good experts on various subjects here on HN. One thing i would recommend is to contact anybody directly if needed (through their email ids in their profile or otherwise) with any questions you might have. That way you can have a longer discussion and/or learn more on specific subjects. Most people are willing to help generously when approached in a knowledge-seeking manner. I always look at HN threads/discussion as merely giving me an idea of different concepts/subjects and ask for pointers to more knowledge either books/papers or experts. Hopefully i also do the same with my comments thus helping the overall s/n ratio of this site.
I will agree that the problem that class hierarchies attempt to solve is a problem one usually does not really have, but in your example you have not solved it at all.
It matches a relational database well, but once you have a just a user reference, you can not narrow it down to an employee without out of band information. If a user should be just one type that can be multiple things at once, you can give them a set of roles.
I agree that there are better ways to model roles and FGA
the point of the example wasn't to be an idiomatic solution to that problem, but to illustrate the pointlessness of inheritance in general, and User Student Employee was the first thing that came to mind that was more "real" than the usual Animal examples
in any case, as for only having a User reference, you don't ever have only a User reference - overwhelmingly you would usually load a Student or Employee,
and each of them would have those attributes on them that characterize each of them respectively (and those attributes would not be shared - if they were, such shared attributes would go on the User object)
only those functions that truly are operating only on User would you send in each of their User objects
the problem with what you're advocating is you end up with functions like this
fun doSomething(user: User) { if (user is Student) { do this } else { do that } }
One example for when you might want something like a class hierarchy is something like a graph/tree structure, where you have lots of objects of different types and different attributes carrying references to each other that are not narrowly typed. You then have a set of operations that mostly do not care about the narrow types, and other operations that do care. You have things that behave mostly the same with small variations, in many cases.
> and each of them would have those attributes on them that characterize each of them respectively (and those attributes would not be shared - if they were, such shared attributes would go on the User object)
Suppose you want to compute a price for display, but its computation is different for Students and Employees. The display doesn't care if it's a Student or an Employee, it doesn't want to need to know if it's a Student or an Employee, it just wants the correct value computed. It can't just be a simple shared attribute. At some point you will need to make that distinction, whether it's a method, a conditional, or some other transform. It's not obvious how your solution could solve this more elegantly.
> which is insane
Not at all, in my view this kind of pattern matching is generally preferable over dynamic dispatch with methods, because you see what can happen in all cases, at a glance. But again, you do not even have a contrived example where you would need dynamic dispatch in the first place, so naturally its use not obvious.
Your price could simply be an interface that both Student and Employee implements, and at the call site, you simply call foo.calculatePrice - Student will calculate it one way and Employee another, there is zero need for inheritance, or for the call site to know which object is which of the two types - all it needs to know is it has the calcultePrice interface.
I also prefer pattern matching over dynamic dispatch. But I maintain that conjuring up attributes out of thin air (which is 100% what you're doing when your function accepts one type, and then inside, conjures up another, that has additional attributes from the input!) is insane. There are so many good reasons not to want to do this I don't even know where to begin. For one, it's a huge footgun for when you add another type that inherits - the compiler will NOT force you to add new branches to cover that case, even though you may want it to. Also, such "reusable" if this then do this else do that methods when allowed to propagate through the stack means every function is no longer a function, it's actually *n functions, so ease of grokking and maintainability goes out the window. It's much better to relegate such ifs to the edges of the system (eg the http resource endpoint layer) and from there call methods that operate on what you want them to operate on, and do not require ifs.
> I also prefer pattern matching over dynamic dispatch. But I maintain that conjuring up attributes out of thin air (which is 100% what you're doing when your function accepts one type, and then inside, conjures up another, that has additional attributes from the input!) is insane.
Does it though? In one case, you have:
type S = A|B
func calculate(x:S):number
In the other, you have:
interface S = {calculate:() => number}
A::calculate():number
B::calculate():number
but also implicitly the possible:
Z::calculate():number
The latter case is more flexible, but also harder to reason about. Whoever calls S::calculate can never be quite sure what's in the bag.
> For one, it's a huge footgun for when you add another type that inherits - the compiler will NOT force you to add new branches to cover that case, even though you may want it to.
Lack of exhaustiveness checks is a problem many languages have, and if that's the case for you, that's an argument for preferring methods.
> it's actually n functions, so ease of grokking and maintainability goes out the window
...but that's exactly what interface methods are: N functions. That's what your problem is. You can't get around it. Moreover, if most of your types do not have a distinct implementation for that method, you will still need to define them all. This is where both the "super-function" and inheritance (or traits) can save you quite a bit of code.
It's sad that late-binding languages like Objective-C never got the love they should have, and instead people have favored strongly (or stronger) typed languages. In Objective-C, you could have your User class take a delegate. The delegate could either handle messages for students or employees. And you could code the base User object to ignore anything that couldn't be handled by its particular delegate.
This is a very flexible way of doing things. Sure, you'll have people complain that this is "slow." But it's only slow in computer standards. By human standards—meaning the person sitting at a desktop or phone UI—it's fast enough that they'll never notice.
I will complain that it is hard to reason about. You better have a really good reason to do something like this, like third party extensibility as an absolute requirement. It should not be your go to approach for everything.
Inheritance is nothing more or less than composition + implementing the same interface + saving a bit of boilerplate.
When class A inherits from class B, that is equivalent to class A containing an object of class B, and recoding the same interface as class B, and automatically generating method stubs for every method of the interface that call that same method on your B.
That is, these two are perfectly equivalent:
class B {
public void foo() {
}
}
class A_inheritance extends B {
}
class A_composition implements B {
private B super;
public void foo() {
super.foo();
}
This is pseudo-code because Java distinguishes between Interfaces and Classes, and doesn't have a way to refer to the Interface of a Class. In C++, which doesn't make this distinction, it's even more equivalent. Especially since C++ allows multiple inheritance, so it can even model a class composing multiple objects.
The problem with inheritance is that people get taught to use it for modeling purposes, instead of using it for what it actually does - when you need polymorphism with virtual dispatch, which isn't all that common in practice. That is, inheritance should only be used if you need to have somewhere in you code base a collection of different objects that get called in the same way but have to execute different code, and you can only find out at runtime which is which.
For your students and employees and users example, the only reason to make Student and Employee inherit from User would be if there is a reason to have a collection of Users that you need to interact with, and you expect that different things will happen if a User is an Employee than if that User is a Student. This also implies that a Student can't be an Employee and vice versa (but you could have a third type, StudentEmployee, which may need to do something different from either a Student or an Employee). This is pretty unlikely for this scenario, so inheritance is unlikely to be a good idea.
Note also that deep class hierarchies are also extremely unlikely to be a good idea by this standard: if classes A and B derive from C, that doesn't mean class D can derive from A - that is only useful if you have a collection of As that can be either A or D, and you actually need them to do different things. If you simply need a third type of behavior for Cs, D should be a subclass of C instead.
I'd also note that my definition applies for inheritance in the general case - whether the parent is an interface-only class or a concrete class with fields and methods and everything. I don't thing the distinction between "interface inheritance" and "implementation inheritance" is meaningful.
This is not quite correct, at least not in most commonly-used languages. The difference comes when a "superclass" method calls self.whatever(), and the whatever() is implemented in both the "superclass" and the "subclass". In the implementation-inheritance-based version, self.whatever() will call the subclass method. In the composition-based version, self.whatever() will call the superclass method. The implementation-inheritance-based version is sometimes called "open recursion". This is why you need implementation inheritance for the GoF Template pattern. See for instance this paper:
for one, see the reply from the handle cryptonector further up
> Inheriting from a class creates dispatch problems, and there's instance variables/fields/whatever to deal with. Never mind multiple inheritance, as that gets hairy real fast. With interfaces there is no hierarchy, and all there is is a type and a data, and you either pass them around together, you monomorphize to avoid the need to pass two pointers instead of one, or you have the data point to its type in a generic way. With class hierarchies you have to have operations to cast data up and down the hierarchy, meaning trade one dispatch table for another.
I agree with all of this. With interfaces, you get all the benefits, but none of the downsides.
This is a solved problem and it's astonishing the world hasn't just adopted the Dutch traffic engineering standards outright. It's FASTER for cars and safer for people.
The lack of adoption of best practices from other countries is generally baffling to me. When I first visited China grim Europe and saw traffic lights with countdowns (like in the US) I thought we did immediately adopt this in Europe. Cultural inertia and lack of looking outwards is really frustrating.
Crazy idea that would be next to politically impossible in North America: Have every traffic light with a countdown also be a speed camera.
It'd eliminate the incentive to drag race, would give drivers more information earlier allowing better driving, and would generally make speeds limits actually a limit on roads with traffic lights.
That's a solved problem in responsible cities, say New York. The cameras only take a picture if they see someone speeding [1], and once they see someone speeding a citation is issued and that's a matter of public record [2] so there is no data to sell.
But like I said, politically impossible, there's a very strong constituency and lobby of people who like cars and like speeding and will come up with excuses for why it couldn't work. For instance New York State restricts New York City to only putting speed cameras in school zones because of that group of people.
Countdown signals don't work with adaptive signalling where phases are dynamically lengthened or shortened (or sometimes entirely skipped) in response to traffic flows. They especially don't work with public transport priority.
I know if I still have time to try to cross or not. Especially on wider roads it's nice not to have to worry about being in the middle when it goes red.
It’s an entire book length of material, but in short, for built up areas drivers are forced through street to design to be higher alert to their surroundings. EX: chicanes in the road, speed tables, brick roads, narrow streets, small/tight turn radius, no turn on red, etc. These all work together to make a system that is amongst the safest in the world for pedestrians, and by happenstance has the happiest drivers.
Also, bike traffic and vehicle through traffic are separated on different networks, so a conflicts are minimized.
Which requires a long and detailed answer that can be found by simply googling "Dutch traffic engineering standards" which leads you to an entire wealth of information.
The hard problem isn’t figuring out what to do. Its to get people on board with shifting from a like for like infrastructure development model where the roads and built environment look more or less the same for decades, to a potential status quo changing model of infrastructure development. If you can solve that fundamental issue, traffic is just a footnote of the long list of problems you also solve on our planet.
To clarify, aren't these standards mostly relevant where heavy bicycle traffic exists? Do they still apply in areas with little to no bicycle traffic? I'm assuming you're mostly referring to this famous manual: https://en.wikipedia.org/wiki/CROW_Design_Manual_for_Bicycle...
There is no bicycle traffic because there are no bicycle roads. It's incorrect to claim that we shouldn't build bicycle roads because there's no bicycle traffic :)
London Underground destroys all other transit on the planet when it comes to noise, and ironically, it's not even the trains themselves, it's the effing speakers
MIND THE GAP
louder than an Iron Maiden concert, it's unbearable and unnecessary, no idea why they don't turn that shit down
Even if the post accurately reflects your lived experience, if it's AI generated, ultimately it is just that, generated by an AI, who is not you. People can smell that from a mile away, and they dislike it. (as they should!)
Get over your anxiety over your writing and just share your authentic self. How good or bad a writer you are doesn't really matter. (like OK it has to be legible but beyond that no one is expecting perfection)
It's whether the story you're telling, the experience you're sharing etc is thought provoking that matters.
A lot of Robert C Martins pieces are just variations on his strong belief that ill-defined concepts like "craftsmanship" and "clean code" (which are basically just whatever his opinions are on any given day) is how to reduce defects and increase quality, not built-in safety and better tools, and if you think built-in safety and better tools are desirable, you're not a Real Programmer (tm).
I'm not the only one who is skeptical of this toxic, holier-than-thou and dangerous attitude.
Removing braces from if statements is a great example of another dangerous thing he advocates for no justifiable reason
The current state of software safety discussion resembles the state of medical safety discussion 2, 3 decades ago (yeah, software is really really behind time).
Back then, too, the thoughts on medical safety also were divided into 2 schools: the professionalism and the process oriented. The former school argues more or less what Uncle Bob argues: blame the damned and * who made the mistakes; be more careful, damn it.
But of course, that stupidity fell out of favor. After all, when mistakes kill, people are serious about it. After a while, serious people realize that blaming and clamoring for care backfires big time. That's when they applied, you know, science and statistic to safety.
So, tools are upgraded: better color coded medicine boxes, for example, or checklists in surgery. But it's more. They figured out what trainings and processes provide high impacts and do them rigorously. Nurses are taught (I am not kidding you) how to question doctors when weird things happen; identity verification (ever notice why nurses ask your birthday like a thousand times a day?) got extremely serious; etc.
My take: give it a few more years, and software, too, probably will follow the same path. We needs more data, though.
I’m not sure any of them do really. It’s been 22 years since TDD made its entry into our field and it’s still worse than the runtime assertions which helped put people on the moon. I know I was lashing out at uncle Bob before but it’s really all of them.
I do agree with these people that nobody has ever regretted writing a test. Well, I mean, someone probably has, but the idea of it is fairly solid. It’s just also useless, because it’s so vague. You can write a lot of tests and never be safe at runtime.
There isnt much consensus on the right kind of test to write with TDD, but when you get it right or wrong it makes or breaks TDD.
Recently Ive been writing mostly "end-to-end unit tests" - stateless, faking all external services (database, message queue, etc.) with TDD which works great.
There is a sweet spot on default test types - at a high a level as possible while being hermetic seems to be ideal.
The other un-talked about thing is that to be able to always write this kind of test you need test infrastructure which isnt cheap to build (all those fakes).
My biggest problem with TDD is that it doesn't actually protect you from the things you miss. This is great for the consultants who sell TDD courses, because they get to tell you that you did it wrong. They'll be right, but that fact is also useless. I don't have a problem with testing though, as I said I think it's hard to regret doing it, but I'm not sure why we wouldn't push something like runtime assertions instead if we were to do the whole "best practice" thing. With runtime assertions you'll get both the executable documentation and the ability to implement things like triple modular redundancy, so basically everything TDD does but much better.
Yet many developers don't even know what a runtime assertion is while everyone knows what TDD is. I guess it doesn't really matter if you're working on something which can crash and then everyone will be like "oh it's just IT, it does that".
It often protects me from things I miss. E.g. when I'm TDD'in a feature and I'm implementing the 5th-6th test I often find myself inadvertently breaking one of the earlier tests.
I do think there is such a thing as overtesting - i.e. regretted tests. TDD actually protects you from this to an extent by tying each test / modification of a test to a change in code.
Runtime assertions definitely give you more bang for the buck (they are ridiculously cheap) but they are complementary to tests, not a replacement. It attacks the same problem from the bottom up instead of top down.
I also find that when you combine the two, the tests become more useful - previously passing tests will fail when they trigger those assertions.
The sane thing to do is to let lower layer functions return
Either<Error, Foo>
then, in the HTTP layer, your endpoint code just looks like
return fooService
.getFoo(
fooId
) //Either<Error, Foo>
.fold({ //left (error) case
when (it) {
is GetFooErrors.NotAuthenticated -> { Response.status(401).build() }
is GetFooErrors.InalidFooId -> { Response.status(400).build() }
is GetFooErrors.Other -> { Response.status(500).build() }
}
}, { //right case
Response.ok(it)
})
the benefits of this are hard to overstate.
* Errors are clearly enumerated in a single place.
* Errors are clearly separated from but connected to HTTP, in the appropriate layer. (=the HTTP layer) Developers can tell from a glance at the resource method what the endpoint will return for each outcome, in context.
* Errors are guaranteed to be exhaustively mapped because Kotlin enforces that sealed classes are exhaustively mapped at compile time. So a 500 resulting from forgetting to catch a ReusedPasswordException is impossible, and if new errors are added without being mapped to HTTP responses, the compiler will let us know.
It beats exceptions, it beats Go's shitty error handling.
> everyone there is contributing to the energy by actually having a good time, instead of being preoccupied with showing people who are not with them that they are having a good time
this screams "economists, behavioral economists, game theorists, sociologists and anthropologists" would love to study this"
people from all the above disciplines, where are you? this is your phd thesis
I watched several groups of “tech nerds” get rejected from night clubs in the Bay Area after the huggingface open source meetup.
It doesn’t matter how much money you give to them, society think they’re still ugly, four eyes, nerds who deserve to be shoved in lockers. Remember that the only reason high functioning autists weren’t murdered by the nazis was dr.Hans Asperger intervening to convince the German high command that they can build rockets.
Look to how the internet uses “sperg” as a pejorative similar to intel. Forcing a neurotypical to listen to your “*tism” rant makes them literally want to murder you. Resentment against tech workers with money is off the charts in trump voting America. Ungrateful to say the least…
> What’s stopping tech nerds from putting effort into their appearance like everyone else to get into a club?
For those who put the effort, nothing will stop them, and they will be more than welcome. The idea is to filter out the smug ones who think they are higher human beings who don’t need to conform to the norms of such places like everyone else, and putting any effort toward such “intellectually plebian” entertainment is beyond them. So everything is kinda working as intended.
Imagine if instead Miami built MetroRail extensions to the beach and everywhere else it should go, increased TriRail frequency and express services, built a real network of fully segregated greenways etc etc. It would turn transport nightmare into transport heaven. We don't need more cars on the streets of Miami or the I95...
> trirail frequency increases can be done overnight
Increasing the frequency can't necessarily be done overnight, unless they actually have the spare rolling stock just sitting around along with all the workers needed to operate and maintain the increased usage and the spare budget to cover the increased operations costs. Otherwise, they need to find the money to procure the rolling stock, actually place the order, wait for the rolling stock to be built/delivered, hire the people to operate and maintain it, etc.
You can get expansions in funding approved, solicit bids from multiple firms to make the trains, analyze and approve the bid, get a factory to make potentially several to dozens more trains, ship them across the country (or potentially internationally), hire and train a lot of workers, in less than 24 hours?
> but tell you and every other frothing at the mouth motorist what, enjoy sitting in traffic
You're being quite rude here about this for no reason and projecting an identity on me that's not warranted. I'm generally pro public transit, but I'm also a realist and not suggesting it takes practically zero time to procure additional rolling stock and hire a lot more people. A lot of people think having a higher level of service is just run the trains/busses more, but chances are they're already running all the stuff they currently have the capacity to own and operate. It's not like most transit orgs have double the current capacity just sitting idle and nobody thought to run them.
It took them three years after finding the funding and getting all the approvals and signing the contracts to add rolling stock last time. So probably more like four or five years at least to add some additional trains. And that was replacing existing trains, not expanding the fleet, so its not like they had to considerably expand their existing workforce. I imagine most people would consider four or five years not "overnight".
The bus service near me is usually every 20 minutes. That's terrible. I'd absolutely love it to cut that in half. It also means it would cost significantly more to operate. Getting everyone to agree to pay that (a massive task at the start), getting all the proposals put together, soliciting bids, signing the contracts, getting the new busses, hiring the new drivers, and actually increasing the service isn't something that is going to happen in 2025. Probably also not 2026.
the disconnect here is you have a status quo biased thinking
the current state of things is, roads get all the money and transit and bike infra get scraps and are poorly run (so are FDOT road projects too btw)
no one disputes that?
what is being advocated is increasing trirail frequency, implementing an actual network of segregated greenways and expanding metrorail
you're saying "oh we can't do that"
but like, yes, we can? I promise you, if you send out construction crews to apply green paint and put down curbs for greenways, there's no natural law of the universe that would make the paint not come out
and once it's in place, there's nothing preventing millions of Miami residents from using them the same way they're being used in NYC, Montreal, Barcelona etc etc instead of having to get in the car for literally every single trip and errand
likewise if you procure trains there's no magic wall that prevents them from crossing into the state of Florida etc etc
these things are trivially achievable, but misinformed policymakers and voters alike think adding more roads is somehow not costing any money (it costs way more) and will fix traffic (it won't)
> the disconnect here is you have a status quo biased thinking
No, the disconnect here is you're being quite rude here about this and projecting an identity on me that's not warranted. And now you're even putting words in my mouth.
> you're saying "oh we can't do that"
I never once made the claim. I just argued it wouldn't be "overnight".
None of my statements were about greenways or even about expanding the Metrorail. Just that adding additional capacity can and often does take a while to be approved, acquired, and put into service. Stating it can be done overnight is ignoring reality just as much as someone arguing the paint somehow wouldn't come out to paint a greenway.
I'm for them adding more trains and expanding the existing lines. I'm for the bus service outside my house being a lot better than it currently is. I'm also looking at the fact the cities around me are talking about slashing the funding instead of increasing it and seeing the people around me cheering for such an idea. Me thinking it can be improved overnight is a delusional thought given the realities of today. Thinking Tri-Rail can just snap their fingers and magically get approvals and sign contracts and get trains delivered overnight is also delusional.
Even if we somehow changed people's minds "overnight" to want to increase train service, it'll still take a few years to actually do all the process for acquiring and implementing the additional capacity. Governments almost always move slowly. Even when talking car infrastructure, something which generally is popular, it takes forever to put together the budget proposals, get the funding approved, get the bids together, purchase the materials, and actually get to work. They're still working on doing projects related to a road bond package in my city passed several years ago, and that's once again ignoring all the planning that went into it just to get the proposal together and get it passed.
None of this happens "overnight". Even just getting everything together to officially change the traffic patterns and put the paint down will take many months at the fastest. And that's assuming it's a popular decision.
With car usage on most roads free of charge at use and maintenance also footed largely by the Federal Government, it probably comes out cheaper long term to invest in rail.
And every car off the toad makes driving more pleasant for everyone who stays.
lol the idea isn't to make money off transit, it's to save money on roads
roads cost more than transit - a LOT more, and motorists aren't paying anywhere near the cost of road construction and maintenance, they're (quite literally) free-riding subsidized trips on the taxpayer
traffic also destroys productivity, public health, life expectancy etc etc so costs money in many more ways than motorists not paying for them
> and motorists aren't paying anywhere near the cost of road construction and maintenance, they're (quite literally) free-riding subsidized trips on the taxpayer
So are public transit riders. And to a worse degree. What's your point?
We should magically spawn mass transit systems overnight and force everyone to ride them?
By the way, I'm a fan of mass transit, and live somewhere in the US - specifically - where that's a viable option.
It just isn't a viable option in ~80% of the US, and even if those areas start doing everything right to be mass transit viable (no indication of that), it still takes decades.
> So are public transit riders. And to a worse degree. What's your point?
that this is wildly incorrect. roads cost more than transit. a lot more. and road users are wildly more subsidized than transit users.
> We should magically spawn mass transit systems overnight
yes
> and force everyone to ride them?
you won't have to when the choice is between sitting hours in traffic vs a fraction of the time on efficient transit and greenways. people are not stupid.
> It just isn't a viable option in ~80% of the US
this is Miami, not middle of nowhere Iowa
> Rome wasn't built in a day
so it's correct of Miami to continue to "invest" in even more roads to nowhere, like yet another new highway bridge across the bay? that will take decades to complete and cost billions, and cause MORE traffic?
like no lol just build the effing transit and greenways and traffic will go down and the government and people alike will save billions instead (and their time, and lives)
>Well that would cost Miami taxpayers hundreds of billions of dollars, and this costs Miami taxpayers nothing. It would also take decades.
Ironically enough, the county approved and passed a tax back in 2000 to expand the MetroMover. Not a single inch of rail has been built since. Wonder where all the tax revenue went?
That doesn't follow. Nobody has said we will force the rich out of their limos.
Some of the rich will chose to use transit though. There is a group of rich people who got that way by being cheap and that group will use transit if possible just because it is cheaper. They don't care about sharing rides with poor people at all.
There is a group of people who appear rich - they live in mansions, drive limos. They are also in dept up to their eyes and one wrong move will put them out on the streets.
this argument against common sense bike infrastructure is one of the most common, most wrong, and most dumb
bicyclists, pedestrians and transit users in fact subsidize motorists, in all countries, everywhere. this isn't up for debate. so under your own logic, motorists should have no right to the roads, because they're "freeloading" and "not paying their fair share". sigh.
ironically, even the most ardent bike infra advocates don't actually think that. they just think the money they're paying shouldn't be expropriated exclusively for motorists, while they themselves get close to nothing, especially when bika infra is so comparatively cheap and efficient (it actually SAVES the government and the public money)
the benefits of bike infra are obvious and self evident. less pollution, less noise, more mobility for children and the disabled. it benefits motorists too, because it takes traffic off the roads, and saves parents time and money having to ferry their kids around all the time etc etc.
tbh people like you seem just like hateful selfish misanthropes.
Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives.
Example: imagine you have a school system where there are student users and there are employee users, and some features like grading that should only be available for employees.
Instead of making Student and Employee inherit from User, just have a User class/record/object/whatever you want to call it that constitutes the account
and for those Users who are students, create a Student that points to the user and vice versa for the Employees then, your types can simply and strongly prevent Students from being sent into functions that are supposed to operate on Employees etc etc (but for those cases where you really want functions that operate on Users, just send in each of their Users! nothing's preventing you from that flexibility if that's what you want)and for those users who really are both (after all, students can graduate and become employees, and employees can enroll to study), THE SAME USER can be BOTH a Student and an Employee! (this is one of the biggest footguns with inheritance: in the inheritance world, a Student can never be an Employee, even though that's just an accident of using inheritance and in the real world there's actually nothing that calls for that kind of artificial, hard segregation)
Once you see it you can't unsee it. The emperor has no clothes. Type-wise functional programmers have had solutions for all these made up problems for decades. That's why these days even sane Object Oriented language designers like Josh Bloch, Brian Goetz, the Kotlin devs etc are taking their languages in that direction.