- 投稿日:2019-04-13T23:29:35+09:00
privateの構造体をjson.Marshalした時に空でreturnされる
概要
同package内でのみアクセスできる(private)構造体を json.Marshal した時に戻り値が空になる。
事象
privateの構造体を用意する
pkg/User.gopackage 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.gopackage 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.gopackage 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脳ですみません。。。)
- 投稿日:2019-04-13T19:30:31+09:00
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_profileexport GOPATH=$HOME/World/Japan/qiita/go【参考】
SettingGOPATH世界に挨拶してみる
ワークスペースを作成したらソース用ディレクトリを用意する。
$ cd $HOME/go # もしくは $ cd $GOPATH mkdir -p src/helloソースコードは以下を作成する。
hello.gopackage 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使い。
- 投稿日:2019-04-13T19:30:31+09:00
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_profileexport GOPATH=$HOME/World/Japan/qiita/go【参考】
SettingGOPATH世界に挨拶してみる
ワークスペースを作成したらソース用ディレクトリを用意する。
$ cd $HOME/go # もしくは $ cd $GOPATH mkdir -p src/helloソースコードは以下を作成する。
hello.gopackage 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使い。
- 投稿日:2019-04-13T18:25:50+09:00
マナー良いスクレイピング開発で心理的安全性を確保する
なにこれ
あれ?私、開発中のマナー悪くない?
スクレイピングやクローラーのプログラムを書く時に、毎回対象のWebサイトにアクセスしていませんか?
これから有り難く情報を拝借するのに、開発中の大量なアクセスでKPIや分析指標に影響を与えちゃってやたら迷惑かも?
単純に自分がハマっているだけなのに...そんな心理的な負い目。少なからずあると思います。
...
...
そうだ、Webページをローカルに保存して開発した方が良さそう!
※完全に思いつきで試した開発方法なので全てのケースで適応はできないかもしれませんので悪しからず
この記事で得られるもの
- Webページをローカルに保存してスクレイピングプログラムが書けるようになる
- Goのテストがちょっとだけ書けるようになる
- ある程度のマナーを身につけられる
- 負い目が減る開発方法を身につけられる
どうやるの?
開発の手順としては以下の通り。
- スクレイピング対象のWebページをローカルに完全保存する
- ローカルに完全保存したWebページを対象に動作確認を行うテストを書く
- スクレイピングの処理を書く
- 途中で動作確認を行う場合は、テストを実行することでローカルに完全保存したWebページに向けて検証する
- テストで意図通りに動作したら、本番環境に向けてアクセスする。
サンプルを見ながら雰囲気を掴む
以前公開した 阿部寛をWebDriverでいじくるを例にします。GoでWebDriverを使う記事です。メインの処理なんかはこちらを読んでいただけると。
1. スクレイピング対象のWebページをローカルに完全保存する
これはお使いのブラウザから簡単にできますね。割愛します。
2. ローカルに完全保存したWebページを対象に動作確認を行うテストを書く
今回のプログラムではWebDriverを使って開発をするのですが、ローカルのHTMLにもバッチリアクセスできます。
まずはテストコードを書きます。テストコードの中でローカルに保存したWebページ(HTML)に対してアクセスするように定義します。準備はこれだけです。
main_test.gopackage 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.gopackage 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により改修が発生したのかどうかを判別する事が容易になると考えられます。
それにより調査の初速が上がり、対応完了までの時間が短くなると考えられます。このへんは効果が見えたらまた書くかも。
- 投稿日:2019-04-13T17:41:55+09:00
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環境でカウンタが壊れることはないようです。
- 投稿日:2019-04-13T16:52:24+09:00
競プロのテストを「go test」でやりたかったので先人の知恵をお借りした
先人の知恵
知恵お借りします。ありがとうございます。
競プロのテストを「go test」でやりたかったので作ったいきさつ
Go習得に向けてPaizaでもやろうと思ったが、元記事と同じように標準入出力のテストを簡単にやりたかった。
Paizaの場合、複数行の入力と複数行の出力の問題があるので、それに対応するように自分向けに修正をしてみた。
というお話。
リポジトリ
下記修正したのもの
trewa-nek9585/GolangSkeletonForSpeedrunイメージ
修正点
複数行対応
テストケース用の構造体のプロパティ型が入力、出力想定ともに 一行だけ受け取る想定になっていたので、 複数行受け取れるように
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がちょっと捗るようになりましたとさ。
以上、お目通しどうもありがとうございました。
- 投稿日:2019-04-13T16:43:11+09:00
Homebrewに自作コマンドを登録して使えるようにする【Go編】
Goの勉強も兼ねて自作コマンドを作ろうと思った。
その前に作ったコマンドを公開して誰でもインストールできるようにしたかったので、その備忘録。前提条件
下記の環境が整っていることとする。
- homebrew (v2.1.0)
- go (v1.12.3)
- githubのアカウント
大まかな流れ
- Goで実行ファイルを作成する
- Githubにソース管理用のリポジトリを作る
- リリース作業をする
- Githubにインストール用のリポジトリを作る
Formulaファイルを作成するbrew tap/brew installでコマンドをインストールする- コマンド実行
やっていき
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」というボタンがあるのでそれを押すとリリース情報の入力画面に行く。タグバージョンやタイトルなどの情報は、一旦適当に入れておく。
大事なのは下図の赤枠の部分で、ここをクリックして先程作った実行ファイルを登録する。終わったら「Publish release」をクリックする。
リリース情報が表示されるので、実行ファイル部分のURLを控えておく(後で使う)。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とは
- 投稿日:2019-04-13T14:01:54+09:00
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")
- 投稿日:2019-04-13T12:51:49+09:00
goplsでnet/http系のパッケージを入れると動かなくなるバグ(Mac)
goplsで快適にプログラムを書いていたんですが、net/http系のライブラリを使うと何故か補完等の全機能が使えなくなるというバグに見舞われて、かなり苦労したのでもし他にもそういう方がいたときのために解決方法等を載せておきます。
どのようなバグか
僕はgolangでweb系のプログラムを書いていたので、gin-gonic/ginのようなライブラリを使っていたのですが、そのライブラリをimportするとgoplsが動かなくなるというバグでした。
原因
様々な場所でログを出力してデバッグしていたところ。
$GOPATH/src/golang.org/x/tools/go/packages/packages.goのLoadという関数でエラーが起きていたようでした。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
- 投稿日:2019-04-13T10:33:03+09:00
オフラインリアルタイムどう書く 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) }冗長な感じなのはそういう言語なので仕方ない。
実装戦略
戦略としては
- 地図を作る
- 36×36回、左上隅の候補を決める
- そこを左上隅としたきれいな矩形があるかどうか調べる
- 結果をまとめる
という感じ。
(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で簡単な問題を出題しようと思っている。
- 投稿日:2019-04-13T01:51:08+09:00
なかなか入門できなかった人のためのdocker(+go)入門以前 part1
まえがき
goのプログラムをdockerで動かしたい、あわよくばk8sでデプロイしたいシリーズ第二弾です。
この part1 では、docker runしてdocker上でプログラムを動作させるところまでを扱います。
流行りのdockerですが、なかなか波に乗り切れず、ローカル開発環境の方が簡単じゃない? と思ってしまっているうちにだいぶ時間が経ってしまいました。
これ以上化石になってしまわないように、なんとかdockerに入門したい。あわよくばgoを動かしたい。そんな記事です。
なお、先々にk8sでのデプロイを目論んでいる関係もあり、この記事ではdocker-compose1を扱いません。
想定読者
- dockerよくわからない
- docker使いたい
- 急に複雑な構成が出てきても困る
- オプションとかの細かい説明や「これが最強だ」みたいなハイテクコマンドが出てくると理解を拒否してしまう
- ローカル環境やVagrantでなら非常に初歩的なことで躓いてdocker入門しきれてない
- 環境構築的には、Vagrantfile(そこまで深くはわからんけど)いじったことあるよーぐらいのレベルの人
あまり想定していない読者
- docker-composeの使い方を学びたい
- docker-composeに限りませんが、しっかり学びたい方はこの記事とかすごいです いまさらDockerに入門したので分かりやすくまとめます
- goの最低限の動かし方を知りたい
- 第一弾の記事を前提にこの記事では説明を進めるので、記事内でのgoに関する説明はあまりありません
- webサーバーとか仮想環境とかなんのことだかよくわからない、ぐらいのレベルの人
- ローカルwebサーバーとしてのdockerの立ち上げ方の説明を試みているので、そのあたりの前提になっている知識の説明が少ない可能性があります。
- Windowsの方
- 筆者がMacを使っていてMacの環境を前提に説明するので、Windowsの方に全然優しくない説明の可能性が高いです
dockerの特徴について
以下のような特徴があり、旧来の勢力であるローカル開発環境やVagrantのいいとこ取りがしやすいとのことで、近年急激に普及してきています。
- 配布可能な仮想環境
- DockerfileによってIac(Infrastructure as Code)が実現でき、共通の開発環境を簡単に配布できます
- コンテナ型仮想化
- 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_NAMEやtestprojectは適宜読み替えをお願いします。
また、 $GOPATH は~/goという仮定で話を進めます。pwd #=> /Users/YOUR_USER_NAME/go/src/github.com/testproject/ここでいつものサンプルを作成します。
/Users/YOUR_USER_NAME/go/src/github.com/testproject/main.gopackage 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の配布などを行いたい人には発生すると思うのですが、これは次回以降に続きます。続け!
docker-composeとは、複数のdockerコンテナ(記事内で言及予定)を構成管理できるツールです。よくセットで出てくるけどdockerに対するメタツールなので必須ではないものです。例えばWebアプリケーションサーバーとしてのコンテナと、MySQLサーバーとしてのコンテナを同時に立ち上げ、接続設定まで済ませるといったことが可能です。この記事ではできる限りシンプルにdockerの動かし方を学びたい(しかもこれ書いてる人もdocker-composeよくわかってない)ため、言及していません ↩
完全に余談ですが、シミュレートは内部の動作を再現すること、エミュレートは目に見える動作を再現することを指すらしく、位置付けがけっこう異なるようです。高度に完成されたシミュレーションとエミュレーションは同じ結果をもたらす、とも言われています。 ↩
IE8を再現するためのVirtualBoxイメージとかね・・・ ↩
Vagrantでの
vagrant upにあたります ↩Vagrantでの
vagrant up → vagrant sshにあたります ↩



