├── main.go ├── Vagrantfile ├── .gitignore ├── docs ├── day01.md ├── day24.md ├── day17.md ├── day14.md ├── day21.md ├── day06.md ├── day03.md ├── day15.md ├── day22.md ├── day05.md ├── day18.md ├── day02.md ├── day13.md ├── day16.md ├── day23.md ├── day11.md ├── day10.md ├── day12.md ├── day09.md ├── day28.md ├── day25.md ├── day20.md ├── day26.md ├── day07.md ├── day30.md ├── day04.md ├── day19.md ├── day08.md ├── day27.md └── day29.md ├── README.md └── LICENSE /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Hello, 世界") 7 | } 8 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "ubuntu/xenial64" 6 | end 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Global/Temp.gitignore 2 | *~ 3 | *.swp 4 | 5 | # Global/OSX.gitignore 6 | .DS_Store 7 | 8 | # Thumbnails 9 | ._* 10 | 11 | # Files that might appear on external disk 12 | .Spotlight-V100 13 | .Trashes 14 | 15 | # Global/Windows.gitignore 16 | Thumbs.db 17 | Desktop.ini 18 | 19 | # NetBeans project files 20 | /nbproject/* 21 | 22 | # IntelliJ IDEA project files 23 | /.idea 24 | 25 | # Eclipse project files 26 | /.project 27 | /.buildpath 28 | /.settings 29 | 30 | # Vagrant logs 31 | /*.log 32 | -------------------------------------------------------------------------------- /docs/day01.md: -------------------------------------------------------------------------------- 1 | # Let's Golang 2 | 3 | [前言](https://github.com/MilesChou/book-start-golang-30-days) 4 | 5 | [Go][] 是 Google 所開發的程式語言,最近有很多新流行的 Server 端應用都是使用 Go 開發的,如 [Docker][] 或 [Drone CI][] 等;除此之外, Go 語言關鍵字少、程式結構相較簡單、加上內建開發工具(如編譯、測試、文件等等)都很完整,這對新手入門是非常友善的。 6 | 7 | 總和以上這些特質,有越來越多人已經入坑 Go 了,而今天開始,筆者也要入坑了! 8 | 9 | ## 為何選擇 Go 語言? 10 | 11 | 其他語言也有優點,那為何選擇 Go 語言?首先,因為筆者興趣比較偏向後端語言;後端語言很多,而筆者比較想學靜態語言。 12 | 13 | 在候選名單中,有 C/C++ 、 Java 、 Rust ,但看了許多文章,最終選了 Go ,原因是: 14 | 15 | 1. C/C++ 雖然快,但 Go 的開發效率比較高,也比較容易上手 16 | 2. 有討論是說 Java 與 Go 的效能差不多, Go 的程式碼比較少,可維護性自然比較高 17 | 3. Rust 的即時運算效能高,比較適合開發系統或是 GUI 應用程式,但學習曲線較高,相反地 Go 好入門,且適合開發一般的網路應用程式 18 | 4. 更重要的是,後面有 Google 撐腰,相信未來還會有更多應用出爐 19 | 20 | ## 原本的 PHP 呢? 21 | 22 | PHP 是目前筆者最熟悉的語言,當跟一個語言越熟,就越會了解它不適合的情境。眾所皆知的,它效能比較差,即使 7.0 的效能大大超過 5.x ,但還是無法跟 Java 等靜態語言相比;其次是,原生提供的函式庫並不擅長處理多執行緒;最後,大家最常使用的組合是 LAMP ,它非常適合設計成無狀態服務,但同時會反應另一個問題:因每次狀態都是從新建立,包括 DB 連線,那在大流量的情境下,該如何有效管理 DB connection pool 。 23 | 24 | 剛好 Go 能解決上述三個麻煩的問題,這也是我想學 Go 的主要原因。 25 | 26 | ## 三十天目標 27 | 28 | 未來三十天預期會從環境建置開始,一邊學習,一邊使用 Go 寫出幾個簡單的 CLI 應用與 API Server 。 29 | 30 | ## 參考資料 31 | 32 | * [值得注意的程式語言:D、Go、Rust](http://blog.cwchen.tw/programming/2016/12/02/the-languages-worth-noting-d-go-rust/) | Michael Talks 33 | * [D、GO、Rust 誰會在未來取代 C?為什麼?](https://buzzorange.com/techorange/2015/11/18/which-can-replace-language-c/) | TechOrange - BuzzOrange 34 | 35 | [Docker]: https://www.docker.com/ 36 | [Drone CI]: https://drone.io/ 37 | [Go]: https://zh.wikipedia.org/wiki/Go 38 | -------------------------------------------------------------------------------- /docs/day24.md: -------------------------------------------------------------------------------- 1 | # Delivery 2 | 3 | 截至目前為止,應用程式該有基本功能都已經完備了,再來就是最後一哩路了--交付。 4 | 5 | ## 分析 6 | 7 | 交付前必須要經過建置([Build][])的過程,不過畢竟只是 side project ,所以測試暫時先跳過吧!但有一個很重要的任務不能不做--**編譯**。 8 | 9 | Go 的編譯還算簡單,直接下指令 build 就行了 10 | 11 | ``` 12 | $ go build 13 | ``` 14 | 15 | 接著就會出現與專案名稱同名的可執行檔,如 `namer` : 16 | 17 | ``` 18 | $ ls namer 19 | namer 20 | ``` 21 | 22 | 交付的目標,應該直接放在 [GitHub Release](https://github.com/blog/1547-release-your-software) 即可。 23 | 24 | Travis CI 的串接方法可以參考[官方網站](https://docs.travis-ci.com/user/deployment/releases/)。 25 | 26 | Travis CI 上做 cross compile 可以參考 [gimme](https://github.com/travis-ci/gimme#travisyml) 。 27 | 28 | ## 開工 29 | 30 | 首先要先安裝 [travis 指令](https://github.com/travis-ci/travis.rb#installation) ,安裝好應該就能看得到版本號了: 31 | 32 | ``` 33 | $ travis -v 34 | 1.8.8 35 | ``` 36 | 37 | 接著切換專案目錄,執行下面的指令: 38 | 39 | ``` 40 | $ travis setup releases 41 | Detected repository as MilesChou/namer, is this correct? |yes| 42 | Username: 43 | Password for jangconan@gmail.com: 44 | File to Upload: namer 45 | Deploy only from MilesChou/namer? |yes| 46 | Deploy from iron branch? |yes| no 47 | Encrypt API key? |yes| 48 | ``` 49 | 50 | 它會問一些問題,其中記得 `Encrypt API key` 一定要選 yes ,因為這個 key 是機敏資訊。 51 | 52 | 接著調整 `.travis.yml` 描述檔: 53 | 54 | ```yaml 55 | branches: 56 | only: 57 | - master 58 | - /^\d+\.\d+\.\d+$/ 59 | 60 | env: 61 | - GIMME_OS=linux GIMME_ARCH=amd64 62 | - GIMME_OS=darwin GIMME_ARCH=amd64 63 | - GIMME_OS=windows GIMME_ARCH=amd64 64 | 65 | before_deploy: 66 | - go build 67 | 68 | deploy: 69 | provider: releases 70 | api_key: 71 | secure: ENCRYPT_API_KEY 72 | file: namer 73 | skip_cleanup: true 74 | on: 75 | tags: true 76 | ``` 77 | 78 | 一切就緒,當打 tag 的時候,就會觸發 deploy 行為,然後把建置出來的執行檔都交付到 GitHub Release 。 79 | 80 | 程式碼可以參考 [PR Day 24](https://github.com/MilesChou/namer/pull/10) 。 81 | 82 | ## 參考資料 83 | 84 | * [CI 起步走][Build] | CI 從入門到入坑 85 | 86 | [Build]: https://github.com/MilesChou/book-intro-of-ci/blob/release/docs/day06.md 87 | -------------------------------------------------------------------------------- /docs/day17.md: -------------------------------------------------------------------------------- 1 | # Commands and Flags 2 | 3 | 在開始正式寫假文產生器前,我們先來看看哪些子命令和參數是需要定義的。 4 | 5 | ## 分析 6 | 7 | 找了一下套件說明,看起來只要把這個值代入 Command 結構的 slice 即可有子命令: 8 | 9 | ```go 10 | app.Commands = []cli.Command{} 11 | ``` 12 | 13 | 另外還會需要參數,比方說一次想要產生的數量有多少, `app` 和 `Command` 都有一個值域叫 Flags ,只要給它 Flag 結構的 slice 即可: 14 | 15 | ```go 16 | app.Flags = []cli.Flag{ 17 | cli.StringFlag{ 18 | Name: "num", 19 | Value: "10", 20 | Usage: "產生數量", 21 | }, 22 | } 23 | ``` 24 | 25 | 取 Flags 的方法如下: 26 | 27 | ```go 28 | func(c *cli.Context) error { 29 | fmt.Println(c.String("num")) 30 | 31 | return nil 32 | } 33 | ``` 34 | 35 | ## 開工 36 | 37 | 先定義兩個子命令 `generate` 與 `status` ,而 `generate` 定義一個 flags 是 `num` ,另外把它抽出另一個函式初始化: 38 | 39 | ```go 40 | package main 41 | 42 | import ( 43 | "fmt" 44 | "os" 45 | "github.com/urfave/cli" 46 | ) 47 | 48 | func main() { 49 | app := cli.NewApp() 50 | app.Name = "Namer" 51 | app.Commands = commands() 52 | 53 | app.Run(os.Args) 54 | } 55 | 56 | func commands() []cli.Command { 57 | return []cli.Command{ 58 | { 59 | Name: "generate", 60 | Usage: "產生假名", 61 | Flags: []cli.Flag{ 62 | cli.StringFlag{ 63 | Name: "num", 64 | Value: "10", 65 | Usage: "產生數量", 66 | }, 67 | }, 68 | Action: func(c *cli.Context) error { 69 | fmt.Println("Hello Generate") 70 | fmt.Println("Generate " + c.String("num")) 71 | 72 | return nil 73 | }, 74 | }, 75 | { 76 | Name: "status", 77 | Usage: "狀態", 78 | Action: func(c *cli.Context) error { 79 | fmt.Println("Hello Status") 80 | 81 | return nil 82 | }, 83 | }, 84 | } 85 | } 86 | ``` 87 | 88 | ## 展示 89 | 90 | 執行: 91 | 92 | ```bash 93 | $ go run main.go generate 94 | Hello Generate 95 | Generate 10 96 | 97 | $ go run main.go generate --num 1000 98 | Hello Generate 99 | Generate 1000 100 | ``` 101 | 102 | Flags 和 Args 好像只能吃字串,不過這問題並不大,而且也蠻正常的,之後再來解吧。 103 | 104 | 詳細程式可以參考 [PR Day 17](https://github.com/MilesChou/namer/pull/2) -------------------------------------------------------------------------------- /docs/day14.md: -------------------------------------------------------------------------------- 1 | # Method 2 | 3 | 結構可以定義同類型的資料,而同類型的資料通常又會有同類型的行為。因為 Go 有 [Anonymous Function][Day 12] ,有寫過 Javascript 可能第一個想到的解法是這樣: 4 | 5 | ```go 6 | package main 7 | 8 | import "fmt" 9 | 10 | type People struct { 11 | name string 12 | age int 13 | Hello func(other People) string 14 | } 15 | 16 | func main() { 17 | var miles People 18 | 19 | other := People{name: `Someone`} 20 | miles = People{`Miles`, 18, func(other People) string { 21 | return `Hi! ` + other.name + `, I am ` + miles.name 22 | }} 23 | 24 | fmt.Println(miles.Hello(other)) // Hi! Someone, I am Miles 25 | } 26 | ``` 27 | 28 | 但明顯的缺點是,它需要動態給匿名函式的值,且需要閉包才能取得 `miles` 變數,這樣一來函式就很難共用,二來得先宣告 `miles` ,才能把匿名函式指定給 `miles` ,整個程式會變得有點混亂。 29 | 30 | 不如,我們把函式定義改一下: 31 | 32 | ```go 33 | package main 34 | 35 | import "fmt" 36 | 37 | type People struct { 38 | name string 39 | age int 40 | Hello func(self People, other People) string 41 | } 42 | 43 | func main() { 44 | hello := func(self People, other People) string { 45 | return `Hi! ` + other.name + `, I am ` + self.name 46 | } 47 | 48 | other := People{name: `Someone`} 49 | miles := People{`Miles`, 18, hello} 50 | 51 | fmt.Println(miles.Hello(miles, other)) // Hi! I am Miles 52 | } 53 | ``` 54 | 55 | 第一個傳的值就是結構變數,在函式裡就可以使用 self 取得結構的資料。這做法有點類似 [Python 定義類別][]裡的 `self` ,只是需要自己手動傳值。 56 | 57 | 事實上 Go 有提供更好的寫法: 58 | 59 | ```go 60 | package main 61 | 62 | import ( 63 | "fmt" 64 | ) 65 | 66 | type People struct { 67 | name string 68 | age int 69 | } 70 | 71 | func (people People) Hello(other People) string { 72 | return `Hi! ` + other.name + `, I am ` + people.name 73 | } 74 | 75 | func main() { 76 | other := People{name: `Someone`} 77 | miles := People{`Miles`, 18} 78 | 79 | fmt.Println(miles.Hello(other)) // Hi! Someone, I am Miles 80 | } 81 | ``` 82 | 83 | 它的概念是定義一個函式 `func` ,而開頭就宣告這是哪個結構所使用的,以及裡面將會用什麼變數來代表結構本身 `(people People)` (傳值或傳址都可以),後面就跟平常定義函數一樣。 84 | 85 | 這樣的寫法,就會把 `Hello` 函式跟 `People` 綁定在一起,用起來就會非常像物件導向的寫法了。 86 | 87 | ## 參考資料 88 | 89 | * [結構與方法](https://openhome.cc/Gossip/Go/Method.html) 90 | * [Python 定義類別][] 91 | 92 | [Python 定義類別]: https://openhome.cc/Gossip/Python/Class.html 93 | [Day 12]: /docs/day12.md 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **此專案已封存不再修改,內容已轉移至另一個專案 [articles](https://github.com/MilesChou/articles)** 2 | 3 | # 從無到有,使用 Go 開發應用程式 4 | 5 | Go 是最近流行的語言之一,許多知名的工具或服務都使用 Go 開發,如 Docker 、 Drone CI 等。未來 30 天,我將會從安裝 Go 的開發環境開始、到寫應用程式、最後部署 API Server 的過程,完整筆記下來。除了逼迫自己學習外,也希望能讓有緣的朋友也可以順利入門一探 Go 的奧妙。 6 | 7 | ## 前言 8 | 9 | 我熟悉 PHP ,也了解 PHP 效能上的瓶頸。曾經想過該如何優化效能,從改演算法,到參考各文章的建議效能優先寫法,還是無法突破。曾考慮過 [Phalcon](https://phalconphp.com) ,也做了 [Docker Image](https://hub.docker.com/r/mileschou/phalcon) ,但因轉換時間成本過高,加上數據不足無法證明比較快,最後還是沒有應用在工作上。 10 | 11 | 正因如此,才會想學習比較高效能的語言。心中打的算盤是:把複雜運算交由高效能語言處理, Web 則交由 PHP 負責。 12 | 13 | 語言選擇除了 Go 之外,也曾看過 Node 、 Rust 、 Python 等,但最後我選擇了 Go 。除了它有著老爸撐腰,加上使用過許多好用的工具(如 Docker )也是用 Go 撰寫的,因此對 Go 的很有信心。 14 | 15 | 之前也曾經寫過 Hello World 就沒再繼續下去,覺得這樣不行。這次希望自己在這 30 天之中,能成功地寫出一個像樣的東西。 16 | 17 | ## 目錄 18 | 19 | * [Day 1 - Let's Golang](docs/day01.md) 20 | * [Day 2 - Environment](docs/day02.md) 21 | * [Day 3 - Hello World](docs/day03.md) 22 | * [Day 4 - Constants](docs/day04.md) 23 | * [Day 5 - Variables & Constants declarations](docs/day05.md) 24 | * [Day 6 - Predeclared Type](docs/day06.md) 25 | * [Day 7 - Array Type](docs/day07.md) 26 | * [Day 8 - Slice Type](docs/day08.md) 27 | * [Day 9 - Map Type](docs/day09.md) 28 | * [Day 10 - Function declarations](docs/day10.md) 29 | * [Day 11 - First class function](docs/day11.md) 30 | * [Day 12 - Anonymous Function](docs/day12.md) 31 | * [Day 13 - Struct](docs/day13.md) 32 | * [Day 14 - Method](docs/day14.md) 33 | * [Day 15 - Inheritance](docs/day15.md) 34 | * [Day 16 - Dep](docs/day16.md) 35 | * [Day 17 - Commands and Flags](docs/day17.md) 36 | * [Day 18 - Random](docs/day18.md) 37 | * [Day 19 - File](docs/day19.md) 38 | * [Day 20 - YAML](docs/day20.md) 39 | * [Day 21 - Send HTTP Request](docs/day21.md) 40 | * [Day 22 - Parse JSON](docs/day22.md) 41 | * [Day 23 - HTTP Server](docs/day23.md) 42 | * [Day 24 - Delivery](docs/day24.md) 43 | * [Day 25 - Docker](docs/day25.md) 44 | * [Day 26 - Refactoring Name Provider](docs/day26.md) 45 | * [Day 27 - Refactoring Command](docs/day27.md) 46 | * [Day 28 - Add Command Parameters](docs/day28.md) 47 | * [Day 29 - Interface](docs/day29.md) 48 | * [Day 30 - The End](docs/day30.md) 49 | 50 | ## 誌謝 51 | 52 | * 良葛格無私貢獻詳細的[學習筆記](https://openhome.cc/Gossip/Go/index.html)。 53 | -------------------------------------------------------------------------------- /docs/day21.md: -------------------------------------------------------------------------------- 1 | # Send HTTP Request 2 | 3 | 中國字非常深奧,有些字的含意,有時候並不是那麼清楚。還好網路上都能查得到這些資訊。 4 | 5 | 今天要來做如何把查出來的網頁資訊抓下來,也就是常見的 HTTP 協定功能。 6 | 7 | ## 分析 8 | 9 | 首先要先有目標網站,以網址簡單來說的話,[萌典][]應該是好選擇。 10 | 11 | 套件本來想找 Curl ,不過想想還是找簡單的就好: [GoReq](https://github.com/franela/goreq) 12 | 13 | 看說明應該非常簡單: 14 | 15 | ```go 16 | res, err := goreq.Request{ Uri: "http://www.google.com" }.Do() 17 | ``` 18 | 19 | 可以來實作了! 20 | 21 | ## 開工 22 | 23 | 先在 `provider` 裡實作查字典功能: 24 | 25 | ```go 26 | package provider 27 | 28 | import "github.com/franela/goreq" 29 | 30 | const ( 31 | dictionary = `https://www.moedict.tw/` 32 | ) 33 | 34 | func Query(word string) (str string, err error){ 35 | req := goreq.Request{ 36 | Uri: dictionary + string(word), 37 | } 38 | 39 | res, err := req.Do() 40 | if err != nil { 41 | return 42 | } 43 | 44 | return res.Body.ToString() 45 | } 46 | ``` 47 | 48 | 先把查到的網頁回傳出去,明天再來處理正則。 49 | 50 | 而原本 `GenerateCommand` 有點亂,先不要在上面加功能,另外開一個 `QueryCommand` 來呼叫這個 `Query` 方法: 51 | 52 | ```go 53 | package command 54 | 55 | import ( 56 | "fmt" 57 | "github.com/MilesChou/namer/provider" 58 | "github.com/urfave/cli" 59 | ) 60 | 61 | var ( 62 | QueryCommand = cli.Command{ 63 | Name: "query", 64 | Usage: "查詢字典", 65 | Action: func(c *cli.Context) error { 66 | return query(c) 67 | }, 68 | } 69 | ) 70 | 71 | func query(c *cli.Context) error { 72 | str := c.Args().First() 73 | 74 | query, err := provider.Query(str) 75 | 76 | if err != nil { 77 | fmt.Println(err) 78 | return err 79 | } 80 | 81 | fmt.Println(query) 82 | 83 | return nil 84 | } 85 | ``` 86 | 87 | `main.go` 的程式碼也貼一下騙版面: 88 | 89 | ```go 90 | app.Commands = []cli.Command{ 91 | command.GenerateCommand, 92 | command.StatusCommand, 93 | command.QueryCommand, 94 | } 95 | ``` 96 | 97 | 因為依賴有調整,需要下指令更新 lock 檔: 98 | 99 | ``` 100 | $ dep ensure 101 | ``` 102 | 103 | > [YAML][Day 20] 那次也有做,只是忘了提這件事 104 | 105 | ## 展示 106 | 107 | 今天做的東西還蠻單純的,展示如下: 108 | 109 | ``` 110 | $ go run main.go query 字 111 | 112 | ... 113 | 114 | ``` 115 | 116 | 程式碼可以參考 [PR Day 21](https://github.com/MilesChou/namer/pull/7) 117 | 118 | ## 問題 119 | 120 | 發現裡面都是 head 的區塊,沒有 body ,看來要爬這個網站會有點麻煩。 121 | 122 | ## 參考資料 123 | 124 | * [萌典][] 125 | 126 | [萌典]: https://www.moedict.tw 127 | [Day 20]: /docs/day20.md 128 | -------------------------------------------------------------------------------- /docs/day06.md: -------------------------------------------------------------------------------- 1 | # Predeclared Type 2 | 3 | [中華小當家][]的劉昴星曾說過:「鍋子是火燄的化身」,使用鍋子也是中華廚師的必學基礎之一;而在一個程式語言裡,資料型別是資料的化身,同樣也是重要的基礎功。廚師練好基礎功,能煮出佳餚;開發者練好基礎功,才有辦法寫出千變萬化的應用程式。 4 | 5 | Go 的資料型別有 11 種,今天先介紹 *Predeclared Type* ,它們也是「有名稱的型態(Named Type)」。 6 | 7 | ## Boolean types 8 | 9 | 最簡單的型別-- `bool` ,它只有兩個預定義的常數 `true` 和 `false` 。 10 | 11 | ## Numeric types 12 | 13 | `numeric` 型態包含了 integer (整數)、 float(浮點數)、 complex(複數)三種。 14 | 15 | 整數又分帶號 `int` 與不帶號 `uint` 兩類,也可以直接指定大小 `int8` 、 `int16` 、 `int32` 、 `int64` 或是不帶號的 `uint8` 、 `uint16` 、 `uint32` 、 `uint64` ,這些相信一看就知道佔了多少容量(bit)。至於 `int` 和 `uint` 會使用哪一個要看平台實作決定,有可能是 32 bit 也有可能是 64 bit。 16 | 17 | 而另外還有兩個整數型態: `rune` 是 `int32` 的別名, `byte` 是 `int8` 的別名。 18 | 19 | float 有 `float32` 與 `float64` 兩種,但沒有 `float` 。 20 | 21 | complex 則表示複數,以 RE + IMi 的方法表示,如: 22 | 23 | ```go 24 | 10+5i 25 | ``` 26 | 27 | 大小則有分 `complex64` 與 `complex128` 兩種。 28 | 29 | Go 內建的 math 套件提供常數取得各型態的最大值和最小值,除了解整數範圍外,也有助於實作上的判斷。 30 | 31 | 如: 32 | 33 | ```go 34 | package main 35 | 36 | import "fmt" 37 | import "math" 38 | 39 | func main() { 40 | fmt.Println(math.MinInt8) 41 | fmt.Println(math.MaxInt8) 42 | fmt.Println(math.MinInt16) 43 | fmt.Println(math.MaxInt16) 44 | fmt.Println(math.MinInt32) 45 | fmt.Println(math.MaxInt32) 46 | fmt.Println(math.MinInt64) 47 | fmt.Println(math.MaxInt64) 48 | 49 | // Uint 最小值是 0 50 | fmt.Println(math.MaxUint8) 51 | fmt.Println(math.MaxUint16) 52 | fmt.Println(math.MaxUint32) 53 | fmt.Println(math.MaxUint64) 54 | 55 | // Float 的表示是最小非 0 浮點數 56 | fmt.Println(math.SmallestNonzeroFloat32) 57 | fmt.Println(math.MaxFloat32) 58 | fmt.Println(math.SmallestNonzeroFloat64) 59 | fmt.Println(math.MaxFloat64) 60 | } 61 | ``` 62 | 63 | 需要注意的是,不同的數字型態,是不能直接摻在一起操作的。如 int8 不能跟 uint8 相加。另外, int 有可能是 32 位元,但 int 也不能跟 int32 相加。 64 | 65 | ## String types 66 | 67 | Go 語言字串都是 UTF-8 字元集編碼,它可以正常的處理多國語言。字串可以使用雙引號 `"` 或反引號 `` ` `` 定義,也可以相加,如: 68 | 69 | ```go 70 | package main 71 | 72 | import "fmt" 73 | 74 | func main() { 75 | fmt.Println("Hello 雙引號") 76 | fmt.Println(`Hello 反引號`) 77 | fmt.Println("雙引號" + `反引號`) 78 | } 79 | ``` 80 | 81 | ## 今日回顧 82 | 83 | 今天是蹲馬步的基本功,後面其他型態將會使用這些基本型態炒出各式各樣的菜色。 84 | 85 | ## 參考資料 86 | 87 | * [The Go Programming Language Specification](https://golang.org/ref/spec#Types) 88 | * [認識預定義型態](https://openhome.cc/Gossip/Go/PreDeclaredType.html) 89 | 90 | [中華小當家]: https://zh.wikipedia.org/wiki/%E4%B8%AD%E8%8F%AF%E5%B0%8F%E7%95%B6%E5%AE%B6 91 | -------------------------------------------------------------------------------- /docs/day03.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | 學習程式的第一隻程式當然就是 Hello World 了,[官方首頁][]有提供 Hello World 原始碼: 4 | 5 | ```go 6 | package main 7 | 8 | import "fmt" 9 | 10 | func main() { 11 | fmt.Println("Hello, 世界") 12 | } 13 | ``` 14 | 15 | > 未來有原始碼,都將會在[這個專案](https://github.com/MilesChou/book-start-golang-30-days)裡更新。 16 | 17 | 討論原始碼細節前,我們先想辦法讓它可以在[昨天][]建好的環境執行。我們先建個目錄,在裡面新增一個檔案叫 `main.go` ,然後切換到目錄,把上面的內容輸入到檔案裡,接著下 `go run main.go` : 18 | 19 | $ mkdir -p /path/to/helloworld 20 | $ cd /path/to/helloworld 21 | # 輸入程式碼 22 | $ vim main.go 23 | # 執行程式碼 24 | $ go run main.go 25 | Hello, 世界 26 | 27 | 順利的話,應該就會如上面的範例一樣,看到 `Hello, 世界` 。恭喜你,寫出第一隻 Go 程式了。 28 | 29 | ## 這之中到底做了什麼呢? 30 | 31 | ### go run 32 | 33 | 首先從指令開始看起: 34 | 35 | $ go run main.go 36 | Hello, 世界 37 | 38 | 指令 `go run` 所做的事正是直譯,也就是直接拿原始碼編譯,同時執行。 39 | 40 | ### package 41 | 42 | 接著來看原始碼: 43 | 44 | ```go 45 | package main 46 | ``` 47 | 48 | 第一行 `package` 指的是定義套件名稱。每個 `.go` 原始碼開頭都必須要宣告 `package` 。 49 | 50 | `main` 套件是有特殊意義的套件名,它是程式的起始點。執行程式的時候,將會從 main 套件開始。 51 | 52 | 可以試著把 main 名字換成其他名字,再執行一次,將會出現錯誤訊息: 53 | 54 | $ go run main.go 55 | go run: cannot run non-main package 56 | 57 | 它說,不能跑非 main 的套件。這個概念與大多數 PHP 框架的 `index.php` 類似,是所有 request 的起始點。 58 | 59 | ### import 60 | 61 | 緊接著這行程式碼: 62 | 63 | ```go 64 | import "fmt" 65 | ``` 66 | 67 | `import` 表示要引用套件,而 `fmt` 套件是 Go 內建的處理格式化輸入輸出函式庫。 68 | 69 | Hello World 的目的是要輸出文字,所以我們需要這個函式庫。 70 | 71 | ### func 72 | 73 | 最後這裡是定義函式,也就是要開始寫流程了。 74 | 75 | ```go 76 | func main() { 77 | fmt.Println("Hello, 世界") 78 | } 79 | ``` 80 | 81 | `func` 定義了程式流程,供其他函式呼叫使用。上面的程式碼可以看到兩個函式,一個是現正定義的 `main` ,另一個則是 `fmt` 套件所提供的 `Println` 函式,這是把後面帶入的文字印出來,然後再另外加一個換行。 82 | 83 | Go 語言有套件庫的概念,同時的函式也有能見度的規範。 Go 採用比較特別的方法:開頭大寫的函式是 public ,不同的套件庫可以呼叫 public func ;開頭小寫的則是 private ,只限套件庫內部使用。 84 | 85 | 上例 `Println` 是屬於 `fmt` 套件的 public func ,因此雖然套件庫不同(`main` 與 `fmt`),仍然可以正常呼叫。 86 | 87 | 而 `func main` 比較特別,它會搭配 `package main` 一起使用。前面提到 `package main` 是所有程式的進入點,而 `go run` 會把 `package main` 的 `func main` 拿出來呼叫。 88 | 89 | 最後總結一下: `go run main.go` 實際上就是執行 `fmt.Println("Hello, 世界")` ,於是就跑出 `Hello, 世界` (和換行)了。 90 | 91 | ## 今日回顧 92 | 93 | * 今天成功寫了第一隻 Go 程式了 94 | * 學習了 Go 的指令 95 | + `go run` 96 | * 學習了 Go 的關鍵字(3/25) 97 | + `package` 98 | + `import` 99 | + `func` 100 | 101 | ## 參考資料 102 | 103 | * [The Go Programming Language](https://golang.org) 104 | * [語言技術:Go 語言](https://openhome.cc/Gossip/Go/index.html) | 良葛格學習筆記 105 | 106 | [昨天]: /docs/day02.md 107 | [官方首頁]: https://golang.org 108 | -------------------------------------------------------------------------------- /docs/day15.md: -------------------------------------------------------------------------------- 1 | # Inheritance 2 | 3 | [昨天][Day 14]學完怎麼在結構上加方法後,它就很像在設計物件導向程式了。今天來看看它怎麼實作繼承。 4 | 5 | 先把昨天最後的程式搬一些過來: 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | ) 13 | 14 | type People struct { 15 | name string 16 | age int 17 | } 18 | 19 | func (people People) Hello() string { 20 | return `Hi! I am ` + people.name 21 | } 22 | 23 | func main() { 24 | miles := People{`Miles`, 18} 25 | 26 | fmt.Println(miles.Hello()) // Hi! Someone, I am Miles 27 | } 28 | ``` 29 | 30 | 假設想要有新的結構是 `Taiwanese` 繼承 `People` ,寫法是這樣的: 31 | 32 | ```go 33 | package main 34 | 35 | import ( 36 | "fmt" 37 | ) 38 | 39 | type People struct { 40 | name string 41 | age int 42 | } 43 | 44 | type Taiwanese struct { 45 | People 46 | country string 47 | } 48 | 49 | func (people People) Hello() string { 50 | return `Hi! I am ` + people.name 51 | } 52 | 53 | func main() { 54 | miles := Taiwanese{People{`Miles`, 18}, `Taiwan`} 55 | 56 | fmt.Println(miles) // {{Miles 18} Taiwan} 57 | fmt.Println(miles.country) // Taiwan 58 | 59 | fmt.Println(miles.name) // Miles 60 | fmt.Println(miles.age) // 18 61 | fmt.Println(miles.Hello()) // Hi! I am Miles 62 | 63 | // 指定結構 64 | fmt.Println(miles.People.name) // Miles 65 | fmt.Println(miles.People.age) // 18 66 | fmt.Println(miles.People.Hello()) // Hi! I am Miles 67 | } 68 | ``` 69 | 70 | 它可以多重繼承,但如果成員重覆的話,就會出現 `ambiguous selector` 編譯錯誤 71 | 72 | ```go 73 | package main 74 | 75 | import ( 76 | "fmt" 77 | ) 78 | 79 | type People struct { 80 | name string 81 | age int 82 | } 83 | 84 | type Animal struct { 85 | name string 86 | } 87 | 88 | type Taiwanese struct { 89 | People 90 | Animal 91 | country string 92 | } 93 | ``` 94 | 95 | ## 覆寫成員與方法 96 | 97 | 剛剛第一個例子可以看到成員與方法是會被繼承下來的,它也可以被覆寫: 98 | 99 | ```go 100 | package main 101 | 102 | import ( 103 | "fmt" 104 | ) 105 | 106 | type People struct { 107 | name string 108 | age int 109 | } 110 | 111 | type Taiwanese struct { 112 | People 113 | name string 114 | country string 115 | } 116 | 117 | func (people People) Hello() string { 118 | return `Hi! I am ` + people.name 119 | } 120 | 121 | func (taiwanese Taiwanese) Hello() string { 122 | return `你好!我是` + taiwanese.name 123 | } 124 | 125 | func main() { 126 | miles := Taiwanese{People{`Miles`, 18}, `麥爾斯`, `Taiwan`} 127 | 128 | fmt.Println(miles.Hello()) // 你好!我是麥爾斯 129 | fmt.Println(miles.People.Hello()) // Hi! I am Miles 130 | } 131 | ``` 132 | 133 | Taiwanese 看起來很像 People 了,不過它還是不能當作是 People 使用(多型),這要使用 interface 之後才能解決。 134 | 135 | --- 136 | 137 | 鐵人賽已過一半,明天要開始來實作應用程式了。 138 | 139 | ## 參考資料 140 | 141 | * [結構與方法](https://openhome.cc/Gossip/Go/Method.html) 142 | 143 | [Day 14]: /docs/day14.md 144 | -------------------------------------------------------------------------------- /docs/day22.md: -------------------------------------------------------------------------------- 1 | # Parse JSON 2 | 3 | 昨天把網頁載好,不過裡面的資料似乎很難處理。後來有找到另一個 API : 4 | 5 | ``` 6 | https://www.moedict.tw/a/字.json 7 | ``` 8 | 9 | 它會回傳 JSON 格式的字串,解析簡單很多。而 [YAML][Day 20] 套件也能解析 JSON ,直接上吧! 10 | 11 | ## 開工 12 | 13 | 首先先把 URL 改掉: 14 | 15 | ```go 16 | const ( 17 | dictionary = `https://www.moedict.tw/a/%s.json` 18 | ) 19 | ``` 20 | 21 | 呼叫改使用 `fmt.Sprintf()` : 22 | 23 | ```go 24 | req := goreq.Request{ 25 | Uri: fmt.Sprintf(dictionary, string(word)), 26 | } 27 | ``` 28 | 29 | 這樣執行應該回傳就會變成 JSON 格式了。 30 | 31 | 下一步是要把 JSON 轉成 go 的資料型態,這次的結構就很鬆散了,改用 Map 做會比較簡單: 32 | 33 | ```go 34 | type MoeDict struct { 35 | Heteronyms []Heteronym 36 | } 37 | 38 | type Heteronym struct { 39 | Definitions []string 40 | } 41 | 42 | func convertJsonToStruct(json string) (dict MoeDict, err error) { 43 | m := make(map[interface{}]interface{}) 44 | yaml.Unmarshal([]byte(json), &m) 45 | 46 | heteronyms := convertHeteronyms(m["h"]) 47 | 48 | return MoeDict{heteronyms}, err 49 | } 50 | 51 | func convertSliceMap(sliceMap []interface{}) (sm []map[interface{}]interface{}) { 52 | for _, m := range sliceMap { 53 | sm = append(sm, m.(map[interface{}]interface{})) 54 | } 55 | 56 | return sm 57 | } 58 | 59 | func convertHeteronyms(in interface{}) (out []Heteronym) { 60 | heteronyms := convertSliceMap(in.([]interface{})) 61 | 62 | for _, heteronym := range heteronyms { 63 | definitions := convertDefinitions(heteronym["d"]) 64 | 65 | out = append(out, Heteronym{ 66 | definitions, 67 | }) 68 | } 69 | 70 | return 71 | } 72 | 73 | func convertDefinitions(in interface{}) (out []string) { 74 | definitions := convertSliceMap(in.([]interface{})) 75 | 76 | for _, definition := range definitions { 77 | def := convertDef(definition["f"].(string)) 78 | 79 | out = append(out, def) 80 | } 81 | 82 | return 83 | } 84 | 85 | func convertDef(in string) string { 86 | def := in 87 | 88 | def = strings.Replace(def, "~", "", -1) 89 | def = strings.Replace(def, "`", "", -1) 90 | 91 | return def 92 | } 93 | ``` 94 | 95 | 因為原始資料的設計是多讀音配多種解釋,會是樹狀結構加一堆陣列,所以必須要很多 `for` 來處理。 96 | 97 | 而 QueryCommand 改寫成這樣: 98 | 99 | ```go 100 | dict, err := provider.Query(str) 101 | 102 | for _, heteronym := range dict.Heteronyms { 103 | for _, definition := range heteronym.Definitions { 104 | fmt.Println(definition) 105 | } 106 | } 107 | ``` 108 | 109 | ## 展示 110 | 111 | ``` 112 | $ go run main.go query 萌 113 | 草木初生的芽。 114 | 事物發生的開端或徵兆。 115 | 人民。 116 | 姓。如五代時蜀有萌慮。 117 | 發芽。 118 | 發生。 119 | ``` 120 | 121 | 目前沒把讀音做篩選,不過這樣也感覺蠻有樣子了。 122 | 123 | 程式碼可以參考 [PR Day 22](https://github.com/MilesChou/namer/pull/8) 124 | 125 | ## 參考資料 126 | 127 | * [萌典][] 128 | 129 | [萌典]: https://www.moedict.tw 130 | [Day 20]: /docs/day20.md 131 | -------------------------------------------------------------------------------- /docs/day05.md: -------------------------------------------------------------------------------- 1 | # Variables & Constants declarations 2 | 3 | [昨天][Day 4]學到了實字常數(literal constants)該如何表示,今天來了解如何宣告變數(Variables)與常數(Constants)。 4 | 5 | ## 變數宣告 6 | 7 | 宣告變數使用 `var` 關鍵字,下面宣告了 num 變數為 int 型態,並給初始值為 10: 8 | 9 | ```go 10 | var num int = 10 11 | ``` 12 | 13 | 使用 IDE 會提示說, `int` 可以省略,因為 Go 會自動推斷 10 的型態為 `int` ,因此也可以這樣宣告: 14 | 15 | ```go 16 | var num = 10 17 | ``` 18 | 19 | 如果不給初始值的話,預定義變數都會有預設值, Go 語言稱之為零值([The zero value][]) , `int` 的零值是 0 ,所以下面兩行宣告是等價的: 20 | 21 | ```go 22 | var num int = 0 23 | var num int 24 | ``` 25 | 26 | Go 可以一次宣告多個變數,下面的型態分別會推斷為 `string` 、 `int` 、 `float64`: 27 | 28 | ```go 29 | var name, age, height = "Miles", 18, 169.9 30 | ``` 31 | 32 | 也可以分多行宣告 33 | 34 | ```go 35 | var ( 36 | name = "Miles" 37 | age = 18 38 | height = 169.9 39 | ) 40 | ``` 41 | 42 | 多行宣告並指定型態與指定初始值 43 | 44 | ```go 45 | var ( 46 | name string = "Miles" 47 | age uint = 18 48 | height float32 = 169.9 49 | ) 50 | ``` 51 | 52 | 多行宣告並指定型態不指定初始值 53 | 54 | ```go 55 | var ( 56 | name string 57 | age uint 58 | height float32 59 | ) 60 | ``` 61 | 62 | ## 短變數宣告 63 | 64 | 在 func 裡,如果要宣告變數同時指定初值,可以使用[短變數宣告][Short variable declarations]: 65 | 66 | ```go 67 | name := "Miles" 68 | age := 18 69 | height := 169.9 70 | ``` 71 | 72 | > 這裡就如同 PHP 的 `$name = 'Miles'` 一樣,宣告變數同時給值 73 | 74 | 一樣可以寫成一行 75 | 76 | ```go 77 | name, age, height := "Miles", 18, 169.9 78 | ``` 79 | 80 | ## 常數宣告 81 | 82 | 宣告變數使用 `const` 關鍵字,下面宣告了 num 變數為 int 型態,並給值為 10: 83 | 84 | ```go 85 | const num int = 10 86 | ``` 87 | 88 | 這時 num 會是不可變的常數,試圖指定新值會在編譯時期報錯。 89 | 90 | ```go 91 | const num int = 10 92 | 93 | // Error 94 | num = 20 95 | ``` 96 | 97 | 除了常數一定要給值外,其他宣告的方法都跟變數一樣,如一次宣告多個常數 98 | 99 | ```go 100 | const name, age, height = "Miles", 18, 169.9 101 | ``` 102 | 103 | 多行宣告 104 | 105 | ```go 106 | const ( 107 | name = "Miles" 108 | age = 18 109 | height = 169.9 110 | ) 111 | ``` 112 | 113 | 多行宣告並指定型態 114 | 115 | ```go 116 | const ( 117 | name string = "Miles" 118 | age uint = 18 119 | height float32 = 169.9 120 | ) 121 | ``` 122 | 123 | ## 宣告後沒使用會? 124 | 125 | 變數宣告了就是要用,不然要幹嘛?如果宣告了一個 num 變數沒使用, Go 會在編譯時期出錯: 126 | 127 | ``` 128 | ./hello.go:8:6: num declared and not used 129 | ``` 130 | 131 | 常數則可以宣告但不使用。 132 | 133 | ## 今日回顧 134 | 135 | * 學習了兩個關鍵字(5/23) 136 | + `var` 宣告變數 137 | + `const` 宣告常數 138 | 139 | ## 參考資料 140 | 141 | * [變數宣告、常數宣告][] | 良葛格學習筆記 142 | * [The zero value][] | The Go Programming Language Specification 143 | * [Short variable declarations][] | The Go Programming Language Specification 144 | 145 | [變數宣告、常數宣告]: https://openhome.cc/Gossip/Go/VariableConstantDeclaration.html 146 | [The zero value]: https://golang.org/ref/spec#The_zero_value 147 | [Short variable declarations]: https://golang.org/ref/spec#Short_variable_declarations 148 | [Day 4]: /docs/day04.md 149 | -------------------------------------------------------------------------------- /docs/day18.md: -------------------------------------------------------------------------------- 1 | # Random 2 | 3 | 如果要產生假資料的話,亂數產生器是必要的。 4 | 5 | 今天先建立一個中文字的資料結構,然後再由 Go 產生亂數來選擇中文字,最後再經由 Command 輸出。 6 | 7 | ## 分析 8 | 9 | 首先要了解亂數是怎麼產生的,查了一下[資料][math/rand 和 crypo/rand 的差別],看來 `math/rand` 就夠用了。 10 | 11 | 因為要給種子,所以查了一下時間 `time` 微秒的取法,結果找到奈秒的: 12 | 13 | ```go 14 | t := time.Now().UnixNano() 15 | 16 | fmt.Println(t) 17 | ``` 18 | 19 | 另外[官方範例](https://golang.org/pkg/math/rand/)有展示 `math/rand` 的兩種用法,一種是全域的: 20 | 21 | ```go 22 | rand.Seed(time.Now().UnixNano()) 23 | 24 | fmt.Println(rand.Int()) 25 | ``` 26 | 27 | 另一種是用種子取得結構後,再開始產生亂數: 28 | 29 | ```go 30 | t := time.Now().UnixNano() 31 | r1 := rand.New(rand.NewSource(t)) 32 | r2 := rand.New(rand.NewSource(t)) 33 | 34 | fmt.Println(r1.Int()) 35 | fmt.Println(r1.Int()) 36 | fmt.Println(r1.Int()) 37 | fmt.Println(r2.Int()) 38 | fmt.Println(r2.Int()) 39 | fmt.Println(r2.Int()) 40 | ``` 41 | 42 | 輸出如下: 43 | 44 | ``` 45 | 6097576665619044690 46 | 3051760752526126731 47 | 1042372804046134795 48 | 6097576665619044690 49 | 3051760752526126731 50 | 1042372804046134795 51 | ``` 52 | 53 | 亂數看來沒問題了,再來可以建一個模組存放這些中文的靜態資料。 54 | 55 | ## 開工 56 | 57 | 首先建目錄 `provider` ,在下面先建資源檔 `resource.go` : 58 | 59 | ```go 60 | package provider 61 | 62 | var names = []string{ 63 | "金太郎", 64 | "金城武", 65 | "金智賢", 66 | } 67 | ``` 68 | 69 | 這個檔比較沒什麼問題,下一個建主要產生器 `generator` : 70 | 71 | ```go 72 | package provider 73 | 74 | import ( 75 | "math/rand" 76 | "time" 77 | ) 78 | 79 | type Generator struct { 80 | rand *rand.Rand 81 | } 82 | 83 | func (generator *Generator) Name() string { 84 | length := len(names) 85 | 86 | return names[generator.rand.Intn(length)] 87 | } 88 | 89 | func Create() Generator { 90 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 91 | 92 | return Generator{ 93 | rand: r, 94 | } 95 | } 96 | ``` 97 | 98 | 這裡說明一下, `Create()` 是建立 `Generator` 結構,類似工廠方法。 99 | 100 | `Generator` 則有著亂數產生器,可以自行建立亂數,也可以依亂數取得資源內的內容。 101 | 102 | 其中 `rand.Intn(length)` 會回傳 0 ~ length 中的一個數字。剛好可以拿來當 key 。 103 | 104 | 原始碼的 Action 函式如下: 105 | 106 | ```go 107 | func(c *cli.Context) error { 108 | num, err := strconv.Atoi(c.String("num")) 109 | 110 | if err != nil { 111 | return err 112 | } 113 | 114 | fmt.Println("Generate " + strconv.Itoa(num)) 115 | 116 | generator := provider.Create() 117 | 118 | for i := 0; i < num; i++ { 119 | fmt.Println(generator.Name()) 120 | } 121 | 122 | return nil 123 | }, 124 | ``` 125 | 126 | ## 展示 127 | 128 | 使用 go run 執行: 129 | 130 | ```bash 131 | $ go run main.go generate 132 | Generate 10 133 | 金城武 134 | 金太郎 135 | 金智賢 136 | 金智賢 137 | 金智賢 138 | 金太郎 139 | 金智賢 140 | 金智賢 141 | 金智賢 142 | 金太郎 143 | ``` 144 | 145 | 詳細程式可以參考 [PR Day 18](https://github.com/MilesChou/namer/pull/3) 146 | 147 | ## 參考資料 148 | 149 | * [math/rand 和 crypo/rand 的差別][] 150 | 151 | [math/rand 和 crypo/rand 的差別]: http://lihaoquan.me/2016/10/15/rand-in-go.html 152 | -------------------------------------------------------------------------------- /docs/day02.md: -------------------------------------------------------------------------------- 1 | # Environment 2 | 3 | 今天來建立開發環境,會分成安裝主程式與設定環境變數兩個部分。 4 | 5 | ## 安裝主程式 6 | 7 | 這裡的主程式是指 `go` 指令,它能處理編譯、直譯、建置、格式化程式碼、測試、下載依賴等多種工具的組合。 8 | 9 | 以下介紹常見的環境該如何安裝 go 。 10 | 11 | ### MacOS 12 | 13 | 使用 [Homebrew][] 可以簡單地安裝: 14 | 15 | $ brew install go 16 | $ go version 17 | go version go1.9.2 darwin/amd64 18 | 19 | 未來將會使用 MacOS 當作主要練習環境。 20 | 21 | ### Ubuntu Linux 16.04 22 | 23 | 使用預設安裝,但筆者試了一下, Ubuntu 預設版本是 Go 1.6 : 24 | 25 | > 使用 [Vagrant](https://www.vagrantup.com/) 建立 Ubuntu 虛擬環境測試 26 | 27 | $ vagrant init ubuntu/xenial64 28 | $ vagrant up 29 | $ vagrant ssh 30 | $ sudo apt-get install golang-go 31 | $ go version 32 | go version go1.6.2 linux/amd64 33 | 34 | 如果需要最新版,可以加入 golang 的 PPA : 35 | 36 | > 注意安裝套件與 go 指令的位置 37 | 38 | $ sudo add-apt-repository ppa:gophers/archive 39 | $ sudo apt update 40 | $ sudo apt-get install golang-1.9-go 41 | $ /usr/lib/go-1.9/bin/go version 42 | go version go1.9.2 linux/amd64 43 | 44 | > 如安裝上遇到問題,也可參考 [wiki](https://github.com/golang/go/wiki/Ubuntu) 。 45 | 46 | ### Windows 47 | 48 | 可到[官方網站](https://golang.org/doc/install#windows)下載 MSI 檔安裝,接著重新打開命令提示字元後,就可以使用 go 指令了: 49 | 50 | C:\Users\User>go version 51 | go version go1.9.2 windows/386 52 | 53 | ## 環境變數設定 54 | 55 | 可下 `go env` 取得環境變數: 56 | 57 | $ go env 58 | GOARCH="amd64" 59 | GOBIN="" 60 | GOEXE="" 61 | GOHOSTARCH="amd64" 62 | GOHOSTOS="darwin" 63 | GOOS="darwin" 64 | GOPATH="/Users/miles.chou/go" 65 | GORACE="" 66 | GOROOT="/usr/local/Cellar/go/1.9.2/libexec" 67 | GOTOOLDIR="/usr/local/Cellar/go/1.9.2/libexec/pkg/tool/darwin_amd64" 68 | GCCGO="gccgo" 69 | CC="clang" 70 | GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/50/nzgqq0d96px715qlh926y2jcm2qxyy/T/go-build204908691=/tmp/go-build -gno-record-gcc-switches -fno-common" 71 | CXX="clang++" 72 | CGO_ENABLED="1" 73 | CGO_CFLAGS="-g -O2" 74 | CGO_CPPFLAGS="" 75 | CGO_CXXFLAGS="-g -O2" 76 | CGO_FFLAGS="-g -O2" 77 | CGO_LDFLAGS="-g -O2" 78 | PKG_CONFIG="pkg-config" 79 | 80 | 裡面有一個 `GOPATH` ,這是需要設定的環境變數。它代表著 go 程式的工作空間(workspace), Windows 預設會設定在 `~\Go` , Unix-like 則沒有預設,官方建議設定在 `~/go` 。 81 | 82 | Workspace 裡,劃分成三個主要目錄: 83 | 84 | * `src` - 原始碼 85 | * `pkg` - go package 86 | * `bin` - 編譯好的執行檔,有需要也可以加入 `PATH` 環境變數 87 | 88 | 接著可以使用 go 的第一個指令-- `go get` ,它會把目標下載回來放在 src 裡,如: 89 | 90 | go get github.com/MilesChou/book-start-golang-30-days 91 | 92 | 這樣會把上面這個 repo ,使用 HTTPS 協定 clone 到硬碟裡。 93 | 94 | 當如果編譯需要第三方的原始碼時,即可使用 go get 下載,同時這也可以用來下載自己或是第三方的原始碼。把所有原始碼集中成一個大大的 workspace ,這就是 go 管理原始碼的概念。 95 | 96 | ## 今日回顧 97 | 98 | * 安裝好主程式,設定好環境變數,明天就可以來 Hello World 了! 99 | 100 | ## 參考資料 101 | 102 | * [Getting Started](https://golang.org/doc/install) | The Go Programming Language 103 | 104 | [Homebrew]: https://brew.sh 105 | -------------------------------------------------------------------------------- /docs/day13.md: -------------------------------------------------------------------------------- 1 | # Struct 2 | 3 | `struct` 是定義資料的集合,跟物件很像,也是把資料集合在一包。 4 | 5 | ## 定義 6 | 7 | `struct` 可以使用 `type` 關鍵字定義,開頭的大小寫跟[函式][Day 10]一樣,會影響能見度;內部的成員名的定義也是一樣。 8 | 9 | 定義與使用的範例如下: 10 | 11 | ```go 12 | package main 13 | 14 | import "fmt" 15 | 16 | type People struct { 17 | name string 18 | age int 19 | } 20 | 21 | func main() { 22 | miles := People{`Miles`, 18} 23 | fmt.Println(miles) // {Miles 18} 24 | fmt.Println(miles.name) // Miles 25 | fmt.Println(miles.age) // 18 26 | 27 | chou := People{age: 18, name: `Chou`} 28 | fmt.Println(chou) // {Chou 18} 29 | 30 | part := People{name: `Part`} 31 | fmt.Println(part) // {Part } 32 | 33 | empty := People{} 34 | fmt.Println(empty) // { 0} 35 | 36 | var empty2 People 37 | fmt.Println(empty2) // { 0} 38 | } 39 | ``` 40 | 41 | 上面可以注意 42 | 43 | * `chou` 範例可以在指定值的時候改順序 44 | * `part` 範例在指定部分值的時候,必須確定指明是哪個部分,比方說上例的 `name` ,即使順序一樣,沒有指明 `name` 的話一樣會出錯,比方說: `part := People{"Part"}` 這是不合法的範例 45 | * `empty` 、 `empty2` 是零值範例 46 | 47 | 如果把一個結構指定給另一個結構時,它會使用複製: 48 | 49 | ```go 50 | package main 51 | 52 | import "fmt" 53 | 54 | type People struct { 55 | name string 56 | age int 57 | } 58 | 59 | func main() { 60 | miles := People{`Miles`, 18} 61 | copy := miles 62 | 63 | copy.name = `Copy` 64 | 65 | fmt.Println(miles) // {Miles 18} 66 | fmt.Println(copy) // {Copy 18} 67 | } 68 | ``` 69 | 70 | 函數傳遞也是如此,如果需要傳址,可以直接用指標 71 | 72 | ```go 73 | package main 74 | 75 | import "fmt" 76 | 77 | type People struct { 78 | name string 79 | age int 80 | } 81 | 82 | func changeName(people *People) { 83 | people.name = `Someone` 84 | } 85 | 86 | func main() { 87 | miles := People{`Miles`, 18} 88 | fmt.Println(miles) // {Miles 18} 89 | 90 | changeName(&miles) 91 | fmt.Println(miles) // {Someone 18} 92 | } 93 | ``` 94 | 95 | 使用 `new` 關鍵字的話,得到的會是指標: 96 | 97 | ```go 98 | package main 99 | 100 | import "fmt" 101 | 102 | type People struct { 103 | name string 104 | age int 105 | } 106 | 107 | func changeName(people *People) { 108 | people.name = `Someone` 109 | } 110 | 111 | func main() { 112 | miles := new(People) 113 | miles.name = `Miles` 114 | miles.age = 18 115 | fmt.Println(miles) // &{Miles 18} 116 | 117 | changeName(miles) 118 | fmt.Println(miles) // &{Someone 18} 119 | } 120 | ``` 121 | 122 | 結構的成員也可以結構,雖然無法直接把自己包起來,但可以使用指標,如: 123 | 124 | ```go 125 | package main 126 | 127 | import "fmt" 128 | 129 | type People struct { 130 | name string 131 | age int 132 | friend *People 133 | } 134 | 135 | func main() { 136 | friend := new(People) 137 | friend.name = `Friend` 138 | 139 | miles := People{`Miles`, 18, friend} 140 | fmt.Println(miles) // &{Miles 18 0xc42000a060} 141 | fmt.Println(miles.name) // Miles 142 | fmt.Println(miles.friend.name) // Friend 143 | } 144 | ``` 145 | 146 | ## 參考資料 147 | 148 | * [結構入門](https://openhome.cc/Gossip/Go/Struct.html) 149 | 150 | [Day 10]: /docs/day10.md -------------------------------------------------------------------------------- /docs/day16.md: -------------------------------------------------------------------------------- 1 | # Dep 2 | 3 | 學一個程式語言,最快的學法就是直接從實作中學。從今天開始要進入應用程式實作階段了! 4 | 5 | 最終想做的應用程式為:[姓名產生器](https://github.com/MilesChou/namer)。 6 | 7 | 簡單介紹:透過應用程式可以產生姓名,並可以建立屬於自己的字典,讓取名可以有自定義的方向,且可避開不喜歡的字。 8 | 9 | --- 10 | 11 | 接下來的方式會是一天一個小迭代,過程中會盡量引發問題,才有機會學到更多東西! 12 | 13 | 迭代的方法為: 14 | 15 | 1. 設定當天目標 16 | 2. 分析 17 | 3. 開工 18 | 4. 如果有發生問題,就解決遇到的問題 19 | 5. 展示 20 | 21 | ## 今日目標 22 | 23 | 使用 [dep][] 初始化一個 CLI 專案。 24 | 25 | ## 分析 26 | 27 | 首先,在那之前,你要先有 dep : 28 | 29 | ```bash 30 | $ brew install dep 31 | ``` 32 | 33 | 再來找現有程式參考。 [Dapper][] 是一個不錯且夠簡單的 CLI 專案,它的 `main.go` 裡面用了 [urfave/cli][] (有改名過)框架,今天就來試著建置起來吧。 34 | 35 | ## 開工 36 | 37 | 首先應該先下載 cli 套件原始碼: 38 | 39 | ```bash 40 | $ go get github.com/urfave/cli 41 | ``` 42 | 43 | 建立主要檔案 `main.go` 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "os" 50 | "github.com/urfave/cli" 51 | ) 52 | 53 | func main() { 54 | app := cli.NewApp() 55 | app.Name = `Namer` 56 | app.Run(os.Args) 57 | } 58 | ``` 59 | 60 | 接著來了解 dep 該怎麼用 61 | 62 | ```bash 63 | $ dep 64 | dep is a tool for managing dependencies for Go projects 65 | 66 | Usage: dep 67 | 68 | Commands: 69 | 70 | init Initialize a new project with manifest and lock files 71 | status Report the status of the project's dependencies 72 | ensure Ensure a dependency is safely vendored in the project 73 | prune Prune the vendor tree of unused packages 74 | version Show the dep version information 75 | 76 | Examples: 77 | dep init set up a new project 78 | dep ensure install the project's dependencies 79 | dep ensure -update update the locked versions of all dependencies 80 | dep ensure -add github.com/pkg/errors add a dependency to the project 81 | 82 | Use "dep help [command]" for more information about a command. 83 | ``` 84 | 85 | 看起來可以先使用 `init` 86 | 87 | ```bash 88 | $ dep init 89 | Using ^1.20.0 as constraint for direct dep github.com/urfave/cli 90 | Locking in v1.20.0 (cfb3883) for direct dep github.com/urfave/cli 91 | ``` 92 | 93 | 它也很聰明,幫我把程式碼裡面的依賴都找一輪,然後也下載到 `vendor` 目錄裡。 94 | 95 | ## 問題 96 | 97 | `vendor` 要不要 commit ,可以參考[官方文件](https://github.com/golang/dep/blob/master/docs/FAQ.md#should-i-commit-my-vendor-directory),它是說隨意,同時也很負責的說明了優缺點。而筆者決定 commit 。 98 | 99 | ## 展示 100 | 101 | 執行後就能看到最簡單的 command 了: 102 | 103 | ```bash 104 | $ go run main.go 105 | NAME: 106 | Namer - A new cli application 107 | 108 | USAGE: 109 | main [global options] command [command options] [arguments...] 110 | 111 | VERSION: 112 | 0.0.0 113 | 114 | COMMANDS: 115 | help, h Shows a list of commands or help for one command 116 | 117 | GLOBAL OPTIONS: 118 | --help, -h show help 119 | --version, -v print the version 120 | ``` 121 | 122 | 詳細程式可以參考 [PR Day 16](https://github.com/MilesChou/namer/pull/1) 123 | 124 | ## 參考資料 125 | 126 | * [dep][] 127 | 128 | [dep]: https://github.com/golang/dep 129 | [Dapper]: https://github.com/rancher/dapper 130 | -------------------------------------------------------------------------------- /docs/day23.md: -------------------------------------------------------------------------------- 1 | # HTTP Server 2 | 3 | 因為參加的是 Modern Web 主題,不管怎樣,還是跟 Web 掛勾一下好了。 4 | 5 | 今天的主題是如何起一個 Web Server 。 6 | 7 | ## 分析 8 | 9 | Go 本身即有內帶一些可用的函式庫,廣大的 [GitHub][Awesome Go] 上也有非常多套件可以參考。今天會使用 [Gin](https://github.com/gin-gonic/gin) 。 10 | 11 | Gin 使用很簡單,官方範例如下: 12 | 13 | ```go 14 | package main 15 | 16 | import "github.com/gin-gonic/gin" 17 | 18 | func main() { 19 | r := gin.Default() 20 | r.GET("/ping", func(c *gin.Context) { 21 | c.JSON(200, gin.H{ 22 | "message": "pong", 23 | }) 24 | }) 25 | r.Run() // listen and serve on 0.0.0.0:8080 26 | } 27 | ``` 28 | 29 | 啟動 Server 的方法就學 Laravel [Artisan][] 好了: 30 | 31 | ``` 32 | $ php artisan serve 33 | ``` 34 | 35 | API 設計直接以 command 的功能命名,先來做 generate 就好。 36 | 37 | 預計的效果如下: 38 | 39 | ``` 40 | GET /generate 41 | [ 42 | "張春", 43 | "李明", 44 | "劉家", 45 | "張志", 46 | "劉雅", 47 | "楊雅", 48 | "陳豪", 49 | "楊嬌", 50 | "劉明", 51 | "劉春" 52 | ] 53 | ``` 54 | 55 | ## 開工 56 | 57 | 首先先把範例程式碼加入 Command : 58 | 59 | ```go 60 | package command 61 | 62 | import ( 63 | "github.com/urfave/cli" 64 | "github.com/gin-gonic/gin" 65 | ) 66 | 67 | var ( 68 | ServeCommand = cli.Command{ 69 | Name: "serve", 70 | Usage: "啟動伺服器", 71 | Action: func(c *cli.Context) error { 72 | return serve(c) 73 | }, 74 | } 75 | ) 76 | 77 | func serve(c *cli.Context) error { 78 | server := gin.Default() 79 | server.GET(`/generate`, func(c *gin.Context) { 80 | c.JSON(200, gin.H{ 81 | "result": "ok", 82 | }) 83 | }) 84 | 85 | server.Run() 86 | 87 | return nil 88 | } 89 | ``` 90 | 91 | 這樣就能正常的打 `/generate` API 了 92 | 93 | ``` 94 | $ go run main.go serve 95 | $ curl 127.0.0.1:8080/generate 96 | {"result":"ok"} 97 | ``` 98 | 99 | 再來把 `GenerateCommand` 的實作搬來 `serve` 函式: 100 | 101 | ```go 102 | func serve(c *cli.Context) error { 103 | res, _ := provider.ParseFile(c.GlobalString("provider")) 104 | num := 10 105 | 106 | server := gin.Default() 107 | server.GET(`/generate`, func(c *gin.Context) { 108 | generator := provider.Create() 109 | generator.Resource = res 110 | 111 | s := []string{} 112 | for i := 0; i < num; i++ { 113 | s = append(s, generator.Name()) 114 | } 115 | 116 | provider.Create() 117 | c.JSON(200, s) 118 | }) 119 | 120 | server.Run() 121 | 122 | return nil 123 | } 124 | ``` 125 | 126 | > `num` 先硬寫,後面再調整。 127 | 128 | 大功告成! 129 | 130 | ## 問題 131 | 132 | 上面的程式碼可以注意到: `c.JSON` 帶的第二個參數似乎是可以任意值的,這應該是 `interface` 的特色。 133 | 134 | YAML 在實作的時候也一直看到 `interface` ,所以看來有機會還是得好好研究一下它。 135 | 136 | ## 展示 137 | 138 | ``` 139 | $ go run main.go serve 140 | $ curl 127.0.0.1:8080/generate 141 | ["李雅","王家","李婷","黃家","張嬌","李志","陳豪","楊婷","黃婷","楊婷"] 142 | ``` 143 | 144 | 程式碼可以參考 [PR Day 23](https://github.com/MilesChou/namer/pull/9) 145 | 146 | ## 參考資料 147 | 148 | * [Awesome Go][] 149 | * [Interface types](https://golang.org/ref/spec#Interface_types) | The Go Programming Language Specification 150 | 151 | [Awesome Go]: https://awesome-go.com/ 152 | [Artisan]: https://laravel.com/docs/5.5/artisan 153 | -------------------------------------------------------------------------------- /docs/day11.md: -------------------------------------------------------------------------------- 1 | # First class function 2 | 3 | [昨天][Day 10]定義好的函式,可以當作變數來使用,如: 4 | 5 | ```go 6 | package main 7 | 8 | import "fmt" 9 | import "reflect" 10 | 11 | func add(a, b int) int { 12 | return a + b 13 | } 14 | 15 | func main() { 16 | add2 := add 17 | 18 | fmt.Println(add(1, 2)) // 3 19 | fmt.Println(add2(1, 2)) // 3 20 | fmt.Println(reflect.TypeOf(add)) // func(int, int) int 21 | fmt.Println(reflect.TypeOf(add2)) // func(int, int) int 22 | } 23 | ``` 24 | 25 | 上面可以看到 `add` 函式也可以當作一個變數來操作,熟悉 Javascript 一定對這個寫法不陌生。 26 | 27 | 上面同時也看得到變數的型態,因此我們也能定義一個變數是函式型態(Function Type): 28 | 29 | ```go 30 | package main 31 | 32 | import "fmt" 33 | import "reflect" 34 | 35 | func add(a, b int) int { 36 | return a + b 37 | } 38 | 39 | func main() { 40 | var add2 func(int, int) int 41 | 42 | fmt.Println(add2) // 43 | 44 | add2 = add 45 | 46 | fmt.Println(add(1, 2)) // 3 47 | fmt.Println(add2(1, 2)) // 3 48 | } 49 | ``` 50 | 51 | 因為它是變數,所以也可以成為其他函式的傳入值,就像 Javascript callback 一般: 52 | 53 | ```go 54 | package main 55 | 56 | import "fmt" 57 | 58 | func cmd(a, b int, callback func(int, int) int) int { 59 | return callback(a, b) 60 | } 61 | 62 | func add(a, b int) int { 63 | return a + b 64 | } 65 | 66 | func main() { 67 | var add2 func(int, int) int 68 | 69 | add2 = add 70 | 71 | fmt.Println(cmd(1, 2, add)) // 3 72 | fmt.Println(cmd(1, 2, add2)) // 3 73 | } 74 | ``` 75 | 76 | 看到一堆 `func(int, int) int` 會覺得很冗長,我們可以使用 `type` 來定義新的型態: 77 | 78 | ```go 79 | package main 80 | 81 | import "fmt" 82 | 83 | type addFunc func(int, int) int 84 | 85 | func cmd(a, b int, callback addFunc) int { 86 | return callback(a, b) 87 | } 88 | 89 | func add(a, b int) int { 90 | return a + b 91 | } 92 | 93 | func main() { 94 | var add2 addFunc 95 | 96 | add2 = add 97 | 98 | fmt.Println(cmd(1, 2, add)) // 3 99 | fmt.Println(cmd(1, 2, add2)) // 3 100 | } 101 | ``` 102 | 103 | ## 匿名函式 104 | 105 | 除了直接宣告函式傳入之外,也可以使用匿名函式: 106 | 107 | ```go 108 | package main 109 | 110 | import "fmt" 111 | 112 | type addFunc func(int, int) int 113 | 114 | func cmd(a, b int, callback addFunc) int { 115 | return callback(a, b) 116 | } 117 | 118 | func main() { 119 | var add2 addFunc 120 | 121 | add2 = func(a, b int) int { 122 | return a + b 123 | } 124 | 125 | fmt.Println(cmd(1, 2, add2)) // 3 126 | } 127 | ``` 128 | 129 | 或者直接 inline 會更簡潔: 130 | 131 | ```go 132 | package main 133 | 134 | import "fmt" 135 | 136 | type addFunc func(int, int) int 137 | 138 | func cmd(a, b int, callback addFunc) int { 139 | return callback(a, b) 140 | } 141 | 142 | func main() { 143 | 144 | // 3 145 | fmt.Println(cmd(1, 2, func(a, b int) int { 146 | return a + b 147 | })) 148 | } 149 | ``` 150 | 151 | ## 參考資料 152 | 153 | * [一級函式](https://openhome.cc/Gossip/Go/FirstClassFunction.html) 154 | * [匿名函式與閉包](https://openhome.cc/Gossip/Go/Closure.html) 155 | * [Function types][] 156 | 157 | [Function types]: https://golang.org/ref/spec#Function_types 158 | [Day 10]: /docs/day10.md 159 | -------------------------------------------------------------------------------- /docs/day10.md: -------------------------------------------------------------------------------- 1 | # Function declarations 2 | 3 | 在我們 [Hello World][Day 3] 的練習裡,曾提到一點點函式的定義,今天要來詳解它。 4 | 5 | ## 定義函式 6 | 7 | 函式的定義語法如下: 8 | 9 | ```go 10 | func ([param1 type1[, param2 type2 ...]]) ([return1 type1[, return2 type2 ...]]) { 11 | 12 | // Do something 13 | 14 | return [value1[, value2 ...]] 15 | } 16 | ``` 17 | 18 | * `funcName` 為 function 名稱,首字大寫為 public 是外部套件可以共用的,小寫為 private 只有套件內部可以看得到。 19 | * `param` 為傳入值。 20 | * `return` 為回傳值。 Go 比較特別的是,它可以定義多個回傳值,而且還能定義它的名稱。定義名稱時,回傳的用法會有點特別。 21 | 22 | 先來個簡單的範例: 23 | 24 | ```go 25 | package main 26 | 27 | import "fmt" 28 | 29 | func add(a int, b int) int { 30 | return a + b 31 | } 32 | 33 | func main() { 34 | sum := add(1, 2) 35 | 36 | fmt.Println(sum) // 3 37 | } 38 | ``` 39 | 40 | 其中 `add` 函式, `a` 與 `b` 為相同型態,所以可以寫在一起,如下: 41 | 42 | ```go 43 | func add(a, b int) int { 44 | return a + b 45 | } 46 | ``` 47 | 48 | 如果回傳值只有一個,而且沒有宣告名稱時,可以不用加括號 `()` 。下面是回傳兩個值的範例 49 | 50 | ```go 51 | package main 52 | 53 | import "fmt" 54 | 55 | func add(a, b int) (int, bool) { 56 | return a + b, true 57 | } 58 | 59 | func main() { 60 | sum, ok := add(1, 2) 61 | 62 | fmt.Println(sum) // 3 63 | fmt.Println(ok) // true 64 | } 65 | ``` 66 | 67 | 這有點類似 [Map][Day 9] 取值的用法,第二個值是確認這個回傳是正確的。 68 | 69 | 如果有定義名稱的話, `return` 會把當下兩個變數的內容回傳出去。 70 | 71 | ```go 72 | package main 73 | 74 | import "fmt" 75 | 76 | func add(a, b int) (sum int, ok bool) { 77 | sum = a + b 78 | ok = true 79 | return 80 | } 81 | 82 | func main() { 83 | sum, ok := add(1, 2) 84 | 85 | fmt.Println(sum) // 3 86 | fmt.Println(ok) // true 87 | } 88 | ``` 89 | 90 | 常看過很多程式在一開始就定義 `result` ,在 `return` 的時候回傳出去。 Go 則是在規格上直接實作出來,覺得蠻有趣的。 91 | 92 | 傳回多個值的時候,必須照順序接值;如果不需要回傳值,可以用 `_` 略過: 93 | 94 | ```go 95 | package main 96 | 97 | import "fmt" 98 | 99 | func add(a, b int) (sum int, ok bool) { 100 | sum = a + b 101 | ok = true 102 | return 103 | } 104 | 105 | func main() { 106 | _, ok := add(1, 2) 107 | 108 | fmt.Println(ok) // true 109 | } 110 | ``` 111 | 112 | 傳回多值通常會用在錯誤處理,如果使用不恰當的話,容易違反[單一職責原則][Refactoring Day 7]。 113 | 114 | 如果傳入值是不定的,可以用 `...` 來表示,如: 115 | 116 | ```go 117 | package main 118 | 119 | import "fmt" 120 | 121 | func add(numbers ...int) (sum int) { 122 | sum = 0 123 | for _, num := range numbers { 124 | sum += num 125 | } 126 | 127 | return 128 | } 129 | 130 | func main() { 131 | sum := add(1, 2, 3, 4, 5) 132 | 133 | fmt.Println(sum) // 15 134 | } 135 | ``` 136 | 137 | 裡面的 `numbers` 型態會是 [Slice][Day 8] `[]int` ,所以可以用 `for range` 走訪。 138 | 139 | ## 參考資料 140 | 141 | * [函式入門](https://openhome.cc/Gossip/Go/Function.html) | 良葛格學習筆記 142 | * [Function declarations][] | The Go Programming Language Specification 143 | * [SOLID 之 單一職責原則(Single responsibility principle)][Refactoring Day 7] | 看到 code 寫成這樣我也是醉了,不如試試重構? 144 | 145 | [Function declarations]: https://golang.org/ref/spec#Function_declarations 146 | [Refactoring Day 7]: https://github.com/MilesChou/book-refactoring-30-days/blob/master/docs/day07.md 147 | [Day 3]: /docs/day03.md 148 | [Day 8]: /docs/day08.md 149 | [Day 9]: /docs/day09.md 150 | -------------------------------------------------------------------------------- /docs/day12.md: -------------------------------------------------------------------------------- 1 | # Anonymous Function 2 | 3 | [昨天][Day 11]在使用 callback 的時候,有用到匿名函式。今天來看一下匿名函式的其他細節。 4 | 5 | 下面是一個簡單匿名函式的使用方法: 6 | 7 | ```go 8 | package main 9 | 10 | import "fmt" 11 | 12 | func main() { 13 | addFunc := func(a, b int) int { 14 | return a + b 15 | } 16 | 17 | sum := addFunc(1, 2) 18 | 19 | fmt.Println(sum) // 3 20 | } 21 | ``` 22 | 23 | 如果覺得還要存放一個變數太麻煩,這也可以省略 24 | 25 | ```go 26 | package main 27 | 28 | import "fmt" 29 | 30 | func main() { 31 | sum := func(a, b int) int { 32 | return a + b 33 | }(1, 2) 34 | 35 | fmt.Println(sum) // 3 36 | } 37 | ``` 38 | 39 | 又或是,想要從另一個函式取得匿名函式: 40 | 41 | ```go 42 | package main 43 | 44 | import "fmt" 45 | 46 | func getFunc() func(a, b int) int { 47 | return func(a, b int) int { 48 | return a + b 49 | } 50 | } 51 | 52 | func main() { 53 | sum := getFunc()(1, 2) 54 | 55 | fmt.Println(sum) // 3 56 | } 57 | ``` 58 | 59 | 再來這是組合技 60 | 61 | ```go 62 | package main 63 | 64 | import "fmt" 65 | 66 | func getFunc() func(a, b int) int { 67 | return func(a, b int) int { 68 | return func(a, b int) int { 69 | return a + b 70 | }(a, b) 71 | } 72 | } 73 | 74 | func main() { 75 | sum := getFunc()(1, 2) 76 | 77 | fmt.Println(sum) // 3 78 | } 79 | ``` 80 | 81 | ## Closure 82 | 83 | 閉包是指,變數被關在某個區塊內。比方說剛剛的例子調整一下: 84 | 85 | ```go 86 | package main 87 | 88 | import "fmt" 89 | 90 | func getFunc() func(a, b int) int { 91 | base := 10 92 | 93 | return func(a, b int) int { 94 | return base + func(a, b int) int { 95 | return a + b 96 | }(a, b) 97 | } 98 | } 99 | 100 | func main() { 101 | sum := getFunc()(1, 2) 102 | 103 | fmt.Println(sum) // 13 104 | } 105 | ``` 106 | 107 | 這樣的結果會跟下面這個例子的結果一樣: 108 | 109 | ```go 110 | package main 111 | 112 | import "fmt" 113 | 114 | func getFunc() func(a, b int) int { 115 | base := 10 116 | 117 | return func(a, b int) int { 118 | return func(a, b int) int { 119 | return base + a + b 120 | }(a, b) 121 | } 122 | } 123 | 124 | func main() { 125 | sum := getFunc()(1, 2) 126 | 127 | fmt.Println(sum) // 13 128 | } 129 | ``` 130 | 131 | 我們可以發現,雖然匿名函式內容是封閉的,但 `base` 變數卻能夠被關進匿名函式裡,甚至是「匿名函式的匿名函式裡」,這就是閉包的特性。 132 | 133 | 最後,因為 Go 有取址運算,我們能拿得到變數真正的位址。那我們來看看在各階段裡面的位址為何: 134 | 135 | ```go 136 | package main 137 | 138 | import "fmt" 139 | 140 | func getFunc() func(a, b int) int { 141 | base := 10 142 | fmt.Printf("In getFunc() %p = %d\n", &base, base) 143 | 144 | return func(a, b int) int { 145 | fmt.Printf("In getFunc() closure %p = %d\n", &base, base) 146 | 147 | return func(a, b int) int { 148 | fmt.Printf("In getFunc() closure's closure %p = %d\n", &base, base) 149 | 150 | return base + a + b 151 | }(a, b) 152 | } 153 | } 154 | 155 | func main() { 156 | sum := getFunc()(1, 2) 157 | 158 | fmt.Println(sum) // 13 159 | } 160 | ``` 161 | 162 | 最後輸出: 163 | 164 | ``` 165 | In getFunc() 0xc420010058 = 10 166 | In getFunc() closure 0xc420010058 = 10 167 | In getFunc() closure's closure 0xc420010058 = 10 168 | 13 169 | ``` 170 | 171 | 這裡可以發現在三個地方的 base 變數位址都是 `0xc420010058` ,這也是所謂「變數被關在某個區塊內」所代表的意思。 172 | 173 | ## 參考資料 174 | 175 | * [匿名函式與閉包](https://openhome.cc/Gossip/Go/Closure.html) 176 | 177 | [Day 11]: /docs/day11.md 178 | -------------------------------------------------------------------------------- /docs/day09.md: -------------------------------------------------------------------------------- 1 | # Map Type 2 | 3 | 許多語言都有提供 key-value 存放方法的 map 結構, Go 使用內建型態 `map` 實作。 4 | 5 | `map` 型態的表示方法為: `map[keyType]valueType` , `map` 是關鍵字, `keyType` 必須是可比較([Comparable][Comparison operators])的型態,如 `string` 、 `int` 等, `valueType` 則是內容形態。 6 | 7 | ## 建立 8 | 9 | 建立 Map 資料型態也是用 `make` ,設定與取值的方法跟大部分的語言(如 PHP )都很像,範例如下: 10 | 11 | ```go 12 | package main 13 | 14 | import "fmt" 15 | 16 | func main() { 17 | score := make(map[string]int) 18 | 19 | fmt.Println(score) // map[] 20 | 21 | score["Miles"] = 80 22 | score["Chou"] = 60 23 | 24 | fmt.Println(score) // map[Miles:80 Chou:60] 25 | fmt.Println(score["Miles"]) // 80 26 | fmt.Println(score["Chou"]) // 60 27 | } 28 | ``` 29 | 30 | 如果有初值的話,設定的方法很像 JSON : 31 | 32 | ```go 33 | package main 34 | 35 | import "fmt" 36 | 37 | func main() { 38 | score := map[string]int{ 39 | "Miles": 80, 40 | "Chou": 60, 41 | } 42 | 43 | fmt.Println(score) // map[] 44 | 45 | score["Miles"] = 80 46 | score["Chou"] = 60 47 | 48 | fmt.Println(score) // map[Miles:80 Chou:60] 49 | fmt.Println(score["Miles"]) // 80 50 | fmt.Println(score["Chou"]) // 60 51 | } 52 | ``` 53 | 54 | 值得一提的是,宣告值最後一行 `"Chou": 60,` 的逗號是必要要加的。 55 | 56 | 這個寫法如果不給初值的話,就會跟使用 `make` 方法結果一樣: 57 | 58 | ```go 59 | score := make(map[string]int) 60 | 61 | score := map[string]int{} 62 | ``` 63 | 64 | `map` 跟 `slice` 一樣是使用參考,比方說: 65 | 66 | ```go 67 | package main 68 | 69 | import "fmt" 70 | 71 | func main() { 72 | score := map[string]int{ 73 | "Miles": 80, 74 | "Chou": 60, 75 | } 76 | 77 | ref := score 78 | 79 | fmt.Println(score) // map[Miles:80 Chou:60] 80 | fmt.Println(ref) // map[Miles:80 Chou:60] 81 | 82 | score["Someone"] = 0 83 | 84 | fmt.Println(score) // map[Chou:60 Someone:0 Miles:80] 85 | fmt.Println(ref) // map[Someone:0 Miles:80 Chou:60] 86 | } 87 | ``` 88 | 89 | > 試了幾次,它的順序應該是不固定的。 90 | 91 | ## 操作 92 | 93 | 取值使用 `[]` 指定 key ,事實上它會回傳兩個值,如果 key 存在,會回傳值與 true ; key 不存在則回傳零值與 false : 94 | 95 | ```go 96 | package main 97 | 98 | import "fmt" 99 | 100 | func main() { 101 | score := map[string]int{ 102 | "Miles": 80, 103 | "Chou": 60, 104 | } 105 | 106 | var value int 107 | var ok bool 108 | 109 | value, ok = score["Miles"] 110 | 111 | fmt.Println(value) // 80 112 | fmt.Println(ok) // true 113 | 114 | value, ok = score["Nobody"] 115 | 116 | fmt.Println(value) // 0 117 | fmt.Println(ok) // false 118 | } 119 | ``` 120 | 121 | 移除 key 使用 `delete` 函式: 122 | 123 | ```go 124 | package main 125 | 126 | import "fmt" 127 | 128 | func main() { 129 | score := map[string]int{ 130 | "Miles": 80, 131 | } 132 | 133 | var value int 134 | var ok bool 135 | 136 | value, ok = score["Miles"] 137 | 138 | fmt.Println(value) // 80 139 | fmt.Println(ok) // true 140 | 141 | delete(score, "Miles") 142 | value, ok = score["Miles"] 143 | 144 | fmt.Println(value) // 0 145 | fmt.Println(ok) // false 146 | } 147 | ``` 148 | 149 | ## 參考資料 150 | 151 | * [成對鍵值的 map](https://openhome.cc/Gossip/Go/Map.html) 152 | * [Comparison operators][] 153 | 154 | [Comparison operators]: https://golang.org/ref/spec#Comparison_operators 155 | -------------------------------------------------------------------------------- /docs/day28.md: -------------------------------------------------------------------------------- 1 | # Add Command Parameters 2 | 3 | 我們在重構 [Name Provider][Day 26] 有提到,指令必須也要加參數,才有辦法傳給 Provider 產生對應的結果。 4 | 5 | 除此之外還有個需求:參考 [Faker](https://github.com/fzaninotto/Faker) ,我們還需要單純取得男性名字和女性名字的方法。 6 | 7 | ## 分析 8 | 9 | 先在 Name Provider 裡實作取得男性名字和女性名字後,再到指令的地方看該如何加參數。 10 | 11 | 另外,因為實作移到 Facade 了,所以可能要調整 Facade 的行為。 12 | 13 | ## 開工 14 | 15 | 使用最笨的方法:新增一堆公開函式來用,最原始的 `Name` 函式則新增了參數來決定背後要呼叫哪些函式: 16 | 17 | ```go 18 | func (generator *Generator) Name(gender string, firstNameNum int) (name string, err error) { 19 | switch gender { 20 | case "": 21 | name = generator.LastName() + generator.firstName(firstNameNum) 22 | case "male": 23 | name = generator.LastName() + generator.firstNameMale(firstNameNum) 24 | case "female": 25 | name = generator.LastName() + generator.firstNameFemale(firstNameNum) 26 | default: 27 | err = errors.New(fmt.Sprintf("gender '%s' is invalid", gender)) 28 | } 29 | 30 | return 31 | } 32 | ``` 33 | 34 | `facade` 則加了兩個公開的靜態變數,來存放要帶入的值: 35 | 36 | ```go 37 | var ( 38 | GenerateFirstNameNum int 39 | GenerateGender string 40 | ) 41 | 42 | func init() { 43 | reset() 44 | } 45 | 46 | func reset() { 47 | GenerateFirstNameNum = 2 48 | GenerateGender = "" 49 | } 50 | ``` 51 | 52 | `Generate` 再去使用這些值帶入 53 | 54 | ```go 55 | func Generate(path string, count int, process GenerateItemProcess) error { 56 | res, _ := provider.ParseFile(path) 57 | 58 | generator := provider.Create() 59 | generator.Resource = res 60 | 61 | for i := 0; i < count; i++ { 62 | name, err := generator.Name(GenerateGender, GenerateFirstNameNum) 63 | 64 | if err != nil { 65 | return err 66 | } 67 | 68 | process(name, i) 69 | } 70 | 71 | return nil 72 | } 73 | ``` 74 | 75 | `ServeCommand` 也可以用 query string 帶入參數: 76 | 77 | ```go 78 | func serve(c *cli.Context) error { 79 | server := gin.Default() 80 | server.GET(`/generate`, func(g *gin.Context) { 81 | num, err := strconv.Atoi(g.DefaultQuery("amount", "10")) 82 | 83 | if err != nil { 84 | g.JSON(400, err) 85 | return 86 | } 87 | 88 | firstNameNum, err := strconv.Atoi(g.DefaultQuery("firstNameNum", "0")) 89 | 90 | if err != nil { 91 | g.JSON(400, err) 92 | return 93 | } 94 | 95 | facade.GenerateFirstNameNum = firstNameNum 96 | facade.GenerateGender = g.DefaultQuery("gender", "") 97 | 98 | s := make([]string, num) 99 | facade.Generate(c.GlobalString("provider"), num, func(item string, index int) { 100 | s[index] = item 101 | }) 102 | 103 | g.JSON(200, s) 104 | }) 105 | 106 | server.Run() 107 | 108 | return nil 109 | } 110 | ``` 111 | 112 | ## 展示 113 | 114 | 現在應該可以自由使用參數調整輸出的結果了,如: 115 | 116 | ``` 117 | $ go run main.go generate --amount 1 --first-name-num 2 --gender male 118 | 楊明豪 119 | $ go run main.go generate --amount 2 --first-name-num 2 --gender female 120 | 李雅嬌 121 | 劉嬌婷 122 | ``` 123 | 124 | HTTP server 也可以用 query string 調整,如: 125 | 126 | ``` 127 | $ curl "http://localhost:8080/generate?amount=1&firstNameNum=2&gender=male" 128 | ["陳明志"] 129 | $ curl "http://localhost:8080/generate?amount=2&firstNameNum=2&gender=female" 130 | ["李嬌雅","劉婷春"] 131 | ``` 132 | 133 | 程式碼可以參考 [PR Day 28](https://github.com/MilesChou/namer/pull/14) 134 | 135 | ## 參考資料 136 | 137 | * [Faker](https://github.com/fzaninotto/Faker) 138 | 139 | [Day 26]: /docs/day26.md 140 | -------------------------------------------------------------------------------- /docs/day25.md: -------------------------------------------------------------------------------- 1 | # Docker 2 | 3 | 做完交付後,下一個目標就是要做部署了!不過部署做簡單一點,在 Docker 上能跑就行了! 4 | 5 | 最後期望的結果是,只要機器有 Docker Daemon ,程式就能正常在機器上跑! 6 | 7 | ## 分析 8 | 9 | 理論上, Go 執行檔的依賴都在執行檔上,或許放到 [alpine](https://hub.docker.com/_/alpine/) 上是可行的 10 | 11 | 來試看看吧! 12 | 13 | ## 開工 14 | 15 | 先編譯過檔案: 16 | 17 | ``` 18 | $ go build 19 | ``` 20 | 21 | 然後進到 `alpine` 裡: 22 | 23 | ``` 24 | $ docker run --rm -it -v `pwd`:/source alpine sh 25 | # --- Docker Container 裡 --- 26 | $ cd /source 27 | $ ./namer 28 | ./namer: line 1: �����: not found 29 | ./namer: line 2: can't open Ԓ��: no such file 30 | ./namer: line 2: H__PAGEZEROx__TEXTЖЖ__text__TEXT�S__rodata__TEXT: not found 31 | ./namer: line 14: syntax error: unexpected "(" 32 | ``` 33 | 34 | 失敗了,可能是缺少了什麼東西,我們換最肥的 `ubuntu` 試試: 35 | 36 | ``` 37 | $ docker run --rm -it -v `pwd`:/source ubuntu bash 38 | # --- Docker Container 裡 --- 39 | $ cd /source 40 | $ ./namer 41 | bash: ./namer: cannot execute binary file: Exec format error 42 | ``` 43 | 44 | 咦,這不科學啊!不是說好了依賴都放在裡面了嗎?後來才想到,原來是因為筆者編譯環境是 Mac ,才會造成這樣的結果。 45 | 46 | 雖然可以把昨天丟到 GitHub Release 的結果下載回來,在放到 Docker 裡面,不過這也太麻煩了。原始碼都在手上了,應該產生執行檔會是件簡單的事呀! 47 | 48 | Go 有支援 cross compile ,不過目前只讓它在 Travis 上運行,我們來用 [Dapper][] 實現目的。 49 | 50 | ### 解決 Dapper 的問題 51 | 52 | 首先,因為 Mac 前一陣子更新,導致 [Dapper 官方提供的可執行檔不能用了](https://github.com/rancher/dapper/issues/47)。 53 | 54 | 一種方法是使用 [Dapper Image](https://hub.docker.com/r/rancher/dapper/) 建立可以用 Dapper 的環境。這個方法比較麻煩,更重要的是,我們都已經學 Go 了,直接抓下來編譯一下就可以了呀! 55 | 56 | 所以我們來編譯它吧,首先把 Dapper Clone 下來: 57 | 58 | ``` 59 | $ go get github.com/rancher/dapper 60 | ``` 61 | 62 | 然後切換到目錄下 63 | 64 | ``` 65 | $ cd ${GOPATH}/src/github.com/rancher/dapper 66 | ``` 67 | 68 | 下安裝指令 69 | 70 | ``` 71 | $ go install 72 | ``` 73 | 74 | > 或是直接下安裝指令 `go install github.com/rancher/dapper` 也可以,上面就是它實際會做的事。 75 | 76 | 接著應該就會發現 `${GOPATH}/bin` 有 dapper 這個檔案了: 77 | 78 | ``` 79 | $ ls ${GOPATH}/bin 80 | dapper 81 | ``` 82 | 83 | 只要把環境變數 `PATH` 指定好,就可以用 dapper 了: 84 | 85 | ``` 86 | PATH=$PATH:$GOPATH/bin 87 | ``` 88 | 89 | > 同樣的概念, Namer 裡使用 `go install` 也會多一個指令是 `namer` 唷。 90 | 91 | ### 撰寫 Dapper 92 | 93 | Dapper 的用法可以參考[官網][Dapper]或是[教學文章][自己來的好選擇 -- Dapper],這邊就不贅述了。 94 | 95 | 寫完內容如下: 96 | 97 | ```dockerfile 98 | FROM golang:1.9-alpine 99 | 100 | ENV DAPPER_SOURCE /go/src/github.com/MilesChou/namer 101 | ENV DAPPER_OUTPUT ./bin 102 | RUN mkdir -p ${DAPPER_SOURCE} 103 | WORKDIR ${DAPPER_SOURCE} 104 | 105 | CMD ["go", "build", "-o", "./bin/namer", "main.go"] 106 | ``` 107 | 108 | 這次 build 出來的 namer 就能正常在 Alpine 下執行了。 109 | 110 | ### 撰寫 Dockerfile 111 | 112 | 因為只有 `namer` 執行檔就行, Dockerfile 相對非常簡單,如下: 113 | 114 | ```dockerfile 115 | FROM alpine:3.7 116 | 117 | RUN mkdir -p /source 118 | WORKDIR /source 119 | COPY ./bin/namer ./ 120 | COPY names.yml ./ 121 | 122 | EXPOSE 8080 123 | 124 | ENTRYPOINT ["./namer"] 125 | CMD ["help"] 126 | ``` 127 | 128 | ## 展示 129 | 130 | Docker Build 與 Docker Run 的範例: 131 | 132 | ``` 133 | $ docker build -t namer . 134 | $ docker run -it --rm namer --version 135 | Namer version 0.0.0 136 | ``` 137 | 138 | 程式碼可以參考 [PR Day 25](https://github.com/MilesChou/namer/pull/11) 139 | 140 | ## 問題 141 | 142 | 在建置的過程發現, `namer` 執行檔一個就要 14MB 之多,有點可怕。 143 | 144 | 暫時還不知道該如何解決,目前先讓它可以用就好。 145 | 146 | ## 參考資料 147 | 148 | * [Dapper][] 149 | * [自己來的好選擇 -- Dapper][] | CI 從入門到入坑 150 | 151 | [Dapper]: https://github.com/rancher/dapper 152 | [自己來的好選擇 -- Dapper]: https://ithelp.ithome.com.tw/articles/10187177 153 | -------------------------------------------------------------------------------- /docs/day20.md: -------------------------------------------------------------------------------- 1 | # YAML 2 | 3 | 昨天已經成功把檔案載入變成 `[]byte` 型態,今天要來讀 YAML 檔了。 4 | 5 | ## 分析 6 | 7 | 昨天有提到會使用 `go-yaml` 解析 YAML 資料,資料格式參考 [Faker](https://github.com/fzaninotto/Faker/blob/v1.7.1/src/Faker/Provider/zh_TW/Person.php) ,大概會長像下面這樣: 8 | 9 | ```yaml 10 | lastNames: 11 | - 李 12 | - 王 13 | - 張 14 | - 劉 15 | - 陳 16 | - 楊 17 | - 趙 18 | - 黃 19 | - ... 20 | 21 | characterMale: 22 | - 家 23 | - 豪 24 | - 志 25 | - 明 26 | - ... 27 | 28 | characterFemale: 29 | - 雅 30 | - 婷 31 | - 春 32 | - 嬌 33 | - ... 34 | ``` 35 | 36 | `go-yaml` 的用法大概如下: 37 | 38 | ```go 39 | s := NamesProvider{} 40 | yaml.Unmarshal(bytes, s) 41 | 42 | fmt.Println(s) // 印出解析完的 struct 43 | ``` 44 | 45 | 這樣應該就可以開始實作了。 46 | 47 | ## 開工 48 | 49 | 開始前,一樣要先重構:把切目錄和讀檔的任務放到最外層(`main.go`)。 50 | 51 | 不過因為不大清楚 CLI 套件怎麼做全域的任務,所以不如就把解析 YAML 的任務先放在 `resource` 裡: 52 | 53 | ```go 54 | type NamesResource struct { 55 | LastNames []string `yaml:"lastNames"` 56 | CharacterMale []string `yaml:"characterMale"` 57 | CharacterFemale []string `yaml:"characterFemale"` 58 | } 59 | 60 | func ParseFile(file string) (res NamesResource, err error) { 61 | r, err := ioutil.ReadFile(file) 62 | if err != nil { 63 | return res, err 64 | } 65 | 66 | if err := yaml.Unmarshal(r, &res); err != nil { 67 | return res, err 68 | } 69 | 70 | return res, nil 71 | } 72 | ``` 73 | 74 | 其中需注意的是, go-yaml 預設會先把 field 轉全小寫,再去 YAML 的資料裡面找。而 LastNames field 預設會找 `lastnames` 的欄位,然後就找不到,這時會需要用 `yaml:"lastNames"` 指定要找的欄位。 75 | 76 | 其他兩個 Command 開頭先執行吧!之後有空再來想想該怎麼重構: 77 | 78 | ```go 79 | t, _ := provider.ParseFile(c.GlobalString("provider")) 80 | 81 | fmt.Println(t) 82 | ``` 83 | 84 | --- 85 | 86 | 今天這樣太簡單了。資料都有了,不如就把亂數取名實作出來吧! 87 | 88 | 回憶一下之前取名的方法(順便騙版面): 89 | 90 | ```go 91 | func (generator *Generator) Name() string { 92 | length := len(names) 93 | 94 | return names[generator.rand.Intn(length)] 95 | } 96 | ``` 97 | 98 | 這裡是用 `generator.rand` 取得亂數,再去口袋名單裡面取。但現在口袋名單變成了 `NameResource` ,最簡單的做法就是在 Generator 加一個 Resource field : 99 | 100 | ```go 101 | type Generator struct { 102 | rand *rand.Rand 103 | Resource NamesResource 104 | } 105 | ``` 106 | 107 | 然後直接在 Command Action 指定: 108 | 109 | ```go 110 | res, _ := provider.ParseFile(c.GlobalString("provider")) 111 | 112 | generator := provider.Create() 113 | generator.Resource = res 114 | ``` 115 | 116 | 這樣裡面就會有資料可以用了。接著參考 [Faker](https://github.com/fzaninotto/Faker/blob/v1.7.1/src/Faker/Provider/Person.php) 的做法,它會把姓跟名分開,實作如下: 117 | 118 | ```go 119 | func (generator *Generator) Name() string { 120 | return generator.LastName() + generator.FirstName() 121 | } 122 | 123 | func (generator *Generator) LastName() string { 124 | length := len(generator.Resource.LastNames) 125 | randomIndex := generator.rand.Intn(length) 126 | 127 | return generator.Resource.LastNames[randomIndex] 128 | } 129 | 130 | func (generator *Generator) FirstName() string { 131 | merge := append(generator.Resource.CharacterMale, generator.Resource.CharacterFemale...) 132 | length := len(merge) 133 | randomIndex := generator.rand.Intn(length) 134 | 135 | return merge[randomIndex] 136 | } 137 | ``` 138 | 139 | ## 展示 140 | 141 | 執行結果如下: 142 | 143 | ``` 144 | $ go run main.go generate 145 | Generate 10 146 | 楊雅 147 | 李婷 148 | 黃明 149 | 劉雅 150 | 陳明 151 | 楊豪 152 | 黃春 153 | 劉家 154 | 王嬌 155 | 趙家 156 | ``` 157 | 158 | 詳細程式可以參考 [PR Day 20](https://github.com/MilesChou/namer/pull/6) 159 | 160 | ## 參考資料 161 | 162 | * [Faker](https://github.com/fzaninotto/Faker) 163 | * [Faker(1)--假文產生器](https://github.com/MilesChou/book-decompose-wheels/blob/master/docs/day06.md) | 輪子們,聽口令,大部分解開始! 164 | -------------------------------------------------------------------------------- /docs/day26.md: -------------------------------------------------------------------------------- 1 | # Refactoring Name Provider 2 | 3 | 前面 25 天,我們已經成功寫出了一個 CLI App 以及 Web App ,包括交付與部署都有實作,這次鐵人賽主題的基本要求已經算達標了。 4 | 5 | 剩下五天的目標將會是改善這個程式,無論是功能上的改進或是[重構][看到 code 寫成這樣我也是醉了,不如試試重構?]。 6 | 7 | 首先有一個問題非常明顯:台灣人的名通常都是兩個字,我們必須保留一點彈性在字數調整上。 8 | 9 | ## 分析 10 | 11 | 這個問題一直拖著沒改,其實是因為要先重構原本的程式碼,才會好動工。 12 | 13 | 原本的 `provider/name.go` 的程式碼片段如下: 14 | 15 | ```go 16 | func (generator *Generator) LastName() string { 17 | length := len(generator.Resource.LastNames) 18 | randomIndex := generator.rand.Intn(length) 19 | 20 | return generator.Resource.LastNames[randomIndex] 21 | } 22 | 23 | func (generator *Generator) FirstName() string { 24 | merge := append(generator.Resource.CharacterMale, generator.Resource.CharacterFemale...) 25 | length := len(merge) 26 | randomIndex := generator.rand.Intn(length) 27 | 28 | return merge[randomIndex] 29 | } 30 | ``` 31 | 32 | 我們可以發現, `LastName` 與 `FirstName` 有一個模式是一樣的:在某個 collection 裡面隨便取一筆資料。 33 | 34 | 因此第一步可以先把這個行為抽出,寫成另一個函式 `pickCharacter` : 35 | 36 | ```go 37 | func (generator *Generator) LastName() string { 38 | return generator.pickCharacter(generator.Resource.LastNames) 39 | } 40 | 41 | func (generator *Generator) FirstName() string { 42 | merge := append(generator.Resource.CharacterMale, generator.Resource.CharacterFemale...) 43 | 44 | return generator.pickCharacter(merge) 45 | } 46 | 47 | func (generator *Generator) pickCharacter(collection []string) string { 48 | length := len(collection) 49 | randomIndex := generator.rand.Intn(length) 50 | 51 | return collection[randomIndex] 52 | } 53 | ``` 54 | 55 | 再來,台灣人的名大部分是兩個字,但姓和單名會是一個字。或許 `pickCharacter` 多一個參數來決定要取幾個字會是個好選擇。先把參數加進去試看看: 56 | 57 | ```go 58 | func (generator *Generator) LastName() string { 59 | return generator.pickCharacter(generator.Resource.LastNames, 1) 60 | } 61 | 62 | func (generator *Generator) FirstName() string { 63 | merge := append(generator.Resource.CharacterMale, generator.Resource.CharacterFemale...) 64 | 65 | return generator.pickCharacter(merge, 2) 66 | } 67 | 68 | func (generator *Generator) pickCharacter(collection []string, count int) (chars string) { 69 | length := len(collection) 70 | 71 | for i := 0; i < count; i++ { 72 | randomIndex := generator.rand.Intn(length) 73 | chars = chars + collection[randomIndex] 74 | } 75 | 76 | return chars 77 | } 78 | ``` 79 | 80 | 接著要開放這個數字讓外界可以呼叫,才會有實際的價值,這裡的做法是先把原本的 `FirstName` 改名為 `firstName` 並加入 `count` 參數,接著開出三個接口 `FirstName` 、 `FirstNameSingle` 、 `FirstNameDouble` : 81 | 82 | ```go 83 | func (generator *Generator) FirstName() string { 84 | return generator.firstName(generator.rand.Intn(2) + 1) 85 | } 86 | 87 | func (generator *Generator) FirstNameSingle() string { 88 | return generator.firstName(1) 89 | } 90 | 91 | func (generator *Generator) FirstNameDouble() string { 92 | return generator.firstName(2) 93 | } 94 | 95 | func (generator *Generator) firstName(count int) string { 96 | merge := append(generator.Resource.CharacterMale, generator.Resource.CharacterFemale...) 97 | 98 | return generator.pickCharacter(merge, count) 99 | } 100 | ``` 101 | 102 | 原本的 `Name` 函式行為改成會亂數取單名與複名。如果要指定單名或複名的話,我們必須再加開兩個函式 `NameSingle` 與 `NameDouble` : 103 | 104 | ```go 105 | func (generator *Generator) NameSingle() string { 106 | return generator.LastName() + generator.FirstNameSingle() 107 | } 108 | 109 | func (generator *Generator) NameDouble() string { 110 | return generator.LastName() + generator.FirstNameDouble() 111 | } 112 | ``` 113 | 114 | 指令也需要重構,明天再針對指令做調整。 115 | 116 | ## 展示 117 | 118 | ``` 119 | $ go run main.go generate 120 | Generate 10 121 | 楊志雅 122 | 劉明志 123 | 張家豪 124 | 趙春 125 | 趙志婷 126 | 王春春 127 | 劉志 128 | 趙嬌家 129 | 李志 130 | 王婷 131 | ``` 132 | 133 | 程式碼可以參考 [PR Day 26](https://github.com/MilesChou/namer/pull/12) 134 | 135 | ## 參考資料 136 | 137 | * [看到 code 寫成這樣我也是醉了,不如試試重構?][] 138 | 139 | [看到 code 寫成這樣我也是醉了,不如試試重構?]: https://github.com/MilesChou/book-refactoring-30-days 140 | -------------------------------------------------------------------------------- /docs/day07.md: -------------------------------------------------------------------------------- 1 | # Array Type 2 | 3 | Go 語言的世界裡,陣列為固定長度,元素型態與長度都是陣列型態的一部分。 4 | 5 | ## 宣告 6 | 7 | 使用 `[n]type` 來宣告一個陣例,其中 `n` 是數字, `type` 則為型態,下面簡單的範例: 8 | 9 | ```go 10 | package main 11 | 12 | import "fmt" 13 | 14 | func main() { 15 | var arr [5]int 16 | arr[0] = 5 17 | arr[1] = 4 18 | 19 | fmt.Println(arr) // [5 4 0 0 0] 20 | } 21 | ``` 22 | 23 | 這裡宣告 arr 的型態是 `[5]int` ,因「元素型態與長度都是陣列型態的一部分」,所以 `[10]int` 與 `[5]int` 會是不同的型態。 24 | 25 | 另外因為後面三個元素並沒有指定新值,但可以看到它的初值是 `0` ,也就是 `zero value` 。 26 | 27 | 宣告給值的話要使用 `:=` 指定,也可以使用不固定長度 `[...]` 來宣告,它會依後面給值的數量來決定陣列長度: 28 | 29 | ```go 30 | package main 31 | 32 | import "fmt" 33 | 34 | func main() { 35 | arr1 := [3]int{1, 2, 3} 36 | arr2 := [5]int{1, 2, 3} 37 | arr3 := [...]int{1, 2, 3, 4, 5} 38 | 39 | fmt.Println(arr1) // [1 2 3] 40 | fmt.Println(arr2) // [1 2 3 0 0] 41 | fmt.Println(arr3) // [1 2 3 4 5] 42 | } 43 | ``` 44 | 45 | 如果存取陣列超過範圍時,會出現 `out of bounds` 的編譯錯誤 46 | 47 | ```go 48 | package main 49 | 50 | import "fmt" 51 | 52 | func main() { 53 | arr := [...]int{1, 2, 3, 4, 5} 54 | 55 | fmt.Println(arr[9]) // invalid array index 9 (out of bounds for 5-element array) 56 | } 57 | ``` 58 | 59 | ## 複製 60 | 61 | 陣列的內容是值,所以也可以複製給另一個變數,如: 62 | 63 | ```go 64 | package main 65 | 66 | import "fmt" 67 | 68 | func main() { 69 | arr := [...]int{1, 2, 3, 4, 5} 70 | 71 | var arrCopy [5]int 72 | 73 | arrCopy := arr 74 | 75 | fmt.Println(arr) // [1 2 3 4 5] 76 | fmt.Println(arrCopy) // [1 2 3 4 5] 77 | 78 | arr[0] = 10 79 | 80 | fmt.Println(arr) // [10 2 3 4 5] 81 | fmt.Println(arrCopy) // [1 2 3 4 5] 82 | 83 | var arrErr [10]int 84 | arrErr = arrCopy // cannot use arrCopy (type [5]int) as type [10]int in assignment 85 | } 86 | ``` 87 | 88 | 最後一行是型態不一致的錯,型態與長度相同,才有辦法複製值。 89 | 90 | ## 比較 91 | 92 | 陣列可以用 `==` 與 `!=` 來比較內容,一樣型態與長度相同才能比較。 93 | 94 | ```go 95 | package main 96 | 97 | import "fmt" 98 | 99 | func main() { 100 | arr := [...]int{1, 2, 3, 4, 5} 101 | arrCopy := arr 102 | 103 | fmt.Println(arr) // [1 2 3 4 5] 104 | fmt.Println(arrCopy) // [1 2 3 4 5] 105 | fmt.Println(arr == arrCopy) // true 106 | 107 | arr[0] = 10 108 | 109 | fmt.Println(arr) // [10 2 3 4 5] 110 | fmt.Println(arrCopy) // [1 2 3 4 5] 111 | fmt.Println(arr == arrCopy) // false 112 | } 113 | ``` 114 | 115 | ## 巢狀陣列 116 | 117 | 當宣告陣列時, `int` 是一種型態,所以我們在前面加上 `[n]` 即成為 `int` 的陣列型態。 118 | 119 | 同樣地, `[n]int` 也是一種型態,在前面加上 `[m]` 就會成為 `[n]int` 的陣列型態。 120 | 121 | 下面是一個巢狀陣列的例子: 122 | 123 | ```go 124 | package main 125 | 126 | import "fmt" 127 | 128 | func main() { 129 | var arr [3][2]int 130 | fmt.Println(arr) // [[0 0] [0 0] [0 0]] 131 | } 132 | ``` 133 | 134 | 上面可以觀察到,這是一個有 3 個 `[n]int` 元素的陣列。 135 | 136 | 巢狀陣列也可以宣告同時指定初值: 137 | 138 | ```go 139 | package main 140 | 141 | import "fmt" 142 | 143 | func main() { 144 | arr1 := [3][2]int{{1, 2}, {3, 4}, {5,6}} 145 | fmt.Println(arr1) // [[1 2] [3 4] [5 6]] 146 | 147 | arr2 := [...][2]int{{1, 2}, {3, 4}, {5,6}} 148 | fmt.Println(arr2) // [[1 2] [3 4] [5 6]] 149 | 150 | arr3 := [...][...]int{{1, 2}, {3, 4}, {5,6}} // use of [...] array outside of array literal 151 | fmt.Println(arr3) 152 | } 153 | ``` 154 | 155 | 其中 arr3 不能這樣宣告的原因是:因為長度也是型態的一部分,宣告陣列時元素的型態必須是確定的,所以 `[2]int` 才能拿來做最外層陣列的元素型態, 而 `[...]int` 不行。 156 | 157 | ## 走訪 158 | 159 | 陣列除了可以用 for + `len()` 來走訪外,也可以使用 [`for range`][For statements] : 160 | 161 | ```go 162 | package main 163 | 164 | import "fmt" 165 | 166 | func main() { 167 | arr := [3]int{1, 2, 3} 168 | for index, element := range arr { 169 | fmt.Printf("%d => %d\n", index, element) 170 | } 171 | } 172 | ``` 173 | 174 | ## 今日回顧 175 | 176 | 今天學習了陣列宣告的基本,也多了解了兩個關鍵字 `for` `range` 和一個 function `len()` 。 177 | 178 | ## 參考資料 179 | 180 | * [身為複合值的陣列](https://openhome.cc/Gossip/Go/Array.html) 181 | * [Index expressions](https://golang.org/ref/spec#Index_expressions) 182 | * [For statements][] 183 | 184 | [For statements]: https://golang.org/ref/spec#For_statements 185 | -------------------------------------------------------------------------------- /docs/day30.md: -------------------------------------------------------------------------------- 1 | # The End 2 | 3 | 最後一天,再找個需求來做一下好了。 4 | 5 | 指令雖然完成了,但是下載下來如果使用者沒有 YAML 檔會無法使用。但理論上,程式應該提供這個檔案。 6 | 7 | 因此,今天來實作初始化 YAML 檔的指令。 8 | 9 | ## 分析 10 | 11 | facade 定義一個新的函式,名稱就叫 `initial` 。 12 | 13 | 上次讀檔是用 `ioutil.ReadFile()` ,這次寫檔是 `ioutil.WriteFile()` 。 14 | 15 | 看樣子資訊足夠了。 16 | 17 | ## 開工 18 | 19 | 將初始化資訊放在 `provider/resource.go` 裡: 20 | 21 | ```go 22 | const DefaultTemplate = ` 23 | lastNames: 24 | - 李 25 | - 王 26 | - 張 27 | - 劉 28 | - 陳 29 | - 楊 30 | - 趙 31 | - 黃 32 | 33 | characterMale: 34 | - 家 35 | - 豪 36 | - 志 37 | - 明 38 | 39 | characterFemale: 40 | - 雅 41 | - 婷 42 | - 春 43 | - 嬌 44 | ` 45 | ``` 46 | 47 | 接著寫初始化的方法,記得要判斷檔案存不存在,不然好不容易改好內容後,又被覆寫會很不開心: 48 | 49 | ```go 50 | func InitFileDefault(filename string) error { 51 | _, err := os.Stat(filename) 52 | if err != nil && os.IsNotExist(err) { 53 | return ioutil.WriteFile(filename, []byte(DefaultTemplate), 0644) 54 | } 55 | 56 | return nil 57 | } 58 | ``` 59 | 60 | Command 的 Action 非常簡單: 61 | 62 | ```go 63 | func(c *cli.Context) error { 64 | err := provider.InitFileDefault(c.GlobalString("provider")) 65 | 66 | return err 67 | }, 68 | ``` 69 | 70 | 打完收工 71 | 72 | --- 73 | 74 | 好像有點太少,再做一點需求好了:前天實作 Command 的時候,忘了把單純產生名的方法加入。 75 | 76 | 設計簡單做就好。先加一個靜態變數 `GenerateFirstNameOnly` 在 facade 裡: 77 | 78 | ```go 79 | var ( 80 | // ... 81 | GenerateFirstNameOnly bool 82 | ) 83 | 84 | func reset() { 85 | // ... 86 | GenerateFirstNameOnly = false 87 | } 88 | ``` 89 | 90 | 接著加一個 CLI 參數 `--first-name-only` 是布林值: 91 | 92 | ```go 93 | cli.BoolFlag{ 94 | Name: "first-name-only", 95 | Usage: "只產生名", 96 | }, 97 | ``` 98 | 99 | CLI 布林值的取值非常簡單: 100 | 101 | ```go 102 | facade.GenerateFirstNameOnly = c.Bool("first-name-only") 103 | ``` 104 | 105 | 接著要調整 `provider/name.go` 產生名的函式。因為再加參數的話會太複雜,因此需要重構一下。把產生全名(`Name`)和只產生名(`NameFirstNameOnly`)的函式分開,然後產生全名的函式去使用產生名的函式即可: 106 | 107 | ```go 108 | func (generator *Generator) Name(gender string, firstNameNum int) (name string, err error) { 109 | lastName, err := generator.NameFirstNameOnly(gender, firstNameNum) 110 | 111 | if err != nil { 112 | return 113 | } 114 | 115 | name = generator.LastName() + lastName 116 | 117 | return name, nil 118 | } 119 | 120 | func (generator *Generator) NameFirstNameOnly(gender string, firstNameNum int) (name string, err error) { 121 | switch gender { 122 | case "": 123 | name = generator.firstName(firstNameNum) 124 | case "male": 125 | name = generator.firstNameMale(firstNameNum) 126 | case "female": 127 | name = generator.firstNameFemale(firstNameNum) 128 | default: 129 | err = errors.New(fmt.Sprintf("gender '%s' is invalid", gender)) 130 | } 131 | 132 | return 133 | } 134 | ``` 135 | 136 | 這樣 Facade 使用也會非常簡單,使用 if 判斷要使用哪個函式即可: 137 | 138 | ```go 139 | if GenerateFirstNameOnly { 140 | name, err = generator.NameFirstNameOnly(GenerateGender, GenerateFirstNameNum) 141 | } else { 142 | name, err = generator.Name(GenerateGender, GenerateFirstNameNum) 143 | } 144 | ``` 145 | 146 | 再來 `ServeCommand` 實作也非常簡單,只要多設定一個 query string 就行了: 147 | 148 | ```go 149 | firstNameOnly := g.DefaultQuery("firstNameOnly", "10") 150 | 151 | if firstNameOnly == "1" { 152 | facade.GenerateFirstNameOnly = true 153 | } else { 154 | facade.GenerateFirstNameOnly = false 155 | } 156 | ``` 157 | 158 | ## 展示 159 | 160 | 指令如下: 161 | 162 | ``` 163 | $ go run main.go generate --amount 3 --first-name-only 164 | 嬌志 165 | 婷婷 166 | 婷春 167 | ``` 168 | 169 | HTTP Server 如下: 170 | 171 | ``` 172 | $ curl "http://localhost:8080/generate?amount=3&firstNameOnly=1" 173 | ["明志","春志","婷嬌"] 174 | ``` 175 | 176 | 原始碼可以參考 [PR Day 30](https://github.com/MilesChou/namer/pull/15) 177 | 178 | ## 最後的回顧 179 | 180 | 當初覺得 Go 非常難懂,最主要覺得難以理解的應該就是強型態的操作,與常數的表示方法。但在這 30 天裡有認真把它們翻過一次,然後多想幾個不一樣的範例試幾次後,現在漸漸有了解它的原理了。非常感謝良葛格詳細的筆記,讓我們能對 Go 的型態操作有較清楚的了解。 181 | 182 | 寫到現在,覺得 Go 確實蠻好上手的, 30 天就可以從只會 Hello World 到寫出一個簡單 CLI App ,雖然還缺少很多重要的觀念如測試、多執行緒設計等,但這就留給未來有機會再學了。 183 | 184 | 最後,很開心總算成功完成鐵人賽成就了!希望開發歷程的學習記錄,能幫助的到大家。對[鐵人賽文章](https://github.com/MilesChou/book-start-golang-30-days)或 [Namer](https://github.com/MilesChou/namer) 有任何建議或問題都歡迎發 issue ,謝謝大家! 185 | -------------------------------------------------------------------------------- /docs/day04.md: -------------------------------------------------------------------------------- 1 | # Constants 2 | 3 | Go 語言的常數有分幾種類型: 4 | 5 | * *boolean constants* ,布林常數。 6 | * *rune constants* ,表示字元的常數。 7 | * *integer constants* ,整數常數 8 | * *floating-point constants* ,浮點數常數 9 | * *complex constants* ,複數常數 10 | * *string constants* ,字串常數 11 | 12 | 這些常數都可以用實字(literal)表示,實字又分成下面幾種: 13 | 14 | * *rune literal* , Rune 實字 15 | * *integer literal* ,整數實字 16 | * *floating-point literal* ,浮點數實字 17 | * *imaginary literal* ,虛數實字 18 | * *string literal* ,字串實字 19 | 20 | 常數有可能是已定義型態(typed)或是未定型態(untyped),實字常數、 `true` 、 `false` 、 `iota` 都屬於未定型態。 21 | 22 | 另外較特別的是,常數運算式裡的運算元都是未定型態時,運算完的結果也會是未定型態。比方說,下面都是未定型態: 23 | 24 | ```go 25 | 10 // 10, Untyped integer constant. 26 | 10 + 20 // 30, Untyped integer constant. 27 | 10 / 20 // 0, Untyped integer constant. 28 | ``` 29 | 30 | 但如果有一個型態是確定的,那運算完的結果也會是確定的,如: 31 | 32 | ```go 33 | int32(10) // 10, type int32 34 | int32(10) + 20 // 30, type int32 35 | float64(10) / 20 // 0.5, type float64 36 | ``` 37 | 38 | ## Boolean constants 39 | 40 | 布林常數是內建的常數,就只有兩個: `true` 和 `false` 41 | 42 | 原始碼實作也蠻有趣的(程式碼來自 [`builtin.go`](https://github.com/golang/go/blob/master/src/builtin/builtin.go#L16-L20)): 43 | 44 | ```go 45 | // true and false are the two untyped boolean values. 46 | const ( 47 | true = 0 == 0 // Untyped bool. 48 | false = 0 != 0 // Untyped bool. 49 | ) 50 | ``` 51 | 52 | ## Rune constants 53 | 54 | Rune 常數使用 Rune 實字(rune literal)來表示,它其實是代表一個 Unicode 的整數。可以使用單引號 `'` 括住 Unicode 字元,或是 byte 值來表示,如下面的範例是輸出 `a` 的三種方法,與輸出 `中` 的三種方法: 55 | 56 | ```go 57 | package main 58 | 59 | import ( 60 | "fmt" 61 | ) 62 | 63 | func main() { 64 | fmt.Println(string('a')) 65 | fmt.Println(string('\141')) 66 | fmt.Println(string('\x61')) 67 | fmt.Println(string('中')) 68 | fmt.Println(string('\u4e2d')) 69 | fmt.Println(string('\U00004e2d')) 70 | fmt.Println(string('\n')) 71 | } 72 | ``` 73 | 74 | > `string()` 函式為強制轉型字串 75 | 76 | Byte 值的表示方法: 77 | 78 | * 直接給字元 `a` 79 | * `\` 開頭為八進制,後面必須是 3 個八進位的字元(`[0-9]{3}`) 80 | * `\x` 開頭為十六進制表示,後面必須是 2 個十六進位的字元(`[0-9a-f]{2}`) 81 | 82 | Unicode 表示方法: 83 | 84 | * 直接給字元 `中` 85 | * `\u` 開頭,後面必須是 4 個十六進位的字元(`[0-9a-f]{4}`) 86 | * `\U` 開頭,後面必須是 8 個十六進位的字元(`[0-9a-f]{8}`) 87 | * 跳脫字元: `\` 後面接 `a` `b` `f` `n` `r` `t` `v` `\` `'` `"` 。 88 | 89 | ## Integer constants 90 | 91 | 數字常數使用數字實字(integer literal)表示。數字實字有三種表示法: 92 | 93 | * 十進位,跟大部分的程式碼一樣,為非 `0` 開頭的連續數字(`[1-9][0-9]+`) 94 | * 八進位, `0` 開頭,後面接八進位數字(`0[0-9]+`) 95 | * 十六進位, `0` 開頭,後面接八進位數字(`0[x|X][0-9a-fA-F]+`) 96 | 97 | ## Floating-point constants 98 | 99 | 浮點數常數使用浮點數實字(floating-point literal)表示,浮點數使用的兩種表示法:小數點與科學符號,下面是幾個例子可以參考: 100 | 101 | ```go 102 | package main 103 | 104 | import ( 105 | "fmt" 106 | ) 107 | 108 | func main() { 109 | // 10.0 110 | fmt.Println(10.) 111 | fmt.Println(10.0) 112 | fmt.Println(010.0) 113 | fmt.Println(10.e+0) 114 | fmt.Println(1E1) 115 | 116 | // 0.1 117 | fmt.Println(.1e+0) 118 | fmt.Println(.1E0) 119 | fmt.Println(.1) 120 | 121 | // 10.1 122 | fmt.Println(10.1) 123 | fmt.Println(1.01E1) 124 | } 125 | ``` 126 | 127 | ## Complex constants 128 | 129 | 複數常數為數字實字加虛數實字(imaginary literal)組合而成。 130 | 131 | 虛數實字的表示法為: 132 | 133 | * 十進位 + 小寫 `i` ,如 `10i` 134 | * 浮點數 + 小寫 `i` ,如 `1E1i` 135 | 136 | 而複數常數的範例如下: 137 | 138 | ```go 139 | package main 140 | 141 | import ( 142 | "fmt" 143 | ) 144 | 145 | func main() { 146 | fmt.Println(10 + 10i) 147 | fmt.Println(1E1 + 1E1i) 148 | } 149 | ``` 150 | 151 | ## String constants 152 | 153 | 字串常數使用字串實字(string literal)表示。如果是純字串,可以使用 `` ` `` 括要表示的字串,如: 154 | 155 | ```go 156 | package main 157 | 158 | import ( 159 | "fmt" 160 | ) 161 | 162 | func main() { 163 | fmt.Println(`\n`) 164 | } 165 | ``` 166 | 167 | 這樣就會輸出 `\n` 兩個字元 168 | 169 | 如果需要轉譯 rune 常數為字元的話,可以用雙引號 `"` 括要表示的字串,如: 170 | 171 | ```go 172 | package main 173 | 174 | import ( 175 | "fmt" 176 | ) 177 | 178 | func main() { 179 | fmt.Println("這是\u4e2d\u6587") 180 | } 181 | ``` 182 | 183 | 這樣就會輸出 `這是中文` 184 | 185 | ## 今日回顧 186 | 187 | 今天先介紹基本的實字表示,再來要解釋變數型態應該就會比較好懂了。 188 | 189 | ## 參考資料 190 | 191 | * [Constants][] | The Go Programming Language Specification 192 | 193 | [Constants]: https://golang.org/ref/spec#Constants 194 | -------------------------------------------------------------------------------- /docs/day19.md: -------------------------------------------------------------------------------- 1 | # File 2 | 3 | 文字清單如果都寫死在程式裡的話,擴充性就太差了,預期它應該要可以從檔案抓出文字清單。 4 | 5 | ## 分析 6 | 7 | 基本的檔案操作應該不大會有問題,要思考的會是,該要用什麼樣的格式來存放文字清單? 8 | 9 | 另外程式應該要從哪載入文字清單?固定位置?或是 Flags 參數帶給程式? 10 | 11 | 格式的部分,會以好閱讀與修改為主,因此會選擇 [YAML](http://www.yaml.org/) ,套件使用 [`go-yaml`](https://github.com/go-yaml/yaml) ,載入路徑會使用 Flags 參數帶入。 12 | 13 | `go-yaml` 試了一下,它支援輸出 Struct 或是 Map 。 Struct 的參數必須要定義公開,而且要跟 YAML 格式相符,不然會存放失敗, Map 則是進來什麼都吃,沒有這些限制。目前情境還很單純,使用 Struct 是個可行的選擇。 14 | 15 | 但它只吃字串,所以必須要寫一段讀檔程式,今天就來試試 Flags 參數加讀檔串接吧。 16 | 17 | ## 開工 18 | 19 | 因為 command 要做的事,目前是硬塞到 `main.go` 裡,這樣會違反[單一職責原則][Refactoring Day 7]。在開始前先重構,把職責分離清楚,不然後面應該會更難搞。 20 | 21 | 做法很簡單:開一個 `command` 的目錄,新增兩個 `generate.go` 與 `status.go` ,把 Command 原本要給的值,換到這兩個檔案裡面定義即可。 22 | 23 | `command/generate.go` 的內容如下: 24 | 25 | ```go 26 | var ( 27 | GenerateCommand = cli.Command{ 28 | Name: "generate", 29 | Usage: "產生假名", 30 | Flags: []cli.Flag{ 31 | cli.StringFlag{ 32 | Name: "num", 33 | Value: "10", 34 | Usage: "產生數量", 35 | }, 36 | }, 37 | Action: func(c *cli.Context) error { 38 | return generate(c) 39 | }, 40 | } 41 | ) 42 | 43 | func generate(c *cli.Context) error { 44 | num, err := strconv.Atoi(c.String("num")) 45 | 46 | if err != nil { 47 | return err 48 | } 49 | 50 | fmt.Println("Generate " + strconv.Itoa(num)) 51 | 52 | generator := provider.Create() 53 | 54 | for i := 0; i < num; i++ { 55 | fmt.Println(generator.Name()) 56 | } 57 | 58 | return nil 59 | } 60 | ``` 61 | 62 | `command/status.go` 的內容目前只是樣版,如下: 63 | 64 | ```go 65 | package command 66 | 67 | import ( 68 | "fmt" 69 | "github.com/urfave/cli" 70 | ) 71 | 72 | var ( 73 | StatusCommand = cli.Command{ 74 | Name: "status", 75 | Usage: "狀態", 76 | Action: func(c *cli.Context) error { 77 | fmt.Println("Hello Status") 78 | 79 | return nil 80 | }, 81 | } 82 | ) 83 | ``` 84 | 85 | 這樣 `main.go` 就會變得非常簡單: 86 | 87 | ```go 88 | package main 89 | 90 | import ( 91 | "os" 92 | "github.com/MilesChou/namer/command" 93 | "github.com/urfave/cli" 94 | ) 95 | 96 | func main() { 97 | app := cli.NewApp() 98 | app.Name = "Namer" 99 | app.Commands = []cli.Command{ 100 | command.GenerateCommand, 101 | command.StatusCommand, 102 | } 103 | 104 | app.Run(os.Args) 105 | } 106 | ``` 107 | 108 | 詳細重構程式可以參考 [PR Day 19 前重構](https://github.com/MilesChou/namer/pull/4) 109 | 110 | --- 111 | 112 | 下面開始實作參數與讀檔。撞了很多牆後,參考[別人的做法](https://github.com/rancher/dapper/blob/master/main.go#L104-L108),發現應該是需要先 `os.Chdir()` 後,再 `os.Stat()` 就能找得到了,來試看看: 113 | 114 | 先建立 `names.yml` 檔案,然後在 `command/status.go` 的 Action 輸入下面的程式 115 | 116 | ```go 117 | os.Chdir(".") 118 | os.Stat("names.yml") 119 | fmt.Println(os.Stat("names.yml")) 120 | ``` 121 | 122 | 輸出: 123 | 124 | ``` 125 | &{names.yml 5 420 {579465000 63650306753 0x121e060} {16777220 33188 1 7295335 673970142 1079850989 0 [0 0 0 0] {1514709976 915571588} {1514709953 579465000} {1514709953 579483767} {1514709952 195533478} 5 8 4194304 0 0 0 [0 0]}} 126 | ``` 127 | 128 | 看來是可行的,接著使用 [`io/ioutil`](https://golang.org/pkg/io/ioutil/) 來取得 byte 資料: 129 | 130 | ```go 131 | os.Chdir(".") 132 | ra, _ := ioutil.ReadFile("names.yml") 133 | 134 | fmt.Println(`------ File Content Start ------`) 135 | fmt.Printf("%s", ra) 136 | fmt.Println(`------- File Content End -------`) 137 | ``` 138 | 139 | 輸出: 140 | 141 | ```go 142 | ------ File Content Start ------ 143 | Hello File! 144 | ------- File Content End ------- 145 | ``` 146 | 147 | 下一步把傳入的檔案參數化,使用 `string` 格式: 148 | 149 | ```go 150 | Flags: []cli.Flag{ 151 | cli.StringFlag{ 152 | Name: "provider", 153 | Value: "names.yml", 154 | Usage: "名字倉庫", 155 | }, 156 | } 157 | ``` 158 | 159 | 最後 Action 的函式會長這樣: 160 | 161 | ```go 162 | Action: func(c *cli.Context) error { 163 | os.Chdir(".") 164 | 165 | r, _ := ioutil.ReadFile(c.String("provider")) 166 | 167 | fmt.Println(`------ File Content Start ------`) 168 | fmt.Printf("%s", r) 169 | fmt.Println(`------- File Content End -------`) 170 | 171 | return nil 172 | } 173 | ``` 174 | 175 | 大功告成! 176 | 177 | 詳細程式可以參考 [PR Day 19](https://github.com/MilesChou/namer/pull/5) 178 | 179 | ## 問題 180 | 181 | 讀檔應該會是全域的設定功能,因此下次要開始前,必須要先重構這部分的程式。 182 | 183 | ## 參考資料 184 | 185 | * [SOLID 之 單一職責原則(Single responsibility principle)][Refactoring Day 7] | 看到 code 寫成這樣我也是醉了,不如試試重構? 186 | 187 | [Refactoring Day 7]: https://github.com/MilesChou/book-refactoring-30-days/blob/master/docs/day07.md 188 | -------------------------------------------------------------------------------- /docs/day08.md: -------------------------------------------------------------------------------- 1 | # Slice Type 2 | 3 | Slice 跟[陣列][Day 7]使用起來很像,而最大的不同是,陣列是值, Slice 是參考到一個陣列。 4 | 5 | ## 建立 6 | 7 | 要建立一個全新的 Slice 有兩種方法,一個是使用 `make` 函式: 8 | 9 | ```go 10 | package main 11 | 12 | import "fmt" 13 | 14 | func main() { 15 | slice := make([]int, 5) 16 | 17 | fmt.Println(slice) // [0 0 0 0 0] 18 | } 19 | ``` 20 | 21 | 另一個方法則是指定初值,雖然用法跟陣列很像,但形態不一樣就不能拿來一起比較。 22 | 23 | ```go 24 | package main 25 | 26 | import "fmt" 27 | import "reflect" 28 | 29 | func main() { 30 | slice := []int{1, 2, 3, 4, 5} 31 | 32 | fmt.Println(slice) // [1 2 3 4 5] 33 | 34 | arr := [...]int{1, 2, 3, 4, 5} 35 | 36 | fmt.Println(reflect.TypeOf(slice)) // []int 37 | fmt.Println(reflect.TypeOf(arr)) // [5]int 38 | fmt.Println(arr == slice) // invalid operation: arr == slice (mismatched types [5]int and []int) 39 | } 40 | ``` 41 | 42 | Slice 是參考到一個陣列,可以看下面這個範列了解: 43 | 44 | ```go 45 | package main 46 | 47 | import "fmt" 48 | 49 | func main() { 50 | slice := []int{1, 2, 3, 4, 5} 51 | ref := slice 52 | 53 | fmt.Println(slice) // [1 2 3 4 5] 54 | fmt.Println(ref) // [1 2 3 4 5] 55 | 56 | slice[0] = 100 57 | 58 | fmt.Println(slice) // [100 2 3 4 5] 59 | fmt.Println(ref) // [100 2 3 4 5] 60 | 61 | ref[4] = 500 62 | 63 | fmt.Println(slice) // [100 2 3 4 500] 64 | fmt.Println(ref) // [100 2 3 4 500] 65 | } 66 | ``` 67 | 68 | ## 操作 69 | 70 | 我們可以對 Slice 做一些操作,如 `len` 函式可以查長度, `cap` 可以查參考的陣列有多少容量: 71 | 72 | ```go 73 | package main 74 | 75 | import "fmt" 76 | 77 | func main() { 78 | slice := []int{1, 2, 3} 79 | 80 | fmt.Println(len(slice)) // 3 81 | fmt.Println(cap(slice)) // 3 82 | } 83 | ``` 84 | 85 | `append` 函式可以追加新元素在 Slice 最後面,下面是一個小範例: 86 | 87 | ```go 88 | package main 89 | 90 | import "fmt" 91 | 92 | func main() { 93 | slice1 := []int{1, 2, 3} 94 | 95 | fmt.Println(slice1) // [1 2 3] 96 | fmt.Println(len(slice1)) // 3 97 | fmt.Println(cap(slice1)) // 3 98 | 99 | slice2 := append(slice1, 10) 100 | 101 | fmt.Println(slice2) // [1 2 3 10] 102 | fmt.Println(len(slice2)) // 4 103 | fmt.Println(cap(slice2)) // 6 104 | 105 | slice3 := append(slice2, 20) 106 | 107 | fmt.Println(slice3) // [1 2 3 10 20] 108 | fmt.Println(len(slice3)) // 5 109 | fmt.Println(cap(slice3)) // 6 110 | 111 | slice3[0] = 100 112 | fmt.Println(slice1) // [1 2 3] 113 | fmt.Println(slice2) // [100 2 3 10] 114 | fmt.Println(slice3) // [100 2 3 10 20] 115 | } 116 | ``` 117 | 118 | 上面可以觀察到 `slice1` 加入新元素產生出 `slice2` 有發生長度與容量的變化(長度 + 1 ,容量 * 2),並且最後面 `slice1` 與 `slice2` 的值並沒有參考到同個陣列。 119 | 120 | 另外 `slice2` 加入新元素產生出 `slice3` 只有長度 + 1 而已,最後面的值也有參考到同個陣列。 121 | 122 | 由此可知,當 Slice 新增元素超過了容量的時候,它會產生新的陣列,且容量有兩倍,給新的 Slice 參考;而容量夠用的時候,則不會產生新陣列。 123 | 124 | `copy` 函式可以複製內容到另一個 Slice 裡,如下: 125 | 126 | ```go 127 | package main 128 | 129 | import "fmt" 130 | 131 | func main() { 132 | src := []int{1, 2, 3} 133 | dst1 := make([]int, 2) 134 | dst2 := make([]int, 3) 135 | dst3 := make([]int, 4) 136 | 137 | fmt.Println(src) // [1 2 3] 138 | fmt.Println(dst1) // [0 0] 139 | fmt.Println(dst2) // [0 0 0] 140 | fmt.Println(dst3) // [0 0 0 0] 141 | 142 | copy(dst1, src) 143 | copy(dst2, src) 144 | copy(dst3, src) 145 | 146 | fmt.Println(src) // [1 2 3] 147 | fmt.Println(dst1) // [1 2] 148 | fmt.Println(dst2) // [1 2 3] 149 | fmt.Println(dst3) // [1 2 3 0] 150 | 151 | src[0] = 100 152 | 153 | fmt.Println(src) // [100 2 3] 154 | fmt.Println(dst1) // [1 2] 155 | fmt.Println(dst2) // [1 2 3] 156 | fmt.Println(dst3) // [1 2 3 0] 157 | } 158 | ``` 159 | 160 | 複製時,即使長度不一還是會執行成功,只是會沒有複製完全。 161 | 162 | 163 | ## 從陣列或 Slice 產生 Slice 164 | 165 | 除了從頭建一個新的 Slice 外,也可以從陣列或 Slice 上產生新的 Slice ,以下是簡單的範例 166 | 167 | ```go 168 | package main 169 | 170 | import "fmt" 171 | 172 | func main() { 173 | arr := [...]int{1, 2, 3, 4, 5} 174 | 175 | slice := arr[1:4] 176 | 177 | fmt.Println(slice) // [2 3 4] 178 | fmt.Println(len(slice)) // 3 179 | fmt.Println(cap(slice)) // 4 180 | 181 | slice2 := slice[1:3] 182 | 183 | fmt.Println(slice2) // [3 4] 184 | fmt.Println(len(slice2)) // 2 185 | fmt.Println(cap(slice2)) // 3 186 | 187 | slice2[0] = 300 188 | 189 | fmt.Println(arr) // [1 2 300 4 5] 190 | fmt.Println(slice) // [2 300 4] 191 | fmt.Println(slice2) // [300 4 5] 192 | } 193 | ``` 194 | 195 | `[1:4]` 代表的意思是,從「第 1 個元素開始,到第 4 個元素,不含第 4 個元素」,因此會取得 `[2 3 4]` 三個元素。而容量會從第 1 個元素開始,一直到結尾,以上例來說就是 `4` 。 196 | 197 | 下一個 `[1:3]` 相信就不難懂了,不過它會從 `[2 3 4]` 這個 Slice 取元素,所以會取到的是 `[3 4]` ,容量是 `3`。 198 | 199 | 最後,因為沒有使用 `append` 函式,所以它們都參考到第一個陣列。 200 | 201 | 另外也可以使用 `[:]` 來取得全部陣列的內容。 202 | 203 | ## 參考資料 204 | 205 | * [底層為陣列的 slice](https://openhome.cc/Gossip/Go/Slice.html) 206 | 207 | [Day 7]: /docs/day07.md 208 | -------------------------------------------------------------------------------- /docs/day27.md: -------------------------------------------------------------------------------- 1 | # Refactoring Command 2 | 3 | 指令套件 [`github.com/urfave/cli`](https://github.com/urfave/cli) 算蠻好上手的。雖然好用,但似乎其他套件也不錯,如 [Cobra](https://github.com/spf13/cobra) 等。 4 | 5 | 目前 Command 實際處理任務的程式都是直接依賴 `cli.Context` ,這樣會違反[依賴反轉原則][SOLID 之 依賴反轉原則(Dependency inversion principle)]--應該依賴更抽象的參數,如 [*Predeclared Type*][Day 6] 或 *interface* 等。 6 | 7 | 會發現這個問題是因為在實作 [HTTP Server][Day 23] 時,行為沒變,只有輸出改變而已,但卻必須要為 `/generate` 的回傳重新客製化寫法,而且這段程式碼與 `command/generate.go` 裡面的 `generate` 函式太像了,這是一個明顯的[壞味道][開發者能察覺的壞味道(Bad Smell)],必須要重新調整設計才行。 8 | 9 | ## 分析 10 | 11 | 首先先調整 `command/generate.go` 的 `generate` 函式。它的任務是產生一堆假資料,所以我們應該可以先把數量這個參數先抽離出來: 12 | 13 | ```go 14 | func generate(count int, c *cli.Context) error { 15 | res, _ := provider.ParseFile(c.GlobalString("provider")) 16 | 17 | generator := provider.Create() 18 | generator.Resource = res 19 | 20 | for i := 0; i < count; i++ { 21 | fmt.Println(generator.Name()) 22 | } 23 | 24 | return nil 25 | } 26 | ``` 27 | 28 | 接著取得 `res` 是 provider 的任務,因此它不適合離開這個範圍,但取得檔案名稱的 `c.GlobalString("provider")` ,是可以抽離的: 29 | 30 | ```go 31 | func generate(path string, count int) error { 32 | res, _ := provider.ParseFile(path) 33 | 34 | generator := provider.Create() 35 | generator.Resource = res 36 | 37 | for i := 0; i < count; i++ { 38 | fmt.Println(generator.Name()) 39 | } 40 | 41 | return nil 42 | } 43 | ``` 44 | 45 | 最後就是 `fmt.Println(generator.Name())` 這是 cli 專屬的行為,因此把它抽出變成 [Closure][Day 12] ,同時把這個方法也公開: 46 | 47 | ```go 48 | type GenerateItemProcess func(item string) 49 | 50 | func Generate(path string, count int, process GenerateItemProcess) error { 51 | res, _ := provider.ParseFile(path) 52 | 53 | generator := provider.Create() 54 | generator.Resource = res 55 | 56 | for i := 0; i < count; i++ { 57 | process(generator.Name()) 58 | } 59 | 60 | return nil 61 | } 62 | ``` 63 | 64 | 到目前為止,只要把上面的程式碼找個地方放即可。因為這有點類似 *Facade Pattern* ,因此決定放在 `facade/facade.go` 裡。原本 CLI 呼叫的地方改成這樣: 65 | 66 | ```go 67 | func(c *cli.Context) error { 68 | num, err := strconv.Atoi(c.String("num")) 69 | 70 | if err != nil { 71 | return err 72 | } 73 | 74 | fmt.Println("Generate " + strconv.Itoa(num)) 75 | 76 | return facade.Generate(c.GlobalString("provider"), num, func(item string) { 77 | fmt.Println(item) 78 | }) 79 | } 80 | ``` 81 | 82 | 同時把不必要的程式碼刪除, import 的項目就不需要 `provider` 了,改成依賴 `facade` 。 HTTP Server 實作的部分也可以如法炮製: 83 | 84 | ```go 85 | func serve(c *cli.Context) error { 86 | num := 10 87 | 88 | server := gin.Default() 89 | server.GET(`/generate`, func(g *gin.Context) { 90 | var s []string 91 | 92 | facade.Generate(c.GlobalString("provider"), num, func(item string) { 93 | s = append(s, item) 94 | }) 95 | 96 | g.JSON(200, s) 97 | }) 98 | 99 | server.Run() 100 | 101 | return nil 102 | } 103 | ``` 104 | 105 | 同樣的重構方法也可以用在 `QueryCommand` ,後面就不贅述了。 106 | 107 | > 另外兩個 Command : `ServeCommand` 目前不知道該怎麼拆出來好(因為裡面也有用到 Facade ); `StatusCommand` 則是因為太簡單,所以沒拆出來的必要。 108 | 109 | ### 效能改善 110 | 111 | 以上已經把 `Generate` 的任務解耦合了,但 HTTP Server 的效能上是有問題的,當 num 到了一個極大的數如 10,000,000 ,執行會需要花 7 秒: 112 | 113 | ``` 114 | [GIN] 2018/01/01 - 23:37:19 | 200 | 7.610408045s | 127.0.0.1 | GET /generate 115 | ``` 116 | 117 | 主要是因為 `append` 了一千萬次,我們把 `Generate` 程式改一下: 118 | 119 | ```go 120 | type GenerateItemProcess func(item string, index int) 121 | 122 | func Generate(path string, count int, process GenerateItemProcess) error { 123 | res, _ := provider.ParseFile(path) 124 | 125 | generator := provider.Create() 126 | generator.Resource = res 127 | 128 | for i := 0; i < count; i++ { 129 | process(generator.Name(), i) 130 | } 131 | 132 | return nil 133 | } 134 | ``` 135 | 136 | 讓 process 可以順便把目前處理的 index 也傳入 Closure , HTTP Server 可以改寫成這樣: 137 | 138 | ```go 139 | s := make([]string, num) 140 | 141 | facade.Generate(c.GlobalString("provider"), num, func(item string, index int) { 142 | s[index] = item 143 | }) 144 | ``` 145 | 146 | 這樣算是用空間換時間,時間結果約為原本的 65% : 147 | 148 | ``` 149 | [GIN] 2018/01/01 - 23:41:34 | 200 | 4.963947486s | 127.0.0.1 | GET /generate 150 | ``` 151 | 152 | 程式碼修改可以參考 [PR Day 27](https://github.com/MilesChou/namer/pull/13) 153 | 154 | ## 問題 155 | 156 | Interface 沒認真去了解,還真不知道該怎麼做好。 157 | 158 | 明天把 Command 的參數加完後,後天就來研究 Interface ! 159 | 160 | ## 參考資料 161 | 162 | * [SOLID 之 依賴反轉原則(Dependency inversion principle)][] | 看到 code 寫成這樣我也是醉了,不如試試重構? 163 | * [開發者能察覺的壞味道(Bad Smell)][] | 看到 code 寫成這樣我也是醉了,不如試試重構? 164 | * [Facade 模式](https://openhome.cc/Gossip/DesignPattern/FacadePattern.htm) | 良葛格學習筆記 165 | 166 | [開發者能察覺的壞味道(Bad Smell)]: https://github.com/MilesChou/book-refactoring-30-days/blob/master/docs/day04.md 167 | [SOLID 之 依賴反轉原則(Dependency inversion principle)]: https://github.com/MilesChou/book-refactoring-30-days/blob/master/docs/day11.md 168 | [Day 6]: /docs/day06.md 169 | [Day 12]: /docs/day12.md 170 | [Day 23]: /docs/day23.md 171 | -------------------------------------------------------------------------------- /docs/day29.md: -------------------------------------------------------------------------------- 1 | # Interface 2 | 3 | 介面(interface)跟一般 Java 所熟知的介面意義是一樣的:定義實體(instance)的行為。 4 | 5 | ## 定義與實作 6 | 7 | 介面定義方法很簡單,只要定義傳入與傳出就行了,比方說 Generator 有個行為叫 `Name` ,我們可以這樣定義介面: 8 | 9 | ```go 10 | type Namer interface { 11 | Name(gender string, firstNameNum int) (name string, err error) 12 | } 13 | ``` 14 | 15 | 介面要如何實作?只要實體有實作這個介面就行了,不管是正常的 `Generator` 實作,或是一隻鴨子 `Duck` : 16 | 17 | ```go 18 | type Generator struct { 19 | rand *rand.Rand 20 | Resource NamesResource 21 | } 22 | 23 | func (generator *Generator) Name(gender string, firstNameNum int) (name string, err error) { 24 | switch gender { 25 | case "": 26 | name = generator.LastName() + generator.firstName(firstNameNum) 27 | case "male": 28 | name = generator.LastName() + generator.firstNameMale(firstNameNum) 29 | case "female": 30 | name = generator.LastName() + generator.firstNameFemale(firstNameNum) 31 | default: 32 | err = errors.New(fmt.Sprintf("gender '%s' is invalid", gender)) 33 | } 34 | 35 | return 36 | } 37 | 38 | type Duck struct {} 39 | 40 | func (generator Duck) Name(gender string, firstNameNum int) (name string, err error) { 41 | return "我是隻醜小鴨", nil 42 | } 43 | ``` 44 | 45 | ## 多型 46 | 47 | 介面也是一種型態,所以 Slice 或 Array 我們可以定義型態是介面,如: 48 | 49 | ```go 50 | var namer1 Namer = Duck{} 51 | var namer2 Namer = &Generator{} 52 | 53 | arr := []Namer{ 54 | namer1, 55 | namer2, 56 | } 57 | 58 | fmt.Println(arr) 59 | ``` 60 | 61 | 這裡必須注意,因為 `Generator` 實作的時候是使用傳址,所以介面這邊是要使用 `&` 取位址; Duck 則是傳值,所以不需要使用 `&` 取位址。 62 | 63 | 這邊會發現,我們可以丟很多不一樣的實體到一個 Slice 裡,只要有實作 `Namer` 就能丟。那有沒有一個型態是可以丟所有的實體?有的,它叫 `interface{}` : 64 | 65 | ```go 66 | arr := []interface{}{ 67 | Duck{}, 68 | &Generator{}, 69 | 123, 70 | func(){}, 71 | } 72 | 73 | fmt.Println(arr) 74 | ``` 75 | 76 | 事實上, `fmt.Println()` 的參數,其實也是定義 `interface{}` ,所以才能接所有種類的參數。 77 | 78 | ## 判斷型別與轉型 79 | 80 | 比方說下面這段程式,定義了一個 slice 裡面有一層 slice ,但執行後會發現它出錯了: 81 | 82 | 83 | ```go 84 | arr := []interface{}{ 85 | []int{1, 2, 3}, 86 | } 87 | 88 | fmt.Println(arr[0]) // [1 2 3] 89 | fmt.Println(arr[0][0]) // invalid operation: arr[0][0] (type interface {} does not support indexing) 90 | ``` 91 | 92 | 它說 `interface{}` 不支援索引取值。 93 | 94 | 其實這是強型態的特色,宣告 `interface{}` ,它就把這個變數當成是 `interface{}` ,因此它不能當作 slice 操作。除非要拜託它把變數當成 slice : 95 | 96 | ```go 97 | arr := []interface{}{ 98 | []int{1, 2, 3}, 99 | } 100 | 101 | fmt.Println(arr[0]) // [1 2 3] 102 | fmt.Println(arr[0].([]int)[0]) // 1 103 | ``` 104 | 105 | `var.(type)` 這是轉型的操作。如果可行的話,它會轉型成 `type` ,然後後面就可以繼續接 `type` 能做的操作。 106 | 107 | 它跟 Map 一樣會回傳第兩個值是 ok : 108 | 109 | ```go 110 | arr := []interface{}{ 111 | []int{1, 2, 3}, 112 | func() string { return "func" }, 113 | } 114 | 115 | fmt.Println(arr[0].([]int)[0]) // 1 116 | fmt.Println(arr[1].([]int)[0]) // cannot call non-function arr[1].([]int) (type []int) 117 | ``` 118 | 119 | 因此可以這樣處理 120 | 121 | ```go 122 | arr := []interface{}{ 123 | []int{1, 2, 3}, 124 | func() string { return "func" }, 125 | } 126 | 127 | fmt.Println(arr[0].([]int)[0]) // 1 128 | 129 | if a, ok := arr[1].([]int); ok { 130 | fmt.Println(a[0]) 131 | } else if f, ok := arr[1].(func() string); ok { 132 | fmt.Println(f()) // func 133 | } 134 | ``` 135 | 136 | 或是使用 `switch` : 137 | 138 | ```go 139 | arr := []interface{}{ 140 | func() string { return "func" }, 141 | } 142 | 143 | switch v := arr[0].(type){ 144 | case []int: 145 | fmt.Println(v[0]) 146 | case func() string: 147 | fmt.Println(v()) 148 | } 149 | ``` 150 | 151 | > `value.(type)` 只能用在 `switch` 。 152 | 153 | ## 型別轉換 154 | 155 | 上面的情況是強制轉型,不過也有的情況是單純的型別轉換 156 | 157 | ```go 158 | type A interface { 159 | foo() 160 | } 161 | 162 | type B interface { 163 | foo() 164 | } 165 | 166 | type Duck struct {} 167 | 168 | func (duck Duck) foo() { 169 | fmt.Println("Hello") 170 | } 171 | 172 | func main() { 173 | var some1 A = Duck{} 174 | var some2 B = some1 175 | 176 | some1.foo() 177 | some2.foo() 178 | } 179 | ``` 180 | 181 | 型別也能繼承,如: 182 | 183 | ```go 184 | type Parent interface { 185 | foo() 186 | } 187 | 188 | type Child interface { 189 | Parent 190 | bar() 191 | } 192 | 193 | type Duck struct {} 194 | 195 | func (duck Duck) foo() { 196 | fmt.Println("Hello Foo") 197 | } 198 | 199 | func (duck Duck) bar() { 200 | fmt.Println("Hello Bar") 201 | } 202 | 203 | func main() { 204 | var some1 Child = Duck{} 205 | var some2 Parent = some1 206 | // Parent 不能直接轉回 Child 207 | // var some3 Child = some2 208 | var some3 Child = some2.(Duck) 209 | var some4 Child = some2.(Child) 210 | 211 | some1.bar() 212 | some2.foo() 213 | some3.bar() 214 | some4.bar() 215 | } 216 | ``` 217 | 218 | 中間會發現, `Parent` 不能轉回 `Child` 了,除非再強制轉型 `Duck` 或是 `Child` 。 219 | 220 | 因為這是強型態的特色,但只要轉換過去有達到介面實作的條件就能正常轉換了;而實作的條件最一開始就有提了:它不會管是不是繼承或是結構,而是只要實體有實作介面的行為就可以了。 221 | 222 | ## 參考資料 223 | 224 | * [介面入門](https://openhome.cc/Gossip/Go/Interface.html) 225 | * [介面轉換與繼承](https://openhome.cc/Gossip/Go/InterfaceCastInheritance.html) 226 | * [element.(T) 型態測試](https://openhome.cc/Gossip/Go/Element.Type.html) 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | --------------------------------------------------------------------------------