Around 1990, object oriented programming (OOP) was all the buzz. I was curious what the term meant and had a hard time finding a good definition. I still remember a description I saw somewhere that went something like this:
Object oriented programming is a way of organizing large programs. … Unless your program is fairly large, you will not see any difference between object oriented and structured programming.
The second sentence is no longer true. OOP is not just a high-level organizational style. Objects and method calls now permeate software at the lowest level. And that’s where things went wrong. Software developers got the idea that if objects are good, more objects are better. Everything should be an object!
For example, I had a discussion with a colleague once on how to represent depth in an oil well for software we were writing. I said “Let’s just use a number.”
double depth;
My suggestion was laughed off as hopelessly crude. We need to create depth objects! And not just C++ objects, COM objects! We couldn’t send a double
out into the world without first wrapping it in the overhead of an object.
Languages like C# and Java enforce the everything-is-an-object paradigm. You can’t just write a function; you have to write member functions of an object. This leads to a proliferation of “-er” classes that do nothing but wrap a function. For example, suppose you need a function to solve a quadratic equation. You might make it the Solve
method on a QuadraticEquationSolver
object. That’s just silly. as John Carmack said,
Sometimes, the elegant implementation is a function. Not a method. Not a class. Not a framework. Just a function.
Languages are changing. You can create anonymous functions in C#, for example. You can even create a named function, by creating an anonymous function first and saving it to a named variable. A little wonky, but not too bad.
I imagine when people say OOP is terrible, they often mean that OOP as commonly practiced now goes too far.
I think extensive use of the -er paradigm is actually indicative of another type of encapsulation failure. Most of the time, the correct OO design is
class QuadraticEquation
{
….
List solve();
}
I still think that creating depth objects is a bad idea. But I do see that it makes sense for depth to be an attribute of another object that gives it its context. Depth of what? Relative to what? In what units? In what coordinate system? (i.e. perpendicular to the surface or along the axis of the well?)
Depth is a measurement, which requires knowledge of the unit used in the measurement. “Depth” is probably too specific, but something like “Distance” or “Length” would be a good encapsulation. So, I do have a point of agreement with your colleague. But it has almost nothing to do with OO. It’s a matter of typing. A language like Java would force you to write a class for the type, but in a language like Haskell, you could define your type more directly. I totally agree that OO has gone too far. Functions and structs are very useful and shouldn’t be relegated to second class (or nonexistent) status. “Too many objects” is close to a code smell that I teach my students: “Too many damn classes”. As soon as you create a class for your depth, someone will want to subclass it! As I said, “too many damn classes”…
So a solution for “how to represent depth in an oil well” was described as “hopelessly crude”. Well, it brightened my day.
Eos: I’d like to say that that pun was intentional, but I didn’t notice it until you pointed it out. :)
It was definitely a slick pun.
OOP leads to complex solutions for simple problems. That is why the Linux kernel is written in C. “double depth” is perfectly OK in the Linux kernel.
http:www.clojure.org. Sane programming.
Is the argument against the abstraction/reification or the overhead of abstraction?
If I could abstract with zero heap cost should I do that?
In Scala (2.10 onward) I would solve the heap cost problem by using value classes.
What about Units of Measure as available in languages such as F#. Wouldn’t depth represetned as d be preferably to a raw number. Should I trade off compile time safety of not being able to add M to M^2 for the heap saving?
My concern is human mental effort, not machine effort. The machine overhead for OOP is often negligible as long as you’re not naive about how you handle memory.
I just don’t want to write or read a couple dozen lines of code when a single primitive type will do. I’m OK with the evertyhing-is-an-object approach as long as it’s not in my face. If it’s invisibly supported by the language, great. But if I have to implement it and look at it, that’s clutter. Unit support, such as in F#, is great because it’s built in.
These slides from Odersky’s keynote as ScalaDays adresses combining OO and functional…”you need to put things somewhere or risk an unmanageable namespace”
http://www.slideshare.net/Typesafe/scaladays-keynote
My choice would be:
typedef double Length_t;
Length_t depth;
a little syntactic overhead, just in case someone will give me in the future a good class for representing a length.
It feels like a confusion of type and object.
Having a type defined for depth could be a good idea. For example, we should have more reasonable bound than double would allow, and precision. Compiler can automatically type check thing so you never multiply two ‘depth’ together – a rather meaningless thing to do with no possible physical representation.
Object is different. Fundamentally – object is a binding of data together with their computation. Having a linear program solver object is a good idea, because you can abstract the outside world from the specifics, and you co-locate all the state that related to solving the program together. The co-location allow you to transport a solving program to another machine, to save and pause the computation, or simply just allow developer to interpret it as a whole, black box state instead of trillions of variables. Depth as an object is a horrible idea. There is no co-location of information – nothing need to be abstracted (except possibly the storage unit of measure) – and nothing need to be encapsulated (except possibly from changing the value).
It’d be awesome if I can create type without creating object. (i.e. pure compile time thing). Not sure if any mainstream programming language does.
Your example is http://c2.com/cgi/wiki?PrimitiveObsession gone too far. Any guideline can be taken too far. It’s still worth knowing them, but you have to think to understand when to use it and when it’s not a good idea anymore.
“Sometimes a cigar is just a cigar.” — Sigmund Freud
s/cigar/number/g
I can see why the oilmen found your plain number “too crude”.
There are plenty of books (“The Inmates are Running the Asylum” comes to mind) where naive numerical errors left warships adrift in the middle of the ocean, or made manned rockets blow up in the air. Software errors. That’s why some disciplines, and I believe oil-exploration is one of them, require very very strict/strong typing of anything. My boss worked in software for oil platforms in the past, and there were hundred-page specifications about HOW TO BUILD the software, and even variable names were strictly standardized and controlled (not a matter of developer choice, that is).
A good example of how to take a “depth” double and make it into a very overheaded object is given in Chapter 3 – Observations and Measurements, from Martin Fowler’s “Analysis Patterns” book, where he models the following (inter-related) entities: number, unit, quantity, conversion ratio, compound unit, measurement, observation, observation protocol, and some others…
I believe that many times it’s only bureaucratic overhead, but I also believe that sometimes it is actually needed to go that far. I can pretty much imagine a number overflow causing an actual OIL overflow…
The COM object in this case would not have added any sophistication. The proposal was to create a COM object that simply contained a
double
, plus the reference counting overhead necessary to make it a COM object.Over time, my code style has evolved so that I write Java code with only two types of classes: classes consisting of entirely static functions and no fields (following a functional programming style), and classes consisting of only fields and no methods (other than sometimes a constructor and maybe a few convenience methods — really I’m using objects as structs).
I drop back to full OOP when it makes sense of course — OOP is the right abstraction for many things. But most of the time, making every method static that can possibly be static (while avoiding static fields like the plague) produces much cleaner code. This is “OOP lite”, I guess.
(I don’t even use getters and setters anymore. I agree with many people that they are evil.)
I work with someone who applies an OO approach to everything. Everything’s an object. He claims everyone’s doing it, and if you’re not, you’re behind the times.
I asked him to solve a relatively simple math problem. He invented 4 classes (two of the classes were related through inheritance) to solve the problem.
“When your only tool is a hammer, everything looks like a nail”
s/a hammer/OOP/g
s/a nail/an object/g
@Luke The inventor of Clojure, Rich Hickey, says he had this exact approach before creating the language. Now that I have been biten by the functional monster, it’s hard to program in a different way.
While I agree with this, I should note one caveat: In many languages (e.g. Java, older C++, ML), the object system is also the module system. This even makes some kind of sense when you consider that an object is an instantiable module.
Objects are, therefore, the only sane way to keep your code well-organised.
It’s easy, of course, to write bad code in any language, and it’s really, really easy in an OO language. I’ve seen plenty of examples of bad OO code–crazy class hierarchies, crazy encapsulations (or lack thereof), crazy dependencies, etc. But, in general, I would object to an API that represented depth as a naked double.
Back in the old days, we’d actually consider CPU clock cycle differences, or number of bytes of machine code required, when choosing how to code something. But nowadays, if you’re reasonably well versed in a modern language, then code clarity, maintainability, and robustness (correctness+non-brittleness) far, far out trump conciseness. If you have to write a few more lines to define a type or an interface, but in return you get clarity, maintainability, and robustness, it’s well worth it. (And if you don’t think so, you probably need a better programming editor.)
Suppose you’re designing a platform API that you expect to be used by many people for many years. Which API approach do you suppose will have a lower lifetime cost?
setDelay(int delay)
setDelay(Duration delay)
I totally agree with the “classes in -er” that are just repositories for plain functions. This is the same thing for “classes” with no properties. Confronted to such a case, I would first wonder why I need to solve quadratic equations (or whatever the function). Is it related to some feature of an object? If yes, the method should integrate a “real” class. If no, it should be made explicit that they are just functions without any state. In Java, I would create a class called something like EquationsLibrary and put in there all those functions as public static methods. At least they are simple to call and there is no mistake about what they are.
Regarding the depth example, I also agree that in that case, OOP goes too far. I would do it slightly differently: if the property is just internal to the object and is never communicated outside, then a double is perfectly fine. If you want to communicate it outside the object, then it is worth putting it inside a Length object, as they could be issues with conversion between different systems. But if the property is just numberOfItems, then a simple integer is enough, I do not see the need to encapsulate it.
Generally speaking, I agree that when you have a tool, you should not try to shape the whole world with it.
In a business context, there are no such things as primitive types. Even integers have business rules and there is no such thing as integer addition. Whaa…aaat? Look: unit sales for the week of 22 July:
Monday: 5 units
Tuesday: 7 units
Wednesday: 2 units (Hmm.)
Thursday: 6 units
Friday: 9 units
5 + 7 + 2 + 6 + 9 = 29. What is the type of 29? It’s NOT an integer. Its interpretation and its handling depend on its context. Of course, this is a toy example, but damn straight, everything is an object.
Frank: As Andrew Au pointed out above, there’s a difference between objects and types. You’re making an argument for types, not objects.
John,
Dead horses shall not be beaten, but I am (in Andrew’s, or anybody’s, terms) talking about binding content and behavior.
Thanks,
FW
.
here is what I find drives me crazy. In many cases starting with a single value is the best choice. But let the program evolve where something containing supporting data occur and so many programmers refuse to move to some real object. It is like the choice today can never be changed.
John, this is likely the real reason they wanted a COM object. Because once (at band camp) they started with a simple numerical value and then found later they needed a COM object, but by then they had written at least fifty lines of code…
I have yet to see someone start a new project after a “proof of concept” was written. Instead they take off with that because it does so much already.
I wouldn’t make a depth object, that seems insane. In 99% of the cases, a double is fine because a depth value has all of the same properties of a number. I *might* however make an type for meters, millimeters, etc.. to avoid mistakes in unit conversions if that is important or seems likely.
Only create a new type if it has some practical benfit to your code. Forget the high level rigid philosophies of gurus preaching with fancy sounding big words. Does making a new type eliminate the possibility of certain classes of bugs? Does it make your code more readable and easier to maintain? Does the design decision make your code understandable with fewer comments? Is the overhead of writing and maintaining the code for the new class type justifyable for the perceived benefits?
To be able to answer these questions effectively becomes more of an art than a science. It only comes from experience as a developer.
In general I ignore silly philosophies and try to think about what a change actually does to my code. If it makes my code easier to understand and prevents stupid me from making mistakes, I’ll probably do it.