文章列表

珠海半小時車程一家大小可以去邊玩?

小城角落
MacauYeah・2024-06-28

珠海半小時車程一家大小可以去邊玩? 緊係要試上年新開今年初營業的珠海毛林金灣大酒店!!! 筆者聞說友人極推位於大灣區半小時車程新開的大酒店,內有中小童足夠喪玩的兒童遊樂城及冰雪運動城,酒店對面就是金灣華發商者,所以筆者當然要找個周末一家人上去放鬆一下。 國內酒店很方便,可以網購或找澳門旅行社訂都十分優惠,筆者比價後選擇了向本澳某大旅行社代訂兩天一夜套票,價格1,000澳門元,不過筆者揀咗車車主題房所以要再加多500澳門元。 雙人套票:包括早餐、兒童遊樂城、冰雪運動城、泳池及健身。 筆者訂購的是周五連周六(公眾假期)的兩天一夜套票,下午才可check in 酒店,但可於11:00後便可入場玩。筆者只有澳腳北上,所以過關後就直接約順風車,約40分鐘直達珠海毛林金灣大酒店。一到酒店,望落豪華的大堂大到呢…..應該有大半個足球場咁大 由於筆者的小朋友還嫰,當然係先去兒童遊樂城探個究竟先啦,一入到場,職員會俾廿個遊戲幣俾客人,但原來幣仲可以用黎夾公仔!!!正呀! 而場內所有遊玩設施都係免費任玩!!即刻沖入去睇吓: 場內樓高兩層: 下層有適合細B玩的大型波波池、迷你飄流、各式電動車、撈魚同積木等等;旁邊仲有岩大人玩的VR 遊戲!而另一邊仲有好多適合中童的大運動攀爬設施! 上層有其他電動遊戲、電動車、迷你war game場、當然唔少得各式玩具同零食夾夾機同小食區! 小朋友真係玩到樂而忘返,到咗晚飯時候當然要同佢講明天仲可以繼續玩先肯乖乖離開。 返到酒店房,唔洗多講一睇間房,嫰B十分喜歡嘩了幾聲,房間清潔,設備齊全仲備有兒童清洗用品,場內仲設有小帳幕同窗外望到開洋的美景。 房間隔音十分好,大家安睡一夜後,翌日先去嘆個自助早餐先! 同澳門五星級酒店有得揮喎,食物多,干淨服務不錯! 食飽轉場去冰雪運動城…… 場內有夠哂玩的仿雪滑梯 雖然哩兩日都十分大雨,幸好哩間酒店內有齊所有娛樂設施,大家都玩得十分滿足,總結哩個行程,性價比高! 溫馨提示:筆者首日同翌日入場的感受有好大差別,首日周五場內冇乜人,干淨安全不用排隊,但翌日是公眾假期,人山人海呀!!!所以雖是新酒店,但係內地玩都係要揀D冇咁多人的時間去比較輕鬆。 最後補充吓套票包咩,套票錢已包括酒店內所有設施,大家可盡情暢玩,但值得注意的是像我們一家三口,即多了一個人需要加購早餐及各場所入場門票。加購規則如下: 早餐:入住客人現場加訂早餐,獲門市價的8折(135人民幣/位(大小同價);身高1.2米以下小童免費,每一位成人限帶一名免費小童; 冰雪運動城:199人民幣/位大人,159人民幣/位小童,大人及小童必需穿帶指定裝備入場,裝備免費提供,但需付押金,離場後退回; 兒童遊樂城:79人民幣/位大人,99人民幣/位兒童; 備註:未滿18歲為兒童,18歲以上為成人,1歲以下免費入園,12歲以下兒童必須由一名成人陪同; 套票內包含的是2位成人門票,如入住時2大1小,小童需要現場加購小童價格的門票。 慳錢TIPS: 筆者獲酒店櫃檯職員告知雙人套票包的入場之手帶其實冇分大人定小童,持手帶便可入場,因此筆者帶同年僅兩岁的幼兒入場時,只加購了一張成人陪同票(成人票比兒童票便宜哦)

《魔物獵人世界》《魔物獵人崛起》通用心得

手機‧電玩
MacauYeah・2024-06-25

好久沒有寫Game心得分享,那是因為筆者也真的很久沒有開新坑。最近因為《魔物獵人》有新作預告,大家又好好地重新把《魔物獵人世界》拿出來練練手。筆者也順便把過去的買了沒有怎玩的《冰原》DLC拿出來,好好地玩一遍,總算完了一個心願。 因為玩了兩款近作,對於魔物獵人系統多少有一點入門心得,就來梳理一下,方便新朋友入坑時不再碰壁。 防具 隨著主線推進,不單武器可以強化,防具也可以繼續強化。強化等級因為階段推進而有突破。筆者以前就是不知道這件事,前期以為防具早已升滿,但後來一直貓車,才知道防具防禦力太低,需要經過重複強化提高防禦力。 防禦、體力增幅技能 防禦力、體力最大值兩者當然是越高越好。但在推進度前期,什至後期因為需要額外技能,防禦、體力不一定全滿。有必要時防禦增幅時選一半,體力增幅點滿比較好。因為後期挨怪物一套連招,即使防禦多高,也不能不回血。而體力增幅通常較易點滿,避免因為異常狀態影響而被連招到死,可以增加容錯率。而且後期回血道具一般直接使用秘藥,一次過回滿血條。所以筆者認為體力增幅比防禦增幅更有效。 防禦力疊加 若果你像筆者一樣菜,後期必需要使用各種方式疊加防禦力。主要方式有三個。食貓飯、道具持有、道具使用。貓飯在《魔物獵人世界》和《魔物獵人崛起》,都有機率成份,不過好在後期,總有票卷可以提升機率,筆者都偏向把投資在防禦力上。道具持有,主要在XX之爪和XX護符之上,帶著出戰就好,兩者最多各帶一份,但可以疊加。道具使用,就是硬化藥、硬化粉塵、忍耐種子。三種可以同時使用,可疊加,但硬化粉塵、忍耐種子有時效。 精靈加護 有一定機率減少傷害,但筆者後期不夠技能欄位,沒有去配。它也是增加容錯的機率,但防禦、體力、精靈加護全要的話,攻擊技能就更少了。所以筆者放棄它。 龍車 中後期,魔物都會四處跑,發怒時更是撞來撞去,總之就是讓你打空氣。筆者初時也不知道怎樣對策,總是跟著魔物屁股走。但其實這樣更費時,其實你可以原地等待,有空就調合或為武器上Buff,反正它很快就會回頭。想更有效的提升DPS,應該花時間去量度回來時的落點。魔物從遠處過來,但其實中途很少變向,比較有條件預判和輸出。若為團戰,因為有隊友分散仇恨,才需要主動追上魔物。 打點 初接觸這系列時,筆者就不斷看到【肉質】這一詞,但其實玩到現在,筆者都不太掌握。但通常都是集中打魔物的頭就了事。當打頭都出現彈刀時,再攻擊其他地方。有條件有心情,可以逐隻魔物研究,長期打到有效位置,傷害差很多,才能會易出現魔物倒地的狀態。 配裝 別人的配裝,其實自己並不一定能用,特別是那些TA影片(Time Attack)。MH系統的技能取得,都很有運氣成份,所以想要的技能不一定馬上能配到,大家主要去找自己武器的核心技能就OK。有些武器可能有多個流派,而且隨著時間累積,素材的隨機出現,我們有必要定期整理裝備組合。不同攻略網站,都會分享畢業裝,不過筆者到現在,都未去到這個攻守兼備的狀態。還是忘提畢業裝吧,後期的強怪,還是要針對性地重點挑整。 救援、團戰 有時卡關,有條件就叫救援吧,不必刻意自己打。因為進度限制,主線沒有推進的話,強化功能不開放,死磨也不一定有裝備提升。來救援的人一般是已推完主線,回來刷素材的,他們有較高的防禦去吸收傷害。他們不一定比你強,但貓的機率就比較低。但也不排除有時人沒打完主線就來抱大腿,結果搭沉船一起貓車。所以一定一定要準備【生命粉塵】,有必要時,就為殘血的同伴回血。那怕團友很強,也有機會出現異常狀態疊加的情況,為他回個血,他能更加集中輸出。 主機版都要買額外的會員制,才能聯機求助。PC版就沒有這個限制,接通網路就可以招外援。筆者推薦PC版,因為會員制的其他贈品都很雞肋,還不如PC版來得清靜。 以上,就是筆者玩完《魔物獵人世界》和《魔物獵人掘起》兩作的心得,雖然筆者還是很菜,但至少後期也玩得下去。希望以上各點可以為沒通關的朋友帶來實際的幫助。

github flow - github 開發流程

科技新知
MacauYeah・2024-06-20

那些年那個很穩定卻又不受歡迎的 git flow 開發流程 多年前,朋友就向筆者介紹git的團隊整操作流程。筆者深思過後,的確實用,那些年的git-flow,很美滿,由開發、測試,到發佈、修補漏動(backport),都有清楚明確的指引。 原作者連結:git-flow 大家如果沒有更複雜的需求,真的可以照搬,筆者也很推這一個模型。 但在長期推廣下,筆者發現大部份人其實都不熟git的基本操作,什至連git graph也不看,現在看git flow,就更不可能接受。那怕是有常用git的個人團隊,也是不怎使用分支模型。 前一兩年,筆者也不懂,筆者也努力地簡化git flow。例如把master和develop合而為一,但最後也是少有人可以接受,很多人還是卡在分支那邊,對checkout、merge還是很陌生。在跟更多不同人的協作過後,筆者總於意會到一件事。其實大部份人,只想知道最後、最新的狀態,只會更新 master / main ,也因為個人開發,所以連衝突也不會有,更不需要使用merge。那怕是少型團隊,頂多也是維護main的衝突,間中用用merge,而checkout還是用不著。 其實這個情況,並不限於小型團隊。因為 web app 和 DevOps 的流行,所以越來越少機會要維護多個舊的穩定版本。大家都專心於最後一個開發及發佈版本就完事,用戶的某個版本有問題?更新到最新版本吧。(註:越底層的應用開發模式,因為相容性問題,不可能只保留一個穩定版本。) 那麼我們就大力簡化吧 - github flow 開發流程 既然大部份情況,大家都只在乎 main / master / 預設分支,那我們也沒有必要跟著複雜的 git flow 走。但在 DevOps 的角度下,為保證 main / master 穩定性,大家還是至少要遵守branching 、pull (merge) request 、code review 、auto test 原則 。 github就最簡單的branching 、pull request 、code review 提出了它們的 github flow。 簡而言之,就是每個人在開發時,都先從 main 起一個新分支,不斷更新。待合適的時候,就透過 pull requst,向原項目負責人提出申請,只要項目負責人點頭,就可以把改動傳入 main 中。又因為Github 原本的定位在於個人與個人之間的協作,初時已經需要通過fork建立獨立的倉庫,那怕你不愛分支也必需分支。所以 pull request,code review 的作用更明顯,後逐的協作更理所當然。 但若果回到公司團隊協,Github flow 就應該像筆者之前提出協作方案,各自起分支,最後由某個人守門,把所有結果放到 main 中。(前文連結)

Coding Anywhere 工作方案

科技新知
MacauYeah・2024-06-13

最近筆者一直在準備軟件開發的教材,因為各種原因,例如:新舊硬件交替,沒有固定的電腦等等,讓寫稿和設計教程的進行得很慢。但其實這種情況並不旱見,即便是真正的開發團隊,也會時時刻刻面對各種新舊設備的更換。在不久的未來,這種更替速度可能更頻繁,工作模式也很可能趨向這樣,為了打造更靈活的工作方案,適筆者一直為自己的coding anywhere情境物色合適的工具。 基本假設 在分享之前,有些前題條件必需要滿足,coding anywhere的基本條件是我們可以把一些厚重的資源變成cloud或遠端工作,如果你是開發主機遊戲,你的測試必需要在PS5上跑,那就沒有條件帶著裝備走。即使你可以設定遠端連線,但你人在外,其實沒法在PS5上做互動。真正有條件實行的工作,必需要可以在外由開發至測試都行得動。 在這個前題下,筆者就開始分享一些自己嘗試過不同組合。 不可或缺的東西 - 滑鼠、網絡 這件事,看似不重要,但筆者一直沒法找到完美的解決方案。 無線滑鼠是標準配置,筆者曾經想去掉滑鼠,但不太可行。這個大家還是選一個不太大,而且可以穩定在不同機器切換的滑鼠吧。至於鍵盤,視乎你的主機有沒有實體鍵盤,如果最後選擇平板或掌機的話,還是需要帶鍵盤外出,亦即是不論你選擇何種方案,鍵盤的重量也是不會消失的。 另一個就是網絡流量問題。我們處身的環境,並不一定有免費網絡。有時為了安全性,我們不想配對公用Wifi。那怕不考慮安全問題,公用Wifi都很常出現因為人流太多而被踢的情況,所以一般都考慮直接使用手機的4G/5G網絡。而為了節省流量,一般控制好大檔案/大更新的下載時機,都是可以達到的。 不同的工作模式,不同的選擇 上述第一個問題在筆者看來,都屬於沒有選擇,但下面的選擇,可以基於價錢、功能、需要而搭配。另外,我們還要假設我們有足夠的Remote資源可以用。但如果大家的開發,必需要帶著硬件資源,就不太可能實現coding anywhere。 一台入門級的Notebook 如果我們大部份工作,都可以經Cloud Service解決的話,其實我們不必投資太多在主機之上。Notebook帶著四處跑,壞的可能性也多,入門級的Notebook就算壞了也沒有那麼心痛。 全Cloud Service還有另外一個好處是不需要擔心備份問題,壞了Notebook就狠心換機。而且Cloud Service的好處是需要更新client software的網絡流量消費不高,不過想真省錢的話,就需要好好控制cloud service。 例子1,如果大家熟識或願意使用github codespace或gitpod等全cloud IDE,Notebook只需要安全Browser就夠。所有IDE, VM都由github或gitpod提供,它們各自有各自的免費用量,也就是說,當大家真的不夠用又不想付費,可以兩著切換用。真的不夠用,就時租codespace 2G 每小時$0.18USD,約為每小時1.44MOP。 例子2,如果大家有自己Cloud VM,可以用VS Code + SSH,除SSH的extension外,其他安裝及運行在VM中,對Notebook client的要求不高。Cloud VM品牌可以使用Digital Ocean、Linode等,2G機器價錢更低,每小時0.018USD左右,不過就要自己初始化各種工具。 一台高階的Notebook 這個方案可能就不需要再多解釋了,那就是你把家裏的核心電腦帶著到處走,一切都自給自足。在外的不可控因素可能就只有電量控制。另外一方面,長期的備份和維修成本也是需要考慮的。 輕便裝:一台中階大平板 跟上面的遊戲用PC掌機類似,不過螢幕更大,但缺點是配上鍵盤後,價錢比得上一台中階電腦,出門的重量也比得上電腦。在軟件上,你還必需要選擇Cloud VM,Local IDE也不一定有。所以在成本上來講,沒有很太優勢。大平板可能只對那些有專門APP需求的用戶有意義。 究極輕便裝,一台7/10寸入門平板 大平板最大的問題是價錢,但如果換成小平板,一切就不錯了,壞了也沒有那麼心痛。源用所有純Cloud解決方案。出門的負重最低,電量也最有保證。這是筆者最推薦的方案。 低成本高階機:遊戲用PC掌機 對,你沒有看錯,筆者指的是主打遊戲的PC掌機,也是筆者現時自己的最佳方案。假如你在工作室、家、公眾環境來回切換,很擔心傷到Notebook的話,那麼買台低成本的PC掌機絕對是可以接受。有些很重要的底層功能,需要多台Cloud VM,可能花費很高,所以還是需要經Local實現比較有性價比。 它最重主要的問題是螢幕小和沒有鍵盤,但這個程度,對比入門平板來講,其實都差不多。但它比平板有更強的CPU、RAM,作為移動核心電腦一定沒有錯。你還可以自由選擇Local VM、Cloud起VM。

何謂 Infrastructure as code - IaC

科技新知
MacauYeah・2024-06-07

在雲端服務出現後,好多新的名字,或許大家都聽過,筆者也稍為再簡介一下。 Software as a Service (Saas)。就像我們的Web App,不用下載體件,可以直接經雲進行業務操作。 Platform as a service (Paas)。這個概念可能最含糊,筆者理解就是,雲端供應商提供一些底層的軟件,供IT人使用。像是資料庫,Web Engine。 Infrastructure as a Service (Iaas)。這個更底層,雲端供應商給出CPU, IP, Memory, Storage等,頂多就再多個預安裝OS的選項。IT人自己去配搭使用。 多得這些彈性服務,雲端應用才真的跟過去租用實體伺服器有所差異。 但對於IT人來講,要使用這麼多不同的服務,實在也不簡單。對於IT消費方,用錢換來實體硬件的靈活性,但因為硬件沒有邊界之後,軟件的量就暴增。管理也不能說是很方便。 在Docker, Kubernetes等Container(容器)出現後,又為這些管理問題帶來另一種希望,就是Infrastructure as code - IaC。它的目標是,管理基礎設施,要像管理原始碼一樣,checkout 就可以回覆到指定狀態。 初聽之下,大家可能覺得好玄,但其實這個概念,在之前筆者的Docker 教學中,已經有出現過。Docker compose 、Docker stack 指令,它們都是基於yaml檔的IaC。 當筆者更新了yaml 檔,執行一次stack deploy ,docker 就會對比之前狀態,如果replica(分身)不夠多,就自動增加所欠數量,如果image 也更新了,就排隊輪流重起換image 。最重要的是,當我們checkout 舊的yaml 檔,回到過去某個狀況,只要還是執行同一句stack deploy ,系統就自然減少所需的replica數量,下戴過去的image。這就代表,我們對於container 構建而成的環境,都可以放入Git等版本控制中,整個管理模式,就像管理程式碼一樣(GitOps)。 對於更進階的Kubernetes更是如些,除了container外,多種不同的network,storage配置,都通過yaml檔進行控制。這樣,架構即使複雜,但只少可以測試、重現和管理。還有一個,它比Docker stack要強的是,它某程度上支緩刪除的概念。雖然不完全,但總比完全沒有方便。 這個IoC的概念,可能還未達到一定廣泛地應用的階段,但它的核心在於,基建項目有檢測試差異的功能,自動去因為這個差異去加或減資源。用Programmer的講法,就是系統有Diff的功能,自動多除少補。

開發者在Steamdeck上的另一個選擇: Gnome box

科技新知
MacauYeah・2024-05-28

前些日子,因為升級podman的關係,筆者對Steamdeck的限制就更為了解。因為Steamdeck是一個修改過的Arch linux,不單止代表是某些區塊是唯讀不可寫。更深一層的問題是,有些依賴包,不能簡單地通過安裝或自行編輯來解決。 例如早前podman 5.0.x需要的pasta依賴,雖然Arch linux官方有這個lib的發佈,但Steamdeck沒有選用,那些我們自己下載原始碼,你地會發現steamdeck的gcc或cc編譯指令還法完全執行,一來是編譯器指令沒有預設對,另一方面則是缺少了更多的c lib (.h) 依賴包。最後筆者只好選擇下載pasta官方預編譯的二進位程式。能用,但就總是多少有點不安心。因為pasta的預編譯只是針對x86_64的CPU,並沒有考慮link lib的問題,不過這次運氣還算可以,沒有無盡依賴的問題。 回來講Steamdeck的情況,之前筆者介紹brew,其實是macOS帶過來的,雖然他們對其他linux的支援很不錯,但多少都基於某些低層的依賴包可以隨時更新。而Steamdeck這個限制版,就沒有保證linux 依賴包的預安裝。(那怕是Ubuntu也是一樣,只是我們可以通過進一步的指令案裝就可以了。)所以在Steamdeck上,長遠還是要找一些官方維護的軟件比較安全。 Steamdeck上預設的是依賴安裝是【Flatpak】,雖然它不像yum, apt, dnf這些仔細可以安裝原始碼依賴,但它們可以安裝App,例如Firefox、Chrome、輸入法等。遺憾的是,Flatpak上沒有podman, docker,對於開發者來說就很不方便。 但最後,筆者終於在【Flatpak】上發現一套【BOX】VM解決方案。它的功能不算強大,但至少可以經ISO安裝自己想要的OS,也有快照功能(只限關機狀態下)。BOX官方亦表明,這套VM不是針對自動化或企業管理所做的,只有一些基本操作。 官方連結: https://apps.gnome.org/Boxes/ 官方原始碼: https://gitlab.gnome.org/GNOME/gnome-boxes Flathub載點: https://flathub.org/apps/org.gnome.Boxes 對於筆者來說,能裝到VM,代表就有更多的操作空間。如果大家不介意多了一些虛擬層,會太影響效能,其實很多操作可以在VM內使用。例如不需要再用podman,可以直接在VM中使用docker、安裝k8s等。對於效能問題,我們必需要在Steamdeck操作時,至少我們可以在VM中先安裝Arch linux,找回必要的依賴包,編譯我們想要的link lib,再抄回Steamdeck下執行。過程的確比較轉折,但若然Steamdeck這台機器只適合打機的話,就真的很可惜。

Spring Boot 04 - 進入http json api 世代

科技新知
MacauYeah・2024-05-23

本節,我們將會建立一個http服務,提供json api讓程式訪問。 下戴模版 我們跟上節一樣,使用Spring Initializr (Maven) 下載模版,但細節筆者就不再講啦。Dependency主要選擇 Spring Web Spring Boot DevTools 下載後,可以直接運行測試,可以用指令 mvn test 或經IDE運行。Spring會至少測試下能不能成功取用預設的8080端口。 Controller 我們若要實作 http json api,需要在 spring 中加入一個類,附註為 @RestController ,那方便起見,類名我們也命名為 XXXController 吧。作為示範,我們弄一個 HomeController.java ,裏面有最常見的 http GET, POST功能。 // src/main/java/io/github/macauyeah/springboot/tutorial/springbootwebapibasic/controller/HomeController.java import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; // ... other import @RestController @RequestMapping("/api") public class HomeController { @GetMapping("/someRecord/{uuid}") public Map readSomeRecord(@PathVariable String uuid) { return Map.of("ret", "your uuid:" + uuid); } @PostMapping("/someRecord") public Map createSomeRecord(@RequestBody Map requestBody) { HashMap ret = new HashMap(requestBody); ret.put("ret", "got your request"); return ret; } } HomeController裏,完整的URL 其實為: GET http://localhost:8080/api/someRecord/{uuid} POST http://localhost:8080/api/someRecord URL中的api之後的路徑,都是定義在 HomeController 中,而前半的8080及context path,是使用預設值。在正式環境下,可能隨時會被重新定義。但我們做本地測試,只需要驗證預設值就可以了。 我們真的運行起程式mvn clean compile spring-boot:run,再使用最簡測試工具進行測試。Windows的朋友,可以選擇Postman作為測試,它有圖形介面。而linux的朋友,請用curl,預設安裝都會有。下列為方便表示測試參數,筆者選用curl。 測試GET,其中1234會自動對應到spring裏的uuid。 curl http://localhost:8080/api/someRecord/1234 # return {"ret":"your uuid:1234"} 測試 POST,其中的 -d 參數,會對應 spring裏的 @RequestBody, -H 參數則是設定 http header 的意思,我們就使用約定俗成的 json 作為 header 。 curl -X POST http://localhost:8080/api/someRecord -H "Content-Type: application/json" -d '{"requst":"did you get it?"}' # return {"requst":"did you get it?","ret":"got your request"} 上面的兩個操作,都回傳了我們輸入的資訊,這代表了我們成功用spring架起了http json api,而且正常讀入資訊。 Test Case 雖然我們可以正常地架起 api,但每次開發都要 postman / curl這種工具額外試一次,其實也有一些成本。而且 api 數量變大,或經多次修改後,就重複人手執行,就變得相當討厭。 面對這個問題,筆者會建議寫測試用例,即是Test Case,而且用Spring內置的@SpringBootTest來寫。 產生一個空的Test類,vscode中,最簡單可以Source Action => Generate Test,然後加入這次要測試的參數。 // src/test/java/io/github/macauyeah/springboot/tutorial/springbootwebapibasic/controller/HomeControllerTest.java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @SpringBootTest @AutoConfigureMockMvc public class HomeControllerTest { @Autowired private MockMvc mockMvc; @Test void testGetSomeRecord() throws Exception { RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/api/someRecord/1234") .contentType(MediaType.APPLICATION_JSON); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").value("your uuid:1234")) .andDo(MockMvcResultHandlers.print()); } @Test void testPostSomeRecord() throws Exception { String request = """ {"requst":"did you get it?"} """; RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/api/someRecord") .contentType(MediaType.APPLICATION_JSON) .content(request); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.jsonPath("$.requst").value("did you get it?")) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").value("got your request")) .andDo(MockMvcResultHandlers.print()); } } 最後就是執行 mvn test 或經IDE運行,應該都會得到所有測試都通過的結果。 mvn test # other test result ... [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.368 s -- in io.github.macauyeah.springboot.tutorial.springbootwebapibasic.controller.HomeControllerTest # other test result ... 上面的程式碼很多,我們逐一來。 @SpringBootTest 寫在類的外面,代表執行這個測試類時,需要運行起整個Spring程序,當然也包括http的部份。 @AutoConfigureMockMvc 寫在類的外面,代表執行這個測試類時,可以模擬一些發向自己的 http 請求。 @Autowired private MockMvc mockMvc 寫在類的裏面,因為之前有定義了可以模擬 http 的請求,Spring在運行時為大家提供了那個所謂的模擬http client的實例。 MockMvcRequestBuilders,則是建造要測試的URL及Header參數。 MockMvcResultMatchers,則是檢查回傳的結果是否如遇期的一樣。 為何這個http client叫模擬 - Mock ? 因為在測試用例中,可能連Controller 內部依賴組件也需要進一步模擬,這樣才能把測試目標集中在Controller裏,這也是單元測試的原意。只是本次的例子看不出模擬與否的差別。 MockMvcResultMatchers.jsonPath(),這是用來檢測json的結構是否跟預期一樣。有些網路上的其他例子會簡寫成 jsonPath() ,但因為vscode IDE的自動import功能比較差,筆者還是保留傳統的寫法。 如果大家覺得@SpringBootTest很難,想折衷地把其他測試方法,那麼把 postman / curl好好管理起來,每次修改完程式,都完整地執行一次 postman / curl ,也可以達到測試的效果。只不過大家還是要好好學會整合 postman / curl,知道如何檢測json結構,什麼時候有錯,什麼時候叫測試通過,所以也要花一樣功夫來實現。 最後,大家千萬不要因為測試難寫而逃課,因為寫測試絕對地可以減輕日後重執行的工作量。除非你的程式碼即用即棄,否則都建議寫測試。(測試跟寫文檔不一樣,有了測試也不能沒有文檔。好消息的是,文檔現在越來越多自動生成的工具,我們日後再找機會介紹。) Source Code spring boot web api basic

Ubuntu 24.04 試用報告-更新

科技新知
MacauYeah・2024-05-21

上期為大家介紹了一些ubuntu docker, multipass的一些改動。本期再繼續介紹一些其他的更新。 apt中的source.list 的位置更新了,格式也更新了,從/etc/apt/sources.list在指向了/etc/apt/sources.list.d/ubuntu.sources,格式變得更親民,就像如下所示 Types: deb URIs: http://mo.archive.ubuntu.com/ubuntu/ Suites: noble noble-updates noble-backports Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg Types: deb URIs: http://security.ubuntu.com/ubuntu/ Suites: noble-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 承上更新,雖然格式好看了,noble-security的部份卻故意折開了。而且在live-cd初次安裝時,大家若要改mirror(鏡像站點),只能修改noble noble-updates noble-backports的位置,noble-security還是會指定在官方的位置。筆者猜測它的用意是針對安全性更新,大家應該要直接訪問官方網站,不要等mirror慢慢更新。此一更新,不單影響ubuntu 24.04,連ubuntu 22.04也受一併折開了,只是22.04還是使用舊版。如果有需要變回統一的方向,減少日後自動化的修改,可以像以下修改 ubuntu 24.04的修改 Types: deb URIs: http://mo.archive.ubuntu.com/ubuntu/ Suites: noble noble-updates noble-backports noble-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg ubuntu 22.04.04的修改,刪除/etc/apt/sources.list,新增/etc/apt/sources.list.d/ubuntu.sources (對齊ubuntu24的位置) Types: deb URIs: http://mo.archive.ubuntu.com/ubuntu/ Suites: jammy jammy-updates jammy -backports jammy-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

軟件發行也需要維修基金?

手機‧電玩
MacauYeah・2024-05-13

筆者參與軟件開發,都己經有好一定年期。面對軟件開發週期,最痛苦的並不是研發階段。好多打機的朋友,可能會以為軟件應該跟遊戲差不多吧,開發完就頂多修BUG,然後全心地投入下一個項目的開發。要持續花時間更新?不可能,微軟不也是幾年要求重買一次新版的Office套裝嗎?幾年也要另外花錢升級OS。概然全部都要另外花錢買,不就是一個全新的項目嗎? 其實除了微軟這種夠大夠惡的龍頭公司外,其他都不是這樣運作的。例如我們現在很常用的手機OS,不論Android, iOS,其實只要硬件支緩,就不需要用戶成本就可以升級的。其內的App應用,也因為手機OS的升級,也要持續升級。所以不論你是哪一層的開發者,好大機會都要一直維護已發佈的軟件版本,好讓它可以在不同環境下運作。而這個維護成本,就看你低層的供應商有多進取、有多佛心。現在基本免費的供應商都會大刀闊斧地改功能。大家要留意,是改功能,不是加功能。也就是有些功能過去有,現在使用模式整個有改變,你不得不重寫自己的軟件。 所以筆者現在最頭痛的是,如何為公司維護這些沒法帶來新收入,而又要不斷支付時間和金錢的訂制軟件。 技術上,一定有很多討論,但在於只關心行政的老闆的角度下,根本聽不懂。在於開發者的角度,也需要很長期的實務經驗才能有好一點的佈署。扣除技術,在本質上,若然各利害關系人都曾經考慮過,大家應該都會有更好的預期。 軟件有生命週期,而且這個重複得越來越快。由開發到發佈穩定版本的時間、人力、金錢最高。因為環境變遷,重回開發的機會越來越多,不斷地重複。 需求狠心地下架過氣軟件。過氣軟件,要麼更新,要麼淘汰。但不是所有軟件都受歡迎,值得投放時間。這個在老闆視角下,他很懂。但老闆通常做不了的是,狠心放棄升級不了的軟件。老闆經常覺得,只要軟件放著不更新,就不會有成本。錯,因為老闆只會記得倉庫中曾經有一個軟件可以做到某個功能,可以給賣給某個客戶。但當你拿出來時,才發現不能直接用,還是很焦急地找人更新。 軟件開發,跟很多其他類型工程很像。不是隨時看看圖表,就可以回憶前世今生。舊軟件要救,要花時間先摸索當初的開發工具、環境,追查問題原因,或許最後可只改一句指令就解問題,但總體成本會令人無法接受。 軟件的可複制性不如以前。很多老闆會認為,你之前開發過一次,抄過來做點少改動,不就可以當一款新的應用嗎?因為原來軟件沒有維護,大部份過氣的軟件,即使你有原始碼,你也未必能找到適合的編譯環境來做改動。想要改動?還是老老實實做先更新。 所以,大家對於軟件維護,應該要像物業管理一樣,要預留一部份費用為維修基金。可能還是有老闆會講,怎麼可以預留到這麼多錢去做維修?所以,筆者更加建議,不是要做一個完美的萬能軟件,要鎖定核心功能。沒化更新的,就放棄、止蝕。

測試驅動開發 | 系統邊界Mock

科技新知
MacauYeah・2024-04-23

好一段日子之前,筆者就介紹了一些寫Test Case的大方向 。對於大部份情況來說,有分隔的開發環境,有整個配套,測試起來是順暢的,想做單元測試可以,做整合測試也可以。但如果沒有,我們其實也要想辦法寫Mock。 Mock這個概念,對於寫前端程式的朋友應該比較熟悉,因為前端開發者總不能等後端準備好之後,才開始慢慢設計。前端很早期就要模擬一些情況,做介面設計,做各種思考。而且這個Mock不是指在運行單元測試時,才使用的臨時修改隨機數據。而是針對開發時,自行模擬的後端或外部環境。不過因為前端介面涉及很多主觀設計,很多元素冇辦法做固定的自動測試,所以前端的測試通常要人幫測試。 而後端開發,邊界Mock這一概念也很有用。在外部環境不足的情況下,為自己系統的邊界部份自建一個Stub / Dummy 等的模疑數據,是很有幫助的。不論我們對外部環境的掌控度有多少,我們走測試驅動開發(Test Driven Development),好好地定義這個外部環境的期待行為是很重要的。 例如,你有個功能,需要存入數據,但資料庫未準備好,也沒有所謂的In Memory資料庫可以用。這時,自己架空寫一個什麼都不做或回傳固定結果的函數作為中轉接口,然後在你的Test Case可以規劃你的想要結果。 也許你會說,這個函數就是存下資料,我不會需要它的回傳結果,但我們其實還是可以在Test case 中定義一些錯誤檢測,確保這個函數沒有Throw Exception 。再進一步想,我們主程式是否真的不負任何儲存失敗的責任?要定義其他回傳變數,方便寫Log讓追蹤?或者我們至少要知道成功後的Primary Key ?若然業務上真的不在乎儲存結果的有效性,我們不存入數據也是可以的? 所以歸根究底,我們還是在乎儲存的成功與否。還是有必要去驗證驗寫入是否成功。 上述例子,因為資料庫不存在,開發途中可能Test Case 有好長一段時間也通過不了,但至少當資料庫完備後,可以直接驗證,不用人手手工測試。 舉另外一個例子,我們要從某個地方,例如API或資料庫,讀取數據。我們也可以先寫中轉接口,並為它寫Test Case定義應有的行為。雖然明明就只是讀取,我們沒法控制太多。但在接口做好異常狀態處理,是很重要的。例如Handle exception、檢查某些重要業務值會不會是空、確保後續部份可以正常使用,這是因為我們不能被外部系統的失誤而導致自身系統癱瘓。 其實測試驅動,本質上就是強逼大家想多一點,好好定義預期的行為,不論內部條件怎樣變化,都有一自動的檢收標準。

Spring Boot 03 - 做好Database的模組化及測試用例

科技新知
MacauYeah・2024-04-12

這節,我們將會使用spring-data-jpa,寫一個業務上的資料庫模組,提供資料表的存取,讓你的好同僚可以直接使用。這樣可以在多模組的環境中,減少同一個資料表在不同地方重複又重複地重定義。將來要更新,也可以使用jar檔的方式發佈。 下戴模版 我們跟上節一樣,使用Spring Initializr (Maven) 下載模版,但細節筆者就不再講啦。Dependency主要選擇 H2 Database Spring Data JPA 對pom.xml作一些微調,並把spring-boot-start-data-jpa,h2改為只在測試中生效。 並把Java檔案搬一搬位置 # old location src/main/java/io/github/macauyeah/springboot/tutorial/springbootdatatest/SpringBootDataTestApplication.java src/main/resources/application.properties # new location src/test/java/io/github/macauyeah/springboot/tutorial/springbootdatatest/SpringBootDataTestApplication.java src/test/resources/application.properties 以上的操作,主要是因為我們的目標是提供Schema,或者叫資料表規格。其他用於做連線的操作,我們不需要打包在jar內。所以把那些次要的東西都放在test資料夾中。我們這時可以先用mvn test指令,確保一切功能還是正常。 Entity folder 然後我們入正題,在pom.xml中加入hibernate-core,spring-data-jpa, 然後在main資料夾下加入 Entity、Repository,例如前述用過的Apple和AppleRepo,最後資料夾就像是這樣。 . |-- pom.xml |-- src | |-- main | | `-- java | | `-- io | | `-- github | | `-- macauyeah | | `-- springboot | | `-- tutorial | | `-- springbootdatatest | | |-- Apple.java | | `-- AppleRepo.java | `-- test | |-- java | | `-- io | | `-- github | | `-- macauyeah | | `-- springboot | | `-- tutorial | | `-- springbootdatatest | | |-- SpringBootDataTestApplication.java | | `-- SpringBootDataTestApplicationTests.java | `-- resources | `-- application.properties 然後我們在Test Case中使用AppleRepo @SpringBootTest class SpringBootDataTestApplicationTests { @Autowired AppleRepo appleRepo; @Test void contextLoads() { Apple apple = new Apple(); apple.setUuid(UUID.randomUUID().toString()); apple.setWeight(100.0); apple.setGravity(1000.0); appleRepo.save(apple); } } 這個跟前述02-spring-data-jpa最大的差別,就是我們的main中只有Entity相關的Class,我們發佈jar,別人引用我們的class,別人不會解發其他不相干的商業邏輯。假如發佈02的例子,因為Spring有自動初始化Component的原因,很可能會誤觸發02中的BasicApplicationRunner.java Source Code spring boot data test

Git Worktree

科技新知
MacauYeah・2024-04-09

看了Git 大神的影片 part two,才知道原來切換git分支還是有不同的做法。傳統中,我們使用git checkout BRANCH_NAME_1 來切換到我們想要的分支。通常這樣做,代表我們放棄原來的工作環境,換到另一個工作環境中。 這樣做很好,對不對? 是的。但有些時候,我們只是被逼離開原本的工作環境,跳到一個過去的分支/節點去查一些東西,或者修正一些東西。更什的是我們原本的工作環境都還是混亂狀態下,我們不想做commit(提交),我們只好用git stash,暫時將工作環境存起,然後再git checkout BRANCH_NAME_1。在你想做的事做完後,再git checkout OLD_BRANCH。 看起來其實也沒有很麻煩,是不是? 但其實當你的專案有一定大小,你在不同版本跳來跳去,你的IDE就會不斷地重新編譯。更不幸的是,當你的不同版本中有模組數量的差異,弱一點的IDE,什至會攪死它的cache,之後就會發生鬼打牆。為解決IDE引發的問題,筆者有時會直接cp -r YOUR_PROJECT TEMP_PROJECT,在一個新資料夾下另起爐灶。那就是有兩個不同的資料夾裝載著你的專案。 這樣應該沒有問題了吧,是不是?這次是真的可以了,扣除了筆者個人健忘的問題,就沒什麼問題了。 不知大家有沒有經驗,連續commit了幾次,但最後一次commit卻忘了push(與伺服器同步),然後就跳到其他地方繼續工作。如果我們在同一個git repository下,我們commit了但忘了push,即使我們git checkout去了其他分支,用git GUI畫出commit graph時,也至少可以提醒筆者有一個未與伺服器同步的分支。但如果當初我們用的是cp,那就沒戲唱了,什至乎當初複制了去哪裏都忘了。(當你老闆同時要你跟多個專案,健忘真的很容易發生。) 這問題有解嗎?有的,git在2.5版本以後,就提供了一個git worktree的指令。它有點像cp 指令,更重要的是,它打通了兩個資料夾下的隱藏資料庫.git,當大家在那兩個資料夾底下,都可以看到另一方的存在。大家可以用git branch -a或git log --oneline --graph來看看。 詳細的指令介紹:git worktree git 大神的影片 Part 2

Git: 何謂MONO Repository

科技新知
MacauYeah・2024-04-02

之前看了一位git大神的演講,提及一個叫MONO Repository的使用情況。後期找資料之後,才發現到這是一個公司成長後的一個重大的挑戰。 何謂MONO Repository git的傳統,就是為每一個獨立的專案,建立一個新的Repository (中譯:倉庫)。這個很直觀,獨立專案,獨立管理。從零開始有很多好處,Repo體積通常會小一點,因為其內的東西都是緊密相關。做更新處理時,維護人員也更清楚自己的影響程度。這種架構方式,就叫Multi Repository。基本上,大家預設也是會走這個模式。 但當公司規模一直變大,多個專案可能不再獨立,各個專案或多或少都有一些關聯性。當任一專案更新,都有機會影響到其他人。如果公司使用Micro Service (微服務),就更有機會提早遇到。每次更新時,要跨專案地找出影響範圍原本就已經不容易,現在每個專案獨立地存放在不同的倉庫中,每個倉庫的更新速度不一樣,想要找到合適的地方、合適的時間點推出更新,更是困難。 所以,就有公司就提出,將所有專案都放在同一個Mono Repository中,方便用工具去檢查更新影響。相比Multi Repository,這樣做還可以保證同一個改動可以發生中同一個Commit中,可以讓跨專案的團隊可以即時合作(強逼修改別人的專案)。但這樣使一定會有很技術問題出現。跨專案團隊不可能每個專案都熟悉,因為不熟悉而引起的副作用一定會有,所以Main / Master分支出現有缺陷的機會提高了。亦有人提出,使用Mono架構,還必要使用trunk base分支模式。也就是那些新功能,雖然要創建分支開發,但亦要盡早整合到Main / Master中。這才能讓不同的團隊盡早知道問題,並解決問題。 除了開發模式更具挑戰外,Mono架構對git的效能也有很大影響。因為多專案混合,Repository的大小基本都會很大。每個git指令都會變慢,所以必需做一些週期性的cache,讓git graph, git status這樣日常操作變得暢順。同樣地,持續整合/發佈需要作出調整。不過這些筆者就不在這邊詳述了,有興趣朋友可以到git 大神的Youtube觀看。 So You Think You Know Git - FOSDEM 2024 註:據筆者的資料搜集,很多大公司(Software龍頭)都有使用Mono Repository去做集中管理。只不過筆者不知道如何Fact check,就不在這裏提了。