程式設計是一種多面向特質的活動,既可以從演算的觀點來看待,也可以從架構的角度來加以檢視。從演算的觀點來看,程式設計的焦點在於,如何讓計算機器解決特定的問題時,能採用執行效率高、節省資源的方式;而架構觀點則重視彈性、易於擴充、改變起來夠便利的方式,迅速地組裝、搭配各種建構區塊,並從中獲取最大的生產力。
演算觀點與架構觀點的過與不及
這兩種角度都反映出某種程度程式設計活動的本質,卻又不完全。所以我們時常會看到一個現象:有些程式人信仰演算觀點,往往認為演算法才是程式設計的王道。對他們來說,程式的時間複雜度或具體的執行效率,是凌駕於一切的指標。程式的美感便於此處展現。信仰架構觀點的程式人,則是重視軟體架構上的特性,例如可擴充性、彈性、生產力、易於理解、……等。對他們來說,程式的美感,有著不同的定義方式。
通常在校園中,電腦科學的程式設計課程多半偏重演算觀點的角度。但是,一旦進入職場後,似乎軟體架構的重要性又在這演算能力之上。當然,這種看法並無法以偏概全,有些人的程式設計工作,便是在發展新的演算法,例如從事電腦輔助IC設計系統的人,或者像開發多媒體編解碼器的程式人,他們的工作對於效能輜銖必較,非得千方百計地壓榨電腦的每一分力氣。對這類程式人來說,演算觀點自然是相當重要而且關鍵的觀點。
但無疑的,隨著電腦科學及軟體工程的演進及發展,在大多程式人日常的開發生活中,所需的絕大多數演算法和資料結構,早已一應俱全。不僅教科書上都有完整的介紹,而且幾乎都已包裝成為高階的程式庫,程式人可以用看待一個個黑盒子的方式來運用它們。對於許多程式人來說,如何更有效而彈性地組裝、運用它們,成了更為要緊的議題。
此外,有些重視演算觀點的程式人,他們只在意程式執行的效率或演算方法的良窳,卻不甚關心他們所寫下的程式碼是否容易閱讀、擴充、維護、修改,以及足夠模組化以便輕易與其他模組搭配一同使用。這樣的論點,並不想一竿子打翻一船人,在我的經驗中,能夠撰寫出既高效、架構又優雅的程式人,同樣也是所在多有。但是實際上我們的身邊,不太在意程式架構、只追求效率的程式人,也是時有所見。
有一個普遍的成見是,能撰寫高效演算法程式碼的程式人,才真正具備價值。而這樣的看法,無疑抹滅掉軟體架構的重要性,畢竟,對軟體開發而言,效能固然重要,但也不是那唯一的聖杯。大多數軟體產品或服務的開發,所遭遇到最大瓶頸往往不在效能,而在開發的生產力,而生產力則深受軟體架構的優劣所影響。正如前文所提及的,日常所需的演算法,其他人早已研究地相當完備,而且各式程式庫垂手可得,如果要做到效能的最佳化,最好的方式便是交付給各領域的專家們。一般應用程式開發者的責任,在這個時代中已經有所不同。
認識演算法及資料結構的價值
或許你會猜想,從演算觀點出發的程式設計在這個時代中,已經不重要了。其實不然。
我們固然看到許多人不重視程式架構,但同樣也可以看到許多不重視演算的開發者。高階包裝的抽象化程式庫,固然將程式人隔離在許多演算法及資料結構的細節之外,使得他們能輕易地運用於開發的系統中。但是,正因為接觸不到細節,也有可能使得他們忽略這些演算法的特性,尤其是效能。
即使已經有許多現成的演算法及資料結構,甚至是其他用途的程式庫,為什麼程式人還是必須修習像演算法及資料結構這樣的課程?
演算法基本是一門探討以電腦程式解決問題的學問,時間複雜度和空間複雜度是貫穿演算法這門學問的主要支柱。這兩種複雜度所代表的是什麼?正是解決問題在時間上及空間上所需付出的代價。我們學習演算法,除了去熟悉常見的演算問題分類,以及理解如何解決這些演算問題之外,更重要的是要學習解決問題的方法,以及培養成本意識。
即使許許多多的問題與解決它們的演算法,在教科書上都列舉了,但再多的例子,所有你在現實的開發生活中遇到的所有問題,都無法窮舉完。或許你可以憑藉著組合既有的演算法來解決問題,但你可能還需要發展新的演算法,才能夠因應。這便是我所謂學習解決問題的方法,雖然問題不一定出現在教科書中,你還是要有能力找出一個可接受的解決方法──即使不是最佳的方法。
不同方法所對應的演算特性,可能造成極大的效能差異
許多應用程式的開發者,其實並不太會遭遇到需要發展複雜或困難演算法的情況。但對演算法的基礎了解,可以、也應該培養我們對於運算成本的概念。現今程式庫高度發展的現況,似乎讓程式人們有了忽略運算成本的趨向。就以Java的標準程式庫當做例子,java.util.ArrayList這個容器類別,有個叫做indexOf()的函式,傳入某個物件,可以找到該物件在ArrayList中的位置索引值。這個動作的時間複雜度,其實是線性的,和ArrayList中所含有的元素總數成正比。可是,程式人可能不會意識到其成本。也許在撰寫程式的過程中,需要記錄不重複的元素,他們可能利用ArrayList來記錄,並且利用indexOf()來判斷重複與否。但是,indexOf()其實是一個代價不低的動作,同樣的需求,利用java.util.HashSet這個類別的contains()函式,它的時間複雜度是常數!也能達成相同的需求!這二者的目的相同,但是執行效率隨著規模的提昇,有著天壤之別。
這只是個簡單的例子,我想表達的是,程式設計朝向抽象化是正確的方向,但高度的抽象化,或多或少造成程式人無法理解對於許多運算的特性,尤其是它們的演算特性。就如同上例所表達的,從抽象上來看,兩種寫法在邏輯上都正確,也都能得到正確的結果,但因為有著不同的演算特性,使得它們最終的效能表現大大不同。
建立解決問題時所面臨的成本意識
新型演算法的設計問題,對大多數的應用程式開發者而言,雖然毋需憂心,但是對於常見的演算法,諸如排序、搜尋等,最好還是能具備一定的認識,在撰寫程式時,才能知道究竟有那些工具可供取用。而且,更重要的是,在撰寫程式碼時,隨時都應該對演算的成本保持警覺,尤其對於每一個高階的抽象操作,都應該確切地了解演算的成本及代價。高階抽象包裝有時就像糖衣,它包覆的究竟對你來說是不是毒藥,很難確定,必須小心。
倘若在撰寫程式時,僅考慮架構上的特性,而缺乏成本意識、忽略了演算特性,那麼還是很容易在無意中寫下效能低落的程式碼,相較之下,這才是真正的問題所在。無法設計出新的演算法,不是真正的問題所在!
「程式設計是一種多面向特質的活動」。單從任何一個面向,去檢視程式設計的特質,都難免偏頗與不足。但不論是演算觀點下或架構觀點下的程式設計,都捕捉了程式設計這項活動中的部分迷人之處。也只有從多方觀點齊下,才能讓自己的程式碼無論從什麼角度來檢視,都能呈現出美的一面。
作者簡介:
王建興
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。
相關連結
演算法、資料結構與程式設計的關係(1)程式設計的2大面向:演算和架構
熱門新聞
2026-01-16
2026-01-16
2026-01-19
2026-01-18
2026-01-19