9 ms·
One hard fought lesson I’ve learned over the years is that copy/paste is often a very good solution. If the downside is that a developer has to manually spray a
by mlthoughts2018 6y ago
One hard fought lesson I’ve learned over the years is that copy/paste is often a very good solution. If the downside is that a developer has to manually spray a change to 50 different locations with the same copy/pasted implementation snippet, that’s really fine. Even for 500 or possibly 5000 locations is fine, with a good editor available or other tooling. Testing these changes for correctness is easy.
Meanwhile the cost for getting an abstraction wrong is often far worse. And it’s really easy to get wrong because abstractions by their nature are always built based on yesterday’s data plus good intentions. People are arguing about subjective theories and unmeasured concepts of extensibility, wasting time arguing about Liskov Substitution Principle, SOLID, dependency injection, type system design patterns, etc., but it’s mostly junk that just adds code bloat.
Obviously there are other solutions that don’t require premature abstraction or copy/paste 1000 times (like simple module functions as the core unit of reusability, or a macro system for code injection). But the point is copy/paste gets a bad wrap. It’s simple, straightforward, easy to automate, easy to test, and adds no extra concepts to the code.
“Functional core, imperative shell” is the best advice I’ve found. Ruthlessly avoid object orientation, and when you need it, stick to extremely shallow inheritance. Make everything a module function, and when you write data structures, don’t give them member function logic for their core functions (like search, sort, add items, remove items), rather create functions that accept data structure instances as arguments and perform these operations with no class-like internal state.
- im3w1l 6y ago“Functional core, imperative shell” I would say this in a slightly different way myself. "Mathematical core, business shell". Code in the mathematical core is defined by it's behaviour. "sort", sorts a list. Functionality in the business shell solves business problems. For instance "add_customer". In a year's time, sort will do the exact same thing. But "add_customer" may update a different database from what it used to, and in a different way. Code in the mathematical core will never become incorrect because the world has changed. But it can become irrelevant. Sorting may no longer be needed by the application.
- tigershark 6y agoNot exactly, the functional core/imperative shell paradigm has nothing to do with the data model/business logic distinction. It’s used in functional languages to create an imperative boundary with the real world and its side effects. For example in the functional core you’ll have no nulls and all the business objects are valid. In the imperative shell that interfaces with the real world you validate all the inputs, build your valid objects and handle all the external error conditions.
- dragonker 6y agoCould you elaborate on '...when you write data structures, don't give them member function logic,' please?
- mlthoughts2018 6y agoI mean heap_sort(my_array) is strictly better than my_array.heap_sort() The main reason is that fluent interfaces (the second format) makes it much harder and require more code to do patching / mocking / dependency injection, since you need to carefully patch around instance creation and ensure the patching only applies to specific instances during tests where some objects need patching and others don’t. The first interface above just needs the simple top level name “heap_sort” patched or mocked with easy control of the scope. The act of array construction and the knowledge of internal array data is completely isolated away from the act of sorting. As you start adding parameters to the mix and involve highly stateful internal operations, start mixing with inherited methods, class methods and static methods, and as you chain the fluent interface (eg foo().bar().baz()) all these effects get worse and test complexity becomes unberable. Basically it boils down to function composition. f(g(x)) is simply a better way to structure programs than x.g().f() And if you’re worried about writing f() and g() once and not rewriting an implementation, there are many ways to achieve this without classes - the best way being use of GADTs to define exhaustive pattern matching in f() or g() that allows custom implementations matched on basically simple record types or named tuples, instead of defining the custom implementation in overridden inheritance methods. You don’t need pure functional programming or compiler enforcement to do this either. It is how I’ve structured large C and Python programs in business settings for many years.
- rileymat2 6y agoThe promoters of oo would not disagree with this example. Clean Code goes into a lot of subtle detail about data structures v. objects. https://blog.cleancoder.com/uncle-bob/2019/06/16/ObjectsAndDataStructures.html https://blog.cleancoder.com/uncle-bob/2019/06/16/ObjectsAndD...
- andi999 6y agoExcellent pointsm What does GADT stand for though?
- crimsonalucard5 6y ago>“Functional core, imperative shell” IO by nature is an imperative phenomenon. Inputs, outputs and prompts happen in procedural temporal steps. Due to this, you have to make the shell imperative. You have no choice because reality is imperative and the shell interfaces with reality. Unless you're thinking about haskell, but even haskell has a black box that takes the IO monad and does imperative things with it.
- wh33zle 6y agoAdding to this, copy/pasting things works especially well if the code is contained within another abstraction that is much more unlikely to change. For example, say your service has a REST API. It doesn't matter much, how the functionality is implemented as long as you are fulfilling the API contract. It is so much easier to just get the functionality down first, properly test it and once it is all there, good abstractions within the implementation might just surface from looking at the (duplicated) code.
- tigershark 6y agoNo, having to change the code in 50 plus locations is not fine at all, it’s criminal. In a team there will be always someone that misses some of the locations and the implementations will inevitably diverge over time. And at that point every time that anyone needs to do a change he will need to manually fine tune each of the implementations and test each of them since their behaviour may well be different.
- mlthoughts2018 6y agoYour concerns (missing locations and divergence over time) are fairly easy to address as copy/paste scales. Of course there are downsides, just as there are major downsides to solving with abstraction, it just comes down to a tradeoff. Many times copy/paste wins that tradeoff but is refused because our industry has a cult obsession with abstraction.
- tigershark 6y agoHow do you address them fairly easily?
- mlthoughts2018 6y agoVarious editor tools, code quality tools like sonarqube that can be configured to track duplications, writing code generation tools (usually around templating or macro libraries), and heavy coverage of unit testing. You can do “test once, reuse everywhere” without any inheritance at all. You could use metaprogramming, sealed types with pattern matching, or macros/code-gen. It’s just tradeoffs to decide between them. The point isn’t that creative abstraction never wins that tradeoff, rather just that it wins very infrequently compared to how often it is preferred based on parochial “software design” hand wavy reasons.
- tigershark 6y agoSo you will need external tools to find duplication and edit all the locations hoping that everyone in your team does the same. In the meantime instead of having 30 lines that solve the problem you’ll have 30*50=1500 lines spread everywhere that do exactly the same thing. With a proper abstraction you can just look at one line and understand what it does, with your solution you need to look at 30 lines for 50 times. And all this because you are incapable or too lazy to find a proper abstraction? I worked on projects quite big with a lot of copy and paste and only who never had this “pleasure” can be in favour of copy and paste. Code generation, meta programming, macros are all tools to avoid duplication not to allow you to copy and paste multiple times.