最近在做一個有簡歷功能的個人網站。
大學的網頁課程要上傳一個 styled 的 profile。起初兩週我想了許多種方案,比如「直接把之前超前進度的作業[1] 改成亮色系,然後繳出去」、「用 ChatGPT 幫我寫一份 styled 的網頁。」但我覺得都太敷衍,再加上當時有一些事情要處理,所以我就先放著——直到繳交前 3 天。
繳交前夕,我突然有個打算:「要不要試著超前上課進度[2]?」如果作業還繼續用 Vanilla JS 也實在太沒有新意,而且我這時突然想到一個很有意思的主意:做出一個可以分享給別人(也方便自我介紹)的簡歷。
我面試的時候常常被問到「你的作品集在哪裡」、「你的簡歷在哪」。我是有個人形象頁面,但幾乎沒有說明自己的經歷:這也就導致沒有人——包括我自己——知曉我究竟參與了哪些專案、製作了哪些 projects,以及設計了什麼東西。如果能藉由這次作業的機會打造出一個可以分享給別人(也方便自我介紹)的簡歷,那肯定會讓這個作業更有價值。
「做一個好看的簡歷網站」和「用現代前端工程標準製作專案」,便成為這個專案的兩大目的。
專案本身沒有用到傳統意義上的敏捷開發,但我在這個專案實踐了一些敏捷開發的「滾動式」精神。
「滾動式」的概念其實就是「慢慢達成趨近完美」:應該要先專注在核心的部分(比如這次的專案就是以個人介紹為核心),然後再慢慢地把其他的東西(比如作品集、開源貢獻)加進去。重構也是:一開始不用追求到完全的 best practice——細節可以留待日後再來改善,但如果為了細節而導致核心部分的缺失,便是本末倒置。
雖然這麼說,但我仍然想追求完美主義:我很討厭承認一個長得超醜、超難維護的 snippet 是我寫的,我希望可以帶給閱聽者極其完美的體驗。正是因為如此,這次的作業依然是花了相當多的時間重構。可是比起細節,內容的完善明顯比較重要——更何況我因為這個 project 荒廢了太多其他的事情,排除掉學習到的經驗,投入的時間成本其實早就超出了這個專案可以帶來的價值。
「時間安排」,在無論是在專案管理、還是日常生活的方方面面都十分重要。安排任務的優先級、並且專注在完成最核心的事情,為專案的管理之道。我覺得我時間管理一直都做得不太好——想法太多,時間卻是有限的——是時候逼迫自己從無限的想法中切分,而不是讓一個想法佔據掉太多的時間。
寫網站的時候我就有和好幾位朋友共同研討「如何做出舒適的設計」,並且共同討論如何做出優秀的動效設計。這次我就和一位有著不小聲望的專業前端工程師和設計師,共同討論網頁每一處細節的設計。
另外,網站在開發後期也有遇到以及在遇到效能問題時與幾位更專業的前端工程師共同查出問題。在網頁撰寫末尾,我就有遇到一個很嚴重的效能問題:滑到卡片的時候會嚴重掉幀,但 Profile 裡面並沒有指出具體是哪個函數導致問題。後來和朋友討論之後,鎖定是 shadow 動效導致的問題,而在解決之後,效能問題也就臨刃而解。
之前看過一篇文章,說:「Best Practice 是總結出來的,不代表一開始就得導入這些東西。」工程更像是事情在遇到事情或需要擴充時的解決方法,而不是在沒必要的時候就無止境導入的玩意。
另外,不完美是難免的:像是我的 codebase 就重構了數次,即將發起的一次是發現自己違背了 Tailwind CSS 的 Atomic CSS 理念,打算全數改回 Component 的形式。重構是個學習,不要害怕不完美——不完美才能學習,學習才能成長。
除了上面提到的 bad smell 之外,我還打算加上一些更多 GitHub 的元素(比如個人的貢獻狀況、以及貢獻圖表)。不過把 bad smell 做完之後應該就是直接 promote 然後暫時放著了——有更多更重要的專案得做啊。
另外,這次專案我也學到了很多技術層面的知識:Tailwind CSS 的 group modifier、設計理念、Next.js appdir 和 React Server Component 的各種嘗試…… 這次的專案收穫甚至比上一個作業還要多上許多!
希望之後的專案也可以繼續獲取新知,並且鞏固能力 🙂
1 | $ scc |
第一次看到沒有 commit package-lock.json 的 repo
事實上這樣在 library 上沒有什麼問題。Lockfile 的追蹤有個小原則:
假如函式庫有用到諸如 ESLint 的工具,追蹤一下可以避免之後設定開發依賴的麻煩,
所以像 NPM 官方就是推薦 無論如何都追蹤 lockfile。
不過也有預設不推薦在 library 情境下追蹤 lock 的例子,比如 Rust 的 Cargo 套件管理器[2]。不過 Cargo 的開發工具主要都是作為 submodule 安裝在系統中,通常不會跟著 repo 一起追蹤,所以不太適合放在一起比較。
不過要注意:這時候的 lockfile 就不是追給下游應用程式看的,主要是為了自己開發方便。
package.json
通常不是描述固定的版本,而是一個版本區間:舉個例子:你可能在 package.json
裡面描述 vue: "^2.4.0"
,但實際上 NPM 幫你選了 2.7.13
。這個行為是可以預測的,可以參考 NPM 官方的 Semver 計算機:https://semver.npmjs.com。
看起來只有更動 minor version,會發生什麼事情嗎?我之前就有遇過一個 case:某個的 Vuepress 站台沒追蹤 lock,CI 完全靠 npm install 自己選版本。然後朋友發現本機產生出的 blog 樣式,和 CI 產生的 blog 樣式不一樣。後來才發現其實就是本機有 lock 記錄版本;CI 沒有 lock 所以就選了樣式有差異的最新版本。
當然,你也可以 pin 固定的版本(2.7.13
),可是你 pin 的依賴,可能也沒 pin 它的依賴…… 老實說用 lockfile 還是比較保險 XD
所以 app 建議還是追蹤一下。而 library 的話由於 npm install 只認 root 的 package-lock.json,所以理論上追不追蹤都不影響使用者——只影響你自己的 DX 而已 XD
原文撰於 Twitter。另外非常建議看原文 Twitter 討論串,有很多很有價值的討論!
即便是有一定年資、從 Microsoft 傳統資訊詞彙走過來的 developers,也開始選擇用對岸的資訊詞彙,比如「優化」、「行列」、「刷新」了;甚至是直接選擇用英文的資訊詞彙。
我猜是因為 (1) 很多 developers(包括我自己)不覺得這塊是討論的重點,以及 (2) 用傳統的資訊詞彙,有人聽得懂嗎?就以「刷新」為例,很多人其實已經聽不懂「重新整理」了。如果你對一些比較新的開發者說「重新整理」,那他們大概聽不懂——試想把 refresh rate 的「刷新率」改成「重新整理速率」
或許有人會有個疑問:「為何不要開班傳授正確的資訊詞彙?」事實上現在有非常多 詞彙對照表、各種關於詞彙的科普,甚至是專業簡轉繁的工具(新同文堂以及 https://zhconvert.org),但事實上這些工具的使用量似乎是偏低的——大多都只在軟體翻譯領域比較實用。
說實話,就 developers 之間的技術討論,我認為「能互相理解」比「『詞彙』嚴謹度」重要多了。我肯定會和懂這些詞彙的朋友,用嚴謹、正確的詞彙交流;但如果是新進的朋友,又何必為了這些詞彙,讓對方聽不懂、confuse 真正核心的話題?
私認為詞彙就像是個「協定」——有共識、聽得懂,比起選字的嚴謹程度重要不少 (看看 Referer? XD)。選擇冷門協定,通常只會導致對方的誤解。不過我 自己寫的技術文章 還是持續使用傳統的資訊詞彙,亦希望台灣原生的資訊詞彙可以得到更多人的了解(甚至採用)。
]]>原文撰於 Dcard。
「威注音」是一款主打智慧 🤖、美觀 🎨、高自訂性 🔧,且追求完美 的注音輸入法。正在為錯字百出的選字、操作習慣、簡體輸入,甚至是原廠注音遲遲不修正的 bug 所苦嗎?威注音或許可以讓你對注音打字改觀 🥳。
威注音自己覺得有八個值得你使用的理由(建議搭配宣傳海報閱讀):
View post on imgur.com
還再打出錯字擺出的句子,成為朋友對話之間的笑柄、公司討論之間的尷尬嗎 😅?或許你可以試試看威注音輸入法。
威注音輸入法的詞庫收錄了三千餘條(且持續增加中的)常用詞組,因此輸入常用詞彙時可以減少選字的次數 🙌🏼。
目前威注音的詞庫仍在持續更新、拓展,因此威注音將會變得愈來愈智慧。如果你有任何想納入官方詞庫的常用字彙,也可以在這篇底下(或到 GitHub 下)留言提議加入~
覺得每次輸入自己或朋友的名字,或者是科系名稱都得選字很煩嗎?威注音提供了一套非常簡單的自訂詞彙新增方式,簡而言之就是「選字後選取,按 Return 存檔。」可以看看下方影片的操作:
蘋果內建的輸入法「不支援簡體輸出模式」——想像每次跟對岸朋友洽談事情,每一句都得使用特色選單裡面的簡體轉換功能轉換 😱……就算你不煩,觸控板應該也挺累的 😰。
威注音除了提供原生的簡體輸出模式,輸出簡體只要切換輸入法即可一步到位之外,威注音的詞庫也是簡體中文與繁體中文各自一份的:方便簡體中文母語者「在不熟悉台澎金馬的審音與習慣讀音的前提下」也可以順利使用威注音敲字。
P.S.: 威注音的繁體中文輸入模式可以使用「轉換至康熙繁體」「轉換至 JIS 繁體」功能。
我覺得這是威注音和其他競品比較下的最直觀第一感受了。當其他競品的選字窗還停留在 Windows XP 的土裡土氣、甚至沒有暗色模式的時候;威注音的選字窗已經和蘋果原生相當相似,甚至可以啟用「IMK 選字窗」來得到和原生注音同款的介面效果 🧑🎨。
IMK 選字窗可以在威注音的「開發道場」開啟。不過威注音自己做的田所矩陣選字框,我覺得已經很接近蘋果的 UI 效果了。
你想用 Shift 切換中英文嗎?預設開啟 🎉;你只想用「中/英」切換鍵切換語言嗎?也可以關閉 Shift 的切換。
你想要輸入罕見到原廠注音打不出來的字嗎?威注音可以讓你啟用「CNS11643 全字庫」,在安裝正確字體後即可直接打出符合政府編碼標準的罕見字 🎉。
覺得選字窗的候選字太小,看不清楚嗎?威注音支援把選字窗文字放到原先 6 倍大 (96px) 的大小 🎉。
另外,威注音也有很多有趣的功能等你發掘 😇
我在上個月中的時候有在 PTT 發佈一篇比較簡陋的更新日誌,當時有不少網友認為這款輸入法相當值得推薦。
假如你喜歡這個輸入法的話,也很歡迎對這篇貼文點心、留言,並且把文章分享給身邊用 Mac 的朋友,讓這款輸入法能被更多人看見 😄
威注音不僅透明開發、積極維護,而且還會在力所能及的範圍內聽取使用者合理的產品功能建議。
我約莫是在今年 8 月底開始使用威注音的,見證了它從 2.0 到 2.8 的成長。這段期間我也和輸入法作者提出許多建議,而有很大一部分的建議作者都接納並寫出來了。
威注音是一個作者用 ❤️ 發電的專案:上述的所有功能都是免費的——甚至連程式原始碼都一併附給你了。開發者甚至為這個專案倒貼了 Apple 開發者付費帳號的錢,降低使用者安裝上的操作門檻;甚至原作者還啟用了沙盒功能——完全沒有機會取得你電腦的任何資料,而且開放稽核程式碼的每一個部分。我自己就在 2.4 的時代做了一次稽核,結果是「沒有任何證據表明威注音會往外傳送資料」,且「只在使用者自行啟用軟體更新之後,才會向 Gitee 儲存庫請求目前最新版軟體的版本資訊」。
至今威注音的作者仍未開啟贊助管道。如果你想要支援作者的話,有兩種途徑:
假如你想嘗鮮這款輸入法,可以到 GitHub 或 Gitee 下載安裝包:
如果想知道威注音的其他特色和使用說明,可以到官網翻閱:
如果有其他問題(比如不確定怎麼開始安裝、或者是不熟悉某些特殊的用法),歡迎留言。倘若詢問的人很多,我大概會在 Dcard 另寫一篇專文介紹威注音的安裝和使用方式 🧑💻。另外可以追蹤官方的 🐦 Twitter,掌握最新的開發動態喔:@vChewingIME
另外如果喜歡這次的介紹方式,或者是有什麼建議,也都歡迎提出 🤩 ~大致如此!
]]>src: https://twitter.com/saadeghi/status/1571980855991574531?s=61&t=UVzhpuQaGcgZkGpySz_yVQ
This response is from Twitter. src: https://twitter.com/bystartw/status/1572015121295355905?s=61&t=UVzhpuQaGcgZkGpySz_yVQ
As a developer that had tried out Solid, I would still prefer the mainstream tech stack in production. Solid.js’ runtime is neat, while its performance is prominent. However, not many front-end utilities support it natively – for example, Storybook.
I encouraged people to try out new stuff in their hobby projects; however, I won’t use them in production. I afraid the crucial components don’t support this novel framework, and we may have to rewrite this front-end to another framework. That’s boring and time-wasting.
BTW I’m not a omnipotent developer. I had attempted to contribute the Solid ecosystem – for ex., rewriting their transpiler module in Rust (SWC). It turned out that I was not familiar with the development under either Babel or SWC. So I gave up, and switched back to React soon.
]]>原文載於 Twitter 和 Facebook 文章底下的留言。我當時是以技術面探討問題,不過原討論串還有討論到很多層面的問題,值得到
又是一個必須要有 LINE 才能參與的高雄活動
這樣的機制設計真的沒有問題??
個人覺得還真的沒有什麼問題:
Telegram 的話也可以辦到,但相較之下使用量就少很多了;Messenger Bot 也是台灣使用的一個大宗,但之前碰過 FB 的審核機制……麻煩到讓人不是很想碰。
所以 LINE 確實是兼顧大部分使用者,而且成本最低的最優解了。至少這個活動還不是強制性的……對岸沒有 WeChat 幾乎什麼事情都做不了。(甚至包括出門)。
另外上面講的這四個平台(LINE、Telegram、Facebook 跟 WeChat)全部都有不自由的地方,感覺換到哪裡都半斤八兩啦 🌚
src: https://twitter.com/bystartw/status/1568217424658583553?s=61&t=UVzhpuQaGcgZkGpySz_yVQ
Noticing some developers are confused with “Linter” and “Formatter,” I made a simple figure showing what messages Linter and Formatter prefer to print.
Thanks to @nonoesp for his incisive introduction to Linter and Formatter!
]]>src: https://twitter.com/bystartw/status/1567200310430662656?s=61&t=UVzhpuQaGcgZkGpySz_yVQ
vChewing 出了 2.4.0 SP2 了!🎉
自己覺得 vChewing 是除了官方注音之外,長得最好看(因為有 IMK 加持),而且功能跟選字功能也很棒的輸入法,真的很推薦想嘗試第三方輸入法的朋友試試看!(連結見引文)
Figure 1: vChewing 提交很活躍!
另外關於 vChewing (下稱 vC) 的資安或隱私疑慮:
如果有興趣稽核 vChewing 外連的情況,可以自己 clone 回來用 regex 查,或者是直接用這個第三方網站看:
sourcegraph
** 如果還是信不過(認為作者會混淆連結)的話,也可以自己掛一個抓包軟體偵測 vC 的所有請求啦…… 雖然我是找不到除了更新以外的請求 code:sourcegraph
]]>在 威注音 2.1.0 SP1 的更新日誌 下看到了這麼一段話:
Interface Builder 給每個標籤的命名都是隨機的,手動改起來又低效又容易改壞,對多語言本地化而言簡直是天災。
事實上在 WWDC21 中,Apple 就為 Xcode 推出了一項可以解決這項痛點的新功能:「使用編譯器擷取字串」。與早期透過從 Swift 原始碼拉取可翻譯字串,經常漏掉一些字串的方式不同——這個功能會先編譯原始碼,然後從編譯出的檔案中判斷可以翻譯的字串。
這項功能在新專案是預設啟用的,但舊專案可以 opt-in:點開專案設定,進入 “Build Settings”,展開所有功能(All),尋找 “Swift” 然後找到 Localization 的 “Use Compiler to Extract Swift Strings”,將其設定為 Yes 即可。
開啟之後的國際化與本地化方式,跟先前會不太一樣。以往需要手動改 strings 檔案,現在可以先到 “Product” > “Export Localization…” 匯出編譯器擷取出的所有字串:
接下來指定匯出的路徑,然後等待 Swift 完成編譯並擷取字串。若是從舊專案 opt-in,擷取字串的過程中可能會拋出一些錯誤,這個時候就得修正(如果只是警告的話也可以忽略):
接下來就可以進入選擇好的資料夾,使用 Xcode 點開對應語系的 xcloc 檔案:
如果不習慣 Xcode 的本地化工具,也可以打開特色選單 > 「顯示套件內容」,Localized Content 裡面就有通用的 xliff 格式以及一些其他檔案(比如 RTF)。xliff 檔案可以用 Poedit 開啟,也可以上傳到 Crowdin、Transifex、Weblate 等協作翻譯平台:
翻譯流程完成之後,可以到 “Product” > “Import Localization…” 匯入翻譯完成的 xcloc 檔案:
匯入時可能會跳出一些 lint 提示(警告或錯誤),這裡可以視情況修正。若是故意為之,亦可直接匯入:
由於是基於編譯器擷取的結果,因此 strings 檔案也會跟著更新,往後就不需要自己維護 strings 檔案了:
我並不是 Swift 開發者,這個功能是之前在對一些 macOS 軟體本地化時研究發現出來的。這個功能除了讓本地化翻譯員可以完整翻譯所有字串,也可以減少開發者維護 strings 檔案的成本。
希望這篇文章能夠幫助到 Swift 的 I18n 工程師以及翻譯者 :)
]]>src: https://redd.it/wllpa4 (https://t.me/programmer_humor/38462)
> low-level programming
Rust
> game development
Rust
> high performance code
Rust
> scientific programming
…Rust?
> machine learning
…Rust………?
> front-end web development
Rust
> back-end web development
Rust
You only need ONE languages for everything: Rust. You don’t need more.
]]>This article is only for fun, and I don’t want to start a war about it. Don’t take it seriously!
考虑到其他语区的朋友,我使用 繁化姬 将这篇文章全文转换为简体(暂时不包含图档)。简体版是以 commit 89f7c52 为底进行转换,若未来有一些更新,亦会随之同步并在此更新。
希望这篇文章能帮到所有地方的 Asynchronous Rust 初学者 :) 如果未来有机会,也会考虑翻译成英文版本。
https://twitter.com/repsiace/status/1554103778994900992/
修改一下:work stealing, thread-per-core, waker, mpsc, task queue 只有他们懂… 正常人不可能看懂 – @twicemoemoe, 22-08-02
作为一个文科生,其实觉得 async 真的没有想象中的这么困难 ⋯⋯ 😂 或许搭配一些图片会好懂很多吧。
“同步”就是整个程序等一件事情完成(blocking,堵塞)。“异步”则是一件事情还没完成,可以做其他不冲突的事情。
就以早餐来说,“同步”就像是等吐司烤完之后,才去准备花生酱(每件任务循序渐进地运行);“异步”就像是吐司正在烤的时候,就先准备花生酱(每件任务并发地运行)。
刚才有提到“吐司正在烤的时候,就先准备花生酱”是个 并发 行为。
并发 就是指程序架构,任务可以互不相关运行的特性。我们可以称“吐司正在烤”和“准备花生酱”是个独立任务,而我们可以 并发 地进行这些任务。
说到并发,那什么是 平行 呢?
假如你妈也想帮你一起做早餐,而你负责“烤土司、准备酱料”,而你妈负责“煎蛋、摆盘”。你做自己的任务、而你妈做自己的任务——这就是 平行。
回到电脑的例子上。我们可以把“你”和“你妈”比拟为 CPU 内核 (core) ,分配给你和你妈的一大堆独立任务叫做 线程 (thread) 。并发 就是工作单元自己用异步的方式处理任务;平行 就是分配其他工作单元处理任务。
你电脑的内核是有限的。就以 Apple M1 这颗 CPU 来说,最多也就只有 8 个内核。那要怎么高效的把一大堆线程,都分配到这些 CPU 上呢?
我们通常把这个“分配”叫做调度 (scheduling,排程)。首先,最简单的做法就是自己跟系统开线程(通常我们把这种线程叫做 OS thread):
在 Rust 里面,使用 OS thread 是非常简单的:
1 | let handle = std::thread::spawn(|| { |
这样的好处是不需要在程序里面包一个调度器,但缺点是线程的 spawn 生成(把任务分配给家人)和 destroy 销毁(告诉家人不用继续运行任务)都得找你的操作系统(管家)操作。那如果我们可以自己调度,是不是就能减少找操作系统(管家)的开销,甚至是做到更多更高效的事情(比如在一个线程里面运行数个并发任务)?
首先,我们需要先跟管家说“我需要 N 个可以分配家人任务的线程,”然后把这些线程都放进去我们的 thread pool
。接下来程序需要 thread 运行任务,就请 thread pool
分配。而我们从 thread pool
分配到的线程,就是 green thread。
用文字描述太混乱,直接用图解吧:
但假如每个 threads 里面都是会堵住程序的任务 (blocking),那 Thread Pool 里面的 OS Thread 就得等这些任务完成,最后反而没有达到预期的高效分配。因此,如果 threads 里面是完全 sync 的任务,就 没有必要用上 thread pool,让 OS 分配即可。
反之,如果 threads 里面的任务可以并发,因为一个 thread 可以并发运行多个任务,thread pool 的安排就会显得比 OS threads 高效许多:
对底层比较没兴趣?可以先看 Async 函数、区块和 await。
基础理论结束,拉回“任务”和 Rust 本身。你或许会很好奇“怎么让 Rust 知道一个任务是否完成?”
这里就要提到 Rust 的 Future 了。Rust 的 Future 就是一个可并发任务的抽象表示。而 Executor 就是负责轮询 (polling) Futures 的程序。把我们之前的“异步”图解用 Executor 的视角描绘出来:
用文字表达:
poll()
,并观察其回应。Poll::Pending
(处理中),就继续处理下个任务。Poll::Ready(T)
(已完成,回传值是 T),则将回传值交给需求方。但这个文字流程有个问题:poll()
只会运行一次吗?如果会运行数次,那 poll()
下次会在什么时候运行?这里就得提到 Rust 的 waker 机制了。每一次 poll()
,Executor 都会给这个任务一个 context
。里面有一个 waker
,可以用来提醒 Executor“可以运行 poll()
了。”
如果对这方面有兴趣的话,可以参考 https://huangjj27.github.io/async-book/02_execution/03_wakeups.html。另外 Future 还有很多很多的知识点(
Pin
等等),碍于篇幅就先搁置。
Polling(轮询),用程序写出来是像这样的:
1 | loop { |
这样有什么问题?首先异步任务通常要一段时间才会完成,一直 poll()
不会加快运行速度。如果真这样写,会浪费很多 CPU 时间在没必要的 poll()
上。另外,loop {}
是个 blocking 同步函数,这样子写,下一个 Future
是运行不了的。
那换另一种方式呢?
1 | let (tx, rx) = std::sync::mpsc::channel(); |
其实这就相当于在 poll()
阶段回传 Poll::Pending
前调用 wake_by_ref()
——这个方法解决了 loop {}
的问题,但还是没能解决“没必要 poll()
的问题。”
倘若如果我们可以等到作业完成,再运行 wake()
呢?要这么做,我们就得先知道“工作什么时候才完成?”如果任务是用 callback 或 event 告知任务状态的,那就是在收到 event、或 callback 触发进行调用。
延伸阅读:https://huangjj27.github.io/async-book/02_execution/05_io.html
上面介绍了很多 Rust 中 Future
与 Executor
的理论基础,但实务上没有这么麻烦。事实上在 Rust 中,用 async 函数和 block 是非常直觉的。
就以上面的 早餐例子 来说,我们可以把它先改写成这样:
1 | async fn make_breakfast() -> Toast { |
你或许会很疑惑他跟下面这个版本有什么差异:
1 | fn make_breakfast() -> Toast { |
首先,虽然整体上“做早餐”还是循序运行的(先烤完吐司、才准备花生酱),但做早餐这件事情因为已经是异步的了,所以你可以在做早餐的时候做其他事情:
后者的话就是纯 Sync,明显是比前者低效的:
对照图片,或许你发现到 .await
刚好就是“任务切换点”。.await
之后,你可以去做其他事情(而不是空等)。等到烤箱声音响了之后 (wake()
) 之后再回来做剩下的事情。所以整体上 async 函数是比较高效的,但我们要怎么让整个任务更高效呢(在 async 里面一次性运行更多任务?)
刚才提到我们希望在一个 async 里面一次性运行数个任务。这里我们可以借助 tokio
的 join!()
工具宏,表示“我希望这两个任务同时操作”,就像是把这两个任务融合为一了:
1 | async fn make_breakfast() -> Toast { |
这样 make_breakfast
里面就是高效的模式了:
那换一种现实中也常遇到的例子:你希望早餐可以在小孩上学前做完,如果没做完就不要继续做了。所以我们想要设置一个计时器,如果计时到了还没做完就直接取消;反之就继续做:
这种情况就可以用 tokio::select!()
——同时等“做早餐”跟“计时器”,回传完成速度最快的任务(分支),而取消剩下没做完的任务(分支):
1 | // Option 包含“有”或“没有”两种可能。如果计时器到了, |
如果你对这方面很有兴趣,可以看看 https://huangjj27.github.io/async-book/06_multiple_futures/01_chapter.html。
要注意的,是 .await
只能在 async block 或 async function 里面使用。也就是说,你不能在同步函数(包括 main()
)里面调用异步函数:
1 | fn main() { |
既然每个调用者都必须是 async 的,那是谁调用第一个 async 函数呢? 这就得提到 Async Runtime 了。
但这里面的 async 和 await 分别代表什么意义呢?async fn
其实展开来看,就是一个回传 Future 的函数:
1 | struct ReadFileFuture { ... } |
而 await 的大致意思就是“没完成就说整个函数没完成;完成就继续”:
1 | // 把这个函数的 context 转交给 read_to_string |
实际上这部分还有许多地方需要考虑:包括要怎么在下次调用 poll()
的时候,知道现在要继续运行哪个 Future
。更详细的资讯可以参考 https://huangjj27.github.io/async-book/02_execution/02_future.html。
实务上你不需要自己写一个 executor,而是使用现成的 async runtime(运行阶段、运行时)。一个 async runtime 除了 executor 之外,还有提供很多功能(比如上文提及的 thread pool、工具宏和函数,以及文件读写、channel 等等常用功能的异步对应方法)。
常见的 async runtime 有 tokio
、async-std
和 smol
,其中又以 tokio
和 async-std
为大宗。下文会以 tokio
作为介绍示例。
main()
变成 async 函数的起源地我们在〈延伸阅读:await 只能在 async function 里面运行〉里面有提及“所有调用者必须都是 _async function_,”那 main()
呢?
还记得上文有提到“Futures 需要一个 executor 调度?”那 main()
原则上就是配置 runtime,让 runtime 准备 executor 的地方:
1 | fn main() { |
不过其实不用这么麻烦:tokio::main!
这个 属性宏 (attribute macro) 就能帮你写好这方面的初始化了。你只要这样就好:
1 |
|
spawn
还记得并发 (Concurrent) 跟平行 (Parallelism) 吗?虽然大部分的情况下,在 单线程“并发”就已经很足够快了。倘若这个任务耗时很长,你希望开另一条线程“平行”专门处理这个任务,那就可以用 spawn
:
1 | let handle = tokio::task::spawn(async { |
tokio::task::spawn
虽然用起来很像建立 OS thread 的 std::thread::spawn
,但 spawn 里面不要放高耗时的同步函数——除非你乐见整个 runtime 被卡在一件任务上面(或者是直接把 runtime 搞死,直接 panic!)
那要怎么在异步函数里面,开另一个 thread 跑同步函数呢?你可以用接下来会提到的 tokio::task::spawn_blocking
。
spawn_blocking
除了开一个 std::thread::spawn
OS thread 跑这种函数之外,你也可以用tokio::task::spawn_blocking
开一个 可以 await 的同步 blocking 堵塞函数。
1 | let _this_returns_42 = tokio::task::spawn_blocking(|| { |
这样子跑高耗时的函数之时,照样可以运行其他不用堵塞的任务。同理,你也可以把这个套进去 join!
并发完成,可是 这样建立出的 thread 是取消不了的——不只是单纯的 select!
,还包含 .abort()
。因此还是尽量选择并善用异步函数。想深入了解 sync 函数与 async 函数桥接的资讯,可以参考 https://tokio.rs/tokio/topics/bridging。
基本上把这则 Twitter 推文想要知道的大部分知识点都讲了。碍于个人能力不足,或许讲得不够清晰或不甚明确,也十分欢迎各路专家指正 QQ
另外这篇文章花了我 7hr 来写,如果觉得有用的话,欢迎把这篇文章分享给更多对 async 以及 Rust 异步程序有兴趣的人 owo 谢谢 🙏
另外也可以 follow 我的 GitHub 支持我的 OSS 工作 ouo
https://twitter.com/repsiace/status/1554103778994900992/
修改一下:work stealing, thread-per-core, waker, mpsc, task queue 只有他们懂… 正常人不可能看懂 – @twicemoemoe, 22-08-02
作為一個文組,其實覺得 async 真的沒有想像中的這麼困難 ⋯⋯ 😂 或許搭配一些圖片會好懂很多吧。
「同步」就是整個程式等一件事情完成(blocking,堵塞)。「非同步」則是一件事情還沒完成,可以做其他不衝突的事情。
就以早餐來說,「同步」就像是等吐司烤完之後,才去準備花生醬(每件任務循序漸進地執行);「非同步」就像是吐司正在烤的時候,就先準備花生醬(每件任務並行地執行)。
剛才有提到「吐司正在烤的時候,就先準備花生醬」是個 並行 行為。
並行 就是指程式架構,任務可以互不相關執行的特性。我們可以稱「吐司正在烤」和「準備花生醬」是個獨立任務,而我們可以 並行 地進行這些任務。
說到並行,那什麼是 平行 呢?
假如你媽也想幫你一起做早餐,而你負責「烤土司、準備醬料」,而你媽負責「煎蛋、擺盤」。你做自己的任務、而你媽做自己的任務——這就是 平行。
回到電腦的例子上。我們可以把「你」和「你媽」比擬為 CPU 核心 (core) ,分配給你和你媽的一大堆獨立任務叫做 執行緒 (thread) 。並行 就是工作單元自己用非同步的方式處理任務;平行 就是分配其他工作單元處理任務。
你電腦的核心是有限的。就以 Apple M1 這顆 CPU 來說,最多也就只有 8 個核心。那要怎麼高效的把一大堆執行緒,都分配到這些 CPU 上呢?
我們通常把這個「分配」叫做調度 (scheduling,排程)。首先,最簡單的做法就是自己跟系統開執行緒(通常我們把這種執行緒叫做 OS thread):
在 Rust 裡面,使用 OS thread 是非常簡單的:
1 | let handle = std::thread::spawn(|| { |
這樣的好處是不需要在程式裡面包一個調度器,但缺點是執行緒的 spawn 生成(把任務分配給家人)和 destroy 銷毀(告訴家人不用繼續執行任務)都得找你的作業系統(管家)操作。那如果我們可以自己調度,是不是就能減少找作業系統(管家)的開銷,甚至是做到更多更高效的事情(比如在一個執行緒裡面執行數個並行任務)?
首先,我們需要先跟管家說「我需要 N 個可以分配家人任務的執行緒,」然後把這些執行緒都放進去我們的 thread pool
。接下來程式需要 thread 執行任務,就請 thread pool
分配。而我們從 thread pool
分配到的執行緒,就是 green thread。
用文字描述太混亂,直接用圖解吧:
但假如每個 threads 裡面都是會堵住程式的任務 (blocking),那 Thread Pool 裡面的 OS Thread 就得等這些任務完成,最後反而沒有達到預期的高效分配。因此,如果 threads 裡面是完全 sync 的任務,就 沒有必要用上 thread pool,讓 OS 分配即可。
反之,如果 threads 裡面的任務可以並行,因為一個 thread 可以並行執行多個任務,thread pool 的安排就會顯得比 OS threads 高效許多:
對底層比較沒興趣?可以先看 Async 函數、區塊和 await。
基礎理論結束,拉回「任務」和 Rust 本身。你或許會很好奇「怎麼讓 Rust 知道一個任務是否完成?」
這裡就要提到 Rust 的 Future 了。Rust 的 Future 就是一個可並行任務的抽象表示。而 Executor 就是負責輪詢 (polling) Futures 的程式。把我們之前的「非同步」圖解用 Executor 的視角描繪出來:
用文字表達:
poll()
,並觀察其回應。Poll::Pending
(處理中),就繼續處理下個任務。Poll::Ready(T)
(已完成,回傳值是 T),則將回傳值交給需求方。但這個文字流程有個問題:poll()
只會執行一次嗎?如果會執行數次,那 poll()
下次會在什麼時候執行?這裡就得提到 Rust 的 waker 機制了。每一次 poll()
,Executor 都會給這個任務一個 context
。裡面有一個 waker
,可以用來提醒 Executor「可以執行 poll()
了。」
如果對這方面有興趣的話,可以參考 https://weihanglo.tw/async-book/02_execution/03_wakeups.html。另外 Future 還有很多很多的知識點(
Pin
等等),礙於篇幅就先擱置。
Polling(輪詢),用程式寫出來是像這樣的:
1 | loop { |
這樣有什麼問題?首先非同步任務通常要一段時間才會完成,一直 poll()
不會加快執行速度。如果真這樣寫,會浪費很多 CPU 時間在沒必要的 poll()
上。另外,loop {}
是個 blocking 同步函數,這樣子寫,下一個 Future
是執行不了的。
那換另一種方式呢?
1 | let (tx, rx) = std::sync::mpsc::channel(); |
其實這就相當於在 poll()
階段回傳 Poll::Pending
前呼叫 wake_by_ref()
——這個方法解決了 loop {}
的問題,但還是沒能解決「沒必要 poll()
的問題。」
倘若如果我們可以等到作業完成,再執行 wake()
呢?要這麼做,我們就得先知道「工作什麼時候才完成?」如果任務是用 callback 或 event 告知任務狀態的,那就是在收到 event、或 callback 觸發進行呼叫。
延伸閱讀:https://weihanglo.tw/async-book/02_execution/05_io.html
上面介紹了很多 Rust 中 Future
與 Executor
的理論基礎,但實務上沒有這麼麻煩。事實上在 Rust 中,用 async 函數和 block 是非常直覺的。
就以上面的 早餐例子 來說,我們可以把它先改寫成這樣:
1 | async fn make_breakfast() -> Toast { |
你或許會很疑惑他跟下面這個版本有什麼差異:
1 | fn make_breakfast() -> Toast { |
首先,雖然整體上「做早餐」還是循序執行的(先烤完吐司、才準備花生醬),但做早餐這件事情因為已經是非同步的了,所以你可以在做早餐的時候做其他事情:
後者的話就是純 Sync,明顯是比前者低效的:
對照圖片,或許你發現到 .await
剛好就是「任務切換點」。.await
之後,你可以去做其他事情(而不是空等)。等到烤箱聲音響了之後 (wake()
) 之後再回來做剩下的事情。所以整體上 async 函數是比較高效的,但我們要怎麼讓整個任務更高效呢(在 async 裡面一次性執行更多任務?)
剛才提到我們希望在一個 async 裡面一次性執行數個任務。這裡我們可以借助 tokio
的 join!()
工具巨集,表示「我希望這兩個任務同時操作」,就像是把這兩個任務融合為一了:
1 | async fn make_breakfast() -> Toast { |
這樣 make_breakfast
裡面就是高效的模式了:
那換一種現實中也常遇到的例子:你希望早餐可以在小孩上學前做完,如果沒做完就不要繼續做了。所以我們想要設置一個計時器,如果計時到了還沒做完就直接取消;反之就繼續做:
這種情況就可以用 tokio::select!()
——同時等「做早餐」跟「計時器」,回傳完成速度最快的任務(分支),而取消剩下沒做完的任務(分支):
1 | // Option 包含「有」或「沒有」兩種可能。如果計時器到了, |
如果你對這方面很有興趣,可以看看 https://weihanglo.tw/async-book/06_multiple_futures/01_chapter.html。
要注意的,是 .await
只能在 async block 或 async function 裡面使用。也就是說,你不能在同步函數(包括 main()
)裡面呼叫非同步函數:
1 | fn main() { |
既然每個呼叫者都必須是 async 的,那是誰呼叫第一個 async 函數呢? 這就得提到 Async Runtime 了。
但這裡面的 async 和 await 分別代表什麼意義呢?async fn
其實展開來看,就是一個回傳 Future 的函數:
1 | struct ReadFileFuture { ... } |
而 await 的大致意思就是「沒完成就說整個函數沒完成;完成就繼續」:
1 | // 把這個函數的 context 轉交給 read_to_string |
實際上這部分還有許多地方需要考慮:包括要怎麼在下次呼叫 poll()
的時候,知道現在要繼續執行哪個 Future
。更詳細的資訊可以參考 https://weihanglo.tw/async-book/02_execution/02_future.html。
實務上你不需要自己寫一個 executor,而是使用現成的 async runtime(執行階段、執行時)。一個 async runtime 除了 executor 之外,還有提供很多功能(比如上文提及的 thread pool、工具巨集和函數,以及檔案讀寫、channel 等等常用功能的非同步對應方法)。
常見的 async runtime 有 tokio
、async-std
和 smol
,其中又以 tokio
和 async-std
為大宗。下文會以 tokio
作為介紹範例。
main()
變成 async 函數的起源地我們在〈延伸閱讀:await 只能在 async function 裡面執行〉裡面有提及「所有呼叫者必須都是 _async function_,」那 main()
呢?
還記得上文有提到「Futures 需要一個 executor 調度?」那 main()
原則上就是配置 runtime,讓 runtime 準備 executor 的地方:
1 | fn main() { |
不過其實不用這麼麻煩:tokio::main!
這個 屬性巨集 (attribute macro) 就能幫你寫好這方面的初始化了。你只要這樣就好:
1 |
|
spawn
還記得並行 (Concurrent) 跟平行 (Parallelism) 嗎?雖然大部分的情況下,在 單執行緒「並行」就已經很足夠快了。倘若這個任務耗時很長,你希望開另一條執行緒「平行」專門處理這個任務,那就可以用 spawn
:
1 | let handle = tokio::task::spawn(async { |
tokio::task::spawn
雖然用起來很像建立 OS thread 的 std::thread::spawn
,但 spawn 裡面不要放高耗時的同步函數——除非你樂見整個 runtime 被卡在一件任務上面(或者是直接把 runtime 搞死,直接 panic!)
那要怎麼在非同步函數裡面,開另一個 thread 跑同步函數呢?你可以用接下來會提到的 tokio::task::spawn_blocking
。
spawn_blocking
除了開一個 std::thread::spawn
OS thread 跑這種函數之外,你也可以用tokio::task::spawn_blocking
開一個 可以 await 的同步 blocking 堵塞函數。
1 | let _this_returns_42 = tokio::task::spawn_blocking(|| { |
這樣子跑高耗時的函數之時,照樣可以執行其他不用堵塞的任務。同理,你也可以把這個套進去 join!
並行完成,可是 這樣建立出的 thread 是取消不了的——不只是單純的 select!
,還包含 .abort()
。因此還是盡量選擇並善用非同步函數。想深入了解 sync 函數與 async 函數橋接的資訊,可以參考 https://tokio.rs/tokio/topics/bridging。
基本上把這則 Twitter 推文想要知道的大部分知識點都講了。礙於個人能力不足,或許講得不夠清晰或不甚明確,也十分歡迎各路專家指正 QQ
另外這篇文章花了我 7hr 來寫,如果覺得有用的話,歡迎把這篇文章分享給更多對 async 以及 Rust 非同步程式有興趣的人 owo 謝謝 🙏
另外也可以 follow 我的 GitHub 支持我的 OSS 工作 ouo
https://blog.init.engineer/posts/BlockedPan93412/
這篇文章僅為行使本人 (Pan93412) 之媒體答辯權。
乾太在未經告知的情況下強行清空我與他的所有通聯紀錄。有鑒於原始訊息證據消失,原文之所有引用皆有杜撰或改寫之可能。即便如此,我仍嘗試推斷原 Context 並給予我這端的解釋。
怎麼不用 Memcached / MariaDB 還有 Redis,不用 docker-compose 或 k8s 感覺很累
這個訊息的內容斷片嚴重,個人認為可能有被斷章取義。這訊息最可能的出處是我詢問乾太有沒有考慮用 Docker 部屬周邊服務,因為手動配置 Redis 跟 MariaDB 的易便性,的確不及直接 docker-compose 部署方便。但這僅為建議 (feature request),我未批評原做法不好。
「不考慮換成 PostageSQL 嗎?聽說效能好一點」
欸?等等,前一句你不是說怎麼不用 MariaDB 嗎?怎麼下一秒問怎麼不考慮換成 PostageSQL 了?
算了,繼續看其他的好了。
不要用斷章取義的訊息推斷訊息情境。本訊息出處已不可考,但或許也是作為一個 feature request 提出。由於 Laravel 的 Eloquent ORM 本來就可以自行切換成喜歡的資料庫後端,這純粹就是提供一個遵循 SQL 標準,在普通情境有更加效能的資料庫系統建議。
你現在的開發模式大概跟 jQuery + Notepad++ 那時代差不多
你們是不是都很喜歡把 IDE 當 VI 寫
這裡的 context,是我發現到乾太在 Blade 範本,利用手動輸入元件路徑的方式取用 Vue 元件,完全不考慮讓 IDE 管理元件的名稱。這部分確實是個調侃,因為無論是現代前端或是後端,都盡可能希望仰賴 IDE 的 auto-completion 而非自己手打,而我又是寫 TypeScript 出身的,本來就會對這塊尤其留意。
縱觀包括這一則訊息的原始 context,我確實站在現代前端工程師的立場,批評了 init.engineer 目前前端開發模式的不足,而乾太也在之中,承認他並不是很了解前端開發的生態。我和乾太畢竟開發的領域和習慣大相徑庭,因此誤會與衝突個人認為是難免的。若乾太認為這種調侃使其不適,我在此遞上十足的歉意。
既然 Blade 可以做 Component,為什麼又需要一個 Vue?
問這問題的當下,我並不了解純靠北工程師的 codebase。因此當我讀到 Blade + Vue 的混合程式碼時,就向乾太詢問了這個混合的必要性。畢竟能用一個渲染模板完成的事情,我們為何要拆成兩個?
因為 Laravel 目錄架構與 Vue 習慣的開發模式不同,我問這句的另一個目的也是為了要把 Vue deprecate 掉。這樣我也比較可以不用為了維護 Vue 元件的名稱而操心。
無論是裝了 Laravel 的 JetBrains PHPStorm,還是裝了一堆 Laravel Plugins 的 VS Code,兩個都看不到這個函數的定義
我畢竟剛入門 PHP/Laravel 開發,因此當我發現乾太可以 auto-complete,而我不行的時候,我就向乾太請教設定 Laravel 相關的事宜。這純粹是一個普通,有些新手級的問題,但乾太的斷章取義,似乎讓整個情境變得像是我在指責 Laravel——我並沒有這個意思。
老實說,我不是很喜歡這個 Codebase 的前端擺放方式,所以我其實得承認,我有點在找碴
這個訊息的情境是這樣的:我原本想要自己開一個 repo,用自己熟悉的架構,獨立寫 init.engineer 的 Front-end。後端 API 的部分,我當時也有積極幫乾太尋找能開發純靠北 API 的夥伴。所以我的確有自己的解決方案,但乾太否決了這個建議,要求我寫在原來的 codebase。既然如此,我就像乾太提出了目前 codebase 開發前端的不足。不過我也得承認:在指出問題的過程中,我欠缺換位思考。在此再向乾太致上十足歉意。
見上,畢竟我原本是想要自己獨立一個 repo 寫前端的。啟動 Laravel 很快樂,但用 Laravel 的架構寫前端就不快樂了 ⋯⋯ 除此之外,乾太將我後來用 Laravel 正式貢獻前端的紀錄完全忽略了,實際上我後來花了幾週的時間,學習 init.engineer 的全端開發方式,並貢獻了乾太所期望的首頁 Milestone。詳情可以見 init-engineer/init.engineer PR#88:
當你把項目下放給這個人後,他雖然會嘗試接觸項目相關的技術,但是整個過程會有許多負面情緒、抱怨,甚至是質疑項目技術。
我承認。
如果對於項目技術不熟悉,會質疑項目是否過時了、難以開發,甚至會有「你現在的開發模式大概跟 jQuery + Notepad++ 那時代差不多」諸如此類的言論,反而不是嘗試去了解相關技術。
與上面的 他雖然會嘗試接觸項目相關的技術
相斥。
本部落格所有文章除特別聲明外,均採用 CC BY-NC-ND 4.0 許可協議。轉載請註明來自 我不會寫程式!
ND 部分我只能確保我想反駁的論點完全取自原文。BY 和 NC 皆已遵循。
有鑒於還沒有人想知道這方面的事情,我先擱置不寫。
原問題在「學霸模擬機」。放在 Blog 純粹是為了更大的排版自由。
究竟該走「已經特選上」,但「未知名聲」的科技大學;還是放棄科大,繼續準備個申,走傳統的私立大學?
我目前是普高生,但對程式實作有濃厚的興趣。目前是幾個知名專案的維護者或共同創辦者,能力也在幾間大學的特殊選才中獲得肯定(書審都過)。
因為數學和自然上的短板,我是社會組,且想要走偏資的資管(數學不重,同時可以滿足我程式探索的興趣)。經過深入比較,我投了包含 高雄科技大學 的 智慧商務系 的幾間科技大學,而我最終也上了。
根據某學校資料,智慧商務系的前身,是偏資訊的 資訊管理系。我有看過課程大綱,且我對這個科系的課程 頗有興趣。而且身為高雄人,若我讀這間科技大學的話,不僅 學費比較省(國立學費通常比私立來得便宜),而且也可以 省下通勤和在外租房的費用。
其實不全然是這間科大的問題。
這間科大特選通過第二階段的有 8 個人(3 正取 / 5 備取)。然而這 8 個人當中,有 5 個人(62.5%)選擇放棄資格。最近我聽聞剩下 3 個人當中,有一個也想要放棄。雖然我確定最後一個人是因為人生規劃而萌生放棄想法,但這也難免會讓我懷疑:「這科系是不是沒人想讀?」
其實我有認識一位就讀本科系的學長,據他所說:「這個科系比較自由,更適合你發展自己想要的東西」。 我不擅長讀言下之意,雖然我喜歡給予比較多自學空間的學校,但他的意思也有沒有可能是 科系課程有點水?
我的學測成績如下:(# = 未考)
國 | 英 | 數 A | 數 B | 社 | 自 |
---|---|---|---|---|---|
12 | 12 | 3 | 8 | 14 | # |
此外英聽 B、APCS 觀念 3 實作 2。使用 https://www.com.tw 進行落點分析,國立和私立的大致情況如下:
我問過其中一位老師,他覺得出外縣市(即便是私立大學)比較能拓展視野,也能獲得比較多的資源。
其他大部分朋友的想法都是「你喜歡哪一個,就去哪一間學校。」我挺滿意現況,但畢竟 我只看過課程大綱,以及聽學長姐的說法。我擔心我會不會因為不夠瞭解這間科系(資訊不對等),而給自己留下遺憾。
此外,根據這個社團的幾篇貼文,普高生走科大是不是 萬不得已 的選擇?即便我本來就想走科大。
如果要做這兩間大學的優劣比較的話:
高科大
個申上普大
我偏好維持現狀(讀高科大),但我仍想請教其他更有經驗的朋友。希望這次詢問可以讓我更清楚該走哪一條路。謝謝 ><
]]>一年又悄悄過去了。這一年的近況又可以寫出一篇文章了。這一年我已經沒有像一年前這麼感性,文筆也退步了許多,所以寫不太出抒情文。因為最近事情比較多,這篇文章被我擱置到 2021 年的最後一天才寫 😅
跟之前一樣,先來看看我去年寫的膠囊。
至於 2020 年 12 月 9 日我忘記是不是寒冬,但是 2020 年 12 月 26 日我感冒了,天氣稍冷而已,半夜三點的溫度是 18 度。
辛苦你了。
新冠肺炎在全球肆虐,台灣其實沒有這麼嚴重,但幾天前北部發生了本土案例,有點怕怕的,天佑台灣。
約半年後,台灣也爆出了挺嚴重的疫情。但在 3 個月之後趨緩了。疫情期間,你體驗到了網課(以及上課多工的快樂),你也寫出了很多至今仍在持續使用的專案,比如 Schweb 和 CSC 簽到系統。
高二的我,也已經比高一當時忙碌上許多。我當年大概幾天就出一個專案,今年的末尾,我卻連一個完整的專案都沒寫出來。覺得是自己退步了,還是要求變高,不願意使用那些唾手可得且容易的 Tech Stack 了呢?
想恭喜你:約半年後的你,開發出了一個總使用人數高達 7K、使用次數高達 200K 的專案——CSCheckin。這個專案讓你開始與各種資訊社群接軌,比如 MOPCON 和 JSDC,並且有很多人因此認識你。高三的我想勸諫你一件事情:「Tech Stack 是一時的,程式思維才是真正重要的。」因此,我選擇用習慣的 Tech Stack 創造更多價值,而不是執著於「哪個 Tech Stack 夠複雜,可以彰顯自己的能力。」
而在 2020 的末尾,我也想寫一個時空膠囊給一年後的自己。
謝謝 ❤️
這一年的你,拿著 Samsung 手機和跟學校借用的 MacBook Air,裝著 macOS Catalina。自己最喜歡的 Tech Stack 是 React.js、TypeScript,後端是 ASP.NET Core。
1 年後,你喜歡的前端 Tech Stack 沒有什麼變化,後端變成 Rust 和 Nest.js 了。你在接下來的一年對微軟感到十分失望,例如 ASP.NET Core(差點)收回 Hot Reload,以及微軟不再積極傾聽社群對於其本地化的建議。裝置方面也沒有什麼大變化,但你在幾天後終於有了自己的 MacBook Pro。
在這一年的最後,你開發了一個自認非常遵循各種工程慣例的機器人——pbot
。不知道你還記不記得呢?😂
不知道 2021 的今天,你跟 YC 是否重溫舊好?買下去 M1 的 MacBook Air 了嗎?
首先 ⋯⋯ 我們兩個都互相解封了,原本是重溫就好了,但相處久,發現個性不合後,關係又冷淡了。我跟 YC 這一年的關係圖可以見下。然後我買了 M1 的 MacBook Pro 💻。
還有,你覺得你有機會上你夢想中的系所嗎?
應該有吧。我正在走「科大特殊選才」跟「申請入學」。此外想問一年後的自己:你最後走了哪一條入學管道?喜歡這個科系嗎?
不知道是變忙,還是變閒了呢?
變很忙。
一年後,文中所提的 VPS 怎麼樣呢?
因為用不太到,退租了。這個 blog 後來改用 Hexo 建,並遷移到 GitHub Pages;上面的匿名發言後來也因此停了一陣子。約莫半年後,我把舊筆電改造成一台 GNU/Linux 主機,並在上面重開了匿名發言。雖然那台舊筆電的效能沒有那台 VPS 強,卻已足夠我的需求。
你還會想追求你的 Apple 全家桶計畫嗎?雖然我對 Apple 全家桶有些許釋懷,但是我還是有點希望能買到 iPhone、iPad 並玩玩看接力和 AirDrop 丟丟樂。
已經沒這麼想了。這個問題,我還是一樣留給一年後的自己。此外,我還有一些問題想問之後的自己:
♥
Best regards,
Yi-Jyun Pan on 2021/12/31.
假設 rust_hello_world
的目錄架構長這樣:(範例源自於我手邊的某個 production 專案)
「crate」概括來說就是一個專案,用 Cargo.toml
區分。每個 mod
都是一個層級,super
就是上個層級,self
就是本層級。上面的圖已經把 crate / super / self 的對應關係寫得很清楚了,以下寫範例:
cli::opt::Opt
想要讀取 LoggingLevel
,路徑可以這樣走:super::super::logger::LoggerLevel
crate::logger::LoggerLevel
cli::opt::Opt
想要讀取 PROG_NAME
,路徑可以這樣走:super::PROG_NAME
crate::cli::PROG_NAME
事實上 Rust 的模組關係也沒這麼複雜。把上面的例子變成目錄:
super
等價於 ..
,self 等價於 .
,而 crate 則類似 /
。
8/27 日晚上,我把一切 App——包括遊戲、休憩用的軟體、聊天軟體甚至是電話都鎖起來。我希望能夠透過隱藏一切無關事物,讓我可以不要被這些軟體干擾甚至吸引。這樣一來,我便可以全然投入在自己的課業中。
理想美好,現實骨感。我失敗了。
電腦我只留下幾個 App:我的 TODO 工作分配軟體、用來統計學習時數的 YPT 軟體、英文字典,和用來查資料的瀏覽器。我利用 macOS 的功能,把這瀏覽器的絕大多數網頁都封死——只留下學習用的網頁。
手機的話也跟電腦一樣,也是一樣封死死,防止我投入「無用」的休憩上。但因為機能上的限制,我的瀏覽器可以瀏覽任何網頁。
其實這個漏洞我知情,但我選擇用意志力想辦法不去瀏覽無關網頁。但我實在是過於輕忽,渾然不知自己的意志力有多麼不堪一擊。
這兩天我試著讀了三科——國文、公民和歷史。
我讀書不喜歡純粹抄答案,並且要求追根究柢——亦即,我會希望我在寫題目前能夠先讀好該章節的內容,接著不希望遇到錯題,遇到錯題也要搞懂——搞懂裡面的每一個詞語。
我首先選擇的是國文。我開始讀《大滿貫》,慢慢把之前沒補齊的作業慢慢重新學起來。但我沒任何讀書技巧:我用了最笨的方法——逐字掃過,catch 重點——去讀這些書。讀了一個小時的國文後,完成一份作業任務之後,我覺得國文讀起來太累,就讀了公民。這時,我其實還有六成的讀書動力。
公民的話有不少圖表,能給我一點學習上的輔助。然而我花了近 3 個小時,只讀了四章。要知道我有 41 個任務要做,3 個小時只完成 4 個任務是多麼沒有性價比的行為。這個時候,我感受到強烈的無力感,卻又想再搏一場。
所以,我改選擇自己稍有信心的歷史。但當我繼續花了 1 個小時讀 1 章,且題目沒有一道會寫時,我信心全碎,發覺自己不是讀書的料。
41 份任務的完成之日,似乎渺渺無期。
在閉關第一天早上,我忍住不去碰任何休憩。結果就在我查資料的時候,我發現 我居然沈溺在新聞和維基百科的世界了。
是的,除了讀書以外,我什麼都可以陷入其中。在我驚醒之時,我早就閱讀了將近一個小時的維基百科條目,和將近半個小時的新聞。
休憩完了,總該起來讀書了吧!當我坐在書桌前,我居然開始玩弄起我手上的筆,甚至開始塗鴉了。
我想盡辦法拉回書本,不久後卻又轉移到另一個事物上。這幾天我始終相信「明天會更好」,明天到來後卻又跟以往幾日一樣頹廢。我終於發現 我不是讀書的料 。
經過這兩天的打擊,我完全失去讀書的動力。在我上網查「怎麼專注讀書」之時,我發現到這問題似乎沒有解答。術業有專攻——每個人都有自己的專長。很明顯,我的專長完全不在學業上。
在我得到這個回答之後,我其實非常無助——畢竟我原本是希望藉著這一次的閉關,讓自己找回讀書的動力,結果我卻因為這次閉關,發現自己不是讀書的料。這一刻,我不再掙扎,在閉關最後一天的晚上,我選擇打開手機僅有的瀏覽器,沈溺在影片的世界。最終,我在跨日之際解開閉關之鎖,眼淚隨之滑落。
直到解鎖之際,我始終沒有完成那些作業;取而代之的,是強烈的無力感。我真的很懷疑我是不是有 ADHD(過動症)之類的東西,以至於我甚至連簡單的 41 樣作業都不能夠專心完成。我看著別人的 YPT 數據,都可以撐超過 1 小時;我自己的 YPT,卻連一個小時都沒撐過。
這一刻,我發現自己是廢物。
]]>是時候退租自己的 Contabo VPS 了。這台主機陪了我 7 個月,陪伴我寫了非常多篇文章,要跟它分離,著實不捨。
然而,我思考了很久,除了放部落格和一些很沒必要的服務之外,我真想不到這筆支出的必要性。很多東西都可以用免費的服務代替,真的沒有必要為了這幾個服務和所謂的情感,每三個月花 500 多續租下去。
這讓我想到「極簡主義」——斷捨離造就乾淨,依依不捨只會佔據空間。我的主機不也一樣——為什麼要因為情感而租沒必要的東西呢?依依不捨也難道不是徒增支出的浪費和主機商資源的浪費?
支出這種東西,理性大於感性會比較好。退租吧。
]]>測試手機是否可以發文。如果可以,那就可以用 Notion 或
HackMD 在手機寫作了!
為什麼 Authouse 最後科展沒有得名?
Authouse 簡言之,就是單純的「資訊顯示站」。說句實話,這種東西已經被「發明」無數遍了,了無新意。我們自認為 Authouse 的特色幾乎都在底層,不過使用者在乎底層嗎?
你說 Authouse 有完整的前後端分離、有完整的 API 設計、底層用了設計模式和 OOP——然後呢?使用者關心的是他能不能操控家居、能不能有什麼更創新的表現。誰會買一個只能看溫度、濕度和 PM2.5 的偵測球?使用者也不會想看程式碼寫得多好看吧。
你說 Authouse 開放原始碼、API 開放、任何人都能寫 Authouse 的擴充程式——然後呢?使用者只想要開箱即用,不會有人想花時間幫你寫程式。即便你的程式碼寫得再好維護,說實話也真的沒人想看;即便你的 API 寫得再怎麼完整,說實話也不會有人想花時間為一個毫無新意的專案開發程式。
第一印象就不好了,何談接下來的使用?
其實這也可以延伸到我的小論文——CircleStream 上面。CircleStream 是一個仿照 Clubhouse,但底層「聲網」是自己打造的程式。我自認為這個「底層自己開發的 Clubhouse」可以很受歡迎,但誰在乎底層啊?最重要的是有沒有創新的功能,不是底層到底寫得多好。這也難怪這篇小論文只有佳作(甲等)——毫無新意的專案,根本不能引人興趣。
我該做的,是不要再花時間琢磨那些很不必要的東西——例如寫測試、例如寫 logging、例如過度使用設計模式。一個程式要真正贏得使用者的喜好,重點絕對不是程式碼寫得多好。相反,程式碼就算寫得再不好,只要功能足夠吸引人,勢必也能帶來流量。我該改進的,不只是程式碼的問題——最重要的是,拋開所有的工程問題,真正用「創意」去贏得使用者的喝采。
共勉之。
同步發表於個人 FB:https://www.facebook.com/pan93412TW/posts/2919325251670369
]]>