3 ms·
I think it is important to differentiate between "early building" of features you don't yet need, and building in a way that doesn't require much refactoring in
by jmadsen 11y ago
I think it is important to differentiate between "early building" of features you don't yet need, and building in a way that doesn't require much refactoring in order to extend/add those features on.
More common than overbuilding features is overbuilding function or library capability. The right balance, IMHO, is _thinking_ about the extendability & designing for it but leaving out the actual details. This tends to lead toward good, SOLID designs that are easy to work with later rather than a blind-ally you need to spaghetti-mofongorate to get to work.
"Refactor later" is fine for folks like Martin Fowler who know _how_, and who haven't painted themselves into a corner with bad early decisions. It is a bit dodgy to tell the average mid-level dev who just wants to "get it done" so his Project Manager can check off their Excel spreadsheet that they are on time.
- deleted 11y ago[deleted]
- moron4hire 11y agoExtensibility is itself a feature, so YAGNI would imply it shouldn't be assumed until proven necessary. I personally use the "zero, one, many" rule (i.e. those are the only counts of things, there is no such thing as 2 or 3, etc.) as my proof of necessity. If I ever get to the point that I need two of something, I make it useful for many of something.
- beat 11y agoI've found that designing for extensibility is wrong more often than right. The code doesn't stretch the way you expect, and then it strains against other boundaries you didn't realize you were setting.
- jmadsen 11y agothat's not quite what I mean. saying, "I think it will need feature X, which will require parameter x_id, so I'll add x_id = 0 now" is wrong. writing (just as an example) a function that takes an config array as its param so you can later build out, instead of passing a_id, b_id, c_id...."oh, crap, how long a list will this be?" in the early build is the type of thing I mean. Writing extensible code more often than not causes you to write smaller, tighter, more testable components. Writing in this style is what I'm trying to say. Many less experienced devs would not do this and end up with long procedural code because they feel it is wasted time for something they aren't anticipating.
- gav 11y agoThere's a lot of times where you know that something needs to be extensible, but you only know the immediate requirement. From Martin's example, the team needs to support storm risk, and think they need piracy risk in the future. I'd suggest that they are 100% sure they need to support multiple risk calculations, they just only have the requirements for the first type now. It's at this point you have several options, but by choosing something that is a bit more generic than the base case but without being an over-architected mess will save you a lot of time in the future.
- mkozlows 11y agoExcept "over-architected mess" is what any generic thing is, if you don't need it. It's very, very easy to convince yourself that a particular extensibility point is "obviously" going to be needed down the road. It's very, very easy to be wrong about this, and extraordinarily common to find out that your attempt at making it generic now makes it harder to extend in the way you actually need it extended. It turns out if you make things bone-dumb to begin with, they're never really that hard to extend later on. Anyone can refactor really ridiculously simple stuff.
- chipsy 11y agoThe only thing I design to extend is the data. That is, I get suspicious now when I take an existing method and try to parametrize it, because what I'm doing if I do that is building up a richer all-purpose code edifice - and that is a Bad Idea when we know that code is disposable and data isn't. By default, all programmers start by planning and testing code towards one use-case, and then they add the other cases one by one. Code that is arbitrarily made "generic" or "extensible" imposes a testing regime that programmers do not arrive at within the natural edit-test-debug cycle, but instead have to consider separately. With data this isn't really the case. Achieving both YAGNI and DRY at the same time is _easy_ with data. Data tends to become more generic and more fungible as it becomes more crude and simplistic, and less so as a defined schema builds up - for example, a Point class with "x" and "y" fields, vs. a numeric array of 2 or more entries. With the former, you can end up with your Point not being the same type as someone else's completely equivalent Point. With the latter, agreement mostly rests on which kind of numeric type you're using(which can be enforced by the language), and the ability to operate on many points as well as just one point is straightforward to support yet not strictly required. And so if the goal is reuse, you design downwards; if it's type safety, you design upwards. Achievement of both imposes an enormous code burden as you try to enumerate all the types and provide various translation layers and fallbacks - in turn creating more code paths, more situations where you can throw an exception or return a null value, etc. That can add up to a lot of lines of code, and a performance tax, and more failure modes, in exchange for sometimes eliminating some runtime errors. I think we naturally tend towards overdefining our data, though, because it looks more "correct" to tightly define each block of code so that it says "point.x" instead of something like "x = point[index * 2]".
- moron4hire 11y agoYou say so much that I agree with, except your core thesis. YAGNI must be applied with DRY, yes. Code is disposable (maybe-leaning-to-yes), data is not, yes. But not properly defining your data is a huge mistake. Torvalds has been noted to say: "git actually has a simple design, with stable and reasonably well-documented data structures. In fact, I'm a huge proponent of designing your code around the data, rather than the other way around, and I think it's one of the reasons git has been fairly successful […] I will, in fact, claim that the difference between a bad programmer and a good one is whether he considers his code or his data structures more important." Well-documented data structures. Schema is data structure documentation. Your Point example is good. A Point is not a Size, but an array of numbers could be interpreted as either, or as a Latitude/Longitude pair, or as anything else. How do we know, looking at an array of numbers, what it is meant to be? But if we knew it was a Point (because the type expressed it as such), then we'd also get to assume we can do point-like things to it. Of course, YAGNI would suggest not creating a Point and Size classes until you need to differentiate between multiple types of ordered pairs. That's fine. If your system only ever handles point data, then it only ever handles point data and we state that up front, allowing us to assuming "oh, an array of numbers? Must be a point". But being able to handle different types of data is a feature, and if you prove you need it, then you need it. Write that Point and write that Size class.