編寫明顯沒有錯誤的代碼


昨天公司的技術微信公眾號貼了一篇關於怎么檢索一天所有訂單的SQL的寫法問題。類似: SELECT order_no FROM orders WHERE create_time >= '2015-04-07 00:00:00' AND create_time <= '2015-04-07 23:59:59',文中提到這種寫法是有問題的,可能有部分訂單落在最后一秒(create_time是Timestamp類型),這樣在進行對賬等環節可能帶來很隱藏的bug,很難被發現。文中還給出了MySQL中的方案推薦這樣寫 SELECT order_no FROM orders WHERE create_time >= '2015-04-07 00:00:00' AND create_time < '2015-04-08 00:00:00'。

就這么個問題在吃飯的時候和幾個同事進行了熱(面)情(紅)洋(耳)溢(赤)的討論。有同事說他會在傳給SQL的參數的截至時間拼接上.999。也即把毫秒數給拼接到截至時間里,這樣就能『正確地』查詢當天所有訂單了。看起來是正確的,因為就目前主流的MySQL版本(5.5, 5.6。5.5版本Timestamp精度支持到秒,5.6版本支持到毫秒)是能把一天的所有時間都包括。當時我們表示拼接上.999難以理解,這就相當於我們常說的magic number一樣,會給維護代碼帶來障礙。大家覺得使用create_time >= '2015-04-07 00:00:00' AND create_time < '2015-04-08 00:00:00'更無歧義一些。不過這位同事給了另外一個理由:因為他在使用SQL定義時間范圍的時候更習慣使用btween ... and ... 。好吧,如果這也算是一個理由的話。

不過這讓我突然感到有點羞愧:我居然不知道between ... and ...表示的是開開,開閉,閉開還是閉閉區間。在詢問了幾個同事,都吞吞吐吐表示是閉閉區間,甚至有位同事還去查了下MySQL官網之后,我覺得SQL的btween ... and ...設計得真扯淡。使用between ... and ...比添加.999更惡劣,因為這讓我這個不知道這個區間的人感到羞愧,最起碼我還知道.999是毫秒。人在被人弄得羞愧后第一個反應就是理直氣壯的進行反駁,以隱藏這種羞愧感。在思考了幾秒鍾后我反駁道使用between ... and ...不好,不推薦使用。如果你想來描述一個區間范圍的話用大於等於,小於等於這樣的方式會讓代碼更讓人容易理解(比如,create_time >= '2015-04-07 00:00:00.000' AND create_time <= '2015-04-08 00:00:00.999',雖然我不想承認加.999是對的),代碼更易讀,因為就算不知道between ... and ...實際區間含義的人都知道這是什么意思(說完這句話,我的羞愧感大大降低了,甚至有點驕傲的感覺)。

但是,在我驕傲的時候,這位同事給我當頭一棒:你不記得between ... and ...是你的事,但是我記得啊,我第一次看到between ... and ...的時候我就決定用她了,因為范圍的意思就是between ... and ... 。言下之意就是between and就是范圍,范圍就是between and是多么【自然】的一件事情。其實我挺喜歡自然這個詞的。在惱羞成怒的狀態下,我拋出了殺手鐧:你知道Junit的assert方法第一個參數是actual還是第二個參數是actual么?(畫外音:因為Junit的這些只有兩個參數的assert方法,兩個參數類型是一樣的,我每次使用的時候都搞不清楚第一個是actual還是expected,如果用錯最后的錯誤提示也是反的,幸好Junit現在提供了assertThat的方式)。但是他居然記得,他果斷地說第一個是expected,第二個參數是actual。看來記憶力不好是硬傷,世上總有些人比你記憶力好,他們記得所有東西,所以千萬不要跟他們拼記憶。

那么說了這么多我想表達什么呢?我可不是為了吐槽我那可憐的記憶力的。其實上面兩個例子只是想說,我們在編寫代碼的時候,應該時刻記着如何編寫明顯沒有錯誤的代碼。在性能沒有量級的不同之前,我們更應該編寫明顯沒有錯誤的代碼(比如剛才那個查找一天訂單的問題我覺得SELECT order_no FROM orders WHERE create_time like '2015-04-07%'是個更直觀的寫法,但是這種寫法可能性能不好,所以只能作罷)。明顯沒有錯誤的代碼就是即使別人來維護這個代碼的時候缺少一些上下文信息(比如當前數據庫使用的是什么精度,或者某方面的知識)都能很容易判斷這個代碼表達的是什么意思,因為當前他所看到的代碼就是所有的上下文,沒有其他隱藏的。我們在編寫代碼的時候一定要記着我們不是一個人在戰斗,你的代碼除了機器執行外會有更多的人來閱讀和修改。在我看來SQL的between ... and ...和Junit的assert序列方法都是反面教材,他要靠人們對某些知識的記憶才能理解,但人的記憶又往往很不靠譜,就這樣bug就出現了。即使在有些地方我們需要進行性能優化,可能把一些代碼給弄得比較難以理解,那么我們也應該將這樣的代碼范圍控制好,局限在某個部分。比如我們將這樣的代碼封裝到一個類里,而不是分散到各個地方,並且我們用類名,用方法名,再不濟用一些注釋向后來者述說着這里曾經的故事。

后記

大部分開發人員(包括我自己),可能對性能優化更感興趣。比如常見討論方式是:這個地方我去掉了一個鎖,那個地方我減少了多少內存,性能提高了多少多少。而很少見到這樣的討論:這個地方我修改了一個方法名那個地方我修改了一個變量名,現在我的代碼更易懂,更【形象】起來了。其實在一個工程化的環境中,性能是不是重要呢?當然重要,但不是最重要的。而代碼質量才是提高整體效率,降低故障率更有效的途徑。而且更加諷刺的是,往往我們的性能到了需要優化的時候了,但是因為代碼實在太糟糕而不知道如何優化,因為看不懂,不敢改。各個地方各種隱藏的上下文,各種歧義代碼讓性能優化難於上青天。你覺得修改這個地方好像沒有問題,但是等你修改之后一個故障就開始了。

還有一些開發人員可能比較喜歡遵循自己固有的一些習慣。誠然,每個人都有一些習慣,可能是很早之前在某個角落看到一段什么話,然后就記下來了,后來一直遵循着這種習慣,但是又說不出真正什么理由(嗯,就是喜歡)。但是,我們在遵循這個習慣的時候,也要想着這是我的習慣,不是別人的。這個代碼有沒有更好的寫法呢?所有人一看就知道是這個意思的代碼或許比習慣更重要。

所以對我來講,我雖然也很熱衷性能優化,喜歡研究一些所謂的高並發高性能,但是我更想把我的代碼寫得【漂亮】,讓別人維護起來的時候更容易看懂。編寫明顯沒有錯誤的代碼不是一件很LOW的事情,雖然逼格不高。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM