4 ms·
Abstraction is like salt -- a little bit will go a long way. The time to use it is when your business users can't agree -- one says do it that way, another say
by DanielBMarkham 16y ago
Abstraction is like salt -- a little bit will go a long way.
The time to use it is when your business users can't agree -- one says do it that way, another says do it this way. Or they say it's one way, but they're not sure if it's going to change or not. That's a perfect time to cover both bases.
A tiny bit of abstraction applied well can save immense heartaches down the road. A bunch of abstraction applied poorly can make your life miserable. Part of "growing up" as an OOP developer is learning what to do when.
Also, this is a good example of why founders and startups use so little abstraction -- nobody knows what the problem is or what's going to change. It's all complete guessing. So might as well get something out the door today instead of playing mind games about what might happen or not based on your imagination of what a user might be. (This is also the reason functional programming maps so much better to startups: you don't have a spec. You just want one thing to go in and another thing to come out. If that isn't what people want, you have to iterate. Completely different paradigm than working in a business environment where somebody gives you ten bucks to go make well-defined change in system X)
- viraptor 16y agoI really agree with some parts. If you expect to grow, you can at least make an effort to keep stuff extensible in the future. Right now, I work with old code every single day. With code written one day assuming that: tax will never change (did twice since then), tax code will never change (changed once), service provider will never change (another one was added, so we have to choose one at runtime based on many dynamic conditions), this modification is needed for only one reseller (file has 10+ lines of if reseller_id == ...). Making things flexible may not sound pretty at the start. But ending up changing 3 apparently completely separate systems to accomodate the fact that you changed the version of some software and the output format needs to be different (hey - why would we do immediate representation, output's not going to change). It looks silly in the example, because it's a simple operation. Now substitute 'factorial()', for 'provision_customer_config()'. You WILL change the backend at some point. You WILL change the endpoint at some point. You WILL change the way the function works completely, but will have to retain the previous procedure for existing users. It's a when not an if question. There should be a saying similar to that about people doing backups: there are 2 types of programmers - those who write modular code and whose who will.
- ollysb 16y agoPerhaps the point is that you should refactor your code into modular code when it's required. In real applications there's a million different ways to modularise the code, it's extremely rare that you guess which way is right in the absence of actual requirements.
- sophacles 16y agoI would like to add to this, changing some endpoint frequently involves a single, one-time migration to the new system rather than a requirement to support the old and the new simultaneously, thus making some of the modularization moot or just pure overhead vs. a port.
- TheSOB88 16y agoBut it's not a when question. It's a what question. And if you don't know, why would you code for all the possible possibilities?
- bermanoid 16y agoPart of "growing up" as an OOP developer is learning what to do when. I think this is the real problem with this particular example - because the author wanted to make this into an anti-pattern joke, he didn't actually think about what a Real Programmer actually trying to make good use of the extra keystrokes would do in this situation. The "requirements" that he's using to build up this bloat appear to be: 1) The result might be really, really, big, so no factorial function worth a damn should be limited to a normal int. 2) We might want to swap out implementations of our factorial function, for instance, to cache intermediate results, to use a loop, to use recursion, etc. 3) We should be able to add new implementations without modifying class files already written, and we should be able to select implementations at runtime. Fine, then. These could be 100% realistic requirements if we were talking about some more substantial or important piece of machinery than a factorial function, so we really might have to satisfy these things, in which case the three-liner would be completely and utterly unacceptable. So let's say we're in that situation, then. A good Java developer still would never write the code the way it came out in that post, because the abstraction doesn't go far enough, and it's obvious. Almost every time he writes "FactorialAlgorithm", we could just as well replace that with "Algorithm" (or maybe better, "BigIntegerAlgorithm"), and now instead of building up a whole bunch of architecture just to support one stupid algorithm, we've spent a bit of effort to make all of our (big integer) algorithms swappable. That memoization stuff could almost immediately be refactored up and out of the individual implementations, so it would take but a single method call or flag to add that ability to an algorithm, and so on. I'm not saying that the particular architecture that gets built up to do this stuff is necessarily pretty, that Java-style OOP is an optimal (ha!) or even passably efficient way to achieve these constraints, or that you should do these types of abstractions before you know you absolutely have to. By all means, if you don't have any other requirements on the table, write the naive factorial, FFS! But when you've got to abstract things, rule #1 is to make sure you pull everything "up" far enough, otherwise you're never going to extract enough value out of the added complexity to keep yourself sane.
- Periodic 16y agoI think there is a good way to figure out when to apply abstraction. The question to ask is whether the abstraction reduces the cognitive load on the reader. Abstractions that reduce the cognitive load by eliminating redundant code, highlighting commonalities, and reducing LoC are generally an improvement. Abstractions that create unnecessary complexity, obfuscate, or result in more LoC are generally a decline in quality. It may not be true in all situations, but I think this benchmark is a good one, as it accounts for the environment of the developer. If the code is only used once, it isn't worth it to abstract it. Once you have two implementations and need to add a third, you may realize they have commonalities and so abstracting and refactoring make sense.