Closed34

Udemyにて、Go言語の講座を受講した際の気づきをメモする(セクション8まで)

mortlackmortlack

基本情報

受講している講座は以下

現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発

始めた時の状態

Go言語の入門用書籍(初めてのGo言語)を7割程度実施、その後、AtCoderでABCの問題A、B、C程度の問題を20問程度解いた状態での受講。
簡単なコードをたくさん書くことで、極基礎の部分の文法はすらすら出てくるレベルにはなっている。

この講座のセクション8までの感想

前半の部分のGoの基本文法のセクションでは、かなり網羅的にGoの文法を扱ってくれている。
反面、なぜそうするのか、いつ使うべきか、などの説明はない。
しかし、ビデオ講座でここまで網羅的に文法を扱ってくれるのはすごいな、とは思いました。

私自身は、この講座の前に、「初めてのGo言語」である程度勉強しています。
「初めてのGo言語」では、「なぜ」や「いつ」についてもかなり詳しく説明がなされいます。
反面、そういったケースの詳しい解説が入る関係で、(初心者の段階では)情報過多で消化不良を起こし勝ちです。

ということで、逆順でやった方がよかったのかな、と思っています。

mortlackmortlack

8.変数宣言

var (
    i int = 1
    f64 float64 = 1.2
)

varは複数宣言したい場合には括弧でくくることができる。
※書籍に書いてあった気がするが、余り使う機会がなかった。パッケージを作成する場合には使うのだろうか。

フォーマットの動詞%Tで変数の型を表示させることができる。
※デバッグ用?

mortlackmortlack

9.const

  • リテラルに名前を付与するだけの機能
  • 型を指定してconstを宣言することもできる

という2点が説明されていなかった

mortlackmortlack

11.文字列

バックコートで囲うと、入力したものをそのまま文字列とすることができる。
例えば、ダブルコートを文字列に含めたい場合、ダブルコートだけをバックスラッシュでエスケープする方法と、文字列全体をバックコートで囲む方法がある。

mortlackmortlack

13.型変換

strconv.Atoiが出てくるけど、カンマokイディオムとか、リターンで複数の値を返せるとか、そういう部分の説明がない。

mortlackmortlack

14.配列

配列の宣言方法

var a [2]int

配列の宣言方法覚えてなかった…。
が、利用するAPIの戻り値が配列だった、とかでなければ使うこともないような…。

mortlackmortlack

15.スライス

スライスの部分スライスをとって、それを変更した場合どうなるか、という議論がなかった。
初心者向けなので問題はないと思うが、そういう使い方しない方がよい(初心者のうちは)、みたいな情報があってもよかったかもしれない。

16.スライスのmakeとcap

スライスがnilの場合、appendが使えないのかと思っていたが、普通にappendは使えた。

mortlackmortlack

17.map

存在しないキーでも聞けてしまって、ゼロ値が返ってくる、という説明が欠落しているような。※他の言語とは異なる部分なので。

これも、自分の知識不足だが、makeは引数として型だけ指定して、数値を指定しないくてもアドレスの確保がされるらしい。

mortlackmortlack

18.関数

これは完全に「初めてのGo言語」の受け売りだが、名前付き戻り値は、

  • シャドーイングの可能性
  • 名前付き戻り値を定義しても利用が強制されない

という問題?を孕んでいるため、余り使わない方がよいのでは、と思ってしまう。

mortlackmortlack

20.クロージャ

クロージャの使い方、わかりやすかった。

func incrementGenerator() func() int {
	x := 0
	return func() int {
		x++
		return x
	}
}
func main() {
	counter := incrementGenerator()
	fmt.Println(counter())
	fmt.Println(counter())
	fmt.Println(counter())
}
mortlackmortlack

27.switch

ブランクstitchを使う場合には、各caseをキチンと考えましょう。通常のswitchで書けるかも。
※初めてのGo言語の受け売り

mortlackmortlack

29.log

Goには他言語のようなlogの仕組みはなく、他言語のようなロギングをしたい場合には、サードパーティ製品を使った方が良いかもしれない。

log.Fatalxxxxを使うと、ログを書いた後にプログラムが終了してしまう。

logファイルへの書き込みについて

ここから突如難しくなるので注意。

ログファイルの指定やフラグの設定がない(デフォルトの)場合には、標準出力に指定されたメッセージをする。
設定がある場合には、設定に従う。
io.MultiWriterは、複数の対象に書き込むWriter。この場合には、標準出力とログ用に開いたファイル。

func LoggingSettings(logFile string) {
	logfile, _ := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	multiLogFile := io.MultiWriter(os.Stdout, logfile)
	log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
	log.SetOutput(multiLogFile)
}

func main() {
	LoggingSettings("test.log")
	_, err := os.Open("fdafdsafa")
	if err != nil {
		log.Fatalln("Exit", err)
	}
}
mortlackmortlack

31.panicとrecover

Go言語では、エラーハンドリングをきちっとやることが推奨されている。
自分で書くコードでは、panicは極力利用しないようにすること。

例えば、割り算における0割のような事前のチェックで判別できるようなものは、panicではなくエラーであるということ。自分の力ではどうにもならない状況が発生したときにのみpanicを利用する。
つまり、そのpanicを回復するrecoverというのは、かなり稀な状況で利用される。例えば、ネットワークの瞬断が起こるような場合で、リトライで回復が望めるような場合とか?

mortlackmortlack

34.ポインタ

var v int = 100
var p *int = &v
  • ポインタの型宣言には、*が付く
  • 値のアドレスを表現するのは、&が付く
  • アドレスが差す値を表現するには、*を付ける
mortlackmortlack

35.newとmakeの違い

宣言をして、型を確認した時に、

  • ポインタ型となるもの:newを使う
  • そうでないもの:makeを使う

「初めてのGo言語」では、スライスとマップはそもそもがポインタである、という説明がなされていた。説明の切り口が異なる。

mortlackmortlack

36.struct

初めてのGo言語によると、構造体のポイントを作りたいときは、newを使わずに&を付ける方が多いとのこと。

構造体を関数の引数にポインタ渡しする場合の挙動は特殊。

(*s).aと書かなくてもs.aでアクセス可能。

ただ、データはイミュータブルとして扱った方が良い、という観点から
構造体をポインタ渡しして、値を書き換えるのは極力控えた方が良い(初めてのGo言語)。

mortlackmortlack

40.コンストラクタ

パッケージにNew関数を定義してあげて、構造体の値(インスタンス?)を作成する。
でもなんで、ポインタを返すのだろう…。

mortlackmortlack

41.Embedded

まるで継承かのような説明がなされているが、コンポジションにあたるものだと思う。
継承のように、埋め込まれた型は、埋め込んだ型のインスタンスとして扱うことはできない、とかその辺の説明がなかった。

mortlackmortlack

42.non-structのメソッド

dotnet系とかの言語の拡張メソッドの代わりになるものかな?
ただし、拡張したことが明示的にわかるように別の型として宣言する必要がある、ということか。

mortlackmortlack

43 インターフェースとダックタイピング

40.コンストラクタのところで気になっていたポインタだが、

インタフェースがあり、そのインターフェースの実装(メソッド)がポインタレシーバであれば、
インターフェースの値には、ポインタを渡す必要がある。※難しい…。

で、こういうことがあるので、ポインタを常に返しておいた方が無難、ということなのだろうか。
※いつ、どのインターフェースが適用されるかは、実装サイドでは判断できないので。

タイトルに「ダックタイピング」とつけているが、ダックタイピングに関する説明は全くない。Goのインターフェース周りの仕様が、ダックタイピングを実現するためのもの、ということなのだが、プログラム初心者にはダックタイピングそのものが分からないと考えられる。

mortlackmortlack

44.タイプアサーションとswitch type文

よく使われるパターンという説明があったが、どういうときによく使うのだろう。
フレームワーク系の処理でも書かないとほとんど使う機会がなさそうなのだけど。
関数型プログラミングのパターンマッチのような形で使う実装パターンが存在するのだろうか。

func do(i interface{}) {
	/*
		ii := i.(int)
		// i = ii * 2
		ii *= 2
		fmt.Println(ii)
		ss := i.(string)
		fmt.Println(ss + "!")
	*/
	switch v := i.(type) {
	case int:
		fmt.Println(v * 2)
	case string:
		fmt.Println(v + "!")
	default:
		fmt.Printf("I don't know %T\n", v)
	}
}
mortlackmortlack

45.Stringer

toStringメソッドを定義するようなイメージだが、明示的に呼び出す必要がないというところが異なる。
ログやデバッグの際などに便利そう。

func (p Person) String() string {
	return fmt.Sprintf("My name is %v.", p.Name)
}

func main() {
	mike := Person{"Mike", 22}
	fmt.Println(mike)
}
mortlackmortlack

46.カスタムエラー

エラーを定義する際はポインタレシーバにして、ポインタ渡しすべき(イディオムとして覚える)
と解説されていたが、「初めてのGo言語」にはそういった記述はない。悩む。

func (e *UserNotFound) Error() string {
	return fmt.Sprintf("User not found: %v", e.Username)
}

func myFunc() error {
	// Something wrong
	ok := false
	if ok {
		return nil
	}
	return &UserNotFound{Username: "mike"}
}
mortlackmortlack

49.gorutineとsync.WaitGroup

何もしないとgoルーチンにしたものの実行の終了をプログラムは待ってくれない。
sync.WaitGroupを使うことで、終了を待つことができるようになる。

func goroutine(s string, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 5; i++ {
		//time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}
func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	go goroutine("world", &wg)
	normal("hello")
	// time.Sleep(2000 * time.Millisecond)
	wg.Wait()
}
mortlackmortlack

50.channel

チャネルを使っている場合は、sync.WaitGroupを使わなくても待ってくれる。

func goroutine1(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum
}

func main() {
	s := []int{1, 2, 3, 4, 5}
	c := make(chan int) // 15, 15
	go goroutine1(s, c)
	go goroutine1(s, c)
	x := <-c
	fmt.Println(x)
	y := <-c
	fmt.Println(y)
}

同じチャネルに複数のGoルーチンを紐づけした場合、結果を区別することはできないっぽい。終わった順に処理される。

mortlackmortlack

51.Buffered Channels

以下で、2個までに限定したチャネルを作成できる。
ch := make(chan int, 2)

チャネルから値を取り出したら、チャネル内の取り出した値は削除される(当たり前と言えば当たり前)。
ということで、for文で回したい場合には、チャネルをクローズする。
rangeで回す場合、チャンネルに中身がなくても、次の値を取りに行ってしまうため。
close(ch)

mortlackmortlack

52.channelのrangeとclose

この書き方で、実行が終わった都度結果が表示されるようになる。

func goroutine1(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
		c <- sum
	}
	close(c)
}

func main() {
	s := []int{1, 2, 3, 4, 5}
	c := make(chan int, len(s))
	go goroutine1(s, c)
	for i := range c {
		fmt.Println(i)
	}
}
mortlackmortlack

53.producerとconsumer

提示されたサンプルコードだと、余り2つのGoルーチンを使っている感覚が得られないので、producerの方に、乱数でスリープする時間を調整するコードを追加してみた。

自分の理解不足により、wg.Wait()以下の処理がよくわからなかったが、
wg.Wait()は、「wg.Addした数字が0になるまで待つ」ので、処理が全部終わったらclose(ch)以下の処理が実行される。

func producer(ch chan int, i int) {
	time.Sleep(time.Duration(100*rand.Intn(20)) * time.Millisecond)
	ch <- i * 2
}

func consumer(ch chan int, wg *sync.WaitGroup) {
	for i := range ch {
		func() {
			defer wg.Done()
			fmt.Println("process", i*1000)
		}()
	}
}

func main() {
	var wg sync.WaitGroup
	ch := make(chan int)

	// Producer
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go producer(ch, i)
	}

	// Consumer
	go consumer(ch, &wg)

	wg.Wait()
	close(ch)
	fmt.Println("Done")
}
mortlackmortlack

54.fan-out fan-in

チャネルの方向をシグネチャに書けるのわかりやすい。

func multi2(first <-chan int, second chan<- int) {

パイプラインの形にしておいた方が、後々のメンテナンス性が上がる。

mortlackmortlack

55. channelとselect

これで、どちらが来ても即座に処理ができるということ。

for {
	select {
	case msg1 := <-c1:
		fmt.Println(msg1)
	case msg2 := <-c2:
		fmt.Println(msg2)
	}
}

こう書いてしまうと、c1が来るまで待ち、次にc2が来るまで待ち、となってしまう。

for {
    msg1 := <-c1:
    fmt.Println(msg1)
    msg2 := <-c2:
    fmt.Println(msg2)
}
mortlackmortlack

56.Default Selectionとfor break

何を説明しているのかわかりにくい気がする。

まず、selectdefaultケースを設定できる。通常、selectのケースはチャネルからの情報の取得なので、チャネルが何も返してこなければ、「待ち」になるはずだが、defaultを置くことで、どのチャネルも値を返してこないときの処理を書くことができる。

次に、breakの特殊な使い方。selectにもbreakを使う構文があるので、selectの中にbreakを書いてもselectしか抜けられない。このため、何を抜けるのかを明確に指定できるような構文がある、ということ。

mortlackmortlack

57.stync.Mutex

「初めてのGo言語」ではGoルーチンのところは未学習だったので、Mutexについての章を確認してみたところ。

使わなくて済むなら、使わない方がよい。ただし、使うことによって処理が簡単になる場合もある。
という感じでした。

mortlackmortlack

61.PublicとPrivate

パッケージの中で、先頭大文字で宣言するとPublic、小文字で宣言するとPrivate
特殊。なれるのに時間がかかりそう。

mortlackmortlack

65.godoc

例は、テストの方に書くというには、理にかなっている気がするし、実際のコードを書くというのも分かりやすい。さすが新しい言語という気がする。

このスクラップは3ヶ月前にクローズされました