> There’s nothing wrong with writing reusable functions, and most well-written functions will be reusable without adding any needless complexity. However, too often I’ve seen developers over-generalize a problem to the detriment of readability.
I think one of the main points why a lot of developers tend to over-generalize is because they are not specific enough with the name of the function they are trying to write. Although it looks dull, I find the best workflow is giving functions (or methods) very specific names.
I think there are two major advantages as to why you should do it:
- the problem mentioned in the text of over-generalising is minimised since you are forced to think about what exactly that method should do when creating it, not while writing it.
- other people using your code will (to a certain degree) instantly know, what the function method is doing without having to look at the code
I find the biggest problem here is defining "simple" between developers. eg: Concise and simple are not the same and can be at odds with each other. The article does touch on it with KISS > DRY but I'd love to see more examples that aren't close to WET.
Linters/static analysis is generally my first pass over a codebase that I inherit and rules/patterns can often find odd/archaic choices with simpler alternatives. I think writing code with a plethora of "automated eyes" provides some help with writing overall simpler code. Still, nothing beats experience and attention to simplicity.
I call that expertise in simplicity "style." Style is what you don't do. Have a style.
People have different styles. Sometimes they clash. That doesn't necessarily mean any of them are invalid, but they may not work together. It's easiest to work with people with complementary styles.
For a challenge, work with people with contrasting styles. This is likely to fail but it's where real innovation comes from. You don't get the Beatles without both Paul and John.
> For a challenge, work with people with contrasting styles. This is likely to fail but it's where real innovation comes from. You don't get the Beatles without both Paul and John.
I somewhat agree, although there are some "styles" that I've encountered over the years that are almost impossible to work with. Just like jazz, rock, country, etc. are different styles of music, there's very few people who will be happy listening to "child banging spoons on a pot". I spent a good portion of my career as a consultant that had to swoop in and pick up new codebases quickly; I've got my own style, and I've encountered a lot of styles that are different than mine that I can work with just fine, but yeesh, there's definitely just straight up poorly-thought-through and poorly-implemented code out there :)
Yes, riff on different styles, and don't automatically write off anything outside your own. But also don't accept trash :D
I honestly think the best way of accomplishing performance is constraints. Constraints allow you to nip performance problems in the bud, they will show you exactly where the performance problems are without guesswork. This makes them much easier to fix than doing it after the fact. If your staging environment is a raspberry pi, I can almost guarantee you will not have performance problems on your production machine.
Also, with proper encapsulation, readability problems aren't that bad. With a clearly defined and well designed interface, you can always just do a new take on the implementation. I often produce fairly messy code when I prototype something, but since I keep interfaces in mind, it's almost never more than hour or two to clean up into something readable.
Simplicity can only be a secondary concern, a sort of aesthetic thing that has no specific meaning. Often we think we know it when we see it.
A lot of businesses try to optimize for profits. This often means a mixture of:
1) Time to Market
2) Cheap employees / infra
3) Largest addressable market
None of these things will make your code simple. Now I'm not some sort of crazy person who thinks simplicity is no concern, I'm just saying it operates against other forces and it's the other forces that really define what simplicity looks like.
optimizing for simplicity is optimizing costs and speed of further changes. So, basically the same as 1) and 2). Effect isn't immediate, but it's not in a far future.
Except it isn't. Simplicity is just a proxy we assume optimizes for time and speed.
But it gets in the way when it leads us to hand write things in vanilla JS, not bother with a proper plugin system when one is needed, use text files instead of sqlite, use a raw UDP packet instead of MQTT, etc.
The biggest problem I've seen that keeps code from becoming simple is the fear of deleting it. Instead of understanding what a piece of code does and whether it could be pruned, people opt to extend it instead.
Another one is bad/thoughtless interface design; everything you add to a codebase can become a dependency for some other part, so give at least a minute or two of thought towards what the interface is and how widely it's exposed. If it's local, it matters less than some core abstraction used everywhere.
I'd like to see hard evidence on KISS vs DRY. I usually choose DRY, any day. If you don't have DRY, you have to make the same change in 5 slightly different places. But they're slightly different. Maybe even hard to find. Maybe some are different for a reason.
KISS first design seems to assume you will know the whole codebase, and you just shouldn't write anything too big for that.
Also, KISS people will tell you 100 lines of code are better than 2 mega frameworks. But you might only need 50 lines if you had those two frameworks. Your complexity has gone up maybe 10 times, but your performance is better and your readability is way better because.... there's less to read.
How Vue does it's thing is not my problem. I don't care how complicated it is.
It doesn't help that their KISS vs DRY example is a poor one. A single use is already DRY. DRY only applies when you have something getting duplicated, and if there is no duplication then you have nothing to DRY. So really it's KISS vs YAGNI that they're showing, and not KISS vs DRY.
Better would have been to show multiple, similar, validation procedures and then the DRY version, at least that would have been an appropriate use of the DRY principle. But even then, marshaling and unmarshaling the data like that is bordering on moronic. Generic, yes, but still bad code. Certainly unsuitable for any tight loops, and it only contains one notion of validation (is data present). It does not handle the case where the empty string may be a valid value. It does not include any kind of invariant checks (if foo.X in A then foo.Y in f(A), 1 < foo.Z < 1000, etc.). Once your validation routines include those, it'll become obvious that there is no good generic validation routine and having one validation routine per structure is DRY.
Sometimes if each repeated bit of code is slightly different the common method ends up being really complicated. I would rather make 6 simple changes than do one really complicated change.
The other problem with a DRY method is that you often increase cyclomatic complexity. Where each single method has one execution path (6 paths in total) the DRY method has 6 slightly different intentional execution paths, but 2^6 possible paths, this can make reasoning about the method impossible.
14 comments
[ 2.3 ms ] story [ 24.9 ms ] threadI think one of the main points why a lot of developers tend to over-generalize is because they are not specific enough with the name of the function they are trying to write. Although it looks dull, I find the best workflow is giving functions (or methods) very specific names.
I think there are two major advantages as to why you should do it: - the problem mentioned in the text of over-generalising is minimised since you are forced to think about what exactly that method should do when creating it, not while writing it. - other people using your code will (to a certain degree) instantly know, what the function method is doing without having to look at the code
https://zguide.zeromq.org/docs/chapter6/#Designing-for-Innov...
Linters/static analysis is generally my first pass over a codebase that I inherit and rules/patterns can often find odd/archaic choices with simpler alternatives. I think writing code with a plethora of "automated eyes" provides some help with writing overall simpler code. Still, nothing beats experience and attention to simplicity.
People have different styles. Sometimes they clash. That doesn't necessarily mean any of them are invalid, but they may not work together. It's easiest to work with people with complementary styles.
For a challenge, work with people with contrasting styles. This is likely to fail but it's where real innovation comes from. You don't get the Beatles without both Paul and John.
I somewhat agree, although there are some "styles" that I've encountered over the years that are almost impossible to work with. Just like jazz, rock, country, etc. are different styles of music, there's very few people who will be happy listening to "child banging spoons on a pot". I spent a good portion of my career as a consultant that had to swoop in and pick up new codebases quickly; I've got my own style, and I've encountered a lot of styles that are different than mine that I can work with just fine, but yeesh, there's definitely just straight up poorly-thought-through and poorly-implemented code out there :)
Yes, riff on different styles, and don't automatically write off anything outside your own. But also don't accept trash :D
Also, with proper encapsulation, readability problems aren't that bad. With a clearly defined and well designed interface, you can always just do a new take on the implementation. I often produce fairly messy code when I prototype something, but since I keep interfaces in mind, it's almost never more than hour or two to clean up into something readable.
A lot of businesses try to optimize for profits. This often means a mixture of:
1) Time to Market
2) Cheap employees / infra
3) Largest addressable market
None of these things will make your code simple. Now I'm not some sort of crazy person who thinks simplicity is no concern, I'm just saying it operates against other forces and it's the other forces that really define what simplicity looks like.
But it gets in the way when it leads us to hand write things in vanilla JS, not bother with a proper plugin system when one is needed, use text files instead of sqlite, use a raw UDP packet instead of MQTT, etc.
Another one is bad/thoughtless interface design; everything you add to a codebase can become a dependency for some other part, so give at least a minute or two of thought towards what the interface is and how widely it's exposed. If it's local, it matters less than some core abstraction used everywhere.
KISS first design seems to assume you will know the whole codebase, and you just shouldn't write anything too big for that.
Also, KISS people will tell you 100 lines of code are better than 2 mega frameworks. But you might only need 50 lines if you had those two frameworks. Your complexity has gone up maybe 10 times, but your performance is better and your readability is way better because.... there's less to read.
How Vue does it's thing is not my problem. I don't care how complicated it is.
Better would have been to show multiple, similar, validation procedures and then the DRY version, at least that would have been an appropriate use of the DRY principle. But even then, marshaling and unmarshaling the data like that is bordering on moronic. Generic, yes, but still bad code. Certainly unsuitable for any tight loops, and it only contains one notion of validation (is data present). It does not handle the case where the empty string may be a valid value. It does not include any kind of invariant checks (if foo.X in A then foo.Y in f(A), 1 < foo.Z < 1000, etc.). Once your validation routines include those, it'll become obvious that there is no good generic validation routine and having one validation routine per structure is DRY.
The other problem with a DRY method is that you often increase cyclomatic complexity. Where each single method has one execution path (6 paths in total) the DRY method has 6 slightly different intentional execution paths, but 2^6 possible paths, this can make reasoning about the method impossible.