Fizzbuzz湊談

由於找不到方向繼續寫SagaECO 深入淺出系列,先行隨筆。在Google沒有收錄的網站刷SEO是不是搞錯了甚麼

相信大家有聽過傳說中的Fizzbuzz問題,全地表最因難的問題。上一篇”整潔代碼湊談“的文末有提到不應在Fizzbuzz問題紏纏clean code,在此解畫。

The “Fizz-Buzz test” is an interview question designed to help filter out the 99.5% of programming job candidates who can’t seem to program their way out of a wet paper bag. The text of the programming assignment is as follows:

“Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”

https://wiki.c2.com/?FizzBuzzTest

遊戲玩法類近於”拍七”。從1開始讀數讀到100,但是遇到5的倍數就不能把數字說出口,改為要拍手(Fizz),追加一項要求是遇到3的倍數就要伸腳(Buzz),如果同時為5和3的倍數就要兩樣都做一遍(FizzBuzz),例如15。可以再增加難度,要求數字如果存在數字 “5”或”3″都要特別處理,如質數53在終端機就打印”FizzBuzz”。

FizzBuzz之所以難,歸因於”同時為5和3的倍數要打印FizzBuzz”這點。這問題已經解很多年了我也束手無策,主流的做法是這樣:

  • 先判斷是不是同時為5和3的倍數 (或者直接除以15) ,打印Fizz-Buzz
  • 否則檢查是否為5的倍數,打印Fizz
  • 否則檢查是否為3的倍數,打印Buzz
  • 上述皆不是,打印數字本身

(避免嘗試先打印Fizz後再在同一行打印Buzz這種解法,改為”Fizz-Buzz”更貼近原意)

開發者往往在面試中遇到這道問題,他們通常分為兩類

  • C, C++開發者:
    嘗試只除數兩次,因除數有計算上的開支
    嘗試不聲明任何變量(例如isDivisibleBySeven),因這帶來了額外記憶體開支
  • Python, Javascript開發者:
    嘗試搞很多花樣,例如運用三元操作字和函數式編程企圖一句搞定
    這些人不計較效率,不惜使用除法後再乘回去之類的

最後他們甚麼都沒有寫出來 或者寫出九陰真經orz
Google一下一抓一大把

我嘗試修改一下原問題,行為不變:

“Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five fifteen print “FizzBuzz”

可以說給人的觀感很不同。我們會自然地先認為這是3個截然不同的條件
隨著代碼實作,我們才會從中抽取15 = 5 x 3。這樣寫出來的代碼,很可能是演變成聲明statement變量,先除以5,再除以3,最後檢查statement是否為空,打印一行statement或數字本身。

再改一下,這次行為稍為不同:

“Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of fifteen print “FizzBuzz” “Alma”

觀感再次改變。打印半句”Fizz”+半句”Buzz”湊出”FizzBuzz”的方案行不通了。
我們會更偏向認為最後打印”Alma”的條件,與5(Fizz)跟3(Buzz)根本沒有關係。

再把更清淅地觀看問題。這只是出外旅行居家必備一大群人就愛玩的黑魔法遊戲。

為甚麼這樣說?

你看,有沒有人拿著槍指著你去實現 DoFizzBuzz(5, 3, "Fizz", "Buzz")?

沒有。

把3 x 5 = 15 = “Fizz” + “Buzz”的限制加諸於問題裡的是你自己呀。
答案只介定了一個 DoFizzBuzz()
普通地玩拍七就足夠了~

真要解決未來需求的話,為甚麼不弄一個 DoFizzBuzz(Dictionary<int, string> words) 呢? 這樣我們隨時可以增加難度,例如拍11叫”Sozz”。可是瑞凡,這道題大概不會給10分鐘以上的,驀然回首,你卻在燈火闌珊處 。

另外,問題只是涉及從1打印到100,為甚麼要弄到代碼像機器碼一樣高效呢?
改需求會改到需要打印到100,000,000嗎? 那麼你是不是該棄用int32了?
還記得我在文首提及的拍七遊戲嗎? 你明明知道FizzBuzz是拍七遊戲,再厲害的人數到天光太累肯定會失誤,我們可以假設最多數到1000好了。
數到天文數字的你該不是用AI拍七orz

總結FizzBuzz的教訓

  • 過早最佳化(premature optimization is the root of evil)
  • 對條件關鍵字過敏(and, or)
  • 超出所需的抽象(over-engineering)
  • 設下技術性的常道(consts)

不單是需要學會避免以上四點,歸根究底,寫應用必須了解足夠多上下文。在不改變對外的行為的情況之下我們是自由的,無謂自縛。竊以為,從業務相關人士多了解需求並構建出適合的產品,這正正是程序員和工程師的差別。

後記:
湊談系列列出的觀點如有雷同,應是巧合。上一篇湊談發佈之後,隨即發現了有大神一早提及過我自發的觀點和建議。發表重合的觀點,或者相反的觀點惹來批評,或者寫成錯誤,這些發文的基本覺悟早有做好。在”SagaECO深入淺出系列”也提過,想要從大神的陰影逃脫是沒可能的… 只有用自己的筆來面對這些大神一途。

Leave a Reply

Your email address will not be published. Required fields are marked *