0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Goを始めたときの自分のためのメモ

Last updated at Posted at 2023-01-30

ポインタにするか値にするか

正直、現時点ではポインタにするのは参照渡しにしたいときという乱暴な理解。ちゃんと理解すべきだけど、こうすべき!と言う確固たるものを持っている人は少ない気がする。

構造体

  • 構造体のフィールドに、スライスやmap、ポインタなどの参照型を持つ場合はポインタ
  • 変更不可な構造体として、運用する場合は値でも良い(コンストラクタを定義し必ずコンストラクタで生成するようにする、不変なオブジェクトなど)
  • 迷ったらポインタ

構造体が値の場合、コピーしたときに参照型ではないフィールドの変更は、コピー元のインスタンスには影響が無いですが、参照型のフィールドの変更はコピー元のインスタンスも変更されます。このようにフィールドによって挙動が異なる状態になってしまうのはわかりにくい。
確かにポインタを意識しない言語で、arraylistやhash的なものは参照型で、メソッドでreturnする必要ないけど、意識するのがめんどいので何でもかんでもreturnするとかよくある。rubyなんかは最後の変数がreturnされるとかあるので、さらに複雑。

コンストラクタ

引数はポインタで返すのが一般的

レシーバ

ポインタを使うのが一般的

数字型で存在しないこと(NULL)を表現したい場合

例えば、RepositoryDTO/Entityのうち、プロパティに対応するDBのフィールドがNULL許容の場合など。
デフォルトで0が入ってしまうので、NULLなのかホントに0なのか判別がつかない。

引数

基本的に引数は値渡しとする。
ただし、シビアな性能要件が発生した場合には別途検討する。

array, sliceの要素は値で持つ

ポインタで持つとメモリ効率が悪いため、値で持つ。

go build と go run

go build

go build [コンパイル後のファイル名] [コンパイルしたいファイル名]
コンパイルし、コンパイル後のバイナリファイルを実行するには
./[コンパイル後のファイル名]
オプション無しでgo buildを叩くと、自動的にmainパッケージを読み込み、main関数があるファイルを特定し、そのファイル名を使ったバイナリファイルが生成される。go buildをすると、基本的にはカレントディレクトリ以下をスコープにして読み込んでコンパイルするファイルを決めるので、カレントディレクトリ以下にあるファイルが読み込まれない、といったことはない。

go run

自動的にコンパイル・実行してくれる便利なコマンド。go run hoge.goとするだけでプログラムを実行することができる。ただ、ここで注意しておきたいのがgo buildのようにカレントディレクトリ以下の全ファイルを読み込むわけではないということです。go run hoge.goと打つと、基本的にはimportされたパッケージだけを読み 同階層にmainパッケージに所属する別の関数などがあるとそれは無視されてしまう。

パッケージパス

  • 環境変数GOPATHにセットしたパスの下にすべてのプロジェクトを配置するのが基本
  • ローカルパッケージをインポートするときも($GOPATH/src以下からの)絶対パスを使う

これはなかなかのルール。好きなところにファイル置けないってことか・・・。
go mod モジュール名 でgo.modファイルが出来上がり、そこにルートパスのようなものが書かれる。これでOK。

モジュールインストール

go get github.com/jinzhu/gorm

go.mod と go.sum

  • go.mod モジュールの依存関係を管理するファイル
  • go.sum 依存モジュールのチェックサムの管理をしてるファイル(githubには追加しないこと)

importのあとのアンダーバー

import _ "hogehoge" の _は、「packageの中身を利用せずに、init()関数が必要な場合に利用する」

  • import _ "hoge" はhoge packageのinit()を実行する
  • 利用するケースは、それをwrapしたpackageがある時(package image, package sqlなど)

interface

とりあえず、Goでは、interfaceの中にある関数名と同一の関数が全て実装されている構造体に自動的に実装される。
言い換えると、インターフェースの中にある同じ名前のメソッドを全て実装するだけで自動的にインターフェースが実装されているということになります。

json

Goでは構造体のフィールド名をアッパーキャメルで書くのがお作法です。しかし json は一般的にスネークケースで扱うのがお作法です。
そのズレを解消するために構造体のフィールドに json タグをうちます。

type Task struct {
	ID      int       `json:"id"`
	Title   string    `json:"title"`
	Content string    `json:"content"`
	DueDate time.Time `json:"due_date"`
}

こうしておくことによって、構造体のフィールド名と json のキーをマッピングすることができます。
つまりこの構造体を json にエンコードした時のキー名はタグで指定した値と等しくなります

基本構文

バッククォート

エスケープしなくても文字列を扱えるようになる。言い換えると、文字列を扱うときはダブルクォートかバッククォートを使う。シングルクォートは使えない。

echo フレームワーク

EchoはRESTful API向けのフレームワークとなっており、小規模~中規模のWebアプリ開発に適しています。

Routing

グループ化もできる

// Routes
e.POST("/users", createUser)
e.GET("/users", findUser)
e.PUT("/users", updateUser)
e.DELETE("/users", deleteUser)

g := e.Group("/admin")
g.GET("/users/new", createUser)
g.GET("/users/:id", findUser)

Path Parameters

ルーティング設定に:name の形式でパスを設定すると、パスパラメータとして登録することができます。パスパラメータを受け取る際は、echo.Context.Param("name")を利用します。

Query Parameters

クエリパラメータを受け取るには、echo.Context.QueryParam("name")

Form Parameters(Post)

パラメータを受け取るには、echo.Context.FormValue("name")

Handling Request

json、xml、form、queryなどのペイロードを、リクエストヘッダーのContent-Typeに基づいてGoの構造体にバインドすることができます。

type User struct {
	Name  string `json:"name" xml:"name" form:"name" query:"name"`
	Email string `json:"email" xml:"email" form:"email" query:"email"`
}

e.POST("/users", func(c echo.Context) error {
	u := new(User)
	if err := c.Bind(u); err != nil {
		return err
	}
	return c.JSON(http.StatusCreated, u)
	// or
	// return c.XML(http.StatusCreated, u)
})

gorm

AutoMigrate

構造体(例:type User struct{})を定義して db.AutoMigrate(&User{}) とやるとそれだけで接続しているデータベースにテーブルが作成されるのでとても楽ではあるのだけど、何か定義の変更があったら構造体を修正すればそれだけでOKと思うと「おやっ?」となるので注意。

gorm.Model

構造体の中に、gorm.Modelを書くことで、自動的に下記の4つのカラムが作成される。

  • id
  • created_at
  • updated_at
  • deleted_at

そして、idにはPrimaryKeyとauto_incrementが設定される。
gorm.Modelでdeleted_atを作成している場合、Deleteすると、自動的に論理削除になるという仕様もある。

個人的には、これも好みではない。便利なのはわかるが、テーブルにあるカラムが構造体に定義されていないように見えるのが気持ち悪い。
共通認識としてPJメンバー全員が知っていればいいのだけど、メンバーの増減は必ずあるもので、知らない人にもわかりやすいようにするというのが、開発パータンだったり開発手法だったりするのでちょっとしたコーディング量を減らすよりもわかりやすくするのが好き。
少数派だと思うけど、変数名も短くするよりも長くても意味ある単語になってるほうが好き。

scan

GORMのAPIを利用

row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)

SQL文を利用

row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
row.Scan(&name, &age, &email)

ScanRows

ScanRows を使用して、取得した行データを構造体にScanする

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()

var user User
for rows.Next() {
  // ScanRows scan a row into user
  db.ScanRows(rows, &user)

  // do something
}

dsn

詳細は https://github.com/go-sql-driver/mysql#dsn-data-source-name を参照

dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"

debug

インストール

1./ delve のインストール

go install github.com/go-delve/delve/cmd/dlv@latest

2./ dlv version で確認

3./ launch.json を作成

4./ breakポイントを張って、F5でデバッグ実行。curl or ブラウザでURLを叩くと止まる。

余談

IDとauto increment

Goだけに限定されたことじゃないけど、TableのIDカラムでサンプルではよくauto incrementになっている。それに倣って作ってると、たいていの場合、会員数がバレてしまう、記事数がバレてしまう、それが理由でセキュリティホールになりかねない。なんてことを言われることがある。

もちろん、これは正論。URLやパラメータで公開される場合、TableのIDカラムはUUIDにします。

でも、社内で不具合対応とかしてる時に、記事番号がUUIDになってるとかなりめんどくさい。チャットで送ってとなる。

だったら、公開されるUUIDと社内で会話するようにauto incrementのIDを持たせとけばいいや。と考えたんだけど、ここでまた指摘。

それって、UUIDにインデックス張ることになるし、insert遅くなるからやめてと・・・。
ごもっともです。

一人でプログラムつくってると気付かないことも複数の目があれば気づける。レビューって大事。

goでは、これ使います。

※ UUIDもGUIDも理論上同じ数字は出現しないってだけで、厳密にはありえる。なので、厳密にやるなら発行するときにチェックは必要。

0
0
0

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
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?