ポインタにするか値にするか
正直、現時点ではポインタにするのは参照渡しにしたいときという乱暴な理解。ちゃんと理解すべきだけど、こうすべき!と言う確固たるものを持っている人は少ない気がする。
構造体
- 構造体のフィールドに、スライスや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も理論上同じ数字は出現しないってだけで、厳密にはありえる。なので、厳密にやるなら発行するときにチェックは必要。