11 ms·
Something this article largely ignores is that often you want to build the support for the presumptive feature now because the act of implementing it affects ar
by eridius 11y ago
Something this article largely ignores is that often you want to build the support for the presumptive feature now because the act of implementing it affects architectural decisions. It came close to this by saying
> it does not apply to effort to make the software easier to modify
But there's a difference between making software easier to modify (where the same effort could be expended later to retrofit the software) versus making architectural decisions. Often times trying to implement something teaches you that your architecture isn't sufficient and you need to change it. If you can learn that while you're building the original architecture, then you've avoided a potential huge amount of work later trying to retrofit it.
To use the example from the article, what if supporting piracy risks reveals that the fundamental design used for representing pricing decisions isn't sufficient to model the piracy risks, and the whole thing needs to be reimplemented? If you can learn this while implementing the pricing in the first place, then you haven't lost any time reimplementing. But if you defer the piracy risks by 4 months, and then discover that your pricing model needs to be re-done, now you need to throw away your previous work and start over, and now your 2-month feature is going to take 3 or 4 months instead.
- pimlottc 11y agoBut on the gripping hand, deferring an implementation often means you understand the problem better by the time you get to it, so that your actual implementation is better than the naïve version you would have written earlier.
- eridius 11y agoFair point. Making these decisions correctly is hard. I'm pretty sure this is one of the things that you slowly learn how to do with experience, that no amount of being smart or research can make up for.
- Spearchucker 11y agoI think you had it right first time. Much like you can define an interface without a concrete implementation, or a unit test without working code. One rather hypothetical approach to the problem is separation of concerns - make pricing composable - starting with storm risks, and supporting other unknown, future risks. The obvious follow-on I see too often is to use some type of dependency injection, which is almost always (my experience - YMMV) a bad move - pricing risk components probably don't change often enough to warrant the configuration nightmare that ensues. Just recompile, and redeploy. Or use reflection to load available pricing modules, assuming the perf hit is acceptable. You may not need to know the intricacies of pirate risk when delivering storm risk, but you do know that you'll be dealing with another risk after delivering storm risk. So plan for it.
- mkozlows 11y agoGood god, no. Do not do that. Because what'll happen is, you'll find out that the type of risk you actually encounter ends up being tied to (say) the time of year in a way that you were not expecting, and now you've got this elaborate risk-plugin architecture sitting there, and there's no way to get the time of year down to the risk calculator, so you need to elaborately hoist out and redesign this entire gigantic apparatus, instead of just adding a parameter to a function call. And meanwhile, as you were daydreaming your future hypothetical risks and trying to have an idea of what stuff they might need to know, you imagined that it might need to know what currency the thing is priced in to calculate currency-fluctuation risks, and so you're passing currencies all over the place, to be prepared for general extensible etc., but it turns out you never need them in your risk calculators anyway, so it's just this pile of unnecessary nonsense of setting currencies everywhere and you have to maintain all that. And if you read those paragraphs and think "hmm, you'd probably want to make it extensible in terms of which fields are passed into the risk calculator to prevent that kind of problem" then realize that you are now solving a hypothetical problem that was caused by the "solution" to your first hypothetical problem, and you're three levels removed from delivering any actual value to anyone. Forget about it. Don't plan for it. Plan for what you need today, because you will not be good at anticipating what you need tomorrow.
- eridius 11y agoYou seem to be dividing the world into two possibilities: 1. Don't plan ahead. Only implement what you need right at this moment. 2. Plan for everything that's even remotely imaginable. #2 is quite ridiculous. But the alternative to that is not #1. There is a huge amount of middle ground here, for assessing likely future needs and designing your architecture appropriately, as well as for identifying what future requirements affect architecture and which ones can be safely ignored as something that can be easily implemented on top of the current architecture. The problem with #1 is this either leads to re-implementing large portions of your app (possibly many times if you persist with this) as you discover your architecture doesn't work; or, more likely, adding hack on top of hack to implement new functionality without having to rearchitect, which leads to massive technical debt and results in an un-maintainable product.
- saganus 11y agoAs I get older, both as a person and as a dev, I have come to the same realization as you: that a lot of the time I end up building things that I don't need "right now", but which I have to at least plan at the architectural level, lest be it a nightmare later on. And then it hit me.. THIS is one of the areas of software development where I don't think reading more articles, techniques "mantras" etc will help. Only more practice will help. It ends up boiling down to how much experience as a developer you have, making those particular decisions. I.e. It's a craft, not a science. So as you grow up as a developer you suddenly find that you start to have certain "intuition" as to why I should actually plan for something that I don't need, vs actually ignoring something (a real "yagni"), but you just can't explain it in terms of generalizations. You end up saying.. "well.. it just feels like I Should do this, because I have been burned in the past when I ignored this intuition" And I don't think I'm alone in this train of thought.
- guscost 11y ago> It's a craft, not a science. I feel exactly the same way. It turns out everything practical I do with software depends more on the "spider sense" part of it rather than some well-defined knowledge (which is easy to look up when needed, so maybe that has something to do with it). The spider sense is not something you can teach or memorize, and it takes a while to develop. Programming is exactly like a craft in that way. But there is plenty of better understood theory and experiment out there which if anything is the science part. From the theory and experiment I've dabbled with it's very much a different thing. And it has a lot to do with the existence of radically different schools of programming. There's a joke, the engineer is showing a laser pointer to the scientist. The scientist asks "why didn't you get an ultraviolet laser when those output far higher energies?" and the engineer says "it's for shining a dot on the wall."
- saganus 11y agoI think what I was trying to say it's not really that developers are divorced from science, but that the day-to-day tasks that we do end up being far from whatever paper or thesis taught us how to do. The example that comes to mind is REST. There is a lot more in that PhD thesis than what is actually used and implemented outside of the lab. That's what I meant for craft. In the end I don't see anyone implementing "all the science" regarding REST, only those parts that are needed and even then... as best as some engineers understood it (myself included of course). That's also the reason why we end up calling "RESTFul" a lot of stuff that Thomas Fielding clearly states is not. At least it's good knowing that I'm not alone and other people feel the same. So in the end I know that I can do my best... but not more. It's a funny beast this development thingie... Edit: So basically just reinforcing your point...
- nostrademons 11y agoI don't think YAGNI ignores this so much as explicitly rejects it. I've participated in the type of rewrites you mention here. Yes, they suck. Yes, they happen far too often. But I've also observed that they seem to happen regardless of how much you plan ahead to future requirements: even if you are exhaustively brainstorming possible future directions and are absolutely sure you're going to want something, you're very often wrong. And the inevitable rewrite that happens is a lot more painful when you have to carryover requirements that you don't actually need. YAGNI fundamentally is a statement about costs and benefits. And it's a statement about personal experiences of costs and benefits, with a counterintuitive conclusion. I've found, however, that the teams I've worked on that just accept that they'll have to rewrite or throw away 90% of what they write end up performing at a much higher level (in terms of their impact on the broader industry) than the teams who figure "But if we could just get that 90% down to 50%, we'll be 5x more effective than other teams!" Another way to look at this is in terms of external vs. internal drivers of success. YAGNI makes sense when the primary drivers of success are external and you need to quickly react to changing market conditions or customer requirements. It doesn't when the primary drivers of success are internal and you need to quickly act to get from known point A to known point B as efficiently as possible. Some engineers are lucky (unlucky?) enough to work on the latter, but typically it only happens when you either have a monopoly or you're deep in the bowels of a corporation and only need to report up to an executive who never changes her mind. To use the example from the article - if the biggest risk or change in the external environment you'll face is your software, go ahead and build the feature into it. But who knows? You may be able to close a round of venture funding in 2 months and then hire the Gondor navy to eliminate piracy. Or Gondor may enter into a trade agreement with Rohan and redoing all your contracts takes primacy. Or Aragorn may arrive with the Army of the Dead and suddenly piracy is not a problem anymore, but a lucrative business in life insurance may pick up.
- eridius 11y agoI think there's a difference between going ahead and implementing piracy risk immediately, vs determining all the requirements of piracy risk and using them when designing your fundamental architecture, with hooks left in place for extensibility that simply aren't actually implemented yet. Maybe the Gondor navy will destroy the pirates, but then you have to worry about Corruption risk because you now may need to bribe the navy or risk having your cargo impounded (so you have to balance the risk of impounding vs the cost of bribing). Sure it's not the same thing as piracy risk, but it's similar, and because you designed your architecture from the get-go to enable piracy risk and other such extensions, you can now implement navy bribes pretty easily. Meanwhile, if you'd said YAGNI to piracy risk and just implemented support for storm risk, you may find that you can't easily implement navy bribes without re-doing much of the work you already did for storm risk. As saganus said[1], this is more of a craft than a science. You need to plan ahead with your architectural decisions, and they need to be made using the actual requirements you expect to encounter (as opposed to theoretical requirements, which aren't really much of a use to anyone), but that doesn't mean you need to actually implement everything immediately. Just enough to be satisfied that your architecture will suffice. [1] https://news.ycombinator.com/item?id=9607881 https://news.ycombinator.com/item?id=9607881
- fournm 11y agoRelated to this is the question of any data migrations as part of those architectural decisions. Especially if the "right" model in the end isn't really compatible with the first, wrong one and you can't afford to mess up the data.
- mkozlows 11y agoSo the thing is, yes, this hypothetical thing will affect your architecture. That's true. But what's also true is that a) this other thing over here you're not aware of will ALSO affect your architecture; and b) the thing you're planning ahead for might never happen. Which is to say the really obvious: The future is unknowable. Some people react to this by saying "okay, let's make everything as general as possible then, so we can be prepared for an unknowable future" and architect up a forest of abstractions. Some people (the YAGNI crowd) react to this by saying "okay, let's making everything as simple as possible, so it's easy to change in the future when we know things" and rigorously eschew unnecessary abstraction. In my experience (which includes creating, maintaining, and managing a single large application for a decade), the YAGNI crowd has the right of it. Complexity is a cost, and you should only incur it if you absolutely, no-question need it.
- eridius 11y agoAs I stated in another comment[1], this is a false dichotomy. There is a large continuum of various ways you can make decisions in between the two extremes of "YAGNI!" and "be as general as possible". The correct solution almost always lies somewhere in the middle. YAGNI is appropriate if you think there's a very good chance that you won't need to do anything in the future, or if the future is so ill-defined that you can't make any sort of reasonable prediction of what you might need. If you're writing something that will be deployed once and never updated again, well, YAGNI is fine. But in most cases you're writing something that will need to be maintained and have more features added over time, and in that case, you have a variety of architectural decisions to make that influence what sort of changes can be easily made in the future. Determining which decisions to make in which ways is a hard problem, and it's something you basically just have to develop an intuition for by working on a lot of projects over years. [1] https://news.ycombinator.com/item?id=9608099 https://news.ycombinator.com/item?id=9608099
- unclebucknasty 11y ago>There is a large continuum of various ways you can make decisions in between the two extremes of "YAGNI!" and "be as general as possible". But, isn't the point of Yagni that there is not such a large continuum? Isn't the argument that attempting to anticipate the future to any degree is generally a waste of time? Once you go beyond solving the problem at hand, you've jumped the shark. My experience has shown me time and again that future planning seldom pays off, and even then in small ways. I come out far better keeping things as simple as possible, as future changes also tend to be simpler. >But in most cases you're writing something that will need to be maintained and have more features added over time, and in that case, you have a variety of architectural decisions to make... I don't know that it requires making "architectual decisions" as much as following generally good design principles and keeping your couplings reasonably loose.
- lowbloodsugar 11y ago1. My team will not have to throw away our previous work. It works for storm pricing. It is deployed. Customers are using it and we are making money. Your company has yet to deploy a storm pricing model because it got caught in the complexity of the piracy model. 2. When it comes time to write the piracy pricing system, my team has already gained a huge amount of relevant experience from writing the storm pricing system. In my experience a team that already has developed a simpler system will be better able to develop the more complex system. 3. If piracy pricing is a significant risk then it should be prioritized higher. If it is late because you thought it would take two months and it took you four, then you failed to assess risk. A really good way to assess risk is to implement something similar (may I suggest storm pricing). However, in my experience, even if we know we want to ship storm and then piracy immediately after, I will still ask my team to develop storm first to completion. And I will stand any one of my teams up against a team that is going to "do it all in one go", and we'll get them both done before the other team. 4. Basically when you make an argument that is "Suppose you have this situation...", then it is equivalent to saying "This thing happened in the past, and if we had known then what we know now...." They are equivalent because now matter how your frame your "thought experiment", it is guaranteed to never happen in real life. The whole point of YAGNI is that you cannot know the future, and no one ever has. The only way to know for sure that "this is how its going to turn out" is to actually be in the future. So, yes, if you had a time machine, you could argue that big upfront design would work. Of course, if you were to start your argument with "Suppose I had a time machine and I could go back in time and tell my team that the piracy model is a perfect superset of the storm model, but is twice as complex because of x, y and z, but there are no other unknowns, and customers love it, and the Navy doesn't kill the pirates, and so therefore we should do it all in one go" then it would be much more obvious that your argument was flawed.
- eridius 11y agoYou're still making some pretty strange assumptions here. An imperfect prediction of the future does not render the prediction meaningless, and making architectural decisions to support expectations of the future does not necessarily mean a significant increase in complexity, it often merely means some upfront "critical thinking" time. In fact, this upfront time may very well speed up the rest of the implementation, even if the predicted future requirements never come to pass, because it produces a more well-thought-out and well-understood architecture, that may be cleaner and more powerful without making the actual implementation any harder. But again, there is a large continuum of possible approaches here, and insisting on reducing it all down to either "do everything" or "YAGNI" is really kind of bewildering.
- unclebucknasty 11y ago>* If you can learn this while implementing the pricing in the first place, then you haven't lost any time reimplementing.* But, isn't this the same kind of thinking that necessitated a precept like Yagni? I wonder if the very notion that we are "architecting" vs. simply building software to solve a clear and present problem is at the heart of the tendency to overengineer.
- pacala 11y agoI have found the opposite. Building things you don't need yet it's just increasing the mass of code that needs to change when the real requirements hit. It pays off to build software as simple as possible, and modify / generalize later, as opposed to designing an up-front "architecture" that is bound to become obsolete and a hindrance in 6 months.
- olavk 11y agoIf you know that you are going to need piracy risk support, then of course it makes sense to prepare the architecture for it, even if you only have to deliver the feature four months down the line. But YAGNI does not apply if you know a feature is needed. If you don't know but are just guessing about possible future development there are a million things in various directions you could also prepare for, and all preparations have a cost.
- lmm 11y agoEvery "architectural decision" I've ever seen has been wrong. It's always resulted in a worse product than simply writing the code and allowing the architecture to happen. (This doesn't mean writing code with no layering, just deferring those decisions to the point where you actually have the code that makes use of them)