- 投稿日:2020-05-19T20:48:59+09:00
Goの変数名はなぜ短いのか
はじめに
Goの変数名は短い方が好まれます。例えば、
member
だったらm
、user
だったらu
にするといった具合です。僕は、「エディタが保管してくれるし、変数名は長い方が具体的でわかりやすいからいいのでは?」とずっと疑問に思っていました。
公式も変数名を短くしろとは書いているけど、その理由まではわからず放置していましたが、「Goに入ればGoに従え」という言葉もありますので、今回ついに短い変数名が推奨される理由を調査してみました。公式:CodeReviewCommentsというGoのコードをレビューするポイントをまとめたWikiのことです。
下記は、Wikiに変数名について記載されている部分の翻訳と原文です。
Goの変数名は長いものより短いものにするべきです。特にスコープが限定されたローカル変数に当てはまります。
lineCount
よりもc
が、i
はsliceIndex
よりも優先されます。基本的なルール: 名前が使用される宣言から離れているほど、その名前はより説明的でなければなりません。メソッドのレシーバの場合は、1文字か2文字で十分です。ループインデックスやリーダのような一般的な変数は、1文字
i
,r
で構いません。より珍しいものやグローバル変数は、より説明的な名前を必要とします。Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex.
The basic rule: the further from its declaration that a name is used, the more descriptive the name must be. For a method receiver, one or two letters is sufficient. Common variables such as loop indices and readers can be a single letter (i, r). More unusual things and global variables need more descriptive names.
https://github.com/golang/go/wiki/CodeReviewComments#variable-names調査結果
調査結果としては、「変数名が短いとコードの内容(振る舞い)を理解しやすいから」 ということが理由みたいです。
ただし注意点としては、なんでもかんでも短ければいいというわけでは無いことです。CodeReviewCommentsにもある通り、「変数名が宣言と使用箇所が離れている場合」「グローバル変数」は説明的でなければなりません。以下の悪い例と良い例を見てもらえれば、短い変数名の良さを感じてもらえると思います。
悪い例
func removeElement(nums []int, val int) int { numbers := make([]int, 0, len(nums)) for index := 0; index < len(nums); index++ { if nums[index] != val { numbers = append(nmubers, nums[index]) } } return len(n) }良い例
確かに長い変数名と比べると短いほうが読みやすい。
func removeElement(nums []int, v int) int { n := make([]int, 0, len(nums)) for i := 0; i < len(nums); i++ { if nums[i] != v { n = append(n, nums[i]) } } return len(n) }根拠
GoogleでGoを開発しているAndrew Gerrandさんの以下の記述を見つけました。
彼はRob PikeともGopherfestで対談するような凄腕エンジニアなので、彼が言うなら間違いないでしょう。(投げやり)(訳)短くすべきです。長い名前はコードの振る舞いを曖昧にします。
Local variables
Keep them short; long names obscure what the code does.
https://talks.golang.org/2014/names.slide#6最後に
今回は、「Goの変数が短い理由」について調べてみました。個人的には、振る舞いがわかりやすくなることに加えて、変数名が何だか辿れなくなるようなスコープのでかい関数を避けることにも繋がるというのも大きな理由なのかなと思いました。
twitter: https://twitter.com/kskumgk63
blog: https://plum-u.me/
- 投稿日:2020-05-19T19:28:15+09:00
【Golang】log
【Golang】log
Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。
package main //log /* https://golang.org/pkg/log/ https://golang.org/pkg/io/ logは、シンプルなログ作成の為の機能がまとめられたパッケージ。 標準エラー出力に任意のログメッセージを出力できる。 他の言語のようなinfoなどはサポートされていない。 複雑なログをやる場合は、サードパーティを使うことも考える。通常はこれで十分 */ import ( "fmt" "io" "log" "os" ) //ログに書き込むd //ファイル読み書きについては別途調べる func LoggingSettings(logFile string) { //_=error //os.O_RDWR READ WRITE 読み書き両方する時 //os.O_CREATE 存在しなかった場合新規ファイルを作成する場合 //os.O_APPEND ファイルに追記したいとき //0666 //引数: ファイルのパス, フラグ, パーミッション(わからなければ0666でおっけーです) //上記モード指定。読み込む、作成、権限(0666=読み書き)を設定。 logfile, _ := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) //stdout 画面上に出る出力 をlogfileに書き込む multiLogFile := io.MultiWriter(os.Stdout, logfile) //フォーマット指定 //日付、時間、短いエラーの名前 log.SetFlags(log.Ldate | log.Ltime | log.Llongfile) //ログファイルの出力先を変更 log.SetOutput(multiLogFile) } func main() { //2 LoggingSettings("test.log") //条件分岐。エラーで終了させる。 _, err := os.Open("fdafdsafa") if err != nil { //ログ出力 //エラーで終了する log.Fatalln("Exit", err) } //1 //日付と時間が表示される log.Println("logging!") //フォーマット //日付、時間 + Type, Valを出力できる log.Printf("%T %v", "test", "test") //Type,Valを表示。エラーで終了 log.Fatalf("%T %v", "test", "test") //エラーで終了させる log.Fatalln("error!!") //表示されない fmt.Println("ok?") }
- 投稿日:2020-05-19T17:11:37+09:00
【Go】AWSのサービス毎の利用料金を取得する
はじめに
これまで、「AWSの利用料金をChatworkに通知してくれるバッチをGoで作った」や「Goでslackに通知するバッチを作った【新Webhook】」ではAWSの合計利用料金を通知してきましたが、サービス毎の明細が欲しかったので、サービス毎の利用料金もいっしょに通知してみることにしました!
通知したいこと
- 昨日の合計利用料金
- 昨日のサービス毎の利用料金
利用料金の取得
Lambda関数の作成
定期的にSlackやChatworkに通知することを想定して、Lambda関数を作成していきます。
IAMポリシー
API「GetCostAndUsage」を実行できる権限を付与します。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "ce:GetCostAndUsage", "Resource": "*" } ] }このポリシーをアタッチしたIAMロールを作成し、Lambdaに紐づけてください。
ソースコード
本コードはサービス毎の利用料金の取得方法にフォーカスしています。合計料金の取得方法は、こちらを参考にしてください。
package main import ( "fmt" "log" "time" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/costexplorer" "github.com/aws/aws-sdk-go/service/costexplorer/costexploreriface" ) // コストの取得 func GetCost(svc costexploreriface.CostExplorerAPI) (result *costexplorer.GetCostAndUsageOutput) { // Granularity granularity := aws.String("DAILY") // Metrics metric := "UnblendedCost" metrics := []*string{&metric} // TimePeriod // 現在時刻の取得 jst, _ := time.LoadLocation("Asia/Tokyo") now := time.Now().UTC().In(jst) dayBefore := now.AddDate(0, 0, -1) nowDate := now.Format("2006-01-02") dateBefore := dayBefore.Format("2006-01-02") // 昨日から今日まで timePeriod := costexplorer.DateInterval{ Start: aws.String(dateBefore), End: aws.String(nowDate), } // GroupBy group := costexplorer.GroupDefinition{ Key: aws.String("SERVICE"), Type: aws.String("DIMENSION"), } groups := []*costexplorer.GroupDefinition{&group} // Inputの作成 input := costexplorer.GetCostAndUsageInput{} input.Granularity = granularity input.Metrics = metrics input.TimePeriod = &timePeriod input.GroupBy = groups // 処理実行 result, err := svc.GetCostAndUsage(&input) if err != nil { log.Println(err.Error()) } return result } /************************** 処理実行 **************************/ func run() error { log.Println("コスト取得バッチ 開始") log.Println("セッション作成") svc := costexplorer.New(session.Must(session.NewSession())) log.Println("コスト取得 実行") cost := GetCost(svc) log.Println("コスト取得 完了") fmt.Println(cost) log.Println("コスト取得バッチ 完了") return nil } /************************** メイン **************************/ func main() { lambda.Start(run) }実行結果
Lambda関数の実行ログから一部抜粋しています。
2020/05/06 03:59:54 コスト取得バッチ 開始 2020/05/06 03:59:54 セッション作成 2020/05/06 03:59:54 コスト取得 実行 2020/05/06 03:59:55 コスト取得 完了 { GroupDefinitions: [{ Key: "SERVICE", Type: "DIMENSION" }], ResultsByTime: [{ Estimated: true, Groups: [ { Keys: ["AWS Cost Explorer"], Metrics: { UnblendedCost: { Amount: "0.05", Unit: "USD" } } }, { Keys: ["AWS Lambda"], Metrics: { UnblendedCost: { Amount: "0.0000019028", Unit: "USD" } } }, ], TimePeriod: { End: "2020-05-06", Start: "2020-05-05" }, Total: { } }] } 2020/05/06 03:59:55 コスト取得バッチ 完了解説
合計利用料金を取得する際に利用したAPI「GetCostAndUsage」のInput要素は以下の3つでした。
- Granularity
- Metrics
- TimePeriod
今回、サービス毎の料金を取得するため、以下の要素を追加します。
- GroupBy
GroupByは指定した条件に沿ってグループ化することができます。条件を指定するために、KeyとTypeを設定します。
Keyは何を基準に判断するのか、Typeはどう分けるのかということを意味します。今回はサービス毎の利用料金を知りたいので下記のようにしました。Key: aws.String("SERVICE"), Type: aws.String("DIMENSION"),よって、SERVICE(サービス)のDIMENTION(種類)でグループ化するという意味になります。
その他サービス毎以外にも、タグ毎など様々な条件のグループでコストを取得することができます。
参考:AWS SDK for Go API Reference注意点
GroupByの要素を追加することで、Totalの値が取得できなくなります。(実行結果参照)
なので、今回のように合計料金とサービス毎の両方のコストを取得したい場合は、2度リクエスト投げる必要があります。ちなみに、AWSのサポートに問い合わせてみましたが、現状は1度のリクエストで両方のコストが取得できる方法は確認できておりませんとの返事が返ってきました。(2020/03/19時点)
おわりに
合計料金とサービス毎の料金を1度に取得できないので、2回APIをたたく必要がある、つまり、倍のコストがかかってしまうというところがありますので…利用料金とその明細が知りたい!!ってときには、サービス毎の利用料金を自分で合計してしまいましょう。
おまけ
ちなみに、自分で合計してみました。
func SumCost(cost *costexplorer.GetCostAndUsageOutput) (total string){ sum := 0.0 for _, data := range cost.ResultsByTime[0].Groups { amount, _ := strconv.ParseFloat(*data.Metrics["UnblendedCost"].Amount, 64) sum = sum + amount } total = fmt.Sprintf("%.10f", sum) return total }取得したコストは
String
型なので、float64
型にしてから足していきます。
これでサービス毎の利用料金を取得するだけで、合計料金も合わせて通知することができそうです!
- 投稿日:2020-05-19T09:37:15+09:00
Go + Vue.js でGETとPOSTをやってみる
はじめに
この記事はGo lang 駆け出しによる駆け出し向けの記事になっていますので基本的なことしか書いておりません。
今回はGo言語とVueを使ってPOSTとGETでリクエストを投げてパラメータの受け渡しをやってみる。
なおGoのフレームワークとしてEchoを使っていく。今回のコードはgithubに(最下部掲載)
Vue.js前準備
まずはvueのプロジェクト作成
次にaxiosを入れる。リクエストはaxiosを使って送る。$vue create ~~ $cd ~~ $npm install --save axios vue-axiosmain.go側
作成したプロジェクトの中にmain.goファイルを作る
今回はechoを使っていくのでフレームワークを入れる
$go get -u github.com/labstack/echo大体の流れ↓
CORSを忘れずに。
main.gopackage main import ( "net/http" "github.com/labstack/echo" "github.com/labstack/echo/middleware" ) func main(){ e := echo.New() //CORSの設定(vueのプロジェクトをGOで立てたlocalサーバーで起動する時は不要) e.Use(middleware.CORS()) // リクエストに対するHandler e.GET("/getTitle", getTitle) e.GET("/getName/:name", getName) e.POST("/postName", postName) e.POST("/postCompany", postCompany) // local サーバー e.Logger.Fatal(e.Start(":8000")) }各Handler定義
main.go// GETリクエスト func getTitle(c echo.Context) error { return c.String(http.StatusOK, "New Game") } // パラメータ付きのGETリクエスト func getName(c echo.Context) error { name := c.Param("name") return c.String(http.StatusOK, name) } // application/x-www-form-urlencoded データのPOSTリクエスト func postName(c echo.Context) error { name := c.FormValue("name") return c.String(http.StatusOK, name) } //JSON受け取り用の構造体 type JsonParam struct { Company string `json:"company"` Works string `json:"works"` } // JSONデータのPOSTリクエスト func postCompany(c echo.Context) error { param := new(JsonParam) //バインドしてJSON取得 if err := c.Bind(param); err != nil { return err } //JSONを返す return c.JSON(http.StatusOK, param) }GETリクエストではurlに
/~~/:パラメータ名
とし、Context.Param(パラメータ名)
とすることでURLに付加されたのパラメータを取得できる。POSTでデータを取得する際にデータがform-urlencodedなのかJSONなのかで取得の仕方が変わってくる。
form-urlencodedの場合はContext.FormValue(キー)
で取れる。
JSONの場合は先に**JSONの構造体を用意しておいてContext.Bind(構造体)
で取れる。これでバックエンド側の準備は完了
次にフロントのvue側を作っていく。$go run main.go今回はポート8000でローカルサーバーを起動させておく
Vue.js側
適当にビューを作作成
App.vue<template> <div id="app"> <button @click="sendRequest">リクエスト送信</button> <h1>取得結果</h1> <p>GET(パラメータ無):<br/><strong>{{title}}</strong></p> <p>GET(パラメータ有):<br/><strong>{{name1}}</strong></p> <p>POST(form-urlencoded):<br/><strong>{{name2}}</strong></p> <p>POST(JSONデータ):<br/><strong>{{company}}</strong></p> </div> </template> <script> //~~省略~~ data:()=>{ return{ title:"", name1:"", name2:"", company:"" } } </script>次にリクエストを送る関数を作成していく
App.vue<script> import axios from "axios" //~~省略~~ methods: { sendRequest: async function(){ //パラメータ無しでGETリクエスト const getRequestNoParam = await axios.get("http://localhost:8000/getTitle") //パラメータ付きでGETリクエスト const getRequest = await axios.get("http://localhost:8000/getName/ひふみん") //application/x-www-form-urlencodedでデータを送信 const params = new URLSearchParams(); params.append("name","青葉"); const postRequest = await axios.post("http://localhost:8000/postName",params) //JSONデータを送信(axiosはデフォルトでJSONを送信) const jsonPostRequest = await axios.post("http://localhost:8000/postCompany", { company: "Eagle Jump", works: "PECO" }); //取得結果をviewに反映 this.title = getRequestNoParam.data this.name1 = getRequest.data this.name2 = postRequest.data this.company = jsonPostRequest.data } } </script>axiosはPOSTリクエストの時デフォルトでJSONを送信するようになっているのでform-urlencodedを使いたいときは
URLSearchParams
APIを使う。これで完成!!
npm run serve
使ってvueのローカルサーバー(localhost:8080)で起動させればリクエストの送受信ができるはず。VueプロジェクトをGoのローカルサーバーで動かす
上の場合はvueプロジェクトを
npm run serve
使って、vueのローカルサーバー(localhost:8080)で動かして、Goはlocalhost:8000で動かしていた。これだとクロスドメインでCORSの設定が必要になる。そこで、最後におまけ的な感じでvueプロジェクトをGoで起動したローカルサーバーで動かしてみる。
まずはvueプロジェクトをビルドする。
$npm run buildうまく実行できるとdistフォルダができてるはず。
あとは、これをGoで動かすだけ。main.goを以下のように修正
main.gofunc main(){ e := echo.New() //CORSの設定(vueのプロジェクトをGOで立てたlocalサーバーで起動する時は不要) // e.Use(middleware.CORS()) // npm run buildでビルドしたものをgoで起動 corsも不要になる // /でアクセスしたときのルーティング設定 e.Static("/", "dist/") // リクエストに対するHandler e.GET("/getTitle", getTitle) e.GET("/getName/:name", getName) e.POST("/postName", postName) e.POST("/postCompany", postCompany) // local サーバー e.Logger.Fatal(e.Start(":8000")) }当然だけどVue.js側のリクエストを送るときも以下のように省略できる。
App.vue//パラメータ無しでGETリクエスト - const getRequestNoParam = await axios.get("http://localhost:8000/getTitle") + const getRequestNoParam = await axios.get("/getTitle")あとはサーバーを起動して
$go run main.golocalhost:8000にアクセスすれば別々に動かしていた時と同じように使える。
おわり
GoとVueを使って簡単にAPIのやり取りができた!
Goって結構たのしいな。
AWSとかで動かしてみたい...今回のコードGitHubからどうぞ
参考
@y_ussie 様 : Go言語のWebフレームワーク「Echo」を使ってみる ②(リクエストパラメータの扱い)
@567000 様 : Go(golang) echoとVue-cliをつなげる
ありがとうございました。
- 投稿日:2020-05-19T09:33:14+09:00
cross-fields validation
go-playground/validator.v9を使って、CustomバリデーションはMininのStructの場合、メモしました。
package main import ( "fmt" "gopkg.in/go-playground/validator.v9" ) type A struct { String string `validate:"required"` } type B struct { Int int64 `validate:"custom=A.String"` } type C struct { A B } func main() { v := validator.New() v.RegisterValidation("custom", customFunc) c := &C{} // Key: 'C.B.Int' Error:Field validation for 'Int' failed on the 'custom' tag c.String = "hoge" c.Int = 3 err := v.Struct(c) fmt.Println(err) // nil c.String = "foo" c.Int = 3 err = v.Struct(c) fmt.Println(err) } func customFunc(fl validator.FieldLevel) bool { param := fl.Param() field, _, _, found := fl.GetStructFieldOKAdvanced2(fl.Top(), param) return found && int(fl.Field().Int()) == len(field.String()) }
- 投稿日:2020-05-19T00:36:57+09:00
ID番号がkeyだった場合のStructの書き方
ちょっとはまったので、メモ。
{ "success": 1, "return": { "182": { "currency_pair": "btc_jpy", "action": "bid", "amount": 0.03, "price": 56000, "fee": 0, "your_action": "ask", "bonus": 1.6, "timestamp": 1402018713, "comment" : "demo" } } }上記のようなJSONデータをStructで取得したい。
しかしながら"182"の部分がID番号で固定では無いため、やり方がわからなかった。type GetTradeAPIResponse struct { ApiResponse Return map[string]GetTradeResponse `json:"return"` } type ApiResponse struct { Success int `json:"success"` Error string `json:"error"` } type GetTradeResponse struct { CurrencyPair string `json:"currency_pair"` Action string `json:"action"` Amount float64 `json:"amount"` Price float64 `json:"price"` Fee float64 `json:"fee"` YourAction string `json:"your_action"` Bonus float64 `json:"bonus"` Timestamp int64 `json:"timestamp"` Comment string `json:"comment"` }上記のように定義することにより、取得ができました。
参考
Mapping JSON returned by REST API containing dynamic keys to a struct in Golang
以上です。
いいねやQiitaやTwitterのフォローいただけると励みになります!
他にも方法がありましたら、コメントお待ちしております。
宜しくお願いします〜