Hacker News new | ask | show | jobs
FizzBuzz in Too Much Detail (tomdalling.com)
134 points by boyakasha 4069 days ago
18 comments

It's good, but do you seriously think that's going to get past review? Here's a much better corporate-level version of FizzBuzz: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
"fizzBuzzSolutionStrategyFactory" ... I wasn't expecting that one ...
It's so loosely coupled that it's completely lost its hold on reality
It's so loosely coupled it'd fit it at a swingers meet-up!
Ruby has the added bonus of Flip Flops to make everything even more opaque!

    a=b=c=(1..100).each do |num|
      print num, ?\r,
        ("Fizz" unless (a = !a) .. (a = !a)),
        ("Buzz" unless (b = !b) ... !((c = !c) .. (c = !c))),
        ?\n
    end
Add comments as needed

Source: http://www.reddit.com/r/ruby/comments/2n987g/the_flip_flop_o...

I take serious issue with the "lesson" that this article is trying to teach at the end, which is that you should not generalize your particular problem into something abstract, solve that abstract problem really well, and then solve your particular problem as an instance of that problem.

Modeling your problem as FizzBuzz is a good way to model an unrealistically simple problem. FizzBuzz itself is only meant to filter out the vast masses of applicants who are struggling to find anything in the job market and therefore apply to any job they see hoping that someone will recognize them and take pity on them. It's not a realistic starting point.

Alan Kay's words are really insightful on this matter; he says that "the right perspective is worth 80 points of IQ." If you can figure out the right way to look at the problem (the right generic problem to solve given your specific constraints) then it's like being much smarter than any ordinary programmer really is.

He gives a much more instructive example: the Windows operating system. This thing is millions of lines of code, so much that if you printed it out in textbooks it'd stretch to the top of the Empire State Building. This was produced by a bunch of people doing exactly what Tom Dalling is suggesting: work only on your specific subproblem, do not solve the generic problems of "how do I make an operating system?", etc. There's no intrinsic reason that a graphical operating system needs to be so big that nobody can read it in a lifetime.

And the bugs that exist in Windows are undebuggable. With that many different components at play in the greater ecosystem, knowing which two are interacting to cause the problems that plague you requires an unbelievable amount of insight into what the computer is doing when it performs those quirks.

I agree with the specific point that software bloat sucks; I'm working with Ext.JS right now and boy is it bloated with internal abstractions that don't make much sense. Fortunately they isolate other people from dealing with those abstractions, but if I were at Sencha developing on the product I'd constantly be wondering "why do we make an 'Operation object' for the thing that the user wants to do? What's the real point of that?" etc.

> I take serious issue with the "lesson" that this article is trying to teach at the end, which is that you should not generalize your particular problem into something abstract...

The problem, simply stated by Emil Persson is "Premature optimizations can be troublesome to revert, but premature generalizations are often near impossible."

Or, less delicately phrased by Chris Eric -- "Premature optimization, that's like a fart. Premature abstraction is like taking a dump on another developer's desk."

The root problem is -- you think you know all the possible use cases, you don't. You think you can see the future, you can't. Solve for what is in front of you -- abstract it when you need to for reuse.

As for Alan Kay and his fight against complexity (which is fair and nobel fight, great video on it: https://vimeo.com/82301919) -- he is arguing for the RIGHT abstractions from the perspective of DSLs ... with all the good (and very, very bad) that entails. That said, it is 7 levels of bullshit when you get down to "deploy to metal". Sure, it is a few hundred lines of code for a DSL and OMeta to render geometry -- but skip to 1:40:00 for the real scoop. It was incredibly hard to get to run with any level of performance on ACTUAL hardware in the REAL world. Alan Kay even puts forth that you should always keep the option of "rolling your own hardware", which sadly speaks to a disconnect with ACTUAL software development (and hardware development for that matter). There is no doubt he is a legend of CS (and brilliant), but I simply think his perspective is too far away from reality.

"Software does not run in a magic fairy aether powered by the fevered dreams of CS PhDs." -- Mike Acton (https://youtu.be/rX0ItVEVjHc)

The point I saw (and heartily agree with) is to be deliberate. DRY is an optimization. What is the cost of DRY? What are the costs of not being DRY?

I think we frequently fall into the trap of doing DRY "because we're supposed to" rather than on a case by case basis evaluating whether or there is a net expected benefit.

That doesn't always mean taking a hard YAGNI line. There are plenty of opportunities for DRY that don't incur a big cost but enable simplicity in future development. It's fairly simple math (though it requires some degree of arbitrary numbers). <Expected cost of refactor (time and added complexity)> vs <Expected benefit (saving future time)> * <Likelihood you will need to update this code>

If you know you're most likely going to revisit the codebase in a month and it's a fairly simple refactor then go ahead and do it.

>> "Dry is an optimization. What is the cost of DRY? What are the costs of not being DRY?"

Agreed 100%.

I actually made a javascript programming game to try to find explore that balance: http://alexrohde.com/zennish/index.html#/challenges

At each step, this abstraction of FizzBuzz may have "gone wrong" - parameterizing is only one way to abstract, and it adds a lot of symbolic content that may not lead to a solution.

Sometimes you want the data to describe which code path should be run on it, and if that's the feature being added, you end up with a very different outlook on the solution space - depending on your preferred style and thinking, you could end up with an inherited class, an interface, a "FizzBuzzData" with an enumerated value describing codepath, communication with a SQL database...there are a lot of options.

Hence, one should always be more suspicious about self-harm through a bad factoring, than about "dumb code."

Corollary: if you only have 2 examples of your abstraction, there's a fair chance that you don't have the proper perspective. And, imho, building abstractions from improper perspectives is worse than not building abstractions from proper perspectives.
Articles like this one make me wonder what percent of the development community actually uses TDD.

Shouldn't it be natural, even expected, to see the tests that drove each iteration of abstraction?

I am particularly interested in how one defines and implements tests for scaling and performance requirements like the 'Lazy Generation' example.

Now the standard response will be that it's an article (or programming book, or interview question) so for the sake of brevity and clarity we omitted the test.

Wouldn't well defined tests help shape the correct amount of generalization and abstraction?

Aren't you assuming TDD is a reasonable way to drive the design of algorithms? I would not expect someone to design fizzbuzz-like functions using TDD.

(I would expect to see tests, sure, but tests aren't TDD).

I still think the "Naïve" implementation is the best one given the actual specification of the problem, and not some made-up specification and assumptions that solve a different problem than the one actually being asked (which may be more interesting, but that is beside the point).
That was the conclusion of the article, if you scroll down to the bottom. "The final implementation represents a veritable explosion of complexity".
I thought that was the point of the article.
It was funny to me, because I immediately liked the second version most, but implemented as as function that takes the range of numbers I want to use. The only reason I liked that version more is because it "solves" common "problems" I run into when I'm screwing around with fizzbuzz in new languages.

Funny to me because I'm adding a layer of abstraction to handle specific issues I've encountered in the problem space for a question where the problem space is totally synthetic

But yeah, as it's asked, I think the first is the best answer to Fizzbuzz

I think the point is more clearly stated in the TDWTF Soft Coding article[0]

Best quotes:

> This brings us right into the definition of Soft Coding: the practice of removing “things that should be in source code” from source code and placing them in some external resource.

> The whole point of software (hence, the “soft”) is that it can change that it will change. The only way to insulate your software from business rule changes is to build a completely generic program that’s devoid of all business rules yet can implement any rule. Oh, and they’ve already built that tool. It’s called C++. And Java. And C#. And Basic. And, dare I say, COBOL.

[0] http://thedailywtf.com/articles/Soft_Coding

I'm glad they got rid of the 'puts' calls in the end.

It was irking me to naively assume that you would always be asked to print the answer sequence.

brillant.
Hardcoding 'Fizz' and 'Buzz' inhibits internationalization.
Here's a version in Haskell that has the same functionality:

    fizzbuzz i triggers = map (\x -> fromMaybe (show x) $ 
                                               liftM mconcat (sequence triggers) x) 
                              [i..]

    fizzbuzz 1 $ [\i -> ["Fizz" | i `rem` 3 == 0]
                 ,\i -> ["Buzz" | i `rem` 5 == 0]
                 ]
Its just me playing around and trying to be clever.The way FizzBuzz can be expressed as a Monoid is cool. The rest is just plumbing.

    mconcat [Just "Fizz", Just "Buzz"] = Just "FizzBuzz"
    mconcat [Just "Fizz", Nothing    ] = Just "Fizz"
    mconcat [Nothing    , Nothing    ] = Nothing
Note that this requires {-# LANGUAGE MonadComprehensions #-} to compile. Thats for the sugar used when constructing the triggers.
If you're going to use the (->) Monad instance, why not:

  {-# LANGUAGE MonadComprehensions #-}

  import Data.Maybe (fromMaybe)
  import Data.Monoid (mconcat)
  import Control.Applicative ((<*>))

  pam = flip map
  fizzbuzz i trigs = pam [i..] $ fromMaybe . show <*> mconcat . trigs
  
  ans = fizzbuzz 1 $ \i -> [["Fizz" | i `rem` 3 == 0]
                           ,["Buzz" | i `rem` 5 == 0]
                           ]
  
  main = mapM_ putStrLn $ take 100 ans
I'm sure that Haskell can do better than that in the readability department.
It can, I was just trying to be clever :P

For one, I don't need to drag monads into function composition:

    fizzbuzz i triggers = map (\x -> fromMaybe (show x) $ 
                                               mconcat $ triggers x) 
                              [i..]

    fizzbuzz 1 $ \i -> [["Fizz" | i `rem` 3 == 0]
                       ,["Buzz" | i `rem` 5 == 0]
                       ]
I still prefer using symbolic languages for problems of such complexity. Such as... Ruby.

https://github.com/sferik/active_emoji/blob/master/samples/f...

The ruby quiz did a round of this back after fizzbuzz had already jumped the shark the first time: http://rubyquiz.com/quiz126.html

I personally thought that my two entries were pretty amusing: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/... and http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/...

If I get asked fizzbuzz in an interview then I'm going to drive my interviewer insane with a switch statement.

case 0:

  print "fizzbuzz";
case 1:

  print 1;
case 2:

  print 2;
case 3:

  print "fizz";
Your solution failed to satisfy the requirements. You need to print the number for 1 and 2.
thanks!
By extracting the modulo operator (%) into its own function, at least we've made the code more self-documenting. If someone else were to read the code and they didn't understand how the modulo operator worked, they could work it out based on the function name.

Someone's taking the piss. Would anyone in the "audience" for FizzBuzz code (coders, managers... HR?) really not understand modulo? That seems like kind of the point of FizzBuzz.

Sure the whole thing is a satire, but this aspect is sort of straw-mannish.

I've met plenty of supposed "coders" who don't know what the modulo operator is.

And don't even get me started on managers.

Did you finish the article?
Yes that's why I specifically noted "sure the whole thing is a satire". I just felt that this particular aspect of the satire was unfair.
Apperently I didn't read the comment.

I think the point being that fizzbuzz is already a strawman of a programming excersize.

Now would you consider a smarter move to do the following in an interview:

- gem install your-fizzbuzz-package

- require 'your-fizzbuzz-package'

- puts FizzBuzz.range(1..100)

?

My point being why reinvent the wheel.

If an interviewer asks FizzBuzz, it means they want to do a quick sanity check that you can write code to solve a simple problem. Answer the question and the interview can move on to something more interesting.

If you try to give a "clever" answer at this point rather than collaborating with the interviewer, it's likely to give a bad impression. A good interview process isn't just looking for people who are strong programmers, it's looking for people who are good engineers - and good engineers are people who collaborate well rather than trying to prove a point at the first available opportunity.

I personally would find it a more experienced approach to look up a prewritten, tested library, used by other people.
It's not the point. At this point the interviewer isn't asking whether you can ship something, he's asking you whether you understand code or not. You need to fully comprehend what you're shipping, whether you imported it from a tested library or you built it yourself.
You should have seen the shambolic mess of a Python script I inherited from a colleague. The script was doing bash calls, to call itself with different arguments. Functions generating functions, not because of any high level of abstraction, but because she didn't know how to write a module. The code inside was more complex than fizzbuzz, but the way it was shipped was beyond terrible.

Coding something like fizbuzz is usually day one of a programming course. Using modules maybe day 2 or 3? I would see it as a sign of a more mature developer.

That's exactly the point of fizzbuzz: weed out those who clearly can't code. Only when the fizzbuzz test is passed can we start going into interesting parts, where an interviewer can assess whether you're a (truly) mature developer.
Didn't make it to the end?
Yes, I read the entire article, maybe I didn't articulate correctly, my point is, given you are in an interview and somebody asks you "Show me factorial please", wouldn't be a smarter move to import a module that something already wrote and just use it?

Again, I read the entire article and saw he ended up with a generic solution.

I think HIS point was about premature abstraction and YAGNI. The article was just examples of terrible abstractions... they were intentionally terrible. The "generic solution" was an example of what NOT to do.

The article wasn't interview advice, it was advice about avoiding premature abstraction.

> a smarter move [...]

The goal is getting the job, or being a smart ass?

As an interviewer, I'll put this one to bed. Pick up the white-board marker and start writing.

The intern can look up libraries; I want to see if you can understand what we'd be looking for in those libraries. If you can't implement and discuss a simple version of it, odds are that you cannot.

If during this process you talk about how you'd be starting a build/buy research cycle at certain points and what that'd look like, as we spec out the problem and build a prototype, that wouldn't be misplaced.

That's not really an optimal way to check divisibility though, if you want to be quick try:

    fizz = (uint)i*2863311531 <= (uint)i;
    buzz = (uint)i*3435973837 <= (uint)i;
Should work for fine for i<=1000.

Edit: changed "<" to "<=", otherwise it fails when i=0.

He mentions ruby uses bigintegers, while this assumes something else, right?
The above assumes unsigned 32bit integers. It abuses integer overflow. Perhaps I should have stated that more explicitly.
This is the best FizzBuzz articles that has come out lately.
It helps having this article next to (nearby) the HN article discussing the 80K static coding violations in Toyota throttle position sensing code, or whatever it was exactly.

THIS article didn't get into it very much, but "obviously" you'll have four times the bugs in a highly abstracted 40 part piece of code, than in a much simpler, less capable 10 part piece of code.

Reality is bug count never scales at a rate as low as linear with code length of course.

At any rate the two HN articles are both better if read at the same time.

I only got to the parametrized version in C++ myself.

Nice reading.