The two most important days in your life are the day you were born and the day you find out why. -Mark Twain

#55 當兵,我的 IT 人生起點

這一篇文章講的是將近二十年前的人生故事 - 當兵,我的 IT 人生起點

當兵,這是一個身為台灣男人都避免不了的過程.除非身體上有不適合的,不然每個男人都得去當兵.對許多人來說,當兵都是很辛苦的,可能是在辛苦的步兵連或每天要顧著大砲的砲兵連,甚至是在特種部隊.不管是在那裡,每個人所經歷的辛苦都是不同的,每個人所留下最深的回憶絕對不是退伍那一刻的光榮或快樂,最深的回憶往往是那些被操的最辛苦的時候.如果你也當過兵,我想你也會同意我這樣說.然而,當兵對我來說卻是我的 IT 人生的起點.雖然也是辛苦,但卻不是身體上勞累,而是承受較多的心理壓力.故事是這樣開始的....

在大學剛畢業沒多久後,我收到區公所的通知要去抽籤,那一天只有兩個人需要參加抽籤,我是其中一個.當時我覺得奇怪,怎麼只有兩個人而己.到現場了之後,有位長官就拿著一個小布袋,聽的出來布袋裡有很多的籤,然後他拿起了一些海軍陸戰隊的籤丟到小布袋裡,同時提醒我和另一個仁兄說,這布袋大約有十多個海軍陸戰隊的籤,然後就祝我們好運了.另一個仁兄先抽,他抽起來後,其中一位長官說: "海軍陸戰隊!".我當時心想,天呀,才剛放進去就被抽起來,有這麼神準嗎? 接下來換我抽,手進去攪拌了一下然後拿出一個籤,"空軍".又是一個天阿,我沒想過我會抽到空軍.結果這時那位長官就說,怎麼前一個跟後一個差這麼多,我想這是一個緣份吧.接著過了一個月左右,我就坐上火車專車到新兵中心報到了.在新兵中心大約一個月之後就要分發部隊了.因為我大學的主科是機械系,所以就被安排到台灣某個空軍基地,也就是某一個機場.如果我的記憶沒錯的話,我那一梯大約有二十多人都被分發到同一個空軍基地.到了基地之後,我們就被安排到一個會議室裡等待基地裡不同單位的長官來挑選.不知你們有沒有過那種等在那邊被挑選的感覺,心情是上上下下,因為被挑走之後,未來將近二年的生活就確定了.我估計我應該會被修飛機相關的單位挑走,畢竟我大學主修是機械系,所以當時就沒有想太多.後來,一切發生的事情就只能用緣份來形容.那時進來了一位年輕少尉軍官,感覺上只是大我個二三歲而己,他進來後就說他是資訊單位的,要挑選會電腦的阿兵哥.我一聽就馬上報名參加他的面試.接著他對每個阿兵哥都做簡單的面試,會問我們對電腦懂多少,有什麼經驗,有沒有當過 BBS 的版主之類的.後來,輪到我時,我跟他說我是機械系畢業,但我會 C 語言,也會用太陽作業系統 (Sun Solaris),而且也會做 HTML 網頁,BBS 也非常熟,並且我中文打字很快.也許我是幸運的,在那一稊的同學中沒有人是資工系畢業的,於是那位年輕的少尉軍官就挑選我了.我就獨自一個人跟著他到他的單位去報到.

這個單位蠻特別的,人不多,但是裡面絕大部份都是職業軍人,而那位年輕的少尉軍官其實是一名資訊預官,他是某間國立大學資工所畢業的,專長是網路工程.整個單位裡只有我跟他是義務役來當兵的.後來我都稱呼這位預官叫崇哥.也許是工作環境與個性的關係,崇哥並不會以軍隊裡學長的姿態來壓榨我,反而幫助我很多.他跟我說之前我這個缺的阿兵哥在我來之前才退伍不久,而他跟我是念同一家大學畢業的但不同科系.想想,這一切應該都是緣份吧.

一開始我在單位上的工作很簡單,由於我是唯一的阿兵哥,其他人都是軍官,所以單位上所有粗重或下等的工作都是我做,例如所有的清潔工作,不管是掃地掃廁所倒垃圾等等.單位裡的老大是一位中校軍官,也是盯我盯的最緊的人,因為他非常注重乾淨.在這單位工作唯一的好處是這些軍官們到了下班時間後就會回家了,所以晚上也是我唯一可以比較輕鬆的時光.

過沒多久後,崇哥丟了一本 “網路通訊” 的書給我,要我把書念一念,因為接下來他要分配一些他的工作給我.他只剩一年就退伍,我猜想他是要我接他的工作吧.那時沒想太多,崇哥要我做什麼,我就做什麼.每天有空的時間就讀那本網路通訊,看了之後才明白基礎的網路原理,包括了網路的類型,區域網路廣域網路等等,還有不同的材料,如同軸電纜或光纖,然後也介紹許多設備,如 Hub, Switch, Router 之類的.最後還介紹 TCP/IP 協定,看完之後讓我對網路有基本的了解,也才知道電腦之間是如何溝通的,也才知道不同的環境下有那些網路連線方式.接下來的幾個月裡,崇哥也會帶我到機場裡不同的單位去拜碼頭,認識不同單位的長官,而其中更重要的是他帶我去看每個單位的網路是如何連接以及相關的設備在那裡.對我來說,這是個很有趣的學習,除了可以從課本上得到知識外,還可以親眼看到那些課本裡講的網路線材以及設備.我想這是開啟我 IT 人生的第一步,而崇哥就是幫助我走上第一步的貴人.

除了遍布整個機場的網路以外,在單位上還有一個相當重要的電腦機房,裡面有一些大型主機執行著一些空軍的電腦系統.漸漸地,我也慢慢被安排做一些日常的機房工作,例如換磁帶等等.我在機房裡看到太陽作業系統的電腦,心裡想這也許是崇哥會挑我進這單位的原因.日子就這樣過了幾個月,依然每天做打掃工作,每天睡覺前去倒垃圾,整理好辦公室,會客室以及長官的辦公官,沒做好的話,隔天早上又被會單位老大罵了.就在崇哥準備退伍前一二個月左右,這時總部來了一道公文,配合新一代戰機,整個空軍的所有電腦系統與設備會進行提昇.其實當時我根本還不知道那些東西是要做什麼的,我還記得當時單位上的軍官們都在恭喜我,我有很多工作要做了,顯然不是件涼差事了.

過沒多久,這項專案就開始了,每天有許多的工人到機場裡面來,在規定的路線上挖土埋管.這是一樣很硬的工程,我是個監工者,每天就跟著這群工人們在大太陽底下工作,每天寫著我的工作日誌.那段時間,我記得我曬到我的耳朵都脫皮了. 還記得有一天傍晚正好在機場的跑道尾端工作,當時滑行道上正好有三架 IDF 戰鬥機緩緩地滑行到主跑道,整齊的編隊,其中一架引擎點火,迅速地衝出去,過沒多久之後就飛上天空,接著另外兩架也依序地升空.當時的我剛好在跑道的尾端看到這一幕,那麼近距離地在戰鬥機後面看到戰鬥機點火並且迅速升空,這真的是一件很酷而且很有視覺震憾的事情.

埋管的工人們完成埋管工程之後,便換來了另外一批佈線的工人,從室外光纖到室內的網路線,我還是都一路跟著跑遍機楊的每個地方.最後換成一批比較高級的工人,我稱他們為工程師,他們架起網路設備,設定 Switch 和 Router,因此那時我也學會基本的 Cisco router 設定.當初崇哥在我一進來時丟給我那本網路通訊一書,在這個專案的過程中可說是發揮的淋離盡致.這個專案前後執行了數個月,在開始沒多久後崇哥就退伍去竹科上班,所以機場裡的網路工程就剩下我這個小兵一肩扛起來.現在回想起來,年輕就是年輕,才有這種勇氣和衝勁.雖然如此,整個單位還是只有我一個阿兵哥,所以我還是得負責單位上的清潔工作.有時在餐廳裡遇到同梯時,有人都表示他們很羨幕我可以在資訊單位工作,上班還可以吹冷氣等等.但我都跟他們說,我們來一年了,你們現在一定都比較輕鬆了,因為你們後面都有學弟一直進來,但我不是,單位上只有我一個阿兵哥,即便是我們來一年了,我還在掃廁所.聽了之後,我的同梯也覺得有好必有壞.但我知道,其實我自己是很幸運的,因為就算是資工系畢業的學生,我想他們網路工程的實務經驗絕對不會有我多,也拜了這次專案之賜真的讓我學了最真實的 IT 工作.這也就是我為什麼說我的 IT 人生起點就是在當兵的時間了.

最後,整個專案的硬體部份完成之後,總部的長官們來視察成果.單位的長官們忙著接待總部來的長官們,然後也派我跟著某個軍官做檢查.這位軍官就說他要進行抽查,到他指定的地點去,然後看所有的電腦與設備是否都正常運作.我就陪著這位長官從機場頭到機場尾,最後抽查完成後,這位長官就拍拍我的肩膀說: "幹的好,辛苦了".那時我心裡有一種爽快的感覺,心裡正盤算著我應該可以放假了吧! 但也有另一種想法湧上來,這網路線連一連,設備設定好,電腦設定好,不就都可以通嗎 ? 難道有人會抽查不過的嗎 ? 後來我請教了這位抽查的軍官,他跟我透露有些單位還真的有遇到網路不通的.沒通也是件蠻神奇的事.在整個專案的執行過程中其實有太多搞笑以及太多辛酸的事情了.搞笑的是常常跟著那些埋管工人們一起工作,聽他們說一堆五四三的事情,一起喝那個對我來說超級難喝的保力達 B, 他們都是做粗重工作的工人,跟他們相處起來卻是最輕鬆,最有人情味.辛酸的是在工地裡騎單車掉到坑裡,受傷不能靠腰,然後無線電的某一個開關保護套掉了卻被長官罵到臭頭.總之,這就是當兵,這就是人生.

故事還沒結束.過沒多久之後,總部又來一個公文,要求每個基地都要架設自己單位的網站.當時我被通知這個消息之後,深刻地覺得崇哥實在太有遠見了,挑選我進來真的是想過的.也許這一切只是巧合,或許是一個緣份.整個單位裡十來人只有我知道怎麼做 HTML 網頁,所以這項工作就由我來執行.當時我就用了一台 PC 裝了 Windows NT Server 做為網站伺服器.那時我對 Windows NT 懂的很少,所以當時有一段學習困難的時間,還好最後也搞定了,最後網站也上線.沒多久後,單位老大說能不能做出像留言版那種東西,可以讓路過網站的人留下一些建議等.我也只能答應老大的要求.因為以前沒有寫過這種東西,不太確定要怎麼寫,還好以前有一點 Perl 經驗,所以就用 Perl 試試看.但寫來寫去,覺得 Perl 超級麻煩,很多內建元件都沒有,許多功能都要自己寫,而且有些東西我也不會寫.於是,就開始找其他的方式.那時,剛好 Microsoft 推出了 ASP.我找了一些 ASP 的資料,試著了解它之後發現這東西實在是太酷太好用了.它內建了許多元件,所以幫助我可以容易地寫入和讀取檔案內容.於是最後決定用 ASP 來做.後來,組長發現我能寫了之後就開始到其他單位去包 “工程” 了,比如到氣象單位去幫他們做一個氣象網站,讓飛行員與其他長官可以很容易地透過瀏覽器就可以看到氣象資料.所以就在那一系列包工程的過程中,我把 ASP 做的很熟練了,而且還學了簡單的美工以及用 Javascript 做一些簡單的動畫效果.

也許最後你想要問的是,我是不是一個人做打掃工作做到退伍.答案是接近了,即便是我升到下士了,我還是得掃廁所.若你當過兵,你應該很少看過下士在掃廁所的吧.即便是我的工作感覺上越來越重要了,但我還是得每天掃廁所倒垃圾,一直到離退伍的二個月左右,單位裡才來了一位士官班剛畢業的年輕新人.因此,我退伍前是有人幫助我的.我這樣的當兵經驗應該是相當少見,不僅學到了網路工程也熟練了寫網頁程式,更利害的是掃了近二年的廁所和清潔工作.至少我這樣的際遇沒有發生在跟我同梯的朋友上,近兩年的當兵生涯,身體勞累不敢說有,心裡壓力比較大,因為你不會希望在重要的時刻來個主機當機或是網路斷線.現在回過頭來看,當兵這段時間真的是我的 IT 人生起點,也是把我從機械系的畢業生變成是一個 IT 人的起點.

Share:

#54 資料庫的 Transaction (交易) - ACID 基本介紹

在關聯式資料庫 (Relational Database) 裡,Transaction 是一個極為重要的特性,或許也可以稱為功能.若我印象沒記錯,Transaction 在台灣的書藉裡普遍翻譯成 "交易".雖然覺得用 "交易" 來表示蠻奇怪的,但也只能將就這情況,畢竟這翻譯詞已存在很久了.基本上而言,一個 Transaction 是指用戶端傳送給資料庫引擎所要執行的動作.這些動作通常是以 SQL 語法組成,然後再由資料庫引擎來解析語法,轉成各式各樣的動作來執行.比如,用戶端傳來了一個 Update Table1 set Column1='some word',這個語法是告訴資料庫引擎去尋找 Table1 的表格,然後將這個表格裡 Column1 欄位的內容改成 'some word'.這個語法本身就是一個 Transaction,其本身的特性需要有足夠的單獨性,一致性,持續性以及不被干擾的特質,也就是市面上書藉裡常提到的 ACID.這些特性是在 1980 年代一位著名的學者 Jim Gray 所提出,後來再經由其他學者加以擴展而成現在所看到的 ACID.

  • Atomicity: 這指的是單獨性.比如,一個 Transaction 裡有一個 Update command,一個 Delete command.如果 Update command 成功了而 Delete command 失敗了,則這個 Transaction 便是失敗的,所以 Update command 必須回復修改過的資料.
  • Consistency: 一致性代表的是在 Transaction 執行前後,資料皆處於合乎所有規則的狀態.例如,某個欄位具有 foreign key 的關係,其資料的內容不是能隨意產生的,必乎符合 foreign key 的關係,所以 transaction 在執行後,這樣的關係必需持續下去.
  • Isolation: 這指的是不同的 Transaction 之間執行時不能被彼此干擾.假設有兩個 Transaction 在同一時間對相同的一百筆資料進行更新動作,而其中一個 Transaction 在更新到一半時產生錯誤,於是進行資料回復.然而,另一個 Transaction 是完成的.可想而知最後的資料狀態一定是無法預測,因為不清楚那些資料是失敗的 Transaction 做資料回復的,還是成功的 Transaction 所更新完成的.
  • Durability: 我將它稱之為資料的耐力.資料庫引擎必須要保證一旦 Transaction 完成後,不論發生任何事情,如系統當機或電力中斷等,運作後的資料都必須存在於儲存媒介中,也就是在硬碟裡.

一個商業化的關聯式資料庫都必須提供這些特性,因為一個強大的資料庫引擎需要服務很多的用戶端,因此這些特性不只要提供,而且得做的夠好才能夠在市面上生存.未來的文章裡將會介紹更多的主題來說明資料庫引擎是如何達成這些功能.

接下來,讓我們用一些符號來說明 Transaction.

一般來說,使用 T 來表示 Transaction,而RT(O) 代表 T 要對 O 進行讀取的動作,WT(O) 就是代表 T 要對 O 進行寫入的動作,其中 O 代表的是資料庫裡某個儲存單元,例如表格.

Transaction 的完成的結果只有兩種,一個是成功 (Commit),另一個是放棄 (Abort).Commit 代表的就是 Transaction 裡的每一個資料的讀取和寫入都是成功的,而 Abort 代表的是 Transaction 裡並不是有所有的資料讀取或寫入都是成功的.在符號上則使用 CommitT 來代表 T 是 Commit 結果,用 AbortT 來表示 T 是 Abort 結果.也許你查不同的書藉會有不同的表示方法,但這不會造成影響.介紹這些符號的目的是為了未來說明 Transaction 的動作時,可以用簡單的符號來代表一些事情.例如,


這代表了系統裡有兩個 Transaction, T1 和 T2,其中 T1 進行的動作是讀取A,寫入A,讀取B,寫入B,最後的結果是 Commit.表格裡的每一行代表同一時間上所做的動作.因此,這一個例子是不合法的,因為不能有兩個 Transaction 在同一時間對同一個物件做寫入的動作,這違反了 Isolation 的特性.

一個資料庫引擎的效能往往也由它對 ACID 特性的執行速度來決定.你可以發生問題都是發生在寫入的動作上.因為寫入代表刪除或更新,這將改變了資料庫內的資料狀態.如果你有一個資料庫只需要提供讀取而沒有寫入,那麼 ACID 特性對資料庫引擎而言就沒什麼意義了.
Share:

#53 Coding 面試- LeetCode #143 Reorder List

題目的網址 https://leetcode.com/problems/reorder-list/

這題跟一般的 List 考題一樣,都是把 List element 做順序上的改變.這題的順序改變也比較特別,因為是一前一後一前一後的順序.

一般來說,List 考這種順序排列的考題時,通常都不希望你用到額外的記憶體空間,也就是說所有的動作都要在原來的 List 空間上完成.記得當初在寫這題時,花了不少時間在想要怎麼排出新順序,後來才想到把 List 對折,這樣就變成第一個對到最後一個,第二個對到最後第二個,以此類推,中間的過程也要將成雙成對的配對中把前一對的尾色連到後一對的頭,這樣就完成了.考這種題目真的蠻討論的,因為要很快想到折一半再成雙成對的搭配起來還真不容易.然而,List 的題目就是這樣,只要想法正確了,寫程式就不是問題了.



以上的解決,Space complexity 是 O(1) ,而 Time complexity 是 O(n)


Share:

#52 物件導向程式設計的一個小技巧 - 切開 dependency

這次放長假回到台灣,到台中參加了一場 Study4TW 社群舉辦的活動.在活動中有個問題時間,讓現場的朋友可以提問問題,我則盡量回答我所知道的.其中有一個問題是 "台灣存在著多數不願意跟上時代變化傳統產業, 若不考慮重構, 開發人員該如何面對舊版本的軟體設計呢?" 這個問題的確不是那麼容易可以完整地回答,我當時的回答是跟大家說至少可以先從降低 dependency 的動作開始.所以,這篇文章就來說明我所謂的降低 dependency 是什麼 !

我們在寫物件導向程式時,一定會常常呼叫到其他人寫的程式,也就是說你的程式裡一定會建立一個別人程式的 instance. 例如,你的程式裡有一個 class 叫 DataWriterHelper ,裡面有一個 WriteSecret(),這方法裡面的內容其實是透過其他人寫的程式來達成.假設其他人寫的 class  叫 SecretHelper,而裡面有一個 Write().所以你的程式一開始看起來如下:

class DataWriterHelper {

    public void WriteSecret(string data) {
        SecretHelper _helper = new SecretHelper();
        _helper.Write(data);
    }
}

這樣子就可以很清楚的看到你的程式 DataWriterHelper 和別人的程式 SecretHelper 有一個關係了,換句話說,你的 DataWriterHelper 依賴了 SecretHelper,因為若 SecretHelper 不存在的話,你的 WriteSecret() 便發揮不了功能.

但這樣寫,有什麼不對嗎 ? 好像沒什麼不對,只是失去了一些彈性.如果 SecretHelper 改了一個版本,把 Write() 改成 WriteData(),那麼你的程式也就必須跟著變動.另外,當你要測試 WriteSecret() 時,你會發現你非得把 SecretHelper 的元件也一起加入到測試專案才行,因為你的程式依賴著 SecretHelper.如果你將它 Mock,也是一種可行的方法,只怕真實情況不是一個 Mock 就能滿足你的需要.

接下來,說明什麼叫降低 dependency. 降低的方法可以用一個神奇的東西 - Interface. 首先,先定義好 Interface 的內容.

interface IWriteSecret {
    void WriteSecret(string data);
}

這個 Interface 定義好之後,理論上就應該不會輕易改變. 接著讓你所依賴的程式去實做這一個 Interface.所以 SecretHelper 就變成如下:

clas SecretHelper : IWrtieSecret {

    public void Write(string data) {
        // code for writing secret data
          .... 
    }

    public void WriteSecret(string data) {
        Write(data);
    }
}

Interface 所定義的 WriteSecret() 去呼叫 Write().接著,DataWriterHelper 就會改成如下:

class DataWriterHelper {

    private IWriteSecret  _secretWriter;
    public DataWriterHelper(IWriteSecret  secretWriter) {
        _secretWriter = secretWriter;
    }

    public void WriteSecret(string data) {
        _secretWriter.WriteSecret(data);
    }
}

經過這樣的修改,DataWriterHelper 便不再依賴 SecretHelper 了,而是變成依賴 IWriteSecret.因此,這樣做就把 class 和 class 之間的 dependency 切開,變成 class 依賴新的 interface. 這樣改變的想法來源是根據物件導向設計的 SOLID 原則.上面程式碼 IWirteSecret 的實作物件是透過 constructor injection 的方式傳來的,換句話說,DataWriterHelper 本身不參與 IWriterSecret 實作物件的建立過程,而是由外部的呼叫物件來決定要傳入那一個 IWriterSecret 的物件.

這樣的改變,將使得 DataWriterHelper 的測試力變得強一點,因為你可以傳入測試用的 IWrtieSecret 物件用在 DataWrtierHelper 的 unit test 或 integration test 的情境中.

Share:

#51 Coding 面試 - LeetCode #9 Palindrome Number

原文網址 https://leetcode.com/problems/palindrome-number/

這一題跟之前講的有一題 palindrome string 有點異巧同工之妙,但不同的是這題指的是數字,而不是字串.如果你把數字當成字母來看待的話,則也能用之前相同的解法.這在演算法來說算是一種 reduction 的技巧.然後,數字和字串的特性畢竟不同,所以若把數字轉成字串來處理是可以的,但就浪費了一些數字的特質.

其實要把數字反轉,最簡單的方法就是用十來取餘數,也就是說 2 取十的餘數是 2 , 12 取十的餘數是 2 , 102 數十的餘數是 2.因此,對十取餘數之後,我們就能得到個位數的值,然後再對商再取一次餘數,用這個方法一直做到商變成 0,這樣子就可以把原來的數字一個一個取了出來.

剩下的就是將取出來的數字排好,第一個取出來的放最前面,最後取出來的放最後面,這裡不需要 "儲存" 的觀念來做,其實只要在每取出一次餘數時,把之前取出來的數字乘十就行了,這樣就等於先取出來的會被 "進位",然後再加上最新取出來的餘數.

最後,還要記得處理正負號.這只需要在一開始的時候判斷是正號或負號,若是負號的話,就一律不符合答案,若是正號的話,就檢查反轉前和反轉後的數字是不是一樣的,這樣就完成了.

參考的程式碼如下:



Share:

#50 Coding 面試 - LeetCode #349 Intersection of Two Arrays

原文網址 https://leetcode.com/problems/intersection-of-two-arrays

這一題在原文網址上列為簡易型的題目,看了題目之後,只要你熟悉一些基本的資料結構,應該可以很快想出不錯的解法.題目輸入是兩個 integer array,然後輸出一個 array,這個 array 包含的元素要出現在兩個輸入的 array 裡.

若用最直覺的想法,你可以做一個 for loop 在第一個輸入 array 上,把每一個元素和第二個輸入 array 的元素相比較.以這樣的做法來看,時間複雜度將會是 O(n2).以這個題目來說,這樣的時間複雜度並不理想.所以得再想想一個比較好的處理方式.

我在寫這題時一開始就想到利用 hash table 來運作,因為 hash table 有極佳的 O(1) 的動作速度.所以一開始把第一個輸入 array 所有的元素透過一個 for loop 加入到 hash table.然後再將第二個輸入 array 利用另一個 for loop 來查看是否有同樣的數字已經存在於 hash table 之中.如果有的話,它就是我們要尋找的對象,但把它加入到輸出的結果裡,這裡是用一個 List 來代表.最後再將整個 List 轉換成 array 輸出,這樣的轉換只需要 O(n). 因此,整個時間複雜度將是 O(n+n+n).有二個 for loop 和最後的轉換,所以一共是三個 n.程式碼如下:



最後我還發現了 LeetCode 提供了與其他同樣語言解法的比較.上述的解法打敗使用同樣語言 75% 的解法.我想這是以他們電腦執行速度來計算的.由此可見,上述的程式碼在最佳化上還有很大的空間可以改進.

Share:

#49 - 物件導向的最基礎 - Access Modifier

若我們用 Java 語言來做為說明,則 access modifier 有三種,分別是 public, protected, private. 他們分別控制了那些對象能使用他們所定義的東西.

https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html 裡面有一個表格呈現出了整個控制力的對照圖.例如,private method 只能被同一個 class 裡面的 method 所使用,只要超過同一個 class 的範圍是無法使用的.public method 剛好相反,它可以被任何的 class 看到來使用,甚至也可以被其他元件看到來使用.因此,當你將 class, property, method 設定為 public 時,你就要非常地小心.這個稍後說明.最後一個是 protected,它能被同一個 class 裡的東西來使用,而也能被該 class 的 child class 來使用.

Private 的控制力道最嚴格,因為除了在同一個 class 的 method, property 以外,其他人都無法存取.因此,在最初的設計上時比較不會對 private method, class, property 等做太多的說明,甚至會跳過他們.Public 就完全相反了.誠如前面所說,一旦你將 class 設定為 public 時,那表示不止你的程式可以看到來使用,其他人的程式也可以看到來使用,這時候你就要非常小心,因為你得考慮到這個 class 一旦被使用了之後,它所執行的程式碼需要具有什麼意義.同時,也要注意在寫程式時是不是有做一些檢查.

來看一個很笨的例子,假設你今天要設計一個傳送訊息通知的 class,而訊息傳送的功能是透過其他的元件來執行,你需要設計的只是一個簡單的 wrapper ,將該元件包起來,然後只要露出一些你需要提供的功能即可.所以程式碼可能是這樣

public class MessageSender 
{
 private Some.Other.Sender _component;
 
 public void Start()
 {
  _component = new Some.Other.Sender();
  _component.OpenConnection();
 }
 
 public void Send(string message)
 {
  _component.Send(message);
 }
 
 public void End()
 {
  _component.CloseConnection();
 }
}

從上面的程式碼你可以看到 MessageSender class 的 access modifier 是 public,這表示全世界都可以看到它,因此任何人都可以使用它.在這個 class 裡面定義了三個 methods,分別是 Start(), Send(), End().會這樣設計可能因為你想要讓使用它的人比較清楚知道該如何使用,因為顧名思義看起來就會覺得一開始的時候就要使用 Start(),需要傳送資料時就用 Send(),而最後在結束時就使用 End().這樣的想法也沒什麼不對,只是漏掉了一點,那就是 public method 是大家都看的到.別人可以看的到不代表他就一定得用它.也就是說,當我 new MessageSender 時,有規定我一定要先呼叫 Start() 嗎 ? 似乎沒有.有規定我最後結束時一定要呼叫 End() 嗎 ? 似乎也沒有.如果我直接呼叫 Send 來傳送字串時,那會發生什麼事呢 ? 答案很明顯,會發生 null object 的現象.

用以上這個笨笨的例子來提醒大家,在設計 public class, public property, public method 時一定要注意到這些 public 的東西被使用時是沒有順序可言的,所以千萬不能自作主張去以為所有人會知道該如何使用,甚至你寫了說明文件該如何使用也不能這樣寫程式.因此,設計 public 時一定要非常地小心,要想到當 public class, public property, public method 是第一個被存取的對象時會發生什麼事.這樣你就會知道上面的程式碼是行不通的,而且相信你也能將上述的程式碼改成一個比較好的程式碼.

Share:

#48 老鼠走迷宮 (mouse maze)

在大學的資料結構的課程裡,大約在教過 Stack, Queue 和 List 這些基本的資料結構之後會有一個老鼠走迷宮的作業.這份作業說難不難,說簡單也沒那麼簡單,對一個正在學習資料結構的學生來說,花個一兩個晚上來寫這份作業是很正常的.如果寫程式正是你的工作,而且你以前沒念過資料結構的話,不妨試著做做看這份作業.

老鼠走迷宮的內容是,要隨機產生一個矩陣,矩陣的大小最好由使用者輸入設定的.然後在矩陣裡的每個位置也會隨機產生三種不同的地形,例如平地,河流與高山.你可以再設計其他不同的地形,老鼠在每一個地形上走動時會花費不同的體力,例如平地是 1 ,河流是 2,而老鼠是無法穿越高山.老鼠的出發點是矩陣的左上角,也就是 (0,0) 的位置, 然後終點在右下角,也就是 (n,m) 的位置,n,m 代表的是矩陣的兩個邊長.

你所要寫的程式是讓使用者輸入矩陣的大小,然後為矩陣的每一個格子隨機產生地形,接著老鼠從出發點開始移動,看看老鼠是否可以走到終點.因為地形中會有高山,所以老鼠不一定都會有一條路可以走到終點.然而,老鼠能走的路也可能不只一條能到終點,你可以試著找出一條就行了.

整個題目的重點在於兩個地方.第一,移動的方向要定出一套規則,第二,要能夠記錄走過的路跡.移動方向的規則可以用最簡單的方式來實行,除了在邊邊與角落以外的格子,每個格子都可以有八種移動方向,上,下,左,右,左上,左下,右上,右下.每到達一個格子時,就可以依照固定的順序來移動到下一個格子,如果在下一個格子都沒有路走或是每個方向都試過了,那就回到上一個格子.因此,路跡的記錄是很重要的,因為需要回到上一個格子.

以下我提供十多年前寫的版本



如果你順利寫完之後,還有一個延伸題目給你繼續挑戰,那就是如果有多條可以走的路跡時,列出最省力的路跡.這延伸題目就不是那麼簡單了,未來再來寫篇文章來討論.

Share:

#47 資料庫基礎 - 以 Hash 為基礎的 Index

前面的文章介紹了資料庫的 Index 是以 tree 為基礎的方式,一般來說大部份資料庫產品都用 B-tree 來建立 Index.然而,除了用 tree 以外,還可以用 Hash 的方式來建立.


可以用上圖來說明 hash index 的運作方式.首先,輸入值會先經過 hash function 的運算後而得到一個 hashed value,這個運作如以前的文章講的是一個 constant time 的運算.輸入值就是使用者要過濾的條件,也就是 SQL statement 中 where 的內容的欄位值.例如,學生證號碼,病歷編號等等.然而,這種輸入值若不是 key 的話,便會發生多筆同樣資料會得到一樣的 hashed value,例如我們是用學生的姓來做 hash index 的話,則同樣的姓就會得到一樣的 hashed value.因此,一個 hashed value 可能會對應到一個或多個在 Data page 上的資料.,以上圖來看,每個 hashed index 的資料可以對應到一筆或多筆在 data page 上的資料,這樣的做法其實並不太好,因為每個 hashed index 的資料大小是無法確定的,就因為我們無法預測每個 hashed index 會對應到多少筆 data page 的資料.因此,可以再改一下如下圖,


我們規定每個 hashed index 的資料只能對應到一筆 data page 上的資料,這樣就可以固定住每個 hashed index 的資料大小.因此,當輸入值經由 hash function 運算之後到達第一個 hashed index 上的位置,便可以得到一筆 data page 的位置而取得資料,然後還要往下一個 hashed index 移動,如果 hashed value 也是一樣的話,就要再取得它所對應在 data page 上的資料,一直到 hashed index 上的值不同了或是沒有下一個了.

以 hashed index 的運作方式來看,其效率比以 tree 為基礎的 index 還要來的好,那為什麼一般的資料庫產品不採用它呢 ? 我想主要的原因就在於 hashed index 無法支援有範圍的尋找.例如,如果用學生證編號做 index,當你下 SQL statement where ID > 10 and ID < 100 時,這在 tree-based index 上可以有很好的尋找效果,只要找到 10 之後就再延著 tree node 一直往下或往旁邊找到 100 就可以停住了.但 hashed index 來說並不是這樣,因為在 hashed index 裡, 10 旁邊的很可能就是 100,要看 hash function 如何設計.所以在 hashed index 上我們找不到一直可以連續讀取資料的方式.如果我們把編號從 10 開始代入一直到 100,這聽起來好像可行,但如果 index 的資料不是數字而是文字,難道要把筆畫漸大的字都帶進去運算嗎 ? 顯然不可能.

雖然 hashed index 不適合用在有範圍的尋找,所以就不適合被一些商業資料庫產品所採用,但還是透過這篇文章介紹 hashed index 讓讀者們可以了解 index 的做法不見得只有 tree 的方式,不同的方式有好有壞,有長處也有短處.



Share:

#46 Coding 面試 - LeetCode #62 Unique Paths

原文網址 https://leetcode.com/problems/unique-paths/

這一題主要是考 dynamic programming 的思考,但老實說,這題過於直覺,所以寫完答案時還沒馬上觀察到這是 dynamic programming 的解法


參考的解法如下:



從 First row 上的可行走法都是1, 因為只有可能從左邊走過來的方式.從 second row 開始,就有可能是從上面或從左邊來的走法,因此才是相加的,也就是上述解法中 r[i,j] = r[i-1,j] + r[i,j-1];

我相信如果你的主考官想要考你 dynamic programming 的話,應該不會用這一題來考你.若是要考簡單的,應該會是用 Fibonacci number 來考你,因為最直覺的 Fibonacci number 的寫法並不是用 dynamic programming 的寫法,而是用 recursive 的寫法.以時間複雜度來看,dynamic programming 的寫法是比較快的,而以空間複雜度來看,recursive 寫法是比較優的.

Share: