search
LoginSignup
195

More than 3 years have passed since last update.

posted at

updated at

これからGolang開発を行うRubyistたちへ

背景

  • APIサーバー開発などを今までRuby(主にRails)で開発していたRubyistがサーバーサイド全部をフルGolangで実装している会社に転職して、2週間が経ち今までのGolang開発で学んだことをまとめました。
  • これからGolang開発をするRubyistたちが、「自分と同じようにつまずくかもしれない」または「Golangを本格的に書き始める前に知っておきたかったなあ」と思うところがまとめてあります。
  • 最近、Ruby on RailsやRuby開発を進めている会社でも、必要な箇所でGoLang開発を行なっていると聞いたので、Golang開発に興味があるRubyistが大勢いるじゃないかと思い、転職をきっかけに記事を書かせていただきました。
  • 「Golang開発やってみたい!」「会社で導入することになった」というRubyistたちの最初のステップになれば幸いです。
  • Rubyはこうだけど、Golangだとこういう風に書くよと比較のコードも混ぜています。

対象

  • Rubyを書いたことがありGolangを書いたことがないけど、これからGolang開発始めよう、または興味がある人。

インストール法は?

  • goenv をおすすめします。
    • rbenv とほぼ同じと思ってもらっていいです。
  • バージョンの切り替えも簡単にできます。
    • localglobal のオプションもあるので、プロジェクトごとやデフォルトでもバージョン設定も可能です。
  • rehash と入力すると、 .ruby-version と同じく.go-version というファイルもできます。プロジェクトでなんのバージョンが使用されているのかチーム内で共有することができます。
$ git clone https://github.com/dataich/goenv.git ~/.goenv

# ログインシェルの設定ファイルに
export PATH="$HOME/.goenv/bin:$PATH"
eval "$(goenv init -)"

$ goenv install 1.7 # お好みのバージョン
$ goenv global 1.7

基本的な文法

型に関しては、ここでは省略します。

  1. Goでは、変数と関数はキャメルケースが使われています。
    • Rubyは、スネークケースですよね。
  2. Goでは、publicにしたいプロパティ/関数を大文字を使います。privateにしたいプロパティ/関数は小文字にし、違うパッケージからのアクセスすることはできません。
  3. Goでは、Rubyのような破壊的メソッドを書くときは、構造体(Rubyでいうとクラス)のポインターをレシーバーにしないといけません。
    • Rubyにはポインタの値渡しがないので、Goを勉強すると最初にここでつまづくと思います。
  4. iota は、型なしの整数の連番を生成します。
    • これは好き嫌いが別れると思いますが、他の言語にはない文法なので、紹介します。
    • 通常はintを元にした定数型を作って型同士の区別ができるようにします。
  5. Goでは、定数は const で宣言します。
  6. Goでは、構造体のインスタンスを作る方法がいくつかあります。
    • new を使うと 空のポインタ型の構造体のインスタンスを作成します。
    • 構造体名{プロパティ: 値}空の構造体を作ります。
user.go
package main

import "fmt"

// 定数
const (
    NonPremium = iota
    Premium
)

type User struct {
    firstName     string
    PremiumStatus int
}

// インスタンスメソッド (レシーバー名 関数名(引数) 返り値
//  構造体の中身を変更させるものなので、レシーバーをポインタ型にしないといけない。
func (u *User) UpdateToPremium() {
    u.PremiumStatus = Premium
}

func main() {
    user := &User{FirstName: "hoge", PremiumStatus: 0}
    // これだと、ポインタ型の空の構造体を作る。FirstNameとPremiumStatusには、空文字と0が入る。
    // user := new(User) 
    user.UpdateToPremium()
    fmt.Println(user) // &{hoge 1}
    fmt.Println(user.firstName) // hoge
}

これでも動きますが、通常はiotaをそのままintとして使わずに型を作ってあげます。

user.go
type PremiumStatus int

// 定数
// NonPremiumとPremiumはPremiumStatus型になるので
// int変数には保存できなくなる
const (
    NonPremium PremiumStatus = iota
    Premium
)

type User struct {
    firstName     string
    PremiumStatus PremiumStatus
}

func main() {
    user := &User{FirstName: "hoge", PremiumStatus: NonPremium}
}
User.rb
class User 
  # 定数
  NON_PREMIUM = 0
  PREMIUM = 1

  def initialize(first_name, premium_status)
    @first_name = first_name
    @premium_status = premium_status
  end

  private 
  # 破壊的メソッド
  def update_to_premium!
    @premium_status = PREMIUM
  end
end

user = User.new('hoge', NON_PREMIUM)
user.update_to_premium!

その他の文法

1. 可変長変数

  • 便利なので、Golangでの書き方を共有
splat_operator.go
package main

import "fmt"

func main(){
  SplatOperator("a", "b", "c")
}

func SplatOperator(args ...string){
  fmt.Println(args) // => [a b c]
}
splat_operator.rb
def splat_operator(*args)
 p args
end

splat_operator('a', 'b', 'c') # => [a b c]

自分がつまずいたエラー集

1. Golangでは、ダブルクオテーションとバッククオートで表現する。

  • Golangではダブルクオテーションとバッククオートを使いますが意味が違います。
  • 自分がRubyを書くとき、文字列の中で変数を展開するとき以外は、シングルクオテーションを使いますが、その癖でビルドしてみるとエラーの嵐が...。
  • シングルクオテーションのほうが速度が上とシングルクオテーションを書くRubyistは注意です。
  • バッククオートはエスケープシーケンスを解釈しないかつ、改行はそのままテキスト中でも改行になるため、ヒアドキュメント(複数行対応可)です。
  • 式展開はどちらもないです。
sample.go
package main

import "fmt"

func main(){
  fmt.Println('hoge') // これはエラー
}

2. ポインタ型のスライスからは、要素を取得することができない。

  • 2週間でここに一番詰まりました。
  • Golangには、ポインタの概念があり、最初Rubyistがつまずくのかなと思っています。
  • Golangには、「スライス型」という可変長配列があります。
    • 配列もありますが、それは要素数などが決められていて、実際に業務で書く時には、要素が固定で決められることはあまりないと思うので、このスライス型がよく使われます。
  • 実際につまづいたエラーは下のものになります。
# command-line-arguments
./sample.go:7:18: invalid operation: fuga[0] (type *[]int64 does not support indexing)
Sample.go
package main

import "fmt"

func main(){
  hoge := []int64{2, 3, 4} // => スライス型を定義
  fuga := &hoge // => わざとポインタ型にしています。
  fmt.Println(fuga[0]) // => invalid operation: fuga[0] (type *[]int64 does not support indexing)
}
  • エラー文にあるように、ポインタ型のスライスはインデックスをサポートしていない = 使えないよということです。
  • なので、ポインタ型から値型に戻さないといけないです。
sample.go
package main

import "fmt"

func main(){
  hoge := []int64{2, 3, 4}
  fuga := &hoge // => わざとポインタ型にしています。
  fmt.Println(*fuga[0]) // => 2 ポインタから値に戻しています。
}

3. インスタンスメソッド内のレシーバーの名前は、thisselfじゃだめ。

receiver name should be a reflection of its identity; don't user generic name as such or this
  • これはエラーではなく、golintで注意されたwarningです。
  • 下のようなUser構造体を作り、インスタンスメソッドを生やして、自身のオブジェクトにアクセスするときに、Rubyだと self と書いてしまうと思います。
user.rb
class User < ApplicationRecord
  def name_with_prefix
    "Mr. #{self.name}" 
  end
end
user.go
package model

type User struct{
  Name   string
  Gender int64
}

func (s *Student) NameWithPrefix() string {
  return "Mr. " + s.Name
}

// これはwarning
// func (self *Student) NameWithPrefix() string {
//   return "Mr." + self.Name  ここはwarning
// }

4. 構造体のインスタンスを作成する方法はいくらでもある。

  • Rubyだとクラスのインスタンスを作る方法は、 下のように new しかないですよね。
sample.rb
class Sample 
  attr_reader :title

  def initialize(title)
    @title = title
  end
end

sample = Sample.new('sample')
p sample.title
  • しかし、Golangでは、下のような構造体があったときに、下の数だけ空の構造体を取得するやり方があります。Rubyistに気をつけてほしいのが、 newポインタ型の構造体 を返すことです。
  • Rubyではnewでインスタンス取得できると思いますが、Rubyにはポインタの概念がないので、Golangでnewとすると、ポインタ型ではない空の構造体が取得できると思われがちですが、ポインタ型なので注意が必要です。
vehicle.go
type Vehicle struct{
  Name string
}
main.go
package main

func main(){
  // 同じ意味 空の構造体を返す
  var v Vehicle = Vehicle{} 
  vehicle := Vehicle{}

    // 同じ意味 Golangでの`new`は、 ポインタ型を返すので注意 
  vehicle := new(Vehicle)
  vehicle := &Vehicle{} 
}

5. Map初期化

  • ここでいうMapは、Rubyでいうハッシュです。
  • 注意してほしいのは、Mapは宣言に加えて、一度初期化を行わないといけないことです。
  • Rubyだと初期化する必要は全くない & Map型だけ初期化が必要になってくるので、ここでまとめておきました。
  • 参考: http://otiai10.hatenablog.com/entry/2014/08/09/154256
panic: runtime error: assignment to entry in nil map
sample.go
package main

import "fmt"

func main() {
    var m map[string]string
    fmt.Println(m) // map[]

    /* panic: runtime error: assignment to entry in nil map
       m["hoge"] = "fuga"
       fmt.Println(m)
    */

    // 初期化すればOK
    m = map[string]string{}
    fmt.Println(m) // map[]

    m["hoge"] = "fuga"
    fmt.Println(m) // map[hoge:fuga]
}


まとめ

  • たった2週間ですが、まとめてみると多くのところに詰まったと思っています。
  • 時間ができたときは、さらに自分が詰まったところを残しておこうと思います。
  • これからGolangデビューするRubyistの参考になれば幸いです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
195