20200316のGoに関する記事は22件です。

Go の json.Marshal の思いがけない仕様

結論

json.Marshal は、uint8 型のスライスを base64 文字列にする。
int8 型だったり スライスじゃなくて配列だったりすると配列になるけど。

ソースコードと実行結果

golang
package main

import (
    "encoding/json"
    "fmt"
)

type S struct {
    U8Array  [3]uint8
    U8Slice  []uint8
    S8Array  [3]int8
    S8Slice  []int8
    U8SliceP *[]uint8
    PU8Slice []*uint8
    IFSlice []interface{}
}

func main() {
    a:=uint8(1)
    b:=uint8(2)
    c:=uint8(3)
    o := S{
        U8Array:  [3]uint8{1, 2, 3},
        U8Slice:  []uint8{1, 2, 3},
        S8Array:  [3]int8{1, 2, 3},
        S8Slice:  []int8{1, 2, 3},
        U8SliceP: &[]uint8{1, 2, 3},
        PU8Slice: []*uint8{&a, &b, &c},
        IFSlice: []interface{}{uint8(1),uint8(2),uint8(3)},
    }
    bytes, _ := json.Marshal(o)
    fmt.Println(string(bytes))
}

実行するとこうなる:

json
{
  "U8Array":[1,2,3],
  "U8Slice":"AQID",
  "S8Array":[1,2,3],
  "S8Slice":[1,2,3],
  "U8SliceP":"AQID",
  "PU8Slice":[1,2,3],
  "IFSlice":[1,2,3]
}

"AQID" について

base64 でデコードすると、下記の通り [1, 2, 3] になる。

ruby
require "base64"
Base64.decode64("AQID").bytes
# => [1, 2, 3]

まとめ

json.Marshal でシリアライズすると下表のようになる。

go上の型 json
uint8 の配列 数値の配列
uint8 のスライス base64文字列
int8 の配列 数値の配列
int8 のスライス 数値の配列
uint8 のスライスへのポインタ base64文字列
uint8 へのポインタのスライス 数値の配列
interface のスライスに uint8 だけを詰めたもの 数値の配列

思いがけない仕様で大変驚いた。
驚きは2点

  • uint8 のスライスが base64文字列になる点。
  • uint8スライス が base64文字列になるのに、uint8配列 は数値の配列になる点。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パワーワードが作れる?ランダム単語合成アプリを作ってみた

はじめに

クリックするだけ!ランダムに2つの単語を「反応」させて、パワーワードを作りましょう!

rand.gif

モードは

  • 形容詞+名詞
  • 副詞+動詞
  • 名詞+助詞+動詞

の3種類です!

phrases.gif

ソースコード

GitHub - Syuparn/LiteralReaction: ランダムに単語を「反応」させて、パワーワードを作ろう

(DockerやVue.jsをはじめて使ったので、汚いところもあると思います…ツッコミ、修正歓迎です)

Webアプリの形で作りましたが、web上では公開していません。
(単語が本当にランダムなので、暴言や不謹慎な表現も生成されるかもしれないからです…)

試したい方は上記リンクからダウンロードしてください。docker-composeを使用しているので、

$ docker-compose -f docker-compose.prod.yml up -d

でコンテナを起動すればlocalhost:8080からアクセスできます。

webアプリケーションの構成

WebサーバとAPIサーバの2段構成になっています。

network.png

WebサーバはUI(html)、APIサーバは単語情報(JSON)をやりとりします。

コンテナもWebサーバとAPIサーバの2つに分けています。
(UIと単語生成を疎結合にして修正しやすくするためです)

containers.png

docker-compose.prod.yml
version: '3'
services:
  apiserver:
    build:
      context: ./apiserver/
      dockerfile: Dockerfile
    ports:
      - "127.0.0.1:5050:5050" # host:container
    expose:
      - "5050" # port for other containers (not host)
  vue-frontend:
    build:
      dockerfile: Dockerfile
      context: ./vue-frontend
    ports:
      - "127.0.0.1:8080:80" # host:container

ただし、ユーザーから見ても別々のURLになってしまうと不格好なので、Webサーバの/api/ではじまるURLにリクエストされた場合はAPIサーバに転送するようにしています。

vue-frontend/nginx_config/default.conf
server {
    listen 80;
    server_name localhost;
    server_tokens off;

    location / {
        # デフォルトではhtmlを返す
        root /usr/share/nginx/html;
        try_files $uri $uri/ @dynamic;
    }

    location /api/ {
        # URLが"/api/"ではじまる場合のみ、APIサーバに転送
        # URLのうち"/api/"に続く部分のみを取り出す
        rewrite /api/(.*) /$1 break;
        # docker-composeの機能によって、exposeされた他コンテナのポートはアプリケーション名でアクセス可能
        proxy_pass http://apiserver:5050;
        # redirectを無効化(既にrewriteでリダイレクトを設定しているため)
        proxy_redirect off;
        proxy_set_header Host $host;
    }
}

単語情報GETの流れは以下の通りです。(例は「形容詞+名詞」の場合)

  1. クライアントは、単語生成(「反応」)ページを開いたときに、形容詞をlocalhost:8080/api/rand/adjectiveへGETリクエスト
  2. WebサーバがリクエストをAPIサーバhttp://apiserver:5050/rand/adjectiveへ転送
  3. APIサーバがリクエストに基づきランダムな形容詞1つをJSON形式で(Webサーバに)返す
  4. Webサーバはレスポンスをクライアントへ転送
  5. クライアントに形容詞データのJSONが届く
  6. 名詞についても同様

単語データ生成

「MeCab IPADIC」を使用しました。

MeCabは、文章を単語(正確には形態素、ことばの最小単位)ごとに分かち書きするソフトウェアです。

そして、MeCabが参照する形態素の辞書の1つがIPADICです。

品詞ごとにcsvファイルで書かれていて、各行に各形態素の表記、読み、品詞、活用などが格納されています。

Adj.csv
あらっぽい,19,19,6956,形容詞,自立,*,*,形容詞・アウオ段,基本形,あらっぽい,アラッポイ,アラッポイ
あらっぽし,23,23,6956,形容詞,自立,*,*,形容詞・アウオ段,文語基本形,あらっぽい,アラッポシ,アラッポシ
あらっぽから,27,27,6956,形容詞,自立,*,*,形容詞・アウオ段,未然ヌ接続,あらっぽい,アラッポカラ,アラッポカラ
...

このアプリの単語合成モードは

  • 形容詞(終止形)+名詞
  • 副詞+動詞(終止形)
  • 名詞+(助詞)+動詞(終止形)

の3種類です。
この形式で最大限単語を利用するため、(文法的用語には少し不正確ですが)以下のルールで単語を抽出しました。

apiserver/db/format-ipadic-csv.bash
# 形容詞として利用

# 形容詞終止形
cat $PATH_FROM/Adj.csv       | awk -F"," '$10~/^基本形$/ {print $1}' >  $PATH_TO/adj.txt
# (「な」を付けると形容詞になる名詞)+「な」
cat $PATH_FROM/Noun.adjv.csv | awk -F"," '{print $1 "な"}'         >> $PATH_TO/adj.txt
# (「ない」を付けると形容詞になる名詞)+「ない」
cat $PATH_FROM/Noun.nai.csv  | awk -F"," '{print $1 "ない"}'       >> $PATH_TO/adj.txt

# 名詞として利用
# 「ナンセンスさ」を出したいので人名、地名、専門用語は除外し、一般名詞だけ使用

# 名詞
cat $PATH_FROM/Noun.csv          | awk -F"," '{print $1}' >  $PATH_TO/noun.txt
# 「する」を付けると動詞になる名詞
cat $PATH_FROM/Noun.verbal.csv   | awk -F"," '{print $1}' >> $PATH_TO/noun.txt
# 「な」を付けると形容詞になる名詞
cat $PATH_FROM/Noun.adverbal.csv | awk -F"," '{print $1}' >> $PATH_TO/noun.txt
# 「ない」を付けると形容詞になる名詞は単体で使うと不自然なので未使用)

# 副詞として使用

# 副詞
cat $PATH_FROM/Adverb.csv        | awk -F"," '{print $1}'                    >  $PATH_TO/adverb.txt
# 副詞としても使える名詞(「毎日」等)
cat $PATH_FROM/Noun.adverbal.csv | awk -F"," '{print $1}'                    >> $PATH_TO/adverb.txt
## 形容詞連用形(小さい「っ」で終わるものは動詞が後続できないので削除)
cat $PATH_FROM/Adj.csv           | awk -F"," '$10~/^連用テ接続$/ {print $1}' \
                                 | awk       '!/っ$/'                        >> $PATH_TO/adverb.txt

# 動詞として利用

# 動詞
cat $PATH_FROM/Verb.csv        | awk -F"," '$10~/^基本形$/ {print $1}'        >  $PATH_TO/verb.txt
# (「する」を付けると動詞になる名詞)+「する」
cat $PATH_FROM/Noun.verbal.csv | awk -F"," '{print $1 "する"}'                >> $PATH_TO/verb.txt

上記で生成したcsvをSQLiteに流し込んだものを、単語データベースとして使用しました。

apiserver/db/csv2sqlite.bash
sqlite3 $SQL_PATH/$SQL_NAME << EOS

/* create tables */
.read ./db/init.sql

/* let col separator "," */
.separator ','

/* import word data csvs */
.mode csv
.import $CSV_PATH/adj.csv adjectives
.import $CSV_PATH/adverb.csv adverbs
.import $CSV_PATH/noun.csv nouns
.import $CSV_PATH/verb.csv verbs
EOS
apiserver/db/init.sql
drop table if exists adjectives;
create table adjectives (
    id integer primary key,
    word text
);

drop table if exists adverbs;
create table adverbs (
    id integer primary key,
    word text
);

drop table if exists nouns;
create table nouns (
    id integer primary key,
    word text
);

drop table if exists verbs;
create table verbs (
    id integer primary key,
    word text
);

(個人的)面白かった生成結果

Screenshot from 2020-03-14 23-17-22.png
私たちがコードを書けるのは、数学様のおかげでございます。

Screenshot from 2020-03-14 22-43-34.png
ウケ狙いちゃうわ!

Screenshot from 2020-03-14 22-08-33.png
こんなアプリ作っちゃうくらいだからしょうがないね

Screenshot from 2020-03-14 22-42-12.png
文章が成立している!

意外とフレーズが成立する割合が高く(体感100回に1回くらい?)、他にも以下のような「まともな」フレーズが生成されました。

居場所を失う
酷い蛇行
自我を失う
泥棒を訴える
移り気なロマンチスト
やさしいインターフェース

はまったところ

docker-compose buildできない

docker-composeapt installでインストールすると古いバージョンが入ってしまいます…

そして、古いバージョンのdocker-composeでバージョン3のdocker-compose.ymlを使うとビルドに失敗します。

apt installではなくcurlで最新バージョンをダウンロードしましょう

Install Docker Compose | Docker Documentation

(公式ドキュメントはちゃんと読まねば…反省)

go buildできない

Go1.13からはライブラリ管理の方法が変わったため、最初にgo mod initをする必要があります。

このコマンドを打つと、モジュールの依存関係ファイルgo.modやモジュールが本物か確かめるためのチェックサムgo.sumが生成されます。

Go Modules - Qiita

Go 1.13 に向けて知っておきたい Go Modules とそれを取り巻くエコシステム - blog.syfm

その代わり、go.modのおかげでライブラリを手動でgo getする必要がなくなりました!
go build時に自動でダウンロードされる)

コンテナ作成中に生成した実行ファイルが消える

volumesCOPYを混同していました。

Dockerfile中でCOPYすると、コンテナのビルド時にホストのディレクトリをコピーします。

docker-compose.yml中でvolumesを指定すると、コンテナの起動時にコンテナのディレクトリにホストのディレクトリをマウントします。

そのため、ホストのソースをCOPYしてせっかく実行ファイルをビルドしても、同じディレクトリをvolumesに指定するとホストのディレクトリ(もちろん実行ファイルは無い)に隠されてしまいます…

docker-composeのvolumesで指定したホストのディレクトリがマウントされずハマった

conic-gradientが使えない

単語生成画面の集中線はCSSのconic-gradient関数を使用する予定でした

…が、この関数はFirefoxでは非対応です。

conic-gradient() - CSS: カスケーディングスタイルシート | MDN

そこで、同名のnpmパッケージを使ってjs側で模様を生成しました。

conic-gradient - npm

しかし、このパッケージはES5で書かれているのでVue CLI内部でimportできません。

結局、

  1. index.htmlで直接グローバルに読み込む
  2. Vue側ではwindow.ConicGradientの形で呼び出す
  3. npm run buildではビルドされないので、別途モジュールをstaticにコピーする

という手順を取りました。

vue-frontend/index.html
<!DOCTYPE html>
<html>
...
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <!-- ここでモジュールを読み込む(prefixfreeはconic-gradientの依存モジュール) -->
    <script src="./static/prefixfree.min.js"></script>
    <script src="./static/conic-gradient.js"></script>
  </body>
</html>
vue-frontend/src/components/bangBackground.vue
<script>
export default {
  name: 'bangBackground',
  data: function () {
    return {
      bangSVG: new window.ConicGradient({
        repeating: true,
        stops: `#ffffff 0,
                #ffffff 2.0%,
                #AAAAAA 2.125%,
                #AAAAAA 2.375%,
                #ffffff 2.5%`
      })
    }
  }
}
</script>
vue-frontend/Dockerfile
FROM node:lts-alpine as builder

RUN mkdir -p /app
WORKDIR /app

RUN npm install -g http-server

COPY ./package*.json ./
RUN npm install \
    # conic-gradientモジュールをstaticにコピー
    && mkdir -p static/ \
    && cp node_modules/conic-gradient/conic-gradient.js static/ \
    && cp node_modules/prefixfree/prefixfree.min.js static/

COPY . .
RUN npm run build

FROM nginx:stable-alpine as product

COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx_config/default.conf /etc/nginx/conf.d/default.conf

CMD ["nginx", "-g", "daemon off;"]

参考文献

Go言語

APIサーバ
golangでREST APIをやってみた① - Qiita

SQLiteとの接続
golangでSQLite3を使ってデータベースを操作する方法まとめ | Black Everyday Company

MeCab

IPADICダウンロード方法
Alpine LinuxでMeCab with NEologd - Qiita

Vue.js

SPA(シングルページアプリケーション)のつくり方
【Vue.js】爆速でSPAを作る - Qiita

axiosを使ったJSONのGET/POST
Vue-CLIのプロジェクトでaxiosを使ってAPIや外部リソースからのデータを取得する | 大阪市天王寺区SOHOホームページ制作 | デザインサプライ-DesignSupply.-

コンポーネント内部に要素を入れる(公式)
スロット — Vue.js

nginx

URLの一部分だけ取り出してポートフォワーディング(/api/hoge -> apiserver:5050/hoge)
Nginx reverse proxy + URL rewrite - Server Fault

Docker,docker-compose

docker-compose基本操作
docker-compose コマンドまとめ - Qiita

複数サーバ(複数コンテナ)を協調させる方法
マイクロサービスほどじゃないけどウェブサービスを分割開発したい人向けDocker設定を集めるスレ - Qiita

vue-cliビルド方法(公式)
Vue.js アプリケーションを Docker 化する — Vue.js

Golangマルチステージビルド方法

(マルチステージビルドのメリット)
Dockerのマルチステージビルドを使う - Qiita
(Go1.7の記事ですが、Go1.13以上の場合Dockerfileでgo getをする必要はありません)

(go-sqlite3ライブラリを使う場合)
go-sqlite3 が入った状態での Docker のマルチステージビルドを行う - Pistatium note

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

Docker + VSCodeでGoの環境構築(ホットリロード対応)

はじめに

個人開発で最近Go言語を使う機会が増えてきました。
Go言語で開発するにあたり私が感じたことは、

  • Dockerで動かしたい
  • ホットリロード機能が欲しい
  • 開発用・本番用でコンテナを分けたい
  • 開発時は、VSCode上でコンテナ内とリモート接続して作業したい

色々な方々のDocker環境を参考にDockerでGo環境を作りました。
よろしければ参考にしてください。

ホスト環境

  • Docker: ver.19.03.8
  • docker-compose: ver.1.25.4
  • VSCode: ver.1.43.0

ホットリロード

Go言語でホットリロードをする際、調べていたら、

  • fresh
  • realize

があるのを知りました。
しかし、筆者はrealize の方はエラー(error: returned a non-zero code: 1)が発生してgo get ができませんでした。

一度コンテナを立ち上げて、コンテナ内でgo get したらうまく行くのですが解決出来ませんでした...

ですので今回はfresh を使用します。

手順

  1. Dockerfile(開発用&本番用) + docker-compose.yml の作成
  2. Remote Containers の設定
  3. ホットリロードの設定
  4. 開発環境の起動
  5. 開発環境の動作確認
  6. 本番環境の起動

開発環境

はじめに作業ディレクトリを作ります。

/
# 作業ディレクトリ作成
$ mkdir go-work
# 作業ディレクトリへ移動
$ cd go-work

1. Dockerfile(開発用&本番用) + docker-compose.yml の作成

続いてDockerfiledocker-compose.yml を書いていきます。
下記コマンドからファイルを作成してください。

/go-work
$ touch docker-compose.yml
$ mkdir -p docker/golang
$ touch docker/golang/Dockerfile.dev
$ touch docker/golang/Dockerfile.dep

※ Dockerfileの言語サポート

Dockerfile.dev(.dep) はVSCodeの標準では言語サポートされていないので設定を追加します。
設定されている方は次へ飛ばしてください。

  1. [Code] -> [基本設定] -> [設定]
  2. Files: Associations で検索
  3. settings.json で編集
settings.json
{
  "files.associations": { 
    "Dockerfile.dev": "dockerfile",
    "Dockerfile.dep": "dockerfile"
  }
}

Docker + docker-composeの設定

  • 開発用Dockerfile
/go-work/docker/golang/Dockerfile.dev
FROM golang:1.14.0-alpine3.11

SHELL ["/bin/ash", "-c"]
WORKDIR /go/src/app
COPY ./app ./
EXPOSE 8080
ENV GO111MODULE=on

RUN apk add --no-cache alpine-sdk

# Golang ホットリロード(freshのインストール)
RUN go get github.com/pilu/fresh

# Golang 環境構築(任意)
RUN go get github.com/go-delve/delve/cmd/dlv \
    github.com/rogpeppe/godef \ 
    golang.org/x/tools/cmd/goimports \
    golang.org/x/tools/cmd/gorename \
    sourcegraph.com/sqs/goreturns \
    github.com/ramya-rao-a/go-outline \
    golang.org/x/tools/gopls@latest
  • 本番用Dockerfile
/go-work/docker/golang/Dockerfile.dep
FROM golang:1.14.0-alpine3.11 as builder

WORKDIR /go/src/app
COPY ./app ./
ENV GO111MODULE=on 
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/app

FROM alpine:latest

RUN apk --no-cache add ca-certificates
COPY --from=builder /go/bin/app /go/bin/app
ENTRYPOINT [ "/go/bin/app" ]
EXPOSE 8080
  • docker-compose
/go-work/docker-compose.yml
version: '3.7'

services:
  app:
    container_name: go-app
    build:
      context: .
      # Remote Containerが立ち上がるまで待機
      dockerfile: docker/golang/Dockerfile.dev
    ports:
      - 8080:8080
    volumes:
      - ./app:/go/src/app:cached
    command: /bin/ash -c "while sleep 1000; do :; done"

2. Remote Containers の設定

インストール

インストールしていない方は、 Remote-Containers からインストールしてください。

準備

まずはじめにVSCode の左下のアイコンをクリックしてください。
01-icon.png
すると、コマンドパレッドが開きますので、
Remote-Containers: Add Development Container Configuration Files...
と検索して選択してください。

02-remote-containers.png

選択したら、
From 'docker-compose.yml'
を選択してください。

03-remote-containers.png

選択後.devcontainer ディレクトリが作成され、設定ファイルが格納されています。

設定

.devcontainer
    ├── devcontainer.json
    └── docker-compose.yml

まずはじめに、docker-compose.yml はもう作成してあるので、
.devcontainer 内のdocker-compose.yml は削除してください。

下記のコマンドを実行してファイルを削除してください。

/go-work
$ rm .devcontainer/docker-compose.yml

次にdevcontainer.json を編集します。

/go-work/.devcontainer/devcontainer.json
{
  "name": "go",
  "dockerComposeFile": [
    "../docker-compose.yml"
  ],
  "service": "app",
  "workspaceFolder": "/go/src/app",
  "settings": { 
    "terminal.integrated.shell.linux": "/bin/ash",
    "go.gopath": "/go"
  },
  "extensions": [
    "ms-vscode.go",
  ],
  "shutdownAction": "stopCompose"
}

3. ホットリロードの設定

準備

まずはGoアプリを開発する作業ディレクトリを作成します。
その後ホットリロードを行うためのfresh の設定ファイルを作成します。

ちなみに設定ファイルを作成しなくても大丈夫ですのでこちらは任意で!

下記のコマンドを実行してください。

/go-work
$ mkdir app
$ cd app
# /go-work/app
$ touch .fresh.conf

設定

/go-work/app/.fresh.conf
root:              .
tmp_path:          ./tmp
build_name:        runner-build
build_log:         runner-build-errors.log
valid_ext:         .go, .tpl, .tmpl, .html
no_rebuild_ext:    .tpl, .tmpl, .html
ignored:           assets, tmp
build_delay:       600
colors:            1
log_color_main:    cyan
log_color_build:   yellow
log_color_runner:  green
log_color_watcher: magenta
log_color_app:

上記の設定はテンプレートから持ってきています。
細かい設定は任意で!

4. 開発環境の起動

さて、いよいよコンテナを起動します。
VSCode から左下のアイコンをクリックしてください。

01-icon.png

すると、コマンドパレッドが開きますので、
Remote-Containers: Reopen in Container
を検索して選択してください。

04-remote-containers.png

すると、画面が切り替わります。
この時点でDockerイメージが作成され、コンテナが自動で起動します。
起動するまでしばらく時間がかかりますので待機...

しばらくするとコンテナとリモートで繋がり作業ディレクトリに移ります。

05-remote-containers.png

VSCode の左下のアイコンも変わっているはずです。
VSCode 上でターミナルを開くと、コンテナ内のターミナルに切り替わっています。

コンテナ内
/go/src/app #

5. 開発環境の動作確認

ここからはコンテナ内で作業していきます。
ホットリロードの動作を確認するためにGo のプログラムを開発していきます。

main.go の作成

開発するためのファイルを作成します。

/go/src/app
touch main.go
go mod init main

つづいてmain.go に簡単なWebサーバを作成します。

/go/src/app/main.go
// ...省略
func init() {
  http.HandleFunc("/", index)
}

func index(w http.ResponseWriter, r *http.Request) {
  fmt.Fprint(w, "Hello World")
}

func main() {
  fmt.Print("Server Start")
  if err := http.ListenAndServe(":8080", nil); err != nil {
    panic(err)
  }
}

実行

main.go を実行するのですが今回はfresh を使って実行します。
VSCode 内のコンテナで実行しても良いのですが、こちらは開発時で利用したいので今回は別ターミナルで実行します。

別ターミナルでコンテナ内に入り、ホットリロードを起動させます。

別ターミナル
$ docker exec -it go-app /bin/ash

# コンテナ内: /go/src/app
fresh -c .fresh.conf

main.go が実行されますので確認作業に移ります。

確認

確認は、ブラウザ または、ターミナル で確認します。

  • ブラウザの場合は、http://localhost:8080 にアクセス
  • ターミナルの場合は、curl http://localhost:8080 を実行

Hello World が返ってくれば起動しています。

変更

つづいてホットリロードが正常に起動しているかを確認したいので、
fresh を起動したままVSCode に戻ってください

VSCode上でmain.go を変更し、保存してください。

/go/src/app/main.go
func index(w http.ResponseWriter, r *http.Request) {
  - fmt.Fprint(w, "Hello World")
  + fmt.Fprint(w, "Hello Golang")
}

保存したら再度、確認作業を行ってください。
Hello Golang が返ってくればホットリロードは正常に起動しています。

6. 本番環境の起動

本番環境はDockerをビルドして実行すれば良いだけです。

イメージのビルド

/go-work
$ docker build -t go-app -f ./docker/golang/Dockerfile.dep .

コンテナの実行

/go-work
$ docker run --name go-app go-app -p 8080:8080 -d . /bin/ash
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】MySQLのTimestamp型カラム(日本時間が入ってる)をtime.TimeにするとUTCになってしまうのを対応する

こんにちは:smiley:

MySQLのTimestamp型カラムに2020-03-12 15:00:00(日本のおやつの時間!)が入っていて、それをGo言語のコードでtime.Time型として取ってくると"2020-03-12 15:00:00 +0000 UTC"になってしまう。

あるとおもいます。

それを期待通りの値に直す小ネタです。

例.go

    tempTime := dbResults[0].CreatedAt
    location := time.FixedZone("JST", 9*60*60)
    tempTime = tempTime.In(location).Add(-9*time.Hour)
    log.Print(tempTime)
  • 強引にJSTに変換します。
  • そのとき勝手に9時間足されてしまうので、自前で引き直します。

2020/03/16 19:44:57 2020-03-12 15:00:00 +0900 JST

やったぜ。

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

Go言語でSliceに要素が存在するか調べるには、要素個数を見るだけで良い ( #go : Error: slice index out of range <nil> )

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

Go言語でSliceを要素に持つStructを定義する例 ( #Go )

package main

import "fmt"

type Card struct {
    Names []string
}

func main() {
    card := Card{[]string{"Ace", "Jack", "King"}}

    fmt.Println(card.Names[0]) // Ace
    fmt.Println(card.Names[1]) // Jack
    fmt.Println(card.Names[2]) // King
}

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3034

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

Go 言語で declared but not used のエラーを回避したい。未使用変数だが名前はつけておきたい時は? ( #go )

未使用変数を無理やりアンダースコアに代入してエラーを防ぐ。

package main

func main() {
    foo := "bar"

    _ = foo
}

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3033

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

Go言語のdelve でブレークポイントを設定してデバッグ実行する ( #go / dlv cli / dlv debug / set break point and debug with single go lang file )

ファイル

こんなファイルがある場合

package main

import (
    "fmt"
)

func main() {
    fmt.Println("A")
    fmt.Println("B")
    fmt.Println("C")
    fmt.Println("D")
    fmt.Println("E")

    foo()
}

func foo() {
    value := "FOO"
    fmt.Println(value)
}

delveの実行

go run みたいにファイル名を指定して実行する

dlv debug go/dlv/main.go

ブレークポイントの指定

ファイル名+行数指定で3つのブレークポイントを指定してみる

例: break main.go:9

(dlv) break main.go:8
Breakpoint 1 set at 0x10c23af for main.main() ./go/dlv/main.go:8
(dlv) break main.go:10
Breakpoint 2 set at 0x10c2485 for main.main() ./go/dlv/main.go:10

実行

continue すると、ひとつずつ進める!
ブレークポイントの残りがなくなり、スクリプトが処理を完了すると、終了する。

(dlv) continue
> main.main() ./go/dlv/main.go:8 (hits goroutine(1):1 total:1) (PC: 0x10c23af)
     3: import (
     4:     "fmt"
     5: )
     6:
     7: func main() {
=>   8:     fmt.Println("A")
     9:     fmt.Println("B")
    10:     fmt.Println("C")
    11:     fmt.Println("D")
    12:     fmt.Println("E")
    13:
(dlv) continue
A
B
> main.main() ./go/dlv/main.go:10 (hits goroutine(1):1 total:1) (PC: 0x10c2485)
     5: )
     6:
     7: func main() {
     8:     fmt.Println("A")
     9:     fmt.Println("B")
=>  10:     fmt.Println("C")
    11:     fmt.Println("D")
    12:     fmt.Println("E")
    13:
    14:     foo()
    15: }
(dlv) continue
C
D
E
FOO
Process 54908 has exited with status 0

再実行

restart で最初から実行できる!

(dlv) restart
Process restarted with PID 54942

ブレークポイントをすべてクリアする

(dlv) clearall
Breakpoint 1 cleared at 0x10c23af for main.main() ./go/dlv/main.go:8
Breakpoint 2 cleared at 0x10c2485 for main.main() ./go/dlv/main.go:10

function + 行数指定でブレークポイントを設定する

(dlv) break foo:2
Breakpoint 3 set at 0x10c2616 for main.foo() ./go/dlv/main.go:19
(dlv) continue
A
B
C
D
E
> main.foo() ./go/dlv/main.go:19 (hits goroutine(1):1 total:1) (PC: 0x10c2616)
    14:     foo()
    15: }
    16:
    17: func foo() {
    18:     value := "FOO"
=>  19:     fmt.Println(value)
    20: }

変数の中身を表示してみる
Go言語の文法が使えるわけじゃなくて CLI のコマンドで表示させるみたいだ

(dlv) print value
"FOO"

ブレークポイントの指定方法

いくつかあるみたいだ。

delve/locspec.md at master · go-delve/delve

(ここに書いたやり方だと、なぜかcurrent file を認識してくれていないが、まだ使い始めたばかりなのでよく分かっていない)

参考

Golangのデバッガdelveの使い方 - Qiita

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3032

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

フロント・バックエンドサービスをコンテナ化してもGitコミット時にLefthookでテストやLint実行

TL;DR

  • フロント・バックエンドサービスをそれぞれコンテナ化、docker-composeで全てのコンテナを管理する
  • monorepoで管理した際に1リポジトリとなるので気軽にGit Hookの処理ができない
  • Lefthookを導入してpre-commit時にすべてのコンテナに対してLintツールを動作させるようにした

サンプルコード

https://github.com/MegaBlackLabel/lefthook-docker-node-go-dev-sample

1リポジトリで開発環境を管理したい

渋川さんの記事

マイクロサービスほどじゃないけどウェブサービスを分割開発したい人向けDocker設定を集めるスレ
https://qiita.com/shibukawa/items/fd49f98736045789ffc3

を読んでフロントエンドとAPIがごっちゃになっている開発環境ヨクナイ!ってことでサービス単位でコンテナ化してvs codeのリモートコンテナ機能を使って開発環境を再構築をしていたらGitとGItHooksの扱いで躓く。

Git Hooksの扱い

1リポジトリでサービスをコンテナ化してmonorepoを構築した際に.gitはルートディレクトリのみに存在する。
何が起こるかというと、フロントエンド開発時に「Husky+lint-stagedでコミット前にeslintやprettierを実行してコミット前にソースをチェックする」ができなくなります。これは治安が悪くなるってことで調査を進めた結果、試してみたのがLefthookです。

Lefthookを使ってみる

Lefthookとは?ということで公式の説明を引用

The fastest polyglot Git hooks manager out there

Fast and powerful Git hooks manager for Node.js, Ruby or any other type of projects.

  • Fast. It is written in Go. Can run commands in parallel.
  • Powerful. With a few lines in the config you can check only the changed files on pre-push hook.
  • Simple. It is single dependency-free binary which can work in any environment.
  • GO製。コマンドを並列実行できる
  • かんたんな設定ファイルでpre-pushのhookが使えるようになる
  • (GOによる)シングルバイナリなので、どのOSでも実行可能

今回は以下のことを実装しました。

  • 起動時にdocker-composeでインスタンス起動
  • 起動したインスタンスに対してコマンドを実施
  • 更新対象のファイルの拡張子をgrepして対象の拡張子がstageにあるときのみ実行する

※、余談ですが日本語での紹介記事は2つのみ。1つは公式の翻訳ともう一つはRubyでのHusky置き換え記事です。

Lefthook: 多機能GItフックマネージャ
https://techracho.bpsinc.jp/hachi8833/2019_10_16/79052

Git HooksマネージャーのLefthookを試してHusky(+lint-staged)と比較した結果、乗りかえました
https://blog.solunita.net/posts/change-lefthook-instead-of-lintstaged-with-husky/

Lefthookインストール

Lefthookインストールですが、公式サイトのInstallationか、リリースページから直接ダウンロードします。自分はWindows環境なのでプロジェクト内にlefthook.exeをそのまま置いて使っています。
インストール後に対象のリポジトリで以下のコマンドを実行

lefthook install #Windowsで直下に置いている場合は lefthook.exe install

インストールが完了するとリポジトリのルートに「lefthook.yml」が作成されますので、こちらに設定を書きます。

Lefthook設定ファイル解説

lefthook.yml
# EXAMPLE USAGE
# Refer for explanation to following link:
# https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md
#
# pre-push:
#   commands:
#     packages-audit:
#       tags: frontend security
#       run: yarn audit
#     gems-audit:
#       tags: backend security
#       run: bundle audit
#
# pre-commit:
#   parallel: true
#   commands:
#     eslint:
#       glob: "*.{js,ts}"
#       run: yarn eslint {staged_files}
#     rubocop:
#       tags: backend style
#       glob: "*.rb"
#       exclude: "application.rb|routes.rb"
#       run: bundle exec rubocop --force-exclusion {all_files}
#     govet:
#       tags: backend style
#       files: git ls-files -m
#       glob: "*.go"
#       run: go vet {files}
#   scripts:
#     "hello.js":
#       runner: node
#     "any.go":
#       runner: go run

pre-commit:
    piped: true
    commands:
        1_docker-compose:
            root: .
            run: docker-compose up -d
        2_eslint:
            root: "containers/frontend/"
            glob: "*.{js,jsx,ts}"
            run: docker exec -it frontend-container yarn eslint-check
        3_frontend-test:
            root: "containers/frontend/"
            glob: "*.{js,jsx,ts}"
            run: docker exec -it frontend-container yarn test
        4_api-test:
            root: "containers/api/"
            run: docker exec -it api-container go test

サンプルとインデントの数が違うのはご愛嬌。今回使っている処理は以下の通り。

  • pre-commit: コミット実施前に実行してほしい処理
  • piped:commandsを名前順に実施する。そのため頭文字に数字をつける
  • commands:実行するコマンド
  • root:どの階層でコマンドを実施するかを記載
  • glob:stagedのファイルのうち、コマンド実行対象とするファイルを選別する
  • run:実行するコマンド

コマンドの内容ですが以下の処理を実施しています。

  • docker-composeでコンテナを起動
  • docker execを使用してフロントエンドコンテナでeslint+Prettierを実施
  • docker execを使用してフロントエンドコンテナでテストを実施
  • docker execを使用してAPIコンテナでテストを実施

フォルダ・ファイル構成

以下の想定でファイル構成を行っています。
- フロントエンドとAPIサーバをそれぞれコンテナ化して管理
- docker-composeでコンテナを一元管理
- フロントエンドではeslint+Prettierでのソース整形とテストを実施
- APIサーバではテストを実施
- それぞれ正常に実行時のみコミットを実施する

ファイル構成
lefthook-docker-node-go-dev-sample
│  .gitignore
│  docker-compose.yml
│  lefthook.exe
│  lefthook.yml
│  LICENSE
│  README.md
│
└─containers
    ├─api
    │      docker-entrypoint.sh
    │      Dockerfile
    │      go.mod
    │      go.sum
    │      main.go
    │      main_test.go
    │
    └─frontend
        │  .eslintrc.json
        │  docker-entrypoint.sh
        │  Dockerfile
        │  package.json
        │  README.md
        │  yarn.lock
        │
        ├─node_modules
        ├─public
        │      favicon.ico
        │      index.html
        │      logo192.png
        │      logo512.png
        │      manifest.json
        │      robots.txt
        │
        └─src
                App.css
                App.js
                App.test.js
                index.css
                index.js
                logo.svg
                serviceWorker.js
                setupTests.js

Lefthookでpre-commit時にコンテナに対してコマンド実行

pre-commitをrunしてみる。

実行結果
.\lefthook.exe run pre-commit
RUNNING HOOKS GROUP: pre-commit

  EXECUTE > 1_docker-compose
api-container is up-to-date
frontend-container is up-to-date

  EXECUTE > 2_eslint
yarn run v1.22.4
$ eslint --print-config .eslintrc.json | eslint-config-prettier-check
No rules that are unnecessary or conflict with Prettier were found.
Done in 0.69s.

  EXECUTE > 3_frontend-test
yarn run v1.22.4
$ CI=true react-scripts test
PASS src/App.test.js
  ✓ renders learn react link (39ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.081s
Ran all test suites.
Done in 3.23s.

  EXECUTE > 4_api-test
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> github.com/MegaBlackLabel/lefthook-docker-node-go-dev-sample.setupRouter.func1 (3 handlers)
[GIN] 2020/03/15 - 03:43:28 | 200 |      44.742µs |                 | GET      /ping
PASS
ok      github.com/MegaBlackLabel/lefthook-docker-node-go-dev-sample    0.014s

SUMMARY: (done in 7.78 seconds)
✔️  1_docker-compose
✔️  2_eslint
✔️  3_frontend-test
✔️  4_api-test

コマンドが順番に実行されそれぞれの実行結果が表示。最後にサマリーとしてOK・NGが出力されます。

まとめ

  • サービス単位でコンテナ化して開発するのは便利だね。でもGit Hooksの処理ができない
  • Lefthook使えばできるよ。シングルバイナリだから導入もかんたんだよ
  • コンテナ化してもGit Hooksが使えるので複数の開発者がいても治安が維持できそう

以上

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

Dockerコンテナ上で動くGinサーバーにアクセスできないエラーの解決法

概要

GolangのWebフレームワークであるGinを使ったAPIサーバーを、Dockerコンテナ上にデプロイして動かそうとしたところ、APIにアクセスできずかなり長い時間悩まされました。

結果的には、Ginサーバーのコードの書き方の問題だったことが分かったのですが、解決方法を念のためここにメモしておきます。

ちなみに、このエラーはWindows10及びAWS上のUbuntuサーバー(t2.small)で起こりました(尤も、実行環境はこのエラーの発生にあまり関係ないようでしたが)。

状況

Ginを用いたAPIサーバーを立てようとしていました。まだ環境構築の段階なので、コードは以下のようなモックのものになっています。

package main

import (
    "log"
    "os"
    "github.com/gin-gonic/gin"
)

func main() {
    logConfig()

    r := gin.Default()
    r.GET("/accounting-api", func(c *gin.Context) {
        log.Println("GET")
        c.JSON(200, gin.H{
            "state": "success",
        })
    })
    r.DELETE("/accounting-api", func(c *gin.Context) {
        log.Println("DELETE")
        c.JSON(200, gin.H{
            "state": "success",
        })
    })
    log.Println("Start Server")
    r.Run()
}

func logConfig() {
    logFile, _ := os.OpenFile("log/log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    log.SetOutput(logFile)
    log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile)
    log.SetPrefix("[LOG] ")
}

単純に、GETDELETEメソッドで特定のパスへのリクエストが来たら、{"state": "success"}というjsonを返すだけのサーバーです。

そして、このサーバーを動かすためのDockerfileが以下です。

FROM golang:alpine
RUN apk update && apk add --no-cache git
RUN go get -u github.com/gin-gonic/gin && mkdir /usr/src && mkdir /usr/src/api
COPY ./api /usr/src/api
WORKDIR /usr/src/api
CMD ["go","run","main.go"]

ホスト上のapiというディレクトリには上記のGoファイル等があるため、それをコンテナ上にコピーして、サーバーを立ち上げます。このDockerfileをapiという名前でビルドして、それを以下のコマンドで立ち上げました。

docker run -p 8083:8080 api

ホスト上のポート8083をコンテナ上のポート8080にマッピングしています。上記のコマンドを実行すると、以下のような出力がなされ、Ginサーバーが立ち上がっていることが確認できます。

[GIN-debug] GET    /accounting-api           --> main.main.func1 (3 handlers)
[GIN-debug] DELETE /accounting-api           --> main.main.func4 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on localhost:8080

ポートを指定しなかったので、デフォルトでポート8080で動いています。このように一見ちゃんと動いているようにも見えますが、ホスト側でhttp:localhost:8083にcurlでアクセスしてみても、以下のようにエラーが出てしまいました。

curl: (52) Empty reply from server

エラー解消のため試みたこと

1. コンテナの中に入って、APIにアクセスできるか確認

まず最初に、コンテナ上で本当にGoのプログラムがちゃんと動いているのかを確認します。そのために立ち上げたdockerコンテナの中に入ってみます。

# apiサーバーの動くコンテナの中に入る
docker exec -it api /bin/ash

このコンテナのベースとなっているAlpineには/bin/bashがなかったので、/bin/ashを使います。そして以下のコマンドを打って、ちゃんとプログラムが動いているのか確かめます。

# そもそもcurlが入っていないので、インストールする
apk add --no-cache curl
# 念のためプロキシを無効にして、curlでapiサーバーにアクセスする
curl -x "" http://localhost:8080/accounting/api

curlの実行結果がこちら。

{"state": "success"}

ちゃんと結果が取れています。なので、コンテナ上ではちゃんとGinサーバーのプログラムが動いているようです。

2. DockerfileでポートをEXPOSE

コンテナ上ではプログラムは動いているようなので、次はポートマッピングの部分がうまくいっていないのではないかと疑ってみます。調べてみるとDockerfileにはEXPOSEというコマンドが書けるようなので、追加してみます。

EXPOSE 8080

これをやっても、解決しませんでした。

そもそも公式ドキュメントによると、EXPOSEコマンドは実際は何の働きもせず、特定のポートを開放する旨を開発者に知らせるための、ドキュメントのような役割しかもっていないようです。なので、EXPOSEコマンドをつけただけで問題が解決するはずがありませんでした。

3. Windowsのファイアウォールの設定の確認

開発は基本的にWindows上のDockerで行っていたため、Windowsのファイアウォールの設定を見直してみましたが、これも意味がありませんでした。

そもそも、このDockerfileをUbuntu上でビルドして立ち上げてみても、同様にAPIサーバーにアクセスできなかったため、最初からなんとなくWindowsのファイアウォールのせいではないことが分かっていましたが。

4. (これで解決)Ginサーバー側でポートの指定

Goのプログラムの中で、GInサーバーを立ち上げる部分でポートを指定するようにしたところ、上手くアクセスできるようになりました。具体的には、以下の部分です。

r := gin.Default()
r.Run(":8080")

何も指定しなくてもデフォルトで8080で立ち上がるため気にしていなかったのですが、しっかりと指定しないとどうやらダメなようです。

なぜポートはデフォルトではダメで、明示的に記さなければならないかは、よくわかりません。分かったら追記します。

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

Go 言語でトランプのStructを作って、カードを強い順に並び替える ( #go lang sort card Struct )

package main

import (
    "fmt"
    "sort"
)

type Card struct {
    Strong int
    Name   string
}

func main() {
    cards := []Card{
        Card{2, "2"},
        Card{3, "3"},
        Card{4, "4"},
        Card{5, "5"},
        Card{6, "6"},
        Card{7, "7"},
        Card{8, "8"},
        Card{9, "9"},
        Card{10, "10"},
        Card{11, "J"},
        Card{12, "Q"},
        Card{13, "K"},
        Card{14, "A"},
    }

    sort.SliceStable(cards, func(i, j int) bool {
        return cards[i].Strong > cards[j].Strong
    })

    fmt.Println(cards)
}

// [{14 A} {13 K} {12 Q} {11 J} {10 10} {9 9} {8 8} {7 7} {6 6} {5 5} {4 4} {3 3} {2 2}]

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3031

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

#Go 言語でシャッフルした52枚のトランプを1枚ずつ配るサンプル ( Go lang deal shuffle playing cards example )

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    cards := generateCards()

    shuffleCards(cards)

    yourCards := []string{}

    for _, card := range cards {
        yourCards = append(yourCards, card)
        fmt.Println("Your cards are ...")
        fmt.Println(yourCards)
    }
}

func generateCards() []string {
    cardSeeds := []string{"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}
    cards := []string{}

    for i := 0; i < 4; i++ {
        cards = append(cards, cardSeeds...)
    }
    return cards
}

func shuffleCards(cards []string) {
    rand.Seed(time.Now().UnixNano())
    rand.Shuffle(len(cards), func(i, j int) { cards[i], cards[j] = cards[j], cards[i] })
}


result example

Your cards are ...
[5]
Your cards are ...
[5 3]
Your cards are ...
[5 3 8]
Your cards are ...
[5 3 8 J]
Your cards are ...
[5 3 8 J Q]
Your cards are ...
[5 3 8 J Q 9]
Your cards are ...
[5 3 8 J Q 9 9]
Your cards are ...
[5 3 8 J Q 9 9 6]
Your cards are ...
[5 3 8 J Q 9 9 6 5]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6 10]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6 10 J]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6 10 J 6]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6 10 J 6 K]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6 10 J 6 K 8]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6 10 J 6 K 8 2]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6 10 J 6 K 8 2 Q]
Your cards are ...
[5 3 8 J Q 9 9 6 5 4 4 8 3 7 Q A 4 9 9 7 2 A A 10 5 6 K 2 A Q 4 J 3 5 10 2 8 7 J 10 7 K K 6 10 J 6 K 8 2 Q 3]

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3030

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

Go 言語で "declared but not used" のエラーを無効にするには 未使用変数をアンダースコアだけにすれば良いじゃない ( #go lang ignore ))

package main

import (
    "fmt"
)

func main() {
    cards := []string{"A", "K", "Q", "J"}

    for _, card := range cards {
        fmt.Println(card)
    }

    // i declared but not used
    // for i, card := range cards {
    //  fmt.Println(card)
    // }

}

_i とかじゃなくアンダースコアだけ _ にすると良さげ

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3029

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

Go 言語で無限ループしながら コンソールのユーザー入力を待ち受ける ( #Go wait user input in console with infinite loop )

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    for {
        fmt.Println("Enter some words!")
        input := bufio.NewScanner(os.Stdin)
        input.Scan()
        fmt.Println("input is " + input.Text())
    }
}

// Enter some words!
// A
// input is A
// Enter some words!
// B
// input is B
// Enter some words!
// C
// input is C
// Enter some words!

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3028

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

go.modってどこにおくのがいいのだろう

go.modってどこにおくのがいいのか

先日一からgoでサービスを作っていたのですが、go.modの置き場で迷ってしまい少し時間を無駄遣いした時に、意外と日本語の情報がなかったので残そうと思います。

結論:ルートディレクトリに置く

 結論からいうと、プロジェクトのルートディレクトリにおくのが正解でした。
go modはもともと業務でも使っていたのである程度知識はあると思っていたのですが、いざ自分で初めから作るとなるとどこで go mod init するんだ?ってなってしまいました。勉強不足です。色々とググってみたのですが、あまりgo.modをおく場所に関する情報はなくて、自分で色々いじってみてなんとか解決しました。
 解決した後に1次ソースには当たっていなかったので(まず当たれという話ですが、、)1次ソースをみてみるとそう行った記述がありました。

A module is a collection of Go packages stored in a file tree with a go.mod file at its root.

ざっくり訳すと

モジュールとは、ルートディレクトリにgo.modがあるファイルツリーに保存されたGoのパッケージのコレクションである

という感じでしょうか。つまり、ルートディレクトリにgo.modを置くのが正解。ということでした。

自分でいじってみて、もうちょっと突っ込んでみる

初め、自分は1次ソースに当たらずに自分で調べたりいじったりしたので、自分なりの理解も得られました。

そもそも、 go.mod がルートディレクトリにないとどうなるのか

例えば、こんな構成のコードがあったとします。

Service
|
ー handler
ー model
ー dao

こういう構成であれば、handlerからdaoをimportする時、こう書きたいですよね

import "Service/dao"

ですが、例えば、go.modをhandler,model,daoの各ディレクトリ内に置いてしまうと、コンパイルした時にhandler,model,daoが別の外部パッケージとして見なされてしまい、「gorootかgopath内にそんなパッケージないよ」といった旨のエラーが吐かれます。
(私はこれが、gorootやgopahtにないというエラー内容から、go.modの配置の問題であることに気づくのが遅れました。)
また、エディタ上でもgo.modを参照して依存を解決している場合、赤くなると思います。
これを、Service配下にgo.modを一つ置くようにすることでhandler,model,daoが同一パッケージないのものとみなされ、上のimport文で解決できるようになります。
 反対に、自分で作ったパッケージをサブパッケージみたいに外部パッケージとして読み込みたい場合は、上の例であれば、handler,models,dao内にgo.modをおいて、giuhubとかに上げて、

import "github.com/user_name/repository_name/..."

と書いてあげれば、良さそうなこともわかりました。

最後に

go.modは自分が同一パッケージとして見なしたいもののルートディレクトリに置く、というのが正解でした。
それと、go.modの配置が悪くて依存が解決できない場合、コンパイルエラーのメッセージが

"package_name" is not in GOROOT

みたいに出ます。(goのバージョンで文言が変わる)なので、このエラーメッセージの時は、go.mod周りを疑うといいのかなと思いました。

何かを調べるときは、英語嫌がらずにまず1次ソースに当たるのがやはりいいですね、、

参考文献

https://blog.golang.org/using-go-modules

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

Go 言語でスライスの要素をすべて展開して、インデックスの順序と値を出力する例 ( #go foreach slice all elements )

// https://golang.org/pkg/math/rand/

package main

import (
    "fmt"
)

func main() {

    cards := []string{"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}
    for i, card := range cards {
        fmt.Println(i, card)
    }

}

// e.g
//
// 0 2
// 1 3
// 2 4
// 3 5
// 4 6
// 5 7
// 6 8
// 7 9
// 8 10
// 9 J
// 10 Q
// 11 K
// 12 A

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3027

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

Go 言語でスライス/配列をランダムな順番でシャッフルっする ( #go shuffle array / slice / cards example )

// https://golang.org/pkg/math/rand/

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    cards := []string{"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}
    cards4 := []string{}

    for i := 0; i < 4; i++ {
        cards4 = append(cards4, cards...)
    }

    rand.Seed(time.Now().UnixNano())
    rand.Shuffle(len(cards4), func(i, j int) { cards4[i], cards4[j] = cards4[j], cards4[i] })

    fmt.Println(cards4)
    // [7 3 10 7 10 9 A 8 K 9 J 10 10 A K Q K 5 K Q 6 J 3 8 2 6 2 7 Q J 8 3 J 6 2 8 4 9 A 9 2 4 4 3 5 A 4 6 5 Q 7 5]
    // [5 2 K K K 10 4 J 9 8 A 3 3 5 4 Q 9 3 4 J 7 6 5 Q 10 J A A 2 7 9 6 7 8 8 7 9 K Q 2 J 5 10 2 4 Q 8 A 6 6 10 3]
}

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3026

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

Go 言語で配列(Slice)からランダムに文字列を選ぶ ( #go random select string in slice )

// https://golang.org/pkg/math/rand/

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    cards := []string{"A", "B", "C"}

    rand.Seed(time.Now().UnixNano())

    for i := 0; i < 10; i++ {
        num := rand.Intn(len(cards))
        fmt.Println(cards[num])
    }
}

// result

// e.g

// C
// C
// C
// C
// C
// B
// B
// C
// B
// C

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3024

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

Go 言語で スライスを合体させてトランプの組み合わせを作る ( #go join multiple string slice n times )

// https://golang.org/pkg/math/rand/

package main

import (
    "fmt"
)

func main() {
    cards := []string{"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}
    cards4 := []string{}

    for i := 0; i < 4; i++ {
        cards4 = append(cards4, cards...)
    }

    fmt.Println(len(cards4)) // 52
    fmt.Println(cards4)      // [2 3 4 5 6 7 8 9 10 J Q K A 2 3 4 5 6 7 8 9 10 J Q K A 2 3 4 5 6 7 8 9 10 J Q K A 2 3 4 5 6 7 8 9 10 J Q K A]
}

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3025

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

Go lang / rand.Seed(time.Now().UnixNano()) used as value / #go

// https://golang.org/pkg/math/rand/

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // NG
    seed_result := rand.Seed(time.Now().UnixNano())

    // OK
    // Seed does not need return value
    // rand.Seed(time.Now().UnixNano())

    fmt.Println(rand.Int())
}

Seed生成した値を使い回すのではなく、一回だけ設定すれば良いみたいだ。なので帰り値は要らない。

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3022

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

Go lang で1桁のランダムな数値を生成する / rand.Intn(9) / #go

// https://golang.org/pkg/math/rand/

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())

    for i := 0; i < 100; i++ {
        fmt.Println(rand.Intn(9))
                // e.g exclude zero
                // fmt.Println(1 + rand.Intn(9-1))
    }

}

// Result example
//
// 2
// 7
// 1
// 0
// 2
// 8
// 3
// 1
// 2
// 4
// 3
// 3
// 3
// 4
// 4
// 7
// 3
// 2
// 6
// 7
// 2
// 4
// 1
// 4
// 4
// 2
// 1
// 2
// 1
// 7
// 1
// 0
// 8
// 1
// 4
// 3
// 5
// 7
// 0
// 7
// 6
// 3
// 8
// 4
// 8
// 4
// 1
// 8
// 2
// 0
// 2
// 4
// 1
// 6
// 7
// 7
// 1
// 1
// 2
// 3
// 5
// 5
// 6
// 5
// 2
// 4
// 5
// 6
// 2
// 4
// 6
// 8
// 2
// 7
// 1
// 3
// 8
// 8
// 6
// 3
// 8
// 0
// 0
// 3
// 1
// 8
// 6
// 1
// 5
// 5
// 1
// 5
// 5
// 1
// 5
// 5
// 2
// 1
// 3
// 7

rand.Intn() に上限の値を与えれば良いみたいだ。

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3023

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

【Go】ポインタ

ポインタ



値渡し、ポインタ渡し、参照渡しの違いについて
基本こちらの記事で理解
参照リンク

自分なりの整理


# このポインタ型の関数は
# アドレス値を受け取る
# 自身のアドレス値も別に持ってる("&"でアドレス確認可)
# 受け取ったアドレス値にあるデータにアクセス"*"

func pointer(i *int){
    fmt.Println("値(受け取ったアドレス)=メイン(アドレス)が入る: ", i)
    fmt.Println("自身のアドレス: ", &i)
    fmt.Println("値(受け取ったアドレス)のデータにアクセス: ", *i)
}


# メイン
func main() {
    i := 10
    fmt.Println("メイン(値): ", i)
    fmt.Println("メイン(アドレス): ", &i)

    # ポインタ型の関数にはアドレスを渡す
    pointer(&i)
}

結果

メイン(値):  10
メイン(アドレス):  0xc00009c008
値(受け取ったアドレス)=メイン(アドレス)が入る:  0xc00009c008
自身のアドレス:  0xc000098020
値(受け取ったアドレス)のデータにアクセス:  10
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む