Winning is the worst thing that can happen in Vegas
37signals.com
37signals.com
Not a single one suffered from over-architecture; indeed, the common thread to all of the problems I saw is that people would intentionally put off the "just in time" architecture improvements that DHH recommends in favor of just one more feature.
By the time we got there, the whole thing was about to collapse under its own weight.
And that's exactly my point: this seems to be a solution in search for a problem. Under-architecture is much, much more widespread of a problem than over-architecture, based on my own anecdotal experience and the horror stories told by my friends over beers.
Software engineering is about striking a balance between choosing an architecture that can grow with your project while being simple enough for you to ship features today. The dogmatic approach from both sides in this ideological war is bizarre. Remember that prescriptivism against prescriptivism is still prescriptivism.
However, I've also worked on the opposite end of the spectrum, lots of turbo nerds who were big on planning for every eventuality (and WERE the types to read 37s etc) and that suffered heavily from exactly what he describes here.
Know your audience!
Exactly and the balance is not an easy thing to do.
YAGNI and KISS principle are often misunderstood and used as a bad excuse to ship crappy code, but used correctly these principles contribute to a good architecture.
My experience is exactly the opposite. Every time someone's told me, "A user's account will only ever have one event associated with it", or "An order will only be paid for with one credit card" or "A credit card payment will only ever apply to one order", that someone is wrong, and it ends up costing us days to fix.
First Ryan's post says he's saying something similar, except it appears to be opposite to the post he's replying to.
Then you say you can't see the times where YAGNI helped you, but that's neither true literally and also in spirt. Literally because it wouldn't be YAGNI if you did actually need it. But more importantly I have often discovered some architectural choice or unused feature stubbed out from years prior that saved me a ton of time in the here and now.
http://news.ycombinator.com/item?id=5003341
My experience is similar to pavel_lishin's, and our experiences are contrary to the author of the article.
I hope that helps to clarify the first bit, anyway.
> "An order will only be paid for with one credit card"
Perhaps there will be a request to allow payment in multiple parts for orders, so adding the generality from the start saves time. But perhaps instead the spec change is to allow payment of multiple orders at a time. Or partial payments of multiple orders. Or payment in foreign currencies and bitcoin. Or not paying orders at all, but marking them "completed" for accounting at the end of the year.
Altogether, it's not possible to correctly predict the future feature set of the project to decide on the correct places to add abstractions, unless you are really great at product design and can read the future minds of whoever is coming up with the requirements.
A somewhat unspoken but generally understood part of this philosophy is that while you don't go out of your way to make things super flexible, you also make sure you don't code yourself into a corner that will be hard to get out of should the requirements change.
Knowing how to balance this sort of thing well takes years and years of experience (IME) but it certainly isn't impossible.
As a side note, something taking "days to fix" is no big deal considering a big part of the desire to not over-architect is to avoid situations where a fundamental and ultimately bad assumption results in a situation that is going to take months to fix with the result being a mostly new and untested code base and all the pain that comes with that.
Aside from being an expert in abstraction, domain knowledge is also a key component to make these decisions. You're right that it takes years to become competent at it, and even then there's a good amount of luck involved.
Should you write the function as
account.credit_100_dollars()
account.credit_dollars(100)
account.process_transaction(new credit_transaction(100))
I have to say I prefer option 3 to option 1 even considering YAGNI. It's better OO, it's more testable, there's more potential for reuse in other parts of your application etc.Interestingly, when I started as a developer I might have written something closer to the first option. After a few years I would have gone for something closer to the third option. Nowadays, I'd probably go for the second option as I've hopefully learnt to strike a balance in the abstractions I create.
previous = account.balance
credit_amount = rand 1000
account.credit_dollars credit_amount
assert_equal previous + credit_amount, account.balance
And more importantly, how is option 3 a better idea for reuse?It's more testable because we can test the concept of a transaction rather than the net result of incrementing the account balance - e.g. test for a business rule to ensure that we can never have a transaction with a negative credit.
It promotes single responsibility because logic pertaining to the transaction can be pushed back onto the transaction object where it is defined once and where it more naturally sits - e.g. validate the transaction or render yourself for printing.
It promotes reuse because the transaction object could possibly be used in another context far removed from this particular scenario - e.g. any logic for validating or rendering the object would sit within the account object otherwise. It makes sense to bring it out where we could potentially reuse it later.
This is just object orientation 101, and it preaches the flexibility that the OP is against. Good OO code is code that is flexible and contains a certain degree of abstractions by design.
Option three would be suitable for the implementation of option two, I'll give you that, but if I'm designing an admin control panel I want to bind a control to credit an account, not to create a transaction to credit something and have an account process that transaction.
This would allow you, for example, to track and handle all transactions, including crediting transactions, using the same code base, with special transaction-type-specific issues dealt with by the transaction-type object.
account.award_loyalty_bonus()
account.award_welcome_bonus()
This is closer to the first option, but will actually model the behaviour of your application using domain terms.account.process_transaction(new loyalty_bonus(bonus_amount))
or possibly
account.process_transaction(new bonus(loyalty_bonus_amount))
depending on the business logic.
The value of the bonus then gets captured in the domain object. If there is enough related logic to justify having a loyalty bonus object, then you might have
account.award(LoyaltyBonus.new)
Or, if we're going to start slicing things up like this, we might go for: LoyaltyBonus.award_to(account)Casinos in vegas have no idea if you're deep into a gambling session or not. They take small statistical edge on every transaction (or a rake).
For example, your probability of winning when betting on black is higher than betting on 12 (playing roulette). But you EV is still the same (or close).
I think roulette is an exception in that it lets you bet with fairly low odds of winning. Most other games (BJ, Slots, etc..) keep the odds of winning something closer to 50-50 on each transaction.
Yes and no. The slot machine has no idea. But the casino certainly does. They do stuff like track you with membership cards, detect when you're frustrated and about to leave, and "helpfully" show up to offer you a free meal so you'll stay.
It's diabolical, really.
http://www.npr.org/blogs/money/2011/11/15/142366953/the-tues...
Note that I am, by far, not! against the creation of library and new framework. It just so happens that this article is ironically funny concerning that aspect.
You're still likely to lose in the long game, but get small wins frequently enough to keep you from quitting.
Based on this, I personally design object models to be easily extensible. The guidelines are simply best practices (separation of concerns, methods which only do one thing, minimising side effects as much as possible). Nonetheless, I agree that the minimum should be used given that things which should be abstracted have been abstracted.
For example, I was implementing reports recently. Overengineering is coming up with a complex reporting engine which managers can use to create anything they want. Underengineering is writing each report from scratch. The golden middle, in this case, was a lightweight framework where you extend a base class, specify the query and you're done. Everything else is automagically taken care of. So one need not worry about fetching the actual results, displaying them, sorting them, adding the report to the menu, etc.
I'm not sure I follow how that equates to technical debt though. If anything, unnecessary but well-implemented abstractions and over-architecture are the opposite of technical debt, aren't they?
In my experience, it's all too easy to find developers who will "say no" too eagerly, erring on the side of sloppy copy/paste duplication, inconsistent structure, and poor maintainability overall. Given the choice, I'd much rather walk into more projects with too much architecture than the one with little-to-none which are more common.
I've found that when I deliberate over these kinds of questions too much it's usually because my design is not quite right, and that when I eventually discover an elegant design the path to flexibility presents itself in an optional and unobtrusive way.
Many designs are possible but the right design is often the one that is easy to think about at various levels of complexity.
Still I respect his opinionatedness!
However, the insights into premature abstraction are good.
Here's a beautiful, and relevant, essay from Steve Yegge: http://steve-yegge.blogspot.com/2008/08/business-requirement...
He's comparing gambling (e.g. risk-seeking) with software development practices that are more often than not an incompetent expression of risk-aversion.
It seems to me it isn't saying that casinos are like software development, but pointing out human psycology using the casino as an example.
The same reward system is there because the psycology (human) is the same.
TLDR; If I compared the performance of the same car on a road to a dirt track, I'm making a comparison, but not a metaphorical one.
My company does Machine Learning for Sales. There aren't a huge number of people who can do both programming and statistics well. Add Sales to that skillset and there are, what, 5 people in the world who can do all 3? The odds of finding a sales person who wants a predictive sales tool and has the capability and interest to write it are vanishingly small.
For a consumer-oriented example look at Coursera or other MOOCs. Andrew Ng is not a Coursera user (i.e. student.) He understands students, and that's enough-MOOCs provides enormous value because they solve an important problem. Students would have had a much harder time creating Coursera than Ng.
I as well. Most software sucks because the people building it aren't motivated. Building for oneself is one source of motivation, but it's myopic to assume that it's the only source of motivation that works.