5 ms·
Everyone saying "linear code doesn't scale" actually has it backwards - it's concise functions with a deeply nested call stack that really becomes a nightmare i
by s17n 3y ago
Everyone saying "linear code doesn't scale" actually has it backwards - it's concise functions with a deeply nested call stack that really becomes a nightmare in large codebases. It's never obvious where new code should be added, the difficulty of understanding what the effects of your changes will be increases exponentially since you have to trace all the possible ways code can get called, you end up with duplicated subroutines, etc etc.
99% of the time, you haven't actually come up with a good abstraction, so just write some linear code. Prefer copy/pasting to dubious function semantics.
- corethree 3y agoWell you're describing a readability problem. And you're essentially saying readability is what causes it not to scale. If we consider the concepts orthogonally meaning we don't consider the fact that readability can influence scalability then "everyone" is fully correct. Linear code doesn't scale as well as modular code. The dichotomy is worth knowing and worth considering depending on the situation. That being said I STILL disagree with you. Small functions do not cause readability issues if those functions are PURE. Meaning they don't touch state. That and you don't inject logic into your code, so explicitly minimize all dependency injection and passing functions to other functions. Form a pipeline of pure functions passing only data to other functions then it all becomes readable and scalable. You'll much more rarely hit an issue where you have to rewrite your logic because of a design flaw. More often then not by composing pure functions your code becomes like legos. Every refactoring becomes more like re-configuring and recomposing existing primitives.
- bcrosby95 3y agoI disagree. It's not the purity of the functions, its having to know the details of them. The details, which could have existed here, are now in two other places. If you need to figure out how a value is calculated, and you use a half dozen functions to come to that value, you now have a half dozen places you need to jump to within the codebase. Small functions increase the chances of you having to do this. Larger ones decrease it, but can cause other issues. Also, many small functions doesn't make code modular. Having well defined, focused interfaces (I don't mean in the OO sense) for people to use makes it modular. Small functions don't necessarily harm it, but if you're not really good at organizing things they definitely can obscure it.
- tonyedgecombe 3y agoI find how easy it is to name something is a pretty good indicator. If I'm struggling to name a function then it probably needs some more attention.
- gen220 3y agoI think you’re right about side effects being the missing ingredient to this discussion, that is leading people to talk past each other. The pattern’s sometimes called “imperative shell, functional core”. And I totally agree, this is how you write large code bases without making them unmaintainable. Where to go “linear” vs “modular” is an important design choice, but it’s secondary to the design choice of where to embed state-altering features in your program tree. I think people dislike modular code because they want to have all the “side-effects” visible in one function. Perhaps they’ve only worked in code bases where people have made poor choices in that regard. But if you can guarantee and document things like purity, idempotency, etc, you can blissfully ignore implementation details most of the time (i.e. until performance becomes an issue), which is definitionally what allows a codebase to scale.
- corethree 3y agoYeah few people have seen the light. But you're right. The only downside is performance. But this is rare and sparse.
- gorgoiler 3y agoAnother risk is if you add print_table() then someone else is going to find it and use it in their code, but also add a little flag to adjust the output for their use case. 12 months later you have: print_table( rows, headers = None, is_unicode = False, left_align = False, align = [], remove_emoji = None, max_width = 80, potato_mode = 7, _debug_frontend = not FLAGS.dont_debug, ellipsis_for = 0, no_print = False, )
- ncann 3y agoI think we all know at least some functions like this in a code base. All it takes is for a newcomer to come across a complex function that they need to update some logics for but also don't understand it enough to refactor, so they just added some parameters with default values and call it a day. > no_print = False love this
- reedf1 3y agoTo play devil's advocate - what's the issue with this? Is print_table() + print_table_without_emoji() better than print_table(remove_emoji= False)?
- myrmidon 3y agoThe issue is that this approach is almost guaranteed to produce basically untestable code with a myriad of invalid/nonsensical/completely broken input combinations, and its impossible to refactor, too, because you don't even know which parts of the parameter space are actually ever needed, or even how they are supposed to interact. Whenever function semantics need to change, everything degrades further because of refactoring uncertainties (=> you end up with even more parameters). This will also be extremely resistant to optimization because even finding the "happy path" is non-trivial.
- leonseled 3y agoAs a fresh dev, I’d like to know the answer to this as well. Abstract to function w multiple params, abstract to multiple functions, no abstraction and keep as switch statement. `print_table() + print_table_without_emoji()` vs `print_table(remove_emoji= False)` vs `switch table_name: case emoji: print(table) case no_emoji: print(table no emoji)`