Of course this is a false dichotomy, but one I encounter quite a lot. It usually comes from younger inexperienced coders, or procedurally-minded coders who never took the time to understand OOP.
The false assumption is that "good code" takes longer to write than "good software." In reality "good code" only takes longer if you don't know what you're doing. If you haven't internalized good OOP, and you haven't applied good OOP principles enough to be efficient and judicious in your application of those principles, then yes, you'll probably do more harm than good, and you'll take longer to get there.
If that's the experience level your team has, then you're faced with two bad options: 1) write a lot of duplicated, procedural-style code that you'll despise in 3 months and be begging the Software Gods for 6 weeks of clear time just to clean that crap up; or 2) attempt to design some nice DRY-ed up, SRP classes with all the right GOF patterns applied, more than likely get it wrong and really end up in the same place as 1).
But if you understand and have experience (there's the rub) with good design (see: POEAA, GOF, Clean Code, Effective Java, etc.) then there's no choice to make, because it's much faster to write clean, well-architected code. It's must faster now, it will be much faster later.
Of course there will always be that guy on the team that doesn't think in OOP and still writes 600 line methods and glazes over if there's interfaces or abstractions involved. That guy is just as bad as Hey Look At My Handy Dandy Patterns Guy.
The solution to over-architecture is not bad architecture. It's understanding architecture and developing experience with it. While I have encountered OP's situation often, even more often I've encountered the consequences of haphazard design for those very same projects that "shipped fast": two guys in a back room refactoring for six weeks so that -- please please dear God -- we can get our bug rates down and start shipping features "like we used to."
As a younger programmer, I've come to the conclusion that good code shares good interfaces, decoupled relationships, appropriate abstractions, and clear responsibilities regardless of methodology or paradigm used, so that your code is easier to implement, change, and maintain. After all, the end result is less complexity, and you can stub out some interfaces and get coding fairly quickly.
However, if you lack experience with certain kinds of problems, the initial investment of designing good code requires quite a bit of thought and care to do right, otherwise there's the risk of introducing lots of unnecessary complexity by adding lots of unnecessary classes and relationships.
And that's exactly the problem Alex is addressing in his blog post, because his fellow programmers don't share those same principles of good code. As a result, they get caught up in making large, cluttered interfaces with lots of interwoven objects that have shared responsibilities, and then they call that good Object Oriented code (because there's lots of objects, right?). They might even apply as many acronym riddled patterns as they can, because that's the way some really smart guys who wrote some books suggested, and they want to do things the right way.
In other words, there's a cargo cult of OOP, and it loves complexity.
In contrast to that, it sounds like what Alex does when he come across something where the architecture necessary isn't very clear is essentially what I do: carefully write code from the bottom up and refactor it as I go, if at all. That way, the relationships inherent in the code become clear, and you can create the minimal number of classes and relationships necessary to decouple those relationships.
And so what he's suggesting is that other programmers do the same. Don't waste time trying to develop a solid architecture from the top down when it isn't immediately obvious and start working from the bottom up instead.
Of course, in ten years, he'll probably be writing most of his software from the top down, finally capitalizing off all those years of hard earned experience writing code from the bottom up, but that'll be then, not now.
Experience with OOP is certainly essentially, but mostly for the reason that the OOP language you're using sucks. Most of the "handy dandy patterns" that have sprung up are to recover properties that should have come for free, but didn't. As a result, code isn't objectively good or bad -- you must consider how it will be used. All of the boilerplate crap you have to write has a flat cost and a benefit proportional to how things scale in certain ways. The author of this piece is advocating "don't write any boilerplate crap at all", which we agree isn't the right answer. But experience or no, you'll get the design wrong too, because you can't predict how your requirements will change. In summary, repeating my earlier point, your OOP language sucks.
Let's take C#'s advancements over Java as an example. The LINQ features are clearly imported from the functional and/or dynamic world as replacements for iterator patterns. C#'s closures make Strategy and Command patterns basically irrelevant. Events and Delegates replace wiring up an Observer pattern.
A meta-concern, if you will, is that general OOP principles don't apply in the same way in dynamic environments the way they do in statically typed environments. When I'm in Ruby I'm not nearly as concerned with coupling and some of the larger architectural concerns the way I am in Java/.NET. Because the language is dynamic, to some degree everything is an interface, and there's no need for complicated frameworks like IOC, etc.
There's also no need for all the overhead of learning and managing generics/parameterized types. All those angle brackets and abstractions just to support what amounts to a more robust compile-time unit test. Makes me tired just thinkin about it.
Java is Java and I don't know if I would say it "sucks." I do know between it and Ruby/Rails which one makes me about four times more productive.
Have you found your productivity level stays the same as a project matures? My experience has been Ruby or Python allow me to ramp up quickly, which is great, because quick wins help boost morale early on. But after 6 months or so, I found productivity roughly evened out on several projects. Right now I'm fighting with a lot of things in Ruby & Rails that I know would be trivial in Java and on the JVM. FWIW, I'm trying to cut over to JRuby to see if I can somehow benefit from the best of both worlds.
I'd have to say yes, once I got comfortable with good Ruby practices I found it was as easy if not easier to keep the code clean as projects mature.
Some of the code-browsing and refactoring tools aren't as smooth, but they're also not as necessary.
I suspect you're dealing with concurrency issues? In that case, yes the JVM is brilliant at that and JRuby might be a great option for you. Ruby concurrency is coming along though. 1.9 is no longer green threads, and Rubinius has gotten rid of the GIL altogether.
Most of the issues I've run into are environment related. I can make 1.9.2 segfault with ease, but there's mounting pressure against supporting REE. Libraries seem to rarely support backwards-compatibility, so I spend an inordinate amount of time backporting fixes or trying to upgrade a large dependency graph. And as you noted, concurrency is problematic.
So, when looking at productivity, I'm not necessarily looking at the language itself, but the entire environment I need to work in. I may just be doing something grossly wrong, which is why I inquired.
If it helps frame things at all, my primary project is coming up on 2 years old and started as a Rails 2.3 app that recently had a very painful upgrade to 3.1 (took on the order of 75 hours). And I had a couple years experience prior to that doing Ruby & Rails stuff.
Ah, gotcha. Yeah, config issues can be painful in many environments, and Rails has its own brand of frustrations, particularly when upgrades are concerned.
This is just a shot in the dark, but if you're on REE/Passenger and getting thread/segfault issues, you might try Apache's Passenger config with the conservative spawn method. I've had similar issues in the past resolved by simply not using REE's shared memory.
Thanks. I had to do that recently because of issues with the prepared statement pool in 3.1 + Passenger. But I can segfault in 1.9.2 just by loading a console or running tests. It doesn't instill a grand sense of stability.
Thanks for the feedback though. I find it helpful to do periodic sanity checks.
But I'll also say that crappy OOP (CROOP?) is a language-independent concern. Those guys who I talked about taking six weeks to refactor? That actually happened and they were on a Rails project.
One problem with OOP is that it's highly dependent on getting it right. If your design is correct, that's great, but otherwise you end up in increasingly hot water. The problem I have with OOP for new projects is that it's hard to see the correct design until you have enough mileage.
Bad design is usually inexperienced design. 99% of the design problems you'll encounter have been encountered before and are probably cataloged. If you have doubts, read up, and bring in other experienced coders.
In fact, if you don't have doubts, you're probably in trouble and need to bring in other coders. If you're not using UML or at least sketching class relationships out in some visual form, you're likely to get it wrong.
Good OOP is hard, and requires experience and at lot of reading and concentration to get right. And even then, it usually requires a good bit of collaboration to get right, even for very experienced architects. But once a good clear design has been identified, it's much faster to code and much much easier to maintain.
I heard that when they wrote the book they basically mailed a whole bunch of people in the field to find out all the patterns people were using and they weren't able to find more then 23. After getting 23 every other pattern they would hear about would be just an iteration of one of the ones they had.
Full Disclosure: I haven't actually read the book yet. But I'm planning to...
It impressed me 4 years ago, now I honestly think it's a relic of a bygone era. Language advances, different API design and dynamic programming and anonymous functions have got rid of a lot of the problems that you actually had to do these shitty patterns for.
UML also sucks and is dead, again, not sure why anyone would bother with it.
When is the last time you saw a UML article or GoF article on HN?
Wake up, they're both dead concepts. I'm not even sure why patterns have died, they just have. Probably because people just program that way now anyway. Yes, they were useful, but they're not needed any more. People don't write code like that any more because they don't really have to.
Maybe it's that patterns are so common place we take for granted that someone had to name them.
I guarantee you've used Proxy, Observer, Factory, Abstract Factory, Facade, Bridge or some approximation of one of these if you've coded more than 100 lines of Java in the last year.
If I say "ActiveRecord" or "DataMapper" you probably think Ruby on Rails, not Chapter 10 of Patterns of Enterprise Application Architecture. If I say "Factory" you're probably not thinking Chapter 3 of GOF. When you think about node.js or EventMachine, do you think about the definition of the Reactor Pattern in POSA Volume 2?
There is a fundamental problem with Deign Patterns: the knowledge and effort required to apply them appropriately vastly exceeds the knowledge necessary to effortlessly re-create them on the spot.
if you've coded more than 100 lines of Java in the last year.
That's just it. If you're a regular here, you're less likely than the average programmer to have done that, and it's probably not really the code you want to talk about if you have. Java isn't especially popular for personal projects, startups or most other things people want to talk about or show off here.
Given some combination of first-class functions, various metaprogramming capabilities, dynamic typing and paradigms other than class-based OO, many of these patters disappear.
> If I say "Factory" you're probably not thinking Chapter 3 of GOF
Honest question, what does this pattern accomplish? I'm asking this because I just saw a bunch of Factory-like classes in some PHP code I was trying to port to Python, and for the life of me I couldn't understand why the original programmer had made it so complicated and convoluted, when he could have done it all in 30 lines of code.
And a second question for whoever might have the free time to answer it: does anybody actively use inheritance (or, why not, multiple inheritance) on a daily basis and in the same time do they feel like it helps them? (as in: does the size of their code base gets significantly smaller? does the code fits better in one's head? things like that).
Factory classes make less sense in a language with first class functions, but Factory functions are handy sometimes. As you can see in this example of a Factory function in Python from the standard library[1], it can be useful to return different subclasses depending on the arguments, but it doesn't have to be heavyweight like in Java.
A Factory is used to create an object of a specific type, when the calling code only has a reference to a supertype or prefereably an abstract type of that object. This reduces coupling and therefore side-effects in your code.
For instance:
Car car = CarFactory.newCar(someLocalContext);
Might return a specific type of car for the given context, but the calling code's coupling is only to the supertype Car, and therefore can operate in the same way on any kind of Car.
Since php is dynamically typed there's not as much reason to use this pattern, although in certain cases it might be the right choice.
The Factories in question might actually be more like Builders -- classes used to hide complex construction processes.
I use inheritance all the time in Ruby and Java these days. With Ruby you get Modules and the Mixin approach, which allows for what is essentially multiple inheritance. I try to keep the level of inheritance close to 1 (I can't remember the last time I went past that) but yes, it's an essential tool in the toolkit.
I'm apparently one of the last coders on earth that still uses UML, but I find it helps me clarify architectures and visualize my code. OOP was meant to be visual in nature -- object relationships are, imho, best understood visually rather than through linear code. My suggestion would be to get used to UML or some hacked derivative thereof and express your dependencies visually, and you may find inheritance starts to make more sense.
Perhaps it's just a difference in styles, I avoid inheritance like the plague. To me something has to be really, really special to inherit off another class that I've written.
And even then I'll come back to it the next day and see if I can get rid of it or it really does still make sense.
Sure, not only the GOF, but 5 volumes of POSA, Martin Fowler's POEAA, and reams of digested pattern books like Head First Design Patterns, etc. There's dozens.
If you do not see correct design - you do not have enough information to start project anyway. Usually, this happens when modeled business process is a mess.
Nice discussion. I agree whole-heartedly, when it comes to full systems and programs. As in the article, when it comes to a simple plug-in to a system however, rewriting an entirely new version of an API just for the plugin was simply unnecessary.
If your API is not flexible enough and require to write ad-hoc code - it is broken and require refactoring. I know this is not lines up with "customer want it now and do not care", but code do not magically become less broken because of it.
Agreed, YAGNI is just as important as SRP. That's good design too. But hopefully if you do eventually need an API, your design was good enough to allow easy interface extraction, etc.
OOP isn't a panacea for bad design. It is a single design strategy that works well for some problems. We can descend deeply into the OOP way of viewing the world . Then we can construct really complicated, epicycle[0] like efforts to model everything under the sun using OOP's ontological system, or we can recognize it as one useful ontology amongst many and use it when it's appropriate. All of the "OOP is the one true way" literature ends up making some good and valid points.
Their main effective point can be summarized sarcastically as
"Here is how to implement Greenspun's 10th Rule in a way that is allegedly more maintainable, and we can say is object oriented. Buy more books, and also we have some modelling tools you can spend time using instead of writing code that does something."
Circles are cool, I agree. Sometimes things are elliptical though, and it's much better to recognize that and use ellipses to model them then to cleave to the circle industry's way of doing business.
In summation, this, and most industry(software or otherwise) gripes can be best understood by meditating on this message from beyond the grave from Christopher Latore Wallace Smith[\\]
I really think the author of the article has no clue about what "good code" is. I could start a whole rant about what's wrong with the post - but davesims summarizes it pretty good.
completely agree.. it takes experience to be able to quickly write code that is also good, but its a worthwhile effort. The hype of trying to start a startup before having really mastered the art of writing code leads to some buggy products later on
Thanks for this comment. I'm glad that some people understand OOP. It gets really anoying reading the whining Of developers that don't realize that it is them that suck, not really the patterns, language, or whatever they are attacking. Ruby programmers use to do this a lot.
The false assumption is that "good code" takes longer to write than "good software." In reality "good code" only takes longer if you don't know what you're doing. If you haven't internalized good OOP, and you haven't applied good OOP principles enough to be efficient and judicious in your application of those principles, then yes, you'll probably do more harm than good, and you'll take longer to get there.
If that's the experience level your team has, then you're faced with two bad options: 1) write a lot of duplicated, procedural-style code that you'll despise in 3 months and be begging the Software Gods for 6 weeks of clear time just to clean that crap up; or 2) attempt to design some nice DRY-ed up, SRP classes with all the right GOF patterns applied, more than likely get it wrong and really end up in the same place as 1).
But if you understand and have experience (there's the rub) with good design (see: POEAA, GOF, Clean Code, Effective Java, etc.) then there's no choice to make, because it's much faster to write clean, well-architected code. It's must faster now, it will be much faster later.
Of course there will always be that guy on the team that doesn't think in OOP and still writes 600 line methods and glazes over if there's interfaces or abstractions involved. That guy is just as bad as Hey Look At My Handy Dandy Patterns Guy.
The solution to over-architecture is not bad architecture. It's understanding architecture and developing experience with it. While I have encountered OP's situation often, even more often I've encountered the consequences of haphazard design for those very same projects that "shipped fast": two guys in a back room refactoring for six weeks so that -- please please dear God -- we can get our bug rates down and start shipping features "like we used to."