Hacker News new | past | comments | ask | show | jobs | submit login
Nerdsniping: A glimpse into a stubborn mind (andyet.com)
145 points by philip_roberts on April 14, 2014 | hide | past | favorite | 92 comments



Yikes. I understand that companies use these posts as a form of advertising, but seeing your "application security specialist" struggling with such a basic expression gives me pause.


I was working with this guy - he always tried to prove that everything is possible wasting time. He ALWAYS over-complicated simple things. He was horrible to work with and everyone hated him.

Not to say you are just like him etc. but this post reminded me of him because it was probably exactly what he would do.


Wasting other people's time is a negative, but deepening your understanding of a topic by examining edge cases is generally worthwhile, IMHO.


I think the difference really lies in WHEN you do the examining. If you think to yourself, "Hey, that's cool, I'll check it out later," then there's no problem. However, when it gets to the point where you're thinking, "My code must be 110% correct! This is an immediate priority!" then maybe you should be re-evaluating your decision making process.


I think he's referring to some people's inability to balance technical issues and product commitments (deadlines).

Focusing solely on the means and disregarding the ends is a good way to not deliver anything useful. Most times, things need to be good enough, not technically perfect...


> Most times, things need to be good enough, not technically perfect...

This sentence means almost literally nothing, given how varied both "good enough" and "technically perfect" definitions are for each developer.


Is it really fair to call poor code an edge case? If not for the double-equals typo, which most code reviews would fix, this wouldn't work.

I'm more interested that this team has conversations where person A asks a really simple question that should be able to be tested in 11 seconds, person B gives a patently false answer that mistakes && for ||, which is the very thing person A is asking about. Meanwhile person C goes and spends 11 days looking for a loophole and writing a blog about it.

As a person gearing up for my first web dev position, I really wonder if this is what constitutes professional Javascript development and ask myself if I shouldn't be applying to jobs already.


No, person C saw an interesting question, and decided to go on an exploratory journey to see what's possible. In any job requiring mental aptitude, you'll find natural curiosity an asset, as long as you keep it within bounds.

To be clear, anyone that's presented a question about a tool they are using that may pertain to it's use (be it efficiency or correctness) that shows zero interest in the answer (whether or not they have time to pursue an answer) is not someone I want to work with or manage, and should probably find another career, because the one they are in isn't holding their interest. Work doesn't have to be the most important thing to you, but if you don't like your job, the people around you WILL notice.


I'm not sure your second paragraph is clear, to me, at least. Person C admitted that their only contribution to the conversation was to announce that they had been nerd-sniped. Which means that they showed zero interest in the "question they were presented with", because they were sidetracked by another, more interesting (to them) problem.

Meanwhile, person A's question went unanswered. I would consider this a case of not keeping curiosity within bounds. Wouldn't you?

I agree wholeheartedly that curiosity is a great motivator, one that I would want driving all my coworkers. But as this blog presents it, the curiosity suffers from the greatest threat to curiosity, which is a lack of discipline that ensures the curious person never rises above a dilettante. I speak from first hand knowledge here - I've wasted more than my share of time doing something just for fun that benefits nobody.

If it were not the case that Person B, the implicit authority figure in the scenario, gave a totally backwards answer to Person A, OR ( || ) if it were not the case that Person C failed to alleviate the confusion of Persons A and B, then I would not see any problem with going off on a trip down pedantry lane. As it is, the whole organization looks like a gang of clowns. Nobody can answer the most basic of syntax questions, and one person even takes a faulty intuition ("b can be both 80 and 443") and follows it on a Quixotic quest into the arcana of the language spec. It's almost as though he stumbled on the real value of the quest (how evaluations of == and === are actually implemented) by sheer accident, in spite of himself. Luckily, many quests follow this pattern.

For what it's worth, I do find the discussion interesting. But it's interesting in way different than that of edge cases. Deliberately breaking something by using it in a way it was never intended to be used can be enlightening if you want to fend off would-be attackers. It exploits the system's implementation constraints. On the other hand, an edge case highlights the edges of legitimate use which the system was never designed to account for. It exploits the system's lack of comprehensiveness. The distinction is a good one, I think.

For example, consider a chair. An edge case might be a 600-pound man. The engineers didn't take this legitimate use of the chair into account when they determined the thickness of the material. That's an edge case. It's not quite the same as saying that the engineers didn't take into account that wood is flammable. This example is not the best, but I'm now well too far into my own trip down pedantic lane to spend any more time trying to improve it. :)


It's not made clear from my post, because I felt it was entirely irrelevant to the context of the post itself (which was merely intended as an interesting meander through the thought process and experiments my mind went through over time) but I stripped a number of comments from the screencap wherein the question was in fact answered.

Had I suspected the quality of my team, or my discipline would be under question, I would have left them in :)


how is the operation of "==" an interesting question to anyone but a novice javascript developer? I think this post is going to give me an aneurism.


It's interesting because it looks at the implementation of '==' vs '===' and how this behavior can be modified at runtime. Every good "hacker" should find this interesting on some level.


I don't know if the last bit was added later, but he did find a solution which would work without using ==


I think it's worth pointing out the article writer recognizes this is a distraction. In the comic, nerd sniping causes people to forget they're crossing the street and they wind up being hit by a car, which is a pretty serious distraction from the task at hand.


And this is why Javascript can be such an awful language. Leaving off a single equals has such a profound effect, often without a new coder even realizing it while reading the code.

Imagine what happens to your code when someone tries to write a function for its side effects similar to the example seen in the blog post. Then releases it in production for it to break in six months with a feature change.


> And this is why Javascript can be such an awful language. Leaving off a single equals has such a profound effect, often without a new coder even realizing it while reading the code.

You get used to this as you get seasoned with javascript. Every language has it's quirks. The human brain is remarkably adaptable to using tools & dealing with intricacies. I personally utilize & appreciate the difference between == and === to reduce complexity in the code. I assume the reader also understands such differences.

I also find automated testing, logging, a module system like commonjs, and linters to be useful when programming in javascript. Once these are in place, systems written in javascript are remarkably maintainable & scale with complexity.


Nothing you said actually differs from what I said.

I spent seven years as a Perl programmer. Like Perl, a language can be extremely productive in the hands of a veteran and dangerous with a newcomer due to the unnecessary confusion it generates. At least eq and == are syntactically easier to grok than == and ===.


>> I also find automated testing, logging, a module system like commonjs, and linters to be useful when programming in javascript

Except when I use JSLint. Talk about bringing you back to Earth when you think you've done something remarkable with JS.


Try JSHint instead. It catches actual problems and everything else is customisable, so you're not forced to change your coding style to fall in line with the whims of a stubborn author.


At the end of the article, it is demonstrated that this can happen even with strict equality by overriding the getters. Any language with getter/setter support can do this. Operator overloading can be used for even nefarious ends.

The point is that side effects in code are dangerous, and can be used to mislead a reader.

If this happens, it is not the fault of JavaScript. Anyone who writes code like that for purposes other than demonstration or learning is a moron.


Not all languages with getter/setter support will invoke them automatically like this.

For example, it's not possible in Java or Objective-C to make an expression of the form a == b, where a and b are plain variables, have any side effects.


In Obj-C you can if you would allow `a.prop == b.prop`.


Sure, but then it's obvious that you're doing something beyond an equality comparison.


Do you mean it is obvious to people who know objective C? I've never used the language, but its not obvious to me just from the other c-family languages I know.


Yes, if you know Objective-C. If you don't know it, I wouldn't expect you to immediately grasp anything about the behavior implied by a snippet of it.


>"And this is why Javascript can be such an awful language."

To be fair, this is breaking Javascript and that is something you could do in other languages, for instance here is a similar version for C++:

http://pastebin.com/seySfpku


> #define int INT

This makes it somewhat obvious that you can't change how basic types work in C++. On the other hand, JS's dynamicity makes for some interesting situations, as seen here. I wouldn't consider it "awful" but it's certainly a language with more "deep complexity" than appears at first glance.


Now do it in Haskell.


Pedantry aside... are you really going to defend Javascript's core method of function handling as good design by using the C function pointer as an example?

On some level, defending JS using C is like saying you can also make a souffle by instead cooking the chicken with the egg still inside it. Both JS and its bad paradigms came from C. You don't arrive at good design by showing that other people also got it wrong.


I think you are missing my higher point. I am not defending JS or C++ for that matter (please note that I was not using C), but the fact that giving enough time you can break most languages to do something silly, in which case I don't blame the tool but the people using it.


There are ways to do stupid things in any language. I don't think anyone would ever tell you that redefining an integer as an object and overwriting its prototypes is an acceptable way to solve...any? problem.


I'm glad that you said, "can be" because language flexibility is a two way street. You can abuse dynamic typing for good and for evil. I, personally, don't think that's a case for declaring it not worthwhile, rather it reenforces investing the time to properly understand the language.

Fortunately, at this stage, we get the best of both worlds with syntax validators and linters. Abuse as appropriate (whatever that means) and be warned about the rest.


Yeah, just use python!

  class B(object):
      def __eq__(self, other):
          return True

  b=B()
It's much more expressive


Okay.


> Imagine what happens to your code when someone tries to write a function for its side effects...

The language used can hardly be blamed for that.


And then you realize that javascript runs everywhere and you smile to yourself and continue coding away.


The differences between programming syntax and elementary/Boolean algebra are very awkward to understand clearly and deal with correctly. We clearly understand mathematical algebra to work one way, and we naturally assume programming algebra works the same way, which it doesn't at all.

Here, the antagonist says that b can't be 1 and 2 at the same time, which would be self-evident in mathematical algebra, but turns out to be quite irrelevant to Javascript and to programming paradigms generally (since two statements will never be checked simultaneously).

This difference in how syntax is understood actually presents a barrier to programming for modestly trained mathematicians, who would otherwise be expected to excel.


>This difference in how syntax is understood actually presents a barrier to programming for modestly trained mathematicians, who would otherwise be expected to excel.

Although this is true in general that syntax can be a barrier to entry, the article provides a particularly poor example. If anything, the article demonstrates how mathematical training is good preparation for many pitfalls of programming.

Mathematicians are used to working with various equivalence relations, even in the same context. So if a modestly trained mathematician saw == and ===, she would immediately ask "what is the difference between these two ER's?".

And then when she finds out the essential difference is that you can override ==, it would be clear that all bets are off.

I can't imagine any mathematician taking more than a few minutes and a google search -- let alone 11 days -- to figure out this loophole.


> Mathematicians are used to working with various equivalence relations, even in the same context. So if a modestly trained mathematician saw == and ===, she would immediately ask "what is the difference between these two ER's?".

This perhaps illustrates my point. While you say == and === are equivalence relations (I would argue they are not), there is a wider problem.

= itself is an assignment operator and should never be considered as an equivalence relation. But it is normal to consider = as algebraic equality for anyone who has completed high school math.

It's a great loss to intuitive understanding that we use = for assignment. If anything, considering Javascript's == and === is a net benefit as programmers are more acutely aware of the issue here.

What percentage of time spent learning to program and recovering bugs is caused by overcoming this misintuition? It's surely a lot.

Wikipedia has further discussion: https://en.wikipedia.org/wiki/Assignment_%28computer_program...


> I can't imagine any mathematician taking more than a few minutes and a google search -- let alone 11 days -- to figure out this loophole.

If anything, mathematicians are incredibly used to overloaded equals operators.


> If anything, mathematicians are incredibly used to overloaded equals operators.

That's my point. The == sign meaning many things is unsurprising (and key to this "hack"), whereas explicitly using two different symbols for equivalence in the same expression is a huge, glaring signpost to pay attention.


As demonstrated in the postscript, it can still be accomplished when the symbols are all the same. So that difference is not the key to the hack. As ronaldx noted, the key is that a variable can change value in between two equivalence evaluations within a single statement.


Arguably the thing that tripped me up for the majority of the time was latching on to the fact that == and === are different, and trying to _always_ satisfy the two conditions, rather than just spotting that I can change the reference to b from within the first valueOf call. I'm not sure how much I'd read into the "if I was more of a mathematician and less of a dumb programmer this would have been solved trivially" argument.


If a == b iff a === b, then == ~ ===. If == and === are different (which they presumable are, because otherwise we would only use one symbol) then by definition there exist counter-examples of the thing you were trying to _always_ satisfy.

In other words, it would never even occur to a mathematician to make your mistake...

More to the point, a mathematician wouldn't assume that some arbitrary predicate that you are allowed to define at will behaves the same way as a system-defined equivalence operator.

Edit: Added last paragraph and readability changes. Also, ~ is another symbol for equality, this time equality over binary relations.


The postscript is irrelevant to the claim made in the original post on this thread. It's a conflated example and not indicative of real programming.

if "get" returns different values when called multiple times within a single expression, one of two things must be true: * Someone's overriding "get" with a mutation to the variable. In all but the very rarest of circumstances, that someone should be publicly shamed. It's a nice hack for the purpose of the article, but not real code. * there's some very fine-grained concurrency. This case is irrelevant to the OP's claim because concurrency of this sort is very likely a fundamental characteristic of the system at hand, and never an artifact of the language.


Which claim is that, exactly? And are you implying that the author's original solution is more indicative of real programming?

Both solutions work the same way - when accessing the value of "b" for the first check, b is mutated such that the next check will return a different value. Both of them are equally bad practices.

The == vs. === issue is the part that's actually irrelevant, it served as a red herring.


> Which claim is that, exactly? A

"This difference in how syntax is understood actually presents a barrier to programming for modestly trained mathematicians, who would otherwise be expected to excel."

> And are you implying that the author's original solution is more indicative of real programming?

Well, neither is... but yes, overriding == or .equals is somewhat common.

> when accessing the value of "b" for the first check, b is mutated such that the next check will return a different value.

The difference is that you could easily imagine a sane example where x == y != x === y, whereas it's hard to imagine a situation where I would want to override get (or ==) with a method that mutates the requested variable.

> The == vs. === issue is the part that's actually irrelevant, it served as a red herring.

For the claim re: syntax and compatibility with mathematician's expectation, it's exactly the other way around.

FWIW I feel the quoted claim re: syntax claim is interesting, whereas the contents of the article (summarily, "you can override == or .get and cause crazy stuff to happen") is unsurprising and kind of silly to most programmers even moderately experienced with OOP in dynamic languages (for the reasons given in this post and others -- everyone knows about it, it shouldn't take 11 days to figure it out, and in practice no one does purposefully obscure stuff like mutating values when == or .get is called).

edit: grammar


Umm , you missed a key point here. The first comparison is == while the second one is ===. These 2 are different. The == checks equivalent. Mathematically speaking he should have used === in both places.

These things are more of quirks of javascript than anything else. The post did nothing but made me hate javascript. Try doing the same in Java !


> Here, the antagonist says that b can't be 1 and 2 at the same time, which would be self-evident in mathematical algebra, but turns out to be quite irrelevant to Javascript and to programming paradigms generally (since two statements will never be checked simultaneously).

This isn't really a problem if b is enforced to be immutable.


Nerdsniping reminds me of the Walt Whitman poem:

   There was a child went forth every day;
   And the first object he look’d upon, that object he became;
   And that object became part of him for the day, or a certain part of the day, or for many years, or stretching cycles of years.
It's especially bad if you'd really rather be doing something other than what you're currently doing.


"I want my code to execute if port is 80 or 443, and http is false"

Couldn't you simply do this?:

if ((a==='80'||b==='443') && http===false) { ... }


I think the goal was

if (a==='80' || (b==='443'&&http===false)) {...}


I think you're right, but it's funny that both the code and the written explanation do not make that clear. Missing parenthesis in one case and missing comma in the other.

I feel like I tend to use parenthesis when they are not required, but I err on the side of caution and I think it makes things more readable.


I use a lot of unneeded parentheses. Makes the intent of the program clear. If the expression gets ugly, factor out subexpressions into boolean variables. If you are depending on short circuiting to avoid computing costly subexpresions, make the dependency explicit with a nested `if`.

I had a thought the other day: Complicated code should look complicated. Don't try to hide your convoluted logic. In the end, someone will have to mentally unpack it into the multi-line verbose version to understand it anyway.


That's an excellent thought.

I feel like programmers too often try to make something complicated "pretty" -- but really this just usually has the effect of making it that much more difficult to understand.


Either way, it's called "Boolean Logic" and as far as I knew every Computer Scientist had to take this class in college...Not sure how you could get a job as a programmer without understanding that...


Call me nitpicky, but it always bugs me when someone references some content, but doesn't link to it. Specifically the XKCD and Stackoverflow links. Link to the comic and question specifically, rather than making me go hunt for that content.


> I guess we’ve messed with the prototype too much, and JavaScript isn’t really convinced it’s still a proper number anymore.

You're comparing a literal number with an object, which are not the same type.

  2 === new Number(2)
  // => false
  2 == new Number(2)
  // => true


> Have I learned anything directly useful?

You probably haven't. But a less experienced programmer has learned why you should always use ===.


The example at the end of the article shows how you can have the same problem while always using ===.


The example at the end of the article shows how you deserve all sorts of bad things happening to you if you mutate values in a freaking getter.


I think it's a great example of why overriding the default prototype functions is a bad idea.


Might not be Java but still interesting, it made me think of one of my favorite little quirks in C++:

  #include <cmath>
  
  //Floating point model: Strict:Precise:Fast
  //Will code execute
  float a = nanf("");
  if (a == a)//S:No P:No F:Yes
      (do something);

  if (a != a)//S:Yes P:Yes F:No
      (do something);

  if (a < 0.f || a > 2.f)//S:No P:No F:Yes
      (do something);

  if (isnan(a))//S:Yes P:Yes F:Yes
      (do something);

NaN's (Not A Number) can propagate a long way through your code, possibly reaching places where they cause real problems. When dealing with input, especially networking, one should always check for NaN's. Basically the rule with NaN's is: The comparison always returns false if any NaN is involved. But as you can see; specifying the fast model throws that out of the window. (Code was otherwise unoptimized.)

In the third statement an otherwise fine check is done to make sure the value in a is sane, it doesn't get changed but its certainly not what you'd want it to be.

Whole lots of fun can be had when serving this to game servers. :D


I think there was a more general question here that was missed: can a == x && a == y ever be true for any arbitrary values of a, x, and y, where x != y.

From a logical point of view, no, this can never be true. I would suspect this can never be true in javascript, and could only be made true in a language where you can override == to always return true.

I think when most developers use the word "never", what they really mean is "never (within the current context)". This makes conversations a lot simpler. Imagine how difficult conversations would be if you always had to qualify never. "This can never be true (assuming a weird valueOf method hasn't been defined and assuming I didn't modify the javascript interpreter to always return true for == and assuming ...)".


I think there are a few cases of non-transitive equalities in JavaScript, specifically around falsey values. Part of it also has to do with whether x is the right-side input or the left-side input, because that changes type coercion rules.

Aha, found one:

  ['0'] == 0
  > true
  [0] == 0
 > true
  [0] == ['0']
 > false
Transitivity of equality can also never be relied upon when dealing with Floating Point numbers. This is correct and unavoidable behavior, but still surprising to many developers.


That's not due to transitivity: ['0'] == ['0'] (and for that matter, ['0'] === ['0']) is also false. These operators compare objects for identity, not structural equality.


Something like this could work, to some degree:

        a = {}
        a.valueOf = function() {
           var caller = arguments.callee.caller.toString();
           //parse the caller function, and return 
           //different values based on what comparison is being made. 
        }
But I think this is even worse than the getter on the global object.


Here's a JavaScript example, based on what was in the article.

    var b = {c:0};
    b.valueOf = function() { this.c++; return this.c; }
    b == 1 && b == 2
    > true


That only works for consecutive integers, not any arbitrary values.


It's not often I see an HN thread which so aptly demonstrates the exact effect under discussion.


var i=0;

var b = {};

b.valueOf = function () {

  return ++i:
};

if (b==1 && b==2) {//success}


  var i=0;
  var b = {};
  b.valueOf = function () {
    return ++i;
  };
  if (b==1 && b==2) { console.log("success")}


And so far everyone has missed telling the poor OP how to avoid writing two equality checks...

    if( [80, 443].indexOf(port) !== -1 && http === false )


Are you certain that that's an improvement?


For two options, almost certainly not ;-)

For N ports, or for a port list defined from a config file however it could be. It's even nicer in python:

    ports = [80, 443, ...]
    if port in ports and http:
        ...
Or with underscore:

    var ports = [80, 443, ...];
    if(_(ports).contains(port) && http) {
        ...
    }
Good collections make it better than manually checking the return code or checking an explicit set of port values. Also configuration > magic_nums.


If( ~[80, 443].indexOf(port) && http=== false)


This kind of abuse is also available in Python!

    In [3]:
    class TheObjectThatIsEqualToAnything(object):
        def __eq__(self, other):
            return True

    In [4]:
    x = TheObjectThatIsEqualToAnything()

    In [6]:
    x == 3 and x == 5
    Out [6]:
    True


It's not necessarily abuse. I once had to code a dummy object that supported all arithmetic operations with normal numbers and had a special comparison logic that it is never equal to itself. Then you could test if something is a number or a dummy object by comparing it to itself.


I think the answer to "can something be fuzzy equal to one value and strictly equal to another in JS?" has an easy answer that doesn't take much effort to find. This might be cooler in another language, though.

var b = 2;

b == '2'; // true

b === 2; // true


I think you missed a bit of his point which was to make that exact expression evaluate as true

(b == 1 && b===2 && b === herpderp)


This is what I was gonna say! I don't know much JS, but after @sgdesign 's recent primer, the answer to the first two conditions seems obvious, b=="2" and b===2. Herpderp is just a cop out, since you could have infinite other variables that b equals, if you set the value to the same thing!


Except the question was b==1 && b===2


And here we all are reading this post during work hours. My god, the amount of productivity lost because of that simple question in chat!


Is this a case of readability versus theoretical perfection?


0.25 = 0.25

1 - 3 + 2.25 = 4 - 6 + 2.25

1^2 + 2(1)(1.5) + (1.5)^2 = (2)^2 - 2(2)(1.5) + (1.5)^2

Since a^2 - 2ab + b^2 = (a - b)^2

(1-1.5)^2 = (2-1.5)^2

1 = 2

QED.


And now for exercise 2:

Port the solution to Haskell


  newtype X = X (IORef Int)
  instance Num X where
    fromInteger = X . unsafePerformIO . newIORef . fromInteger
  instance Eq X where
    (X a) == (X b) = unsafePerformIO $ do
                       x <- readIORef a
                       y <- readIORef b
                       writeIORef a $ x+1
                       return $ x == y

  ghci> a <- fmap X $ newIORef 1
  ghci> a == 1 && a == 2
  True
Edit: formatting.


even more general:

    let a = 1; _ == _ = True in a == 1 && a == 2


so...

(everything == all && all == everything) == stardust/code


Defining a number to be a procedure (or 'word' in the parlance) is simple in Forth.

> : a 2 ;

> : 2 3 ;

> a 2 =

> a 3 =

The two flags on the top of the stack are now equal.


This took you 11 days?!


the classic feigned surprise




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: