Site note: If you want to relate this to life-advice or self-help advice, go read this excellent blog post too: http://thelastpsychiatrist.com/2011/01/are_law_schools_lying_to_their.html
I don’t doubt for a moment he sincerely believes he is a lawyer, because lawyer for him isn’t a profession or even a job, it’s a label, a code word for a kind of intellectualism he wants for himself. As long as “all of my friends see me as…” it was well worth the cost. He didn’t study to become an attorney, he bought a back-up identity.
Composing my thoughts linearly on this subject is hard, but I’m going to try. Let’s begin with some classical examples in any introductory OOP class/textbook/course.
Every object is of a certain “type”, the logic goes. A car is also a vehicle. A vehicle is also a machine. A boat is a vehicle. An aircraft is a vehicle. Knowing this, you can now write targeted code that operates on all vehicles once and run it against any vehicle.
Furthermore, you can write code targeted to each of these specializations too. Code that only works on an aircraft. Code that only works on a car. Code that only works on a boat. Code that works on all machines.
All of this makes so much sense. It’s a good world view. You define your business model as a set of “objects” with “types” and then you write code against those types. Now you can extend your system by declaring new “things”, which might be vehicles, and once you declare them as being a vehicle, your vehicle-based code works against them instantly!
So far so good, right? See any problems with this? Now the obvious cases are easy.
- What if a car is also a plane? (Multiple inheritence). Then how do you determine which “drive” function is called? Etc. etc.
- What about a “Truck”? Is a Truck a subset of car or directly a subset of vehicle?
But these are copouts. Let’s address something FAR more real-life oriented, since OOP-people are all about real-life modeling.
Is your car exactly the same as it came out of the factory? I bet the brand new factory car’s “o.pedalToTheMetal()” has a different impulse response than on yours. I bet your truck’s “o.turnLeftIndicatorOn()” doesn’t always work. Mine doesn’t. I bet your “o.stopInTenFeet()” doesn’t always stop in ten feet.
Beyond the very academically cool problems that research papers are made of, like multiple inheritence and how to define a “type”, is the very very pragmatic problem none of these examples ever considers: After you drive your car out of the showroom, it is distinctly YOUR car. It stops meeting factory specifications almost instantly. It acts different. It behaves different. It performs different. A mirror is chipped. A wheel is scratched. A door has a dent. A window doesn’t quite roll open.
This is why your textbook never moves past the definition of simple objects. It truly doesn’t have the balls to follow through with that example as a real living breathing growing changing software environment would. What it doesn’t compare is that each individual car, over time, becomes a class over time. Thus you end up with a thousand confusing classes, repeated code, and a bunch of junk which you are forced to contain with the infamous “design patterns.”
What I’m saying is, those OOP analogies DO work, but OOP proponents were always too cowardly to take them to their logical conclusion. If by now you’re thinking Alan Kay got it right the first time around with Smalltalk, welcome to every functional programmer’s point of view.
Now for a moment lets focus on identity. This could get slightly philosophical in the sense of “Who am I?” – go read the blog post to ponder further. The takeaway from that article which is relevant to this discussion is: Do you care that someone *is* a doctor, or do you care that *they can remove your tumor*? How you think about this, determines greatly how you approach any desgin, problem and thus solution in life – and in programming.
Does it tell you more that a person “canDefendAgainstADUI()” than that they “areLawyer() && !areLawyerWithoutLicense()” (Because you know someone could be a lawyer, and not have a license to practice law.)
See what I mean about “identity”. I’m an engineer because I engineer things. When I’m scuba diving, I’m a diver. When I’m dancing, I’m a dancer. My “identity” is far less valuable in these situations than by “ability”. Whether I can perform a cross-body lead is far more relevant than who I am and the host of identities and sub-identities that define me.
If you’re casting for the part of Long John Silver, you’re looking for the condition “has one leg”, than you are looking for the identity of “One-legged Person”. It is easier to test, reason about, think about, program against, debug, maintain and in general understand and comprehend. It is direct. It is to the point. And it expresses *intent*. Last year I wrote a very popular article about choice of programming languages, and my key recommendation was: Pick the language that expresses your intent. Of all the things you can decide on – expressing intent is the ONLY one that matters.
Ever since I learned programming, this is what I started to do in old BASIC:
DIM a, b, c
It is literally the first thing you do before you can do anything useful. You have to define a name, and assign it a “type”. Then all is well, because the “type” protects you against doing something rash.
In the real world though, types can be a problem. When you have a program linked across three versions of the same library, you can begin to see problems. What one programmer thought is “APerson”, is not the same thing as what another programmer thought it was, which is still different from what a third one thought it was.
That’s where we realize why type systems are so painful: what a specific, particular, constructed, existing, working, living, breathing object can do for me is FAR more important than what it declares itself to “be”. I can declare myself a lawyer. For all you readers, you have no clue whether or not I “am a lawyer”. You might google my degrees, but you will never know that I *don’t* have a law degree. What you *do* know is that I am not bar certified in the state of Washington. Even by the most simplistic modeling angle, modeling behaviors is far more relevant than modeling identity. Incidentally, it also happens to be simpler. Go figure.
Wait a minute you ask – you’re just about thinking of interface-enforcement. Good job! You’re about to counter me with this: “What if I wrote a piece of code that should take in an object of an interface to, let’s say sort. One object might implement ‘Sort()’, another might implement ‘sort()’ while yet another might implement ‘doSort()’ and yet another might implement ‘performSort()’. Doesn’t a class/interface help me enforce consistency?”
That is a very astute observation and counter-challenge to my argument. There are two distinct responses I have to this.
My first response is based on group theory and equivalence classes – funny how I’m using classes to argue against classes. Look at it practically. Does a class REALLY help you enforce consistency? Think about it deeply to the last time you really did this and when it wasn’t toy software you wrote in your spare time where you were the sole contributor.
Furthermore, you can’t encapsulate context for shit! Now you’re doing globals and thread-locals and all sorts of nonsense. Then you’re doing locks and synchronization to deal with it. Then you’re doing async and continuations to deal with the locks! Then you’re using frameworks to deal with those callbacks and what not. Then you’re using IDEs to help you use those frameworks. Why? Because fundamentally you couldn’t pass around behaviors to save your life.
The correct (better) way of passing an object to a consumer is to never really pass an object. Hardly any consumer cares about your structure. It cares about performing an action. It is FAR easier to pass it in an anonymous function called “bookAirplaceTicket”, than to pass it in 10 objects called, “(ticketParams, context, customerInfo, databaseConnectionString,…. etc. etc.)”
Behind your function the consumer doesn’t care that it’s a toy function, a mock function, a stub or uses 10 objects or 500. You don’t care to tell your consumer about a thousand interfaces.
With me so far? Haven’t lost you? Is any of this making some sense? Then let me give you my second response, which is rhetorical.
What about “a Lawyer” who doesn’t want to prosecute? Who cares “how long”, or “why”. The classical approach is to give the lawyer a bunch of attributes. “OnVacation”, “VacationDuration”, and so on. Then every other person has to understand what a lawyer’s vacation means. And he has to check it. And if someone forgets to, they just booked themselves a useless lawyer. What if a lawyer broke his kneecap? Add another attribute: “IsKneecapIntact”. Then tell each human every intending to deal with a lawyer to check THAT condition.
Why? All because you refused to check whether the person “bool canProscecute()”. Because you insisted on getting all philosophical and typing him as “A Lawyer” more than you cared what you want out of him – which is a person who is currently capable, qualified and intends to prosecute.
How does this apply to OOP? Ever use an object that throws “NotImplementedException”? What does it mean? How is the object “Bus” if “Drive()” throws “NotImplementedException”? Is there a non-drivable Bus? It’s a tin can – I’ll go with you that far. But do you care that you have a “Bus”, or do you care that you have a thing that is capable of being driven and can carry a certain payload?
So now, not only do types not tell you something about an object, but you’re actively fighting them – checking for nulls or nils or exceptions or what not – when all the object had to do, was remove it’s method/function/behavior/message-handler called “drive”, and you would have stopped caring. Because it calls itself a Bus, and because the language insists that a Bus always have a method called Drive, you now have to execute it before you know it’s going to work. Surely that’s a terribly way to write code that can be reasoned about.
But wait you say – you have frameworks and static analyzers and expensive tools to prevent people can’t do that. You my friend have solved an NP-hard problem – you have successfully statically analyzed a program, using another program, and assured me it terminates.