當前位置: 妍妍網 > 辦公

入行 14 年,我還是覺得編程很難

2024-06-24辦公

很多年前,當我還是一名電腦專業的大四學生時,整天上網瀏覽各類招聘資訊,想找到一個合適的程式設計師實習崗位。

除了實習崗位外,我偶爾也會點進一些「高級工程師」的招聘帖裏。現在回想起那些貼文,拋開讓人眼花繚亂的技術名詞,我印象最深的就是常出現在第一行的崗位年限要求:「本職位要求 工作經驗 5 年以上 」。

作為一只一天班都沒上過的小菜鳥,這些年限要求在我眼裏簡直長到誇張。不過,望洋興嘆之余,我有時也會在心中暗暗憧憬一下:「五年工作經驗的程式設計師,那該多厲害啊?寫程式碼對於他們來說,是不是像吃飯一樣簡單?」

時光荏苒,一晃十幾年過去了。如今回頭一望,自己也成了一名有著 14 年工作經驗的光榮打工人。在軟體開發行業摸爬滾打這些年後,我發現很多事情,與我在大四時所想象的大不相同,比方說:

隨著經驗增長,編程並不會變簡單太多,「像吃飯一樣簡單」只出現在夢裏 給許多「大計畫」寫程式碼不光沒意思,還很危險,遠不如在 LeetCode 上做一道演算法題有趣 只從技術角度思考問題,成不了好程式設計師,有些東西遠比技術更重要

細想起來,這類關於編程的感觸還有許多。我整理了其中 8 條,寫成了這篇文章。如果其中某些觀點引起了你的共鳴,我會非常高興。

1. 寫程式碼很簡單,但寫好程式碼很難

編程曾經是一項門檻很高的專業技能。從前,一個普通人想學編程,最常見的做法就是透過教材和書本學習。不過大部份編程專業書,十分艱深晦澀,對於初學者來說很不友好。因此不少人在嘗到編程的樂趣前,就早早地半途而廢。

但如今,學編程正在變得越來越容易。學習不再像以前那樣,只能硬啃書本,而是多了許多新途徑。觀看教學視訊、參加 Codecademy [1] 的互動式課程,甚至直接在 CodeCombat [2] 透過玩遊戲來學編程,每個人都能找到適合自己的學習方式。

「媽,我真沒在玩遊戲,我在學編程呢!你看螢幕右邊!」

此外,程式語言也在變得越來越易用。經典的 C 和 Java 不再是大多數初學者的首選,許多更簡單、更易上手的動態型別語言如今大受歡迎,與之相關的 IDE 等工具也變得越來越完善。這些因素進一步降低了編程的學習門檻。

總而言之,編程早已褪去了它的神秘面紗,從只有少數人才能掌握的神秘技能,變成了一門人人皆可學習的普通手藝。

但更低的學習門檻,更友好的程式語言,並不意味著人人都能寫出一手好程式碼。如果你已經工作,參與過一些計畫,那我很想問你一個問題:」你日常接觸的這些計畫的程式碼品質如何?是好程式碼多,還是爛程式碼多?」

不知你會怎麽回答,我先來說說我的答案。

好程式碼還是很少

2010 年,我跳槽到了一家總部位於北京五道口的大型互聯網公司。

加入這家公司前,我只在十人規模的小公司待過,因此,我對新公司在各方面都有著很高的期待,尤其是軟體品質方面。當時,我心裏想的大概是這樣:「這可是支撐了有著千萬使用者量的產品的‘大’計畫,程式碼品質跟之前那些比,肯定有質的飛躍吧!」

等到在新公司工作了一周後,我才發現自己實在是錯得離譜。所謂「大」計畫的程式碼品質同我的預期相去甚遠。開啟 IDE,數百行的函式和神秘的數位字面量比比皆是,開發任何一個小需求都難如登天。

後來,在待過更多公司,接觸了更多軟體計畫後,我總結出一個道理: 不論公司多大、計畫多牛,在實際工作中遇見好程式碼,仍然是小機率事件。

好程式碼有哪些要素?

話說回來,到底怎樣的程式碼才算是好程式碼?在這方面,Martin Fowler 有一句話常被大家參照:

「Any fool can write code that a computer can understand. Good programmers write code that humans can understand.」

「任何傻瓜都能寫出電腦能理解的程式碼。優秀程式設計師寫人類能理解的程式碼。」

我認為它可以作為評價好程式碼的原點:好程式碼一定是可讀、易讀,且容易理解的。寫出好程式碼的第一原則,就是把人類讀者放在第一位。

除了可讀性以外,評價程式碼好壞還有許多其他維度:

貼合程式語言 :是否使用了當前程式語言的推薦寫法?語言特性和語法糖,使用程度是否恰到好處? 易於修改 :程式碼設計是否考慮了未來的需求變更,當變化發生時,程式碼是否容易隨之修改? API 設計合理 :API 設計是否合理,易於使用?好的 API 在簡單場景下使用方便,在高級場景下又可以隨需求擴充套件。 效能夠用 :程式碼效能是否滿足當前業務需求,同時為未來保留了一定提升空間? 避免過度設計 :程式碼是否存在過度設計、過早最佳化的毛病?

總而言之,對於任何層級的程式設計師來說,好程式碼都不是什麽唾手可得的東西。要寫出好程式碼,需要在許多維度上反復權衡、精心設計,最後再加以持續打磨。

既然如此,假如想盡快掌握寫程式碼這門手藝,有捷徑嗎?

寫好程式碼的捷徑

在許多層面上,我認為 編程和寫作非常相似 [3] 。二者都是使用文本和符號來表達思想,只是方式略有不同。

談到寫作,我想問一個關於作家的問題:「你聽說過不讀書的作家嗎?你有沒有聽到過某位作家說,他從來不讀其他人的作品,唯讀自己的東西?」。我猜答案應該是否定的吧。

如果你去查閱相關資料,你會發現許多職業作家的日常生活,就是閱讀和寫作兩件事在不斷迴圈。他們每天會花大量時間閱讀各類文字,然後再寫作。

同樣是「文字工作者」,程式設計師們就很少重視閱讀。但要想快速提升編程能力,閱讀正是不可或缺的重要一環。除了日常工作接觸到的計畫以外,我們應該更多地閱讀那些經典軟體計畫,從中學習 API 設計、模組架構和程式碼編寫的技巧。

不光程式碼和技術文件,最好再定期讀一些電腦方面的專業書,保持閱讀書籍的習慣。在這方面,我認為 Jeff Atwood 在 15 年前寫的文章 "Programmers Don't Read Books -- But You Should(都說程式設計師不讀書——但你應該讀)" [4] ,如今讀來仍不過時。

提升編程能力的捷徑,就藏在 「閱讀 <-> 編程」 這個無盡迴圈裏。

「一個好的程式設計師應該做什麽?」

2. 編程的精髓是「創造」

在程式設計師的日常工作中,有很多事情會讓人充滿成就感,甚至情不自禁地感嘆「編程真美好」。比方說,修復了一個極難定位的 Bug,用新演算法將程式碼效能提升了一倍,等等。但在所有的這類事情當中,沒有任何一件,能和「親手 創造 出一件東西」相比。

當你在編程時,創造新事物的機會實際上隨處可見。因為並非只有釋出一個新軟體,才稱得上是「創造」。寫一個可復用的工具函式、設計一套清晰的數據模型,全都可以歸入「創造」的範疇。

身為程式設計師,保持對「創造」的熱情至關重要。因為它可以幫我們:

更高效地學習 :學習一門新技術,最高效的方式就是用它開發一個真實計畫,在創造的過程中學習,效果最好。 有機會邂逅了不起的東西: 許多改變世界的開源軟體,最初都是作者純粹出於興趣所創造,比如 Linus Torvalds 和 Linux,Guido van Rossum 和 Python。

1989 年的聖誕假期,荷蘭人 Guido van Rossum 敲下了 Python 語言的最初幾行程式碼,Python 最初僅被期望作為 ABC 語言的繼承者,但後來「吞噬」了全世界

雖然「創造」好處多多,程式設計師們也有大把機會去做,但許多人常常缺少一種身為「創造者」的覺悟。就像那個廣為流傳的小故事所說:一位哲學家詢問正在砌磚的工人,有人清楚地知道自己是在建造一座大教堂,有人卻認為自己只是在砌磚。很多程式設計師正是「只見磚塊,不見教堂」。

將自己定位成創造者後,看待事物的方式就會發生天翻地覆的變化。舉個例子,同樣是給 API 增加報錯提示文字,創造者們就能跳出「快速完成需求就好」的思維陷阱,向前一步,追問自己一些更重要的問題:「我想為使用者 創造 什麽樣的產品體驗?怎樣的報錯文字,更能幫助我達成該目標?」

就像任何一個有用的編程模式一樣,「創造者思維」也能成為你的職業生涯的一道巨大推進力。因此,現在就試著問自己一個問題吧——「我的下一份創造會是什麽?」

3. 打造高效試錯的環境至關重要

我曾參與開發過一個互聯網產品,它設計精美,功能豐富,每天都有大量使用者使用。

但就是這麽一個從市場角度看頗為成功的產品,工程品質卻非常糟糕。如果你開啟它的後端計畫,把所有目錄翻個底朝天,都找不到任何一行單元測試程式碼,其他自動化測試流程也是無從談起。而業務邏輯偏偏又十分復雜,最後,計畫程式碼間的意料耦合多如牛毛,開發一個新特性,很容易把舊功能給搞掛。

「在忙啥呢?」 「試著修復我之前修一個問題時搞出來的問題,那問題是我之前解決另一個問題搞出來的,而那個問題又是我……」

因此,計畫每次釋出時,開發和產品同學全都得嚴陣以待,氛圍十分緊張。整個釋出過程也很刺激,緊急回滾時有發生。一個人在這樣的環境中工作,技術成長拋開不談,心理素質肯定能得到極大鍛煉。

編程原本是一件充滿樂趣的工作,但為這樣的計畫編程,樂趣根本無從談起。究竟是什麽奪走了編程的樂趣?

理想的編程體驗≈「刷題」

LeetCode [5] 是一個著名的編程學習網站,上面提供了許多覆蓋各個難度的編程題,大部份與演算法相關。使用者可以選擇自己感興趣的題目,直接在瀏覽器上編寫程式碼(支持十幾種程式語言)並執行。如果透過了全部的測試用例,則算作解答成功。

在 LeetCode 上做題

在 LeetCode 刷題很像在玩遊戲,富有挑戰性,同時也很有趣。整個做題過程,實際完美展現了一種理想化的編程體驗:

關註點分離 :每道題目都是一個獨立個體,同一時間內,開發者可以完全沈浸在一道題目中; 快速獲得精準反饋 :開發者每次調整程式碼後,能透過自動化測試快速獲得結果反饋; 零成本試錯 :寫出的程式碼語法有錯誤、邏輯有問題,沒有任何不良後果,心理負擔小。

不過,螢幕前的你很可能覺得我在說些廢話。

「不然呢?解演算法題、寫小指令碼,不就是這樣的體驗嗎?有啥特別值得說的?」你很可能會繼續補充道,「你知道我們公司的計畫有多復雜嗎?規模超大,模組巨多,你懂我意思嗎?每天服務 ××× 萬人,光資料庫就好幾套,訊息佇列都有三種,開發起來當然要麻煩一點咯!」

確實,全世界的軟體千差萬別,開發起來不可能都像在 LeetCode 上刷題一樣輕松愉快。但這並不意味著,我們不應該努力改善自己身處的編程環境,哪怕只有一點點。

要透過改善環境來提升編程體驗,可用的理念和工具包括:

模組化思想: 妥善設計計畫中的每一個模組,降低耦合,提升正交性 設計原則: 微觀層面上,套用那些經典的設計原則和模式,比如「SOLID」原則 自動化測試: 編寫規範的單元測試,必要時使用 Mock 技術,用自動化測試覆蓋業務關鍵路徑 縮短反饋回路: 切換編譯速度更快的工具,最佳化單測效能,竭盡全力縮短從「改完程式碼」到「獲得反饋」的等待時間 微服務架構: 必要時,將大單體拆分為多個職責各異的微服務,分散復雜度 ……

關註編程環境,刻意創造出允許高效試錯的「程式碼樂園」,讓工作像刷題一樣輕松愉快。是經驗豐富的程式設計師能為自身團隊做出的最好貢獻之一。

4. 避開程式碼完美主義陷阱

在程式碼品質上精益求精是好事,但也要註意別掉進完美主義的陷阱。因為編程不是藝術創作,不鼓勵人們無限度地追求極致。作家大可花上數年打磨一本傳世之作,但程式設計師在程式碼上鉆牛角尖就很有問題。

世間沒有完美的程式碼。大多數時候,你的程式碼只要能滿足當前需求,又為未來擴充套件留了一些空間就夠了。有那麽幾次,我在簡歷上看到候選人給自己打著「程式碼強迫癥」標簽。隔著螢幕,我雖能感受到 TA 對程式碼品質的那份重視,但在我心底,其實更期望 TA 早已將完美主義陷阱遠遠甩在了後頭。

5. 技術很重要,但「人」也許更重要

在軟體開發領域,「單一職責原則」(全稱為 Single responsibility principle,後簡稱為 SRP)是一條非常著名的設計原則。它的定義很簡單,一句話就可以概括:「每個軟體模組應該只有一個被修改的理由」。

單一職責原則:能做到,並不意味著你就該這麽做

要掌握 SRP 原則,關鍵在於搞清楚「被修改的理由」為何物。很顯然,程式是沒有生命的,它自身不能也不需要主動去改變。任何修改程式的理由,都來自與之相關的人,人是導致修改的「罪魁禍首」。

舉個簡單的例子。看看下面這兩個類,其中哪一個違反了 SRP 原則?

1. 一個字典數據類,支持兩類操作:存數據、取數據; 2. 一個員薪資料類,支持兩類操作:更新個人資訊、渲染一張使用者資料卡片圖。

在大多數人眼裏,第一個例子沒問題,但第二個例子卻明顯違反了 SRP 原則。要得出該結論,好像無需任何嚴格的分析和證明,運用一丁點直覺即可。但假如做一些正經分析,第二個例子的可疑之處,在於能為其輕松找出兩個不同的修改理由:

1. 管理員認為資料中的「個人電話」欄位不能有非法號碼,需增加簡單的校驗邏輯 2. 某員工認為資料卡片圖上的「名字」部份太小,希望加大字型

」It is people who request changes. And you don’t want to confuse those people, or yourself, by mixing together the code that many different people care about for different reasons.」 ——「The Single Responsibility Principle」

「是人在要求軟體變更。你絕不想把那些不同人出於不同原因所關心的程式碼混在一起,這樣只會把他們和你自己搞糊塗。」——「單一職責原則」

理解 SRP 原則的關鍵,在於先理解人以及人在軟體開發中所扮演的角色。

再舉一個例子。微服務架構是近些年很火的一個技術話題。但許多人在討論它時,往往只關註技術本身,卻忽視了微服務架構與人之間的關系。

將微服務架構風格與其他東西區分開的關鍵,在於將大單體拆分為獨立的微服務後,不同模組間的邊界可以變得更清晰。跟數百人的團隊一同維護著一個大單體比起來,許多小組織各自維護著獨立的微服務,明顯擁有更高的運作效率。

如果缺少了特定的組織規模(也就是「人」)作為前提,空談微服務的各種技術優勢和那些花活,純屬本末倒置。

技術當然很重要。身為技術人員,那一張張瑰麗的架構圖和獨具匠心的程式碼細節,天然吸引著我們的註意力。但是,也請千萬不要對軟體開發裏的另一個重要因素「人」視而不見。必要時,轉換一下看事情的角度(從「技術」轉向「人」),那樣對你大有裨益。

6. 求知若渴是好事,但也要註意方法

如今人人都在說「終身學習」,而程式設計師是一個尤其需要終身學習的職業。因為電腦技術的叠代更新非常快,某個三年前流行的框架或程式語言,很可能一個月前已經過時。

一分鐘之內會發生什麽事情?Netflix 觀看時間增長 70,000 小時;Snapchat 上有三百萬視訊被觀看;Google 新增兩百四十萬次搜尋;一個 JS 新框架被發明(這條不是真的 🤓)

要在工作中表現得遊刃有余,程式設計師們需要學習的東西非常多,涵蓋各個層面。拿我比較熟悉的後端領域舉例,一位合格的後端工程師至少需要掌握以下這些:

一種或多種後端程式語言 / MySQL 等關聯式資料庫 / Redis 等常見儲存元件 / 設計模式 / 使用者體驗 / 軟體工程 / 編譯原理 / 作業系統 / 網路基礎 / 分布式系統 / …

雖然要學很多,但據我觀察,大部份程式設計師其實都挺愛學習(至少不排斥),因此心態不是問題。不過有的時候,光有「求知若渴」的心態並不夠,學習時,我們尤其需要關註「價效比」。

關註學習價效比

下面這張圖,展示了學習成效和投入之間的關系。

學習成效與投入關系圖,橫軸為學習投入,縱軸為學習成效

從圖中可以看到,在學習的初級階段,投入較少時,所獲得成效增長飛快。但當成效超過某個閾值後,之後再想繼續提升,所需要的學習投入就會呈指數級增長。

正因如此,我建議你在學習任何一項新事物時,先在腦海中想清楚一個問題: 「我應該在上圖中的哪個位置停下來?」 ,而不是悶頭猛學。

知識的海洋浩瀚無邊,有些東西需要我們成年累月的持續學習,不斷精進。也有些東西,蜻蜓點水般學到一些皮毛已綽綽有余。準確判斷並分配自己有限的學習精力,甚至比努力學習本身更重要。

挑選合適的學習資料

有了學習目標後,下一步就是尋找合適的學習資料。在這方面,我想分享一次自己的失敗經歷。

有段時間,我突然對產品互動設計產生了濃厚的興趣,認為自己應該在這方面有所精進。於是,我精心挑選了一本領域內非常經典的專業書: 【About Face 4: 互動設計精髓】 [6] ,將其買回家中,滿懷信心地認為自己的互動設計能力可以迅速獲得提升。

但事與願違,當我捧著那本經典著作時,發現自己連第一章都無法順利讀完——那句老話說的沒錯:「隔行如隔山」。

從這次失敗中,我總結出了一點經驗。那就是學習某項新東西時,我們最好挑選那些更易讀,更適合「門外漢」的學習資料,不要「眼睛大,嘴巴小」,只知道奔著最經典、最權威的資料而去。

回顧之前的經歷,我覺得以下幾本書非常適合門外漢學習使用,價效比極高:

【寫給大家看的設計書】 [7] :設計相關 【點石成金】 [8] :Web 使用者體驗相關 【鳥哥的Linux私房菜】 [9] :Linux 系統相關

也許每個人的內心,都想成為一個博學的人,無所不知,無所不曉。但可供分配的時間的精力總是有限,我們不能,也不需要在所有領域都成為專家。

7. 越早開始寫單元測試越好

我非常非常喜歡單元測試,我認為寫單測這件事,對我的編程生涯影響極大。誇張點說,如果以「開始寫單元測試」作為分界線,把我的職業生涯分割成兩段,後面那段遠比前面那段精彩得多。

寫單測的好處很多,比如單測可以驅動你改善程式碼的設計、可以作為程式碼的一種文件,等等。此外,完善的單元測試還是構建前面提到的 「高效犯錯的環境」 的關鍵。

我已經寫過幾篇關於單測的文章,比如 【有關單元測試的 5 個建議】 [10] 【遊戲「蔚藍山」教我的編程道理】 [11] 。所以在這兒,我不打算再重復一遍。只說一句: 如果到目前為止,你從未試過寫單元測試,或從沒重視過測試,我建議你從明天就開始寫起來。

一般情況下我不測試我的程式碼,但假如測的話,我在生產環境測

8. 程式設計師最大的敵人是什麽?

在大多數程式設計師段子裏,產品經理經常作為反派角色出現。他們口中的計畫需求總是變個不停,一天冒出一個新想法,搞得程式設計師苦不堪言。

客戶每天都在不停修改需求,所以,我們決定在下次釋出前,把這些需求「凍結」起來

在這些段子的烘托下,不斷修改需求的產品經理,仿佛真成了程式設計師們最大的仇敵。似乎只要產品不亂改需求,大家的工作環境馬上就會成為烏托邦。

雖然偶爾吐槽一兩句產品經理很有意思,但我還是想一本正經的說一句:產品經理不是敵人。

因為從某種角度來說,軟體生來就是準備被修改的(不然你猜,軟體為什麽叫「軟」件?)。這樣看來,開發軟體和修建房子完全不同。因為沒人會在建好一棟大樓後說:「讓我們把它推倒重建一遍吧!一樣的樓,但是用的鋼筋和水泥比之前少 30%!」

所以,產品經理以及不穩定的需求不是程式設計師的敵人。並且,能否寫出易於修改、適配變化的程式碼,是區分普通程式設計師和優秀程式設計師的重要標準之一。

那麽,程式設計師們最大的敵人又是什麽呢?

復雜度是最大的敵人

就像【程式碼大全2】中所說:軟體開發的核心問題是管理復雜度。失控的復雜度就是程式設計師最大的敵人。

來看看那些導致計畫復雜度不斷增長的要素:

不斷增加的新功能: 更多的功能等於更多的程式碼,更多的程式碼通常意味著更高的復雜度 對高可用的需求: 為了實作高可用,訊息佇列等額外的技術元件和程式碼被引入 對高效能的需求: 為了提升效能,緩存和相關模組程式碼被引入,部份模組被拆分後,換成高效能語言重寫 一再被推遲的重構 :因計畫排期過於緊張,迫在眉睫的重構被一再推遲,技術債越積越多 忽視自動化測試: 沒人寫單元測試,也沒人關心測試

終有一天,當計畫的復雜度增長到一定程度後,空中會傳來一聲巨響。「咚!」,一個大家不願改、不敢改的「大坑」憑空出現在了所有人的 IDE 中。

猜猜看,究竟是誰挖下了這個坑?

那些在降低復雜度上投入時間的團隊,所負責的軟體計畫更容易成功

減緩復雜度增長的過程

雖然復雜度總是會不可避免地持續增長,但有許多實踐可以減緩該過程。如果每個人都能做到以下這些事,復雜度就有可能被長期控制在合理範圍內:

精通當前程式語言與工具,寫整潔的程式碼 使用合適的設計模式和編程模式 對重復程式碼零容忍,抽象庫和框架 適當運用整潔架構、領域驅動設計思想 編寫詳盡的文件和註釋 編寫規範有效的單元測試 分離那些變動的與不變的

要求看上去很多,但總結起來,核心其實就是一句話: 寫更好的程式碼

寫在最後

2020 年,我在小組內做了一個分享,當時的 PPT 標題是【編程十年後的十個感觸】。將資料分享在公司內網後,有位同事看到,評論說光看 PPT 不過癮,希望我能將其擴充套件成一篇文章,我回復說沒問題。如今 3 年過去了,我總算是兌現了自己的承諾。

當年準備分享材料時,我做完整個 PPT,最後一頁實在不知道該放些啥。於是靈機一動,搞了個純白色的背景,中間打了一行黑體大字: 「十年很短,編程很難」 。如今,第二個十年也已快行至中途,而這句話的後半部份好像對我仍然適用——長進不大,繼續加油 😅。

作者: piglei

References

[1] Codecademy: https://www.codecademy.com/
[2] CodeCombat: https://codecombat.com/
[3] 編程和寫作非常相似: https://www.zlovezl.cn/articles/if-programming-is-writing/
[4] "Programmers Don't Read Books -- But You Should(都說程式設計師不讀書——但你應該讀)": https://blog.codinghorror.com/programmers-dont-read-books-but-you-should/
[5] LeetCode: https://leetcode.cn/
[6] 【About Face 4: 互動設計精髓】: https://book.douban.com/subject/26642302/
[7] 【寫給大家看的設計書】: https://book.douban.com/subject/3323633/
[8] 【點石成金】: https://book.douban.com/subject/1827702/
[9] 【鳥哥的Linux私房菜】: https://book.douban.com/subject/4889838/
[10] 【有關單元測試的 5 個建議】: https://www.zlovezl.cn/articles/5-tips-on-unit-testing/
[11] 【遊戲「蔚藍山」教我的編程道理】: https://www.zlovezl.cn/articles/what-celeste-teaches-me-about-programming/
[12] 【Python 工匠:案例、技巧與工程實踐】: https://book.douban.com/subject/35723705/
[13] 京東購買: https://item.jd.com/13068111.html
[14] 豆瓣書評: https://book.douban.com/subject/35723705/

Crossin的新書【 碼上行動:用ChatGPT學會Python編程 】已經上市了。 本書以ChatGPT為輔助,系統全面地講解了如何掌握Python編程,適合Python零基礎入門的讀者學習。

購買後可加入讀者交流群,Crossin為你開啟陪讀模式,解答你在閱讀本書時的一切疑問。

Crossin的其他書籍:

添加微信 crossin123 ,加入編程教室共同學習 ~

感謝 轉發 點贊 的各位~