好程式具體的標準
「好程式」的標準其實很主觀,因為程式好不好最終還是要回歸到是否有符合使用者的需求,畢竟程式開發的目的是要給人使用的,所以使用的人主觀覺得好用就是好程式。所謂的「台上一分鐘,台下十年功」,要怎麼樣讓使用者覺得程式好用,在開發的過程之中就隱藏著許多的細節,這些細節牽動著使用者對程式的觀感。如果要把整個開發的過程,也就是軟體開發的生命週期 (SDLC) 中相關細節都列入文章的內容,所涵蓋的範圍太過廣泛。所以在這篇文章中先撇除需求和設計間落差的問題,單就程式設計來看如何寫「好程式」。程式設計通常都被安排在需求分析的活動之後進行,畢竟總要先知道使用者要的是什麼樣的功能才有辦法著手寫程式。需求確立後,大多是經過系統分析、系統設計、程式編寫等過程,也就是要先製作藍圖、才開始蓋房子。只是在實際操作上,如果是一個規模不大的團隊,把這些過程混在一起、且戰且走、邊寫邊設計的也還不在少數。
由於是接在需求分析之後的活動,在程式設計這個階段對需求並沒有太多左右的能力,說的不值錢一點,這個階段就只是把需求文件裡用自然語言描述的內容轉譯為電腦語言。當然最終產出的標的物就是程式本身,代表著使用者的期盼、結案的關鍵、收錢的依據!
翻譯三原則
在自然語言的翻譯中有「信、達、雅」三大原則用來衡量翻譯結果的良窳。照剛才不值錢的說法,寫程式也勉強算得上是翻譯事業,是不是也適用這三大原則?符合這三大原則精神的程式是不是就是好程式?首先,以第一項「信」的原則來看,就如同這項原則的精神,翻譯的結果應該要忠實地呈現原文的意思。換句話來說,按圖施工本來就是放諸四海皆準的道理,若是不按圖施工又振振有詞、一堆藉口,會被貼上「無良」的標籤應該也是意料中之事。所以,無庸置疑地程式寫出來的結果本來就應該要符合需求的敘述,不然就等著被 PM、使用者、業主打槍,最後落得收不到錢、做白工的下場。
看起來第一項「信」的原則還滿適用的,接下來第二項「達」的原則是期望翻譯的結果能夠語句通順。雖然程式碼不像自然語言有各式各樣的變化,充其量不過是一大堆的 if-else 判斷式和迴圈的組合,要求通順似乎沒有太大的意義。但不管是早期的程序式或是現今流行的物件導向程式設計風格,適切地分割與配置程式碼區塊,讓執行的順序合理、順暢一點,以便能增加執行效率,是編寫程式碼的一項重要工作。像是物件導向中的 Design Pattern 就是為了讓開發人員在遭遇已知的設計情境時,藉由其他人的經驗快速地決定程式碼最佳分配方案。以此來看,似乎「達」的精神也可以用在好程式的標準上。
最後一項「雅」的原則是期望選用的詞句能夠儘可能地優雅,這對繕打程式碼這項工作來說就有點難度了。會來寫程式當工程師大多不是文青的料,在進度壓力之下應該也沒有興緻對著機器附庸風雅。況且程式語言寫來寫去也就那幾個保留字,能夠用來展現優雅的空間非常有限。有人可能會引申「雅」就是讓程式碼愈簡潔愈好,程式碼能夠縮就縮、能夠少打就絕不佔版面。但我卻不是很贊同這種評斷程式好壞的觀點,我倒覺得程式碼應該要能清楚地表達執行上的意圖,所以不應該是字愈少愈好。當然也不是無意義地增加程式碼來充版面,畢竟程式碼字數的多寡不是重點,而是能不能提供足夠的資訊讓閱讀的人了解程式運作的過程。在之前發表的「軟體開發團隊管理經驗分享」文章中有提到相關的內容,有興趣的人可以參考一下。所以「雅」要成為好程式的標準應該要解釋為:程式碼的寫作風格是否夠優雅。
「信、達、雅」這三個原則看下來好像都可以成為好程式的標準,但仔細想想又似乎有一些不足,沒有辦法精確地掌握軟體開發工作的一些特性。就像之前提到的,整個程式設計的過程並不是只有把程式碼打進去,還包含了許多在「翻譯」前週邊的構思、規劃與設計等等的工作,以便讓需求翻譯結果能夠符合「信、達、雅」。這些工作雖然不像程式碼能直接影響程式運作,但也和程式有一定程度的關連,像是:系統架構、介面的配置、操作動線的規劃、資料庫的調校、資訊安全考量以及非功能性的需求等等。
好程式的四項原則
所以我依據過去的工作經驗和對於這個產業特性的了解,歸納出了好程式應該具備有「穩、引、速、諧」四項原則。- 「穩」就是讓程式不容易出現錯誤。
- 「引」就是讓程式可以直覺地、方便地被操作。
- 「速」就是讓程式有效率地執行。
- 「諧」就是讓程式看起來不覺得礙眼。
接下來就分別對這四項原則做更進一步的說明。
穩:讓程式不容易出現錯誤
這是四個原則中最優先的原則,不論程式的功能再強大、畫面再華麗、運算再神速,如果程式一碰就中止,豈不是根本沒有人可以體驗這些程式帶來的優點!這個原則如同字面上的意義 - 不容易出錯,對於不可預期的情況要有適當的處理,避免程式無預警的中止,「程式無預警的中止」用行動平台的說法就是「閃退」。在使用經驗上最讓人生氣的大概就是資料輸入到一半程式當掉、所有打的內容全部不見,然後要再輸入一次。尤其是要輸入的欄位數量眾多、輸入很費時耗神之下,程式瞬間消失在眼前的情況一出現,實在很難不讓人理智斷線。
要讓程式不容易出錯,講起來很輕巧,但卻是個很複雜的課題,這是要靠一連串對細節的堅持所累積出來的結果。程式開發是一種講求精確的活動,這主要是因為目前程式最基本的運作基礎就是二進位,不是零就是一,沒有第三種可能。而程式的邏輯判斷也只有「是」與「否」,所以在寫程式時一定要明確的區分各種資料的狀態並做對應處置。一但情況超出程式可處理的範圍就會產生例外、影響程式的判斷,最終極的結果就是被作業系統中斷程式的執行、出現閃退的情況。
沒閃退就沒事嗎?其實,程式被作業系統中止感覺上很嚴重,但卻是最幸運的結果,最少錯誤沒有持續地擴大。在出現了不可預期的情況、程式仍然繼續運作,代表沒被考慮到的資料一直在運算、儲存著,對程式和資料的影響更是難料。當運作的是無關緊要的資料,頂多只是垃圾進垃圾出,等有人看到再校正就沒事了。但如果是商業價值很高的資訊呢?再加上經過很長一段時間才被發現呢?資料的錯誤可能因為連鎖反應,已被延伸、擴展到許多的週邊資料中,形成一個錯綜複雜的結構、完全無法修正或回復。
說得太虛無飄渺,感覺不到嚴重性嗎?假設今天開發的是加班系統,你寫的程式沒有考慮到某個工時組合,於是在這種組合出現的情況之下會把應該是 1.66 倍率的部份全部誤以 1.33 來計算。原本是個很罕見的工時組合,但在工作型態的改變之下開始持續出現。問題一直到幾個月之後才終於被某個錙銖必較的員工發現,加班費竟然少給了!是變相苛扣嗎?不是的話,當然是要重算清楚再補發!但麻煩的是,發現的時間點已跨過會計年度,年度結算已經完成、上年度會計資料已經被涷結、主管提報資料已依據錯誤的加班資訊送出、敘薪晉用獎懲都已底定,更糟糕的是財報已經被簽證...一堆遠比補算加班費要麻煩的問題待處理。
光是只以修正錯誤的直接成本來看,如果是負責寫程式的人應該會覺得:為什麼程式當初不直接當掉就算了!這個例子對負責的開發人員來說是不可承受之重,但在規模上不過就只是一個企業茶壺內的風暴,隨著程式使用的範圍加大還有可能會有更嚴重的結果。
所以「穩」是指程式的可靠度,也就是品質,而不僅僅只是程式運作時的可用性。然而,穩就是可靠度愈高愈好,最好達到百分之一百嗎?理想上是!但現實上,有負責過測試工作的人應該可以理解,不是不願意,絕大部份瓶頸都是高層不同意投入達成百分之百可靠度所需的成本。也的確,要達成百分之百可靠度的成本很有可能會遠高於收益,是非常不符合經濟效益的一種做法。
可靠度通常都是妥協之下的結果,在可接受的成本下讓可靠度達到最大化。而要提昇程式的可靠度可分二個層面來看,一是開發工作的品質以維持程式運作時的可用性,一是程式碼的可維護性以延長程式的生命力。
開發工作的品質包含了開發人員需具備足夠的知識來預測並回避可能出現的問題,並且對需求內容要有足夠的自主意識,而不是照單全收,再來就是看測試和驗證的功夫。這裡所謂的知識並不是光指會使用錯誤處理的語法,有時候一知半解是很可怕的,盲目地增加錯誤處理,有的時候反而可能是造成程式崩潰的原因。更何況攔到例外並不是處理問題的結束,而是處理問題的開始。而自主意識是指對於需求的內容要有反思、辨證的能力,寫需求的也是人,哪有吃芝麻不掉燒餅的。這句諺語並不是記反了而是真的發生過,我就曾遇過好幾份需求內容是對芝麻小事鉅細靡遺的描述,唯獨就漏了燒餅。
程式碼的可維護性與程式的生命週期息息相關,如果把程式看成是有生命的個體,當程式被需要、有在使用才算是活的。但人的需求是會改變的,如果程式沒有辦法跟上需求的變化做出調整,程式就逐漸變得難以使用,代表著程式開始走向死亡。一直到最後因為所有的功能都不符合需求而被下線,成為生命終結的程式。
在需求變遷的過程中,程式碼的可維護性就成了左右程式生命的關鍵。程式碼的可維護性愈低,跟上需求變化的成本就愈高,背離需求的可能性就愈大。當碰到命名毫無邏輯、排版混亂、程式碼次序怪異、沒有或錯誤註解的程式時,修改的人如果不是原作者,就需要花很多的心神、像看推理小說一般地,去抽絲剝繭讀懂盤根錯結的程式。或是類似的程式碼片段到處都有,像是有人施展了多重影分身之術,修正一個問題要改上數次的程式碼,還完全不知道程式改了會出現什麼副作用。有改過這種程式的人都應該有共同的心情,會有一股想打掉重練的衝動,但礙於進度的壓力落入了進退維谷的窘境,不打掉揪心、但重練卻會傷肝。
有人會以「寫出別人看不懂的程式」為榮,但我覺得「寫出讓人容易了解的程式碼」境界更高。要不要做取決於心態,能做到什麼程度端看個人修為。但也並非無跡可循,開發人員間的默契是增加程式碼可維護性的方法之一。默契應該就是一種相互預測意圖的互動過程,明文規定則是最有效形成默契的方式,這也就是為什麼需要有 Coding Conventions。接下來要做的就是在團隊 Coding Conventions 框架下,以命名、註解、排版等方式來建立「雅」所提到的撰寫風格。
基本上可以朝以下幾個方向來進行:
- 為增加閱讀上的便利性,程式碼應保持簡潔、一致性、可預測性,讓閱讀者能以合理的邏輯來推演,避免過於跳躍式的邏輯。
- 延續上一點,在撰寫程式碼前,先思考如何配置程式碼,將程式碼的重用性及擴充性在合理的成本下,達到最大的利用範圍。
- 程式碼最好能明確地表達程式的意圖,減少使用過於精簡、合併或隱晦的語法,多輔以註解說明。
- 程式碼註解應該是明確、有效的,不只是要補充說明程式執行了什麼動作,也應該要解釋程式設計背後的原因。
引:讓程式可以直覺地、方便地被操作
「引」指的是導引,操作介面是使用者接觸程式的第一線,所以使用者在評價程式好壞時,往往操作介面佔了很大的因素。最完美的操作介面設計應該是讓使用者不需要靠任何外在文件的說明,在看到畫面時就能夠直覺地反應要進行什麼樣的動作,以便驅使程式達成想要的結果。也就是說程式不只是要開發功能,同時也要思考如何引導使用者來操作功能,才能讓程式發揮出功效。就像是前一個原則提到的,程式的功能再強大,如果不能被使用,那這些功能的意義不過就是一堆機械碼罷了。不過,「最完美的介面」定義上要附加一個但書,那就是使用者要具備操作軟體的對應知識。只有開發程式的人和使用者在共同的知識基礎下,所謂最完美的介面才有意義。否則,再怎麼調校,都不可能會有「不需要靠任何外在文件的說明就可以直覺地使用」這種情況出現。像是要一個沒有會計背景的人去操作會計軟體,就算是在畫面中加上各式各樣的使用指引,要能順利地操作基本上是天方夜譚。
在畫面上配置輸入或顯示的元件和室內設計的概念很接近,有些室內設計在規劃時往往太著重主觀的視覺效果來討好業主,卻犧牲了生活中動線的細節。往往要等到施工完成之後入住了,才發現原來就只是好看而已,在使用上卻是另外一回事。像是冰箱開門的方向沒考慮好,冰箱打開後的門會擋在流理檯和冰箱之間,以致於拿取食材時要先把冰箱的門關上之後,才能把食材放到流理檯上,食材一多就要反覆的動作,使用上非常地不方便。
操作的動線和排列畫面的潛規則
所以導引的第一個重點應該是規劃使用者操作的動線,在現今主流視窗作業系統的制約之下,大家都習慣由視窗的左上往右下開始了解畫面的構成。所以比較常見的動線規劃手法都是重要的畫面元素排在靠上、靠左的位置,並按重要性依序排列,動作置於靠下、靠右的位置,也就是按鈕通常都擺在這樣的位置上。藉此來導引使用者,先把重要的資訊輸入完成之後,再來選擇要觸發的動作。
在排列畫面元素的時候,可以增加使用 Frame 或分隔線區分不同特性的內容,讓關連性高的資訊形成一個群組。這樣的方式可以導引使用者歸納畫面的資訊,快速地在心裡形成一個操作步驟的概念,達到直覺使用的目標。
如果要輸入的資訊項目數量較多的時候,採用分頁的方式也很常見。比較重要的項目放在較前的頁次,逐頁地導引使用者進行資訊的輸入,並且在觸發動作前提供檢視的畫面,來供使用者確認是否輸入正確。有在使用線上購物的人,對於這樣的操作模式應該不陌生才對。
類似的技巧五花八門,在此就不一一列舉。但左上右下這個模式是絕對的嗎?我想不是,就我所知道的,阿拉伯語系的操作介面就不是,是一個和我們熟悉的習慣完全左右相反的世界。這也帶出另外一個議題是操作介面設計不應該是一體適用,而是要依據使用的對象來量身訂做,像是考慮到使用對象的生理狀況、操作習慣、生活背景、成長文化、使用情境等因素來做適當的調整。
有一個大家很熟悉的程式可以用來做為負面的例子,那就是拒老的 Facebook Android App。也許是 Facebook 產品規劃人員自詡這是年輕人使用的軟體,所以完全不體諒年長者的生理需求,顯示的字型大小預設都是年長者沒有辦法順利閱讀的大小。這就算了,竟然還限制程式不隨著系統的字型設定而改變,App 本身又沒有調整的功能(也許是藏在一個我找不到的位置),最後都只能是放棄使用 App 尋找替代方案來解決年長者的不便。
Facebook 的這個生理狀況類型的問題如果是在 Windows 平台上,也許就不是大問題,最少還可以調個解析度或是買個無敵大的螢幕。同樣地,在使用情境的類型上,介面的平台不同,設計的細節就必須要再調整。例如網站有一陣子很流行使用 Hover 的效果,就是讓滑鼠的游標移到特定的位置就會有一些畫面上的特效,像是跳出選單供進一步點選。但想要讓這樣的網站也能在行動平台上瀏覽,就可能會造成很大的使用障礙,除非是你有氣功,才有可能讓觸控螢幕偵測試你的手指懸空移到了可以 Hover 的位置。
曾經還有遇過一個和操作習慣有關的案例,現在的人如果是由 Windows 開始接觸電腦,大概都很習慣滑鼠的操作模式,所以在操作以滑鼠為主的介面設計不會感到有什麼困擾。而在協助經歷過 Clipper 時代的資深會計人員開發系統時卻是截然不同的體驗,當時 Clipper 還是運作在 DOS 之下、滑鼠還不是主流的輸入設備。輸入的工作都只靠鍵盤來完成,欄位的切換靠 Tab 或 Enter 鍵來觸發。也因為工作型態的關係,所以他們一般都練就了盯著紙張資料盲打的功夫,對他們來說使用滑鼠是一種工作上的阻礙。一來手要在鍵盤和滑鼠之間移動,二來使用滑鼠一定要移動目光回到螢幕上、不可能盲移(有練就這等神功的高人請讓我知道,以便立碑膜拜),讓原本的順暢的工作節奏變得很沒效率。
當然只要設計得當,在 Windows 下還是可以用 DOS 的操作模式來使用程式。只是遙想 Visual Basic 這種 RAD 剛出來的年代,開發人員對於只要拖拉就可以完成程式的革命性變化感到興奮,以為控制項放定位靠著滑鼠就可以天下無敵了。事實上果實並不甜美,但嚐到苦果的卻是這群資深的會計人員,因為一般開發人員都會忽略了所有可接受 Focus 的控制項屬性清單裡都有一個叫 TabIndex 的屬性。當使用者按下 Tab 期望游標依序跳到下一個欄位時,游標卻像打地鼠一般地亂跳,以致於使用者被迫要使用「滑鼠點選」這種沒效率的輸入模式,這就是很典型的沒考慮操作習慣的案例。
如魔術表演般吸引使用者的目光
第二項重點是吸引使用者的目光,使用這個觀念最極致的當屬魔術表演,不同的是魔術是以誤導為操作的方向,和這裡說的引導是相反的效果。常見的舞台魔術為了要製造驚喜的心理感受,表演者會使用眼神、言語、肢體、聲光、環境等花招來讓觀眾聚焦在特定的角落,同時間會在不受觀注的角落準備下一步要呈現的內容。當一個觀眾沒有預期到的結果突然出現,就會產生不可置信的心理落差。
在操作介面的設計中,則是要讓使用者把目光專注在重要的操作元素上,例如使用動畫、圖像、高反差的顏色來引起使用者的注意。有使用過 Android 平台上新的 Material Design 的人應該會發現,在 Gmail 畫面右下角會出現一個醒目的圓形圖案,點了之後就可以開始撰寫新郵件。我想這樣設計的概念是因為進信箱的 App 後,不是要讀信就是要發信。要讀信的話所有收到的信已經在畫面的清單裡,要看哪一封就點哪一封。然後就剩下發信這一個重點功能,所以使用明顯的色調來吸引你的注意,告訴你要發信點這裡就對了!
導引不見得要使用顏色、圖示之類這麼噯昧的方式,直接了當的用文字來陳述也不是不行,像是按鈕上搭配的文字就是最明確的導引,直接告訴你按下按鈕會產生什麼效果。還有另外一個以前很常看到使用文字的例子,記得在 Windows XP 的年代,只要剛安裝完 Windows XP 進入桌面就會有一個很明顯的特效,並且有文字告訴你要開始使用 Windows 就從左下方的開始按鈕開始 (嗯,真繞舌)。
告知但不要恐嚇
第三項重點是讓使用者了解程式發生了什麼事,但卻不要造成使用者的恐慌。以最常見的訊息顯示來說,有的開發人員會習慣在攔到錯誤之後就把資訊寫入 Log 內,就當是完成錯誤處理了。對使用者來說,看到的是程式沒有任何異樣,如果今天是新增一筆資料,發生錯誤了,但程式並沒有回饋給使用者。使用者當然以為資料被存起來了,哪知下次要編輯時卻找不到這一筆資料,才發現被騙了,這時的心情絕對好不到哪裡去!
另外還有一個很常見的情況是,使用者觸動了程式要求進行資料處理,程式也依指令開始動作。但這時候使用者卻發現程式沒反應了,好像已經在處理了、又好像當掉了,不知道該等待?還是該強制中止程式?(噯昧總是讓人受盡委屈...) 會有這樣的現象大多起源於在開發的階段,開發人員在對功能進行測試時都以為瞬間工作就會完成了,殊不知這是測試用資料量不足、環境單純的假象。等到真正上線使用,執行沒有瞬間完成,反倒是畫面就像是灌了液態氮一樣,瞬間凍結了!
為了避免這樣的情況,在二大行動平台 iOS 和 Android 上都會建議開發人員不要在 Main Thread 也就是 UI Thread 上進行資料處理([1], [2]),甚至會限制不得在 Main Thread 上執行遠端呼叫這種不確定何時會結束的程序。如果一定要封鎖畫面,也許是為了要避免使用者進一步的輸入干擾資料處理的進行,這時也應該提供工作進度的顯示資訊,讓使用者可以清楚地知道工作的進度和預估完成的時間,最低限度也要讓使用者知道程式有在動作。在可允許的情況下,最好也能提供取消的機制,讓使用者決定是不是要繼續等待沒有完成的工作。
然而,過猶不及也不是件好事,像是把底層的訊息直接顯示給使用者。這種訊息看起來是很專業,但對使用者來說感受到的可能只有恐慌。因為使用者大多不可能理解底層訊息的意義,相對地就不可能知道要如何處理,慌張的反應是必然的。或者是過於頻繁的訊息顯示,不管什麼雞毛蒜皮的小事都跳個訊息出來,對於使用經驗並不會有太大的幫助。所以訊息應該要經過適當的篩選、包裝,最好不要只是通知使用者發生了什麼事,而是建議使用者可以怎麼做,讓使用者在操作程式時的不安全感降到最低。
貼心地提供的輔助
第四項重點是方便性,程式並不是只要功能俱備就足夠了,是否能夠方便地操作也必須要被考量在內。再以室內設計來做例子,如果在設置洗手檯時沒有考量到居住者的身高,雖然有洗手檯、有水龍頭、有鏡子,而且功能都正常。但使用時因為設置的高度較低,洗手檯的鏡子沒有辦法在站立時照到全身、臉被鏡子切掉一半,所以每次檢視儀容時還得採半蹲的姿勢;或是洗碗槽高度不夠,以致洗碗時要一直弓著身體來遷就洗碗槽,長久下來讓腰產生很大的負擔。
回到畫面的設計,以一個最常見的操作情境來看,輸入日期時讓你年月日一個字一個字輸入比較方便?還是跳出一個日曆畫面讓你點日期比較方便?很難說!為什麼?如果是要輸入最近的日期,而日曆是以今天為基準點顯示日曆,那還滿方便的。但如果是要輸入出生年月日,又沒有辦法快速跳到指定的年份,對比較早出生的人來說那就是一場惡夢,這個時候我還寧願一個字一個字打還比較有效率,而這就是一個直覺但使用上不方便的例子。
不方便的操作會中斷使用者的注意力,使得原本設定好的導引效果打了折扣,甚至是完全沒有達到目的。所以這幾項重點都是相輔相成的,必須要相互配合才能達到最大的導引效果,也才有可能讓使用者在沒有外在文件的協助之下,順利地使用程式所提供的介面。
速:讓程式有效率地執行
「速」就是程式執行的速度,也就是儘可能地縮短執行的時間,有多快跑多快。當然人的慾望是無止境的,永遠不可能滿足使用者對速度的期望,所以重點就是要讓程式的執行效率達到最大化。一但出現程式執行速度不如預期,當然就是做效能的檢測,找出執行速度的瓶頸點。如果程式有發現明確的效能瓶頸造成執行速度不理想,就針對這個部份做調校,一定會有顯著的改善效果呈現出來。最擔心的是檢測沒有辦法區分出產生瓶頸的程式碼,也就是慢是所有程式碼的共業。這時能做的大概只有逐一地對程式碼進行檢視、調整,其實也差不多就是重寫所有的程式碼。
為了避免這種情況出現,當然最好是在編寫程式碼的時候就謹慎地選擇要輸入的程式碼。以下面的二段程式碼為例:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (i = 0; i < n; i++) { | |
a = b + c; | |
d[i] = i * 2; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
a = b + c; | |
for (i = 0; i < n; i++) { | |
d[i] = i * 2; | |
} |
執行後達到的效果都一樣,但第一段的寫法在效能的表現和第二段相比是有差距的。有些人可能會想,只不過是一行簡單的指令,現在 CPU 都這麼夠力,差不了多少時間!如果把這二段程式碼以時間複雜度的時間函數來表示,分別可以簡單地表示成 2N 及 N+1,把這二個方程式轉成圖形會得到二個斜率不同的直線。隨著 N 的數值變大,二條線之間的垂直距離會愈來愈遠,也就是說就算 CPU 的運算能力再強大,只要迴圈的次數夠多,二段程式碼在執行時間上終究會出現不可忽視的時間差距。
這只是個簡化過的示範,真實的世界中會出現的情況遠比這個示範要來得複雜許多,效能差距顯現的門檻就會低很多。但有經驗的開發人員會犯這麼明顯的錯嗎?就像剛才說的,範例中使用的是一個很簡單的迴圈結構,所以很輕易地就可以看出問題所在。在複雜的迴圈結構之下,是不是還能這麼輕鬆地察覺程式的效能問題,可能就要打上個問號了!
再來看一個例子,在很多時候要達成需要指定的效果,可能會有很多的選項,表面上看起來挑哪一種都無所謂,但常常會失之毫釐、差之千里。像是在 .NET Framework 裡有 String 和 StringBuilder 二種可用來處理字串的選項,如果要處理的字串會出現頻繁地異動,選擇 String 和 StringBuilder 在功能上都可以達成目的,但在效能上卻有著明顯的差異。
所以如果不希望寫出來的程式永遠都比別人慢上一線,但又沒有什麼明顯的效能瓶頸,就要開始注意細節上的效能考量,畢竟聚沙能夠成塔、滴水也能夠穿石。再來就是充分利用 CPU 運算的技術,最常見的就屬多執行緒或是平行運算。因應 CPU 的物理制限,新一代的 CPU 開始朝向多核心的方向發展,也就是說現在的資料處理工廠都配備了多條生產線。在處理資料的時候,二條、四條生產線同時處理資料,絕對比只用一條生產線來處理資料要快上很多。
但使用多執行緒來設計程式絕對不是件容易的事,最少不像喝水那麼簡單。如果是要同時處理多項不相關的資料,那情況會比較單純,一種資料一條生產線、一個蘿蔔一個坑,大家各做各的井水不犯河水。反之,如果要處理很大量的單一資料,又想要利用所有生產線的產能來縮短執行時間,就可能會有工作如何切割、資料處理次序等問題需要煩惱。再進階一點,因為資料都在同一個記憶體區塊裡,所有的生產線同步進行會產生資源競爭、重覆進入的情況,或是莫明其妙地進入無止盡相互等待的死結狀態,這時程式就會出現呆滯的反應。如果想要偵察這種錯誤,出現問題的資料組合可能稍縱及逝,不是單純地設定中斷點就能攔到,沒有一點想象和推理的能力,還有可能根本查不出問題的根源。
不過這細節已經脫離了這個小節的內容,牽涉到的是開發人員的素質問題,會在之後的內容說明。
這項原則為什麼會排在「引」後面?
當一個有提供進度資訊、有取消按鈕但要執行二分鐘的程式,和一個執行時間只要一半,但執行期間畫面會凍結、沒反應的程式,你選哪一個?我選前者!
還記得求學時等公車是個很深刻的記憶,尤其是等末班車。學生時代沒有錢,搭計程車一定是最後不得已的手段。這時候等末班公車的心理就變得很微妙,等待是一種生活體驗、不知道何時結束的等待則是一種折磨。在不確定公車過站沒的情況下,這時能做的只有不住地望向公車來的方向,怕一個閃神被公車給跑了,但又會懷疑末班公車是否已經過去了。如果是,應該忍痛花錢改搭計程車早點回家,但又怕公車其實還沒過,一搭上計程車卻看到公車在遠方緩緩地駛來,心裡不甘,想著如果再多等一下就好了。人在候車亭裡,心裡會呈現一種膠著、煎熬的狀態,覺得度秒如年。
假設那時的候車亭有像現在這樣,提供智慧型站牌能夠知道公車到底收班了沒、或是有 App 可以用 GPS 查詢公車位置,就不用遭受這種心理摧殘。類似的心理效果也會出現在程式的使用上,一個不知道多久的等待在心理認知上可能會比有限度等待的時間要久,縱使實際上短很多。再加上使用者可能會因為程式沒有回應,心裡慌亂、急於改變現狀,所以干擾、中斷執行,以致程式根本就沒有機會完成工作,雖然他處理得比較快。
基於這樣的理由,導引使用者當然要比增加程式效率來得重要,在調校程式效能之前應該先解決使用者感受上的問題。
諧:讓程式看起來不覺得礙眼
「諧」所想表達的概念是操作介面在配置上的和諧,讓使用者在看到程式時能產生優雅的感受,和「雅」有點異曲同工之妙,只是之前看「雅」的時候是比較專注在程式碼的部份。程式的操作介面優不優雅也許太過抽象、許多人沒有辦法在腦海中形成畫面,可是只要一拿 Android 早期的畫面風格和現在 Material Design 相比應該就會很有感!一個很... 「工程」,但另一個就給人有截然不同的感受,比較「藝術」。同樣是這幾個元件做的畫面配置,卻在套上不同的呈現手法後讓使用的感受上有很大的差別、使用的愉悅度大為提昇。Apple 在這方面一直都是佼佼者,也難怪 iOS 的介面設計能讓這麼多人擁護。
要做出讓人在視覺上感到舒服的畫面,是需要有美感及配色上的敏銳度,可惜這是天生的!如果你沒有天份,又得不到視覺設計人員的奧援,除了多閱讀一些官方提供的設計準則、模仿專業之外,還是有一些不需要天份就可以進行的細節,那就是「對稱」和「平衡」。
曾經看過不少的本土自行開發的軟體,在功能上比起國外同類型的產品毫不遜色,但使用起來總覺得欠了點質感。這是很可惜的一點,用廉價來形容可能太不厚道了,但的確會讓人有「這應該是不同價位帶產品」的念頭,即使和國外的軟體是同級的產品。會有這樣的差別,就我的觀察大部份是缺了對稱和平衡,像是有些會看到畫面中輸入框的字有的大、有的小,但大小又沒有一定的邏輯;或是在一樣的字型大小的設定下,有些輸入框比較緊貼著文字、有一些有留下不小的空間,很明顯地有胖瘦不一致、排列沒有對齊的情況。
而有一些則是在固定大小的視窗中,把所有可以操作的元件集中在畫面的左上方,右方和下方留下一大塊空白的區域。可能是我沒慧根,我實在沒有辦法領會這樣設計的意圖,也許這其實是開發人員讓使用者在螢幕上書寫註記或貼上便利貼的空間?!
這樣的畫面配置結果,在使用時會讓人覺得礙眼、增加潛意識裡使用程式的負面觀感。曾經聽過有一個心理測試是測試者拿著一張點了一個黑點的白紙,問受測者看到了什麼,大部份的人會回答黑點而不是白紙。所以人的大腦是很容易被操弄的,同時人的大腦會被對稱的東西吸引,於是保持畫面上的「對稱」和「平衡」就是讓大腦覺得愉悅很重要的潛規則。
雖然在大家的印象中微軟的 Windows 介面設計常常是被批評得一無是處,但其實仔細地觀察會發現在 Windows 中,畫面的設計是經過很嚴謹的規範,包含了許多「對稱」和「平衡」的細節。以對話視窗為例,可以看到視窗的上、下、左、右邊緣和最靠近邊緣的元件一律保持著一定的距離,包含元件與元件之間也使用相同的距離做為間隔,當然輸入框不會有胖瘦不一致的情況。而所有的元件也都以一致的對齊規則排列著,例如橫向一定是對齊中心線、直向則是標題全都靠左切齊。在顯示文字時使用的是相同的字型、在大小上除非有特殊的目的外也保持一致。這些配置的方式,不只是在同一個視窗中,就算是不同的視窗也維持著統一的風格。
這些細節組合出來的結果,也許不是讓人驚艷的蘋果經驗,卻是紮紮實實地表現出大公司應該展現的水準,而這也是「諧」希望達到的效果。雖然這是四項原則中的最後一項,但卻是門檻最低的一項,並不需要血統而是每一個人都可以辦到,只要訂下畫面配置的標準、按部就班,就可以打造出一個使用起來不礙眼的程式。
開發出好程式要做的功課
原則設定好了,接下來還需要能夠實踐,否則就只是空談,而實踐就得依賴開發人員的素質。要提昇開發人員的素質,有三個重點,「基礎、基礎、基礎」。沒錯!想要提昇素質其實沒有速成的方法,就像功夫電影都會有強調紮馬步的橋段,腳力是提供一切招式力道的根源,而再凌厲的招式也可能因為重心不穩而失去了威力。開發的領域中,愈充實的基礎知識在遇到狀況時愈能有效的解決問題,甚至是提早預防。因為在知道有哪些現成選項可以運用,也就不需要重新打造一個輪子,而是站在巨人的肩膀上。心酸的業界生態
身為開發者必須要很清楚自己設計的每一個環節會產生的效果或可能的問題、限制,才能夠貼切地符合需求的內容。這是需要具備一定的經驗和知識,尤其是隨著時間的累進、程式愈來愈精緻化,開發程式時不論是程式語言或是作業平台都伴隨著龐大的內容、大量的資訊,需要時間來消化、理解。入行的條件不再高不可攀
也許是現代開發工具的強大與坊間的教育機構的普及,很多人經過簡單的訓練就以「會把網路上搜尋來的程式片斷兜起來、可以正常執行」當成是寫程式的全部。但是科班出身的就充滿著光芒嗎?我也看過不少人,不是在課堂上虛擲光陰,就是沒有將所學融會貫通,展現出來的工作水準也只是一般般。現在寫程式的模式除了有現成的框架、函式庫可以選用,網路資訊的發達讓形形色色的程式碼片段都可以搜尋得到,很多時候只要選好框架、把網路上的程式碼片段組合起來,就可以有一個能夠執行的程式。當然沒事就沒事,但不幸出事了要算是誰的責任?
在過去的工作經驗裡就曾經遇過當程式發生問題,我問負責的人出問題的程式碼片斷在做什麼事,他回答我:「不知道,從網路上貼來的!」如果你是老闆,遇到這種情況會不會想:「那我應該付錢給原作者?還是負責貼上的人?」
當你不了解程式執行的內容,怎麼能保證你的程式能夠穩定地執行、沒有安全上的疑慮?的確,在實作上有很多時候需要進階一點的演算法,像是壓縮、圖形運算,非科班出身、沒有數學基礎的還真沒有辦法理解。有點職業道德的人會儘可能地做好測試,就算不知道內容,但最少確認執行的結果是自己要的。而不是交差後惦惦當沒事,祈求程式不要出問題!
這個情況,讓人聯想到前陣子一連串的食安事件。有一部份餐飲業者沒有具備熟飪的基本功夫,所以很多材料必須要靠外購,對食材的特性也沒有足夠的掌握能力。但在成本的考量下,黑心一點就選擇明知有問題、來路不明但成本低廉的食材。像是用的油出了事、用到了餿水油,就只能推說:「我也是受害者,是那些做餿水油的廠商該死,應該要向餿水油的廠商求償,與我無關。」
當然,也有負責任的店家並不是用成本低廉的食材但也遭到波及。第一時間會覺得這些人真的很無辜,但進一步想想,要從事餐飲業是你的選擇,你的客人是信任你的烹飪技巧、口味的掌控、食材挑選能力等等的品質要素,而不是信任你的進貨廠商。出了問題,要說你完全沒有責任並不合理!對消費者來說,合理方式應該是先賠償給消費者,至於和進貨廠商的賠償問題是店家內部的事,這樣對我們這些末端的消費者才公平不是嗎?而且,規模大的商家更應該要有資源、能力與責任承擔起這樣的標準。
寫程式不再是閉門造車
反觀我們自己理所當然地把別人寫好的東西拿來用,卻鮮少深究內部運作的邏輯,是不是也是一樣的缺乏商業道德?說起來還挺心虛的,批評的義正嚴詞、擲地有聲,原來比起食品產業我們高尚不到哪去!用「我們」似乎太過一竿子打翻船人,所以我招認最少我自己是。在社會分工細致化、講求效率的年代,工作當中使用自己不了解的產品成了必要之惡,就像是要求店家所有的食材全部自製一樣,是一種奢求。再加上資訊科技雖然發展沒幾年,但是已經形成了一個很龐大的知識體係。不一而足的作業系平台、程式話言,多如繁星的框架、SDK、函式庫,各行各業的領域知識、商業邏輯,每一個區塊都形成了一個有高度的門檻和學習障礙。在開發程式時,如果要完全以自製的模式來進行,不是成本高得嚇人,就是時間長得不能接受,以致於難以實行。
知名廠商的函式庫、開源的框架比較不可能會是問題的來源,畢竟這些程式都是以一般化的用途為出發點、能夠適用於大多數的情況之下,並且經過重重的檢驗、測試。知名的公司也不可能自毀招牌,放任產品中有明顯的瑕疪或安全問題。但由網路上搜尋到的程式碼片斷往往是示範性質或專為解決特定問題,不見得與程式的需求百分之百的契合,如果一字不改或是對執行的內容一知半解的情況下編入程式內,這樣的做法和之前舉的食安例子中使用來路不明食材的店家有何差別?
那有些小公司或不明來源編譯好的函式庫豈不是問題潛在的製造者?會不會內藏惡意的程式碼?這個問題對於管理者來說會是一個取決於風險與成本的困難抉擇。端看你的程式對於安全的要求程度,是企業內部敏感資料的系統?還是給一般人使用的系統?打算付出多少的成本?沒有打算付出太多的成本,只能承受較高的風險;不能承受風險的,就得要付出較多的成本,像是有制度一點的應該要成立專職的安全小組對程式做必要的檢測。
如果要問我,所開發的軟體遇到像食安風暴的情況,會不會採用和那些店家一樣的態度?我會,同時還會用投名狀中龐青雲的語氣說「裝死無敵!這是規矩!」。但這是問題發生了的假設,在發生問題之前我會想要儘可能地避免,畢竟程式是自己寫的,光嘴巴上喊沒責任就可以置身事外也不太可能。
追求品質的價值在哪裡?
要把這個想法做好,首先會遇到的就是被海量資訊淹沒的無助。這是資訊從業人員普遍會有的心酸,要學習的東西愈出愈快、愈來愈多。先不看是不是有足夠的時間吸收,光資訊量就已經超出一般人大腦能夠承載的極限。餐飲界裡隨處可見幾十年的老味道,能夠傳承好幾代的手藝。反觀軟體界,當年縱橫 COBOL 和 Fortran 語言的系統,試問如今安在?姑且不看這種由時代浪潮所帶來的興衰,同一家公司的技術總會一脈相傳、延續演進吧?過去靠微軟的技術混過飯吃的那段日子,有過不少次被微軟打臉的經驗,那頭才在研討會上大張旗鼓地熱烈推銷,回頭卻是悄悄收攤,不再發展、更新。收兵尚且要鳴金,有的時候是連鳴金都省了!我的青春、我的人生,就在不斷地用新技術翻修舊系統的循環中渡過。資訊技術的更替如過江之鯽的現象,再加上公司高層多以利字為先,造就了不少開發人員速成的心態。對使用的技術一知半解、得過且過,程式碼能用就好、不求甚解,反正用沒多久又要換一種寫法,複製貼上的時間已經比自己打字的時間還多。而衍生出來的問題是遇到了技術上的狀況,常常磨了很久得到的答案是不可能解決、沒辦法處理。但等到換一個對技術比較自我要求的人去試時卻又不是這麼回事,有時解決的方案還直接到不行,可是原本負責的人找答案時又認真到你分不清楚是否是該歸類成偷懶的藉口!這樣的情況我遇到的並不是個案,也不是單一個人才有,甚至習以為常到不忍去苛責這個大環境所形成的共業。就管理者的角度,迫於專業分工下的現實,只能儘可能讓開發人員針對自己擅長的領域做專精,用團隊的力量來截長補短。
既然業界風氣如此,那一昧的追求開發人員的素質是否太過矯情?還是個人完美主義在作崇?組合網路上的程式碼所產出的程式真得有這麼不堪、有這麼嚴重嗎?就如同做吃的這件事,沒有高超的烹飪技巧、專業級的食材挑選眼光,就不能煮東西給別人吃了嗎?那這樣豈不是所有家庭的爸爸、媽媽都不能下廚,都只能外食?如果我有一道拿手的菜就不能分享給親戚好友、街坊鄰居?
我個人認為,程式本來就是解決問題的工具,如果寫的人快樂、用的人也快樂,是否得這麼吹毛求疪地一定要設定什麼門檻或標準?我想並不是這樣的,業界常常有一句話:「沒有最佳的解決方案,只有最適合的解決方案」。所以在這裡完全沒有要貶低不是接受傳統制式訓練的從業人員,或是對於願意投入熱情從事程式開發工作的人有任何不敬的意思。相反地,能夠用程式解決生活中或是專業領域上遇到的問題,是很值得敬佩的一件事。最少人家有第二專長,而我只能靠人家的第二專長糊口,光這一點就沒什麼資格對人說三道四的。
不過,這文章怎麼好像愈寫愈沒立場,那還談什麼素質的提昇?先來看看 Uber 和 Airbnb 的所帶來的爭議,這二個平台所使用的模式很類似,都是共享經濟的實踐者,分享私人閒置的資產來對大眾提供服務。像是如果我有閒置的車子我可以開著它去載客;如果我有閒置的房間我可以提供給短期需求的人來住宿。聽起來很美好,不但是各取所需,還可以有效利用資源、落實環保的概念。
但為什麼有人要出來反對、抗議,而政府也介入干預?是政府狗拿耗子?那些既得利益者眼紅?如果是眼紅,那小黃司機為什麼不叫車隊也搞一個類似的平台來競爭就好?我用自己的車載朋友也要證照?加入 Uber 載人違法,自己載朋友不違法,差別在哪?是因為沒有從中獲利?朋友請吃飯當謝禮算不算從中得利?我使用的是全新車,車款高級、內裝豪華,載客時也是西裝革履,開車時戰戰競競,車上還有額外的飲水服務,哪一點比計程車差?明明雙方都是你情我願,又沒礙著別人,這樣也要抗議?那有乙級證照的廚師為什麼不出來抗議滿街沒證照的路邊攤?開店做生意的店家為什麼不出來抗議擺在騎樓下的地攤?
上面是延伸前一段的立場只呈現單一面的意見,而實際上兩造的論點各據一詞、沒有太大的交集。但可以看到一個現象,都是提供服務的二方在爭議,購買服務的第三方卻沒聽到很大的負面評價。我想那是因為還沒有遇到危害切身利益的情況,以服務提供者的角度來看,重點還是那句話:沒事就沒事,但不幸出事了要算是誰的責任?一但牽扯到利益,問題就會變得異常的複雜,不管你是爭議的哪一方、有沒有政府背書的執照、或是你從哪個名校畢業的。面對的若是親朋好友,可能會看你的情面,不用你開口,就主動放棄權利,也許幫忙出個必要的費用就當沒事。但如果是你不認識的人,你有把握可以說服苦主讓你輕鬆過關嗎?奧客事件在新聞中屢見不鮮,當你碰上了一個要跟你走法律程序的對象,你真的有時間、心神應付?
所以,如果你對於寫出好程式還有一點期待,不管你是完美心態作崇、還是想要明哲保身,提昇品質是一項看得見回報的投資,所謂一分耕耘一分收獲,端看你想要先甘後苦還是先苦後甘。也就是說,看你想要先花時間避免未來的損害?還是你想要把時間用在出問題後擦屁股這件事情上?
基礎、基礎、基礎
能做的努力大概就是一開始提到的:基礎,多一分了解才多一分的機會知道怎麼逃避責任... 我是說保護自己,就像法律通常都是保護懂法律的人。當然在軟體的工作領域中,很多是商業機密、只能用不能看,在沒有授權的情況下太「深入」是會有觸犯法律的風險。所以這裡指的努力,當然就是針對公開的部份,像是文件、開源碼,至少讓自己不是在一知半解的情況下做出錯誤的抉擇。具備關鍵的知識
基礎中的基礎不外就是在學校中教授的那些課程,像是計算機概論、作業系統、資料結構、工程數學等等。不過,這些內容卻也是在實際從事程式開發時最不具存在感的內容。現在的程式設計都架構在層層的堆疊與包裝之上,很多底層的問題都被隱藏、處理掉了。像是很多的語言都有提供記憶體回收(GC)的機制,所以 Memory Leak 問題變得不需要花時間處理,原本課堂上學的 Memory Leak 注意事項也無用武之地。但這些設計上的問題仍然實際存在著,有一些仍然是要靠開發人員素養來做出適當的抉擇。在選擇變數型別來儲存數字資料時,有些人會想容量應該是愈大愈好,以免數字不小心超過上限而讓程式出現問題。在沒有接觸過相關的知識會根本沒想到,變數的型別會對運作的效能上產生影響。而造成效能影響的原因,來自變數型別與記憶體長度的關係,是底層一個意想不到的運作規則。
還有像是之前提到的多執行緒在設計時會遇到諸多的問題,如果具有相關的知識就會了解系統底層的工作原理、多執行緒的管理方式、對應處理的機制。資料結構可以協助設計有效率的資料處理模式,像是如果要進行排序會有許多的排序演算法可用運用,每一種都有其特性和適用的時機,並且有一套學術上的方法可以預估處理模式在效率上是否符合需求。同時也會知道遞迴很好用,但呼叫階層數太多程式是會當掉的。如果要計算 1 加到 N 的總合,第一直覺是使用遞迴來寫,寫出來的程式卻有個致命的缺點,N 有上限、超過程式會當掉。可是改用梯形面積公式,程式只要一行、執行時間天差地別,N 也可以理論上達到無限大,最少可以達到變數能儲存總合的最大值。
這些相關的機制大多會被實作在程式語言所搭配的 SDK 上,技術文件中也許只會提到使用的規格,而不會有詳細的說明來解釋背後的原理。當你擁有相關的背景知識就可以適切的運用這些功能,而不是靠著猜測與試驗來決定要選擇哪一種方案。這些選擇對程式的穩定性與執行效率都會有舉足輕重的影響,也決定著程式一但出現問題時初步處理的策略,發現及解決問題所要耗去的時間。
數學更是這一些基礎知識最不起眼的,就像我過去在企業 IT 部門服務的經驗裡,高階的數學知識完全束之高閤,大多數的程式只是單純地要求資料庫的 CRUD,資料處理的運算需求也頂多是加減乘除。不過,數學基本上可以訓練邏輯推演、協助思考,如果能夠融會貫通是有機會可以讓自己增加薪資條件、進入特定的專精領域,像是圖學裡的演算多是靠矩陣、向量等運算來進行,這是在影像處理、遊戲、3D 應用中必須的。
同時,沒有明顯的助益並不代表就是多餘的。這些知識會成為基礎是有其道理,所有的人造物都不會是跳躍式地一下就以複雜的結構出現,都會是由簡單的規則組合並堆砌所演化出來的。這個理論套用在軟體產業中一樣適用,基於相同底層所發展出來的系統都會俱有一定的相似度。所以吸收的這些知識會內化成為你的工作能力,例如增加技術的敏銳度,提昇學習的效率,更容易接納新的技術、縮短上手時間。這就好像如果你有富爸爸,做起事來一定是事半功倍;但反過來如果是白手起家,遇到困難就是只能一步一腳印地走出來了。
掌握程式語言
再接下來的這項基礎效果就會比較明顯一點,那就是程式語言。程式語言是程式開發的核心,用來編譯成 CPU 可執行機械碼的依據。而程式語言靠的是保留字來跟編譯器溝通,透過保留字設定好的意義來轉換成機械碼或中介碼。程式語言是所有人接觸程式的切入點,學習程式語言的脈絡不外是變數宣告、運算元、判斷式、流程控制,進階的還有物件導向,但有不少人對於程式語言的學習也就僅此而已。隨著時間的推演,不少的新概念被導入既有的程式語言之中,像是現在最新的是大家都流行要 Lambda 一下,再早一點是使用 Attribute 來提供程式碼在執行時期的附屬資訊,或是利用保留字來簡化多執行緒環境的資料同步問題。
這些程式語言的新語法如果不會對寫程式造成改變,通常也都不會、畢竟還是要考慮到相容性,很多人的選擇都是視而不見。就算是以喜新厭舊聞名的資訊產業,仍存在著為數不少的堅持不輕易改變的從業人員。姑且不看新的語法所帶來的撰寫上的彈性、工作效率的增加、執行效能的提昇等優勢,就算是只安於剪下貼上的工作模式,當網路上搜到的示範程式碼愈來愈多是以新語法撰寫時,打算如何銜接到所負責的程式內?或是有一個對新技術高度狂熱的同事寫的程式碼要讓你接手,這時還能夠以不變應萬變嗎?
只有新玩意才不被人重視?泛型就不是個新玩意,但沒聽過、不知道如何使用還大有人在,不然就是只會依樣畫葫蘆地呼叫以泛型寫的功能,自己要設計卻無從下手、也不了解別人含有泛型設計的程式碼。所以對程式語言不夠了解會導致的下埸就是看不懂別人寫的程式碼,能被分配的工作就只能是只寫不改,可能嗎?
雖說看得懂別人寫的程式碼,是發現邏輯錯誤或語法問題的第一步,看懂了卻忽略細節一樣也可能對錯誤視而不見。有一個很古老的案例,以下有一個在 VB 還沒有進入 .NET 的時代的程式碼範例:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Dim A, B, C As Integer |
我遇到很多人都以為三個變數全部是以整數型別儲存在記憶體中,但實際上卻是只有 C 是整數型別,另外二個是以 Variant 的型別來運作。這個問題嚴重嗎?就 VB 的語言特性來說,平常不會出現什麼異樣。只是當問題出在 Variant 上,程式碼的差異一下沒看出來,要確認問題發生的原因還是挺累人的。
還有一個很基本卻是不少人忽略的細節,那就是變數的生命週期、可見範圍的議題。看似不經意的變數宣告動作,影響所及是程式會不會虛耗寶貴的記憶體空間、佔著毛坑不拉屎;也有可能變數的內容在沒有料想到的惰況下被更改,讓程式時不時地出現莫名其妙的反應,卻又遍尋不著發生問題的原因。
善用開發的工具
要開發程式,絕大多數的人大概已經脫離不了對 IDE 的依賴,一個整合好開發所需環境的工具。從單純的打字、編譯、偵錯等基本的開發功能,到程式碼提示、即時語法檢查、程式碼重構、版本控管、程式碼複雜度檢查、效能檢測、測試報告等跟團隊與品質相關的功能。能做的事愈來愈多,但我們在進行開發工作時對 IDE 提供的功能中能夠掌握運用的比例有多少?很多跟 Application Lifecycle Management 相關的功能也許不是負責開發工作的人說要用就用得上,但還有不少是和工作效率習習相關的可以快速發現問題、縮減開發工作負擔的功能。曾經就有遇過有人在除錯時,仍在使用最基本的左右逼近法,完全不知道有條件式中斷可以用。很難想像在遇到大型迴圈時,怎麼有辦法耐得住性子,一次又一次地按著單步執行的按鈕,直到期望的條件出現;又或者是在很多階層的遞迴之間,是如何保持清醒、很明確地知道現在程式走到了第幾層的呼叫?
「工欲善其事,必先利其器」大家都知道,很多人也羨慕馬蓋先可以用一把瑞士刀就可以解決很多問題,但卻在自己拿到瑞士刀時,只知道使用其中一項工具來解決所有問題。有的時候,功能的使用是需要有一點創意才能找到符合自己習慣的模式,但最少應該要先點開來把玩看看,才能藉由熟悉找到最合適的使用情境。
熟悉程式運作的環境
這裡的環境指的是由作業系統內建函式、官方 SDK、各式框架、第三方函式庫所組成,用來提供程式執行時期一切運作的基礎。既然是基礎,就是在提昇開發人員素質裡所要討論的範圍內。這個部份還真的是知易行難的典範,跟學校教授的課程相比變遷劇烈、跟程式語言的內容相比更加龐雜無邊、跟開發工具的功能組合相比更是繁複多變。隨便挑一小區塊的資訊都可能看到成了過去式還看不完,是很心酸,但無奈之餘最少應該要對工作所需要的領域有「基本的認識」。
過去有接觸過不少有所謂的「開發 Android 程式」經驗的人,對談之後發現這些人對 Intent, Service, Content Provider, Broadcast Receiver 等等基礎的元件完全沒有概念,不要說沒有使用的經驗,甚至連這些名詞都一無所悉。還有的根本不知道自己寫程式用來控制畫面的元件叫 Activity,當然也不用提進階一點的 PendingIntent 或 IntentService。這應該已經不是專不專業,而是合不合格的問題了!
再來看一個架構大一點的例子,在因應 RESTFul 輕量化需求而誕生的 ASP.NET Web API 之前,WCF 一直佔據著微軟遠端呼叫的主要舞台。WCF 藉著簡化的開發過程、彈性的設定檔調整,讓不具底層知識的開發人員也可以輕易的建立跨電腦傳遞的功能,成為了企業內架構分散式系統的好選擇。但隨著行動平台的堀起,行動裝置要被納入分散式系統成了熱門的需求。這時,不知 HTTP 規格是何物的開發人員就踢到了鐵板!要讓沒有 WCF 的行動平台可以呼叫的情況下,限制服務端只能用 HTTP 通訊是必然的。然而安全性是 WCF 主打的特色之一,服務端一定設計了相當程度的安全驗證,沒有 WCF 怎麼通過驗證?
該想到的情況 WCF 都想到了,沒想到的也保留了接口可以擴充,調整設定參數就可以完成準備。行動裝置要如何送出驗證資訊卻是尚待解決的問題,如果送不了,是不是把服務端的安全機制撤下來?有資安在,這個想法是不可能的!為了行動平台再重新打造一組服務?如果有閃電俠在可以考慮一下!這時如果對 HTTP 的規格有一定的了解、研究過 WCF 設定細節,就可以知道 WCF 要進行何種設定,並且在行動平台送出要求時要附上什麼樣的驗證資訊,以最低的成本來達成任務。
對於所謂「基本認識」的作法,每個人天賦不同、做法也因人而異,也許有神人能夠把所有的資訊記憶在腦海之中。我的習慣是最少先瀏覽一遍文件的目錄或大綱讓腦海中有一個印象,然後期望體內的小宇宙能在遇到障礙時,提醒我排除的方法可能存在於文件的某一處。有的時候的確會很神奇的靈光一閃、小有幫助,多數的時候還是得靠 Google 大神,有道是團結才是力量嘛!只是就算 Google 再神,也得要知道怎麼問、下什麼關鍵字,這就要看平常閱讀的功夫下得有多深了,也就是一開頭提到的「基礎、基礎、基礎」。
0 意見:
張貼留言