許式偉:Go+ 演進之路

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+ 的一個設計哲學: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+ 會是獨一無二的。它的未來,值得我們共同期待。

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章