7 ms·
I'm profoundly unconvinced by this. "Handle special cases, then do the thing" is such a widespread, nigh-universal pattern that I don't think it hides anything
by fenomas 8y ago
I'm profoundly unconvinced by this. "Handle special cases, then do the thing" is such a widespread, nigh-universal pattern that I don't think it hides anything from the programmer. It's the pattern used by any function that checks its inputs!
Rejiggering an algorithm so as not to have special cases just puts cognitive load on whoever reads the code to suss out what the original algorithm was. "Why does this algorithm require its input array to be padded with zeroes? Oh I see, it doesn't - the developer just did that to remove some if statements."
- westoncb 8y ago> Rejiggering an algorithm so as not to have special cases just puts cognitive load on whoever reads the code to suss out what the original algorithm was. Sometimes that's how it goes, but other times the special cases were a result of just not knowing the original problem well enough to think of a clean solution that really captures its essence, and the special cases are evidence of a sort of resultant duct taping. Agreed though, what you're describing definitely happens and I'm not a fan.
- userbinator 8y agoIndeed, in the first example the special cases should really be moved out of the loop too, so you have the code looking like this: - code for left edge - code for body - code for right edge Now, both you and the machine don't have to keep thinking about the edge cases every time through the loop body. It's a win-win situation.
- elephantum 8y agoIt means you have two extra special cases: 1) list is empty; 2) left edge is the right edge (I.e. length is 1)
- riskable 8y agoNothing that can't be solved with more conditionals! Conditionals are great because they're clear and concise... if not list: # Can't do anything with this return # or raise error if len(list) < 3: # Ditto return #ditto Sure, there's more code but it's not bad code. It's just straightforward stuff that's easy to read and debug (if necessary).
- deleted 8y ago[deleted]
- zapzupnz 8y agoI'm glad I read this comment straight after I read the article because you've convinced me not to eliminate special cases in this particular manner. I'm sure special case elimination is useful and sometimes necessary for clarity, but only if not done at the expensive of said clarity. I would be open to seeing some examples that make clearer sense, though. A video I found much more convincing was Embracing Algorithms[1] from Apple's WWDC 2018. I just watched it last week, and I found the ways it broke down and deobfuscated rather simple problems into easier solutions that describe the problem and solution more concisely to be superior to what was in the article. [1]: https://developer.apple.com/videos/play/wwdc2018/223/ https://developer.apple.com/videos/play/wwdc2018/223/
- voidhorse 8y agoI think it depends on the situation. For example, under a functional paradigm the first problem is solvable as a special form of fold. The functional paradigm has tons of concepts backing it that make solving it in a ‘foldlike’ manner clear (which would involve the sort of padding the author suggests). In fact, the elimination of the special case is just a utilization of a monoidal operation and identity. In a lot of contexts “monoid” is a bunch of Mumbo jumbo, but in some functional languages it’s an incredibly clear way to go about resolving the issue. It’d also be perceived as more elegant than pattern matching against the first and last element of a list using some kind of predicate (aka a guard). I think a suggestion to think about your reader foremost resides at the core of your comment, and I think that’s what’s most important. In a context that’s predominantly iterative or object oriented you’re completely right—the special case pattern is far more pervasive and you’d be doing your readers no favors by abandoning it. However if you could expect your readers to come equipped with a host of functional idioms, the elimination suggestion isn’t so bad (at least from a semantic/comprehension standpoint, ignoring performance).
- pure-awesome 8y agoI'm struggling to understand your motivation at the abstract level. Would it be possible to provide a snippet of Haskell code with an example of a solution implemented this way?
- dpark 8y agoThis feels very much to me like architecture astronaut behavior. This overwhelming drive to pull "elegance" out of everything mundane or tediously complicated. Yes, you can sometimes rewrite your algorithm to eliminate "special cases", but if you do it at the cost of comprehensibility, it's not a net gain. In the case of these examples, we're not really even talking about "special cases", but edge cases, where you literally need to deal with an edge in the data. In the case of the neighbor sums, edge cases were masked by padding. In the case of the staircase, the edge is just obfuscated by the doubled labels and then hidden in a modulo operation. These are totally fine except that they seem to provide no value except "elegance", and they come at a high cost in terms of understandability. (The first step is literally "transform to a different problem", so now you need to understand the original problem as well as the new problem, and how they relate.) I'm all for elegance when it comes bundled with understandability or legibility or performance or even simplicity. But often it actually means "clever" and it's an extremely subjective measure that delivers little to no value and is prone to causing problems later.
- deleted 8y ago[deleted]
- arethuza 8y agoThat first example in the article was interesting because it made me realise that I have a strong aversion to pushing the complexity into the data rather than into the code. Modifying the list seems to me that it could be a cause of other bugs (what if the list is re-used and something depends on it?).
- DarkWiiPlayer 8y agoNever mind bugs, imagine that array has 5 million elements. Good luck re-allocating and copying that to add an element at the start and another at the end.
- dpark 8y agoI don't like the approach of pointlessly reallocating the array this way, but 5 million elements is not a problem on any machine built in the last 20 years. That's just 20 megs for 4-byte integers. With modern GC, this is cheap. It's not something you'd want to do in a tight loop (the copy time will start to matter), but for something like the example, the cost (in machine terms) is negligible.
- diminoten 8y agoCognitive load isn't some anathema to all of programming. The trick isn't to avoid it, the trick is to isolate it. Pretending like cognitive load is never worth the effort is wrong.
- richmarr 8y agoI see where you're going with this, but to be useful you need to split cognitive load (and the complexity it processes) into two groups. There's complexity inherent to the problem and complexity that's incidental to the problem. Cognitive load inherent to the problem should be isolated. Cognitive load required to deal with incidental complexity (such as clumsy abstractions, etc) is a waste of time and should absolutely be minimised.
- ummonk 8y agoI think where you can easily handle the edge cases, as done in the first and third` examples here, it's a good idea, because it's far easier to check correctness of the algorithm. In the particular case you mentioned it's perfectly reasonable to explain why the array is getting padded in a code comment.
- DarkWiiPlayer 8y agoThose examples clearly show how used programmers have gotten to the ease of use that languages like Ruby (the one used in the examples) provide. "Just pad the array with zeroes" sounds like an easy enough idea until you realize this might imply reallocating several gigabytes of memory depending on the size of your data set, possibly even running out of memory. Either way, the example implementation assumes the first and last elements are treated as having only one neighbor; which seems like an odd specification for the algorithm in the first place. To me it seems like the source of this code smell is a bit further up in the call stack. As for the second example, it becomes even more obvious how inefficient the "better" solution is. Just copy the entire array except the last element, in reverse order no less (so it can't just be memcpy()'d). This is what happens when you teach kids Ruby (or php, or pythin or...) because the job market demands it, but not the fundamentals (i.e. C, C++, Pascal, etc.). As for elegance, both "improved" examples seem extremely ugly to me. Not only do they turn a clearly readable if statement into something more magical, they also mutate their data before operating on it. Turning 5 lines of structured code into a one-line abomination isn't elegant, it's dumb. It decreases maintainability. It increases complexity. It makes room for errors to hide. It's, to put it simply, bad coding style.
- nikanj 8y agoAnd if you argue against this insanity at a code review, some clod is going to misquote Knuth at you. "Don't prematurely optimize" doesn't mean "just alloc half a gig of memory, and profile later if performance is unsatisfactory."
- efdee 8y agoIronically, complaining about the elegant solution of padding the array with "well this is going to suck on a multi-gigabyte in-memory dataset" is pretty much the definition of premature optimization. Chances are very high his dataset is not multi-gigabyte in-memory.
- dahart 8y agoNot really. Making a one-time allocation for a linear pass through an array is almost always bad. It’s often just as bad for many small arrays as it is for one big array. The idea to not prematurely optimize was never intended to be a free pass to use wasteful practices everywhere until there’s a horrible problem, it’s more about avoiding factoring your code into something you can’t maintain. Generally speaking, baking your corner cases into your precondition code is more dangerous than avoiding allocations. The idea was also not to leave concerns about performance completely off the table until your application is correct; it is assumed that you are making somewhat reasonable choices for your unoptimized code and your architecture, not that you are actively trying to slow things down with unnecessary allocations.
- cabaalis 8y agoExactly: In the first example, they take an array, copy it and add extraneous data for the sole purpose of eliminating a couple if statements? I'm not perfect either, but that deficiency for the sake of elegance is technical debt. A year later, when that piece of code is running thousands of times over thousands of numbers, nobody is going to know why it does what it does. Just that the system throws random out of memory errors or something.
- phkahler 8y ago>> Exactly: In the first example, they take an array, copy it and add extraneous data for the sole purpose of eliminating a couple if statements? The IF statements are not even necessary. Compute the sum for the first elements neighbors first (sum[0]=element[1]) and the last element (sum[n-1]=element[n-2]) Then loop over the remaining elements with no conditionals. I agree with the author that it's probably better to pad it than to have conditionals at every iteration, but for this problem there is no need to have a conditional at all.
- dpark 8y agoYour algorithm breaks for lists of size 0 or 1.
- riskable 8y agoMy thoughts exactly... The French have a saying, "With enough 'ifs' you could Paris in a bottle." This is what we, as software developers, do! We create conditions to make (amazing) things happen! Conditionals are the bread and butter of programming. They're what allow us to "fix" things in short order. They give us the flexibility to make our software work in a seemingly infinite number of situations. They also make debugging easier! Waking through the logic of even a zillion conditionals is quite straightforward and gives you that, "aha!" moment pretty quickly. Whereas walking through an algorithm that only works on a weirdly transformed data set just has us scratching our heads wondering why the previous developer felt the need to, "fuck with the data" instead of just writing a few clear and concise conditionals. Conditionals are also easy to split up! They give you lots of clear options if they start to "grow out of control" as it were. Whereas transforming the data in various code paths can rapidly create debugging nightmares!
- chriswarbo 8y agoThe tradeoff is that conditionals increase the number of paths through the code. It's like Tony Hoare said: > There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. I hit this problem the other day, writing some code to divide a set of items evenly into N subsets. I wrote a bunch of property tests for this, but kept having problems with the way it was handling remainders. I could have made the tests pass by adding a few more conditionals, but instead I tried to solve it using no more than one conditional plus a handful of arithmetic operations. The reason I constrained myself was that those test failures were a precious resource to help me understand the problem. Adding complexity to the solution could have produced a correct implementation, but I felt it would more likely out-smart my test suite. I would rather work with something that's clearly broken in an obvious way, rather than possibly broken in an unknown way. Eventually, I came to realise that the requirements I'd come up with were inconsistent!
- wtetzner 8y ago"Programmers are not to be measured by their ingenuity and their logic but by the completeness of their case analysis." - Alan Perlis
- jfengel 8y agoDoes that really cover everything that goes into a good measure of their code? ;-)
- wtetzner 8y agoNo, I would say that it's necessary but not sufficient.
- klyrs 8y agoIt's a good baseline. I once wrote a paper featuring about 20 pages of case analysis. This makes me good programmer, yes? Well okay I wrote a dozen paragraphs of code and a theorem-checking algorithm, and abused that algorithm to populate a human-readable latex template, and published that without shame. Such a good programmer.