20200519のGoに関する記事は6件です。

Goの変数名はなぜ短いのか

はじめに

Goの変数名は短い方が好まれます。例えば、memberだったらmuserだったらuにするといった具合です。僕は、「エディタが保管してくれるし、変数名は長い方が具体的でわかりやすいからいいのでは?」とずっと疑問に思っていました。
公式も変数名を短くしろとは書いているけど、その理由まではわからず放置していましたが、「Goに入ればGoに従え」という言葉もありますので、今回ついに短い変数名が推奨される理由を調査してみました。

公式:CodeReviewCommentsというGoのコードをレビューするポイントをまとめたWikiのことです。

下記は、Wikiに変数名について記載されている部分の翻訳と原文です。


Goの変数名は長いものより短いものにするべきです。特にスコープが限定されたローカル変数に当てはまります。lineCountよりもcが、isliceIndex よりも優先されます。

基本的なルール: 名前が使用される宣言から離れているほど、その名前はより説明的でなければなりません。メソッドのレシーバの場合は、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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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?")
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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は指定した条件に沿ってグループ化することができます。条件を指定するために、KeyTypeを設定します。
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型にしてから足していきます。
これでサービス毎の利用料金を取得するだけで、合計料金も合わせて通知することができそうです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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-axios

main.go側

作成したプロジェクトの中にmain.goファイルを作る

今回はechoを使っていくのでフレームワークを入れる

$go get -u github.com/labstack/echo

大体の流れ↓

CORSを忘れずに。

main.go
package 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を使いたいときはURLSearchParamsAPIを使う。

これで完成!!
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.go
func 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.go

localhost:8000にアクセスすれば別々に動かしていた時と同じように使える。

おわり

GoとVueを使って簡単にAPIのやり取りができた!
Goって結構たのしいな。
AWSとかで動かしてみたい...

今回のコードGitHubからどうぞ

参考

公式ドキュメント

@y_ussie 様 : Go言語のWebフレームワーク「Echo」を使ってみる ②(リクエストパラメータの扱い)

@567000 様 : Go(golang) echoとVue-cliをつなげる

ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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())
}

https://play.golang.org/p/pNGZISzVhCC

https://github.com/go-playground/validator/blob/c68441b7f4748b48ad9a0c9a79d346019730e207/baked_in.go#L1337

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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のフォローいただけると励みになります!
他にも方法がありましたら、コメントお待ちしております。
宜しくお願いします〜

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む