個人開発してるやつを Python3 から Golang に書き換えてみた

TuneCoreJapan 新卒エンジニアの skfvr です。

この記事は  [WanoグループAdvent Calendar 2019](https://qiita.com/advent-calendar/2019/wano-group)  4日目の記事です。

# この話題にしたきっかけ
現在自分の配属されてるプロジェクトでは基本的に Perl+Ruby によって開発されていますが、自分が新卒として配属されたタイミングで「Golangを使ってリプレースする」が本格的に始動しました。

それに先立って自分で動かしてる個人開発物(Python3 で開発)を全部Goに入れ替えてみるか！という感じでやってみました。その中で感じたことを話していきたいです。

# 諸注意
* どちらかの言語を上げ(下げ)する目的の記事では無いことを先に断っておきます(不快に感じる方が居たらごめんなさい)。
* 初投稿故、誤字/脱字/知識不足を暖かく見守っていただけると幸いです。知識不足の祭は指摘していただけると嬉しいです。



# もともとどんなものを動かしていたのか
個人開発と言っても大した規模のものは動かしていませんでした。

* Slack incoming webhook / slash command
* Twitter Bot 
* その他...

など。参考にならないと思いますがファイル数も100ファイル満たず、行数も多くありません。
SlackBotの内訳は自作したTODO管理やメール管理などといったものです。
TwitterBotはツイートの取得からツイートの投稿、フォローなどある程度の機能を使用していました。


# 入れ替えてよかった！なところ
最初は良かったなぁというところから。

## Go側でいろいろチェックしてくれる
Go言語に切り替えてよかったのは gofmt など以下の存在だと思います。

* gofmt : 自動的に整形してくれる(公式で提供されるので個々人の間でブレない)。
* goimports : 使用しているパッケージをコードから勝手に import してくれる。
* go mod : 使用しているパッケージを良い感じに管理してくれる。
* 他、開発者が作成した linter など：未使用変数や error 確認忘れなどを検査できる。

個人開発でも十分な量ですが、特にgofmt などは公式で提供されているので**チーム開発などでもブレる心配がない**というのは大きいと思います。
~~単純にコードを書くときにも後でどうせ gofmt/goimports するから `=` の間にスペースを手動で入れなくても良い。~~

有志の方が作成した静的解析ツールなどを使えば更に高度なチェックが出来るのでコードの質を保ちやすいのかなと思います。これも複数人開発の場合には特に役に立ちそうです。

また、型の方についてもGo言語は非常に厳しいと思います。それ故「実行するまでわからん」が減りました。interface{}とかを使うときは少しヒヤヒヤします。

## error
関数の返り値に err を同時に返す方式が個人的にはとてもハマりました(良い意味で)。
例えば文字列を数字に変換する関数 `strconv.Atoi(string)(int, error)` という関数があります。簡単に言えば変換にミスったら `error!=nil` の値が帰ってきてそれを受け取るわけですね。


``` sample.go
hoge, err := strconv.Atoi("1")
// => hoge == 1, err == nil
// 変換が成功したら変換成功したときの値と err(==nil) が返ってくる

hoge, err := strconv.Atoi("hoge")
// => hoge == 0, err == NumError
// 変換ミスで err が返ってくる
```

個人的に try-catch するよりもどこで何が返却されるのかなどすごくわかりやすいのが良かったです。ついでに err を使ってログ部分も書きやすかった。意図的に無視する場合はアンダースコアで受ければ ok だと思います。

``` sample.go

hoge, _ := strconv.Atoi("5")

```

``` sample.py
try:
    hoge = str("hoge")
except:
    # try-catch が多くなると辛い（'ω`）...
```

特に「ある関数の返り値に error 」がある => 「そいつが error を返す可能性がある」と分かりやすいのも安心です。
ただし関数によっては error を返却せずに `panic` を起こして終了などもあるので気をつけましょう...。


## (実行速度が)早い
ここは言うこと無い気がします...。

自分は卒業論文でのシミュレーションプログラムをPython3で書きましたが1試行あたり大体1分ほど掛かっていましたが...卒業論文が終了した後に書き換えたら言い過ぎかもしれませんが秒で終わりました...。

ほぼ同じようなアルゴリズムで組んだはずですが(Python3 と Golangだけを比較すると仮定して)速度を求める場合はGolangに軍配が上がるということでしょうか。




# 書き換えるときに苦戦したところ

次に書き換えるのに苦戦したところを紹介します。

## こまかーい部分

例えば最大値ほしいとき。

``` math.Max.go
func Max(x, y float64) float64
```

です。
Python3では

```max.py
max(1,2) => 2
max(1,3,2,7,6,5,9,8,4) => 9 # 複数 ok

int_list = [1,3,5,9,7]
max(int_list) => 9
# リストならそこから最大値を取得する
```

など様々な書き方ができました...。

特にGolangでも `len` 関数はあるのになんで `min` や `max` が！使いにくい！みたいに最初は思ったのを覚えています。

後は文字列操作部分だったり...。


Pythonの場合

```sample.py
hoge = "bar,piyo,foo"
splited = hoge.split(",")
```

Golangの場合

``` sample.go
import strings

hoge := "bar,piyo,foo"
splited = strings.Split(hoge, ",")

```

ココらへんはよく間違えます。（'ω`）
このようによく使う関数というのは中々癖が抜けませんでした。この辺りは辛いポイントの稼ぎどころでした。

## リスト内包表記を書きたい(個人的な願望)
書き換え中はしばらくアレルギーを起こすレベルでした(呆れ)。Golangを本格的に書き始める前はPython3ばっかり書いていたのでここでも辛いポイントを大量に稼ぎました。


```sample.py
hoge_list = [i for i in range (10) if i%2==0]
```

これに慣れすぎてしまっていたのでGolangで同じ操作を記するときになかなかしんどかった...との記憶があります。

もちろんリスト内包表記以外にもPythonで使いまくっていた~~闇(?)~~文法だのがあったのでそれを使わず書き換えるときには苦労しました。


## 必要パッケージなどを探す必要がある

これはPythonもGolangも関係無く、ある言語から別の言語に書き換える場合に起きる事象です。

例えば自分の作っていたものでTwitterやSlackだののサービスやMeCabなどの形態素解析など外部のものを組み込んでいたので。

* mecab
* Twitter
* Slack
* Mysql
* Firebase

など5つ程使っていたのでパッケージを探しておくことは重要だと思います。最悪「**前の言語だと XXXX のAPIパッケージ合ったのに新しい言語だと見つからない！**」とかあります。
ここら辺の入念な調査は必要でした。特に移行先の言語がかなり新しい言語だと本当に見つからない可能性も有ります...。

## 番外編

本筋とはあまり関係ない(?)番外編です。

### 競技プログラミングするとき

自分は競技プログラミングなどをする場合、例えば「整数の配列を受け取る」であれば

```sample.py
# ipunt > 1 3 4 2 6 8 1 2 3
hoge = list(map(int, input().split()))

```

とかよくやってました(もしかしたらもっといい方法あるのかも?)。
Golangで競技プログラミングはやったことないですがどうやるのでしょうか...。



# 入れ替えた感想と知見

* (もしクソコードが存在して入れば)**クソコードを一掃することができるチャンス**。
* 覚悟は必要(規模としては小さいけどチマチマやって1か月かかった)。特に細かい部分の癖は抜けない。
* **外部サービスなどのパッケージは絶対に調査しておこう。**また、ビルトインでも「なんでこれがないの...」もあるかもしれない。
* **~~リスト内包表記中毒が治らない~~**

特に3つ目の項目は重要だと思います。「言語移したら対応パッケージ無かった！」は十分起こる可能性が有りますので。

何かの参考になれば幸いです。

現在は業務で Perl => Golang の移行を初めたばかりですが、来年の今頃にはそれについて知見を得て話をアップグレードできればと思います。