許式偉:Go+ 演進之路

七牛雲發表於2022-07-15

7 月 10 日,一年一度的 ECUG Con 2022 線上上圓滿舉行。許式偉作為七牛雲 CEO、ECUG 社群發起人、Go+ 語言發明人,為大家來帶了《Go+ 演進之路》的主題演講。以下內容根據演講實錄整理。 
圖片
大家好,歡迎來到 ECUG Con 2022。ECUG 大會從 2007 年開始,到今天已經第 15 個年頭了,我基本每年都會為大家帶來演講。繼上屆大會之後,今年我想和大家繼續分享 Go+ 的相關內容,聊聊 Go+ 的演進之路。我們會談談 Go+ 過去都發生了什麼?我們現在正在做什麼?以及我們未來會怎樣繼續去進行迭代? 
圖片
 一、Go+ 歷史的關鍵節點縱觀 Go+ 的發展歷程,我們大概會分四個關鍵的節點。
圖片
 首先是 v0.5 版本及以前的「史前版本」。因為它當時叫做 qlang,其實和 Go+ 沒有關係,所以叫「史前版本」。我們現在把 qlang 的程式碼,從 Go+ 移到我個人的 GitHub 下面了。之後是 v0.6 到 v0.7 的原型版本,主要是為了讓大家看到 Go+ 到底長什麼樣,因為它和之前的  qlang 有非常大的不同,qlang 是一個指令碼語言,Go+ 實際上是一個靜態型別的語言。此後,我們從這個原型版本出發,讓它能夠更加接近工程的使用。比較重要的里程碑就是去年 v1.0 版本的釋出,Go+ 的目標和程式碼風格被正式確定下來。此後,我們基本上是延續這個目標和它的程式碼風格繼續前進。今年上半年我們釋出了 Go+ 的 1.1 版本,它實際上是 Go+ 的第一個工程化版本,它可以正式用於生產環境。 
圖片
 從 Go+ 1.0 開始,我們首先提出了「三位一體」的概念,即面向工程,STEM 教育和資料科學。 實際上我們談的是全民程式設計,也就是人人都可以學程式設計。在今天我們可以看到,程式設計教育在未來,一定有越來越多的人會把它看作基礎學科,和數學、語文、英語沒有什麼本質區別。這也是為什麼 Go+ 會把 STEM 教育作為非常重要的支撐點。 那麼 Go+ 1.0 都做到了什麼呢?首先是確定了 Go+ 的程式碼風格,它是以命令列風格為基礎,極盡可能去實現低門檻化。我們希望 7 到 8 歲的小朋友,就有能力學 Go+。 另外一個很重要的點,是我們實現了類檔案 Beta 版。它實際上試圖實現物件導向、領域知識表達的低門檻化,也就是現在比較火的低程式碼領域。實際上物件導向雖然是好的東西,有助於對世界的抽象,但是它也帶來了理解上的難度。因此如何去讓這些高階的工程概念低門檻化,Go+ 類檔案是在這方面最重要的探索。 另外 Go+ 1.0 在相容 Go 語法方面取得了突破性的進展,這也是它最後被標為 1.0 的原因。我們在這個版本上,把大部分 Go 語法都實現了比較好的相容性,基本上做到了在 Go 基礎上去做擴充套件這樣一個最底線的目標。 談到 Go+ 的目標,大家可能會有非常多的疑問,實際上從剛才的目標也可以看出,我們非常關注低門檻化。那談到低門檻化,就不得不提 Python 這個語言。Python 的成功,到底告訴人們什麼事情呢? 
圖片
 首先第一個重要的點在於,它告訴我們效能並不是最重要的。雖然大家都比較看重效能,但單從效能來看的話,Python 在指令碼語言裡面我認為只能算二流,它其實並不快。因為效能其實是可以靠時間去解決的。語言的生命週期都非常長,Python 到今年已經有 32 年的歷史了,它的效能問題是有機會可以靠時間來不斷迭代解決的。但語言的特性並不能,每一步語言特性的選擇都是未來的包袱。所以從這個視角來看,也希望大家對待語言,儘量避免唯效能論吧。 第二個點在於,它揭示了對語言來說最重要的是什麼?或者說 Python 為什麼能成功?其實我覺得,目標人群的選擇是非常根因的東西。語言的特性跟目標人群的選擇有關,所以語言特性的選擇最重要。Python 從誕生之初,並沒有給自己資料科學語言的定位,它認為應該讓語言儘量精簡,容易被理解、被學習。它其實是一個少有的低門檻語言,因為在我看來,真正可以稱為低門檻的語言並不多。 我們想一下大家熟知的語言,比如說 Ruby,大家都說它很簡潔,但是其實它有非常多的語言魔法。所以它可能很強大,但是不能稱之為易學習。所以在我心目中,能稱得上是低門檻的語言 BASIC 算一個,面向教學領域的 Scratch 算一個,也就是說其實在低門檻領域進行探索的語言並沒有那麼多,但正因為 Python 面向了低門檻,所以它雖然沒有將自己定位成資料科學語言,卻成了資料科學的王者。這其實蠻諷刺的,因為有非常多專注於資料科學的語言都沒有 Python 這麼成功,我認為這背後有非常深刻的道理。 從全民程式設計這個大的趨勢來說,其實低門檻化是未來語言主流的發展趨勢,Python 恰恰順應了這個大潮流,使得它今天能夠比最初我們看到的還要成功。 那麼 Python 到底還差什麼? 
圖片
 首先從工程的視角來說,Python 這方面比較弱。談 Python 有些人會想到 Go,因為從 Python 轉向 Go 的程式設計師也不少,原因就在於 Go 是設計思想最接近 Python 的工程語言,使用者的心智負擔也是非常低的。但是 Go 語言的設計者心中基本只有工程,大家看 Go 的官網就知道,它最關心的一個詞是 scale。也就是如何實現一個大型的工程,實現一個程式碼量非常龐大,但是仍然在工程師掌控之中的工程。所以它沒有在意低門檻,它在意的是如何去實現工程上的 scale。 所以從這兩方面去看, Go+ 既然是面向全民的程式設計,那麼我們自然會去關注如何把 Go 和 Python 的優勢融在一體,把 Go 的工程能力和 Python 的低門檻化結合。 我們知道工程很龐大,去實現更龐大、更復雜的系統也是我們所關注的,但是 STEM 教育也好,資料科學也好,更關注的還是如何實現低門檻化。所以工程、STEM 教育、資料科學三位一體,實際是工程和低門檻的融合,這就是 Go+ 的目標。 
圖片
 它們融合之後的樣貌,我們可以通過下面 Go+ 的程式碼範例來了解。 下面的這個示例大家可能會想到 Shell 程式設計,很多程式設計師可能會覺得 Shell 指令碼是比較低門檻的。它雖然在實現複雜任務上比較難用,但是它從理解上是大家最熟悉的,我們會看到 Go+ 的語法與 Shell 的非常接近。 
圖片
 我們再看另外一個例子,就是用 Go+ 去做遊戲。下圖實際上是兩個角色的對話,一個非常簡單的遊戲,它的程式碼也是非常簡潔的。這裡我們會看到命令列的影子,比如 onStart 這樣的語句,是說在程式開始的時候,我們應該做什麼。onMsg 是我收到一個訊息以後要做什麼。基本上有 onStart 和 onMsg 這樣的事件機制,以及我們看到裡面的程式碼有 say、有 broadcast(廣播訊息)。整個程式的流程,其實是通過事件加上 say 還有廣播訊息,這樣幾個很基礎的元素組成。 
圖片
 我們可以看到這是兩個角色的對話。第一個角色在程式開始的時候,說你來自哪裡,然後緊接著廣播訊息 1。另一個角色收到了訊息 1 後,他就會說我來自英國。然後他再廣播訊息 2。角色一收到訊息 2 以後,他就會說你們國家的天氣怎麼樣。這樣,整個時序就由訊息驅動,兩個角色之間的對話就形成了。這個程式非常簡潔,通過它我們可以看到 Go+ 在表達自然的語義上,有天然優勢,它的程式碼是非常通俗易懂的。 從這兩個例子中我們也可以看到,Go+ 雖然實際上是 Go 相容的產物,但它的語法或者說建議的最佳實踐風格,和 Go 是有非常大差異的。它甚至比 Python 還要簡潔,因為它選擇了命令列的風格。 這裡我們可以從工程的幾個概念來理解。一是命令,它是一段程式碼的抽象化,最早期的語言如 FORTRAN,它的命令其實是和函式分開的。當然後來所有的高階語言,基本上都把命令和函式合為一體。但在 Go+ 裡,命令和函式從程式碼風格上來說是有差別的,但是它們背後都是函式。 
圖片
 所以在 Go+ 的程式碼風格上,我們選擇了以命令風格為主體。因為命令的理解難度是最低的,小學生就能理解。其次是函式,這個概念初中生基本也就開始接觸,比如三角函式。那結合計算機和數學中的函式,將兩者互相印證,對初中生而言理解起來難度也不算高。基本上只要理解形參和實參的概念就可以了。 在物件導向中,類、方法這些概念,雖然它確實有助於抽象世界,但實際上物件導向程式設計的理解門檻是最高的。所以 Go+ 其實在極力避免讓程式設計師用物件導向的寫法。實際上在背後我們會使用物件導向的一些思想,但在語言語法上,我們儘量避免太過於物件導向化。所以 Go+ 1.0 中我們看到了它基本奠定了 Go+ 的程式碼風格和目標。 
圖片
 既然目標和風格都定了,基本上我們希望的第一件事情,就是能夠實現工程,成為第一個可以用於實際生產環境的版本。為了實現這個目標,從優先順序來說最重要的有兩件事情。一個是對模組 Module 的支援,大家都知道 Go 對 Module 支援是很晚的,而 Go+ 在 1.1 版本基本上就相容了 Go 的模組概念。我們實現了對模組比較完備的支援,和 Go 用起來的體驗是非常一致的。另外一個很重要的特性是,我們實現了 Go 和 Go+ 的混合工程。這對用於生產環境是有非常大的幫助的。因為大部分程式設計師面臨的第一個問題,就是 Go+ 用來做什麼?歷史的工程可能是 Go 寫的,那如何把它轉化成 Go+ 呢?其實不用轉,因為你的 Go 工程,就是 Go+ 的工程,你只需要在上面寫一些 Go+ 的函式就行了。這樣一來,我們就可以非常輕鬆地去把 Go+ 用於生產環境。 接下來是提供 c2go 的預覽,這是為後續版本服務的,它是一個非常難啃的骨頭。我們也在 Go+ 1.1 版本基本把它實現了。Go+ 支援 C,實際上是引用了 c2go 這個專案。我們在這個版本實現了 c2go 最基礎的能力。 
圖片
 我們看 Go+ 的版本演進,如果說 Go+ 1.0 版本是明目標、定風格,那麼 1.1 版本是為了進生產環境。模組也好,Go/Go+ 混合程式設計也好,其實都是在為進入生產環境打基礎。 
圖片
 Go/Go+ 的混合程式設計,正如我剛才提到的,任何一個 Go 工程,只要在其中新增幾個 Go+ 的原始碼,然後去把 go 的命令換成 gop,就可以正常地去做 Go+ 開發了。這實際上把 Go+ 的使用門檻降到了最低。 二、Go+ 當前節點:v1.2.x 
圖片
 以上是 Go+ 的過去,下面我想和大家分享當前 Go+ 正在做的事情,也就是 Go+ v1.2  版本。這個版本我是把它定義為 Go+ 特色化形成的過程。我們預計在今年十二月份將 Go+ v1.2 版本正式釋出。 我們說這個版本是特色化形成的過程,主要有這樣幾個原因。首先是我們在 1.0 版本中引入的類檔案會轉正,結束 Beta 過程。類檔案是 Go+ 裡非常重要的概念。第二個是 c2go,它對 Go+ 後續發展起著至關重要的作用。我們希望 Go+ 的 v1.2 版本能夠讓 c2go 進入工程化,它的實現標誌是至少完成了 sqlite3 的遷移。 除了這兩個很特色的功能外,Go/Go+ 混合程式設計也將得到增強。目前,Go/Go+ 的混合程式設計還不支援呼叫 Go 的泛型。我們知道 Go 的 v1.18 版本,引入了非常重要的特性就是泛型,但現在 Go+ 還不支援 Go 的泛型。那我們在 v1.2 版本也會去支援。我們不是在 Go+ 裡去定義泛型,而是呼叫 Go 的泛型。 基於這樣方式,我們是讓 Go+ 能夠對泛型概念有最小化的能力,因為泛型是一個比較複雜的概念,但我們並不希望 Go+ 變得特別複雜。從長遠來看,Go+ 對泛型實際上持非常開放的態度,也許有一天會全面支援泛型,但我們不會把它看成優先順序很高的東西。如果真需要用泛型,我們希望是通過和 Go 的混合工程來達到。 我們接下來重點講講這兩個特色功能,類檔案和 c2go。 先聊類檔案。類檔案最直白的一個解釋,就是我們用一個檔案去定義一個類。下圖右側就是用 Go 去寫類的方法,想必大家非常熟悉。 
圖片
 我們先定義一個叫 Rect 的結構體,它有長度和寬度兩個成員,那我們再定義面積的成員方法,那就是長度和高度的乘積,這就是一個非常簡單的程式。 用類檔案來實現這個能力的話,程式碼可以見上圖左側。我們基本上看不到任何物件導向的隱藏。我們定義了兩個全域性變數,一個叫寬度,一個叫高度,然後定義一個全域性的方法叫面積,它是這兩個全域性變數的乘積。那這個程式碼比正常的物件導向程式碼看起來要簡潔很多,非常容易被理解。但實際上它這兩個檔案是等價的。因為類檔案最直接的能力,就是負責把一個看起來像是程式導向的程式碼,自動變成一個物件導向的方法。因為它沒有引入任何新的語法,所以其實對中小學生也相對容易,不用去學新的知識。 
圖片
 但類檔案做的並不只這些。實際上它還能夠自定義基類,能夠自定義整個程式執行的框架。我們最近 Go+ 的公眾號也重點在談類檔案,大家可以去看一看。Go+ 類檔案:DSL vs. SDFGo+ 類檔案:ClassFile 機制詳解 
圖片
 提到類檔案,就需要提到 Go+ 的一個設計哲學:Go+ 不支援領域專用語言,也就不是不支援 DSL,但 Go+ 卻對專業領域友好(Specific Domain Friendly)。 為什麼說它是專業領域友好呢?從上文我們的舉例中來看,第一個例子是 Shell 程式設計,或者叫 DevOps 的領域程式設計。我們可以看到 Go+ 的程式碼看起來非常接近於 Shell 程式設計。 
圖片
 實際上 Shell 程式設計更像是 DevOps 的 DSL,我們很少在 Shell 之外去用這個語言,它只用於非常基礎的一些自動化(automation)。但是我們可以看到,Go+ 實際上是可以讓語言本身的程式碼非常接近於 DSL,但實際上它卻不是 DSL,它是非常正宗的 Go+ 語法。 同樣的道理,我們可以看到在更復雜的遊戲程式設計中,Go+ 也是非常的簡潔的。而且它不僅簡潔,更重要的是它同時也非常強大,它能夠去做類似於《植物大戰殭屍》這樣比較複雜的遊戲。當然我們沒有去做 3D 遊戲的引擎,如果要做,那麼它依然是非常精簡的 3D 遊戲的引擎。 
圖片
 正是因為 Go+ 引入了類檔案,讓它的語法看起來非常領域化。這意味著 Go+ 非常善於結合領域的特徵去提煉領域知識。從而使得 Go+ 利於領域的開發。 當然我們類檔案現在還在 Beta 的階段,當前已經在 2D 的遊戲、DevOps 這些領域做了一些實踐。但是畢竟領域是非常多的,對類檔案這個概念來說,最大的挑戰是領域非常多,在有限的幾個領域裡試驗是不夠的,需要有更多的領域來驗證類檔案機制的普適性,判斷它能否適應各個領域。 
圖片
 另外,我們還需要清除 Beta 版本類檔案裡面的一些不必要的約束。比如說,我們當前一個專業的領域只允許有一種工作類,在一些專業領域裡顯然很有可能會需要打破這個約束。所以我們接下來在 v1.2 版本,會去消除掉類似這樣的不必要約束。我們希望能夠讓一個專業領域有多種工作類,如此一來它的普適性就會更強。 v1.2 版本的第二個特色功能,就是剛才提到 c2go。c2go 的語法可能看起來有點像 cgo,但是它和 cgo 完全不可同日而語,cgo 用起來大家吐槽非常多,但是用 c2go 會覺得非常爽,因為我們基本做到了無縫對接 C 語言。
圖片
 首先,C 語言的程式碼是不需要經過額外包裝的,直接就可以由 Go+ 來呼叫。其次,我們讓 C 和 Go+ 的型別系統儘可能一致,極大化地降低了 C 和 Go+ 的對接成本。這樣的話兩者相互操作,型別上基本不用做轉化。比如說在 C 語言裡面的 void,就是無返回值、無引數的一個函式,到了 Go+ 裡,基本上是一個 func()。那這樣一個型別的對映,其實在 cgo 裡面是做不到的。因為 cgo 需要做必要的函式呼叫約定轉化,才能實現這樣的呼叫。最後,我們把 C 翻譯之後,它的資料結構記憶體佈局和程式語義儘可能保持不變。也就是說 C 程式設計師,對 C 的程式碼的常規語義理解仍然是正確的。比如說字串,它是以 '\x00',就是以 0 為結尾的,這些概念到了 Go+ 這邊翻譯以後,其實它仍然是正確的。這樣也會有助大家不至於在語義上出現分歧。 從下圖的例子中我們可以看出,通過 c2go 的方式,我們實現了 C 的簡潔呼叫。 
圖片
 我們可以看到第一句是 import C,但是它和 Go 的語義是完全不同的,在 Go+ 裡它其實是 C/github.com/goplus/libc 的縮寫。那我們可以看出,這個例子中我們呼叫了 2 個 C 函式:printf 和 fprintf,使用了一個 C 變數 stderr。另外還有一個比較有意思的地方,是字串,我們看到在 Go+ 的標準字串前面寫一個 C 字首,就代表 Go+ 裡傳入 C 的字串常量,這實際上是一個 Go+ 的語法。但是有了這個語法以後,會使得在 Go+ 裡呼叫 C 的程式碼會非常精簡,不至於像 cgo 一樣,會有一個從 Go 字串轉化成 C 的字串,並且最後還要釋放它這樣一個過程。 這是一個非常小的例子,但是我們可以看到 c2go 在表達上,是能夠讓大家感覺到好像 C 和 Go 的包是沒有區別的。我們引入 C 的包和引入 Go 的包,基本使用上大差不差。而且在所有的細節上,都會讓大家感覺 C 的模組好像就是 Go 的模組,當然同時也是 Go+ 的模組,這是我們希望能夠達到的最終效果。這也是我們在 Go+ 裡無縫相容 C 的一個邏輯。 當然當前 c2go 還是一個預覽版,它連 Beta 版都算不上。c2go 當前已經完成了 C 語法 99% 以上的相容,它沒有完成的部分,主要在於標準 C 庫的遷移,它的完成度可能只有 5%,處在非常早期的階段。對 c2go 來說,它最主要挑戰首先在於跨平臺。一方面是 C 標準庫(libc)的跨平臺,另一方面如何讓 c2go 對所有的 C 工程都可以輕鬆實現跨平臺能力,同樣是非常重要的能力建設工作。 
圖片
 單從 libc 本身來說,其實無論是 syscall、pthread,都有比較大的工作量。syscall 現在我們已經支援了 mac 版本,但 Linux 和 Windows 還沒有支援,那 pthread 就更不用說了,這是我們接下來工作量最大的一個板塊。所以 c2go 接下來最重要的是整個標準 C 庫的遷移,它本身就是比較龐大的工作。 以上就是當前 Go+ v1.2 版本想要解決的事情,當然還有支援 Go 模板呼叫的小細節,我們就不展開了。基本以上幾個點構成了 Go+ 的特色能力,無論是類檔案,還是對 C 的相容,以及 Go 和 Go+ 的混合工程,都使得 Go+ 有了非常好的底子。 三、Go+ 未來規劃 那麼從 Go+ 的未來規劃來說,大家都知道 Go+ 在談工程和 STEM 教育、資料一體化,實際上到 v1.2 為止,Go+ 在資料科學領域做的事情是相對少的。會有一些非常有限的能力去實現,比如列表解析、range 表示式等等,但談不上體系化,實際上 Go+ 的資料科學技術棧還是沒有的。 所以在 v1.7 這個非常重要的大版本里面,我們希望 Go+ 自身資料科學的技術棧能夠形成。然後到 v2.0 又做了一個大的版本越級,我們希望 v2.0 這個階段,要能夠支援 Python 語法。當然,不是說在 Go+ 裡面去寫 Python。實際上跟支援 C 比較類似,能夠讓 Go+ 無縫地去 import Python 的包,這樣就使得 Python 資料科學領域歷史的積累,都可以無縫變成 Go+ 的資料科學能力。 
圖片
 實際上這兩個內容,都是面向資料科學的。原因在於,從工程和低門檻化的大方向來說,到 v1.2 版本基本上能力已經完備。Go+ 基本上不太會去在語法上花很多精力,這一點 Go+ 和 Go 是有非常相似的哲學,我們認為語法越少越好,而不是越多越好。所以,基本上到了 v1.2 版本以後,Go+ 的語法比較定型了,我們不太會加各種稀奇古怪的語法。 但是資料科學是 Go+ 最後的攻堅戰,它並不是簡單的一個語法創新上能夠解決的問題。Go 的資料科學基礎能力是比較薄的,當然也是因為 Go 本身興起的時間比較短,所以它在服務端的工程實踐居多。 那麼資料科學底子這麼薄的話,應該怎麼辦呢?v1.2 版本把 c2go 能力去工程化以後,為最後的資料科學攻堅戰打下了重要基礎。因為 Python 的基礎是 C,我們如果對 C 做好了相容,相容 Python 就變得更加簡單了。 
圖片
 v1.7 版本我們會關注什麼?它的目標實際上是 Go+ 資料科學技術棧的形成,打造 Go+ 自身的資料科學能力,比如向量、矩陣等一些方面的探索。實際上即使有了這樣的基礎能力,它仍然還是比較單薄的。 
圖片
 如何真正解決這個問題,讓自己站在巨人的肩膀上?我們的設想是通過 c2go 來支援 Python 的資料科學底座(也就是 C 庫那部分),v1.7 版本我們希望能夠把 Python 的 C 庫部分進行相容,從而走上 Go+ 和 Python 資料科學能力生態融合的道路。但是這個階段我們對 Python 本身不會去做太多考慮,主要還是關注 Python 的 C 庫部分。 
圖片
 但是到了 v2.0 版本,我們就開始考慮把 Python 的語法也引入進來,讓 Python 的包自然地成為 Go+ 生態的一部分。我們設想在 v2.0 版本,至少要支援 CPython、NumPy、pandas 這三個工程。第一個是 Python 本身,其餘兩個是最知名的 Python 資料科學工程,有了它們以後,我們認為 Go+ 就具備了對資料科學的基礎生態能力。 這裡為大家展示這三個工程的程式碼,列一下程式碼行的結構。我們看 Python 本身,大部分底座的程式碼是 C,但是有 65% 的 Python 的程式碼其實主要是一些標準庫,這個是很容易理解的。但是最核心的能力都是 C 完成的,佔 30% 多。 
圖片
 第二個就是 NumPy,NumPy 其實也是一樣的做法,它最核心的能力就是 C 寫的,還有少量的 C++。但是在這個基礎上迭加了一個工具包,是用 Python 自己寫的。從這個程式碼行可以看到,佔比基本在 6:3、6:4 的樣子。 
圖片
 pandas 結構有非常大的差異,它本身大部分程式碼是 Python 寫的,C 的部分比較少。基本上到了 v2.0 版本才能比較好地支援 pandas,v1.7 版本基本上還不太能支援 pandas,但已經可以支援 NumPy 了。 
圖片
 總結一下 Go+ 的演進之路,我們的目標是實現工程、STEM 教育、資料科學的三位一體。這主要還是因為我們未來的語言,主流趨勢是面向全民程式設計,也就是人人都可以學程式設計。實際上這個目標很難,但它是未來語言發展的主流趨勢。目前鮮有語言在面向這樣的趨勢努力,Go+ 可以認為是第一個。 
圖片
 今年內,Go+ 在工程化和低門檻融合的探索就將告一段落。從明年開始,我們將對資料科學發起最後的攻堅戰。大家都知道,Go+ 誕生之初我們就在談資料科學。但是實際真正去執行的時候,資料科學反而放到最後一點。 原因在於資料科學真的是比較難的一件事情,對 Go 來說它的距離比較遠。但是我們基本上有了 c2go 的基礎,就會發現它對我們日後去做好資料科學,會產生很重要的支撐作用。 最後,我相信有了資料科學的支撐,Go+ 會是獨一無二的。它的未來,值得我們共同期待。 
圖片

相關文章