【第 1 回 cocone tech talk・前編】Java開発者のGoプロジェクト1年の振り返り【イベントレポート】

こんにちは。cocone tech blog 編集長の N です。

 

https://connpass.com/event/207743/

https://techplay.jp/event/811842

 

先日 2021/03/25 に「Java開発者のGoプロジェクト1年の振り返り / 大規模リファクタリングについて」というオンラインイベントを開催しました。

弊社の『ポケコロ』のクライアントエンジニアから1名、『ポケコロツイン』のサーバーエンジニアから1名が登壇し、各プロジェクトでの取り組みをライトニングトーク形式でお話ししました。

 

本記事はイベントレポートとして、トピックと話していた内容をまとめたものを前編・後編で分けたものの前編になります。

 

 


登壇者について

 

林 賛昊(イム チャノ)さん

サーバー / ポケコロツイン開発リーダー。

NHN Japan 株式会社(現LINE株式会社)からココネに転職。その後、NC Japan を経て ココネに再度入社。

アウトドア系が趣味で、キャンプ・登山・運動が好き。ボクシングやストラックアウトも好き。

韓国のアイドル BLACKPINK が大好き。

 

目次

 

 


Go? Project アサイン

 

チャノさんの開発言語のボリューム・何をやってきたかを示した図。

薄ら記憶から消えている言語もあるし、Unity を触ったりしていた。

それに比べて、Javaは図に見えるように、比べ物にならないくらいのボリュームになっている。

青い円が Java と言うよりは、背景が Java と言った方が近いかもしれない(経歴として)。

 

 

ある日、CTO の K さん から相談があった。

 

K「新規プロジェクトがリリース直近ですが、開発が足りないのでお願いします。できますよね?」

林「アハハハハ……^___^」

 

魂のない笑いとプロジェクトにアサインされることになった。

林「僕にとってはディープインパクトでしたね。Goという小惑星が直接衝突して絶滅の危機っていうところですね。もちろん頭の中も真っ白になっている状態でした」

 

 

しかし救いの手はあった。

アサインされる1ヶ月前に社内での Go 勉強会に参加して「Go ってこういう風にできるんだ!」と気づいて、他でも触った経験があった。

自身が(Goが)初めてということではなかったし、プロジェクトで Go の開発経験がある人からサポートを受けることもできた。

また、リリースまで1ヶ月残っていたので、CMS 作成でトレーニングを始めた。

 

 


Go の特徴

  • まず、早い
    • コンパイル・起動が秒速。サーバーモジュールを立ち上げた時、一瞬で起動が終わってしまったので、起動していることに気づかなかった
    • 処理スピードが速い(Java: バイトコードを実行時に機械語に変換、Go: コンパイルの時点で機械語に変換のため速い)
  • メモリ管理を Go で行う
    • Java は JVM で全てメモリ管理するのであまり意識しなくて良い。
    • ObjectCを経験した時にメモリ管理に失敗してプログラムが止まってしまうということがあり心配していたが、Goが全てやってくれるので楽だった
  • モジュールだけで動く
    • Java だとJVM をインストールしないといけないが、Go ではコンパイルしたモジュールだけ持っていけばどこでも動く。素晴らしいプラットフォーム。
  • goroutine 軽量スレッド処理(channel)
  • シンプルで使いやすい

 

 


Go という壁

ポインター型とバリュー型

どうしてもGoという壁があって、最初に感じたのはポインター型バリュー型

Java はプリミティブタイプ以外は全て内部的にポインタがあり、意識しなくても動かせた。

Go ではポインター型・バリュー型を明確に使い分けてコーディングしなければならない。最初は慣れなくて苦労した。

 

  • ポインター型
    • ポインターで参照する
    • *StructA, []*StructB
    • structA := &StructA{}
  • バリュー型
    • 値をコピーして参照する
    • StructA、[]StructB
    • structA := StructA{}
  • Map, Slice ポインター型のみ
    • 他のは全部あるのに、これはポインター型しかないのか?という疑問があった
  • タイプの厳格さ
    • Goでは必ず同じ型をアサインしないとコンパイルエラーになる
      • Java の場合は long 型の変数に int 型の変数をアサインしても特に問題なかったので、やりづらかった
      • int / int8 / int16 / int32 / int64, (uintptr)
        ※ int: OSやCPUなどの実装系に依存する

 

 

func main() {
	var val1 int32 = 1
	var val2 int64 = 2
	
	val2 = val1 // <- compile error
	
	val3 := val2 // <- ok, 新しい変数にアサイン
	
	println(val1)
	println(val3)
}

 

nil

  • Java の null に当たる nil が型を持っている。
    • Java 経験者からしたらありえない…!
    • サンプルコードの A 型 の nil と B 型の nil がイコールであるか比較すると結果が false になる。
  • nil 自体は単独で動く時は Java と同じだが、型にマッピングする時は型をもつ nil になる(これが壁だった)

 

 

func main() {
	var val1 *A = nil
	var val2 *B = nil
	
	equals(val1, val2)
}

func equals(x, y, interface{}) {
	println(x == y) // 結果は false
}

type A struct {
	val1 string
}

type B struct {
	val1 string
}

 

panic

  • Go で一番難しいと感じたところ
  • Java の Exception に該当
    • Java のException は処理しなかったら、JVM まで throw / catch されて処理される
  • panic は処理しないとプロセスが終了する
    • AP が死亡することもあった(開発者もパニックに…)
    • defer { ~ recover{} } でハンドリング可能。
      • Java の try ~ catch ~ finally に相当

error

  • パニックと同じくエラー系ではあるが、error型 は処理の結果によるもの
  • コーディングで「こういう状況ではエラー処理をしなければならない」というところでerror を返すパターンで実装が可能
  • function の return で異常終了を知らせる
    • func testFunction() (int32, error) {}
  • error は処理しなくても良い
    • rVal1, _ := testFunction()

 

 


Go と Java の差

interface と interface{} 型

Goでは interface と interface{} 型というものがあって、差がよくわからなくて苦労した。

  • inteface
    • Go, Java : 概念
  • interface{} 型
    • Java:interface{} 型はない。Objectに近い
      • Object → super class
    • Go:型(Data Type)
      • interface{} → 他のデータタイプと無関係
      • 全てのデータタイプが入れる(Java の Object 型と同じ)
      • データを取り出すのに型をチェックしなければならないのが Java と違うところ

multiple return value

  • Go で一番嬉しい機能
  • Java ではモデルを作る必要があったが、Go ではモデル不要で関数から 2 つ 3 つの要素を返せる

 

func main() {
	cocolonWelcomeMessage :=  "so cute!!"
	cid, ptData, err := getMultipleValues(cocolonWelcomeMessage)
}

func getMultipleValues(cocolonID string) (int32, *PokeTwinData, error) {
	// return data
}

func PokeTwinData struct {
	CocolonName     string
	CocolonThumbURL string
}

 

package

  • Go はクラスの概念がないため、package 単位で全てをハンドリングする
  • 1 個の package の中で複数ファイルがあっても、全て同じ package 名でアクセスできる

使わない変数・import はエラーになる

  • 無駄なコードをなくせて良い

 

 


Goと仲良くなれる近道

  • Java の習性は捨てる
    • Go は Object 指向の言語ではない(公式な言及はないが)
    • Java のようなパターンで実装するとコード的に嬉しくないことになる
  • 真似する
    • 他の Go のコードを読む、同じく実装する
  • CodeStyle Checkerを活用する
    • 他の言語でも様々な CodeStyle Checker がある
    • 実行してみてこういうパターンが良い・悪いを学べた
    • ポケコロツインのプロジェクトでは checkstyle を使っている

 

 


まだ慣れない Go のところ

暗黙的な処理(型推論)

タイプが厳格な言語と紹介したが、暗黙的な処理もある。Go が自動でやってくれるありがたい機能だが、まだ慣れない。

 

  • 例 1 )数値を暗黙的に int にされた
    • param := 100 -> var param int = 100

 

  • 例 2 : Sliceの場合)宣言したのに初期化される
    • var sliceParam []string -> nil だが初期化される

 

primitive typeのアドレス参照

  • Java では primitive type は全て値でハンドリングする
  • Go の場合はアドレスで参照できる(でも値でいいじゃん……)
    • count := 100  abc := &count

 

関数型変数

  • Java でも 関数をパラメータで渡す機能はあるが、Go ではもう一歩先進した感じ。
    変数に関数を宣言して、その関数を色々なところで使う機能がある。慣れると便利だが、まだ慣れないので仲良くなりたい。
  • func sampleFunction(paramFunc valFunc() rtn)

 

Java が恋しくなるところ

Genericsが使えない

いちいちデータのタイプをチェックして、switch ~ caseなどでこの型ならこの処理…を書かなければならない。Javaだったらすぐできる。

高度な抽象化が難しい

interface があまり強くない

interface型のConvert

いちいちタイプチェックから変換が必要……何が入っているかわかってるからやってほしい

 

 


Go で Go! Go! Go!

  • Go は軽い!早い!がメイン
  • 本番はGoモジュールだけで完結
    • 10Mb程度のモジュールで完結可能
    • 環境構築が不要
  • 学習しやすい
    • 同プロジェクトのメンバーも実感していて間違いないと思う
  • 性能が高い!

 

 


担当プロジェクト紹介

ポケコロツイン

 

技術スタック

Server Go
Client Unity(C#)
通信 gRPC
データタイプ protocol buffer

最近メンバーの成長が凄まじい。コードレビューの時にチームメンバーから「ここはこうですよ」という指摘を受ける(若手の成長が早くて慣れやすい!)

 


Tips

 


Q & A

Q1. エラーハンドリングについて、Golang の error 型で扱うのと、Javaのように Exceptionで取り扱うのはどちらがあっていましたか?

A1. Go で panic を ハンドリングするのがあっているのか、errorをハンドリングするのがあっているのかということでしょうか。

 

Java は Exception が発生したら try ~ catch で拾って処理して JVM 側に戻すこともできるし、止めて次の処理に流すこともできます。

Go でも同じく、panicが発生した場合、defer {~ recover{} } で拾って、panic を投げて処理を終了させることもありますが、プロセスが死んでしまうこともあるので、error 型に変えて返すというパターンで実装しています。

 

バッチ処理など、プロセスが止まっても良いものであれば、panic を投げれば処理が終了するのでそれでも問題ないですが、プロセスが死ぬ時に何もログなどを残せないので、あとでなぜ死んだのかを追えなくなります。

なので、panic は処理をしてから error にするのが正しいかなと思います。

Q2. (Q1補足)Goでは panic を使わずに if error で関数から error を返却するのが一般的ですが、panic の方が使いやすかったですか?

A2. わざとpanicを発生させる場合があります。

システムエラーなどの場合はpanicが発生する(処理上発生する)ので、defer ~ recoverを使って error に変換して次の処理に続けています。

panic の方が使いやすいなどということはないですが、プロセスが死んでしまうので、必ず処理しなければならない場合があります。基本的にわざと panic を発生させることは少ないです。

 


スライド

 


 

レポートは以上です。

 

まとめ

今回は Java 開発から Go 開発に移行したエンジニアの 1 年の振り返りをご紹介いただき、レポートとして掲載いたしました。
私は特に「Go の壁」と「Java と Go の違い」を興味深く感じました。ここを抑えていけば Go と早く仲良くなれそうだなと、楽しんで聞くことができました。
これから Go を学ぶ人の参考になると思います。

後編ではポケコロでの大規模リファクタリングについてをご紹介します。
100 万行を超えるスパゲッティコードのリファクタリング……気が遠くなりそうですね。

 

後編:

【第 1 回 cocone tech talk・後編】大規模リファクタリング〜100万行超えのスパゲッティコードからリファクタリングする際やるべき9項目〜【イベントレポート】

 

 

また、ココネでは一緒に働く仲間を募集中です。

ご興味のある方は、以下のリンクから是非ご応募ください。

 

ココネ株式会社 採用情報

サーバーサイドエンジニア(ジュニア)

サーバーサイドエンジニア(シニア)

 

Category

Tag

%d人のブロガーが「いいね」をつけました。