OOP Criticism
category: general [glöplog]
eh.
you have a perl geek code.
you have a perl geek code.
Quote:
To me the main visible feature of OOP is a very high level of abstraction and inheritance.
Inheritance is overrated and, worse, often overabused. Implement as many interfaces in a class you want, but be careful when using inheritance to reuse code. Things can get very very messy then. I find code reuse by delegation yields usually code that is simpler to maintain.
Sample for silly usage of inheritance from the shop I work in: someone coded a class DateTime, which holds a hh:mm:ss time and a dd.mm.yyyy date. You might argue this alone is an stupid way to store times (which it is, as soon as you have to do arithmetics on them), but it is totally sufficient for what this class is used in the software. He could at least have coded a Date and a Time class and make DateTime contain one of each (then again, probably he'd have coded a Date and a Time class and derived DateTime from Date and from Time, because they DO overuse inheritance where I work). Anyway, here comes the silly thing: DateTime is a value object, but there is also a RealDateTime class, which derives from DateTime. RealDateTime is actually the driver for the hardware's RTC and knows how to retrieve the time from the RTC and how to set it. See? It's a frigging RTCDriver, not a DateTime, yet it derives from DateTime. This partial case isn't too bad because there are just these two classes involved but...
...this software uses a proprietary RTOS kernel. On top of it an abstraction layer is bolted on which might (or might not, nobody ever tried) make it simple to replace the underlying kernel. That abstraction layer implements some kind of event driven model (very much like WndProcs) using the OS kernel's event queues, with the difference that every object that wants can have its own message pump and there's great control available what should be received by that message pump and what shouldn't, which among other things somewhat reduces switch-case-fuckoff-and-die orgies. Every class that wants to have such a msg pump derives from a MagicBaseClass and implements its Main() method which receives the events. Now there are parts in the software with very deep class hierarchy where MagicBaseClass is at the top. Events enter some Main() deep town, a class handles some of the events it is interested in, some it discards, and some it passes up to its base class' Main() which handles some of the events, some it discards, and some it passes up to its base class' Main() which...people are highly reluctant to touch that code, and those who aren't get yelled at by those who are (fortunately).
Uarrghghgh. And I could rant on and on. Mind you, I'm not criticising OOP itself in any way.
I guess it just boils down to: A fool with a tool is still a fool.
Or put it differently: a lot of sillyness you find in code is not the language's fault, but the programmers. I keep telling people they don't have to allocate that ~69 bytes large object they use temporarily in a function on the heap, because they have like a whopping 16KB of stack and their call graph isn't deep, yet they keep doing so.
As soon as their function gets branches which contain return statements (nothing wrong with that alone), they either produce memory leaks or very messy memory management code. You would be tempted to tell people to use at least auto_ptrs if they insist in allocating on the heap but
a) You don't necessarily want certain people to use auto_ptrs neither
b) You get the argument "This is STL, this is big, bloated, slow". Showing them a comparison between auto_ptr's implementation and their handwritten memory management code is then usually pointless.
Personally I give up at this point and just make sure my own code works and is maintainable.
As soon as their function gets branches which contain return statements (nothing wrong with that alone), they either produce memory leaks or very messy memory management code. You would be tempted to tell people to use at least auto_ptrs if they insist in allocating on the heap but
a) You don't necessarily want certain people to use auto_ptrs neither
b) You get the argument "This is STL, this is big, bloated, slow". Showing them a comparison between auto_ptr's implementation and their handwritten memory management code is then usually pointless.
Personally I give up at this point and just make sure my own code works and is maintainable.
killer, make them use smart pointers ...
"smart pointers" is a bit imprecise.
I guess you're mainly talking about auto_ptr ?
(After having overdosed on it, I hate shared_ptr with a passion)
I guess you're mainly talking about auto_ptr ?
(After having overdosed on it, I hate shared_ptr with a passion)
smart pointer is a loose general term.
To me, with regard to C++ anything that looks like a built-in pointer but contains additional logic is a smart pointer, so to me std::auto_ptr is a smart pointer, and so is boost::shared_ptr (think duck typing:). Using that definition you could even go and call container iterators smart pointers, and in a way they are.
The problem with auto_ptr is: where people don't (or refuse to) understand its transmission of ownership mechanics it simply replaces leaks and double frees by null pointer dereferencing. If these are detected by an MMU this might be a small improvement, though :D
I thought about integrating shared_ptr before. It should be simple enough that our stone age compiler we're forced to use might be able to compile it. But the real deal is not to start using smart pointers all over the place but to use RAII techniques, i.e. allocate memory on the heap if you really must but do so in constructors and free it in destructors. Then put these classes onto the stack, godamit.
To me, with regard to C++ anything that looks like a built-in pointer but contains additional logic is a smart pointer, so to me std::auto_ptr is a smart pointer, and so is boost::shared_ptr (think duck typing:). Using that definition you could even go and call container iterators smart pointers, and in a way they are.
The problem with auto_ptr is: where people don't (or refuse to) understand its transmission of ownership mechanics it simply replaces leaks and double frees by null pointer dereferencing. If these are detected by an MMU this might be a small improvement, though :D
I thought about integrating shared_ptr before. It should be simple enough that our stone age compiler we're forced to use might be able to compile it. But the real deal is not to start using smart pointers all over the place but to use RAII techniques, i.e. allocate memory on the heap if you really must but do so in constructors and free it in destructors. Then put these classes onto the stack, godamit.
killer: That coder in your shop isn't overusing inheritance, he's just failing to understand is-a/has-a relationships. Smart pointers aren't the answer, at least. :P
Customising new and delete only gives you control over how memory is allocated and freed, not when it happens. Using tailored allocation strategies can give you huge performance benefits over "just using the heap". You can do the same using placement new, but by overriding new and delete in the class definition you make it transparent to the rest of the application.
// Allocate x "somewhere on heap" because it doesn't matter much for this class. It's a single-instance sort of thing.
A *x = new A();
// Allocate y in a pre-allocated array of B objects. This type of allocation is appropriate for B objects for cache and fragmentation reasons.
B *y = new B();
// Allocate z on top of a preconstructed or previously deleted object. This is appropriate because objects of this particular class need fast construction.
C *z = new C();
And it's compatible with using smart pointers, which I do as necessary. I don't consider smart pointers necessary for a single instance of a class whose only function is to redirect and log debug messages. It would just falsely imply that the Debug object is somehow a dynamic entity. Its destruction can be guaranteed in various other ways, regardless. E.g.
class Application {
public:
Application() { initGlobals(); }
~Application() { deinitGlobalsAndMakeSureAllBuffersAreFlushedEtc(); }
void run() { ... consider this the actual entry point ... }
};
void main() {
Application a; // destructor called by stack unwind or end of main()
a.run();
}
Besides I was only trying to illustrate a style of commenting. ;)
Quote:
doom, you should avoid using direct memory allocations (i.e. new, malloc, calloc, etc.)
Customising new and delete only gives you control over how memory is allocated and freed, not when it happens. Using tailored allocation strategies can give you huge performance benefits over "just using the heap". You can do the same using placement new, but by overriding new and delete in the class definition you make it transparent to the rest of the application.
// Allocate x "somewhere on heap" because it doesn't matter much for this class. It's a single-instance sort of thing.
A *x = new A();
// Allocate y in a pre-allocated array of B objects. This type of allocation is appropriate for B objects for cache and fragmentation reasons.
B *y = new B();
// Allocate z on top of a preconstructed or previously deleted object. This is appropriate because objects of this particular class need fast construction.
C *z = new C();
And it's compatible with using smart pointers, which I do as necessary. I don't consider smart pointers necessary for a single instance of a class whose only function is to redirect and log debug messages. It would just falsely imply that the Debug object is somehow a dynamic entity. Its destruction can be guaranteed in various other ways, regardless. E.g.
class Application {
public:
Application() { initGlobals(); }
~Application() { deinitGlobalsAndMakeSureAllBuffersAreFlushedEtc(); }
void run() { ... consider this the actual entry point ... }
};
void main() {
Application a; // destructor called by stack unwind or end of main()
a.run();
}
Besides I was only trying to illustrate a style of commenting. ;)
Developers! Developers! Developers!
216: :D
Quote:
killer: That coder in your shop isn't overusing inheritance, he's just failing to understand is-a/has-a relationships.
Sure he doesn't understand is-a/has-a. Still these people are (see next paragraph) building huge hierarchy trees with functions calling up (And occasionally down - using a well-placed downcast! Ouch!) the class hierarchy when all they really should have coded was a bunch of classes that implement a single freaking interface (i.e. they should have flattened that whole mess of classes).
Quote:
Smart pointers aren't the answer, at least. :P
You read the last few posts a little bit too fast, didn't you?
The bit where the smart pointers came in wasn't about overuse of inheritance anymore, more about people in general not knowing what they do. In the case of but-i-have-to-allocate-that-on-the-heap-because-i-used-raw-arrays-for-years (and, to be fair here, our crap compiler/library doesn't have a working std::vector), smart pointers definitely may help at least in locally decluttering code. But it's really a two-edged sword. Anything you might come up with is really just another power tool in the hand of the next power fool coming along.
I could tell stories of people going "What? malloc() doesn't call my class' constructor?", but it starts to bore me. If these people were electrical engineers, nobody right in their mind would let them do circuit design, but in software development they employ anybody who states in his CV he knows "C/C++ and Java". That's the fucking problem when you get down to it. Software sucks because the people who wrote it sucked, and very badly.
Sometimes I wish I'd chosen a serious profession, like gardener or smth :D
Quote:
Sometimes I wish I'd chosen a serious profession, like gardener or smth :D
What stops you?
Quote:
If these people were electrical engineers, nobody right in their mind would let them do circuit design, but in software development they employ anybody who states in his CV he knows "C/C++ and Java".
That's right.
chopping down trees for a living is quite manly too
Quote:
You read the last few posts a little bit too fast, didn't you?
I often just ignore what people actually say and go on to comment on what I want them to have said. It's charming in a way. ;P
Quote:
The bit where the smart pointers came in wasn't about overuse of inheritance
That wasn't really directed at you. It was more to confirm what you're saying, that the underlying problem isn't programmers choosing the wrong design pattern or whatever, that that's just one of many symptoms of some programmers being immensely thick.
Smart pointers, like most things, are good for what they're good for. Granted they're good for more than most things (and reading up a bit, probably more than I was aware of, e.g. looks like my Debug object above would be more clearly referenced by a const auto_ptr), but that doesn't make them an automatic solution to anything. Unless he understands why he needs an auto_ptr or a shared_ptr or whatever, a clueless programmer would just try to work around these seemingly arbitrary limitations that have suddenly been imposed. I guess what I'm saying is it's hopeless to try to force other programmers into your design patterns, no matter how much better they are, if they don't understand why your way is better.
So anyway, funny you should mention electrical engineers, because you're right, the kind of incompetence programmers are allowed to exhibit wouldn't be tolerated in other industries, like engineering. But the strange irony is that engineers write the worst code of all (what you find on embedded devices for instance).
Maybe it's just that programming is much harder than anything else. ;) Be optimistic, though. Look at the standard for quality of code today, 10 years ago, and 20 years ago. There's a clearly positive trend.
Louigi: Bjarne Stroustrup (creator of C++) has a very good way to think of a class. He considers it to be a "user-defined type". In other words, you have int, float, char, etc... but what if you want more types than that? You can't wait around for a language that happens to have the types that are relevant for your program. So they gave you the ability to make your own types. Just think of how much easier it is to write a text-heavy program, such as a web page, when you have a "string" type. Container types similarly make things much easier, if they happen to be relevant to what you're doing. Once you get used to the concept, you can make even more specific types that happen to fit your particular program perfectly, but which would probably not be of much use elsewhere.
Stroustrup wrote an essay called "What is Object Oriented Programming", available from a torrent site near you. If you read it, maybe you'll understand a bit more about what it is that you are actually criticizing.
Also, I should point out that "templates" have nothing at all to do with OOP. Templates are an artifact of strongly-typed languages. You can have template functions (which are very useful) and template structures. PHP is an example of an object oriented language that has no templates (nor any need for templates).
Stroustrup wrote an essay called "What is Object Oriented Programming", available from a torrent site near you. If you read it, maybe you'll understand a bit more about what it is that you are actually criticizing.
Also, I should point out that "templates" have nothing at all to do with OOP. Templates are an artifact of strongly-typed languages. You can have template functions (which are very useful) and template structures. PHP is an example of an object oriented language that has no templates (nor any need for templates).
Yesso: don't waste bandwidth on torrent when it's available on BS's own page: http://www.research.att.com/~bs/whatis.pdf
Quote:
[...] He considers it to be a "user-defined type". In other words, you have int, float, char, etc... but what if you want more types than that? You can't wait around for a language that happens to have the types that are relevant for your program. So they gave you the ability to make your own types [...]
Did you heard about algebraic data types?
(bad english notwithstanding)
I keep bumping my head at work with people who think first about building cathedrals of type structures (without much documentation or justifications about it) without them actually writing down the algorithm they want to achieve with them.
You know, nothing fancy.
Just being able to clearly tell what kind of input data there is, what operation we should be performing and what output it should produce. As far as I'm concerned, the types are a product of that understanding.
It seems other people starts with classes, as if there was a way to create them completely out of thin air, and proceed to write the algorithms afterwards on top of that.
Recommended reading:
http://okmij.org/ftp/Computation/Subtyping/
You know, nothing fancy.
Just being able to clearly tell what kind of input data there is, what operation we should be performing and what output it should produce. As far as I'm concerned, the types are a product of that understanding.
It seems other people starts with classes, as if there was a way to create them completely out of thin air, and proceed to write the algorithms afterwards on top of that.
Recommended reading:
http://okmij.org/ftp/Computation/Subtyping/
Quote:
Those were written in OOP, lots and lots of code and I had a hard time changing something without screwing something up. To make a small-small change you would have to dig up like 5 or 6 separate classes and study what they do and how, then finally implement the change and then check if that change broke something.
best case of nonfunctional and hardcoupled bunch of god object...
that's not oop. it's spaghetti code with classes.
optimizations tend to be a subset of spaghetti code. unfortunately.
if you don't know how compilers work, yes.
"smart pointers" is the generalization of the derivates you described. those could be scoped pointers, shared pointer (bad), etc.
See: http://www.boost.org/doc/libs/1_40_0/libs/smart_ptr/smart_ptr.htm
Smart pointers aren't the cure alone for a badly written program ..
See: http://www.boost.org/doc/libs/1_40_0/libs/smart_ptr/smart_ptr.htm
Smart pointers aren't the cure alone for a badly written program ..
Quote:
If these people were electrical engineers, nobody right in their mind would let them do circuit design, but in software development they employ anybody who states in his CV he knows "C/C++ and Java"
plus, your customers do the rest by believing, they can be part of the process because "someone in my team once has been doing some excel vba scripts, so developing software can't be that hard and >I< want to tell you how to do it"
that's the big problem with software. I for myself wouldn't try to tell my favourite car vendor how to build the car I wanna buy simply because I have no fucking clue how to build a car, even if I might have been changing tires and oil myself.
killer:
Perhaps. But if it is a tool that 90% of coders misuse, then why popularize it so much?
yesso:
Thanks for the reference - I will definitely read it. And, just to note once more, I myself am not criticizing, the site that I linked does. I just find that criticism interesting. I haven't exactly made my opinion yet.
Quote:
Or put it differently: a lot of sillyness you find in code is not the language's fault, but the programmers.
Perhaps. But if it is a tool that 90% of coders misuse, then why popularize it so much?
yesso:
Quote:
Stroustrup wrote an essay called "What is Object Oriented Programming", available from a torrent site near you. If you read it, maybe you'll understand a bit more about what it is that you are actually criticizing.
Thanks for the reference - I will definitely read it. And, just to note once more, I myself am not criticizing, the site that I linked does. I just find that criticism interesting. I haven't exactly made my opinion yet.
Quote:
Because any given programming tool/paradigm will be misused by at least 90% of the programmers.Perhaps. But if it is a tool that 90% of coders misuse, then why popularize it so much?