2014年3月29日土曜日

Golang Cafe #22を開催しました。

Golang Cafe #22を開催しました。

ソースコードはgithubにありますので、そちらをごらんください。

今回は、私がインフルエンザにかかってしまって、少し時間が取れたので、じっくり準備をすることができました。(今回のテーマにする予定はしていなかったのですが…)

今回のテーマは、「OAuthのライブラリをリファクタリングする」というのをテーマにしてみました。私は出題者なので、出題の背景を書き残しておきます。

前回までに、Twelve Go Best Practicesを読み、ある程度、コードを書くためのコツを掴んだので、それを実践する事ができないかというところから考えました。
(Golang Cafe自体、私がコードを書く時間をネタで募集をしたところから始まっているので)
資料と標準パッケージの使い方はある程度やってきたので、次は、「人のソースコード」を読んでみるという方向性で考えました。公式サイトには、候補として、vitessと、dockerのソースコードを読むというのを挙げていましたが、以前、OAuth2をやったので、https://github.com/mrjones/oauthを読むのも良いかと思い、先行して読み進めていました。ただ、動きをみないとわからない点も多かったので、力づくで、最低限動くコードを作ってみた所、いい感じにダメコードができたので、ちょうど良いのかな?と思ったので設定しました。

ただ、ソースコードを修正するということをしようと思うと、まず、読んでもらわないといけないのもあって、今回は、最初のコードを読み、出題者(私)に質問するという内容になりました。

個人的には、以下の点について議論ができそうな気がしているので、良いテーマなのかな?と思っています。

  1. ライブラリ開発ということで、APIの設計(?)
  2. Twelve Go Best Practicesの適用
  3. ランダム文字列が生成される部分についてのテスト
  4. 通信が発生するので通信に関するテスト
  5. 必要ならMockを使ったテスト
それから、次回は、その次の京都(祝半年)のための準備もしておきたい気がしていますが、そういう時間が取れれば良いですが…。


2014年3月19日水曜日

Golang Cafe #21を開催しました。

Golang Cafe #21を開催しました。今回は、毎週日曜日に開催しているのですが、香川で「春のGoogle勉強会」があるということで、今回の参加者が全員参加する、しかも、開始時間が16:00からということで、その前にやってしまって、日曜日をゆっくりしようということになりました。

メモと動作確認したソースコードはgithubにありますのでごらんください。

では、続きです。

その14

 APIで非同期にするのは避けよう!


  • 順番にAPIを利用したい時はどうすればいいのか?
    • 同期APIを用意すると、並列実行が簡単に呼び出せる。
    • (※runtime.GOMAXPROCS(2)とかすると、順番がおかしくなるが、呼び出し順をどうするか?という議論ではない!)

ここでの議論は、API側で非同期の処理を実装するのではなく、同期APIにしておいて、非同期で実行させるかどうかは呼び出し側の都合で変えられるようにしておくのがいいということでしょう。

その15

  goroutineと通信するための構造体のメンバのChannelか、単体のChannelを使え!

Channelの動作について(何度も復習するところですが、私の認識は以下です。)
chanのバッファが0の時は、他のGoroutinesがChannelを受け取らないとGoroutineがブロックされる。chanのバッファが1の時は、1個はバッファに置けるので、Goroutineがブロックされず、突き抜ける。

goroutineを使った時のgoroutine間の通信をどうするか?という話題で、channelを使って、データのやりとりを行えばいいということです。

で、ここで議論になったのは、以下のコードです。(抜粋ですので、全体は原著の資料か、githubの資料をごらんください。

func (s *Server) Stop() {
    fmt.Println("server stopping")
    s.quit <- true
    <-s.quit
    fmt.Println("server stopped")
}

s.quit<-trueをして、すぐに、<-s.quitでChannelから値を取り出しているように見えますが、Channelはバッファ0のChannelですので、s.quit<-trueした時点で、goroutineはブロックされます。(他のgoroutineでChannelから抜き取るまで。私はスレッドがブロックされるという表現ではなく、goroutineの切り替わりと言っています。)したがって、別のgoroutineが終了処理を行って、完全に終了した後に<-s.quitが実行されることになります。

その16

Buffered Channelを使って、goroutineのリークを避けよう!

  1. 何度も繰り返しになりますが、channelに書き込むと、goroutineはブロックされます。
  2. goroutineはchannelの参照を握っているので、channelがガベージコレクトされることはないことになります。
  3. goroutinesの切り替わりは「明示的なブロック」で発生するので、今回のようにバッファがないchannelだと、goroutineの切り替わりが発生するので、goroutineの処理が終わる前にchannelの受信待ちが終了してしまうことがある。資料の例だとlocalhost:8080がエラー応答になった時に、エラーを検知して終了する。しかし、google.comの処理がまだ残っているので、結果としてリークしたことになる。

その17

quit chanを使って、goroutinesのリークを避けよう!

close(quit)を行うと、channelに受信しない事が明示されます。
その16と同じように、先にエラー応答が発生した時にそのまま終了するのではなく、close(quit)することで、登録されているgoroutineはすべて、case: <-quit:が実行されます。したがって、全て、終了させる事ができます。
goroutineの切り替わりを意識したコードを書くことで、channelのリークを防ぐことができるのでしっかり意識しましょう。
chan(struct{})はクローズ(終了)用なので、誰もChannelに送信できないようにするために、空の構造体にしているものと思われます。

次回は明確にテーマを決めなかったのですが、何かやります。

2014年3月9日日曜日

Golang Cafe #20を開催しました。

Golang Cafe #20を開催しました。

今回は、前回に引き続き、Twelve Go Best Practicesの13ページから読み進めました。
プラクティスのメモはgithubにありますので、ごらんください。
(今回は、新しいコードを書くことはしていないので、メモが追記されただけです)

以下、今日のまとめです。資料を見ながら読み進めてみてください。

その7

重要なコードは先頭に書くこと!


  1. ライセンス情報、ビルドタグ、パッケージドキュメントを記載する。
  2. import文は、(gofmtコマンドを実行すればいい気がするが…。)標準パッケージを最初に記述し、1行開けて、go getで取得したパッケージを記述する。
    1. gofmtは自動的にアルファベット順に並べてくれるが、1行あけると、並べ替えの適用を受けなくなります。
    2. gofmt sample.goだと、標準出力に出力されてファイルが書き換わらないので、gofmt -w sample.goと-wオプションをつけよう!
  3. 重要な型から始まり、ヘルパー関数などで終わるようにする。
  4. ファイル名は型に合わせたものにした方が良い!
    1. という結論に至りました。goの標準ライブラリのソースコードを見る限りだと、最初にtypeの宣言があり、typeの名前とファイル名が一致していたので。
    2. 機能ごとにファイルを分けて、わかりやすくするべきなんだろう。

その8

パッケージ名の前に連想させるようなドキュメントを書くこと!

godocに出力されるものは、ドキュメントを書きましょう!

その9

短い事はいいこと。


  1. 意味がわかって、できるだけ短い名前をつけるようにしましょう!
    1. "MarshalWithIndentation"よりも"MarshalIndent"という名前が好ましい。
  2. 識別子にパッケージ名が含まれる場合は、つける必要はない!
    1. encoding/jsonにEncoderがあるが、JSONEncoderではない。
    2. 参照される時は、json.Encoderとなるから、必要ないでしょう?
      1. 確かに、Goでパッケージのコードを書く時は、json.JSONEncoderだと命名としてブサイクなのかな…?

その10

1つのパッケージを複数に分けるべきか?


  1. コードの長いファイルは避けよう。
    1. net/httpパッケージは15734行を47ファイルに分けている。
  2. コードとテストは分けよう。
    1. net/http/cookie.goとcookie_test.goみたいに。
    2. そもそも、go testが動かないはずなので、ほぼ強制的にそうなるではないかと思われる。(go testは**_test.goを実行するので)
  3. パッケージドキュメントをdoc.goに分けておくといい。
    1. 複数のファイルがある時に、どこにパッケージドキュメントを書くか迷うので、わかるようにdoc.goを作り、そこに書く。標準のパッケージもdoc.goはpackageの宣言と、ドキュメントの記述しかない。
    2. ファイルが1つの時はそのファイルの先頭に書けばいい。


その11

go getができるようなパッケージを作りましょう。

ネットワークプロトコルを定義するようなパッケージは使われるかもしれない。
mainから始まるコマンドは再利用されないかもしれない。


その12

必要なものだけを求める。

Gopher型を使います。
    
以下のメソッドを定義した。
func (g *Gopher) WriteToFile(f *os.File) (int64, error) {

しかし、テストがしにくい!そこで、インターフェイスに書きなおした。
func (g *Gopher) WriteToReadWriter(rw io.ReadWriter) (int64, error) {

必要最低限のインターフェイスを使ってメソッドを定義すべきだ。
このメソッドは、ファイルへの書き出し機能だけなので、Readerの機能は必要がない。したがって、必要なものを引数に渡すように求めること!
func (g *Gopher) WriteToWriter(f io.Writer) (int64, error) {

ここでは、余計なものを指定できると、よくわかっていない人が想定外の引数を指定してしまうのを防ぐためにもこのプラクティスは必要!という結論になりました。
(が、それぞれ流儀があると思うので…。と議論がおかしい方向になる前にやめました)

その13

独立を保つ!パッケージごとに独立させておく。


  1. parserは解析のみ。drawerは描画のみ。pngへのエンコードはエンコードのみにする。など。
  2. 依存するパッケージをimportするのではなくて、interfaceを使って、Parse機能を持った関数を受け取り、呼び出すようにしよう!
  3. 具体的な型を使うよりも、インターフェイスを使った方がテストが簡単に作れる。
  4. TestFunc型はEval()を実装しているので、interfaceを実装していることになる。

この時点で12個じゃない!と思いましたが、資料の最後に12個のリストがあるようです
…。が、12個じゃないよな…?という気がします。

引数で受け取った関数を呼び出す形にして、他パッケージの依存を防ぐというのは良いのだけど、引数で指定する関数の中身は呼び出し側が実装するわけなので、「誰でも新たな処理を追加できる」というのが良いのか悪いのか…。という意見が出ました。
これも奥が深い話になる(というか、プロジェクトのメンバー間の意識統一かな?)と思ったので、やめておくことにしました。

ということで、次は、並列処理などのパターンが記述された、25ページ以降を読み進める予定です。

2014年3月3日月曜日

Golang Cafe #19を開催しました。

Golang Cafe #19を開催しました。

今回は、Twelve Go Best Practicesの資料を読むということで、(恐らく)その6まで読み進めました。
ソースコードと、プラクティスのメモはgithubにありますので、ごらんください。
commitログと履歴を遡ればどういう変化があるかを見ることができるようになっています。

以下、今日のまとめです。資料と共に読み進めて下さい。

その1
深いネストは避けよう!

Goは例外機構がないので、全ての呼び出しについて、エラーチェックが必要になります。
(エラーチェックじゃなくても、戻り値を受け取った後に何らかのif文が必要になる)
そこで、全ての(仮にここではerrorをチェックするとして)エラーをif文で囲むとネストが深くなりやすくなります。
では、どうするかというと、”エラーがある場合は、即return”ということになります。
こうすることで、ネストが深くなるということが避けられます。

その2
構造体に必要なオブジェクトを格納することで、シンプルなコードに!

その1で”エラーがある時は即return”としたのですが、「毎回チェック」をする必要はないわけです。というと、errorを構造体のメンバにしておき、戻り値をメンバに退避しておきます。
そして、メソッド呼び出しの最初に、メンバのerrorがnilかどうかをチェックし、nilでなければ、即終了させる。という形にします。
これにより、呼び出し側でエラーを確認する必要がなくなるので、シンプルな呼び出しにすることができます。

その3
型で処理が決まる場合は、型で処理を分岐させましょう!

型によって、処理がわかれる場合に、switch v.(type) {}を使うと、型の判定を行うことができます。実は、標準パッケージだと、よく書かれている記述で、ユーティリティを作るのに非常に便利です。(Go以外の言語で型による分岐って無い気がするので、独自のパターンかもしれません)
引数は、interface{}で受け取ることがポイントです。

その4
switch文の:=を使え!

その3で型による分岐が出てきましたが、実際に処理を進めるに当たって、Type Assertionをする必要があるのですが、v.(type)については、Goの構文として短縮構文にすると、case文は型で判定し、値は短縮構文の時にType Assertionされているという状況を作ることができます。したがって、型によって、Type Assertionをする必要がなくなります。

その5
全て書くか、何も書かないかのどちらかにする。

データベースのトランザクションと同じような感じですが、例えば、通信プログラムなど、「途中まで成功」というのは都合がよくありません。そこで、一旦、bytes.Bufferに送信電文のデータを書き出しておき、Flush()メソッドなどで、一括で送信するというようにしておくと、良いですよという話。
しかも、このソースコードは、再帰呼び出しを使っていて、エラーのチェックも1か所に集約されています。が、これは日本(の中小企業(?))だと怒られるのではないか?という話になりました。慣れないとすぐには気がつかない…w

その6
リクエストを処理し、エラーを返す関数と、エラーを受け取り、エラーを処理する関数に分ける!

ここから、Webサーバの例になりますが、複数の処理に対するエラーの処理をどうするか。というのがテーマだと思います。(実は、私もGAE/Gで同じ失敗をしているのですが…)
毎回if文でエラーチェックして、エラーレスポンスの処理を書くとイケてないコードになります。(これは、私も疑問に思っていた)そこで、エラーを返すだけの主処理と、主処理を呼び出し、エラーチェック後、レスポンスを返す、エラー処理関数を用意します。
すると、エラー処理がきれいに集約されてきれいになります。
また、errorを変数で宣言しておくと、エラーの判定により、レスポンスを変える事ができるので非常に便利です。ただ、資料のfmt.Errorf()は、内部でerrors.New()をしているので、一見便利に見えますが、エラー判定ができなくなるので、使うと後で困ることになるので使わないようにしようということでした。

今回は、資料の12ページまで読み進めたので、次回は、13ページから読み進める予定です。


2014年2月23日日曜日

Golang Cafe #18を開催しました。

Golang Cafe #18を開催しました。

今回は、goauth2というライブラリを使って、GoogleのAPIを実行するというのが
テーマでした。

サンプルコードは、githubにありますので、そちらをごらんください。
(ディレクトリを別にしていて、goauth2sampleに入っています)

今回は、サンプルの解説と、installedAppsの認証を1回で完結させるというのをやりました。
installedAppsの認証を1回にするというのは、oauth2の認証(installedApps)では、最初にユーザに承認させることになっていて、承認後のコードをアプリケーションに教えてあげる必要があります。サンプルでは、引数で承認コードを指定するようにしていますが、その引数での指定をやめて、Webアプリのように承認後、継続する処理に変えるというのにチャレンジしました。

サンプルの解説からですが、まず、calendarsampleは、CalendarAPIを実行し、カレンダーリストを取得するサンプルです。

認証の設定をいれる

最初に、認証の設定を保持するConfig構造体にClientID、Secretなど必要な設定をいれます。
config := &oauth.Config{
       ClientId:     auth.ClientID,
       ClientSecret: auth.Secret,
       RedirectURL:  auth.RedirectUrl,
       Scope:        scope,
       AuthURL:      request_token_url,
       TokenURL:     auth_token_url,
       TokenCache:   oauth.CacheFile(cachefile),
}

ClientID、Secret、RedirectUrlはGoogle Developers Consoleでアプリケーションキーを取得したものを設定します。(私のサンプルはgithubにキーが上がってしまうと困るので、最初は入力させて、以降は、ファイルに保存したものを利用するコードになっています。)

ScopeはAPIによって指定するものが違うので、APIのドキュメントを参照して下さい。
TokenCacheは一度認証した時にキャッシュとしてファイルに保存され、ファイルがある間で、有効期限が過ぎていなければファイルの認証トークンを使ってAPIを実行するようになっています。

キャッシュから認証情報を取得する

次に、キャッシュからトークンを取得します。

    _, err = config.TokenCache.Token()
if err != nil {
    // キャッシュなし

    // 認証コードなし=>ブラウザで認証させるためにURLを出力
    if code == "" {
        url := config.AuthCodeURL("")
        fmt.Println("ブラウザで以下のURLにアクセスし、認証して下さい。")
        fmt.Println(url)
        return
    }

    // 認証トークンを取得する。(取得後、キャッシュへ)
    _, err = transport.Exchange(code)
    if err != nil {
        fmt.Println("Exchange: ", err)
        return
    }

}

キャッシュファイルがない場合は、errorが返されるので、認証コードを取得するURLを生成し、コードを取得します。(ここで、一度プログラムを終了させる。)
もし、引数で指定してあれば、transport.Exchange(コード)でExchangeし、認証トークンを取得します。

APIを実行する

APIを実行します。oauth2のパッケージで認証トークンを持っているので、何も考えずにGet()すれば、レスポンスが返ってきます。

// Calendar APIにアクセス
r, err := transport.Client().Get(request_url)

思ったよりも簡単だった。

やることがたくさんあるわけではなく、簡単にアクセスできることはわかりましたが、承認キーを渡してもらう部分を何とかしないと、installedAppsでは使えない気がします。(これをGAEのappcfg.pyは上手にやっているらしく、どうやっているのかソースを読んで解説して貰う必要がありそうです。+Ryuji IwataさんがPythonを知っているので今度読んで、解説してもらおうという話にもなりました。)
+Takanobu Haginoさんは、goroutineでWebサーバを起動(http.ListenAndServe())して、リダイレクト先をlocalhostにするという荒業を実現していました。私も挑戦したのですができなかったので、そのコードの解説はそちらに譲ることにします。)

次回は、2013の12個のベストプラクティスを読む予定です。

追記:
それから、今回無断欠席をされた方がいらっしゃったので、無断欠席に関する注意点を増やしました。申込をして、来られないのは仕方がないと思いますが、何の連絡も無いのはどうかと思いますので、次回から、そういう方は管理者権限で参加取り消しをする可能性があります。

2014年2月19日水曜日

Google Compute Engine(CentOS6)にGoのCI環境を作る

今日、少し時間があったので、Google Compute Engine(以下、GCE)上でJenkinsを動かしてビルドとテストを行う環境を作ってみました。
GCEは、DebianとCentOS6が選べるのですが、私はCentOS6を選びました。

GCEのインスタンスを起動するには、Google Cloud Consoleからインスタンスを起動するのですが、まず、前提として、「課金設定をする」必要があります。(課金設定をしないと、GCEの初期化が成功しない。)インスタンスを生成して、起動するところまでのメモは以前投稿しているのでそちらをごらんください。Google Cloud SDKもダウンロード済みだということにします。(私は忘れていたけど…)

ファイアウォールの設定


まず、GCEのファイアウォールの設定を行います。これは、iptablesではなく、GCEのインスタンスに対するファイアウォールの設定です。Cloud Consoleからでもできますが、以下のコマンドを実行しました。ポートを8888を許可する設定にしていますが、Jenkinsのデフォルトポートをそのまま使えると思うので、デフォルトのポートを使う場合は、tcp:8080にしてください。
$ gcutil addfirewall [インスタンス名] --description="Jenkins用のポート" --allowed="tcp:8888"

そして、sshでログインして、iptablesを切る。(GCEのファイアウォールに委ねるので。これに気がつくのに時間がかかってしまいました…。二重にブロックされるので結構うっとうしい…。)

$ sudo service iptables save
$ sudo service iptables stop
$ sudo chkconfig iptables off

次回の起動でiptablesが動いてくれると困るので、chkconfigでoffにしておきます。

Javaのインストール


Javaもyumで入れましたが、公式のものが入れられなかった(多分、ブラウザでアクセスして、利用規約に同意のチェックがいる?)のでopenjdkを入れました。Jenkinsが動けば問題は無いので、細かいことは無視しました。
$ sudo yum search jdk

java-1.7.0-openjdk.x86_64 : OpenJDK Runtime Environment
java-1.7.0-openjdk-demo.x86_64 : OpenJDK Demos
java-1.7.0-openjdk-devel.x86_64 : OpenJDK Development Environment
java-1.7.0-openjdk-javadoc.noarch : OpenJDK API Documentation
java-1.7.0-openjdk-src.x86_64 : OpenJDK Source Bundle
ldapjdk-javadoc.x86_64 : Javadoc for ldapjdk
icedtea-web.x86_64 : Additional Java components for OpenJDK - Java browser plug-in : and Web Start implementation
ldapjdk.x86_64 : The Mozilla LDAP Java SDK

$ sudo yum install java-1.7.0-openjdk-devel.x86_64

Jenkinsのインストール

Jenkinsをインストールします。Jenkinsのrpm版が提供されていたのでもしかしたら?と思ったら、yumでインストールも可能だったので、yumにKeyを登録してインストールしています。

$ sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo

$ sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key

$ sudo yum install jenkins

Jenkinsの設定とサービスの登録

PORTを8888に変更する。(デフォルトが8080なので、そのままでもいいと思いますが)
$ sudo vi /etc/sysconfig/jenkins
JENKINS_PORT="8888"

Jenkinsのchkconfigをon
$ sudo chkconfig jenkins on

Jenkinsを起動させる。(初回だけ)
$ sudo service jenkins start

Jenkinsの起動確認


ブラウザで、http://[VM InstanceのEXTERNAL ADDRSSのipアドレス]:8888/
にアクセスしてJenkinsが表示されれば、成功です。

Gitも忘れずにインストールしておきましょう
Gitのインストール

$ sudo yum install git


Goをソースからインストールする。

私はこれしかしたことがないので(?)、今回もソースからGoをインストールしました。
Goをコンパイルするために、gccとmakeを、ソースコードをダウンロードするためにmercurialをインストールしました。

$ sudo yum install mercurial-hgk.x86_64

$ sudo yum install gcc

$ sudo yum install make

$ hg clone -u release https://code.google.com/p/go

$ cd go/src

$ ./all.bash

GCE上でGoのコンパイルをしてみたのですが、意外と早くて、1分30秒程度で終わったような気がします。そこからtestが始まりましたが、testはもう少しかかったかも?
で、喜んでいたら、一般ユーザのディレクトリでビルドしてしまったので、Jenkinsからコマンドを実行しようと思うと、Permissionで弾かれてしまいますから、これも良くないのですが、/home/ユーザのディレクトリにchmod o+rxしてしまいました。
次回以降は気をつけようと思います。基本は/usr/local以下ですかね。

Jenkinsのプロジェクト設定

Jenkinsにgitプラグインを入れたのですが、よく考えると、Goの場合は、go getでダウンロードしてからgo testですので、gitプラグインは無くても大丈夫です。
したがって、ビルドの設定の「シェルの実行」のところに、コマンドをいくつか書きました。

export GOPATH=${WORKSPACE}
/home/yokoyama/go/bin/go get github.com/taknb2nch/go-pop3

cd ${WORKSPACE}/
/home/yokoyama/go/bin/go test 

ポイントは、GOPATH変数を都度exportしているところでしょうか。go get/go testコマンドを実行しようと思うと、GOPATHに入れておかないといけませんからこのような形になります。
リポジトリは、+Takanobu Haginoさんが作られた、POP3のライブラリを対象にしてみました。(私のリポジトリにしようかと思いましたが、私のはどれもレガシーコードですので…w)

結果


ちゃんと、PASSの文字が輝いております。
Started by user anonymous
Building in workspace /var/lib/jenkins/jobs/Sample/workspace
[workspace] $ /bin/sh -xe /tmp/hudson5366985436882835859.sh
+ export GOPATH=/var/lib/jenkins/jobs/Sample/workspace
+ GOPATH=/var/lib/jenkins/jobs/Sample/workspace
+ /home/yokoyama/go/bin/go get github.com/taknb2nch/go-pop3
+ cd /var/lib/jenkins/jobs/Sample/workspace/
+ /home/yokoyama/go/bin/go test
PASS
ok   _/var/lib/jenkins/jobs/Sample/workspace 0.003s
Finished: SUCCESS

今回は、「手動実行」まででとどめました。常時起動するほどテストをすることがないということと、課金の問題があるので、無駄に起動しておくのはまずい。ということが大きな理由です。これで、GCEについてのノウハウが少しずつ貯めていけそうです。

参考サイト

https://developers.google.com/compute/docs/quickstart#centos
http://d.hatena.ne.jp/katsuren/20121030/1351569655
http://d.hatena.ne.jp/okinaka/20120404/1333524730

2014年2月17日月曜日

Golang Cafe #17を開催しました。

Golang Cafe #17を開催しました。
今回のサンプルコードはgithubにおいてありますのでごらんください。

今回は、os/exec、os/signal、os/userパッケージを見ていきました。
その中で、Windows版だと、動かない現象があったので、メモを残しておきます。
(来週の土曜日まで残しておくと、記憶が薄れそうなので)

サンプルコードのコメントにも書きましたが、syscall.Kill()がWindowsでは、定義されていないため、コンパイルエラーになります。こういうOSの仕組みがまったく異なっていることによるソースコードの差分でしょうが、以前の"return -1"ではなく、定義さえもされないということで、signalsample2.goはWindowsではコンパイルもできないということになります。
(今後、こういう問題は改善されるのだろうか…?)

execパッケージのポイント


Command構造体


execパッケージのサンプルのポイントは、Command構造体で、execするプロセスを管理するのですが、標準入力、標準出力、標準エラー出力にPipeを使って、アクセスすることができます。
それぞれ、StdoutPipe()、StdinPipe()、StderrPipe()を使ってio.ReadCloser()、io.WriteCloser()を受け取り、io/ioutilパッケージを利用するなどして読み取ります。

Start()でプロセスの起動、Wait()でプロセスの終了を待つ


exec.Command("コマンド名", パラメータ...)でCommand構造体を生成し、cmd.Start()でプロセスが起動します。Pipeで標準出力などにアクセスする場合は、必ず、Start()の呼び出しよりも前にStdoutPipe()を呼び出しておく必要があります。Start()の後に、呼び出すと、errorが返されてしまい、アクセスすることはできません。また、Start()を呼び出さずに、Wait()だけを呼び出してしまっても、無限待ちになってしまいますのでStart()→Wait()の順番は守るようにして下さい。
Start()はブロックするわけではなく、そのまま処理を続行します。もし、プロセスの終了をその場で待ちたい場合は、cmd.Run()を呼び出すと、内部でStart()→Run()の順番で呼び出しますので、ブロックされます。

ブロックして、標準出力を得るのであれば、Output()


コマンドの終了を待ち、しかも出力結果も受け取りたい場合は、
exec.Command().Output()とすると、戻り値に[]byteとして、標準出力の結果を受け取ることができます。また、exec.Command().CombinedOutput()にすると、
標準出力と、標準エラー出力がマージされた状態(恐らく、出力された順番)で[]byteを受け取ることができます。
この辺りは、実行して監視するコマンドによって使い分けが必要でしょう。

Signalは受け取るのみ可能

os/signalパッケージは、Notify()とStop()だけが定義されています。Notify()は外部からSignalを受け取った時に、引数で指定した、Channelに対して、受け取ったSignal構造体を送信します。Channelに送信されるSignalは同じく、引数で指定したSignalのみとなります。(サンプルコードでどんなものか、確認してみてください)

Stop()はChannelに格納されているSignal構造体を全てキャンセルする関数で、何もなければ、すぐに終了します。したがって、「Signalが送られていないことを保証する」事ができます。プログラムを終了するときなどに呼び出すもの。と思えばいいと思います。

Windows版はAPIに依存しているos/userパッケージ


user.Current()は実行ユーザを取得、user.Lookup("アカウント")は指定したユーザを取得、user.LookupId(uid)はユーザIDからユーザを取得します。
Mac/Linuxは、この説明で問題はないのですが、Windows版はkernel32.dllをLoadlibrary()した挙句に、Win32API(?)をコールしてAPIの動作に依存したコードになっています。(DLLの読み込みなどは、cgoが使われています。unsafeパッケージの使い方などは、いい勉強になるかも知れません)
(システムに依存しているのでどうにもならないと思いますが…。今後大丈夫なんだろうか…?)

実際User構造体はuid、gid、アカウント名、ホームディレクトリ、ユーザのフルネームなどが格納されますが、uid、gidは、GetTokenInformation()を使って、SIDを取得しています。uidの方は、TokenUserで指定し、gidはTokenPrimaryGroupで指定した結果を受け取ってそれぞれ設定していました。
ホームディレクトリは、GetUserProfileDirectory()が使われていました。
ユーザ名とフルネームについては、(Golang Cafeの最中には途中でやめたのですが、せっかくなので読みました。)ドメイン/ワークグループに参加しているかどうかを、NetGetJoinInformation()を呼び出し、結果が取得できるかどうかを確認しています。
ActiveDirectoryとかそういうのに参加している場合はこのAPIがエラーを返すのでしょう…。恐らく。
ドメイン/ワークグループに参加している場合は、TranslateName()を呼び出し、情報を取得します。ActiveDirectoryの場合は、NetUserGetInfo()を呼び出し情報を取得します。
その結果から、フルネームとユーザ名を取得し、UsernameとNameにセットされるという流れになります。

結果として、Windows版は大丈夫か?と言いたくなる程度にはWin32APIに依存しています。今後、.NETFrameworkの機能しか提供されなくなったらかなりまずそうです…。

ということで、Golangの標準パッケージを使うというのもひと通り見たなということで、
次回は、goauth2パッケージを使ってGoogle Apps APIを叩くという内容になります。

サンプルコードで挙動を確認するという段階は十分やったと思うので、今後は、実際に
コードを書き、"Goらしいコードの書き方"というのを追求できたらと思っています。