1. 關渡騎單車

    這次來寫點不一樣的,寫點休閒的吧。

     

    上個月跟家裡大人跟兩個小孩,去了趟關渡騎腳踏車,騎完覺的那邊還不錯 :D,不過因為小孩狀況多,最後沒能騎到八里天就黑了,租來的腳踏車又沒燈,只好半路就折回來了。回家翻了一下地圖,發現只剩 1/3 不到的路程啊 @_@... 於是這次趁著大人帶著小孩回娘家,碰到難得的好天氣,就自己一個人帶著裝備出發去了 :D

    計劃要騎的路線很簡單,一點都不困難... 就是從捷運 [關渡站],往關山公園,沿著淡水河岸的腳踏車道,騎過關渡大橋,到八里老街,再一路騎到十三行博物館。 不過計劃總是跟實際執行時不一樣 =_= ...  直接來寫流水帳吧... 照我騎的順序看下去...

    這篇不是什麼專業的介紹,老實說我也是第一次騎而已,沒做什麼功課,想來參考的可能會失望吧 :D 只是單純的記下來自己留個紀錄而以。有興趣的請繼續往下看 :D

     

     

    1. 捷運關渡站(15:09) ~ 八里渡船碼頭 (16:05), 共 8.4 KM

    image 

    GOOGLE MAPS 還蠻好用的,地圖跟路線都標的好好的 :D 上面的路線其實是車子走的,跟我真正騎的腳踏車道有點不同... 不過差不多啦,我就借用它的地圖標示一下。上面的每個點 (綠色的英文字母) 就是底下照片標的 ABC,各位可以對照著看。

    第一段的路限很簡單,就是到了關渡捷運站後,租了腳踏車就上路了。從關渡捷運站出發,一路騎到八里渡船口而以.. 太陽還不小,但是天空雲也很多,還頗耽心會不會下雨... 因為這次出發帶了一堆配備 (腳架,相機,閃光燈,耳機...),就是沒帶傘 ...

     

    A. 15:09,捷運關渡站 ( 0.0 km )
    IMG_1086 (Canon PowerShot G9) (1024x768)

    搭了半個多小時的捷運,到了關渡站,拍個照紀念一下。其實這裡沒什麼好照的,只是我也懶的拿紙筆出來記時間了... 哈哈,直接拍個照,回家可以看到照片,也看的到時間...  突然覺的應該買個有 GPS 的照相手機,這樣拍出來連座標都有了 [Y]

    另外一個敗家目標是 MP3 隨身聽... 平常沒在聽,不過自己一個人邊騎車邊聽還真愜意,聽了一下午的陳綺貞... 結果聽到連手機都沒電了 =_=,看來用手機聽 MP3 不是長久之計,有空來物色一台...

    好,列入敗家清單內... 出了捷運站就有租腳踏車的地方。雖然看其它網站,都是說要步行 15 分鐘到關渡宮那邊再租車,不過... 實在是懶的多走這 15 分鐘的路程了,在門口的租車店就租下去,到晚上八點,一次一百...

     

     

     

    B. 15:16,大度路 (0.3km)
    IMG_1090 (Canon PowerShot G9) (768x1024)

    騎出巷子口,穿過橋下就到大度路口了,單純覺的這個景跟這個建築,好像在國外的感覺,就順手拍了一張...

     

     

     

    C. 15:26,關渡棧橋碼頭 (1.1km)
    IMG_1091 (Canon PowerShot G9) (1024x768) IMG_1092 (Canon PowerShot G9) (1024x768)

    騎著腳踏車還蠻快的,穿過巷子,經過關渡醫院,到了關度宮,就到 [關渡棧橋碼頭] 了... 十分鐘不到的車程而已。在這邊看到天氣有點陰陰的,遠方已經看的到等等要過去的關渡大橋...

     

     

    D. 15:32,關渡大橋上 (2.5km)
    IMG_1095 (Canon PowerShot G9) (1024x768)

    騎起來好像真的沒什麼挑戰 @_@,跟上次載著小孩完全不一樣,一方面親子車本來就不好騎,另一方面自己騎也比較自在一點,不到十分鐘已經在關渡大橋上了... 上橋是吃力了一點,人也多,只好下車用牽的... 這張照片就是在橋邊的步道拍的... 再往前左轉就在橋上了..

     

     

    E. 15:56,已經到對岸了 (5.5km)
    IMG_1100 (Canon PowerShot G9) (1024x768) IMG_1104 (Canon PowerShot G9) (1024x768)
    過到對岸後,沿著河畔騎了廿分鐘左右,這個點不知道叫啥名字,會停下來只是喝帶來的冰釀綠茶,也剛好看到有個左右相反的 [八里左岸] 石碑,停下來照個相記錄一下... 不過有對姊妹 (應該是姊妹吧) 抱著狗一直在那邊照... 哈哈,心裡噓了半天還是不肯走... 還一直猛拍,一直拍,一直拍.... =_=,我又不想跟她們慢慢耗...,就讓妳們倆跟愛犬免費登上我的部落格一次吧 =_= ...

     

    騎到這邊已經快到八里渡船頭了,沒有想像中的久嘛... 上次竟然騎不到 @_@

     

     

    F. 16:05,八里渡船頭 (8.4km)
    IMG_1106 (Canon PowerShot G9) (1024x768) IMG_1107 (Canon PowerShot G9) (1024x768)

    再往前騎不到十分鐘,就... 到.. 了 @_@,從租到車到騎到八里,也才五十分鐘左右,扣掉停下來喝個水,照個相的時間... Hmm… 果然是很休閒的路線...

    這裡一樣一堆人,趕不走 (我也沒那個膽.. 哈哈),就照進去了 :D

    這邊是八里渡船頭,可以直接搭渡輪到淡水碼頭... 就是有顆大蓉樹那邊,還有一堆阿給小吃店的地方。

     

     

     

    IMG_1109 (Canon PowerShot G9) (1024x768) IMG_1112 (Canon PowerShot G9) (1024x768)

    照完相想繼續往下騎,一路直攻十三行博物館... 不過… My God! 那來這麼多人... 我最討厭人擠人了 :@,看到一堆人就很沒力... 何況牽著車跟本動彈不得... 就放棄繼續往前走的念頭了。

    翻了翻地圖,另一邊有生態公園,看看時間跟騎的速度,應該還很夠吧 :D,就改變計劃往回走了...

     

     

     

     

     

     

    2. 八里渡船碼頭 (16:05) ~ 疏洪生態公園 (17:20),共 8.0 KM

    image

    在決定不想擠過人群,往十三行博物館前進之後,看了看地圖,就決定往南騎到生態公園看看,這種地方人應該不會那麼多吧 :D

    這一趟的路程也差不多一樣八公里,沒騎過,反正自己一個人就騎看看 :D

     

     

     

     

    B. 16:26,八里左岸石碑 (11.3km)
    IMG_1114 (Canon PowerShot G9) (768x1024) IMG_1119 (Canon PowerShot G9) (1024x768)

    又回來這裡了! 不過,這次那對姊妹跟狗已經不在了 :D,沒人在拍照...  終於輪到我拍了 :D

    石碑旁邊原來還有說明啊... 剛才都沒看到。本來想拿起相機自拍,不過技術不好,都拍不到後面,腳架也懶的扛出來... 就算了...,繼續往下一站!

     

     

     

    C. 16:38 岸邊某個休息區 (12.3km)
    IMG_1122 (Canon PowerShot G9) (1024x768)

    回程的途中,有一小片沙灘,正好看的到關渡大橋,就照一下紀錄時間...

     

     

     

     

     

    D. 16:50,虹橋廣場 (12.9km)
    IMG_1124 (Canon PowerShot G9) (1024x768) IMG_1128 (Canon PowerShot G9) (1024x768) image

    已經騎回來到關渡大橋橋下了,這邊剛好是從橋上下來的自行車道的地方,原來這 SQUARE 叫 "虹橋廣場" 啊... 拍個照。

    不過這次沒有要過橋了,繼續沿著左岸往南騎...

     

     

    E. 17:06 獅子頭長橋 (14.5km)
    IMG_1133 (Canon PowerShot G9) (1024x768) IMG_1136 (Canon PowerShot G9) (768x1024) image

    這裡是個半園型的橋... 不大會講,我剛好也沒照 @_@,抓張 GOOGLE MAP 的衛星照來看看... 那個像量角器的東西,就是獅子頭長橋啦...

    這邊我只停下來拍照而已,沒多休息就往下一站去了...

     

     

     

    17:12 觀音坑溪橋 (15.2km)
    IMG_1140 (Canon PowerShot G9) (1024x768) IMG_1141 (Canon PowerShot G9) (768x1024) image

    還蠻特別的一座橋,造型不錯就照了一下,其實橋很小一座... 就貼個照片跟 GOOGLE 衛星空照圖意思一下..

     

     

     

    17:20 疏洪生態公園 (16.0km)
    IMG_1146 (Canon PowerShot G9) (1024x768) image

    不知不覺就騎到目的的了,果然沒很多人,一邊是河一邊是草地,在這邊坐著休息還蠻舒服的... 陪我騎了半天的腳踏車,終於有機會入鏡頭了 :D,叫不出來的牌子,不過還蠻好騎的 (Y),一次一百塊,我是覺的不貴啦,自己買一台少說四五千吧? 光是帶車子搭捷運就不只這一百塊了 =_=,還是當場用租的方便...

     

     

     

     

     

     

     

    3. 疏洪生態公園 (17:20) ~ 捷運關渡站 (19:00),共 6.6 KM

    image

    看看時間也差不多了,還得趕回去接大人跟少爺公主回家 =_=,在疏洪生態公園休息一下就回頭了。一路上的風景跟景點都介紹過,就不多提了。在回程的路上,才發現 MP3 隨身聽的重要啊 @_@,一整天聽下來,也沒幾個小時 (3HR左右),我的手機在接到大人打來的電話之後,就... 沒... 電... 了,嘖嘖,windows mobile 的手機聽個 MP3 就這麼耗電...

    不過這樣一路聽聽 MP3 還真是過癮,就是這樣我才想去買台來用... 有沒有推薦的? iPod 就不用推了,我沒這麼時尚 @_@...

     

     

    17:42 關渡大橋 (八里 --> 淡水,19.0 km)
    IMG_1156 (Canon PowerShot G9) (1024x768) IMG_1160 (Canon PowerShot G9) (1024x768) image

    又回到關渡大橋了,這次是從左岸南方的步道上橋,第一張照片是還沒過橋前照的,第二張照片則是同一個地點,拍上來的地方,就是從畫面中間一路往右邊爬上來... 第三張是 GOOGLE MAPS 的空照圖,順手放上來...

    這次因為時間的關係,不能待太晚,可惜沒等到晚一點天黑,沒機會拍到關渡大橋的夜景... @_@,看來腳架是白帶了...

     

     

    18:14 關渡宮前的小吃 (21.5km)
    IMG_1190 (Canon PowerShot G9) (1024x768) IMG_1196 (Canon PowerShot G9) image

    繼續往回騎,騎到關渡宮前面的小市集吃東西... 其實當地我也不知道有什麼特別的小吃,就點了平常愛吃的就好... 這邊的鹹鴨蛋好像還蠻出名的,上回大人有買一些,不過這次就沒買了。另外離這裡不遠的淡水很有名的鐵蛋,這邊也有... 不過 $$ 幾乎便宜了一半 (9顆50),這個吃起來比鹹鴨蛋方便 (哈哈,不用剝殼),常常買了就當零嘴吃.. =_=

    關渡宮就是 GOOGLE 衛星照中間的橘色建築,隔著馬路對面 (橘色屋頂),一個正方型的建築就是個小吃攤集中的場地... 無奈當天沒啥胃口,吃了一盤蚵仔煎 (五十元) ... 一份花生糖冰淇淋 (卅五元) ... 一顆鐵蛋 (帶了一包回家,九顆 50 元) 就... 飽了 =_=  不然還有其它的東西想吃一吃...

     

     

    18:24 一堆怪名字的租車店
     IMG_1191 (Canon PowerShot G9) (768x1024)IMG_1192 (Canon PowerShot G9) (1024x768)IMG_1197 (Canon PowerShot G9) (1024x768)

    除了蚵仔煎是坐在裡面的位子吃之外,其它我就在路邊的椅子買了就坐下來吃,路邊不是小吃就是租車店,發現他們店名還真有創意... 哈哈,害我邊吃邊笑..

    第一家叫 "租八借",虧老闆想的出來...

    第二家叫 "租羅記",老闆八成姓羅吧... =_=

    這邊租一次只要 80,不過搭捷運的話,大概來回得多走個卅分鐘吧,算了,我是懶人,就讓另外的店家多賺廿塊錢吧...

    第三家在旁邊一點,喵喵休閒車,人氣就差多了... 哈哈,招牌還在,不過店已經收起來了,底下是掛著店面出租的紅紙... 果然名字好不好記還是有差.. @_@

     

    最後 19:00 整,回到捷運站前的租車店 (22.6km)

    這裡就沒再拍照了 @_@    沒想到這樣很輕鬆的騎下來,也不知不覺騎了廿幾公里... 夠高速公路從台北開到桃園了吧? 這樣看起來好像還蠻遠的.. 哈哈。自己一個人騎,聽聽 MP3 就不無聊了,騎了多遠也沒什麼感覺,很適合來放鬆的。騎單車還真不錯,有風景可以看不會無聊 (平常騎機車或開車,都不能看風景 =_=),也 "好像" 有運動到,聽起來比較健康一點... :D

     

    下次再看看天氣怎樣,試試別條路線... 看了看台北縣市自行車道的介紹 (這裡有地圖PDF檔下載),其它路線有往淡水 (不過淡水去過幾次,都像八里一樣人擠人 @_@),也有往關渡自然公園看水鳥的路線 (這路途比較短,不用一個小時就到了吧)....,另外還有往三重方向,可以繞一整圈三重/蘆洲... 還會經過三和夜市... 不知道有沒有好吃的小吃? 還有不知道會不會經過很紅的爆米花店? 哈哈,順便買個兩桶回來 :D

     

    雖然自己騎蠻自在的,不過有人要跟團也接受報名啦 :D  看看下次有沒有機會拼完三重蘆洲這條自行車道...

    2009/05/05 有的沒的 當年勇

  2. 個人檔案 + 版本控制...

    自從過年時換了 SERVER 的作業系統,加上過年前 NOTEBOOK 掛掉換 X40 + SSD 之後,這幾個月都陷在東換換西調調的狀態中 @_@, 好在換了 2008 之後,有 Hyper-V 的幫忙,問題簡化不少...。不過今天要講的倒是很不起眼的小東西: SVN (Subversion)

    SVN 這種版本控制系統,通常是用來作程式碼的版本管理。也對啦,除了軟體開發之外,其它場合好像也不大需要這麼複雜的版本機制。不過這類系統弄多了,平常在非軟體開發的場合,也發現其實很多時後都有檔案版本問題要處理。像是平常的文件 (WORD),簡報 (PPT) 等等,都會作好一份通用的,碰到 A 客戶就改一改拿來用,B 客戶再改一改... 這不就是 brench / merge 之類的問題嘛? 所以我一直在找這樣的 solution,看看有沒有適合一般使用的。不過到現在,也換了好幾種作法,歷年來試過的作法有好幾種:

    1. VSS (Microsoft Visual SourceSafe 5.0)

      這個有用過的人,看版本號碼就知道有多古老了… 不過真正在用是 6.0 版開始。因為工作上會用的到,就順便拿來用了。它的好處是很簡單,搞懂它的邏輯就很容易上手。架設也簡單,完全是 File Based, 不需要架設專用的 Server。不過這也是後來換掉它的原因之一。

      它的使用方式,是以嚴格的控制為主要邏輯。什麼意思? 意思是你不能隨意更改檔案,要開使改檔案之前,要先 check-out 才能開始改。這樣的邏輯就是要避免未來一連串的版本衝突 (conflict) 及合併 (merge) 帶來的問題。 以軟體開發的角度來看,這樣的作法還不錯,整個團隊的開發是值得這樣作的。不過拿來管理個人檔案的話,就太過頭了。個人檔案不大會發生 LOCK 的問題,就是我改你也改,最後存檔總會有一個人的資料被蓋掉... 不過,如果我是大老闆,有十幾個秘書在幫我打雜的話就難說了 [H]

    2. VSS (Windows Volume Shadow Copy Service)

      Visual Source Safe 用了之後,發現障礙多於它帶來的優點 (以處理一般文件而言)。主要的缺點是,VSS 透過網路 / Internet / VPN 使用的速度實在是龜到可以,雖然後來 Microsoft 推出了 LAN Boost Service (還是很慢),也另外推出了 HTTP / Web Service 的存取方式 (只能透過 Visual Studio) 速度也不快。另一個缺點是一定要先開 VSS Explorer / Visual Studio, 我不過只是想開個 WORD 檔啊...

      所以後來換了另一個角度找 solution, 就試用了 windows 2003 內建的 VSS (Volume Shadow Copy Service), 替代版本控制用的軟體。它是做在 File System 層次上的機制,用了 Copy On Write (COW.. 這是縮寫,不是在罵人...) 的方式,做版本的差異控制。因此只要把檔案放在開啟 VSS 的磁碟機,完全不用更改任何使用習慣..。

      但是太自動的東西還是不適用。這種作法主要的問題在於版本太不精確了。VSS 仰賴定期作快照 (snapshot) 來作版本的管理。定期做的快照,留下來的版本很可能是無意義的,你也無法針對特定檔案的特定版本作註記 or 回複... 另外自動的快照也無法選則那些檔案要進版本,那些要退出。總之一切全自動,沒有什麼好選的。很簡單,但是功能也很有限。

      不過即使如此,一般情況下也夠用了,操作也夠簡單,當作第二種保護機制也不錯。這個 solution 我也用了好一陣子...。

    3. TFS (Team Foundation Server)

      老實說,連一般小型軟體開發,用到 TFS 都太肥了一點,自己的檔案管理用到這個真是太離譜了... 哈哈,因此這個 solution 只是閃過念頭而已,跟本沒實際裝起來試過。用這個方案,工具會是個大問題... 用的時後得開個 Visual Studio, SERVER 還得裝一大票軟體 (IIS, TFS, SQL + Reporting, SharePoint Team Service, AD…)

    4. USB DISK + PortableApps

      其實這個算不上是個 SOLUTION,只不過順便把它列上來,待會說明用。某次無意間,同事告訴我 PortableApps.com 這個工具,它是個灌在 USB 隨身碟上面的工具 & 一些綠色軟體,有自己專用的 "開始" 選單,方便你插上隨便一台電腦,就把它當作你自己的 PC 一樣使用... 老實說還不錯用 (Y),我就試著用一陣子,把所有個人相關的資料都移到上面了。現在的工作環境有點複雜,公司一台 PC,家裡一台 PC,偶爾還需要用 notebook 去客戶那邊簡報 (咳,就是我那台只有 8GB SSD 的 X40,正好沒地方放檔案)

      用了一陣子還不錯,不過碰到的又是很常見的問題: 檔案掉了怎辦? 備份問題? 讀寫速度問題? Flash Disk 寫入次數限制問題... 不外乎常備份,每天一份 ZIP 檔,用苦力作好版本控制…


    5. USB DISK + SVN

      最後,就是現在用的方案了... 主要是補 (4) 的不足: 一般的定期 ZIP 備份就跟快照一樣,事後要追出變更其實很麻煩,每次變更想加個註解又更麻煩了。當然搭配 Visual Source Safe 這種工具,把 Working Folder 指到 USB DISK 上就可以兩全齊美了。

      不過使用便利則是另一個問題,我希望能夠找個無腦一點的工具,不需事先 check-out (lock) 的動作就可以開始編輯,改完再決定 check-in (commit) 或是 undo (revert) 的模式最好。用了 USB DISK 就是希望能拔來拔去,如果必需配合特定工具 & 要即時連上 SERVER,那就有點麻煩... 想看看,當我 USB DISK 插到 NOTEBOOK 帶到客戶那邊去,都按兩下打開 PPT 在簡報了,臨時要改幾個字,用 VSS 的話,我得關掉 PPT,打開 VSS,CHECK-OUT,打開PPT,修改...

      所以後來的首選就變成 SVN 了。SVN 因應 internet / open source project 的開發模式,採取的就跟 Microsoft 是不同的策略,就是先改再說。SVN 賭你不會多人同時編同一個檔案,就算會,也不會編同一段 code … 真的碰到就再人工處理吧。另外它支援各種不同的 protocol, 透過 internet 這種連線來使用,效能也不會很糟糕...

     

    到目前為止,我用的就是 (5) USB DISK + SVN 這種 solution, 老實說越用越覺的它不錯 (Y)。SVN 我還是個新手,應該輪不到我來介紹他的特色吧 XD,不過我還是挑幾個特別的地方介紹一下,這些是我用它的主要原因啊...

    1. 操作邏輯合適

      SVN 是 CVS 的接班人,它先天就繼承了 CVS 的特性: 就是適用 open source 的開發團隊。Open Source 的開發團隊跟一般的開發團隊有什麼差別? 一般商業開發都是正職的工作,很固定且很密集的進行開發及變更,因此像 Microsoft Solution (VSS / TFS) 那種要事先 lock 的機制會比較有效率。不過 open source project 就反過來,業餘的比例比較高,而且人都散布在世界各地,如果真正用 LOCK 的機制大概會哭出來吧...。我要改的檔案被你 LOCK 住了,不過我又不知道你是誰? 除了等就沒辦法了...。

      因此 SVN 先天就是以這樣的觀點來設計: 你先改了再說,改完就 commit 。反正只要沒人跟你改同一個檔案就沒事... 如果運氣真的不好,那這個人不要跟你改同一段 code 也沒事,直接 merge 就好... 只有真的很背的時後,有人跟你改同一段,那麼後 commit 的人就要負責處理 merge 的問題。不過機率很低嘛 (沒錯,尤其是只有我自己用的時後),你可以不用管它...

      過去用 VSS 常碰到這種情況: 原本只是開個文件起來看 (READ),跟本沒想要去改它,就沒有先作 check-out 的動作了。不過看到一半發現內容有誤,想要修正時... 問題就來了。以 WORD 來說,已經開起來才去 check-out 檔案的話,WORD還是會認為檔案是唯讀的... 除非你關掉 WORD 再開啟一次才有用。不過這麼一來思緒都被打斷了...。

      當然,還是一樣,正規的開發動作還可以要求,一般的文書處理要求到這樣就有點過頭了。因此 SVN 這樣的邏輯就佔了點優勢,我最常碰到的案例就是: 要出門開會,把 USB_DISK 拔出來帶走。開會過程中 (在外面,沒有網路連線) 修修改改 PPT 的內容,回到公司後直接在 NB 或是把 USB DISK 插回 PC,再用 SVN 作 commit 的動作...。

    2. SERVER 的資訊跟著目錄

      有些工具 (像是 TFS),你的工作目錄對應到那個 SERVER,是工具在維護的 (TFS 的 workspace),這時搭配 USB DISK 可能會在不同的電腦 (可能是我的 PC,也可能是我的 NOTEBOOK,甚至是帶回家裡用)。一般把設定綁在工具上的作法就很頭痛,因為好幾台都要設成一樣的,而且 USB DISK 還有可能每次的磁碟機路逕都不大一樣...

      我用的工具是小烏龜 (TortoiseSVN),它的設定就是在每個目錄下放個 .svn / _svn 的子目錄,檔案總管按右鍵叫出 SVN 的選單後,藏在裡面的設定就自動套上來了。這種操作模式,剛好對於我的用法 (USB_DISK) 很方便...

    3. 更精確,更有效率的 "備份機制"

      現在隨身碟廠商,都很愛在商品上加一些小工具,有的有壓縮,有的有密碼保護... 不過 USB DISK 很容易掉,所以所有廠商都不會忘記附上一個備份工具。連我前面介紹的 PortableApps.com 都有附一個 ( 7-ZIP + SHELL )。不過這些備份工具都有個通病... 它就真的只是 "備份" 而已,是讓你心安的。使用時機是你自己要勤勞點,記得每天按 BACKUP。要還原回來,通常就是整支 USB DISK 的內容都還原回來了,如果你只想要還原某幾個檔,或是只要查看過去備份的某個檔,那你得點好幾下滑鼠,甚至是要把整個備份解開才看的到。

      另一個備份問題是,每次都是 FULL BACKUP ... 雖然有些工具作的比較好,有差異備份 ( PortableApps.com 就有提供 7-ZIP 的差異備份),不過不還原還好,一旦要把舊資料撈出來也是很辛苦。當然這些並不是備份工具的錯,備份本來就是作這些事。中間有落差的地方在於 USER 需要的是一個歸檔的機制啊,除了備份也需要調閱舊的版本內容。這時版本控管工具,正好就成為 USB DISK 在 PC 上的第一線 "備份資料庫" 了。當你在 check-in / commit 時,不自覺的就在版本系統內放了一份備份了,不放心的人可以再啟用像 VSS (Volume Shadow Copy Service) 或是定期壓 ZIP 這類一般的備份機制作第二層保護,就很足夠了。

      這裡的重點倒不是備份安不安全啦,而是這樣的操作方式,很自然的就會在 SVN Repository 內留下一份內容,同時也方便你替這個版本作註記,未來要調閱,甚至是比對內容差異都很容易...

    4. 異地存取

      USB DISK 雖然很方便,也可以隨身攜帶,但是我就是會常常忘掉它... 常常忘了拔就出門... 在外面如果還要存取我的 USB DISK 的內容,有網路的話,版本控制系統也很好用。我用的 SVN SERVER 是 Visual SVN,它就有個很簡易的 WEB 介面,真的忘了帶還可以連回我自己的 PC,把檔案下載回來。

      如果用的電腦有灌 SVN CLIENT,那你還可以做些基本的操作...。這套比起來就比 VSS 強的多。VSS 完全是 file system base, 透過遠端的操作必需先用網芳之類模擬 file I/O 的方式,效能很糟糕... 雖然 2003 年左右 Microsoft 替 VSS 加其了很多功能,像是 LAN Boost Service (我搞不懂它怎麼做的),或是替 VSS 加上 Web Service Interface (可以透過 HTTP),不過效果都不盡理想。

    這些功能加一加,就是我現在在用的個人檔案管理方案了啦。家裡有台現成 SERVER,很多問題就更好解了。這套作法正好给有需要的人參考看看,如果你用了有什麼心得,或是有其它更好的用法也歡迎分享 :D

    2009/04/20 Tips 技術隨筆 有的沒的

  3. RUNPC 精選文章 - 運用ThreadPool發揮CPU運算能力

    果然這個什麼東西都上網的年代,要三不五時的 GOOGLE 一下自己,才會知道那些網站把你的八卦跟內幕爆了出來... 不過應該沒啥週刊記者對我有興趣吧? 哈哈。在 GOOGLE 自己名字時,倒是意外發現,之前投稿的文章,又有一篇被拿來登在網站上的精選文章了 :D

    特此留念一下 :D

    http://www.runpc.com.tw/content/main_content.aspx?mgo=176&fid=E02

    --

    順便整理一下懶人包:

    另一篇精選文章 [RUN!PC 精選文章 - 生產線模式的多執行緒應用]

    過去投過的系列文章 (multi-threading programming using c#):

    2008/11. 生產線模式的多執行緒應用
    2008/09. 用ThreadPool發揮CPU運算能力
    2008/06. SEMAPHORE在ASP.NET的應用
    2008/04. 以ASP.NET開發同步WEB應用程式

    2009/04/18 RUN! PC 專欄文章 .NET C# RUN! PC 作品集 多執行緒 專欄 技術隨筆 有的沒的

  4. EF#3. Entity & Inheritance

    繼承 (inheritance) 是物件技術的核心,就是這個特性提供了 OOP 絕大部份的特色。這東西被拿掉的話,OOP就沒這麼迷人了。繼然談到了 ORM,就不能不來看看 R(關聯式資料庫) 怎麼被對應到 O(物件),同時還能處理好繼承關係。

    RDBMS 連基本的物件 (Object Base) 都不支援了,更別說物件導向 (Object Oriented) 了。因此要搞懂 ORM 及繼承的關係,就得先瞭解基本的 OO 是怎麼實作 "繼承" 這個動作。這些知識是古早以前學 C++ 時唸到的,現在的 CLR 不知道有沒有新的作法? 不過應該大同小異吧! C++ 主要是靠 virtual table 來實作繼承關係,當子類別繼承父類別時,父類別定義的 data member 跟 method 就全都遺傳到子類別身上了,這動作就是靠 virtual table 作到的。細節我就不多說了,有興趣的讀者們請先上網找找相關資訊看一看。

    ORM 的運氣好多了,只要處理資料的部份。因此前一段提到的 virtual table 如果要拿來應用也會簡單的多。virtual table 可以很直覺的想像成是 DBMS 裡 table schema 的定義,而一個物件 (instance) 的 virtual table 資料,正好就對應到該 table (DBMS) 的一筆資料。這正好是 ORM 基本的動作。大部份 OO 的書都會說,繼承就是 " Is A " 的關係。在資料上則是子類別擁有父類別所有的欄位定義。這很容易對應到資料庫的正規化,該如何切割資料表的規責。你可以切開靠 PK / FK 再併回來,或是直接反正規化讓它重複定義在多個 TABLE... 事實上,兩大 ORM (EF & NH) 都歸納出三種作法,後面來探討一下彼此的差異...

    再來看看繼承關係,假設父類別 class A 對應到 table A, 那麼衍生出的子類別 class B 對應的 table B, 則應該要包含所有 table A 定義的欄位才對。從這點出發,就帶出了第一種作法: 就是把 table A 所有的欄位都建一份到 table B (註: table per concrete type)。

    不過這樣看起來有點蠢,DBMS 熟悉的人也許會採另一種作法: 沒錯... table B 只要留個 foreign Key, 指向 table A 的 primary Key,需要時再 join 起來就好了,這是第二種作法 (註: table per type)。

    唸過 DBMS 的人都還記得 "正規化" (normalization) 跟 "反正規化" 吧? 切割過頭也是很麻煩的,因此有第三種作法逆其道而行,就是建一個 table 給所有的子子孫孫類別共用。因此 table 需要的欄位,就是所有的子類別的所有欄位集大成,通通都建進來... 不用的話就空在那裡,這是第三種作法 (註: table per hierarchy)。

    這三種作法,在 Entity Framework (以下簡稱 EF) 或是 NHibernate (以下簡稱 NH) 都有對應的作法,只不過名字不大一樣... 這篇 ADO.NET team blog 借紹的還不錯,可以參考看看。這三種方式,在 EF 裡的說法分別是 (括號裡是 NH 的說法,參考這篇: Inheritance Mapping):

    • Table per Hierarchy (NH: Table per class hierarchy)
    • Table per Type (NH: Table per subclass)
    • Table per Concrete Type (NH: Table per concrete class)

    事時上,處理方式大同小異,不外乎用三種不同的對應方式,來處理物件繼承關係。這些不同類別的物件彼此有繼承關係,對應到 TABLE 的方法不同,各有各的優缺點。其實 ADO.NET team blog 講的都很清楚,我就不再多說,簡單列張比較表:

      適用於 不適用於
    Table Per Hierarchy
    1. 最簡單的實作方式
    2. 所有同系類別的實體 (instance) 數量不會很多時
    3. 需要用單一 QUERY 查出所有的子類別物件時
    4. 繼承階層較簡單的情況
    5. 類別的欄位要調整很容易
    1. instance數量太多,會嚴重影響效能
    2. 無法在table schema上做太多嚴格的檢查
    Table Per Type
    1. 繼承關係清楚的對應到 TABLE
    2. 需要用單一QUERY查出所有子類別的物件
    3. 不同於 TPH,可以針對每種類別,設定嚴僅的 table constraint
    4. 每個類別要變動或調整都很容易
    1. 繼承階層較多時,要取得單一 instance data 需要透過多層 join
    2. table 數量會隨著類別的數量快速增長
    Table Per Concrete Type
    1. 綜合 TPH / TPT 的優點 (也綜合了兩者的缺點)
    2. 可以針對每種類別設定 table constraint
    3. ORM mapping 很簡單
    1. 要用單一QUERY查出所有子類別的物件並不容易 (需要把所有的 TABLE JOIN 起來)
    2. 父類別的欄位調整很麻煩,所有的 TABLE 都需要配合調整

     

    [未完待續] to be continue…

    2009/03/03 系列文章: Entity Framework 與 物件導向設計 .NET C# SQL 技術隨筆 物件導向 Entity Framework

  5. EF#2. Entity & Encapsulation

    前一篇講了一堆大道理,這篇就來看一些實作吧。各種 ORM 的技術都有共同的目的,就是能把物件的狀態存到關聯式資料庫,而這樣的對應機制則是各家 ORM 競爭的重點,勝負的關鍵不外乎是那一套比較簡單? 那一套包裝出的 Entity 物件能夠更貼近一般的物件?

    會有這樣的 "對應" 機制需求,原因只有一個,物件技術發展的很快,已經能解決大多數軟體開發的需求了,不過資料庫就沒這麼幸運,現在的 DBMS 撇開一些技術規格不談,本質上還是跟廿年前差不多,就是關聯式資料庫而已,本質上就是一堆 table + relationship, 配合 SQL 語法來處理資料。發展至今,物件技術跟資料庫技術能處理的問題,已經是兩個完全不同世界的問題了,三層式的架構在這段出現斷層...。

    解決方式大概有兩條路,一種就是想辦法把這兩個世界串起來,就是 ORM framework 想做的事。另一個就是改造 RDBMS,讓 RDBMS 進化成也具有物件導向特性的資料庫。不過以眼前的五年十年來看,ORM 還是大有可為。ORM 只要能把 "對應" 這件事做到完美的地步,其實在某個層面上就已經做到 OODB 的願景了,只差在這些物件是活在 APP 這端,不是活在資料庫那端...。

    扯遠了,接下來我會試著從物件技術的三大核心 (封裝、繼承、多型),及資料庫最需要的查尋機制 (QUERY) 來看看 Entity Framework 各能提供什麼支援,才能客觀的評論 Entity Framework 值不值得你投資在它身上。

    在繼續看下去之前,請先俱備基本的 Entity Framework 運用的能力。在 MSDN 名家專欄裡 MVP(朱明中) 寫的這幾篇我覺的很不錯,可以參考看看。我就是看這幾篇入門的 :D。幾年前在比賽上碰過他幾次,我還蠻配服他的,可以靠自學而有今天的成就。以下是他寫的幾篇 ADO.NET / Entity Framework 的系列文章:

    1. 讀寫 ADO.NET Entity Framework (2007 年 9 月)
    2. 由 LINQ 存取 ADO.NET 物件 (2007 年 9 月)
    3. 整合 ADO.NET Entity Framework 到應用程式中 (2007 年 9 月)
    4. 首次接觸 ADO.NET Entity Framework (2007 年 9 月)
    5. ADO.NET Entity Framework 概觀 (2007 年 9 月)

     

    在開始之前,我們先來看看一個最簡單的 Entity Framework 的範例,然後來看看封裝性能夠對你的程式帶來什麼影響? 先來看看只用到了 ORM 卻沒發揮封裝性的例子:

    image

    這是存放會員資料的表格,對應的 TABLE 很簡單,SQL 如下:

    [copy code]
       1:  CREATE TABLE [dbo].[Users](
       2:    [ID] [nvarchar](50) NOT NULL,
       3:    [PasswordHash] [image] NOT NULL,
       4:    [PasswordHint] [nvarchar](100) NOT NULL,
       5:    [SSN] [nchar](10) NOT NULL,
       6:    [Gender] [int] NOT NULL,
       7:   CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
       8:  ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

     

    大部份的人在 EDMX Designer 裡把資料表拉進來後,就開始用這個 Entity Class 了吧? 密碼的部份為了安全及實作上的考量,DB只存放 HASH,而 HASH 的運算則透過 .NET 程式來計算,不透過 SQL 的函數。作法決定後,你可能會寫出這樣的程式碼:

    建立帳號的程式碼[copy code]
       1:  // 準備 object context
       2:  using (Membership ctx = new Membership())
       3:  {
       4:      // create user account:
       5:      User newUser = new User();
       6:      newUser.ID = "andrew";
       7:      newUser.PasswordHint = "12345";
       8:      newUser.PasswordHash = HashAlgorithm.Create("MD5").ComputeHash(Encoding.Unicode.GetBytes("12345"));
       9:      newUser.SSN = "A123456789";
      10:      newUser.Gender = 1;
      11:      ctx.AddToUserSet(newUser);
      12:      ctx.SaveChanges();
      13:  }

     

    檢查密碼的程式碼[copy code]
       1:  // 準備 object context
       2:  using (Membership ctx = new Membership())
       3:  {
       4:      string passwordText = "12345";
       5:      User curUser = ctx.GetObjectByKey(new EntityKey("Membership.UserSet", "ID", "andrew")) as User;
       6:      bool isPasswordCorrect = true;
       7:      {
       8:          byte[] passwordTextHash = HashAlgorithm.Create("MD5").ComputeHash(Encoding.Unicode.GetBytes(passwordText));
       9:          if (passwordTextHash.Length != curUser.PasswordHash.Length)
      10:          {
      11:              isPasswordCorrect = false;
      12:          }
      13:          else
      14:          {
      15:              for (int pos = 0; pos < curUser.PasswordHash.Length; pos++)
      16:              {
      17:                  if (passwordTextHash[pos] != curUser.PasswordHash[pos])
      18:                  {
      19:                      isPasswordCorrect = false;
      20:                      break;
      21:                  }
      22:              }
      23:          }
      24:      }
      25:      Console.WriteLine("Password ({0}) check: {1}", passwordText, isPasswordCorrect ? "PASS" : "FAIL");
      26:  }

     

    這樣的 User 類別設計有什麼問題? 我列幾個我認為設計上不妥的地方:

    1. 直接提供 PasswordHash 曝露過多不必要的實作細節
    2. 在台灣,身份證字號 (SSN) 跟性別 (Gender) 是相依的欄位 ( functional dependency )

    以物件導向的角度來看,User 真正要提供的是接受 "驗證密碼" 的要求,至於你的實作是提供明碼或是用 Hash, 都是實作的細節。提供原始未加密的密碼,或是提供處理過的 HASH,在需求上都是不必要個功能。物件的介面定義要盡量以能滿足需求的最小介面為原則,其它的都不要公開,才滿足 "封裝性" 的要求。因此良好的設計應該把這些細節封裝起來,只在公開的介面表達你要提供的功能。

    另外依照台灣的身份證字號規則, SNN 跟 Gender 是連動的。目前 User 的設計是把兩者的關係丟給前端寫網頁的人來維護,一不注意就會發生不一致的情況。DB 對於這種問題的解決方式,不外乎寫 trigger 或是其它 constraint 的方式來阻擋不正確的資料被寫入 DB,不過看了前面提到的規則,要單純用 SQL 的功能完整實作出來,還不大容易。

    另一種作法,只儲存 SSN,Gender 欄位則以 VIEW 的方式提供,這樣就不會有不一致的問題。不過這方法的缺點在於,當邏輯太複雜的時後,常常會超出 SQL 能處理的範圍,效能也許會是個問題,或是 constraint 不能完全跟程式端一致。

    就我看來,這類看似應該在 data layer 實作的複雜邏輯,又難以在 SQL DB 上面解決的問題,才是 Entity Framework 的強項。現在來看看 Entity Framework 能怎麼解決這些資料封裝的需求:

    首先,把不需要公開的細節改成 Private 隱藏起來,包括 PasswordHash 的 Getter / Setter, Gender 更名為 GenderCode, 同時把 Getter / Setter 也改為 Private ...

    接下來就要把這些封裝起來的細節,提供另一組較合適的公開資訊的方式。這時 .EDMX designer 替我們產出的 code 就能搭配 partial class 擴充功能了。來看看我們在 partial class 裡寫了什麼?

    User.cs 的內容 (partial class)[copy code]
       1:  public partial class User
       2:  {
       3:      public string Password
       4:      {
       5:          set
       6:          {
       7:              this.PasswordHash = this.ComputePasswordHash(value);
       8:          }
       9:      }
      10:      public bool ComparePassword(string passwordText)
      11:      {
      12:          byte[] hash = this.ComputePasswordHash(passwordText);
      13:          // compare hash
      14:          if (this.PasswordHash == null) return false;
      15:          if (hash.Length != this.PasswordHash.Length) return false;
      16:          for (int pos = 0; pos < hash.Length; pos++)
      17:          {
      18:              if (hash[pos] != this.PasswordHash[pos]) return false;
      19:          }
      20:          return true;
      21:      }
      22:      public GenderCodeEnum Gender
      23:      {
      24:          get
      25:          {
      26:              return (GenderCodeEnum)this.GenderCode;
      27:          }
      28:      }
      29:      partial void OnSSNChanging(string value)
      30:      {
      31:          // ToDo: check ssn rules.
      32:          // sync gender code
      33:          this.GenderCode = int.Parse(value.Substring(1, 1));
      34:      }
      35:      private byte[] ComputePasswordHash(string password)
      36:      {
      37:          if (string.IsNullOrEmpty(password) == true) return null;
      38:          return HashAlgorithm.Create("MD5").ComputeHash(Encoding.Unicode.GetBytes(password));
      39:      }
      40:  }
      41:  public enum GenderCodeEnum : int
      42:  {
      43:      FEMALE = 0,
      44:      MALE = 1
      45:  }

     

    被隱藏起來的 PasswordHash, 公開的介面就用 Password 的 Setter 跟 ComparePassword( ) method 取代,明確的用程式碼告訴所有要用它的 programmer:

    "密碼只准你寫,不准你讀 (read only)... 只告訴你密碼對不對, 不會讓你把真正的密碼拿出去"

    另一個部份,就是身份證字號跟性別的問題,則改用另一個方式解決。SSN 這個屬性維持不變,在它被更動時就一起更動 GenderCode 這個欄位。GenderCode 完全不對外公開,公開的只有把 int 轉成 GenderCodeEnum 的屬性: Gender。同時為了保護資料的正確性,只開放 Getter, 不開放 Setter。

     

    同樣的程式,在我們調整過 Entity 的封裝之後,再來重寫一次看看:

    建立新的使用者帳號[copy code]
       1:  // 準備 object context
       2:  using (Membership ctx = new Membership())
       3:  {
       4:      User newUser = new User();
       5:      newUser.ID = "andrew";
       6:      newUser.PasswordHint = "My Password: 12345";
       7:      newUser.Password = "12345";
       8:      newUser.SSN = "A123456789";
       9:      ctx.AddToUserSet(newUser);
      10:      ctx.SaveChanges();
      11:  }

     

     

    檢查密碼是否正確[copy code]
       1:  // 準備 object context
       2:  using (Membership ctx = new Membership())
       3:  {
       4:      EntityKey key = new EntityKey("Membership.UserSet", "ID", "andrew");
       5:      User user = ctx.GetObjectByKey(key) as User;
       6:      // 要比對的密碼
       7:      string passwordText = "123456";
       8:      bool isPasswordCorrect = user.ComparePassword(passwordText);
       9:      Console.WriteLine("Password ({0}) check: {1}", passwordText, isPasswordCorrect ? "PASS" : "FAIL");
      10:  }

     

    修改過的程式簡潔多了。不過比少打幾行程式碼更重要的是,它的邏輯更清楚,更不容易出錯。如果沒有妥善的處理封裝性的問題,可以想像寫出來的程式一定亂七八糟。要嘛不正確的資料都會被寫進 DB,不然就是 DB 有作適當的防範,但是程式沒有作好,最後就是到處都出現 SqlException ...

    這裡只是簡單示範一下 Entity Framework 如何替資料提供封裝的特性,後續的文章會繼續示範 Entity Framework 如何能把 DBMS 的資料,進一步的應用到物件技術的繼承及多型等特性。敬請期待下集 :D

    2009/01/23 系列文章: Entity Framework 與 物件導向設計 .NET C# SQL 物件導向 Entity Framework