golangの欠陥

Go(golang)はかなりいろいろなことがよく考えられた良い言語だが、大きな欠陥がある。その欠陥はやはりimport。gitやmercurial等のリモートリポジトリを指定できてそれはそれで大層便利ではあるのだが、タグやブランチ、バージョン等を指定できない(常にmasterのHEADが使われる)ことがしばしば問題となる。

このimport問題の中でもとりわけ大きなものが、local importの問題だ。

リポジトリ上で開発していて、同じリポジトリのサブディレクトリを対象にimportしたいとする。

import "./subdir"

通常はこれで良いのだが、$GOPATH/src以下もgo getでcloneしてくるリポジトリになっていて、ここのソースに対して作業をしていると、相対パスによるimportがエラーになる。

can't load package: /path/to/go/src/github.com/wtnb75/xxxxx/yyyy.go:16:2: local import "./subdir" in non-local package

つまり、$GOPATHにのっとって開発する(実際golangではそれが推奨されている)場合は、こう書かなければならない。

import "github.com/wtnb75/xxxxx/subdir"

それがgithub.com/wtnb75/xxxxxからimportする場合でもそうなのだ。これは時折深刻な問題を引き起こす。xxxxx/subdir以下の関数の引数を変更し、呼び出し元のxxxxxの呼び出し方も追随して変更することを考える。github flowではこのような場合ブランチを切ってcommit, pushし、作業が終了して動くようになったらPull Request(PR)を出し、レビューを受けてマージしてもらう。自分がメンバーに入っていない赤の他人のプロジェクトであればpush先を自分のテンポラリのリポジトリにしてPRを出すし、メンバーに入っていれば元のリポジトリのブランチで作業するわけだ。

このときdroneやtravis(jenkinsも)といったツールを使ってビルド/テストしたり、自分でもローカルでcloneしてビルドするとか、ブランチを切ってテストするといったことをする。$GOPATH以下にpullしてきてテストする場合は良いのだが、別のところにcloneしてしまった場合。このときimport文が元のままだと、この、、、

import "github.com/wtnb75/xxxxx/subdir"

これはmasterのHEADなわけだから、subdir以下のAPIは変更される前であって、呼び出し元はcloneされた新しいものだ。結果として起きるのはビルドの失敗。テストができない。github flowにおいてはmasterは常にデプロイ可能な状態である、という原則がある。そこで質問だが、ビルドもできないものを好んでmasterにマージしたい人はいるだろうか?

だから、私としてはimportは、それが$GOPATH/src以下であっても相対パスで指定できて欲しいと思う。実際go getで取ってきたディレクトリそのままのところでgitで差分を見ながら作業するってのがそれなりに快適なので、それを邪魔するような機能はつけないで欲しいと思う。local import in non-local packageのエラー…それをエラーにする意義はないんじゃないかな。

おそらく現状だとテスト側としては正しいのはgo getで$GOPATH/src以下に持ってきて、git checkoutでブランチを手動で切り替えてテストを続ける、という一手間だろう。go getやgo buildではcloneが起きるときはmasterだけどあと修正されたかどうかは気にしないので、これでうまくいく。

ただ面倒っちゃ面倒だよね。

$GOPATH/src以下でlocal importができるようになれば解決する。

将棋 (3)

最近は子供たち側の初期配置をまともにして対戦しています。まあまだこの配置だと大人が勝つことが多い。長男に持ち駒を持たせたり金も落としはじめると大人が負けるかな。次男はまだそれよりは弱い。

将棋盤

ここまで来ましたね。たまに大人(私)がびっくりするような良い手を打ってきたりしますね。まあ私は全然強くないんですけどね。

WordPressの「スパムをすべて削除」って遅くない?

コメント画面の「スパムをすべて削除」する操作が全く戻ってこない。しびれを切らしてリロードするとちょっとは消えてるけど大部分が残ったまま。

このサイトにはほとんどコメントは来ないのだが、普通の設定(akismetで自動判定されたspamが1ヶ月で自動消去)でも常時数千件のspamが溜まっている。それでたまに全件削除しようとボタンを押してみるが…スピードが遅すぎる。何をやっているのか?

MySQLのDELETEが遅いのかも、と思って試しに以下のSQLを実行してみたら、瞬時に終わった。綺麗さっぱり。まあすぐに10件を越えてくるのだが。

DELETE FROM wp_comments WHERE comment_approved='spam'

WordPressは出しているSQL文がものすごく下品なものになっているに違いないな。どうせPHPで1件1件チマチマ消しているんだろう。自動削除というのもたぶんメチャクチャなSQLを発行していて無駄に重くなっていた可能性もあるな。

Macでdrone

  • Macでdockerを使うことができる
    • boot2dockerを使う。VirtualBoxで上がるTinyCoreLinuxでdockerが動く
  • droneはdockerを使えれば使えるはず

というわけで、Macでdroneを使ってみよう。dronedはLinuxでサーバを立ててgithub APIと連携させればいいとして、Macではcommit/push前のチェックとして、コマンドラインのdrone buildを使えるようにしたい、と私は考える。

  • brew install boot2docker
  • brew install docker
  • boot2docker download
  • boot2docker init
  • boot2docker start
  • eval $(boot2docker shellinit)
  • もしどこかでパスワードを聞かれたら「tcuser」と答える

ここまででdockerを使える状態になる。

  • brew install go
  • export GOPATH=$HOME/go
  • go get -d github.com/drone/drone/cmd/drone
  • 現状だと少し変更が必要
    • $GOPATH/src/github.com/drone/drone/pkg/build/docker/image.go
      • github.com/dotcloud/docker/utils → github.com/dotcloud/docker/pkg/parsers
      • utils → parsers
    • $GOPATH/src/github.com/drone/drone/pkg/plugin/notify/irc.go
    • func (i *IRC) Connect()の中身を削除
  • go build github.com/drone/drone/cmd/drone
  • go install github.com/drone/drone/cmd/drone
  • export PATH=$PATH:$GOPATH/bin

これでdroneを使える。試しにこんな.drone.ymlを置いて、、、

image: bradrydzewski/base
env:
 - TZ=Asia/Tokyo
script:
 - date
  • drone build

まあ初回はイメージのpullに時間がかかりますが、2回目以降はなかなかいい感じになります。

ソースに修正が必要だったのはまあ困るよね。golangのimport文は読み込むモジュールのバージョンを指定できないから、常にmasterのHEADを使うことになる。そして使っているライブラリのバージョンが上がって互換性がなくなったときにコンパイルできなくなる。サードパーティでそれを解決するウェブサービスがあったような気もするが、広まってないし自分もそのサイトを忘れてしまった。

droneのソースはアレな面があって、作者のbradrydzewskiのイメージであればubuntuユーザで実行されるが、それ以外のイメージを使うとrootで実行される。この判定はハードコードされている。

ActiveRecordのincrementはポンコツか?

SQLではカウンタのincrement/decrementは1つの文で済ますことになっている。

UPDATE table SET counter = counter + 1 WHERE ...

ActiveRecordのモデルにはincrementやdecrementの定義がある。そうか便利だなぁ…??

1.9.3-p545 :021 > Tbl.take.increment!(:counter, 2)
D, [2014-09-04T00:36:32.902849 #73566] DEBUG -- :   Tbl Load (0.1ms)  SELECT "tbls".* FROM "tbls" LIMIT 1
D, [2014-09-04T00:36:32.903201 #73566] DEBUG -- :    (0.0ms)  begin transaction
D, [2014-09-04T00:36:32.904809 #73566] DEBUG -- :   SQL (0.4ms)  UPDATE "tbls" SET "counter" = ?, "updated_at" = ? WHERE "tbls"."id" = 1  [["counter", 10], ["updated_at", 2014-09-03 15:36:32 UTC]]
D, [2014-09-04T00:36:32.914149 #73566] DEBUG -- :    (9.1ms)  commit transaction
 => true

SELECTで読んで、Rubyが足し算して、UPDATEで足した結果を書き出す…こんなのってないぞ。それをORマッパーがincrementと名乗るか普通?

正解は、モデルのクラスメソッドのupdate_countersを使う。これは複数のカウンタの操作が可能。第1引数のオブジェクトIDも複数書けるし、第2引数以降のカラム名も複数書ける。オブジェクトから呼べないのが使いにくいのを除けば、普通のSQLが発行される。まあ↓の例ではIDを求めるのにSELECTは発行されるが、UPDATE自体は普通にDB側で加算されてくれる。

1.9.3-p545 :022 > Tbl.update_counters(Tbl.take.id, :counter=>1)
D, [2014-09-04T00:37:01.497430 #73566] DEBUG -- :   Tbl Load (0.2ms)  SELECT "tbls".* FROM "tbls" LIMIT 1
D, [2014-09-04T00:37:01.507024 #73566] DEBUG -- :   SQL (9.0ms)  UPDATE "tbls" SET "counter" = COALESCE("counter", 0) + 1 WHERE "tbls"."id" = 1
 => 1
1.9.3-p545 :023 > Tbl.update_counters(Tbl.take.id, :counter=>1, :counter2=>2)
D, [2014-09-04T00:37:06.088688 #73566] DEBUG -- :   Tbl Load (0.2ms)  SELECT "tbls".* FROM "tbls" LIMIT 1
D, [2014-09-04T00:37:06.098522 #73566] DEBUG -- :   SQL (9.4ms)  UPDATE "tbls" SET "counter" = COALESCE("counter", 0) + 1, "counter2" = COALESCE("counter2", 0) + 2 WHERE "tbls"."id" = 1
 => 1

将棋 (2)

長男がついに将棋の必勝法を編み出しました。それがこの初期配置。

shogi_1

これで、じゃんけんに勝って先手を取る。私は基本的には必ずグーを出して先手を与えているので、飛車爆弾で5手で勝ちます。角まで5三を狙っているのはオーバーキルもいいところですね。何度かやって覚えたなと思ったので、試しにチョキを出して私が先手を貰ってみたところ、それだとまだ勝てないようでした。まあでもだいぶ攻め方も受け方も覚えてきたし、そろそろ自陣は普通の配置からやらせてみようかな…まだ早いかな?

王の近くを空けておくのは、それで何度か逃げ場がなくてやられて学習したんですね。

 

column(1), colrm(1)

join(1)の衝撃いまだ冷めやらぬ中…column(1), colrm(1)っていうコマンドもありますね。

まあこれらの使い方を覚えようとする前にawkを使うでしょうね。しかしもっと凄いのは、これらのコマンドを涼しい顔で使っている人が会社にいること…かな。なんであんたこれを当然のように使ってんだよ、っていうね。

将棋

子どもたちが将棋にハマっています。きっかけは次男がもらった公文の「スタディ将棋」。動ける方向が書いてあったり成ると赤くなったりする。今はもう普通の漢字の駒でも理解できています。少し前にはオセロにもハマってたんですが、片付けがなかなかできなくて母親に取り上げられてしまいました。将棋は今のところ大丈夫なようです。オセロはハンデをつけにくいからな。隅に置いてあげてもなんか違和感があるようで、たいていハンデなしでやってましたが、普通にやって大人が負けることもありました。どうにもオセロは好手と悪手の区別をつけられる気がしなかったな。

まあ将棋にハマっているとは言ってもまだ子供、「8枚落ち(王金歩のみ) vs. 残りの駒を自由に並び替え」対戦でもまだ大人に勝つのは稀ですが、歩も落としたり金を片方だけにしたりすると勝率が上がるようです。長男と次男だと長男のほうが強いな。さすがに次男はまだ幼稚園の子だからね。三男はまだ駒を勝手に持って握ってたり、動かしたり置いたりしてるだけ。そういやオセロもこいつのせいでそこら中に散らかったんだっけ…

平日は私が相手できる時間も限られていますから、一人でできる棋力向上と言えば詰将棋だろうと、試しに子供向けの詰将棋の本を与えてみたところ、毎日研究しているようです。子供向けだからもうちょっと大きい字でひらがな中心に書いてある本だと思ったら、普通に大人向けの言葉を使ってルビを振ってイラストがあるだけでした。語尾を子供向けにゴマカしたって、言い回しは普通に工夫がないから、小学1年生には分からないですよ。盤面も駒の形がなくて文字の向きで判断する必要がある。長男の読み方を見ていると、文章は見ないで盤面だけ見て考えているようです。しかしもう漢字の向きが分かるんですね。今やっている問題はまだ1手詰めでそんなに難しくない。

囲碁もやってみたいらしく、「スタディ囲碁」のチラシを見せてきまたりしますが、私は囲碁のルールわからんのでまだ相手できんのだよなー。

join(1)

SQLのJOINがどう…というエントリが続きましたが、Linuxのコマンドにもjoinがあり、これはテキストファイルでSQLのinner joinに似た操作ができるんです。私はcutは時に毎日のように使い、たまにpasteは使うことがあるけど、joinは使ったことがない。最近この操作が必要になり、一時的にSQLiteに入れてjoinさせるかawkで頑張るか、sortとuniqで2個カウントされたやつを選ぶか、どうするか熟慮した結果、awkのone-linerでしのいだことを思い出します。joinコマンドさえ知ってれば悩むことなんてなかったのに。

どうせ忘れるけどメモしておこう。MacOSにも入ってます。いやー便利な世の中に…いや、どうせ忘れると思うけどね。