🔰

フロントエンドエンジニアが初めてGoに触れてみた感想!

2023/12/08に公開

はじめに

本記事は mediba Advent Calendar 2023 の8日目の記事です。

株式会社medibaにて、フロントエンドエンジニアをしています、kamabokoです。
今年もアドベントカレンダーの季節になりましたね。🎄🎁

何か写真を載せたいなと思ったので載せます。
これは今年撮った写真の中で、一番のお気に入りの一本桜です。
寒い時期が苦手なので、早く春が来てほしいです🌸


本題に移ります⛄️

下期よりチームリーダーとして、バックエンドやインフラがメインとされているプロジェクトにJOINさせていただくこととなりました。
フロントエンドの私にとっては、技術的にもドメイン知識的にも、全てがとても刺激的で新鮮といった感じでした。

そんな中でチームリーダーをやるためには、あらゆる方向に対する最低限の理解・知識が必要になります。
まずは段階的に、広く浅く知っている状態を目指そう、というのが私の直近の個人的な目標です。

その一歩として、プロジェクトで採用されているGo言語の基礎を最近学習しました。
フロントエンドエンジニアのスキルセットの私が初めてGoに触れて
どう思ったのか、どう感じたのか
そんなゆるい感想を、書いていこうと思います。

もし私と似たような境遇であったり
これからバックエンドの世界に踏み出そうと考えている
フロントエンドの方の参考に、少しでもなれば幸いです。

シンプルで書きやすい

まず、非常にシンプルで書きやすいという印象を持ちました。
JavaScript、TypeScript(以下、JS・TS)を普段書いていれば、変数宣言や各種制御構文は
多少書き方に違いはありますが、なんとなく読めるし書けそう、と思いました。

以下にいくつか簡単な例を紹介します。
普段フロントエンドとしてコードを書いていれば、なんとなく読めてしまうのではないでしょうか。

変数

変数定義は
var 変数名 型 = 値
変数名 := 値
のように短縮した書き方があるようです。

sample01.go
package main

import "fmt"

func main {
    var i int = 100
    fmt.Println(i) // 100

    s := "Hello Go" // 短縮した変数宣言
    fmt.Println(s) // Hello Go
}

配列・スライス

Goの配列は、同じ型の要素が固定された数で構成されるデータ構造で、
配列の長さは固定されており、後から変更できないそうです。

普段JS・TSで配列に値を追加したり、減らしたりと加工することに慣れていたため新鮮でした。
Goには、スライス(slice)と呼ばれる柔軟で動的なデータ構造も提供されており、
こちらはJS・TSで扱っていた配列のような感じで、加工することが可能なようです。

sample02.go
package main

import "fmt"

func main {
    // 配列
    var arr [2]string = [2]string{"A", "B"} // 初期値を設定
    fmt.Println(arr) // [A B]

    // スライス
    var numbers []int // 空のスライス
    numbers = append(numbers, 1, 2, 3, 4, 5) // 値を追加することができる
    fmt.Println(numbers) // [1 2 3 4 5]
}

制御構文(if、for...)

JS・TSに慣れているので、if文を書くときは
ついつい条件文を括弧で囲ってしまいそうになります。
また等価演算子は1種類で、
Goでは5 == "5"では、コンパイルエラーとなるため
Goの==はTS・JSでいうところの厳密等価(===)にあたりそうなのかな、と思いました。

sample03.go
//if文の例
package main

import "fmt"

func main {
    n := 100
    if n == 100 {
        fmt.Println("100")
    } else if n == 200 {
        fmt.Println("200")
    } else {
        fmt.Println("I don't know")
    }
}
sample04.go
// forの例
package main

import "fmt"

func main {
    arr := [3]int{1, 2, 3}

    for i := 0; i < len(arr); i++ {
        fmt.Println(arr[i]) // 1,2,3と順番に出力
    }
    
    // i: index, v: 要素
    // range: インデックス(またはキー)と要素(または値)のペアを返す
    for i, v := range arr {
        fmt.Println(arr[i], v)
    }
}

新しい学び

今回学習した中で、私が個人的に新鮮に感じたものの中から一部ご紹介しようと思います。

ポインタ

大学時代の授業で学習したことはあれど、ほとんど覚えていなかったため新鮮に感じたのがポインタです。

ポインタについて簡単に例を記載しておきます。
以下の例では、10という値が代入されたn1をupdate関数に渡し、2倍にしています。
しかし、出力すると値は10のまま、変わりません。

これはn1とupdateのiでは、元々は同じ変数ですが
関数に渡ったタイミングで、変数が別の変数にコピーされ、それぞれが独立するため
updateでiに対して値を変更したとしても
元の変数n1に影響しないため、と理解しています(値渡し)

sample05.go
package main

import "fmt"

func update(i int) {
	i = i * 2
}

func main() {
	n1 := 10
	update(n1)
	fmt.Println(n1) // 10
}

このようなケースで、値を更新したい場合にポインタが役に立ちます。
ポインタは、変数のメモリ上のアドレスを参照することができるため
どのアドレスの値を更新するかを明示的に示すことで値を更新することができます。(参照渡し)

sample06.go
package main

import "fmt"

func update(i *int) {
	*i = *i * 2
}

func main() {
	n1 := 10
	update(&n1)
	fmt.Println(n1) // 20
}

コンカレンシー(ゴルーチン、チャネル)

コンカレンシーは、複数のタスクが同時に進行することを指します。
Go言語では、軽量スレッドであるゴルーチンを使用してコンカレンシーを実現します。

ゴルーチン: Go言語のスレッドの単位。
チャネル: ゴルーチン間の通信と同期を実現するためのメカニズム。データの送受信に使用される。

完全に並行でタスクが並走するというよりかは、小刻みにタスクの処理をスイッチしながら繰り返すことで実現していると、理解しています。

並列実行させたい場合は関数呼び出しの先頭に goを付けます。
またチャネルの受信はi := <-ch
送信はch <- i
のように記述します。

下記の例では、チャネルを受信して出力する関数reciever
main関数で、ゴルーチンとして設定し、チャネルch1ch2をそれぞれ引数として渡すようにしています。
forループの中でiを1ずつインクリメントし、100より小さくなるまで
各チャネルにiを送信しています。
関数recieverは、ch1とch2それぞれチャネルを受信、出力という処理を
並行に処理していることになります。

sample07.go
.go
package main

import (
	"fmt"
	"time"
)

func reciever(c chan int) {
	// チャネルを受信して出力する
	for {
		i := <-c
		fmt.Println(i)
	}
}

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	// ゴルーチンで並列処理
	go reciever(ch1)
	go reciever(ch2)

	i := 0
	for i < 100 {
		// 各チャネルにiを送信
		ch1 <- i
		ch2 <- i
		time.Sleep(300 * time.Millisecond)
		i++
	}
}

おわりに

今回はGoの基礎演習を終えた感想を記述しました。
個人的に次は、より実践的なコード(DBからデータを取得したり、RESTAPIの実装)などをやってみようと思っています。
おわり!


最後に弊社からのお知らせです。

現在medibaでは、メンバーを大募集しています。
募集・応募ページ
medibaってどんな会社だろう? と興味を持っていただいた方は、
カジュアル面談もやっておりますので、お気軽にお申込み頂ければと思います。

Discussion