20190413のGoに関する記事は11件です。

privateの構造体をjson.Marshalした時に空でreturnされる

概要

同package内でのみアクセスできる(private)構造体を json.Marshal した時に戻り値が空になる。

事象

privateの構造体を用意する

pkg/User.go
package pkg

type User struct {
    id    string
    name  string
    token string
}

func (u User) GetId() string {
    return u.id
}

func (u User) GetName() string {
    return u.name
}

func (u User) GetToken() string {
    return u.token
}

func New(id, name, token string) *User {
    return &User{id, name, token}
}

対象の構造体(User.go)をjson.Marshalする。

test.go
package main

import (
    "encoding/json"
    "fmt"
    "work/pkg"
)

func main() {
    user := pkg.New("0001", "test-user", "abcdefg")
    fmt.Println(user)

    userjson, err := json.Marshal(&user)
    if err != nil {
        fmt.Errorf("error marshal:", err)
    }
    fmt.Println(string(userjson))
}

実行結果は以下の通り、空で出力される

&{0001 test-user abcdefg}
{}

なんとなく、察してはいたけれどjson.Marshalはprivateな値は対象にしてくれない模様…
先頭文字を直してpublicにしようか迷ったがオブジェクト指向っぽく作りたかったため、今回はMarshalJSONで誤魔化す事にした。

pkg/User.go
package pkg

import "encoding/json"

type User struct {
    id    string
    name  string
    token string
}

func (u User) GetId() string {
    return u.id
}

func (u User) GetName() string {
    return u.name
}

func (u User) GetToken() string {
    return u.token
}

func New(id, name, token string) *User {
    return &User{id, name, token}
}

/* json.Marshalする時に各項目をGetする処理を追加 */
func (u *User) MarshalJSON() ([]byte, error) {
    v, err := json.Marshal(&struct {
        Id    string
        Name  string
        Token string
    }{
        Id:    u.GetId(),
        Name:  u.GetName(),
        Token: u.GetToken(),
    })
    return v, err
}

再度、実行する

&{0001 test-user abcdefg}
{"Id":"0001","Name":"test-user","Token":"abcdefg"}

所感

値にjsonパラメータを設定する際にprivateでも取得できる様な設定方法があるのかは随時調査して行く予定です。
jsonに変換する構造体自体をpublicで作るべきなのかは、正直分からず…
もっといい実装方法をご存知の方がいらっしゃいましたら教えて頂けると幸いです。

そもそも、Goではパッケージ外部からアクセスできない構造体をprivateと読んでいいのかも謎です。
(java脳ですみません。。。)

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

Goで世界にこんにちはする

はじめに

気になってたGo言語で世界に挨拶してみる
以下公式サイトのGetting Startedに沿って進めた。
Getting Started

環境

macOS Mojave(v10.14.4)
homebrew v2.1.0
go v1.12.3

インストール

まずはGoのインストールから
自分はHomebrewでインストールした。

$ brew install go

インストール成功したら、Goのワークスペースを作成する

$ cd $HOME && mkdir go

※Goはデフォルトワークスペースを$HOME/goとしているので、他のディレクトリをワークスペースにしたい場合は以下のようにGOPATHというパスを設定する必要がある
くれぐれもsource $HOME/.bash_profileを忘れずに

.bash_profile
export GOPATH=$HOME/World/Japan/qiita/go

【参考】
SettingGOPATH

世界に挨拶してみる

ワークスペースを作成したらソース用ディレクトリを用意する。

$ cd $HOME/go # もしくは $ cd $GOPATH
mkdir -p src/hello

ソースコードは以下を作成する。

hello.go
package main

import "fmt"

func main() {
  fmt.Printf("こんにちは, 世界\n")
}

packageは名前空間1を示し、importで依存パッケージを取り込むための指定を行っている。
そして、funcで関数の宣言を行い、fmt.Printfでコンソール出力を行うようだ。

ちなみに、Go言語はpackage mainのmain関数をエントリーポイント2として扱うので、名前空間と関数宣言がそれぞれmainになっている。

実行するときはgo run ファイル名
なお、ビルドして実行ファイルを作成したい場合は対象のファイル名を含んだディレクトリ内でgo buildを実行する。

$ go run hello.go
こんにちは, 世界

世界に挨拶できたらこっちのもんだ
これで僕も君も今日からGo使い。

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

Goで世界にこんにちはする HelloWorld

はじめに

気になってたGo言語で世界に挨拶してみる
以下公式サイトのGetting Startedに沿って進めた。
Getting Started

環境

macOS Mojave(v10.14.4)
homebrew v2.1.0
go v1.12.3

インストール

まずはGoのインストールから
自分はHomebrewでインストールした。

$ brew install go

インストール成功したら、Goのワークスペースを作成する

$ cd $HOME && mkdir go

※Goはデフォルトワークスペースを$HOME/goとしているので、他のディレクトリをワークスペースにしたい場合は以下のようにGOPATHというパスを設定する必要がある
くれぐれもsource $HOME/.bash_profileを忘れずに

.bash_profile
export GOPATH=$HOME/World/Japan/qiita/go

【参考】
SettingGOPATH

世界に挨拶してみる

ワークスペースを作成したらソース用ディレクトリを用意する。

$ cd $HOME/go # もしくは $ cd $GOPATH
mkdir -p src/hello

ソースコードは以下を作成する。

hello.go
package main

import "fmt"

func main() {
  fmt.Printf("こんにちは, 世界\n")
}

packageは名前空間1を示し、importで依存パッケージを取り込むための指定を行っている。
そして、funcで関数の宣言を行い、fmt.Printfでコンソール出力を行うようだ。

ちなみに、Go言語はpackage mainのmain関数をエントリーポイント2として扱うので、名前空間と関数宣言がそれぞれmainになっている。

実行するときはgo run ファイル名
なお、ビルドして実行ファイルを作成したい場合は対象のファイル名を含んだディレクトリ内でgo buildを実行する。

$ go run hello.go
こんにちは, 世界

世界に挨拶できたらこっちのもんだ
これで僕も君も今日からGo使い。

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

マナー良いスクレイピング開発で心理的安全性を確保する

なにこれ

あれ?私、開発中のマナー悪くない?

スクレイピングやクローラーのプログラムを書く時に、毎回対象のWebサイトにアクセスしていませんか?
これから有り難く情報を拝借するのに、開発中の大量なアクセスでKPIや分析指標に影響を与えちゃってやたら迷惑かも?
単純に自分がハマっているだけなのに...

そんな心理的な負い目。少なからずあると思います。

...

...

そうだ、Webページをローカルに保存して開発した方が良さそう!

※完全に思いつきで試した開発方法なので全てのケースで適応はできないかもしれませんので悪しからず

この記事で得られるもの

  • Webページをローカルに保存してスクレイピングプログラムが書けるようになる
  • Goのテストがちょっとだけ書けるようになる
  • ある程度のマナーを身につけられる
  • 負い目が減る開発方法を身につけられる

どうやるの?

開発の手順としては以下の通り。

  1. スクレイピング対象のWebページをローカルに完全保存する
  2. ローカルに完全保存したWebページを対象に動作確認を行うテストを書く
  3. スクレイピングの処理を書く
  4. 途中で動作確認を行う場合は、テストを実行することでローカルに完全保存したWebページに向けて検証する
  5. テストで意図通りに動作したら、本番環境に向けてアクセスする。

サンプルを見ながら雰囲気を掴む

以前公開した 阿部寛をWebDriverでいじくるを例にします。GoでWebDriverを使う記事です。メインの処理なんかはこちらを読んでいただけると。

1. スクレイピング対象のWebページをローカルに完全保存する

これはお使いのブラウザから簡単にできますね。割愛します。

2. ローカルに完全保存したWebページを対象に動作確認を行うテストを書く

今回のプログラムではWebDriverを使って開発をするのですが、ローカルのHTMLにもバッチリアクセスできます。

まずはテストコードを書きます。テストコードの中でローカルに保存したWebページ(HTML)に対してアクセスするように定義します。準備はこれだけです。

main_test.go
package main

import (
    "testing"
)

func TestMain(t *testing.T) {
    // ローカルに保存したHTMLに向けてテストする
    pageURL = "file:///abe_hiroshi/DammyHtml/阿部寛のホームページ.html"
    screenShotFileName = "test_screen_shot.jpg"

    // 実際の処理を実行
    main()
}

3. スクレイピングの処理を書く

本処理を記載します。
ここで、テスト時に用いるプロパティについては同パッケージ内からはアクセス可能とするため、グローバル宣言を行いました。こうする事で開発時と運用時でアクセス先を変更する事が可能です。

簡単にトップページにアクセスするところまでを記載します。

テスト時にはテストコードにて上書きされた、pageURLとscreenShotFileNameが利用されます。

main.go
package main

import (
    "log"

    "github.com/sclevine/agouti"
)

// テストコードからもパラメタを変更できる様にパッケージ内ではどこからでも呼べる様にする
var pageURL = "http://abehiroshi.la.coocan.jp/"
var screenShotFileName = "abe_hiroshi.jpg"

func main() {
    driver := agouti.ChromeDriver()
    if err := driver.Start(); err != nil {
        log.Fatalf("driverの起動に失敗しました : %v", err)
    }
    defer driver.Stop()

    page, err := driver.NewPage(agouti.Browser("chrome"))
    if err != nil {
        log.Fatalf("セッション作成に失敗しました : %v", err)
    }

    // 阿部寛のウェブページに遷移する
    if err := page.Navigate(pageURL); err != nil {
        log.Fatalf("アクセスできませんでした : %v", err)
    }
}

4. 途中で動作確認を行う場合は、テストを実行することでローカルに完全保存したWebページに向けて検証する

こちらは上記で説明したように、テストから処理を実行すればローカルに向けてアクセスされるので、先方のサイトにアクセスされません。大量のアクセスも気にせず、思う存分開発もできます?

5. テストで意図通りに動作したら、本番環境に向けてアクセスする。

これはそのままですね。処理が作成完了したらスクレイピング先の本番環境にアクセスして情報収集しましょう。

おわりに

こうやれば、多少でもマナー良く開発できるかも?と思って試してみました。
今回のボリュームであれば、ローカル保存でも十分に開発できましたが、もっとボリュームが大きくなると問題が出てくるかもしれません。

しかし、アクセスでスクレイピング先に迷惑をかけないことから、色々試したりする事に負い目を感じずに開発する事できました!私のようにスクレイピングに強くないエンジニアにはハマったポイントで何回も試すにはもってこいですね?

以上です。マナー良く、自分も安心して開発をできる一助になったら嬉しいです!

サンプルのブランチあげておきます。
abe_hiroshi


extra 他のメリット

ここからはまだ試していないので効果がわかっていませんが、想定するメリットを記載します。

前提として、大手企業のシステムに送客するプログラムをWebDriverを用いて作成するケースを想定します。
(案外APIを建ててくれないとか、新規事業のため準備に時間が掛かるため、このようなケースは少なくないのかもしれません。先方にテスト環境が存在しない場合もあります。)

WebDriverを用いた処理のテストを簡単に書ける

WebDriverを用いた連携プログラムのテストを書くのに、外部システムの状態に依存されるテストは書きたくないし、WebDriverのモックを準備するのも大変。

しかし、他システムのWebページを完全保存しておけば、本処理についてはほぼ変更をかけずにテストを書けます。
そして、E2Eテストの準備ができずにテストを諦め、テストカバレッジ0%で安心できないプログラムである状態からある程度のテストカバレッジを担保できるようになります。質の良いテストとは言い難いですが、何もテストが存在しないよりは良いのでは?と思っています。

スクレイピング先の改修箇所がわかりやすい

Webページを完全保存する事で、スクレイピング先の改修があった場合に変更箇所をdiffですぐに特定できるかもしれません。

先方システムの改修を事前告知されず、急にエラーが多発した事が何回もあります。
先方システムの改修のため発生しているのか、自社プロダクトの改修によって引き起こしたバグなのかが調査するのも時間が掛かるのですが、Webページを完全保存していれば、diffにより改修が発生したのかどうかを判別する事が容易になると考えられます。
それにより調査の初速が上がり、対応完了までの時間が短くなると考えられます。

このへんは効果が見えたらまた書くかも。

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

A Tour of Go の sync.Mutex サンプルは絶対に壊れない?

みんなだいすきTour of Go。Concurrencyの章に、よくある並行カウントアップのサンプルもあります。
https://tour.golang.org/concurrency/9
どの言語でもよくある Lock/Unlock の話。「ロックしないとカウンタが壊れて変な値になっちゃうね!だからロックするよ!ほら期待通りの値になっただろう?」ってやつ。

このページの説明、最初から正しく Lock / Unlock されたバージョンが提示されていて、実行すれば当然成功します。そうなるとつい壊して失敗したバージョンを見てみたくなるもの。ですが、Lock / Unlock を全部削除してみても成功します。何度実行してもきっちり1000とprintされます。どういうことだよ…。goroutineの中身が一瞬で終わってしまうのが原因か?と中で10000回ループしてみても同じ。

どうやら、2019年4月現在ではtour.golang.orgの実行環境ではプロセッサ数が1のため、goroutineをいくら生成しようが同時には実行されないようです。fmt.Println(runtime.NumCPU()) を入れてみたら 1 と出ました。

手元のマルチプロセッサ環境で go get golang.org/x/tour を用いてローカル実行した場合、Lock / Unlock を外したら期待通り失敗してくれました。 ただ fatal error: concurrent map writes で終わってしまって、結局壊れたカウンタを見ることはできません。こういうのは、学習過程では壊れたものを見せてくれたほうがわかりやすいです。普通に int のカウンタにしなかった理由はなんだろう。

シングルプロセッサでも壊れるはずなのでは?

OSのスケジューラがプロセスに対して行うのと同様に、goroutineでも無慈悲なコンテキストスイッチが起きうるのであれば、カウンタの読み→書きの隙間にスイッチされて死ぬパターンがあるはずです。ですが、ユーザー空間で自前スイッチングを行う場合は中身がわかるので、無駄の少ないタイミングを狙ってスイッチングをしてくれるようです。
https://qiita.com/niconegoto/items/3952d3c53d00fccc363b より基本的な切り替えタイミングは:

アンバッファなチャネルへの読み書きが行われる
システムコールが呼ばれる
メモリの割り当てが行われる
time.Sleep()が呼ばれる
runtime.Gosched()が呼ばれる

なので、ループでインクリメントするだけのgoroutineでは…

func (c *SafeCounter) Inc(key string) {
    for i := 0; i < 10000; i++ {
        c.v[key]++
    }
    fmt.Printf("%d\n", c.v[key])
}
// 以下呼び出し側
for i := 0; i < 100; i++ {
    go c.Inc("somekey")
}

などとすると…

10000
20000
30000
40000
50000
(以下略)

生成した順に、直列に走り切っています。

まとめると、「Web環境ではプロセッサ数が1で並列実行されない」かつ「このgoroutineでは内部で再スケジューリングも起きない」という2点により、tour.golang.org環境でカウンタが壊れることはないようです。

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

競プロのテストを「go test」でやりたかったので先人の知恵をお借りした

先人の知恵

知恵お借りします。ありがとうございます。
競プロのテストを「go test」でやりたかったので作った

いきさつ

Go習得に向けてPaizaでもやろうと思ったが、元記事と同じように標準入出力のテストを簡単にやりたかった。

Paizaの場合、複数行の入力と複数行の出力の問題があるので、それに対応するように自分向けに修正をしてみた。

というお話。

リポジトリ

下記修正したのもの
trewa-nek9585/GolangSkeletonForSpeedrun

イメージ

screenshot.png

修正点

複数行対応

テストケース用の構造体のプロパティ型が入力、出力想定ともに 一行だけ受け取る想定になっていたので、 複数行受け取れるように string から []strings に変更しました。

それに伴い getLine も修正。複数引数の場合、getLine にて、 os.Args[1] で一番最初の引数を受け取って、受け取った分は引数から消すという処理にすることで複数引数を受け取れるようにしました。

何故最後なのかというと、go testの後に続くフラグの数がわからないから。入力が一行だけなのであれば、コレで十分です。(つまり複数行には非対応)

後ろのフラグ消しちゃって大丈夫かな?という懸念はあったものの、特に問題なさそうだったので、毎回テストケース毎に os.Args = nil で初期化&テストケース毎の想定結果(複数行)を突っ込むことにしました。

os.Args[0]には引数の長さを突っ込んでますが、これは引数から入力を取得するのか、標準入力から取得するのかが分岐できなくなってしまうので、便宜上突っ込んでるだけで別にあんまり意味はないです。

os.Args = append(os.Args,strconv.Itoa(len(tc.args)))
for _, arg := range tc.args {
    os.Args = append(os.Args, arg)
}

出力結果に改行コードを含むか否か

paizaの場合、解答の出力に改行を含むことが前提になっているので、flagパッケージを利用して、フラグ newline がある場合は出力結果に改行を含むのが正解となるようにしました。

終わり

そんな感じで入出力の複数行対応、正解の改行対応を行って、paizaがちょっと捗るようになりましたとさ。

以上、お目通しどうもありがとうございました。

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

Homebrewに自作コマンドを登録して使えるようにする【Go編】

Goの勉強も兼ねて自作コマンドを作ろうと思った。
その前に作ったコマンドを公開して誰でもインストールできるようにしたかったので、その備忘録。

前提条件

下記の環境が整っていることとする。

  • homebrew (v2.1.0)
  • go (v1.12.3)
  • githubのアカウント

大まかな流れ

  1. Goで実行ファイルを作成する
  2. Githubにソース管理用のリポジトリを作る
  3. リリース作業をする
  4. Githubにインストール用のリポジトリを作る
  5. Formulaファイルを作成する
  6. brew tap / brew installでコマンドをインストールする
  7. コマンド実行

やっていき

Goで実行ファイルを作成する

とりあえず動けば何でも良いので、実行するとhello, worldと出力するhelloコマンドを作る。
プログラムは以下。

package main

import "fmt"

func main() {
    fmt.Println("hello, world")
}

go buildでビルドして実行ファイルを作成する。
実行ファイル名は実行時に指定するコマンド名と同じにする。

go build -o hello

この時、後で使うのでsha256のハッシュ値を取得しておく。

shasum -a 256 hello
# 2dbc3742be9c77c4e3bb30c99790699b7d9c3cfa7895ecc9c1e1663f9507fddd ←こんなのが出力される

Githubにソース管理用のリポジトリを作る

Githubにリポジトリを作成し、先程作ったファイルをpushする。この時、実行ファイルはpushしなくてよい
リポジトリ名は何でも良いが、ここではコマンド名と同じで一旦helloとする。こんな感じで。

https://github.com/hunzy/hello

リリース作業をする

作った実行ファイルをリリースする作業をする。
下図のreleaseに行くと「Create a new release」というボタンがあるのでそれを押すとリリース情報の入力画面に行く。

hello 2019-04-13 14-17-51.png

タグバージョンやタイトルなどの情報は、一旦適当に入れておく。
大事なのは下図の赤枠の部分で、ここをクリックして先程作った実行ファイルを登録する。

homebrew-hello 2019-04-13 14-24-42.png

終わったら「Publish release」をクリックする。
リリース情報が表示されるので、実行ファイル部分のURLを控えておく(後で使う)。

hello 2019-04-13 14-37-00.png

Githubにインストール用のリポジトリを作る

ソース管理とは別にHomebrewでインストールできるようにするリポジトリを作成する。
このリポジトリの命名ルールは決まっており、必ずhomebrew-${コマンド名}とする。
今回だとhomebrew-helloとなる。こんな感じ。

https://github.com/hunzy/homebrew-hello

Github上から適当に作成したら次に進む。

Formulaファイルを作成する

一番厄介そうな部分。やりたきことは「これこれこういう形式でインストールしますよ」という手順を書いたFormulaファイルを作成し、それを先程作ったインストール用リポジトリに登録すると、あとはHomebrew側がそれを見てよしなにインストール作業をしてくれるという仕組みになってるっぽい。

ということで先程作ったリポジトリにFormulaを置きたい。
リポジトリをクローンする必要があるが、実はこれはbrew tapコマンドでできる。

brew tapを実行すると/usr/local/Homebrew/Library/Taps/${Githubのアカウント名}配下に先程のリポジトリがクローンされている。
そこにFormulaファイルを配置する必要があるが、これもbrew createコマンドで実行できる。
まとめると、下記のコマンドを打ってくれ

brew tap hunzy/hello # アカウント名とコマンド名の部分は適宜読み替える
brew create --tap hunzy/hello

そうするとFormulaファイルの雛形がエディタで開くので、必要な情報を入力する。
下記をコピペして、urlとsha256の値だけ読み替えてもらえれば動くはず。

class Hello < Formula
  desc "コマンドの説明" # ←コマンドに関する説明を記述する
  homepage "https://github.com/hunzy/hello" # ←何でも良いがGithubのリポジトリを書くことにする
  url "https://github.com/hunzy/hello/releases/download/v0.0.1/hello" # ←コピーしておいた実行ファイルのURL
  sha256 "2dbc3742be9c77c4e3bb30c99790699b7d9c3cfa7895ecc9c1e1663f9507fddd" # ←実行ファイルのsha256の値

  def install
    bin.install "hello" # ←実行ファイルを実行Pathに配置するよの意味
  end

  test do
    system "true" # ←本来はここにテストを書くが今回は一旦無視
  end
end

終わったらcommitしてpushし、リポジトリに反映させる。

brew tap / brew install でコマンドをインストールする

一旦tapし直してリポジトリを最新化させる。その後installコマンドを打つことでコマンドが実行できるようになる。
本来ならbrew install ${コマンド名}でインストールできるはずだが、今回作ったhelloコマンドはhomebrewの公式にすでに同じ名前のものが存在しているため、普通にやるとそちらがインストールされてしまう。

その場合はインストール時にどのコマンドをインストールするか直接指定する。
形式は${tap元}/${Formula名}になっている。今回だとhunzy/hello/helloになる(感じ取ってくれ)。

brew untap hunzy/hello
brew tap hunzy/hello
brew install hunzy/hello/hello

コマンド実行

> hello
hello, world

動いた。

まとめ

今回はとりあえず動かすことが目的だったので、実行ファイルの置き方とか、Formulaファイルの書き方とか、あまり正しくはないかもしれない。
自分と周囲の人が気軽に使えるようにする場合の最小構成ということで。

リリース作業を自動化するgoreleaserというものがあるので、最終的にはそれを使うのが良さそう。

参考資料

Goで自作コマンドを作成し、homebrewで公開する
Homebrewで自作のプロジェクト(ライブラリ)を公開する
HomeBrewで自作ツールを配布する
brew tapでGolangの自作アプリケーションを公開する方法
goでcliのコマンドを作ってhomebrewで使えるようにしてみた
brew tapとは

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

Go言語の基本的な使い方

だいたい入門書の3章あたりまでの内容です。
より詳しく知りたいなら本家へ。

コマンド

コンパイル

src/hello/hello.go として。
helloフォルダに入ってコマンド叩く。

go build 

コンパイルと実行。
ファイル名を分けてる場合は、全部書く必要あるっぽい。

go run hello.go
go run hello.go db.go

うっかりハマりやすい初歩的なポイント

  • 変数宣言やインポートしても、使ってないとエラーになる。
  • if文は{}必須。
  • 関数名や変数名、構造体の中身の名前の最初が大文字だとpublic、小文字だとprivateみたいな働き。
  • 改行に意味があるので、変に改行したりしなかったりするとエラーになる。
// これはだめ。
if true print("OK")
// 絶対こう
if true { print("OK") }

// これはだめ。
if true { print("OK") }
else { print("NG") }
// これならいける。
if true { print("OK") } else { print("NG") }

// これはだめ。
fmt.Println(
  "coding",
  "test"
)
// これはいける。
fmt.Println("coding", "test")
// こっちでもいける。最後にカンマ。
fmt.Println(
  "coding",
  "test",
)

宣言

変数

var i int
var i int = 1
i := 1
var a, b, c int
var d, e, f := 1, 2, 3

ポインター

var p *int 
var i := &p 

関数

func nibai(i int) int {
  return i * 2
}
// 関数だって戻せる。
func retFunc() func(int) int {
  // ここは宣言(代入?)されたときのみ。ループとかで使うときに注意。
  a := 1
  return func(i int) int {
    a += i
    return a
  }
}

スライスと配列

// スライス。個数指定なし。
slice := []int{1,2,3}
// 配列。個数指定あり。
array := [3]int{1,2,3}
// 取り出すときは共通。
a := slice[1]
// 範囲指定で取り出す。
b := slice[:2]
c := slice[1:3]
// スライスの追加。
slice = append(slice, 4)

構造体

type Complex struct {
    Re int
    Im int
}

メソッド

//上の構造体に対するメソッド。
func (c Complex) ToString() string {
    return strconv.Itoa(c.Re) + " + " + strconv.Itoa(c.Im) + "i"
}

//使い方。
mycomp := Complex{5, 3}
fmt.Println(mycomp.ToString())

インターフェイス

実装時に引数の型を変えるとオーバーロードができるっぽい。

type MyInterface interface {
    ToString() string 
}

マップ

連想配列とかキーバリューとかの類。
make関数で素を作らないといけないっぽい。

m := make(map[string]string)
m["Hello"] = "World"
fmt.Println(m["Hello"])

if

if num == 100 {
    print("OK")
}

// For文みたいな初期値作成も出来るっぽい。
if num := 100; num >= 100 {
    print("OK")
}

// 分岐。
if num := 105; num > 100 {
    print("Over")
} else if num == 100 {
    print("OK")
} else {
    print("Less")
}

switch

他の言語で言うselect文。
breakがない。caseを2つ付けたいときはカンマ区切り。

switch os {
    case "win":
        fmt.Println("Microsoft")
    case "android":
        fmt.Println("Google")
    case "ios", "mac":
        fmt.Println("Apple")
    default:
        fmt.Printf("Unknown")
}

// 初期値もいける
switch os := "solaris"; os {
    case "ios", "mac":
        fmt.Println("Apple")
    default:
        fmt.Printf("Other")
}

for

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// Whileっぽい使い方。(GoにはWhile文がない)
sum := 1
for sum < 1000 {
    sum += sum
}
fmt.Println(sum)

// 無限ループ
for {
    time.Sleep(100 * time.Millisecond)
    fmt.Println("test")
}

defer

deferの後ろは関数内の最後に実行される。

// 表示は「2, 3, 1」の順番。
defer fmt.Println("1")
fmt.Println("2")
fmt.Println("3")

// deferを重ねるとpush/popで実行される。
// 次の場合は「4, 3, 2, 1」の順番。
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("4")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goplsでnet/http系のパッケージを入れると動かなくなるバグ(Mac)

goplsで快適にプログラムを書いていたんですが、net/http系のライブラリを使うと何故か補完等の全機能が使えなくなるというバグに見舞われて、かなり苦労したのでもし他にもそういう方がいたときのために解決方法等を載せておきます。

どのようなバグか

僕はgolangでweb系のプログラムを書いていたので、gin-gonic/ginのようなライブラリを使っていたのですが、そのライブラリをimportするとgoplsが動かなくなるというバグでした。

原因

様々な場所でログを出力してデバッグしていたところ。
$GOPATH/src/golang.org/x/tools/go/packages/packages.goLoadという関数でエラーが起きていたようでした。

exit status 2: # runtime/cgo","_cgo_export.c:3:10: fatal error: 'stdlib.h' file not found

というエラー文が出てきました。
goplsはcgoを使っているらしくC系の動作がちゃんとしていないと動かないようです。
このエラーについてググるとMacOSのアップデート時にxcodeがstdlib.hを見失ってしまう?ことがあるらしいです。

もし、これと同じエラーかどうかを試したければ、stdlib.hをimportしたC++の簡単なプログラムを書いて、ちゃんと動くかを試してみるといいと思います。

解決方法

僕の場合は以下のようにして、pkgをインストールしたら治りました。

$ open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg

参考: https://stackoverflow.com/questions/51761599/cannot-find-stdio-h

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

オフラインリアルタイムどう書く E32 の実装例(go)

問題の名前 : きれいな四角
問題 : http://nabetani.sakura.ne.jp/hena/orde32rects/
実装リンク集 : https://qiita.com/Nabetani/items/bd16f3fa1d9e4d0d60ae

で。

会場でお見せした go の実装。

実装

実装
package e32

import (
    "errors"
    "fmt"
    "log"
    "sort"
    "strconv"
    "strings"
)

const n = 0x1000
const wh = 36

type rectType struct {
    x, y, r, b int
}

func (r *rectType) size() int {
    return (r.r - r.x) * (r.b - r.y)
}

func mustAtoi(s string) int {
    i, err := strconv.ParseInt(s, 36, 32)
    if err != nil {
        log.Fatalln(err)
    }
    return int(i)
}

func createRect(s string) rectType {
    return rectType{
        x: mustAtoi(s[0:1]),
        y: mustAtoi(s[1:2]),
        r: mustAtoi(s[2:3]),
        b: mustAtoi(s[3:4]),
    }
}

func makeField(rects []rectType) map[int]int {
    f := map[int]int{}
    for ix, r := range rects {
        for y := r.y; y < r.b; y++ {
            for x := r.x; x < r.r; x++ {
                f[y*n+x] += (1 << uint(ix))
            }
        }
    }
    return f
}

func findRight(field map[int]int, x0, y0 int) (int, error) {
    val0 := field[x0+y0*n]
    for x := x0; x < wh+1; x++ {
        if val0 != field[x+y0*n] {
            return x, nil
        }
    }
    return 0, errors.New("no right end found")
}

func findBottom(field map[int]int, x0, y0 int) (int, error) {
    val0 := field[x0+y0*n]
    for y := y0; y < wh+1; y++ {
        if val0 != field[x0+y*n] {
            return y, nil
        }
    }
    return 0, errors.New("no bottom end found")
}

func hborder(field map[int]int, x0, x1, y0, y1 int) bool {
    val := field[y0*n+x0]
    for x := x0; x < x1; x++ {
        if field[y0*n+x] != val || field[y1*n+x] == val {
            return false
        }
    }
    return true
}

func vborder(field map[int]int, y0, y1, x0, x1 int) bool {
    val := field[y0*n+x0]
    for y := y0; y < y1; y++ {
        if field[y*n+x0] != val || field[y*n+x1] == val {
            return false
        }
    }
    return true
}

func samecol(field map[int]int, x0, y0, r, b int) bool {
    col := field[y0*n+x0]
    for y := y0; y < b; y++ {
        for x := x0; x < r; x++ {
            if col != field[y*n+x] {
                return false
            }
        }
    }
    return true
}

func isCleanRect(field map[int]int, x, y, r, b int) bool {
    return hborder(field, x, r, y, y-1) &&
        hborder(field, x, r, b-1, b) &&
        vborder(field, y, b, x, x-1) &&
        vborder(field, y, b, r-1, r) &&
        samecol(field, x, y, r, b)
}

func getRect(field map[int]int, x, y int) *rectType {
    r, err := findRight(field, x, y)
    if err != nil {
        return nil
    }
    b, err := findBottom(field, x, y)
    if err != nil {
        return nil
    }
    if isCleanRect(field, x, y, r, b) {
        return &rectType{x, y, r, b}
    }
    return nil
}

func solveImpl(rects []rectType) []rectType {
    fields := makeField(rects)
    res := []rectType{}
    for y := 0; y < wh; y++ {
        for x := 0; x < wh; x++ {
            rect := getRect(fields, x, y)
            if rect != nil {
                res = append(res, *rect)
            }
        }
    }
    return res
}

func solve(src string) string {
    srects := strings.Split(src, "/")
    rects := []rectType{}
    for _, s := range srects {
        rects = append(rects, createRect(s))
    }
    sizes := []int{}
    for _, r := range solveImpl(rects) {
        sizes = append(sizes, r.size())
    }
    sort.Ints(sizes)
    ssizes := []string{}
    for _, size := range sizes {
        ssizes = append(ssizes, fmt.Sprintf("%d", size))
    }
    return strings.Join(ssizes, ",")
}
テストランナー
package e32

// `go test -args data.json` で実行する

import (
    "encoding/json"
    "io/ioutil"
    "log"
    "os"
    "testing"
)

func TestAll(t *testing.T) {
    var data map[string]interface{}
    jsonBytes, err := ioutil.ReadFile(os.Args[1])
    if err != nil {
        log.Fatal(err)
    }
    json.Unmarshal(jsonBytes, &data)
    failCount := 0
    for _, item := range data["test_data"].([]interface{}) {
        i := item.(map[string]interface{})
        e := i["expected"].(string)
        s := i["src"].(string)
        a := solve(s)
        ok := e == a
        if !ok {
            t.Errorf("solve(%s) is %s, want %s", s, a, e)
        }
    }
    log.Println("Fail Count:", failCount)
}

冗長な感じなのはそういう言語なので仕方ない。

実装戦略

戦略としては

  1. 地図を作る
  2. 36×36回、左上隅の候補を決める
  3. そこを左上隅としたきれいな矩形があるかどうか調べる
  4. 結果をまとめる

という感じ。

(1)の地図は、各マスがn番目の矩形の内側なら $2^n$ を加えるという方式。このソースなら矩形31個まで対応。
64個を超えると面倒なことになるけどそうなったら math/big を使うんだと思う。
以降、マス目に入っている数字のことを「色」と呼ぶ。

(2)は単なる2重ループなので説明略。

問題は(3)。(3) は getRect という関数になっている。この関数は

a. 右端を決める
b. 下端を決める
c. その矩形がきれいな矩形かどうかを調べる

とう三段階になっている。

(a)は、左上隅から、左上隅と違う色が出てくるまで右方向に調べる。
(b)は、同じように下向きに調べる。
(c)は、isCleanRect という関数になっている。この関数は

甲.外周があるかどうか調べる
乙.中がきれいになっているかどうか調べる

の二段階になっている。

(甲)は、矩形のぎりぎり外側が、全部矩形の内側と違う色になっているかどうかを調べる
(乙)は、矩形の内側が全部同じ色になっているかどうか調べる

という感じ。

最後に面積の列に変換して、整列して、コンマ区切りでつなげれば完成。

見返してみると無駄な計算をしている箇所があるけど、まあいいか。

地図を作っているので、座標の値が大きかったり浮動小数点だったりすると負ける。
計算量としては、地図の一辺の長さを L、矩形の数を R としたら $O(L^2 \times R)$ かな。

解説解題っぽいなにか

出題する前から難しいよねこれ、と思っていた。
でも、私が出したい問題の条件

  • 計算機科学力は問われない
  • 数学力も問われない
  • シンプル
  • 明快
  • 紙とペンで解くのは簡単
  • 実装方針が複数ある

を全て満たしていたので出してみた。

さらに

  • コーナーケースがたくさんある

という楽しいおまけも付いていた。

コーナーケースとしては
#11#18#24 辺りが面白いと思っていたんだけど、わりと #6 にやられている人が多かったと思う。

早めに問題が出来上がったので、図を頑張ってみた。面塗りにグラデーションを入れたり、マス目の大きさによって数字の大きさを変えたり。

というわけで

というわけで、私の気が変わらなければこれにてオフラインリアルタイムどう書くは最終回。
以降は年に3〜4回のペースで不定期にyokohama.rbで簡単な問題を出題しようと思っている。

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

なかなか入門できなかった人のためのdocker(+go)入門以前 part1

まえがき

goのプログラムをdockerで動かしたい、あわよくばk8sでデプロイしたいシリーズ第二弾です。
この part1 では、 docker run してdocker上でプログラムを動作させるところまでを扱います。


流行りのdockerですが、なかなか波に乗り切れず、ローカル開発環境の方が簡単じゃない? と思ってしまっているうちにだいぶ時間が経ってしまいました。

これ以上化石になってしまわないように、なんとかdockerに入門したい。あわよくばgoを動かしたい。そんな記事です。

なお、先々にk8sでのデプロイを目論んでいる関係もあり、この記事ではdocker-compose1を扱いません。

想定読者

  • dockerよくわからない
  • docker使いたい
  • 急に複雑な構成が出てきても困る
  • オプションとかの細かい説明や「これが最強だ」みたいなハイテクコマンドが出てくると理解を拒否してしまう
  • ローカル環境やVagrantでなら非常に初歩的なことで躓いてdocker入門しきれてない
  • 環境構築的には、Vagrantfile(そこまで深くはわからんけど)いじったことあるよーぐらいのレベルの人

あまり想定していない読者

  • docker-composeの使い方を学びたい
  • goの最低限の動かし方を知りたい
    • 第一弾の記事を前提にこの記事では説明を進めるので、記事内でのgoに関する説明はあまりありません
  • webサーバーとか仮想環境とかなんのことだかよくわからない、ぐらいのレベルの人
    • ローカルwebサーバーとしてのdockerの立ち上げ方の説明を試みているので、そのあたりの前提になっている知識の説明が少ない可能性があります。
  • Windowsの方
    • 筆者がMacを使っていてMacの環境を前提に説明するので、Windowsの方に全然優しくない説明の可能性が高いです

dockerの特徴について

以下のような特徴があり、旧来の勢力であるローカル開発環境やVagrantのいいとこ取りがしやすいとのことで、近年急激に普及してきています。

  • 配布可能な仮想環境
    • DockerfileによってIac(Infrastructure as Code)が実現でき、共通の開発環境を簡単に配布できます
  • コンテナ型仮想化
    • コンテナと呼ばれる技術(というか、考え方?)を用いて環境構築をします。
    • イメージとしては、OSをdockerがエミュレート2してくれて、開発に実際に関係ある部分だけ都度環境構築するイメージ
    • 従来型のホスト型仮想化と呼ばれる形式と比べて軽量に動作しやすいです3
  • Kubernetes(通称k8s)などのコンテナオーケストレーションサービスとの連携
    • 連携しやすかったです(感想)。GKEでは作ったコンテナをCloud Registryに追加して画面ぽちぽちするだけでデプロイできました。
    • よりスケーラブルな話はまたいつか

以下の記事の方がよりわかりやすかったり、詳しかったりしそうです。

さて、入門以前に入っていきましょう。
動かすまでの手順というイメージです。

dockerをインストールする

docker公式サイトからインストールしてきましょう。

右上のGet Startedから、Get Started with Dockerのページに行って、 Download for Mac をクリックします。

DockerHubのDocker Desktop for Macというページに飛ばされるので、サインアップなどを済ませて Get Docker しましょう。

Docker.dmg

というファイルがダウンロードできたら、展開してインストールを進めます。
しばらくGUIの指示に従ってインストールを進めると、処理が完了しdockerが使えるようになります。

terminal上で

docker -v

として、

Docker version 18.09.2, build 6247962

のような表示が出れば成功です。(インストールだけは昔からしているので、↑はちょっと古いかも)

dockerを動かす

dockerを動かすまでには、いくつかのステップがあります。
最低限以下のことがわかっていれば、dockerを試すところまではいけると思います。

  • docker imageを取得する
  • docker imageからコンテナを起動する

それでは早速やっていきましょう。

docker imageを取得する

以下のコマンドを打てば取得できます。

docker pull golang:1.12.3-alpine3.9

このコマンドについてもうちょっとだけ説明していきます。
まず、

docker pull image名:タグ名

とすることで、 docker registry と呼ばれる docker image が保管されている場所から docker image を取得してくることができます。

取得してきた docker image の一覧は

docker images

を実行することで確認できます。

docker imageってなに?

とはいえ docker image って何よ、という話なのですが、端的にいうと「サーバーの設計書」です。
docker は docker image があれば、 docker container と呼ばれるサーバーをセットアップし、起動することができるのです。

今回でいうと、 golang というのが docker image の名称になります。

docker imageのタグについて

では、例にあるコマンドの 1.12.3-alpine3.9 ってなんなのでしょうか。
これはタグと呼ばれる情報で、取得するイメージのバージョンや環境などを選択できます。

1.12.3 の部分はgoのバージョンを表しています。

そのあとはモノによるのですが、今回のように追加情報が記載されていることがあります。

stretch と alpine について

golang イメージには主だったもので stretch 系と alpine 系のタグが存在します。

タグごとに docker image を定義している Dockerfile の記載が異なるので、動作が異なります。
stretch系はdebian系のimageで、 g++, gcc, libc あたりの機能をはじめにインストールしているみたいです。純粋にgolangを動かしたい方に向いている印象です。

alpine系はlinux系のimageで、あらかじめ bash, openssl などをインストールしているため通信系の機能などを含むアプリケーションの場合は取り回しがしやすいです。簡易的にWebAPIを作りたいようなときはこちらが便利そうですね。

色々と使いやすいのもあり、今回は 1.12.3-alpine3.9 を利用します。

なお、Dockerfileのレベルで確認したい方は以下のリンクが参考になります。
docker、最初はとっつきづらいなと思っていたんですが、慣れてくるとあらゆる情報がDockerHubなどを通じて公開されているのでこういうところがとても追いかけやすいなあ、と感じました。

docker imageからコンテナを起動する

docker run image名:タグ名

で起動できます4

このコマンドで起動され立ち上がったサーバーのことを、 dockerコンテナと呼びます。

起動時の動作に指定がない docker image の場合、上記のコマンドは成功しますがその後何も起きません。

コンテナ起動時には、パターンに応じて以下のオプションを理解しておくと良いと思います。

dockerコンテナのなかでbash等のコマンドを実行したい

bashなどのコマンドを実行したい場合、 -it オプションを用います。5

docker run -it image名:タグ名

dockerコンテナをホストマシンのディレクトリでマウントして、webサーバーとして起動したい

開発用のwebサーバーとしてdockerコンテナを起動したい場合、

  • 手元のコードを反映してほしい
  • ローカルマシンから localhost:8080 とかでアクセスできるサーバーとして立ち上がってほしい

というニーズがあると思います。
その実現にあたっては、ざっくり以下のことを把握しておくと良いと思います。

  • -p オプションで port forwarding
  • -v オプションで マウント元とマウント先の指定
  • -e オプションで 環境変数の指定
  • -d オプションで バックグラウンド実行
  • -it オプション + image:tag の後にコマンドを記載することで、webサーバー起動コマンドの流し込み

上記を踏まえた実行時のterminalのイメージは以下のような感じ。
/path/to/host/path/to/docker は仮のパスなので適宜読み替えてくださいませ。

docker run -it \                                     # コンテナ内部に入ってbashの操作を行う。最終行でコマンド指定
           -p 8080:8080 \                            # ホストマシンのポートとdockerコンテナのポートを接続する。
           -v /path/to/host/go:/path/to/docker/go \  # ホストマシンのディレクトリ情報でdockerコンテナの中身をマウントする。フルパスじゃないと動かないかも
           -e ENV_VARIABLE=env_value \               # 環境変数の指定
           -e ENV_VARIABLE2=env_value2 \             # 環境変数を複数指定する場合は繰り返し用いる
           -d \                                      # バックグラウンド実行
           image:tag \                               # 使用するdocker imageの指定。確か公式リポジトリにあるやつならdocker runするときに取ってきてくれる
           go run main.go /path/to/docker/go/main.go # 実行するコマンド。 -it コマンドの初期値として与えることができる

なお、 docker run の際にはタグ名を省略することもできますが、同じ名称のimageを複数pullしてきている場合はタグまで書いた方がわかりやすいです。

ここで起動されたサーバーのことをdocker containerと呼びます。

なお、コンテナを動かすにあたっては、こちらの記事がより参考になります。コマンドの実例集は本当にありがたい。。

起動したコンテナを使って開発や動作確認をする

動く docker run コマンドの例を書いてみます。
とりあえず動かしたい! という方は真似してみてください。

事前に、上記のdocker imageを取得しておいてください。

一旦仮定として、以下のパスにいることにしましょう。
/YOUR_USER_NAMEtestproject は適宜読み替えをお願いします。
また、 $GOPATH は ~/go という仮定で話を進めます。

pwd #=> /Users/YOUR_USER_NAME/go/src/github.com/testproject/

ここでいつものサンプルを作成します。

/Users/YOUR_USER_NAME/go/src/github.com/testproject/main.go
package main

import "github.com/gin-gonic/gin"

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "pong",
    })
  })
  r.Run() // listen and serve on 0.0.0.0:8080
}

この状態で、以下のようなコマンドを実行します。

docker run -it \
           -p 8080:8080 \
           -v /Users/YOUR_USER_NAME/go/src/github.com/testproject:/go/src/github.com/testproject \
           golang:1.12.3-alpine3.9 \
           go run /go/src/github.com/testproject/main.go

これを実行すると、 docker image を元にした docker container が起動し、ローカルサーバーが立ち上がります。

localhost:8080/ping

にアクセスして、 {"message": "pong"} のレスポンスが返ってくれば成功です。

その他のコマンドについて

以下を覚えておくと入門以前のレベルとしては十分だと思います。

docker images              # 手元にある docker image が確認できる
docker rmi [IMAGE ID]      # 指定したimageが削除できる。IMAGE IDは docker images で確認できる
docker ps                  # 起動中のコンテナが確認できる
docker kill [CONTAINER ID] # 指定したcontainerを停止できる。CONTAINER IDは docker ps で確認できる

Dockerfile使ったりdocker buildしたりしたい。そしてコンテナをデプロイしたい

そんな感じで、dockerのなんとなくの概要とかをまとめつつ、docker runでサーバーが起動するところまでを駆け足でまとめてみました。
Dockerfileってなに?
手元で動くだけじゃ困るんだけど?
みたいな悩みが本番デプロイやdocker imageの配布などを行いたい人には発生すると思うのですが、これは次回以降に続きます。続け!


  1. docker-composeとは、複数のdockerコンテナ(記事内で言及予定)を構成管理できるツールです。よくセットで出てくるけどdockerに対するメタツールなので必須ではないものです。例えばWebアプリケーションサーバーとしてのコンテナと、MySQLサーバーとしてのコンテナを同時に立ち上げ、接続設定まで済ませるといったことが可能です。この記事ではできる限りシンプルにdockerの動かし方を学びたい(しかもこれ書いてる人もdocker-composeよくわかってない)ため、言及していません 

  2. 完全に余談ですが、シミュレートは内部の動作を再現すること、エミュレートは目に見える動作を再現することを指すらしく、位置付けがけっこう異なるようです。高度に完成されたシミュレーションとエミュレーションは同じ結果をもたらす、とも言われています。 

  3. IE8を再現するためのVirtualBoxイメージとかね・・・ 

  4. Vagrantでの vagrant up にあたります 

  5. Vagrantでの vagrant up → vagrant ssh にあたります 

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