20190811のGoに関する記事は8件です。

[Go]構造体の使い方(フィールドで準備/初期化/値の設定)について(サンプル付き)

いきなり全体のサンプル

いきなり全体のサンプルコード
package main
import "fmt"

type user struct {
    name string
    height int
    gender string
}

func main() {

    // 1. 初期化と値設定 別パターン
    u1 := new(user)
    u1.name = "織田"
    u1.height = 170
    u1.gender = "男"
    fmt.Println(u1) // => &{織田 170 男}

    // 2. 初期化と値設定 一気パターン
    u2 := user{name:"徳川", height:180, gender:"男"}
    fmt.Println(u2) // {徳川 180 男}

    // 3. 初期化と値設定 さらに省略パターン
    u3 := user{"豊臣", 165, "男"}
    fmt.Println(u3) // => {豊臣 165 男}

}

構造体とは

すごいざっくり言うと、複数の値をグループにして定義することができるもの…みたいです。

フィールドの書き方

フィールドの書き方
type /*構造体の名前*/ struct {
    /*データの名前*/ /*型*/
    /*データの名前*/ /*型*/
    /*データの名前*/ /*型*/

    // こんな感じで、好きなだけ書く
}
フィールドのサンプル
type user struct {
    name string
    height int
    gender string
}

1.実際に使う(初期化と値設定 別パターン)

まず初期化する

初期化の書き方
func /*何かのメソッドの中*/() {
    /*変数名*/ := new( /*構造体の名前*/ )
}
初期化のサンプル
func main() {
    u1 := new(user)
}

ちなみに、この時点では0(空文字)が入って初期化され、ポインタで返されている。

次にフィールドに値を設定する

フィールドへの値設定の書き方
func /*何かのメソッドの中*/() {
    /*変数名*/ := new( /*構造体の名前*/ )
    /*上で定義した変数名*/ . /*設定したいデータの名前*/  = /*設定したい値*/
    /*上で定義した変数名*/ . /*設定したいデータの名前*/  = /*設定したい値*/
    /*上で定義した変数名*/ . /*設定したいデータの名前*/  = /*設定したい値*/

    // こんな感じでデータ定義してるものを、好きなだけ書く
}
フィールドへの値設定のサンプル
func main() {
    u1 := new(user) // 初期化

    u1.name = "織田"
    u1.height = 170
    u1.gender = "男"
    fmt.Println(u1) // => &{織田 170 男}
}

サンプルの方には、どのように出力されるか、やってみてます。
ポインタなので、&が表示されています。

2.実際に使う(初期化と値設定 一気パターン)

一気に初期化と値設定する書き方
func /*何かのメソッド*/() {
    /*変数名*/ := /*構造体の名前*/ { /*データの名前*/ : /*値*/ , /*データの名前*/ : /*値*/ , /*データの名前*/ : /*値*/ }
}
一気に初期化と値設定するサンプルコード
func main() {
    u2 := user{name:"徳川", height:180, gender:"男"}
    fmt.Println(u2) // {徳川 180 男}
}

かなりスッキリかけます。
ただし、ポインタが返ってくるワケではないので注意してください。
fmt.Printlnでの実行結果も、&がありません。

3.実際に使う(初期化と値設定 さらに省略パターン)

初期化と値設定するさらに省略した書き方
func /*何かのメソッド*/() {
    /*変数名*/ := /*構造体の名前*/ { /*1つ目の値*/ , /*2つ目の値*/ , /*3つ目の値*/ }
}
初期化と値設定さらに省略したサンプルコード
func main() {
    u3 := user{"豊臣", 165, "男"}
    fmt.Println(u3) // => {豊臣 165 男}
}

さらに省略して、こう書くこともできます。
こちらも、ポインタが返されるワケではないので注意。

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

[Go]マップやスライスをrangeでループさせるがキーは必要でない場合、ブランク修飾子が便利な件

とりあえずサンプルコード

スライスの場合

スライスの場合
package main
import "fmt"

func main() {
    s := []int{2, 4, 6, 8}
    for _, v := range s {
        fmt.Println(v)
    }
}
スライスの場合 実行結果
2
4
6
8

マップの場合

マップの場合
package main
import "fmt"

func main() {
    m := map[string]int{"maeda":165, "yamada":175, "suzuki":180}
    for _, v := range m {
        fmt.Println(v)
    }
}
マップの場合 実行結果
165
175
180

ブランク修飾子の書き方

変数名のところに_このように、アンダースコアを書くだけです。
こうすることで、キーやインデックスに入れられる予定だった値を、処理の中で使わなくても、怒られません。

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

[Go]マップやスライスをrangeでループさせるも、キーやインデックスが必要でない場合、ブランク修飾子が便利な件

とりあえずサンプルコード

スライスの場合

スライスの場合
package main
import "fmt"

func main() {
    s := []int{2, 4, 6, 8}
    for _, v := range s {
        fmt.Println(v)
    }
}
スライスの場合 実行結果
2
4
6
8

マップの場合

マップの場合
package main
import "fmt"

func main() {
    m := map[string]int{"maeda":165, "yamada":175, "suzuki":180}
    for _, v := range m {
        fmt.Println(v)
    }
}
マップの場合 実行結果
165
175
180

ブランク修飾子の書き方

変数名のところに_このように、アンダースコアを書くだけです。
こうすることで、キーやインデックスに入れられる予定だった値を、処理の中で使わなくても、怒られません。

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

[Go]sliceやmapで、要素の数だけループさせたいときの、len関数やrangeの使い方(サンプル集)

1.slice × len関数 でのループ処理

1.sliceをlen関数で、要素の数だけループ
package main
import "fmt"

func main() {
    s := []int{2, 4, 6, 8}
    for i := 0; i < len(s); i++{
        fmt.Println(i,s[i])
    }
}
1.の実行結果
0 2
1 4
2 6
3 8

2.map × len関数 でのループ処理

不可
※ mapだと、実行の度に出力される要素の順番がランダム化されるので、そもそもキーが指定できない。

3.slice × range でのループ処理

3.sliceをrangeで、要素の数だけループ
package main
import "fmt"

func main() {
    s := []int{2, 4, 6, 8}
    for i ,v := range s {
        fmt.Println(i, v)
    }
}
3.の実行結果
0 2
1 4
2 6
3 8

4.map × range でのループ処理

4.mapをrangeで、要素の数だけループ
package main
import "fmt"

func main() {
    m := map[string]int{"maeda":165, "yamada":175, "suzuki":180}
    for k, v := range m {
        fmt.Println(k, v)
    }
}
4.の実行結果
maeda 165
yamada 175
suzuki 180

結論

  • mapでは、len関数が使えない。
    • そのため、mapでの場合はrange一択になる。
  • sliceでは、len関数もrangeも両方使える。
  • rangeはmapでもsliceでも、要素数ループできる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Go]スライスやマップで、要素の数だけループさせたいときの、len関数やrangeの使い方(サンプル集)

1.スライス × len関数 でのループ処理

1.スライスをlen関数で、要素の数だけループ
package main
import "fmt"

func main() {
    s := []int{2, 4, 6, 8}
    for i := 0; i < len(s); i++{
        fmt.Println(i,s[i])
    }
}
1.の実行結果
0 2
1 4
2 6
3 8

2.マップ × len関数 でのループ処理 (※不可!!)

不可
mapだと、実行の度に出力される要素の順番がランダム化される。(v1.11以下の場合。)
つまり、そもそもキーが指定できない。(または理想的ではない。)

ちなみに、len関数で要素数を取得するところまでは可能です↓

2.len関数で要素数ループはできないが、要素数を取得することは可能
package main
import "fmt"

func main() {
    m := map[string]int{"maeda":165, "yamada":175, "suzuki":180}
    fmt.Println(len(m))  // => 3
}

3.スライス × range でのループ処理

3.スライスをrangeで、要素の数だけループ
package main
import "fmt"

func main() {
    s := []int{2, 4, 6, 8}
    for i ,v := range s {
        fmt.Println(i, v)
    }
}
3.の実行結果
0 2
1 4
2 6
3 8

4.マップ × range でのループ処理

4.マップをrangeで、要素の数だけループ
package main
import "fmt"

func main() {
    m := map[string]int{"maeda":165, "yamada":175, "suzuki":180}
    for k, v := range m {
        fmt.Println(k, v)
    }
}
4.の実行結果
maeda 165
yamada 175
suzuki 180

結論

  • マップでは、len関数が使えない。
    • そのため、マップでの場合はrange一択になる。
  • スライスでは、len関数もrangeも両方使える。
  • rangeはマップでもスライスでも、要素数ループができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoのGCなどで見かける3色マーキング

これはあくあたん工房お盆休みアドベントカレンダー2日目の記事です.
本当はGoでDeepLearningする記事を書きたかったのですが,ギリギリまで粘ってもバグが取り切れずとても悲しいので,GoのGC1に関係する話をちょっと書きます.

先日mattnさんがGo ランタイムのデバッグをサポートする環境変数という記事を公開されました.
最近趣味でGCを勉強し始めた身としては,runtimeのコードを読む際にとてもためになる記事で大変ありがたいです.

Concurrent GC

昔話にはなりますが,Goは1.5にて並行GCを導入しました2.それまでは停止形GC(Mark&SweepGC)を採用しており,ヒープのサイズによってはMarkフェーズのたびに秒単位でプログラムが止まるということがあったらしいです34.
この頃Goを触っていた方はどのように対処されていたのでしょうか.気になります.

3色マーキング

Goは並行GCということもあり,3色マーキング(Tri-color Marking)という言葉をよく見かけます.
これは実際にオブジェクトを3色に塗っているわけではなく,マーク処理における3種類のオブジェクトの状態に便宜上つぎのように色を付けて呼んでいます.

  • 白: マークされていないオブジェクト
  • 灰色: 探索候補に入っているオブジェクト
  • 黒: 探索済みのオブジェクト

白と黒は従来のMark&SweepGCと同様,オブジェクトのヘッダ(Goでは別に確保したbitmap)にフラグを建てることで表現します.
では灰色の「探索候補」とは何でしょう?

並行GCではマークフェーズとミューテータによるオブジェクトの操作が交互に行われるため,前回のマークフェーズにどこまで探索したのかを記録しておかなければなりません.
そこで,Goではルートから直接オブジェクトをマークしていくのではなく,一度探索対象のオブジェクトをキューに詰めるという作業を挟みます.このときオブジェクトは灰色の状態になります.
そして,キューからオブジェクトを取り出し,マークをした(黒に塗った)あと,そのオブジェクトがさらに参照しているオブジェクトをキューに詰めます.

しかし,ミューテータによって参照関係が変えられたり,新しいオブジェクトが追加されたりした場合はどうなるでしょうか.

スクリーンショット 2019-08-11 15.29.09.png

コレクタはミューテータによって参照構造が書き換わったとしてもルートから辿れるオブジェクトすべてを黒にしなければなりません.
このような事故を防ぐために,ミューテータ自身も新しくオブジェクトを確保した場合はすぐに黒に塗ったり,参照関係を変更したときは灰色オブジェクトから到達可能な状況になくなるかどうかを判断してそのオブジェクトをキューに詰めたりと,コレクタが次のマークフェーズで生存オブジェクトを見失わないようにします(WriteBarrier)5

このようにしてコレクタは見落としの無いようにオブジェクトをマークしていき,キューが空になるか,一定回のマークフェーズの後,ガッとすべてをマークしてスイープ側に処理を譲ります.

最後に

Goのruntimeは一部アセンブリもありますがほとんどがGoで書かれていたり,コメントがしっかり書いてあったりして,比較的読みやすいように思えたのですが,そもそも概念が難しいのでなかなか理解が進みません.
間違いがある場合はぜひご指摘よろしくお願い致します.

参考文献

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

Wifi打刻の話

ググると結構他の会社さんもやってたりするのですが、今働いている会社でもやってみたいなーと思ったので初期段階の半年前ぐらいから導入してみましたというお話。

Wifi打刻とは

個人のスマホなどが会社の Wifi に接続されたことを検知して打刻として記録します。打刻忘れで注意したり修正・承認したりとかそういうことに使う非生産的な時間と精神の消耗を抑えられるのが良いです(そもそも忘れんなというのはごもっともなんですが…)。

基本的な仕組みは、 ARP と呼ばれるプロトコルを利用して Wifi に繋がっている Mac アドレスを取得し、事前登録した名簿と突合させます。

実装

ARP をスキャンする部分とそれを集計して表示する部分に分かれて 2 人で本業の片手間に作りました。

ARP Scan

TCP/IP と比べるとそれほど有名ではないですが、ルーターが直接繋がった機器と通信するためにいつもお世話になっているプロトコルです (RFC 826)。「この IP 誰?」「ワイやで (MAC アドレスを返す)」という感じです。

シェルスクリプトで作ったりすると簡単ですが、人数によっては細かい調整が必要かなとか Windows でも動かしたいとかあると思い Go で Daemon として実装しました。 github.com/google/gopacket を利用して、 Scan 結果を Web API に投げます。

コマンドで実装する方法と比べて良いのは、 ARP を常に Read したままにできることと Write の頻度を調整できるあたりです。

examples/arpscanreadARP を継続したまま、適当な頻度で writeARP を呼び出します。このとき、おそらく writeARP の頻度が高すぎると、スマホ側の応答回数も増えて電池の消費やネットワークの品質に関わってくるはずです。ある程度調整した結果、皆が出社してくる辺りだけ 10 秒間隔にして、それ以外は 1 分ごとにしました。

ただ、 /16より大きいネットワークは制限されているので、ここだけは組織が大きくなってきたときにちょっと注意が必要です。そもそもそんなサブネットに皆が直接つなぐ運用するのかという話ではありますが。

UI/Chat bot

こちらはもう一人別の方の成果なのですが、 Firebase を GSuite のドメインで絞って ([Firestore] Google認証で特定のドメインのユーザーだけアクセスを許可する) 作っています。

スキャン結果がアップロードされるとすぐに画面が更新されるので、確認するともう打刻されてる!って感じがとても気持ち良いです。

Chat bot は Scan されなかった通知や直行・直帰などで関わってきます。こちらも内輪ネタを軽く含みつつ親しみやすいように作られていて快適です。

運用

共有の端末にスキャンする Daemon を仕込んで動かしています。

複数拠点対応

ローミングできるように SSID や暗号化方式等を合わせて Wifi を組むことで、普段とは別な拠点に出社した場合でも自動で Wifi に繋がって打刻までされるという状況が出来上がりました。

Daemon の配置も Go で作るとバイナリを配置するだけなので楽です。 また、(結局はやらなかったのですが) Npcap を使えばライセンスの制限内なら Windows 機でもいけるようです。

サポート

導入の段階では何度か人事・労務の方々と調整して疑問を解消したり小さい改善をしたりしてしていました。特に Mac アドレスの表示方法は iPhone, Android 両方の画像を用意するのが良いと思います。また、システムに関わる質問等は Slack で勤怠関連のチャンネルと混ざると苦しいので、 Wifi 打刻システム専用のチャンネルに分ける方が良いです(といっても最近は 1 ヶ月に 1 件あるか無いかぐらいですが)。

発生したトラブル

50 人程度を超えたあたりでルーターの限界が来るようになって、スキャンしている端末の方が Wifi から切断されるようになってしまいました。早めに来た方に Wifi を繋ぎ直してもらって回避しつつ、結局はネットワークの増強で解決したのですが、本当はスキャンする側で一旦 SQLite とかで保存して繋がったら同期するとかがスマートなんだと思います。

他社さんの事例

まとめ

好評いただいているようで何よりです

ビットキーでは独自の打刻方法を導入しているんですけど、これはあるエンジニアが構想含め0から作っているものなんです。今では日々の勤怠において欠かせないものになっています。こういった開発は事業にダイレクトに影響する訳ではないですしもちろん売上に貢献するということもないですが、会社として時間を投資することを認めているし重んじているんです。テクノロジードリブンな文化がエンジニアとしては素直に嬉しいですし面白いですね。

  • 打刻そのものの手間はもちろん、打刻忘れから発生する色々な消耗が減ります
  • Go で ARP を扱うと、結構細かい調整までできます
  • ローミングと合わせて構築すると、拠点が増えても快適です
  • ルーターの負荷にはお気をつけください
  • 人事・労務の方とも連携しましょう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者がGo言語のImportで上手くいかなくて5時間ぐらい調べた

初めに

未経験の初学者ですが、VScodeでGoを学習中にimportが上手くいかず多分5~7時間調べました。
結果「5時間も調べるとか時間かかり過ぎだろ、30分で十分」と思ったので、
同じような初学者がこんな所で時間を食わぬようまとめてみます。当たり前の事書きます。

Github上でのimportについては触れません
学習目的のため業務の場合については触れてません

環境

macOS Mojave: 10.14.5
Go version: 1.12.7
VScode バージョン: 1.37.0

詰まった時の状況

  • File名もPakeage名も間違ってない
  • VScodeの設定もおかしいとは思えない
  • GOPATHも間違ってない
  • File名/Pakege名でimport書いてみるけどエラー

やった事

  • Goの再インストール
  • 公式などを読みながらひとつひとつ確認する
  • 仮説を思いついたら試す
  • 後はひたすら調べる

結論と解決方法

先に結論だけいうとGoのimport補完機能を味方につけるのが良いのではないか思います。

それが出来なくって困ってるんだよ という方は多分私と同じかもしれません。

私も何回試してみてもならなくて調べて全部やり直したら補完してくれて「そういう事かーい」ってなった人間です。
私の場合はボケボケの理解しかせずただ調べてそれっぽい形にしていたのが全ての原因でした。
設定さえおかしくなければimport補完が味方してくれるのではないでしょうか。

参考記事

Go再インストール 〜再確認〜

そもそもGoをインストールした時点でどうなってたんだ?と思ったので初めからやり直しました。
コマンドを叩いて確認

/usr/local/go
/
├ usr/
 ├ local/
  ├ go/
   ├ この先は調べてないのでわからないですがGoがGoであるための場所のようです
Users/username/go
Users/
┣ username/
  ├ Go/
   ├ bin
   ├ pkg
   ├ src

これがインストール済んだ時点で作成されているディレクトリ(/usr/local/go,Users/username/go)みたいです。

次にGOPATHGOROOTを確認します。 コマンドgo envで一覧を表示してみると
GOPATH="/Users/username/go"
GOROOT="/usr/local/go" と自動で設定されております。
どんな意味があるのかと思ったので次に参考記事を見てみます。

調べてみる

Go公式サイトより一部抜粋(google翻訳使いました...)

概念

Goプログラマは通常、すべてのGoコードを単一のワークスペースに保持します。

ワークスペース
ワークスペースは、ルートに2つのディレクトリがあるディレクトリ階層です。

  • src Goソースファイルが含まれている
  • bin 実行可能なコマンドが含まれています

GOPATH環境変数

GOPATH環境変数は、ワークスペースの場所を指定します
別の場所で作業する場合は 、そのディレクトリへのパスをGOPATH設定する必要があります

公式のリンクにあるGitHubより一部抜粋(google翻訳使いました...)

GOPATH

Introduction

GOPATH環境変数は、Goプロジェクトとそのバイナリのソースを含む$ GOROOT以外のディレクトリを指定するために使用されます。

Tips and tricks

単一のGOPATHを使用する
GOPATHがディレクトリのリストである場合でも、一般に、マシン上のすべてのGoコードに単一のGOPATHを使用すれば十分です。>>「go get」で取得したすべてのパッケージには一意のURL(したがってディスク上の一意のパス)があるため、Goツールでビルド>>する場合、複数のGOPATHを持つ必要はほとんどありません。

Everything you need to know about Packages in Go より

Goは最初にディレクトリ内のパッケージディレクトリを検索し、GOROOT/srcが見つからない場合はGOPATH/srcを探します

また、Getting started with Go を読んでみると
ワークスペースという考え方、仕組みがGoの管理を助けているようです。

という事で

  • Goの作業はワークスペース内で行うのが基本とされている
  • ワークスペース内のどこで行えばいいいかといえば最初に作られたbin,pkg,srcの中のsrc
  • GOPATH, GOROOTは基本いじらないで大丈夫
  • GOPATHGoのワークスペースの場所を指定している
  • 基本上記で十分みたいですが、状況に応じてPATH設定を追加する という流れになるのではないかと考えます

ずれた考えかも知れませんが、importの自動補完を味方につけるには、このワークスペースとGOPATHの関係をさらっとでも理解しないとimportが敵になりそうですね。

Users/
┣ username/
  ├ Go/  
   ├ bin
   ├ pkg
   ├ src <- ここでコード作成などを行う

という事で試す

そんな事も知らずに雰囲気でやってた私のGoの学習用フォルダーは

Users/
┣ username/
  ├ Go/  <-ワークスペース
   ├ bin
   ├ pkg
   ├ src
   ├ qita <- やっちまってる
      ├ main.go
      ├ jojo
        ├ jojo.go 
      ├ dio
        ├ dio.go

これだと

main.go
import (
    "fmt"


    // GOPATHは(Users/username/go)

    // エラー(これでいいハズなのに) 
    "qita/jojo"
    // cannot import absolute path とエラー(GOPATH/qita/jojoのつもり)
    "/qita/jojo"
    // 相対パスだと認識される。けど自動補完はされません。相対パスは推奨されてないようです。
    "./jojo"
)
func main() {
    fmt.Println("Jojo:" + jojo.Voice)
}

なので

Users/
┣ username/
  ├ Go/  <-ワークスペース
   ├ bin
   ├ pkg
   ├ src
      ├ qita <- src下に移動(GoはGOPTAH/srcを探すため)
        ├ main.go
        ├ jojo
          ├ jojo.go 
          ├jojo2    <-試しに追加 
          ├jojo2.go <-試しに追加
        ├ dio
          ├ dio.go

これで試す

main.go
package main

import (
    "fmt"

    // GOPATHは(Users/username/go)

    // 自動で補完されました 成功
    "qita/jojo"

    // cannot import absolute path とエラー (前回と変わらず)
    "/qita/jojo"

    // 試しに qita/jojo/jojo2 というのも作りましたが自動補完してくれました 成功
    "qita/jojo/jojo2"

    // 今度は相対パスだとエラー(今度はエラー)(GOPATHかGOROOTの中じゃないと探せないよとエラー)
    "./jojo"
)

func main() {
    fmt.Println("Jojo:" + jojo.Voice) //出力 Jojo: Oraa!

    fmt.Println("Jojo2:" + jojo2.Voice) //出力 Jojo2: Mudaa!
}

とりあえずはimport補完が効くようようになりました

結局importは...

相対パスの件など調べてみましたがちゃんとはわからなかったです。推奨はされてないようです。
GitHubの場合もありますし、設定できても自動補完出来たりできなかったりする時もあるようです。

ただワークスペースGOPATHに気をつけて、src下でコードを作成しているぶんには、
通常のimportの書き方で済むハズなのでこれでエラーが起こる可能性は減ったと思います。

結局importはちょっとややこしい

後は都度調べて行きましょう!

参考になれば幸いです

至らない所もあるかと思いますが、間違っているぞ!などあればご指摘のほどよろしくお願いします。
またガイドラインに反してる、反してはないけどよろしくない部分がありましたらお手数ですがご指摘のほどお願いいたします。



蛇足

初めての投稿でしたがこの記事書くのに多分10時間くらいかかったと思います。(白目
私と同じような人にとってわかりやすければ何よりです。

なんで書こうと思ったかというと
高校時代に自作PCの本きっかけでコンピューターに触れましたが(金なくて作れませんでした)

その時にlinuxを知り、オープンソースであると、有志が作ってんだぞと、無料だぞと、オープンソース文化を知った瞬間でした。
PC自作の時も今プログラミングを勉強してる時もオープンソース文化に助けてもらってるなぁと感じたんですよね
この先続くのか知らないですけど、ましてや未経験ですけど、
でも書いておこうと。名無しの有象無象になって貢献しよう思いました。

書くのも、伝えるのも違った面白さがありますが、少なくとも楽では無いですね(汗
改めてオープンソース文化に寄与されてる方々にこの場を借りて感謝申し上げます

もっと質の良いものが書けるよう精進致します。 

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