2020 年振り返り

ライフイベント

娘が生まれました。

f:id:tchssk:20201231222513j:plain

OSS

Goa v3 のプラグインを作りました。

tchssk.hatenablog.com

Goa v1 から v3 への design 移行をサポートするツールを公式に移譲しました。

tchssk.hatenablog.com

買い物

リングフィット アドベンチャー

ヨドバシ.com の抽選で当たりました。一ヶ月くらいは続けていたのですが、娘が生まれてからやらなくなってしまいました。代わりに抱っこをするようになったので運動量自体は変わっていないはず。

リングフィット アドベンチャー -Switch

リングフィット アドベンチャー -Switch

  • 発売日: 2019/10/18
  • メディア: Video Game

SHARP KS-HF10B-B

5 年以上使っていた無印良品の炊飯器を人に譲ってしまったので購入しました。廉価モデルより美味しく炊けて欲しいけど高級モデルのような多機能は要らない、ということで選定。かつての三洋電機の開発者が手がけたモデルだとか。普通に美味しいです。

The Last of Us Part II

ついに発売された The Last of Us の続編。 Game of the Year も受賞していましたね。賛否両論を呼んだ作品ですが個人的には良かったです。ボリュームもすごい。

Lenovo ThinkPad E495

久しぶりに Windows を触りたくなって Ryzen 搭載モデルを探していたらコスパ最強という触れ込みを見つけて購入しました。 WSL 入れたりはしたけどまだあまり活用できていない ...

SONY ILCE-6000

娘の写真を撮るのに iPhone では飽き足らなくなり購入しました。 2014 年に発売されたモデルですがカメラ初心者には十分なスペックだと感じます。

SONY SEL30M35

HARIO V60コーヒーサーバー02 セット

レギュラーコーヒーを頂いたのでドリッパーが必要になり購入しました。サーバーとフィルターも付いて 1,000 円程で買えるのすごい。

総括

子供可愛い。無事親バカになりました。

Goa v1 から v3 へのマイグレーションツールを公式に移譲した

これは Go2 Advent Calendar 2020 の 13 日目の記事です。

以前作った Goa v1 から v3 へのマイグレーションツールを Goa 公式の GitHub Organization に移譲しました。

元々このツールは自分が関わっている Goa v1 のプロジェクトを v3 に移行するために作ったのですが、今回 Goa の作者からのオファーを受けて移譲する運びとなりました。

Goa v1 と v3 では DSL に互換性がありません。移行するためには v3 の DSL を使うよう design を書き換える必要があります。例えば RoutingHTTP に、 GET のパスパラメータ表記は : から {} に変更しなければなりません。

// v1
Routing(GET("/:user_id"))

// v3
HTTP(func() {
    GET("/{user_id}"))
})

このような書き換えを手作業で行うのは大変ですが、マイグレーションツールを使うことで大部分を自動変更できます。 Goa v1 から v3 への移行を検討する際に使ってみてもらえたら嬉しいです。

github.com

CodegenDisabler という Goa v3 プラグインを作った

codegendisablerGoa v3プラグインで、コードジェネレータの部分的な無効化を可能にします。

github.com

プラグインの有効化

プラグインを有効にするには、下記のように codegendisabler の package を import します。

import (
  _ "github.com/tchssk/goaplugins/codegendisabler/gen/http/client/types/client_body_init"
  . "goa.design/goa/v3/dsl"
)

このコードは、 gen/http/<service>/client/types.goというファイル にコード生成を行う処理の内、 client-body-init というセクションテンプレート名の箇所を無効化します。 codegendisabler 配下の package が、生成されるファイル名・生成を行うセクションテンプレート名に対応しています。

一般的な使い方

Goa が生成するコードには HTTP クライアントと HTTP サーバが含まれますが、 サーバ側 (もしくはクライアント側) の生成だけを無効化するという使い方ができます。

HTTP クライアントコード生成の無効化

import (
  _ "github.com/tchssk/goaplugins/codegendisabler/gen/http/cli/cli"
  _ "github.com/tchssk/goaplugins/codegendisabler/gen/http/client"
)

HTTP サーバコード生成の無効化

import (
  _ "github.com/tchssk/goaplugins/codegendisabler/gen/http/server"
)

React でタギロン用 Web アプリを作った

昨年購入したタギロンというボードゲームが面白くて、友人が遊びに来たときによくやっています。

たぎる、論理 TAGIRON タギロン 新装版

たぎる、論理 TAGIRON タギロン 新装版

  • 発売日: 2018/09/28
  • メディア: おもちゃ&ホビー

このゲームはターン毎にメモを取るのが大事で、今までは JELLY JELLY CAFE が提供しているタギロンメモを使わせてもらっていました。これはよく出来ているのですが、もう少しだけ機能が欲しい部分があったので、勉強も兼ねて自分で新しい Web アプリを作ってみました。

tchssk.github.io

JELLY JELLY CAFE 版と比べて下記の機能が追加してあります。テストプレイで使ってみた感じ自分的には使いやすかったので満足しています。

  • 最大の数から最小の数を引いた数の記録欄
  • 中央が 5 以上か 4 以下かの記録欄
  • 同じ色がとなり合っている箇所の記録用にダミー色 (グリーンとオレンジ) の追加

作るのに使用したのは以下のツールとライブラリ。

Material-UI のコンポーネントを弄るのに時間がかかりましたが、正味 3 日ほどで出来ました。

簡単な Web アプリ作成にこの組み合わせはお手軽で良かったです。

tchssk.github.io

2019 年振り返り

OSS

Goa v3 がリリースされたので、仕事で Goa v1 を使っているプロジェクトを v3 に移行し始めています。使ってみると気になる挙動が見つかるので本家に Pull request を送りながら進めているのですが、 v1 の最初の頃にも同じように Pull request を送っていたことを思い出して懐かしくなりました。

あと v1 から v3 への design 移行をサポートするツールを作ったりしました。

github.com

買い物

Koizumi ヘアドライヤー

以前使っていた安いドライヤーの調子が良くなかったので Amazon のセールで購入。かなり風量が多くなったので髪が乾き易くなりました。

TAGIRON

購入した当初は毎日やるくらいハマっていました。友人が遊びに来たときに 3 人でやれるのもいいです。機会があれば 4 人でやってみたい。

たぎる、論理 TAGIRON タギロン 新装版

たぎる、論理 TAGIRON タギロン 新装版

  • 発売日: 2018/09/28
  • メディア: おもちゃ&ホビー

THERMOS 真空断熱カップ

両親へのプレゼント用に購入する際ついでに自宅用も購入。保温・保冷効果が高いので普段使いに重宝しています。

サーモス 真空断熱カップ 360ml ステンレス JDH-360P S

サーモス 真空断熱カップ 360ml ステンレス JDH-360P S

  • 発売日: 2018/12/01
  • メディア: ホーム&キッチン

Nature Remo

以前から購入を検討していた商品がセールに出ていたので購入。設置後はテレビの物理リモコンを使わなくなりました。

Nature スマートリモコン Nature Remo Remo-1W2(2nd Generation)

Nature スマートリモコン Nature Remo Remo-1W2(2nd Generation)

  • 出版社/メーカー: Nature 株式会社
  • メディア: エレクトロニクス

DEATH STRANDING

Twitter で話題になっていたので勢いで購入。システマチックな要素が多くエンジニア受けするのがわかります。ゲームの難易度自体は低めなので気軽にできるのもいいです。

【PS4】DEATH STRANDING

【PS4】DEATH STRANDING

CONTROL

最近買ったゲーム。前評判通り SCP っぽさが随所に感じられます。ねこですよろしくおねがいします。

健康

健康診断で少し引っかかったものの再検査の結果いずれも経過観察で終わったので胸をなでおろしました。家族が入会したジムに付き添い料金で行けることになったので来年は運動頻度を増やして行きたいです。

総括

健康大事。

golang.org/x/tools/go/analysis を使ってコードを自動修正する

これは Go3 Advent Calendar 2019 の 3 日目の記事です。

今年 2 月にリリースされた Go 1.12 において、 公式の静的解析ツールである go vetgolang.org/x/tools/go/analysis を使う形に書き直されました

Analyzer

golang.org/x/tools/go/analysisAnalyzer (静的解析モジュール) を作るためのパッケージで、 go vet が行う様々な処理は個別の Analyzer として分離されこのパッケージ配下に格納されています。これらは外部からも参照できるようになっており、サードパーティAnalyzer から参照することもできます。

Diagnostic

Analyzer は、解析して見つかった問題を Diagnostic (診断) として報告します。 Diagnostic には Pos (コードの開始位置) と Message (メッセージ) が含まれます。

type Diagnostic struct {
    Pos      token.Pos
    Message  string
    ...
}

例えば Printf() の書式識別子と引数の型が合っていないと go vet は診断を出します。

$ cat main.go
package main

import "fmt"

func main() {
    fmt.Printf("%s", 1)
}
$ go vet .
./main.go:6:2: Printf format %s has arg 1 of wrong type int

このように問題があったコードの位置 (./main.go:6:2) とメッセージ (Printf format %s has arg 1 of wrong type int) を出すことでユーザーに修正を促す仕組みになっています。

Analyzer は checker というパッケージを通して実行可能ファイルにすることができます。 checker には singlecheckermultichecker 、そして unitchecker の 3 種類があり、 go vetunitchecker を使って作られています。

SuggestedFix

今年の 6 月、この golang.org/x/tools/go/analysisSuggestedFix (修正案) という機能が入りました。これにより、今までのように解析して見つかった問題を報告するだけでなく、その修正案を提示できるようになりました。 SuggestedFix には Message (メッセージ) と複数の TextEdit (テキスト編集) が含まれます。

type SuggestedFix struct {
    Message   string
    TextEdits []TextEdit
}

TextEditPos (コードの開始位置) と End (コードの終了位置) 、そして NewText (新しいテキスト) から構成されます。

type TextEdit struct {
    Pos     token.Pos
    End     token.Pos
    NewText []byte
}

そして SuggestedFixDiagnostic に複数紐づけることができます。つまり 1 つの診断に対して複数の修正案を提示できるということですね。

type Diagnostic struct {
    Pos      token.Pos
    Message  string
    SuggestedFixes []SuggestedFix
    ...
}

SuggestedFix に複数の TextEdit を含めているのは修正案に柔軟性を持たせるためでしょう。例えば「関数に不要な引数が含まれる」という診断に対する修正案には、関数そのものに対する編集だけでなくその呼び出し元に対する編集も含めることができます。

func add(a, b, c int) int {
    //       ^^^ ここを編集する (1 つ目の TextEdit)
    return a + b
}

func main() {
    fmt.Println(add(1, 2, 0))
    //                  ^^^ ここも編集する (2 つ目の TextEdit)
}

SuggestedFix のサンプルとして、引数が 1 つの fmt.Printf()fmt.Print() に修正する Analyzer を作ってみました。

github.com

-fix を付けて実行することで SuggestedFix を適用し、コードを自動修正することができます。

$ cat main.go
package main

import "fmt"

func main() {
    fmt.Printf("Hello, World")
}
$ fmtprintf -fix .
./main.go:6:2: fmt.Printf call which have one argument can be replaced with fmt.Print
$ cat main.go
package main

import "fmt"

func main() {
    fmt.Print("Hello, World")
}

それから今年リリースされた Goa v3 へ移行するため v1 から v3 へのアップグレードツールを作っているのですが、こちらも SuggestedFix を使う Analyzer として実装しています。

github.com

SuggestedFix の適用

SuggestedFix の適用方法は現状 2 通り用意されているようです。

1 つは前述したサンプルの例で出た -fix を使う方法で、 singlecheckermultichecker はこのフラグが渡されると SuggestedFix を自動で適用する仕組みになっています。 go vet に使われている unitchecker はこのフラグに対応しておらず、現状だと SuggestedFix の適用はできないようでした。

もう 1 つは gopls を使う方法です。 SuggestedFix は既に gopls に統合されているようですが、私が使っている vim-go はまだ該当のメソッド (textDocument/codeAction) に対応していないようで今回は試せませんでした。

gopls 自体もまだ開発途中のようですが、近い将来に Analyzer が提示する修正案を様々なエディタから適用できるようになるかもしれません。

参考

Goa v1 と v3 の DSL の比較

前回の記事ではプリミティブ型の比較を行いましたが、今回は DSL の比較を行っていきます。各 DSL の詳細については随時追記していきます。

v1 v3
API API
APIKeySecurity APIKeySecurity
(APIKey)
(APIKeyField)
AccessCodeFlow (廃止) -
Action Method
ApplicationFlow (廃止) -
ArrayOf ArrayOf
Attribute Attribute
Attributes Attributes
BasePath Path
BasicAuthSecurity BasicAuthSecurity
(Password)
(PasswordField)
(Username)
(UsernameField)
CONNECT CONNECT
CanonicalActionName CanonicalMethod
CollectionOf CollectionOf
Consumes Consumes
Contact Contact
ContentType ContentType
Credentials (goa.design/plugins/cors に移動) -
DELETE DELETE
Default Default
DefaultMedia (廃止) -
Description Description
Docs Docs
Email Email
Enum Enum
Example Example
(Value)
Expose (goa.design/plugins/cors に移動) -
Files Files
Format Format
Function (廃止) -
GET GET
HEAD HEAD
HashOf MapOf
Header Header
Headers Headers
Host Host
Server
URI
Variable
ImplicitFlow ImplicitFlow
JWTSecurity JWTSecurity
(Token)
(TokenField)
License License
Link (廃止) -
Links (廃止) -
MaxAge (goa.design/plugins/cors に移動) -
MaxLength MaxLength
Maximum Maximum
Media Result
MediaType ResultType
Member (廃止) -
Metadata Meta
Methods (goa.design/plugins/cors に移動) -
MinLength MinLength
Minimum Minimum
MultipartForm MultipartRequest
Name Name
NoExample (廃止) -
NoSecurity NoSecurity
OAuth2Security OAuth2Security
(AccessToken)
(AccessTokenField)
OPTIONS OPTIONS
OptionalPayload (廃止) -
Origin (goa.design/plugins/cors に移動) -
PATCH PATCH
POST POST
PUT PUT
Package (廃止) -
Param Param
Params Params
Parent Parent
PasswordFlow PasswordFlow
Pattern Pattern
Payload Payload
Produces Produces
Query (廃止) -
ReadOnly (廃止) -
Reference Reference
Required Required
Resource Service
Response Response
ResponseTemplate (廃止) -
Routing (廃止) -
Scheme (廃止) -
Scope Scope
Security Security
Status Code
TRACE TRACE
TermsOfService TermsOfService
Title Title
TokenURL (廃止) -
Trait (廃止) -
Type Type
TypeName TypeName
URL URL
UseTrait (廃止) -
Version Version
View View
- AuthorizationCodeFlow
- Body
- ClientCredentialsFlow
- ConvertTo
- CreateFrom
- Elem
- Error
- Extend
- Fault
- Field
- GRPC
- HTTP
- Key
- MapParams
- Message
- Metadata
- Services
- StreamingPayload
- StreamingResult
- Tag
- Temporary
- Timeout
- Trailers

BasePath

  • Path に変更されました。
  • HTTP 内に記述します。
// v1
BasePath("/users")
// v3
HTTP(func() {
    Path("/users")
})

Consumes, Produces

  • HTTP 内に記述します。
  • これらの DSL 内で使用できた FunctionPackage は廃止されました。
// v1
Consumes("application/xml")
Consumes("application/json")
Produces("application/xml")
Produces("application/json")
// v3
HTTP(func() {
    Consumes("application/xml")
    Consumes("application/json")
    Produces("application/xml")
    Produces("application/json")
})

Format

  • v1 では引数の型が string でしたが、 v3 では expr.ValidationFormat になりました。
  • 使用可能な expr.ValidationFormatgoa.design/goa/exprconst として定義されています。

GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH

  • v1 ではパスパラメータをコロン (:) で記述しましたが、 v3 では中括弧 ({, }) を使います。
// v1
GET("/users/:id")
// v3
GET("/users/{id}")

HashOf

  • MapOf に変更されました。
  • key のためのオプション DSLKey で記述します。
  • value のためのオプション DSLElem で記述します。
// v1
HashOf(String, Integer,
    func() {
        MinLength(1)
        MaxLength(16)
    },
    func() {
        Minimum(1)
        Maximum(5)
    },
)

// v3
MapOf(String, Int, func() {
    Key(func() {
        MinLength(1)
        MaxLength(16)
    })
    Elem(func () {
        Minimum(1)
        Maximum(5)
    })
})

Headers

リクエストヘッダ

  • Payload 内に Attribute を、 HTTP 内に Headers を記述します。
// v1
Action("show", func() {
    Headers(func() {
        Header("Authorization", String)
    })
})
// v3
Method("show", func() {
    Payload(func() {
        Attribute("Authorization", String)
    })
    HTTP(func() {
        Headers(func() {
            Header("Authorization", String)
        })
    })
})

Host, Scheme

  • v1 では HostScheme (と BasePath) で記述しましたが、 v3 では ServerHostURI を使います。
// v1
Host("localhost:8080")
Scheme("http")
BasePath("/cellar")
// v3
Server("app", func() {
    Host("development", func() {
        URI("http://localhost:8080/cellar")
    })
})

NoExample

  • 廃止されました。
  • Meta で同等の定義ができます。
Meta("swagger:example", "false")

Params

パスパラメータ

  • Payload 内に Attribute を、 HTTP 内に Params を記述します。
  • Params は省略できます。省略した場合は暗黙的に定義されます。
// v1
Action("show", func() {
    Params(func() {
        Param("id", Integer)
    })
})
// v3
Method("show", func() {
    Payload(func() {
        Attribute("id", Int)
    })
    HTTP(func() {
        Params(func() {       // 省略可
            Param("id", Int)  //
        })                    //
    })
})

クエリパラメータ

  • Payload 内に Attribute を、 HTTP 内に Params を記述します。
  • Params は省略できません。
//v1
Action("list", func() {
    Params(func() {
        Param("name", String)
    })
})
// v3
Method("list", func() {
    Payload(func() {
        Attribute("name", String)
    })
    HTTP(func() {
        Params(func() {            // 省略不可
            Param("name", String)  //
        })                         //
    })
})

Routing

  • 廃止されました。
  • HTTP 内に HTTP リクエストメソッドと同名の DSL を記述します。
// v1
Action("show", func() {
    Routing(
        GET("/:id"),
    )
})
// v3
Method("show", func() {
    HTTP(func() {
        GET("/{id}")
    })
})