變數

幾乎現今所有的程式語言都有支援變數的使用,其中當然也包含 Scratch 以及 micro:bit 所使用的 Block Editor。雖然變數的觀念與使用算是簡單易懂,但是變數的使用可說是程式設計師技能表中最重要的修練之一。良好的變數使用習慣可以大幅提升程式的品質,並減少開發以及日後修改的困難度。在本篇文章中,我想要跟各位分享在使用變數時需要注意的地方。本篇的程式範例雖然主要以 Scratch 為主,搭配少部分的 micro:bit Block Editor 範例,但是只學過其他程式語言的人也不用擔心,因為當中提到的內容與觀念與實際使用的程式語言無關,使用 Scratch 單純只是為了方便說明。

對於 Scratch 來說,有一件事必須先稍微解釋一下,那就是 Scratch 在積木的分類上把資料分為變數與清單兩種。但是對程式而言,其實不管是哪種類型的可變資料,都可算是變數。也就是說,後面文章中所謂的變數,對 Scratch 來說其實是同時意指變數與清單這兩種資料類型。

在此先劇透一下,有關變數的使用,可以總結為四句話:正其名、辯其意、安其份、盡其力。

接下來就讓我們一起來看看這個葫蘆裡賣的究竟是甚麼藥。

不就是些變數,有需要大費周章嗎?

在開始談論之前,我們先來看看下面這個範例

變數 T?

變數 T?

我相信只要學過程式設計的人,都可以輕易看出這段程式在做些甚麼。這段程式首先判斷 S 這個變數的值是不是大於 300,如果大於 300 就把 T 這個變數的值減少 100。然而看似簡單的短短兩行指令,卻足以讓人滿頭黑人問號:

  1. S 變數代表甚麼?300 這個數字有單位嗎?
  2. T 變數代表甚麼?100 這個數字有單位嗎?
  3. T 變數減少 100 對程式有何影響?
  4. 這麼做的用意是甚麼?

我們將變數名稱稍作調整後並重新檢視這段程式碼:

Score 與 Time 有進步,但還不夠清楚。

Score 與 Time 有進步,但還不夠清楚。

喔,原來這兩行程式是判斷如果分數 (Score) 大於 300 時,需要將時間 (Time) 減少 100。儘管已經比原先的程式容易理解許多,但是仍留下不少問號。分數,是甚麼分數?時間,又是甚麼樣的時間?我們再重新調整一次

PlayerScore 與 DelayTime 清楚表達,但仍有進步空間。

PlayerScore 與 DelayTime 清楚表達,但仍有進步空間。

這次可就清楚多了。當玩家分數 (PlayerScore) 大於 300 時,要將延遲時間 (DelayTime) 減少 100。不過還記得我們一開始的那堆黑人問號嗎?300 跟 100 的單位是甚麼啊?一般而言,分數通常沒有單位,但是時間可是肯定有單位了。時、分、秒,是我們人類習慣使用的時間單位。但是到了電腦的世界,毫秒 (千分之一秒)、微秒 (百萬分之一秒)、甚至更短的時間,對電腦來說可都是漫長的等待。所以 DelayTime 的單位是甚麼?如果弄錯了,差距往往就是千倍以上。為了解決這個問題,我們可以將 DelayTime 這個變數名稱修改為 DelayTimeInMillisecond,如此一來,就可以很清楚的知道其時間單位為毫秒 (millisecond)。

幾近完美。

幾近完美。

透過變數的重新命名,我們大大地減少了腦中所產生的問號。

既然變數的名稱很重要,那改成用中文豈不更加容易理解?

中文名稱變數,NO!

中文名稱變數,NO!

對此,我的建議是盡可能使用英文作為變數名稱,主要原因在於並不是所有的程式語言都支援中文的變數名稱。即使像是 Scratch 或 micro:bit Block Editor 已經支援中文變數名稱的環境,也是一樣盡量使用英文就好,如此才便於與其他國家的網友分享哦。

程式碼的可讀性

從前面的例子中,我們可以感受到程式碼的可讀性對於我們理解程式的重要性。雖然說當我們把程式丟給電腦執行時,電腦其實一點也不在乎變數名稱到底是 T 還是 DelayTimeInMillisecond,甚至可說電腦根本不在乎也不懂甚麼是變數名稱,但是很可惜的是我們人類需要透過有意義的變數名稱才能便於理解程式本身的意思。我們寫出來的程式碼,需要不斷的修改與增強,而要能夠正確修改或增強的前提是修改的人要先能看得懂原本的程式。如果考慮到程式可能交給其他人來修改,或許不難理解可讀性的重要性。但是如果從頭到尾都是自己一個人所開發的程式,是不是就可以隨意一點呢?剛好相反!相信我,即使是自己當時燃盡生命所開發、熟悉到無法再熟悉的程式碼,過了三五個月後也往往變得跟小學時背過的唐詩三百首一樣,忘記的多,記住的少,而且當中還有不少是記錯的。所以說如果是自己一個人開發的程式,更要注意程式的可讀性,不然受害者就是你自己。至於多人開發的程式嘛,受可讀性不佳程式的受害者還不一定是你呢。事實上,就真的有程式設計師會故意弄一些無意義的變數名稱,目的正是為了整之後接手的可憐蟲。當然,這是很沒職業道德的事情,我們可千萬不要這樣做。扣除這種沒職業道德的作法,有一類保護程式智慧財產權的工具,就會故意把程式的變數全部改成毫無意義的名稱,以避免程式被其他不相干的人修改濫用。當然,程式的可讀性並不是只跟變數名稱有關,還有很多因素都會影響程式的可讀性。儘管如此,良好的變數名稱確實對程式可讀性而言是最為根本與重要的一個因素,且幾乎所有的程式都會使用到變數,所以變數命名變成了一個程式設計師的必備修練。

正其名、辯其意

對於只有兩個變數的程式,隨便命名變數或許尚不至於造成過多的困擾,把全部程式看上一遍大概就知道用途了。但是絕大多數的程式不會只有兩個變數,數十個、上百個、甚至更多都很常見,所以變數名稱的重要性不言可喻。根據非正式的統計,命名變數可說是程式設計師最頭痛,卻同時也是最重要的事情。變數命名只有一個終極目標,那就是正其名、辯其意。也就是當我們看到變數名稱時,就可以很清楚的知道它的用途而不會產生任何的誤會

有關變數的命名,有一個最主要的基本原則,那就是必須是正確且有意義的字詞組合,而且通常是名詞的形式。名詞與非名詞的形式在中文裡往往沒有任何差別,但是對英文的某些字詞來說卻是很重要的原則。以前述的例子而言,DelayTimeInMillisecond 就是一個有意義字詞所組合成的名詞,而 T 很顯然不是。

單字間的組合方式

以 DelayTimeInMillisecond 這個名詞變數來說,其實是由四個英文單字所合成,也就是 delay、time、in、millisecond。而要將這幾個單字組合在一起,主要有下列三種方式:

  1. 所有單字的首字母大寫,也就是 DelayTimeInMillisecond。這稱為大駝峰式命名法。
  2. 除第一個單字外,其餘單字首字母大寫,也就是 delayTimeInMillisecond。這稱為小駝峰式命名法。
  3. 全部小寫,用底線 (_) 連結單字,也就是 delay_time_in_millisecond。

看到第三種組合方式,有些人可能會想說能不能用破折號 – 來連結單字,也就是 delay-time-in-millisecond,畢竟輸入 – 比輸入 _ 還要簡單。可惜的是這種結合方式在大多數的程式語言都是行不通的,因為 – 通常是代表數學運算的減號,所以無法使用在變數名稱中,而且實際閱讀上破折號也沒有比底線來的更加讓人容易辨識斷字的位置。

至於三種當中應該使用哪一種呢?雖然有些程式語言會建議某種用法,但是這三者並沒有對錯之分,也沒有一定要用哪一種。重點是找到自己習慣的方式,並且維持下去,千萬不要變來變去。如果與其他人一同開發程式,那當然就要大家一起遵守同一套規定啦。

此外,有時候我們會看到全大寫的方式,像是 DELAY_TIME_IN_MILLISECOND。嚴格來說,這並不是變數的常用命名方式,而是使用來表示常數。常數可以看成特殊形式的變數,主要差別在於常數的數值不像變數般可以任意變換,一經指定後就無法再改變。雖然 Scratch 與 Block Editor 並沒有支援常數的概念,但是我們也可以考慮用這個命名方式來提醒其他人這是一個不會被改變數值的變數。

除了常數之外,有些程式語言也會針對變數的其他性質做一些命名的變化,像是在最前面加上底線 _ 或變數型別之類。不過因為不同程式語言之間有不同的做法,所以在此就先掠過不介紹了。

使用精確的字詞

前面提到了變數名稱必須正確,在這裡怎麼又會出現精確呢?兩者雖然乍聽之下極其相似,但是 “正確” 與 “精確” 其實是完全不一樣的概念。正確只是沒有錯,但是卻有可能會讓人產生誤解,而精確則是為了避免誤解情況的發生。舉例來說,如果我養了一隻狗當寵物,那麼 “我養了一隻寵物” 跟 “我養了一隻狗當寵物” 這兩句話都算是正確的。但是就精確度而言,第二句很明顯高於第一句,絕不會有人因此誤解我是一個貓奴。回到變數命名的問題上,當我們看到 PersonalData、PersonalInformation、PersonalDetails 這幾個變數名稱時,第一時間可以猜想到這些變數應該都跟個人資料有關。如果程式裡只使用了三者當中的一個變數名稱那還相安無事,但是萬一出現了三個當中的兩個、甚至是三個都同時出現,那麼這三個變數到底各自代表甚麼意思呢?嚴格來說,即使程式中只出現當中的一個變數名稱,也算不上是好的選擇。Data、Information、Detail,這幾個名詞適用的範圍太過廣泛,往往無法讓我們在第一時間就明確了解變數的意義與用途,因此應該盡量避免使用而改用更為精確的字詞。

雖然精確很重要,但卻也不是越精確越好。首先,太過精確的變數名稱會變得超長。這個問題雖然可以靠編輯器或 IDE (整合開發環境) 的自動選取功能減少所需輸入的字,但是在閱讀程式碼時仍會造成不必要的困擾。更重要的是太過精確的變數名稱很容易因為程式小幅變動而影響了變數名稱的精確性,所以必須不斷地重新命名以免產生誤會。

有話直說,千萬不要反著說

這句話可能不是那麼容易理解,所以我們直接用例子來加以說明。假設我們正在開發一個名為 “瘋狂蘋果” 的 Scratch 遊戲,在這個遊戲中會有一個蘋果不斷地從畫面上方往下掉落。我們必須控制我們的角色不被掉下來的蘋果所擊中,而且每躲過一個蘋果都可以得到分數。為了增加遊戲的難度,我們希望得分數增加時蘋果的下降速度也要跟著加快。因為我們需要控制蘋果在畫面上的位置以及移動速度,所以我們定義了 X、Y、Speed 三個變數分別用來表示蘋果的 X 座標、Y 座標以及移動速度,並且設計出類似下面的積木組合(為了簡化說明,在此僅先設計蘋果不斷下降的功能,得分與其他遊戲功能則予以忽略):

瘋狂蘋果程式範例一

瘋狂蘋果程式範例一

程式在每次固定時間間隔中 (0.01 秒) 計算下一次蘋果應該出現的位置,並將蘋果移動到該位置。因為蘋果為垂直落下,所以每次只要改變 Y 座標即可,也就是畫面中的最後一個積木,而改變的數值跟速度(也就是變數 Speed)有關。因為越下方的 Y 座標值越小,所以必須改變 0 – Speed 才能達成往下移動的效果。

雖然這個做法很直覺,但是有一個問題,就是當蘋果的下降速度太快時,有可能因為每次移動距離過大而讓蘋果產生瞬間移動的現象。所以有些人會採用另外一種做法,也就是固定每次移動的距離,但是改變每次移動的時間間隔,透過縮短移動間隔時間來達成下降速度變快的效果。因此設計出類似下面的積木:

瘋狂蘋果程式範例二

瘋狂蘋果程式範例二

這個範例跟上一個範例幾乎一模一樣,只是等待的時間變成變數 Speed 的數值,而 Y 座標則每次固定減少 10,也就是往下移動 10 個點。而因為 Speed 變數的用途改變了,所以初始值也從 100 變成 0.01。程式看起來並沒有甚麼問題,不過事實真是這樣嗎?當我們開始設計分數增加也要跟著增加下降速度的功能時,在第二個範例就會出現類似下面的積木:

瘋狂蘋果程式範例三

瘋狂蘋果程式範例三

如果只看這個積木區塊,我們發現當分數超過 100 時 Speed 變數的值會減少 0.001。雖然僅從這段程式碼當中我們無法得知 Speed 是甚麼東西的速度,但是你會認為 “這個未知東西” 的速度是變快還是變慢呢?對一般人而言,應該會認為速度數值變小肯定是表示變慢了。但是因為我們看過程式的其他部分,所以知道其實這是表示蘋果的下降速度變快了。這樣的寫法強迫我們必須同時了解整個程式的運作才能夠順利解讀每一段程式碼的意思,而且每次在解讀這段積木時,腦袋都必須重複提醒這個違反一般認知的陷阱,平白增加了許多理解上的困難。

如果你說那是因為我從第一個範例直接修改才會把變數名稱設定成 Speed,而不是比較合理的 DelayTimeInMillisecond,建議你可以自己多觀察實際的範例,將會發現這樣寫的範例可並不在少數。不少時候我們開始撰寫程式後才發現不能夠直接透過原先預期的方式來控制程式的行為,而必須拐個彎。如果這個彎拐了 180 度,就會產生類似的問題。或者也有可能是設計者覺得兩者之間本就是有所相關,所以使用了這樣的變數名稱其實相當合理而無不妥。然而,如果這個相關剛好具有相反的意思,事實上可能比取不相關的名稱還慘。不相關的名稱只會讓人滿頭霧水,相反的名稱可就是讓人誤會連連了。解法呢?同樣只要把變數改個更合適的名字就可以了。

盡量以用途為變數名稱,而非實際內容

“瘋狂蘋果” 第一版釋出後,有一家販賣蘋果的公司很喜歡這個遊戲,所以願意免費幫我們推廣。不過有一個問題,這家公司販賣的是青蘋果,而原本遊戲中的紅蘋果正是他們的死對頭,所以對方希望我們能把遊戲中的紅蘋果全部塗成綠色的。改變顏色,在 Scratch 裡只要一個積木就可以輕鬆達成:

Scratch 顏色積木 - 數值

Scratch 顏色積木 – 數值

然而這家公司實在太喜歡綠色了,所以他們決定不只是蘋果要塗成綠色,包含遊戲主角在內的其他角色也都要塗成綠色。我們可以直接在每個物件都加上這個積木,但是我們想到也許未來還會有藍蘋果、黃蘋果的版本,所以新增一個用來記錄顏色的變數應該是個不錯的主意。之後只要修改這個變數的數值,所有物件就會統一變成指定的顏色。問題來了,這個變數應該叫甚麼名字好呢?既然是綠色,那變數就叫 Green 好了。不但很貼切,而且看到的人馬上就知道程式將物件指定為綠色。事實上,Scratch 對於顏色的指定與其他程式語言有很大的差異,對於數字與所代表顏色之間的關係並不是很容易就能理解,所以使用 Green 當作變數名稱似乎是很不錯的選擇。所以原本的積木就變成了:

Scratch 顏色積木 - 變數

Scratch 顏色積木 – 變數

積木本身確實清楚又明瞭。

但是還記得嗎?我們希望未來會有藍蘋果、黃蘋果的版本,所以對藍蘋果的版本而言,變數 Green 代表的顏色就是 “藍” 而不是 “綠” 了。而到了黃蘋果的版本,變數 Green 又變成了代表 “黃色”。這問題跟之前反著說一樣,都是很容易讓人產生誤解的變數名稱,所以絕對要予以避免。比較好的變數名稱會是 ObjectColor (物件顏色),清楚的標示出這是個用來代表物件顏色的變數。如果我們擔心 255 這樣的數字太過抽象,以致於讓閱讀程式的人無法理解實際代表的顏色,我們可以改採用雙重變數的方式,先指定 Green 變數,再將 ObjectColor 設定為 Green,如下列積木所示:

Scratch 顏色積木 - 雙重變數

Scratch 顏色積木 – 雙重變數

而藍蘋果跟黃蘋果呢?我們只要分別新增用來代表藍色、黃色的變數 Blue、Yellow,並指定給 ObjectColor 就可以達到變色的目的。

雖然這樣的積木組合看起來似乎有些多餘,但是在實務上這是用來增加程式可讀性的常見作法。因為對電腦而言綠色這件事是固定而不會改變的事實,所以如果是支援常數定義的程式語言,將 Green 指定為常數會是更好的做法。不過即使無法使用常數,採用兩個變數分別代表不同意思的事情已經比前面幾個範例都更加容易閱讀,未來修改時也不容易發生誤解的情況。

真還是假?

在所有類型的變數中,有一類變數算是比較特別的,那就是只用來表示真或假兩種可能性的變數。這類變數我們常又稱為開關,意指用來控制某些程式碼的執行與否。回到之前的 “瘋狂蘋果” 遊戲,如果我們希望在程式開始後必須先等待使用者按下空白鍵 (Space 鍵) 才開始進行遊戲,應該要怎麼達成這個目的呢?一個可能的作法是利用一個變數來紀錄空白鍵是否已經被按下,而遊戲相關的程式碼必須先檢查此變數才能決定是不是要繼續往下執行,如下面積木所示:

瘋狂蘋果程式範例四

瘋狂蘋果程式範例四

瘋狂蘋果程式範例五

瘋狂蘋果程式範例五

當然,S 絕對不是一個理想的變數名稱,那麼我們應該要怎麼替這類變數取合適的名字呢?前面提到變數名稱原則上以名詞為主,而這類變數算是例外,建議的命名方式以 Is 開頭,像是 IsSpacePressed。不過根據前一項建議,這樣的變數名稱其實並不好,因為當我們想要改用其他按鈕 (如 A 鍵) 來開始遊戲時,這個變數名稱就會令人誤解了。所以比較好的建議會是 IsGameStarted 這樣的變數名稱。所以積木修改如下:

瘋狂蘋果程式範例六

瘋狂蘋果程式範例六

瘋狂蘋果程式範例七

瘋狂蘋果程式範例七

比較可惜的是 Scratch 並不支援布林形式的數值,所以程式在指定時必須用 1 或 0 來代表真或假,而判斷時也必須使用 If IsGameStart = 1 這樣的表示方法,因此不容易看出為什麼要這樣命名。micro:bit 支援布林值的變數,因此我們改用 Block Editor 來呈現類似的範例。

瘋狂蘋果程式範例八

瘋狂蘋果程式範例八

首先,IsGameStarted 變數可以直接指定為真或假,明確表達出這是一個布林數的變數。在判斷時也不再需要使用 If IsGameStatred = 1 這樣的用法,而可以直接使用 If IsGameStarted。這樣的用法不但簡潔,也比較接近英文 (甚至是中文) 的語法,大幅增加程式的可讀性。

除了 Is 之外,包含 Can、Has 或類似的動詞也可以用來作這類變數的開頭。至於哪一個比較合適,必須考慮這個變數所代表的意思以及英文合適的說法。舉例來說,使用變數 CanRotate 來表示蘋果在下降時是否可以轉動會比 IsRotated 更為合適。

因為這類變數表示有兩種可能性,所以命名時往往也有兩種可能。例如我們可以用 CanRotate 表示蘋果能不能”轉動”,當然也可以用 CantRotate 來表示蘋果能不能”不轉動”。雖然我們不太可能取 CantRotate 這樣的變數名稱,但是有些英文本身就有反義字,所以就會面臨兩種命名方式的選擇。儘管如此,兩者之間的選擇通常不會造成太多的困擾,比較需要特別注意的是如果反義字本身就具有否定的意思。舉例來說,如果 “瘋狂蘋果” 決定將得分不到 60 分的玩家判斷為挑戰失敗,而 60 分以上則為挑戰成功。當遊戲結束後背景以及玩家角色都會根據挑戰成功或失敗作出不一樣的行為,也就是說我們需要一個變數來代表挑戰的結果。如果我們選擇 IsFailed 來表示挑戰失敗,而在判斷時又剛好寫出下列的程式,就出現了雙重否定的用法。

瘋狂蘋果程式範例九

瘋狂蘋果程式範例九

雙重否定會造成程式閱讀與理解上的困難,所以應該盡量予以避免。如果遇到這種情況,通常可以視情況從下列兩種解法中找尋比較適合的一種。其中一種解法是更改變數名稱,而另外一種則是修改 If else 的順序。

“一件事物一個字詞”以及”一個字詞一件事物”

“一件事物一個字詞”,指的是在同一個程式中,描述相同的事物要用同一個名詞。”瘋狂蘋果” 推出後,為了增加遊戲的挑戰性,我們希望當分數增加時,不只蘋果的下降速度要增加,蘋果的大小也要跟著變大。再三考慮過後,我們使用變數 AppleSize 來代表蘋果在畫面上的顯示比例。當 AppleSize 為 1.5 時,表示顯示比例為原來的 1.5 倍,也就是 150%。過了一陣子後,黃蘋果公司看到 “瘋狂蘋果” 大受歡迎,希望我們幫他們開發瘋狂黃蘋果版本。小事一件,別忘了我們之前已經定義好顏色變數 Yellow 與 ObjectColor ,只要把 ObjectColor 指定為 Yellow 就大功告成了。不過黃蘋果公司為了讓瘋狂黃蘋果的版本更具有獨特性,所以要求我們將程式修改成玩家的大小也要隨著分數的增加而放大,但是卻與蘋果的放大比例不一樣。也就是我們需要另外一個變數來代表玩家的顯示比例,經過再三考慮後,我們選擇了 PlayerScale 這個變數名稱。發現問題了嗎?我們前後使用了 Size 跟 Scale 兩個名詞來代表顯示比例這件事。或許你會覺得這麼明顯的問題,根本就不應該會發生。但是現實是殘酷的,這絕對不是會不會發生,而是發生的機會有多高的問題。當程式有許許多多且散落各處的變數,如果再加上同時有好幾位開發者,這類問題幾乎是很難完全避免的。儘管如此,我們仍應該盡全力避免這種問題的發生。

跟 “一件事物一個字詞” 相關的觀念,就是 “一個字詞一件事物”。顧名思義,“一個字詞一個事物” 表示在程式中一個字詞只能用來表示一件事,千萬不可有多種意思。我們的 “瘋狂蘋果” 實在太受歡迎,所以我們決定加以改良,推出 “超瘋狂蘋果”。”超瘋狂蘋果” 除了原先從上方掉落的蘋果外,還會從畫面左方飛入香蕉來攻擊玩家,玩家除了原本的左右移動外,也必須上下移動來躲過蘋果與香蕉的攻擊。與蘋果不同的是,分數增加並不會改變香蕉的大小,但是卻會增加每次出現的香蕉數量。當分數越高時,出現的香蕉數量也會越多。我們必須設計一個變數用來表示同時出現的香蕉數量,而在考慮過後,我們將此變數命名為 BananaSize。碰!在這個變數中,我們用 Size 來表示香蕉的數量,跟之前在變數 AppleSize 時使用 Size 來表示蘋果的顯示比例很明顯是代表完全不一樣的意思。所以我們違反了 “一個字詞一件事物” 的原則,同樣將導致程式碼難以閱讀與理解。

使用專業術語

這點其實有時候很容易做到,有時候卻又不是那麼容易。

舉例來說,當我們在 Scratch 或其他類似的程式語言中移動角色時,我們會用 X、Y 兩個變數來表示角色在 2D 畫面中的位置。其實變數名稱 X 與 Y 就是專業術語,只要知道座標關係的人就可以理解這兩個變數的意義是用來分別表示 X 軸與 Y 軸的座標。而其實 2D 座標並不是只有這種表示方式,使用 X、Y 變數也就等於宣告程式主要使用的是直角坐標,而非極座標。也就是說,透過專業術語的使用,可以讓具備專業知識的人很容易理解你的程式。至於那些不具備專業知識的人呢?不用擔心太多專業術語會讓他們感到困惑,反正你寫的再好他們可能也看不懂。

聽起來很簡單的觀念,作起來也不複雜,那為什麼我會說有時候要作到其實並不容易呢?身為程式設計師,常常需要撰寫不同應用的程式。舉例來說,如果你今天要寫一個無人機的控制程式,那麼你知道 Pitch、Roll、Yaw 是甚麼意思嗎?甚至是否聽過這幾個名詞?又或者你今天要幫學校設計一個考試成績的統計程式,你聽過平均值、中位數、標準差這些名詞並清楚它們的定義嗎?這點對不是以英文為主要語言的我們來說更為重要,因為我們不只要知道中文的說法,更要知道英文的專業術語,以免變成牛頭不對馬嘴。你也許會想,即使如此也稱不上困難啊,只要仔細看一些資料就知道了。但是在實務上,不見得每次撰寫程式前都有時間好好研究相關的知識,所以最後就變成亂講一通。而也因為未使用專業術語,所以只能使用自己隨意套用的字詞。如果每次套用時的心情又不一樣,那麼違反 “一個事物一個字詞” 原則也只是早晚的事情。

千萬不要拼錯字

對不是慣用英文的我們來說,努力拚好每一個英文單字是比較辛苦些。儘管如此,還是要盡一切力量拼出正確的單字。其實很多編輯器或 IDE 都有拼字檢查的功能,可以提示錯誤的拼字,大幅減少我們拼錯字的機會。比較可惜的是 Scratch 與 Block Editor 目前並沒有提供這樣的功能,所以可能需要多查查字典或使用其他工具囉。拼錯字影響最大的不是你自己,而且其他想要修改程式的人。因為一旦他們使用了正確的拼字,就會變成兩個不一樣的變數。至於會產生何種錯誤,不同的程式語言會有不一樣的狀況。不過即使知道問題可能在變數名稱上,要解決這類問題依舊不是那麼容易。首先,沒有人知道你的變數名稱是哪裡拼錯了,是把 Information 拼成 Infomation 還是 Infornation,所以沒辦法用搜尋的功能找出錯誤的拚字。不能搜尋,就只能人工看程式了。但是問題來了,以 Infomation 跟 Infornation 這兩個錯誤來說,如果不是很用力地認真看,還真無法馬上發現其實都是錯字。更慘的是,人類的腦袋有自動修正的功能,所以就算看到錯字也往往會腦補成正確的拼字。所以要找出這類錯誤往往是很困擾的苦工,細心再加上具備拼字檢查功能的編輯器或 IDE,讓錯誤的拚字沒有任何出現機會才是最佳做法。反過來說,如果你很討厭某個跟你一起合作的人,只要故意拼錯一些變數名稱就可以整的他頭上少掉好幾撮頭髮。

安其分、盡其力

在前面我們提到變數名稱必須精確,其實這點的弦外之音就表示變數不能一”數”多用,而必須安分守己。因為一旦有多個用途,就沒辦法用單一字詞或字詞組合加以精確地描述。還記得我們的 “瘋狂蘋果” 有一個用來判斷遊戲是否已經開始的變數 – IsGameStarted 嗎?如果我們想要加上遊戲暫停的功能,因為暫停時遊戲相關功能的程式碼幾乎都不需要執行,所以似乎可以同樣使用 IsGameStarted 變數來加以控制,也就是用 IsGameStarted 為假 (或 0) 來表示遊戲屬於暫停的狀況。別忘了,原先 IsGameStarted 為真是表示遊戲已經開始,也就是 IsGameStarted 為假其實是表示遊戲尚未開始。而遊戲尚未開始跟遊戲暫停其實是兩件不一樣的事情,只是我們偷懶將兩件事一併處理了。這樣的做法或許一開始相安無事,但是當我們需要讓遊戲在暫停時表現出跟遊戲尚未開始不一樣的行為時 (例如遊戲暫停時畫面上必須出現閃爍的暫停訊息) 問題就來了。有些人可能就會讓 IsGameStarted 變數出現第三種可能的數值,也就是原先的 0、1 另外再加上 2,而其中 2 用來表示暫停的狀態。這樣的做法沒啥大問題,只不過變數名稱 IsGameStarted 就變得不那麼合適,而必須改成更為合適的 GameState。另外一種修改的方式則是新增一個專門的變數,用來表示遊戲是否處於暫停的狀態。這兩種修改方法各有其優缺點,不過最重要的是,我們一開始就不應該偷懶借用 IsGameStarted 變數來表示遊戲的暫停狀態,因為如此一來程式的可讀性已經大幅降低,而且日後的修改也可能會變的很麻煩。

最後,有些變數會因為程式的演化而變得失去原本的功能。一旦發生這種情況,最好能夠將這些變數予以刪除或整併,以免造成理解程式碼時不必要的負擔。也就是說每個變數都必須盡其力,才有其存在的必要性。

勇敢打臉過去的自己

程式是活的,變數名稱與用途也是。這裡所謂程式是活的,當然不是說程式會吃喝拉撒睡。我們並不是花幾個小時寫完某隻程式後就可以從此不再理它,會如此幸運 (或不幸) 的情況通常只有一個可能性,那就是你寫了一隻連你自己也不想用的廢材程式。只要是有人用的程式,就會需要修改或增強功能,也就是程式持續成長而活了起來。在程式的成長過程中,我們需要更多的變數來滿足功能,而中間的過程很容易就產生一些病狀。舉例來說,我們一開始的程式範例有一個 PlayerScore 變數是用來代表玩家的分數。如果之後程式由一個玩家變成兩個玩家,第二個玩家的分數要用哪個變數名稱?PlayerScore2 是最常見的方式,但是不夠清楚。相較之下,Player2Score 會是比較好的選擇。那原本的 PlayerScore 呢?也變得不夠好了,應該要改成 Player1Score。不這樣修改的話你在寫程式的過程中就要一直記得 “第一個玩家的分數變數名稱沒有 1,而第二個玩家的分數變數名稱中則有 2” 。不累嗎?寫程式已經夠燒腦了,不需要再幫自己挖坑。當然,Scratch 與 micro:bit Block Editor 都可以用下拉方式選擇變數。而就算是其他程式語言,大多數的編輯器或 IDE 也都可以自動提示或選取變數,那有沒有那個 1 還有那麼大的差別嗎?我的建議是能改就盡量改,因為閱讀程式時還是有很大的差別,而且改用一致的命名方式才能讓兩個變數緊靠著出現在下拉選單中。再舉另外一個例子,在某段程式中一開始我們使用 PersonalData 來代表個人的基本資料 (姓名、電話)。而當我們需要紀錄個人的生日、地址的資料時,我們想出了另外一個變數 PersonalInformation。看起來還沒甚麼大問題。但是過一陣子後,我們發現需要再增加記錄個人的興趣、習慣等資料。好吧,那就只好再加上 PersonalDetails 這個變數了。看到了嗎,前述的問題就產生了。PersonalData 一開始是沒啥 (大) 問題的,但是當日後加上 PersonalInformation 跟 PersonalDetails 後問題就炸裂了。當然這個例子有一個很直覺的解法,那就是把全部的資料都放到 PersonalData 這個變數名稱中。但是很多時候在實務技術跟心理層面的考量下,並不會採用這樣的做法。

重點在於,之前看似合適的變數命名或使用方式,往往會因為程式功能的改變而變得不再那麼合適。我們必須隨時保持警戒的心情,而當發現開始違反規則時,請不要客氣地用力往自己的臉打下去並作出修改。或許打下去的瞬間會覺得很痛,但是用這種短暫痛苦來避免日後更大的痛苦,絕對是一件超划算的買賣。

結語

在這篇文章中,我們看到應該如何使用變數。老實說,完全不在乎、甚至違背這些原則的程式還是可以順利運作,並不會因此出現任何額外的錯誤。但是就像人的相處一樣,理解是雙方良好關係的第一步。一個不願意被理解的程式,會使得負責的程式設計師相當痛苦。唯一不同的是,程式不會自己選擇成為不被理解的那一方,而是身為程式設計師的我們所造成。也就是我們可以避免程式難以理解,也只有我們有能力避免這樣的事情發生。一個能夠被理解的程式,不只可以自己愉快地活著,也會讓處理的程式設計師同樣地愉快。如此,寫程式才會是一件愉快的事情,而不是無限的痛苦地獄。

Facebook 留言
Print Friendly, PDF & Email
Summary
正其名、辯其意、安其份、盡其力 - 談程式變數之使用
Article Name
正其名、辯其意、安其份、盡其力 - 談程式變數之使用
Description
雖然變數的觀念與使用算是簡單易懂,但是變數的使用可說是程式設計師技能表中最重要的修練之一。良好的變數使用習慣可以大幅提升程式的品質,並減少開發以及日後修改的困難度。在本篇文章中,我想要跟各位分享在使用變數時需要注意的地方。有關變數的使用,可以總結為四句話:正其名、辯其意、安其份、盡其力。
Author
Publisher Name
Everlearn Studio
Publisher Logo