スポンサーリンク
概要
「Go言語ハンズオン」を読んだので感想を書こうと思います。
2022/1/2追記
Go言語の書籍としてみんなのGo言語の感想を下記で公開しています。合わせてご覧ください。
技術書のセールとおすすめ書籍を紹介しています。合わせてご覧ください。
スポンサーリンク
書籍概要
どんな本?
Googleが開発したGo言語は、少しずつシェアを広げて、いまでは「Go言語が使える」となれば周囲から一目置かれる存在になりつつあります。本書は、「習うより慣れろ!」でGo言語の基礎からWebサーバープログラムまで、サンプルを作りながら学ぶハンズオンタイプの入門書です。電卓、超簡易エディタ、Webスクレイピングツール、Youtube動画を管理するWebアプリなどを実際に作りながら、 Go言語の仕組みや特徴を学びましょう。
- 掌田 津耶乃 著
- 2021年03月 発行
- 448ページ
- 定価3,080円(税込)
スポンサーリンク
スポンサーリンク
内容のまとめと感想
Go言語をハンズオン形式で学んでいく入門書です。
構成として、前半はいわゆる入門書的な言語仕様の説明になっていて、後半はGUIアプリケーションを作ってみるといった応用的な話になっています。
こういった言語文法に説明を割いた入門書だと、後半の応用的な部分は軽い紹介だけになりがちですが、幅広いテーマの実装サンプルを取り扱っているのは素晴らしいです。
プログラム初心者だと後半は厳しいかもしれませんが、ある程度WEBアプリケーションなどを開発した事があるエンジニアであればすんなり読めるのではないかと思います。
一方で、最新のGo言語の入門書籍として、記載すべき内容が記載されていないなど内容にバラツキがあるので注意が必要です。
良かった点
幅広い内容
Go自体の言語仕様の説明から始まり、後半はGUIやWebアプリケーションなど実践的な内容まで含まれていて、この手の入門書としてはかなり幅広く書かれています。
1冊でここまで学べる書籍は珍しいと思います。
気になった点
Go moduleの説明がない
Goのパッケージ管理やフォルダ構成において、大きな変更であるGo moduleに関しての説明がありません。
Goの書籍としてはかなり後発なのにも関わらずこの重要な機能に関して説明がないのはちょっと残念ですね。
一部のサンプルが古い
これはタイミングの問題かもしれませんが、説明で使用しているFyneというGUIのフレームワークが古いバージョンとなっていて、最新のバージョンを使用していません。
公式ページ等を確認するとv2を使用していますが、本書はv1で書かれています。
importするパッケージの変更だけでかなりの部分は動きそうですが、データバインディングなどv2の特徴的な機能が使われていないため、ちょっと残念ですね。
スポンサーリンク
読書ノート(個人的なまとめ)
第1章 Go言語をスタートする
Go言語の思想や背景と、環境のセットアップ方法に関してまとめている章です。
エディタとしてはVisual Studio Code(VS Code)を使用した際の説明がされています。
第2章 Goの基本文法
Go言語一般的な文法に関して説明している章です。
変数、型、forループといったプログラムの初歩的な話がメインなので、他のプログラミング言語の経験者は流し読みでも良いと思います。
一部の構文はGo固有の書き方もあったりするので、気になる部分だけよく見てみるのも良いですね。
後半はクロージャなど、関数関係の話も出てきます。
ショートステートメント付きif
ifチェックの前に1つの処理を動かした上で、処理を分岐させる方法。
変数のスコープがif文内に限定される。
if 文 : 条件 { ... }
1 2 3 4 5 |
if n, err := strconv.Atoi(x); err == nil { // errが nilの場合 } else { // errが nil でない場合 } |
for
for分も少しクセがありますね。
1 2 3 4 5 6 7 8 9 10 11 |
// 定番のforループ for i := 0 i < n; i++ { // loop処理 } // 配列を使用したrange // arは配列 i, d := range ar { // iがインデックス // dが配列の中身 } |
第3章 Goの高度な文法
Go固有であったり、オブジェクト指向言語では見られないようなポインタの話など、少し高度な話が中心の章です。
といったも、プログラム初心者でなければ何かしらの言語で似たような機能などは見た事が多いと思います。
主なトピックとしては、ポインタ、構造体、平行処理になります。
GoのポインタはCやC++ほど複雑なものではないので、そこまで敷居が高くは無いので取り組みやすいと思います。
Goにはクラスが無く、構造体のみとなっていて、構造体にレシーバーという方法でメソッドを追加していくスタイルになるので、オブジェクト指向プログラミングに慣れていると最初はとっつきにくさを感じるかもしれません。
平行処理は、Go独自の仕組みとなっていて、JavaやC#のマルチスレッドプログラミングなどより簡単に使えます。
ポインタ
&でアドレスを表して、*で中身のデータを取り出す
1 2 3 4 5 6 7 8 9 10 11 |
func main() { n := 123 // &で変数のアドレスが格納 p := &n fmt.Println("n=", n) // 123 // *でアドレス内の変数を示す fmt.Println("p=", *p) // 123 *p = 456 fmt.Println("n=", n) // 456 fmt.Println("p=", *p) // 456 } |
値渡しとポインタ(参照渡し)の違い:値渡しだと関数内で変数がコピーされる。大容量の配列などを渡すとコピーでコストが大きくなる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func main() { ar := []int{10, 20, 30} // [10, 20, 30] fmt.Println(ar) initar(&ar) // [0, 0, 0] fmt.Println(ar) } func initar(ar *[]int) { for i := 0; i < len(*ar); i++ { (*ar)[i] = 0 } } |
構造体
構造体の初期化方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
type Person struct { Name string Age int } func main() { person1 := Person{"A", 10} fmt.Println(person1) person2 := Person{ Name: "B", Age: 20} fmt.Println(person2) person3 := new(Person) person3.Name = "C" person3.Age = 30 // newの場合ポインタ fmt.Println(*person3) } |
func (構造体) 関数名 (引数) { ... } といった形で構造体に関数を割り当てる事ができる。(構造体)の部分をレシーバーと呼ぶ。
レシーバーにポインタを割り当てる事で、値の変更が可能になる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
type Person struct { Name string Age int } func main() { person1 := Person{"A", 10} fmt.Println(person1) person1.PrintData() person1.SetData("AA", 99) person1.PrintData() } // ポインタなし (値の変更は反映されない) func (p Person) PrintData() { fmt.Println("Name:", p.Name) fmt.Println("Age:", p.Age) } // ポインタあり func (p *Person) SetData(name string, age int) { p.Name = name p.Age = age } |
インターフェイス
Goのインターフェイスは構造体に明示的に実装を宣言しない。
インターフェイスの定義を実装していれば、自動で満たしたものとして扱える。(コンパイルエラーにならなくなる)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
type Displayer interface { Display() } type Person struct { Name string Age int } type Dog struct { Name string } func main() { person := Person{"田中", 10} dog := Dog{"Dog"} var displayer Displayer = person display(displayer) displayer = dog display(displayer) } func display(d Displayer) { d.Display() } func (p Person) Display() { fmt.Println("-- Person --") fmt.Println("Name:", p.Name) fmt.Println("Age:", p.Age) } func (p Dog) Display() { fmt.Println("-- Dog --") fmt.Println("Name:", p.Name) } |
ポインタレシーバーを用いる場合には、インタフェースにはアドレスを渡す。
インターフェイス自体はポインタなので、アドレスを渡さないとダメ?(まだ理解できていない)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
func main() { person := Person{"田中", 10} dog := Dog{"Dog"} var displayer Displayer = &person display(displayer) displayer = &dog display(displayer) } func display(d Displayer) { d.Display() } func (p *Person) Display() { fmt.Println("-- Person --") fmt.Println("Name:", p.Name) fmt.Println("Age:", p.Age) } func (p *Dog) Display() { fmt.Println("-- Dog --") fmt.Println("Name:", p.Name) } |
空のインタフェース
空のインタフェースを定義すると任意の値が設定可能になる。
1 2 3 4 5 6 7 8 |
// 空のインターフェス type General interface{} var v General // 下記は全て代入可能 v = 123 v = "abc" v = true |
空のインタフェースを用いる事で、任意のデータを同じ構造体に保持させる事ができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
type General interface{} type GDataImpl struct { Name string Data General } func main() { data := []GDataImpl{} // 文字列 data = append(data, GDataImpl{"A", "abc"}) // 数値 data = append(data, GDataImpl{"A", 123}) // 配列 data = append(data, GDataImpl{"A", []int{1,2,3}}) for _, ob := range data { ob.Print() } } func (g *GDataImpl) Print() { fmt.Print(g.Name) fmt.Println(g.Data) } |
平行処理
メソッドに呼び出しにgoと付けるだけで、別スレッドでの実行となる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
type GDataImpl struct { Name string Data General } func main() { go heavy("Go") heavy("Not Go") } func heavy(msg string) { for i := 0; i < 10; i++ { fmt.Println("%d %s", i, msg) time.Sleep(1000) } } // 出力 0 Not Go 0 Go 1 Not Go 1 Go 2 Go 2 Not Go 3 Not Go 3 Go ... |
チャンネルを使って、goルーチンとのデータのやりとりができる。
goルーチンの外でチャンネルの値にアクセスするとルーチンが値を返すまで待つ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func main() { c := make(chan int) go heavy("Go", c) // チャンネルから値が送られてくるまで待つ fmt.Println(<-c) } func heavy(msg string, c chan int) { n := 0 for i := 0; i < 10; i++ { n += i fmt.Println(i, msg) time.Sleep(1000) } c <- n } |
ルーチンからの値の受け取りFIFOとなっていて、ルーチンから呼び出されるたびにチャネルの値が取り出せる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
func main() { c := make(chan int) // 2つのルーチン作成 go heavy(1, c) go heavy(2, c) // 2つのルーチンの結果を受け取る(2つ受け取るまで待つ) x, y := <-c, <-c // 20, 10 (=>タイミングによっては10,20) fmt.Println(x, y) } func heavy(addNum int, c chan int) { n := 0 for i := 0; i < 10; i++ { n += addNum time.Sleep(50) } c <- n } |
ルーチン側への値の設定方法も可能。下記はメインスレッドからGoルーチンにチャネルを通して値を送信しています。
チャネルの値を取得するまでルーチン側の処理は停止します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
func main() { c := make(chan int) // ルーチンにチャネルを渡して実行 go heavy(c) // チャネルの値を設定(設定されるまでheavyのチャネルの処理は止まる) c <- 100 fmt.Println("end") } func heavy(c chan int) { n := <-c for i := 0; i < 10; i++ { n += 1 fmt.Println(n) } } // display result ... 100 101 102 .... |
下記のように2つのチャネルを用意して双方向のデータのやり取りも可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func main() { // 受信と送信用のチャネルを作成 cs := make(chan int) cr := make(chan int) // ルーチンにチャネルを渡して実行 go heavy(cs, cr) // チャネルの値を設定(設定されるまでheavyのチャネルの処理は止まる) cs <- 100 // ルーチンからの値を取得 fmt.Println("cr:", <-cr) } func heavy(cs chan int, cr chan int) { // 別スレッドから送信されるデータをチャンネルから取得 n := <-cs for i := 0; i < 10; i++ { n += 1 fmt.Println(n) } // 送信用のチャンネルにデータ送信 cr <- n } |
スポンサーリンク
第4章 FyneによるGUIアプリケーション開発
GoでマルチプラットフォームのGUIが開発できるフレームワークのFyneでアプリケーションを作る方法が説明されている章です。
Fyneのインストールから始まり、各UIコンポーネントの使い方が説明されています。
そして、応用として電卓アプリと簡易テキストエディタといったアプリケーションを作成します。
Choroniumを使ったWebベースではないマルチプラットフォームのデスクトップGUIは、見た目がモダンではない感じのものが多いと感じていましたが、本フレームワークはかなり見た目も良いのが嬉しいですね。
Fyne自体のバージョンが2になったためか、使用しているモジュールが違ったりして、最初のセットアップにかなり時間がかりました。
公式ページの導入方法に従って実施した方が良いです。
幸いにもインポートするパッケージ以外はそこまで大きくAPIは変わっていなさそうでそのまま使えるものが多いです。
ただ、Fyne自体がv2でデータバインディングに対応するなど、より便利な書き方ができるようになっているようです。
go mod init xxx
go get fyne.io/fyne/v2
go get fyne.io/fyne/v2/...
https://teratail.com/questions/339897
スポンサーリンク
第5章 データアクセス
ファイル、ネットワーク(HTTP)、データベースといった各種データへのGoでのアクセス方法に関してまとめた章です。
4章でGUIの作成という少し、実践的な内容をやりましたが、本章ではまた文法や機能といった説明に戻ります。
ですが、章末では説明した機能を使用したGUIを作成し、実践的に学ぶ事ができます。
ネットワークでは、Webスクレイピングの実施したり、JSONデータを取得して構造体へパースするなど、少し応用的な内容も実施しています。
データベースはSQLiteを使用した説明になっています。
スポンサーリンク
第6章 Webサーバープログラム
4~5章に引き続き、実践ベースで実際にWebアプリケーションを作ってみる章です。
単純なHTMLを返す内容から始まり、テンプレートを用いた本格的な画面を返すなど、Webアプリケーションでよく用いられるケースを実際に実装していきます。
DBのアクセスにはGOのORMのGORMを使用してクエリを書かずに実装します。
Webフレームワークを使用せずに、Goの標準機能だけでそこそこのWebアプリケーションが実装できることに驚きました。
実際にはWebフレームワークを使うにしろ、標準的な機能でここまでできるのはGoらしい点なのかもしれませんね。
スポンサーリンク