10 ms·
Writing code with multiple overlapping side effects with a straight face
- jxramos 9y ago"...who firmly believed that the terser your code, the faster it ran. The author crammed multiple side effects into a single expression, used ternary operators like they were going out of style, and generally believed that run time was proportional to the number of semicolons executed, and every variable killed a puppy." How does one go about countering that mentality in some rule of thumb fashion if at all possible? Could a disassembly illuminate identical push/pop operations on the memory stack as explicit intermediate variables would? Maybe a simple intermediate result expansion timing profile side by side the terse code's profile?
- regularfry 9y ago"Show me a benchmark."
- jlangenauer 9y ago"Here are our coding guidelines. If you don't like them, the door is over there."
- thesmallestcat 9y agoThat doesn't work when you're handed a team of lazy idiots.
- animal531 9y agoOr even someone who is fast and/or good. He just goes and does it whatever way he wants, then because it's done it just stays that way forever.
- rkangel 9y agoYes, but what happens when the person in question wrote the guidelines and that's his attitude. Depressingly this is something that happens.
- make3 9y agoThen it's you who takes said door and starts looking for a more sane place to work at
- sawmurai 9y agoIf wouldn't even discuss this. If that the of code is that performance-critical just implement it in assembler.
- bjpbakker 9y agoIMO it does not necessarily need an assembler implementation, only if that is more clear. For performance enhancements where code readability suffers big time, I always want /proof/ by comparing the instructions (and some benchmarks help too).
- Someone 9y agoFor some microcomputer basics that was true, as they implemented the 'find that line' part of goto and gosub by a linear search from the start of the program, skipping from line to line. Having fewer lines, or having the most often called routines at the start of the program would speed up programs, potentially a lot.
- taneq 9y agoAlso various constrained environments (calculators and whatnot) where variables were significantly faster to access if they were declared locally than if they were global, or were faster if the name was shorter, etc. Not to mention the simple fact that, for old interpreted languages, the entire program text had to fit inside memory and so terseness literally saved you working memory.
- nerdponx 9y agoIn CPython, local function calls are still much faster than global function calls.
- blattimwind 9y agoSimilarly, in CPython function-local variables are faster to access than class or module attributes (with the usual bunch of exceptions you always have in Python). This probably applies to similar languages (like Ruby) as well.
- supergreg 9y agoI've seen people recommend shorter names for MongoDB keys for faster querying.
- warrenm 9y agoIn C64 BASIC, iirc, only the first two characters of the [case-insensitive] variable name mattered - so "vacuum" and "vacation" were the same, since the 'real' variable name was "va"
- mikekchar 9y agoI once went to a code review in a very, very large (but now defunct) corporation. The author of the code did not allocation the memory they were writing into. There were 8 people at the code review. I could not convince a single person that writing into unallocated memory was a bad idea. "Allocating the memory would be slow", they countered. "And besides, we've tested it and it doesn't crash". Sometimes you can help people become better programmers. Sometimes you can't. Try not to stick around in positions where you don't have the ability/opportunity to make a positive contribution, is my best advice. But assuming that the person is willing to listen to you, all of the above seem like good approaches.
- fundabulousrIII 9y agoWhat the hell? Why didn't you just initialize the pointer to NULL and ask them to compile and test?
- phkahler 9y agoWhat memory were they using? How did they get a pointer to it? Perhaps there was reason to believe it would not be used but they had lost that reason to time. For example on many micro controllers you can have the linker reserve space for various things (mapping structures over memory mapped peripherals is common) so maybe the memory they were using was guaranteed to be available somehow. Or perhaps they really were clueless.
- mikekchar 9y agoKeep in mind that this is more than 20 years ago, so my recollection of the details may be (probably are!) completely wrong. But this is what I recall. It was an embedded system with a proprietary language. The compiler didn't clear the variables when they were created (again, for performance reasons). For some crazy reason, the language allowed you to declare a pointer without initialising it. So what would happen is that the pointer would end up being whatever happened to be on the stack previously. The reason that it didn't crash in testing was because most of the code in the system was essentially copy and pasted from other parts of the system. As long as the previously run function had a pointer in the same location that happened to allocate some memory in the heap, and then deallocate it, this function would end up using the same memory and it would be fine. And this is exactly what happened in this case (because most of the functions had similar shapes and pointers were declared at the top of the stack frame). The main reason they were reluctant to change the code was because they had copied it from somewhere else and didn't understand how it worked. Because it didn't crash, they didn't want to touch it.
- pdpi 9y agoIntroduce them to the concept of static single assignment form, and how it is an intermediate form that enables a tonne of downstream optimisations. Most of those "wasteful extra variables" would be introduced by the compiler anyway, so it's not a problem. Alternatively, just ask them to prove it. If it's faster, they can benchmark before and after, and prove that it's faster? No proof means you're full of shit. This is the Right Answer in that it's what you should be doing anyhow to avoid performance regressions, but it's way more combative and can cause problems within the team, so must be done tactfully.
- madflame991 9y ago> If it's faster, they can benchmark before and after That goes bad very fast. I've worked with someone who tried winning arguments with one-line microbenchmarks; They even tried to convince me that I should always use undefined over null in JS since one is faster than the other (forgot which). > Introduce them to the concept of static single assignment form I failed with this as well; some coders don't trust the compiler/runtime at all. I had people tell me that garbage collectors don't deal with cyclic dependencies or that "simple comparisons" (<, >, ==) are faster than "compound comparisons" (<=, >=).
- nerdponx 9y agoTo be fair, in R on my machine > and < seem to be faster than ==, at least for integers. But the difference is miniscule compared to the overhead of doing just about anything else.
- arethuza 9y agoCraziest one I remember from my Java years (long long ago) was someone claiming that returning values by throwing exceptions and catching them to extract the return value was faster than simply returning the value.... This was wrong on a number of levels.
- sgc 9y agoTo be fair, they must have been full on bats--t insane.
- maeln 9y agoIt's the programming equivalent of the people who try to put as many complex word in a sentence as possible to look clever. I met some people who were trying their best to make everything a oneliner because "shorter is better" or whatever reason they had at the time. The real reason I always saw was that it was making the code "hacker-like" (and completly unreadable) and they could brag about how clever they were.
- yen223 9y agoOn the flip side, I met people who thought Python list comprehensions were "magic" and should be avoided at all costs
- falcolas 9y agoI've met people who have created entire programs out of nested list comprehensions; "they're faster than for loops!". I don't avoid them myself, but you would be hard pressed to convince me to nest them, or do anything that takes more than one line to express.
- whoopdedo 9y agoI think that's called Scheme.
- btschaegg 9y agoSame goes e.g. for Linq. I know of people who would prefer screen-filling methods of multiple nested loops with throwaway datastructures and various checks sprinkled on top to a simple three-liner Linq expression. Funnily enough, their argument always seems to be "it's easier to read this, since I'm accustomed to for (x;y;z) if (foo ||(bar && baz)) lorem = ipsum; as a pattern."
- dep_b 9y agoI can only imagine they got scared after seeing the SQL performance of Linq to SQL and all Linq to ORM based stuff after that.
- JustSomeNobody 9y agoYou don't. You reward that developer by allowing them to persue other interests. Or stick them on writing one off tools that will never be customer facing.
- ipnon 9y agoWrite a small program that uses a well-established compiler, then "optimize it" with all manner of clever tricks. Look at the assembly produced and you'll find nearly identical machine instructions with even more clever optimizations than you could have imagined.
- mannykannot 9y agoI think the problem is, to some degree, psychological. While the rational me knows there is very rarely any reason to prefer terseness over clarity of expression, the emotional me still gets a little thrill from one-liners and unconventional solutions (it can sometimes last right up until the point where I have to modify or debug code containing the damn things.) I think it is a pathological corner-case in an otherwise generally useful interest in, and appreciation of, innovative and economical problem solving.
- ridiculous_fish 9y agov8 at one point made inlining decisions based on character count. Terse code would be inclined more aggressively, and even adding comments could defeat inlining: https://top.fse.guru/nodejs-a-quick-optimization-advice-7353b820c92e https://top.fse.guru/nodejs-a-quick-optimization-advice-7353...
- notamy 9y agoI can maybe understand why v8 would do that, but why on earth would comments be included in that?
- maccard 9y agoBecause its faster to get the length of the string before you strip out the comments I guess?
- spoiler 9y agoIt's because back then V8 didn't save the AST in `SharedFunctionInfo`, so AST was inaccessible, so to avoid re-parsing and generating the AST to use AST-specific heuristics for inlining big function bodies, it used just the source length as part of the inlining strategy. This has been fixed for a while now with TurboFan[0] (also, now you can call `ast_node_count()` and get the AST count directly). [0]: https://github.com/v8/v8/wiki/TurboFan https://github.com/v8/v8/wiki/TurboFan EDIT: added information about `ast_node_count()`
- js8 9y agoSomehow I thought this would be about monads and using them to control side effects.
- quickthrower 9y agoI was hoping the opposite. That it would be imperative code but somehow you can sandbox the effects a bit like a custom monad.
- schindlabua 9y agoI use the ternary operator all the time. result = boolean_flag ? do_this() : do_that(); I think it's less noisy and nicer to look at then an if-else, provided the statements aren't too long. God forbid multiple side effects though, even in languages with less complicated sequencing rules than C/C++.
- dfgdghdf 9y agoI think that the ternary operator should only be used for side-effect free code. So: auto result = boolean_flag ? compute_property() : compute_another_property(); if (boolean_flag) { do_this(); } else { do_that(); }
- schindlabua 9y agoYeah, I agree!
- Mithaldu 9y agoWhat is your justification for the restriction though? Personally i dislike the latter form because it requires me to spend extra time reading what is boilerplate that is functionally identical to a ternary. Edit: Not impressed by the down-voting of an earnest question of someone trying to learn more about how other people see things, HN.
- ProblemFactory 9y agoTernary is the only option if you are using "B ? X : Y" as an expression, want to get the value of X or Y. If-else is the only option if you want to have multiple statements in a {} block, instead of only one function call. So it seems more consistent to use ternary for expressions and if-else for statement control flow.
- Mithaldu 9y agoWhy are those the only options? I know languages where if/else blocks return values. (Rust, Julia, Python, Perl, R) I also know languages that allow easy ways to embed multi-line blocks into a ternary. (Rust, Perl) I'm sorry, but i really don't follow your logic, can you try to explain it more clearly?
- d--b 9y agoWell, maybe a good way to avoid this is to have the compiler email the manager rather than printing a warning.
- fazkan 9y agoor summon nasal daemons http://www.catb.org/jargon/html/N/nasal-demons.html http://www.catb.org/jargon/html/N/nasal-demons.html
- skgoa 9y agoYou might be joking, but I know at least one major electronics company where the automated machine tools will send text messages to the plant manager in ever increasing frequency, when scheduled maintenance is coming up. Because otherwise those plant managers wouldn't allow the required downtime.
- wyldfire 9y agoIn all seriousness, management who takes things like static analysis results seriously is a valuable asset. We should consider teams that have management like this as superior to the ones who don't. They clearly value foresight and planning and will probably be less likely to host a chaotic/careless work environment.
- avip 9y agoThe pythonic example I've seen not once: {"some": "dict"}.setdefault("some", foo()) while expecting foo() to be short-circuited.
- raverbashing 9y agoThat's awful What are people expecting with that?
- avip 9y agoThey expect to have some form of "lazy caching": conf = {} def get_conn(): return conf.setdefault("db_conn", expensive_creation_of_db_conn())
- tomjakubowski 9y agoProbably expecting something like you get from collections.defaultdict: "dict subclass that calls a factory function to supply missing values": https://docs.python.org/2/library/collections.html#collections.defaultdict https://docs.python.org/2/library/collections.html#collectio...
- HCIdivision17 9y agoI thought, "gosh, that seems at least marginally useless; why wouldn't that short circuit?" and then just tried it. I tried a few variations, and about the only use I can think of is to signal as a side effect for when the dict is made. Fun trick to learn exists. Though I would really like to know what led up to it, since it seems ... unhelpful. Edit: Looking at other comments, I did try popping off that entry in the dict, and no, it didn't have anything in it (it doesn't make the dict anything like defaultdict).
- nerdponx 9y agoAs far as I know there is no native mechanism for short circuiting in Python. So anything that short-circuits should be considered a special case.
- userbinator 9y agoI wonder if the first one was meant to be a -= a * a; which evolved from a *= a; after the author thought "I need to subtract its square from itself, not just square it". I can see how this could occur in numerical code. I've noticed there are two opposing schools of thought on issues of "insane code" like this; on one side there's "use the language to the fullest if it makes sense to", and on the other is "avoid anything that might be the slightest bit confusing". I think that taking either of those to their logical conclusion is not a good idea --- in the former case, you'll end up with extremely dense and (initially) difficult-to-understand code, but the latter case will cause a gradual degradation of code into something approaching the verbosity of Asm, but with none of the benefits (e.g. "ternary operators are confusing, don't use them; multiple levels of precedence are confusing, so always fully parenthesise expressions; nested parentheses are hard to read, so don't use them either and only do one operation per statement; boolean expressions are confusing, so always use if/else; nested if/else are confusing, so don't nest and always refactor to use a function call, etc. etc.) It does seem that different languages vary in where they are on this scale, with more "exotic" ones like APL tending highly towards the former (interesting related discussion: https://news.ycombinator.com/item?id=13565743 https://news.ycombinator.com/item?id=13565743 ) and C#/Java towards the latter. The ideal is probably somewhere in the middle.
- lmm 9y agoIf you're not going to use it you're better off using a language without it - otherwise you pay all the costs (because you have to understand it to be able to read code, e.g. libraries you use) but get none of the benefits.
- chii 9y ago> a gradual degradation of code into something approaching the verbosity of Asm there's a simple rule of thumb - if you can't immediately and unabmiguously deskcheck the expression as it is written, it's too complicated and should be simplified.
- ipnon 9y agoPride, unfortunately, gets in the way of code being understandable instead of impressive.
- __s 9y agoClosest I've come to really embracing that kind of thing is https://github.com/serprex/pythonaes/tree/master/aespython https://github.com/serprex/pythonaes/tree/master/aespython NB it really is faster edit: nvm, Crumb is way more that kind of thing: https://github.com/serprex/Crumb/blob/master/C3.py https://github.com/serprex/Crumb/blob/master/C3.py I forgot I concocted that little devil
- proaralyst 9y agoAre you expecting that code to be maintainable? It looks like it's been run through a minifier...
- __s 9y agoNope, haven't had to maintain it in years. I did have to port it to Python3 which was a pain & grew it by ~30 bytes. The minifier was myself. It could actually be tighter but I optimized it for gzipability since I put it through a build tool to produce https://github.com/serprex/Crumb/blob/master/Crumb.py https://github.com/serprex/Crumb/blob/master/Crumb.py I even did a C version https://github.com/serprex/Crumb/blob/master/Crumb.c https://github.com/serprex/Crumb/blob/master/Crumb.c which is a pretty boring implementation besides while(p>q?(j=d==b?:i*q/p,i++<p):(i=c==a?:j*p/q,j++<q))
- wyldfire 9y ago> The people who would benefit from the warning don't have the necessary background to understand it. In my experience, this is accurate, but underscores a teaching opportunity. If you show Joe Beginner how to get the warnings, explain how they apply to the code and why the warning was good, they may slowly begin to appreciate the warnings. So that even if they don't understand, they won't necessarily dismiss the warning next time -- instead they'll go ask for help.
- nerdponx 9y ago"Did you mean..." or "this is a bad idea because..." type notes in error/warning messages are underrated teaching tools. The R package "data.table" does a good job at this. Just scroll through https://github.com/Rdatatable/data.table/blob/master/R/data.table.R https://github.com/Rdatatable/data.table/blob/master/R/data.... and search for "warning(".
- drewg123 9y agoI used to think I had a pretty good grasp on how the compiler was going to encode things. After spending months looking at disassembled FreeBSD kernel C code in a profiler, trying to hunt down cache misses, I'm realize that it is hard to count on anything and that my instincts, even after 25+ years of C programming, are often wrong.
- nailer 9y agoWhat does: a -= a *= a; Do? Looking through C# docs (which I assume this is, but don't have experience with), it seems A's value is changed to be the previous value of a, minus itself (ie 0) multiplied by a? In other words: a = 0 ?
- nerdponx 9y agoI would hope it's equivalent to a = a * a a = a - a That would be the "least astonishing" outcome for me.
- bryanrasmussen 9y agoI wonder if, from this: He gave as one example a book from an apparently-successful author (sales of over four million and counting) who firmly believed that the terser your code, the faster it ran. The author crammed multiple side effects into a single expression, used ternary operators like they were going out of style, and generally believed that run time was proportional to the number of semicolons executed, and every variable killed a puppy. If the described author could be identified? Perhaps not absolutely, but they do have some specific stylistic quirks combined with a particular sales level.
- jxramos 9y agoI noticed that books in general have different constraints as well in not having an infinite vertical view of the code unlike a text editor. The act of publishing to physical paper imposes constraints on line length both horizontally and vertically. I wonder if that drove part of it.
- mcguire 9y agoFrom one of the comments there, “Nathan Reed "July 19, 2017 at 4:34 pm " Didn't you just write such code with a straight face yourself only yesterday? ;) In your bytecode interpreter example: memory[NextUnsigned16()] = NextUnsigned8(); "Presumably the NextUnsigned8/16 functions have side effects of advancing a buffer pointer, so this is nearly the equivalent of p[x++] = ++x."
- nottorp 9y agoAfter gaining a bit of experience programming, I've developed for myself the following rule: If I feel particularly smart and proud after writing a piece of code, I look at it again and remove some smartness in favour of readability. Unless it's absolutely performance critical, in which case I try to comment the hell out of what's going on.
- taeric 9y agoFirst, I am not at all contradicting this. :) I just want to get that out of the way. I have found it pedagogical to read some more difficult programs, without trying to find ways to make them necessarily "read" better. In particular, generic and/or high performance versions of algorithms. To your point, this is precisely why I will refuse to generalize some parts of my code on the first pass. First, solve my problem specifically, then try and generalize. (This is not a waterfall style approach, but I do try and keep it somewhat phased.)
- nottorp 9y agoIMO don't generalize unless you find out later you need the same algorithm somewhere else. Otherwise, just leave the specific version in and stick some info in the comments (like a link to the high level description of the algorithm, if it's a common one that has a name, plus some pointers to your specifics).
- taeric 9y agoCompletely agreed. I do recommend playing with it (in like a repl or some other place) to see if you can possibly improve the algorithm, but have specific metrics for what an improvement would be. I'm reminded of a section in Programming Pearls where they explore several versions of a function, each time improving it. Too often I'm impatient and want the final solution first.
- euyyn 9y ago> First of all, there will be a lot of false positives. For example, you might write total_cost = p->base_price + p->calculate_tax(); > This would raise the warning because the compiler observes that the calculate_tax method is not const, so it is worried that executing the method may modify the base_price, in which case it matters whether you add the tax to the original base price or the updated one. Now, you may know (by using knowledge not available to the compiler) that the calculate_tax method updates the tax locale for the object, but does not update the base price, so you know that this is a false alarm. I think the warning in that case is still fair, and I'd be perfectly happy to get it. calculate_tax() could be modified to affect base_price with this compilation unit being none the wiser.
- corpMaverick 9y agoAgree. calculate_tax should be declared const. Or it should not be used on an expression like that.
- pmarreck 9y agoA lot of these pitfalls and concerns would just completely go away in a language that used immutable data structures pervasively
- sgt101 9y agoTwo unfashionable lessons that I learned when I was a practicing programmer rather than a hobbyist and loathsome manager. 1. Code is a communication system : between you and the next person to touch it. You are telling the other person how you made this work. Invest in this because the next person will probably be you in six months. 2. Compilers and type systems are your friends. Write code that extracts errors from the compiler by explicitly constraining things as much as you can afford to do. Avoiding typing or checking via long methods or low level datatypes is bad. Short typed, functional methods and high level data types are good.
- sgt101 9y agoTwo unfashionable lessons that I learned when I was a practicing programmer rather than a hobbyist and loathsome manager. 1. Code is a communication system : between you and the next person to touch it. You are telling the other person how you made this work. Invest in this because the next person will probably be you in six months. 2. Compilers and type systems are your friends. Write code that extracts errors from the compiler by explicitly constraining things as much as you can afford to do. Avoiding typing or checking via long methods or low level datatypes is bad. Short typed, functional methods and high level data types are good.