Hacker News new | ask | show | jobs
by sinofsky 4223 days ago
These are some great quotes. Caveat: I am old. Programming methodology debates wear me out as quickly as language or commenting convention debates :)

I certainly remember many of the quotes when they first came about. I’m a product of the object-oriented wave. At the time I felt I had all the arguments as to why things were so much better with (or so much worse without) OO. In many cases, looking back I realized I was basically arguing for better tools and/or slightly better adherence to a few conventions.

My lessons from going through each “language” transition from debates over “assembler is the only way” through today’s DSLs and more:

1. OO wasn’t good or bad intrinsically. The principles, however, can easily lead to more manageable or coherent code over time. By and large, inheritance, polymorphism, encapsulation, abstraction all form the foundation of large scale systems.

2. Languages can do a great deal of harm, not concepts. Far too often, engineers dive into a new language or paradigm and assume all the code needs to exhibit all the properties of the new religion. In the early days of C++ programming, the saying we had on our projects was “a framework is not a compiler test suite”. I think in all languages, especially today, the risk is that you more harm than benefit when you try to do everything in some fancy new way. Maybe there is a role of operator overloading or templates in C++ but I never really found it. But I am pretty certain nearly every framework employed these techniques. You can’t blame the language because every language has stuff you can abuse. You can blame the zealots or evangelists which often cause the most challenges.

3. Language and paradigm innovation benefits rarely scale in very large systems, but small systems early on exhibit amazing benefits. Most engineers are seduced by new languages and paradigms (OO was just the one that came after structured programming and before functional and others). In the beginning, the new language or approach is amazing. Always amazing. Over time the real world shows up and every new engineer feels that the code is bloated and needs a rewrite when they join a project. Efficiency declines. The magic fades as reality dominates. With more than a few engineers the complexity of interconnection between parts of the code base trumps the simplicity and elegance within one part of the architecture. Expressing those in paradigm or language elegant ways approaches a very high degree of difficulty over time. At one extreme we see competitions of “hello world” or the most basic app all being amazingly simple. At the other extreme we see a constant breakdown in even the most basic methodological approaches. Even “Goto” was hard to do without and certainly in OO maintaining a pure inheritance model, public/private data, or more become as close to impossible.

4. In algorithmic complexity terms, a language or paradigm is at best a constant factor improvement over any other choice. The age-old rule of thumb is that programmer productivity is language independent. While I have no doubt that one could not spin up a new social network in assembler, one would be equally hard pressed to write a device driver or graphics runtime in Ruby or Python. Part of why methodologies gain attention is because the runtimes/libraries/frameworks that come with them do the things that need to be done the way that people want them to work today---that’s what gives the appearance of improved productivity. The right libraries in C can serve a great purpose. We see this when a language gets a new library that seems to bring renewed interest to it.

5. Tools are everything. What makes or breaks a paradigm/language are tools. You can take a simple language or paradigm and have great tools and become much more productive than a “better” paradigm with poor or ill-suited tools. One way that this surfaced over time was with tools that generated the right code—interface builders for example. Then using complex, archaic, or intensely manual approaches lacking formal foundations would become much easier. Plus the bonus of transparency of code generation really helped because other tools could easily integrate (having access to a whole tool system is also more productive than any one methodology+tool).

Ultimately, I think OO is perfectly good and most all modern systems make use of the 4 basic pillars of the paradigm. I don’t think it ever became the answer to code reuse or code quality that proponents claimed. Ultimately the methodology is going to be trumped by scale and age of code and system. Any success means your ability to start over is reduced and so the best bet is to focus on knowing what principles your project is being created and run with.

4 comments

> The principles, however, can easily lead to more manageable or coherent code over time.

This. 1000 times over. This is why well factored code bases can often seem to have lots of redundant classes and interfaces to inexperienced developers. It's all about maintainability over time, building bulkheads around change.

> tools that generated the right code—interface builders for example

The irony is that these tools often fail when the benefit is considered over time. This comes through a lack of tools - their code works badly with SCM (no domain-specific diff tools). They throw away everything we've learnt about software engineering just to win the marketing demo's.

How do redundant classes and interfaces improve maintainability over time?
Because they improve maintainability, they are not redundant.
I asked how redundant classes and interfaces improve maintainability over time.

I'll narrow down my question some more:

How does duplicated code improve maintainability over time?

Author stated that classes and interfaces seem redundant, not actually redundant. Repeated code could improve maintainability because it may actually have different reasons to change, and only look similar on the surface. I've seen this often missed by inexperienced devs who get overzealous trying to DRY up everything.
> Maybe there is a role of operator overloading or templates in C++ but I never really found it.

There is. For templates I'd think the role is obvious, for operator overloading, at least operator-> is indispensable.

Many times though, the operator overloading is just a way to hide what's really going on, making it harder for the programmer to have a good grasp of either what the error conditions might be or why there's an expensive operation going on in a particular statement.
Operator overloading is just a function call. Everything you say about overloading an operator could be said about the function call that would replace the overloaded operator. I've never had trouble recognizing that a use of an operator was being done with a non-primitive type, which is the only way the confusion you describe could possibly happen, and the problem you describe has never happened to me while working on C++ code. I don't know why you think it is even plausible.
Operator overloading is a syntactic sugar trick that hides the real method call. Nothing more, nothing less.

It's useful in vector math, complex number math, matrix math.

Operator overloading is useful for a lot more than just math. Overloading * and -> is useful for pointer-like types. Overloading () is useful for function-like types. Overloading [] is useful for collections and overloading ++ and -- is useful for iterators.
The overloading of (), [], and the like is used but it's not necessary. They could have been other named overloaded functions.
A function call is always a function call, an operator is not. It makes for more opaque code.
Function calls aren't always function calls.
> Tools are everything. What makes or breaks a paradigm/language are tools

I completely agree. In my experience, and it's probably the same for many people, I spend a non-trivial amount of time not writing "primary" code. Instead I'm testing, debugging, dealing with build issues and dependencies, packaging, wrangling deployments etc...

> Maybe there is a role of operator overloading or templates in C++ but I never really found it.

Operator overloading like using + on strings or adding n-space vectors to other n-space vectors is at least convenient, isn't it?

But at the same time, something that could be achieved by something like Haskell typeclasses, e.g. a "concatenatable" typeclass - which then gives you more polymorphism for free than you originally anticipated!
Defining an instance of "concatenable" (eg, Monoid) for some type in Haskell is pretty much isomorphic to overloading some free function (or operator) on some type in C++. It's the same "amount of polymorphism" either way, namely ad-hoc polymorphism.