フロントエンドエンジニアがGoの書き方を理解する

本記事は Go3 Advent Calendar 2020 15日目の記事になります。

こんにちは、株式会社カミナシのエンジニア @tomiです。

JavaScript, Node.jsをメインに扱ってきたエンジニアがGoに触れるときにどう解釈したかを、JavaScriptGolangを比較しながら、理解を深める記事となっています。

はじめに

まず私自身のJavaScriptへの理解としては、VueやReact,Redux.Next.jsなどのフロントエンド周りに加え、ExpressやAdonisJSなどのバックエンドもかじっており、Node.jsでフルスタックに開発できるレベルです。
また、TypeScriptもここ1年ほどは個人開発でも積極的に取り入れております。

そんなJavaScriptにしがみついてきた私が、カミナシへのジョインをきっかけにGo言語を本格的に触れることになり、コードをどう噛み砕いったかを改めてまとめました。

ひとつひとつの機能を紹介するというよりは、Goではこう書くのかという視点となります。

さっそく、コードを見ながら比較してきます!
(Goが静的型付け言語なので、JavaScriptではなくTypeScriptとの比較がほとんどとなります。)

import/export

まずはじめに、コードを開くとpackageimport がほとんどのファイルに書かれています。

// hello/world.go
package hello

import (
  "fmt"
)

func World() {
  fmt.Println("Hello, World!")
}

上のソースを見たらだいたい察しは付くと思いますが、

importは外部パッケージを読み込んだり、作成したpackage を読み込むことができます。

JavaScriptでいうとこの部分。

import fmt from "fmt"

exportに関しては、packageに書いたパッケージ名から関数を読み出せるようにしており、先程のHello, World!を出力するには以下のように書きます。

package main

import (
  "./hello"
)

func main() {
  hello.World()
}
// Hello, World!

これをJavaScriptで書くと、以下のようになります。

// hello/World.js
export default function World () {
  console.log('Hello, World!')
}

// hello/index.js
import World from './World.js'

export default {
  World,
}

// index.js
import hello from './hello'

hello.World()
// => Hello, World!

Goでは同じpackage名のファイルを複数用意できるので、関数を複数のファイルに分割することがとても簡単になっています。

// hello/local.go
package hello

import (
  "fmt"
)

func Local() {
  fmt.Println("Hello, Local!")
}

// main.go
package main

import (
  "./hello"
)

func main() {
  hello.World()
  hello.Local()
}

// Hello, World!
// Hello, Local!

Var(変数)

変数でよく使う書き方が2パターンあります。

// ひとつめ
var hello string
hello = "Hello, World"

// ふたつめ
hello := "Hello, World"

:=とすることで、宣言と代入をまとめることができます。

Typescriptで書くと、以下のようになります。

// ひとつめ
let hello: string
hello = "Hello, World"

// ふたつめ
let hello = "Hello, World"

Function(関数)

上でもさらっと書いていますが、funcが関数となっています。

Goでは、Typescriptのように引数や返り値には型が必須になります。

func GetHello(name string) string {
  message := "Hello, " + name
  return message
}

(name string)で引数nameはString型であることを示し、関数getHelloの返り値の型を引数( )の後ろ書きます。

これをTypescriptで書くと、以下のようになります。

function GetHello(name: string): string {
  const message = `Hello, ${name}`
  return message
}

for ... range(繰り返し)

fruits := []string{"apple", "banana", "orange"}
for index, fruit := range fruits {
  fmt.Println(index, fruit)
}
// 0 apple
// 1 banana
// 2 orange

for i := 0; i < 10; i++ { ... }という書き方もありますが、for ... rangeを使ったやり方の方が良く見かけます。

indexが不要な場合は、ブランク修飾子_を使って、for _, fruit := range fruitsと書きます。

これをTypescriptで書くと、以下のようになります。

const fruits = ["apple", "banana", "orange"]
fruits.forEach(fruit, index) {
  console.log(index, fruit)
}

Struct(構造体)

type User struct {
  name string
  age int
}
tom := User{"Thomas", 20}
fmt.Println(tom.name, tom.age)
// Thomas 20

これをTypescriptで書くと、以下のようになります。

type User = {
  name: string
  age: number
}
const tom = new User("Thomas", 20)
console.log(tom.name, tom.age)

classを使った書き方もできます。

class User {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}
const tom = new User("Thomas", 20)
console.log(tom.name, tom.age)

レシーバ(構造体と関数の紐付け)

先程作成したtomが、tom.intro()という関数で自己紹介を出力したいとする。

type User = {
  name: string
  age: number
}

func (u *User) intro () {
  fmt.Println("I am " + u.name)
}
const tom = new User("Thomas", 20)
tom.intro()
// I am Thomas

関数名の前に構造体を記述することで、構造体用の関数であることを示し、関数内で構造体を扱うことができる。

これをTypescriptで書くと、以下のようになります。

class User {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  intro() {
    console.log(`I am ${this.name}`)
  }
}
const tom = new User("Thomas", 20)
tom.intro()

はじめてこの関数を見たときは、関数名の前後に( )がある・・・なんだ?🤔となりました。

実際には↓のように引数も返り値もあるので、さらに複雑に見えたのが初見の印象。

func (r *Report) GetReports(limit, offset int64, query *entity.GetReportsQuery) (*entity.Reports, int64, error) { ... }

まとめ

JavaScript脳がGolangを理解するために、JavaScriptGolangを比較してみました。

この書き方で戸惑っていたのかと思うと自分のレベルの低さに落ち込みますが、Goを触ってみたいという人も同じところで悩むはず!
そんな人のお役に立てる記事となれば幸いです。