Sign in to follow this  

YAGNI-Where does it end?

This topic is 3624 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

This question popped into my head today at work, as I was getting ready to rafactor some duplicated code into a function. I'll give an example to make my point clearer. Let's imagine the dialog between 2 partner programmers, 1 producing some 'questionable' code, and the other critisizing this code:
void hit_player(int hit)
{
  Player* player=Player::GetInstance();
  switch (player->type)
  {
     case PLAYER_MORTAL:
       player->DecreaseHealth(hit);
       break;
     case PLAYER_DEMIGOD:
       player->DecreaseHealth(hit*0.2);
       break;
     case PLAYER_GOD:
       player->DecreaseHealth(hit*0.05);
  }
}


-Ehm, shouldn't we try to avoid singletons or globals? It's bad practice... -Why? We only have 1 player in the game -Yeah, but what if in the future we want to add additional players? -You Aint Gonna Need It -Okay, but Player::GetInstance() is accessible from everywhere, even unrelated modules. This is going to be bad for bug hunting. -I've checked the code I've written. Right now, it's accessed only in related modules. No worries. -Yeah, but what if in the future... -You Aint Gonna Need It -Okay. So how about this "switch" thing? -What about it? -It's brittle and error-prone. Shouldn't we use virtual functions instead? -Error-prone my arse. I checked the code. There are no errors. All possible types are handled right now. -Yeah, right now. But modules should be open for extension, closed for modification. What if in the future we need to add another type? Then we would... -You Ain't Gonna Need IT -But what if... -YOU AINT GONNA NEED IT! See, I'm having a bit of a difficult time seeing where this attitude stops. YAGNI, as defined in this page, means that "Even if you're totally, totally, totally sure that you'll need a feature later on, don't implement it now.". That means: code according to your current needs, not according to what needs you think you'll have in the future. But doesn't that mean, that whatever code compiles, run, and does what is expected to do *right now*, is good enough? Is Yagni univeral, or is there an area where it doesn't apply? Does it include overall design choises, or primary specific functionality features? Are there proven design principles(such as OCP), that should in fact "override" YAGNI and be folllowed from early on, even when the benefits are not apparent, precisely because you'll benefit from them later on? What is your opinion?

Share this post


Link to post
Share on other sites
Quote:
-Ehm, shouldn't we try to avoid singletons or globals? It's bad practice...
-Why? We only have 1 player in the game


False premise.

This is *not* the reason singletons are bad. It's bad due to coupling.

The code is also poorly designed:


void Player::takeDamage( int hit )
{
decreaseHealth( hit * damage_reduction );
};

Player::Player( ..., PlayerType pt )
: ...
, damageReduction( DamageTable[pt] )
{}

// Damage table is provided from somewhere, might be passed as part of parameters to constructor





Using proper OO makes the rest of discussion moot.

Quote:
See, I'm having a bit of a difficult time seeing where this attitude stops. YAGNI, as defined in this page, means that "Even if you're totally, totally, totally sure that you'll need a feature later on, don't implement it now.". That means: code according to your current needs, not according to what needs you think you'll have in the future.


There's a good and bad way to assume YAGNI.

Don't implement now is not the same as "build everything as hostile to future extensions as possible". This is what singletons and switch statements do - and they are really easy to work around.

It's the same as optimize last. It does not mean: "write the crappiest slowest poorest buggiest algorithms there are and then fix bugs as customers discover them".

This is why it's pointless to discuss or even mention things like this to management, since they'll make incredibly uninformed decisions which will devastate the project on false premises.

YAGNI does not mean abandon proper design.

Share this post


Link to post
Share on other sites
If it works, leave it for now.

That said, the conversation is a little silly.

Point 1: avoiding Player::GetInstance.

Sure, having more than one player is YAGNI, but you do need 1 player. Using a singleton and passing it in as a parameter are equally difficult/complex solutions to a problem you already need (and implement). Switching to the other solution for something you clearly need is not YAGNI; it's maybe silly since it's already done and tested, but that is rather shallow in the face of the known problems that will crop up.

Point 2: the Switch

Again, you already need something to change the effect based on type. The switch is just as difficult/complex as a virtual function or a strategy, or best yet a nice table lookup. Again, it's done and tested so leave it be, but again a shallow argument for a clearly inferior solution.


In the end, the argument here shouldn't be YAGNI, but 'work on stuff that doesn't work yet'.

Share this post


Link to post
Share on other sites
Quote:

'work on stuff that doesn't work yet'


What do you mean? Both the singleton and the switch work right now *exactly* as expected. Why change them? What proper OO techiniques offer *right now* that the existing solutions don't?

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
Quote:

'work on stuff that doesn't work yet'


What do you mean? Both the singleton and the switch work right now *exactly* as expected. Why change them? What proper OO techiniques offer *right now* that the existing solutions don't?


You are right, perhaps I was not clear. I meant that the person arguing for YAGNI in the example, should have argued for prioritization instead.

Share this post


Link to post
Share on other sites
Quote:
Original post by mikemanWhat do you mean? Both the singleton and the switch work right now *exactly* as expected. Why change them? What proper OO techiniques offer *right now* that the existing solutions don't?


YAGNI is design.

If design document said:
- There will only ever be one player
- There will only ever be 3 difficulty settings
- There will only ever be 1 damage type
then this code is perfect.

If design document didn't say that, then it's time to have a serious talk with the coder.

Also - good coders will never code themselves into corner like this, since experience has taught them that requirements change. So they'd never use singletons and hard-coded values like this.

Share this post


Link to post
Share on other sites
You May Not Need It, But If You End Up Adding It Later It Would Be Much More Painful Than Doing It Now is probably too long to be a good acronym, but is nevertheless a good thing to keep in mind. YAGNI should never be used to excuse poor design, only to delay time-consuming design; and relative costs of adding things now versus later should always be kept in mind.

Share this post


Link to post
Share on other sites
Quote:
Original post by rip-off
Applying YAGNI from the start, player will never start as a Singleton.

QFT.

A singleton itself often violates YAGNI. Writing all the singleton boilerplate (even if that just involves grabbing some prewritten templated singleton class and using it) is already over engineering a simple task. Simply create and instance with suitable scope and pass it in as a parameter. Doing anything more complicated (ie. a singleton) is doing wasted work.

For switch statements, I almost always start with a switch and only refactor to polymorphism when it gets unweidly. A small switch like that I'd probably leave alone. You only really see the benifit if you've either 1. already got a type hirarcy in place. 2. Have more than a screen full of states and/or need to add states without touching the original code or 3. have the same switch (with different effects) in multiple methods (eg. maybe you have one in your render() and one in your update()).

Also bear in mind that YAGNI assumes that you'll be able to refactor and change things around whenever you like. If this doesn't apply (like, maybe you're designing a DB table structure and your DBA won't change it without forms written in triplicate, or maybe when you're creating a file format which will be released into the wild and you'll have to maintain support for) then planning ahead is going to be more important.

Like pretty much all programming, there are no hard and fast rules which apply all the time. The core concept of YAGNI is (IMHO) that you should design for requirements you need *right now* and avoid overcomplicating things by guessing what you'll need in the future. If you constantly question whether you really need the functionality vs. whether it just seems like a good idea then you're well on the right track.

Share this post


Link to post
Share on other sites
Refactoring is not a feature. You pretty much always need cleaner code (because every subsequent bit of code you write has a chance of depending on it).

Share this post


Link to post
Share on other sites
Quote:
YAGNI-Where does it end?


When you start Needing It. ;)

Seriously, I think it's simply a case of not coding things that meet specifications you don't yet have. When implementing a feature, it's always worthwhile making it flexible and future proof to a degree. What isn't worthwhile is making your job harder now in the hope that it'll help with something else later. A singleton adds arbitrary restrictions, so it doesn't do that. If it was just a getPlayer() call then you can easily extend that later to take an index or something, if and when you need it.

As for the switch statement, there might be ways of factoring that into something bigger, but I do think that creating 3 extra classes and defining new virtual functions just to eliminate a 11 line switch statement is exactly what YAGNI is there to prevent. A default: assert(0); break; is a reasonable approach in code that gets hit often, such as this. Better still, make DecreaseHealth private, add DecreaseHealthModifiedForType (or something with a better name) public, and move the switch statement into there, encapsulated in the player class. Let the player class and interface worry about managing subtypes, and whether it does that via derived classes, switch statements, bitflags, or removes types altogether and uses a DamageAttenuationFactor, is not the calling function's problem.

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
-Ehm, shouldn't we try to avoid singletons or globals? It's bad practice...
-Why? We only have 1 player in the game
-Yeah, but what if in the future we want to add additional players?
-You Aint Gonna Need It

But you *are* gonna need to extend the code *somehow*. Maybe by adding more players, maybe by doing something else. So the code should be easy to extend and build on.
[quot€]
-Okay, but Player::GetInstance() is accessible from everywhere, even unrelated modules. This is going to be bad for bug hunting.
-I've checked the code I've written. Right now, it's accessed only in related modules. No worries.
[/quote]
You *are* going to need to debug. So the code should be easy to debug.

The thing is, YAGNI talks about *features*, not about design.
Saying "Our code doesn't have to be easy to maintain, because we're not sure we're going to have to debug it" is just nonsense.
Avoiding singletons is a design issue, not a feature. Same goes for being able to debug your code.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
You May Not Need It, But If You End Up Adding It Later It Would Be Much More Painful Than Doing It Now is probably too long to be a good acronym, but is nevertheless a good thing to keep in mind. YAGNI should never be used to excuse poor design, only to delay time-consuming design; and relative costs of adding things now versus later should always be kept in mind.
QFE/T.

Share this post


Link to post
Share on other sites
Is everyone here (except me) just so good a programmer that they never have to sacrifice best practices in order to achieve a goal?

On large projects, no matter how much time I put into code design, I almost always end up facing a choice between redesigning or hacking my way through.

At the beginning of a project it's easier but as the codebase gets larger - the choice becomes far more difficult.

And, yes, maybe that's why there's so much bad code out there but it's also probably why many games (and other software titles) make it to the finish line in the first place.

Share this post


Link to post
Share on other sites
Quote:
Original post by linternetOn large projects, no matter how much time I put into code design, I almost always end up facing a choice between redesigning or hacking my way through.

At the beginning of a project it's easier but as the codebase gets larger - the choice becomes far more difficult.


Ergo - *Never* use singletons. *Never* use globals. And so on.

This is what these good coding practices say. In the long run, certain techniques work marginally better than others. But multiply marginally with large codebase, changing designs and real world, and you get a difference between maintainable code and a train wreck.

YAGNI and good design in the above example don't matter even a bit. It's one single function, so debating it in isolation is completely pointless.

Share this post


Link to post
Share on other sites
Quote:
Original post by linternet
Is everyone here (except me) just so good a programmer that they never have to sacrifice best practices in order to achieve a goal?

On large projects, no matter how much time I put into code design, I almost always end up facing a choice between redesigning or hacking my way through.

At the beginning of a project it's easier but as the codebase gets larger - the choice becomes far more difficult.

And, yes, maybe that's why there's so much bad code out there but it's also probably why many games (and other software titles) make it to the finish line in the first place.
Having to re-do some things is not bad. It's to be expected. It's when you refuse to do this and just add on layers of hacks it becomes a huge mess. But you should be refactoring rather than rewriting code.

Share this post


Link to post
Share on other sites
Quote:
Original post by d000hg
Having to re-do some things is not bad. It's to be expected. It's when you refuse to do this and just add on layers of hacks it becomes a huge mess. But you should be refactoring rather than rewriting code.


I'm probably going to draw disagreements from this but, to me, a poorly coded, complete game is worth more than the most beautifully written unfinished project (It's also worth noting that "poorly coded" doesn't necessarily mean the game doesn't work or performs badly)

Refactoring working code takes time that could be used for putting additional features in. It could end up saving time or you could be far enough along that hacking is the fastest way to finish. I don't have a foolproof formula to tell the difference.

The point I'm making is that time is (obviously) a critical resource and even independent projects don't have an unlimited amount of it. Refusing to refactor definitely does make for messy, difficult to maintain codebases, but I think it can also help there BE a complete codebase in the first place.



Share this post


Link to post
Share on other sites
Quote:
Original post by linternet
I'm probably going to draw disagreements from this but, to me, a poorly coded, complete game is worth more than the most beautifully written unfinished project (It's also worth noting that "poorly coded" doesn't necessarily mean the game doesn't work or performs badly)

You won't get a disagreement from me. The codebase for Rumble Box is messy, to say the least. The whole 30,000+ line program was written by two students over the course of eight months... needless to say, there's some ugly, ugly hacks in there. The UI and collision code are the biggest offenders, but there's not a system in that game was wasn't pieced together with bubble gum and duct tape.

However, the game performs well and since we first launched in July 2005 I've only seen it crash once (that includes it running non-stop for 3-5 days at festivals). It's finished, it's fun, and it got us a job: I'm not trying to say that it's ok to write bad code, but I'll agree that if that's your only option (due to ability or time constraints) you can still make something good.

Share this post


Link to post
Share on other sites
Quote:
Original post by linternet
I'm probably going to draw disagreements from this but, to me, a poorly coded, complete game is worth more than the most beautifully written unfinished project (It's also worth noting that "poorly coded" doesn't necessarily mean the game doesn't work or performs badly)


Sure poorly coded and complete is great but poorly coded hardly ever makes it to complete. Since it has odd bugs, never works quite right, and crashes when you look at it funny, or the layers of bad code make it impossible to finish the last 5%. Some times an ugly bit here or there can be a fast, get it done sort of thing, but when the ugly starts to out weigh the pretty you have a real problem.

Share this post


Link to post
Share on other sites
I've built a replica of Golden Gate using planks, nails and clothesline.

Some people said I should use steel and concrete support, but my answer to them was YAGNI. Grand opening should be any day now, we expect at least 10,000 cars the first day.

Know thy context. Sometimes YAGNI - but make sure YAGNI out of experience, not ignorance.

Share this post


Link to post
Share on other sites
Quote:

Sure poorly coded and complete is great but poorly coded hardly ever makes it to complete.

Sure it does. Just redefine "complete" and ship it. You can always patch it later, right? :D

Share this post


Link to post
Share on other sites
As I think others are saying, isn't the problem that by asking, "Where does YAGNI end?", one is assuming that that's the only advice one should follow? Isn't there another rule, zero-one-infinity? The two rules agree if you need zero or one, but, if you need two, YAGNI says you don't need infinity while zero-one-infinity says you do. Maybe one place YAGNI ends is where zero-one-infinity begins? Maybe YAGNI needs to be used alongside "make if work, make it right, make it fast"?

If you only have one damage type while you're coding, there's no sense trying to add in the ability to use other damage types even if you suspect that it's very likely the end product will have more than one damage type. It's not entirely because "very likely" doesn't mean "it is most certainly so", but also because you don't know what sort of framework will be required to support the new damage types. In other words, you're writing code based on assumptions about your assumptions. So, not only are you making the code more complicated in the short term, the chances that it'll pay off in the long run aren't necessarily very good, and the time you spent making your life harder by adding complications could be spent on making other parts work/right/fast.

Share this post


Link to post
Share on other sites
Quote:
Original post by jpetrie
Quote:

Sure poorly coded and complete is great but poorly coded hardly ever makes it to complete.

Sure it does. Just redefine "complete" and ship it. You can always patch it later, right? :D

Unfortunately I've shipped more than one game that way...

Share this post


Link to post
Share on other sites
Quote:
Original post by linternet
Refactoring working code takes time that could be used for putting additional features in. It could end up saving time or you could be far enough along that hacking is the fastest way to finish. I don't have a foolproof formula to tell the difference.


I suspect most people underestimate the benefit/cost ratio. I'm pretty aggressive about it. Some justification.

Share this post


Link to post
Share on other sites

This topic is 3624 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this