なまえは まだ ない

思いついたことをアウトプットします

ぼくがかんがえた(わけでもない)さいきょうのGoかいはつかんきょう

フラーに入社してもうすぐ1年になります。ということは、Goを触り始めてもうすぐ1年ということです。 良い節目(?)なので、ここ最近の私の開発環境を紹介します。

VSCodeがド安定

Goのコードは基本的にVisual Studio Codeで書いてます。Microsoftが言語拡張機能を提供しているので、とりあえずそれを入れておけば事足ります。

Go - Visual Studio Marketplace

VSCode以外にも定番エディタはありますが、VSCodeが安定すぎて調べてません。他にもっと良いエディタあったら教えてください。

VSCodeの追加設定

とりあえずGoの拡張機能を入れれば事足りる、とは書きましたが、それだけではつまらないので追加で設定している項目を紹介します。

Lint Tool

弊社ではGoのコード静的解析ツールとしてgolangci-lintを使ってます。GitHub Actionsに言わずとしれたreviewdogを登録することで、プッシュされた変更に対して自動でLintがかかるようにしています。

github.com

で、ですよ。いくらGitHub上でreviewdogさんが静的解析してくれるとはいえ、プッシュして初めて警告に気付くってなんか癪じゃないですか。 VSCodeではGoのLintツールにgolangci-lintを指定できるので、これを設定しておくことでワークスペースのコードにもLintが効くようになります。

"go.lintTool": "golangci-lint"

Use Language Server

私がGoを触り始めたのがほぼ1年前。Goは1.12系と1.11系がメインで走っていた時期かと思います。 Go 1.11/1.12といえばGo Modulesが実装され始め、Go関連のツールやライブラリがGo Modulesへの移行を絶賛進めていた時期かと思います。

その当時、GoのLanguage Serverといえばgo-langserverだったかと思いますが、残念ながらこちらはGo Modulesに非対応。 弊社では私が入社した時点ですでにGo Modulesへの移行をほぼ完了していたため、Language Serverのパワーを知ることなく選択肢から外れてました。。

github.com

時は経ち、いつの間にかGoogleによる公式のLanguage Server、goplsが登場しました。goplsがあればコードフォーマッタやインポート、ナビゲーション等全部やってくれます。多分。今までgoimportsとか入れてた設定が全部不要になります。多分。

VSCodeからはLanguage Serverを有効にすると、自動的にgoplsが選択されます。

"go.useLanguageServer": true

VSCode の機能で生産性を上げる

今までVSCodeをあまりちゃんと使えてなかったのですが、Visual Studio Code実践ガイドという書籍を読んで少し勉強しまして、いくつか便利そうな機能を使ってみました。 この書籍、基本的な機能から便利な拡張機能までたくさん紹介されています。 興味がある方は是非お手にとってみてください。

Visual Studio Code実践ガイド —— 最新コードエディタを使い倒すテクニック

Visual Studio Code実践ガイド —— 最新コードエディタを使い倒すテクニック

  • 作者:森下 篤
  • 発売日: 2020/02/21
  • メディア: 単行本(ソフトカバー)

GoaのDSLテンプレートを挿入するスニペット

弊社サーバーサイドではフレームワークとしてGoa v1、を更にforkしたものを使用しています。

github.com

基本的な使い方はGoaとほぼ同じなので割愛しますが、独自のDSLによってAPIのエンドポイントを設計します。 で、このDSLを書く時に必要なパッケージをドットインポートするわけですが、このドットインポートをぱっと書く方法が無くて困ってました。

DSLを記述するパッケージ(大体はdesignパッケージ)で最初に書くコードは大体固定なので、これを挿入するスニペットを作ってみました。 VSCodeのメニューから新規スニペットを構成して、 go.json という名前で保存します。

{
    "Goa Design":{
        "description": "Goa で書く design パッケージの共通部分",
        "prefix": "dsl",
        "body": [
            "package design",
            "",
            "import (",
            "\t. \"github.com/shogo82148/goa-v1/design\"",
            "\t. \"github.com/shogo82148/goa-v1/design/apidsl\"",
            ")",
            "",
            "var _ = Resource(\"${TM_FILENAME_BASE}\", func() {$0})",
            ""
        ]
    }
}

${TM_FILENAME_BASE} という部分はスニペットを挿入するファイル名(拡張子抜き)が挿入されます。例えば sample.go というファイルにこのスニペットを挿入すると、ここには sample という文字列が入ります。Resourceの名前とファイル名を一致させる場合が多いのでこのような設定にしています。

{$0} はこのスニペットを挿入した後のカーソル位置です。このスニペットを挿入した後はResourceの中にDSLを記述していくことになるので、この位置にカーソルがあると便利です。実際に実行してみると以下のような感じになります。

f:id:furusax0621:20200330224902g:plain

go generate を実行するタスク

Goaで書いたDSLを基にコードを自動生成してるわけですが、これをgoagenを使って自動生成してしまうと実際は不要なコードまで生成されます。 それだと色々使い勝手が悪いので、弊社では次のような自動生成用のコードを書いて、 go generate でこれが実行されるようにしています。

package main

import (
    _ "github.com/furusax0621/goasample/webapi/design"

    "github.com/shogo82148/goa-v1/design"
    "github.com/shogo82148/goa-v1/goagen/codegen"
    genapp "github.com/shogo82148/goa-v1/goagen/gen_app"
    genswagger "github.com/shogo82148/goa-v1/goagen/gen_swagger"
)

func main() {
    codegen.ParseDSL()
    codegen.Run(
        genswagger.NewGenerator(
            genswagger.API(design.Design),
        ),
        genapp.NewGenerator(
            genapp.API(design.Design),
            genapp.OutDir("app"),
            genapp.Target("app"),
        ),
    )
}

というわけで新しいAPIを開発するときは、まずdesignパッケージでDSLを記述し、一旦ターミナルに移動してgo generateを叩き、その後またVSCodeに戻ってコーディングし。。という感じでVSCodeとターミナルを行ったり来たりしてました。これが微妙に煩わしいので、go generateをタスク機能で叩けるようにしてみました。

リポジトリのルートに .vscode/tasks.json というファイルを作り、例えば次のように記述します。

    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "generate",
            "type": "shell",
            "presentation": {
                "echo": true,
                "reveal": "silent",
                "focus": false,
                "panel": "shared",
                "showReuseMessage": true,
                "clear": false
            },
            "command": "go",
            "args": ["generate", "./..."]
        }
    ]
}

ポイントは presentation パラメータの "reveal": "silent" の部分です。タスクを実行した時にコンソールを開くかどうかの設定で、デフォルトは always に設定されています。silentにするとタスクが失敗した時のみコンソールを表示するようになります。go generateの結果は失敗した時に見れば良いので、こういう設定をしておくと毎度コンソールがピョコっと現れて煩わしさを感じることがなくなります。

依存関係の更新タスク

さて、開発を進めてると依存関係がどんどん更新されていきますよね。リファクタリングとかしててパッケージの依存関係が変わったりすると、 go.modgo.sum に差分が出てきたりします。それらをガッと整理するのに時々 go mod tidy を叩くのですが、これもタスク定義することができます。先程の tasks.json"tasks": []の中に、次のような記述を追加します。

{
    "label": "gomod",
    "type": "shell",
    "presentation": {
        "echo": true,
        "reveal": "silent",
        "focus": false,
        "panel": "shared",
        "showReuseMessage": true,
        "clear": false
    },
    "command": "go",
    "args": ["mod", "tidy"]
}

ポイントは先程のタスクと同じです。最も、 go mod tidy の実行結果を眺める必要性は多分全く無いので、 reveal の設定は never でも良いかもしれません。

まとめ

というわけで、最近の私のGo開発環境の紹介でした。なんだかんだVSCodeがめっちゃ便利なので、よっぽどのことが無い限りこれからも使い続けるんじゃないかと思います。