おそらくはそれさえも平凡な日々

ghqを使っていても(使っていなくても)goimportsを爆速にする

ghqを使ったローカルリポジトリの統一的・効率的な管理について」というエントリで書かれているように、 ghq.root$GOPATH/src を一緒にする運用で長年やってきて、goimports が遅いことに少し困っていたのだが、以下でめちゃくちゃ快適になった。

どれくらい快適になったかというと、エディタの保存時に goimports を気兼ねなく実行させられるようになったくらい。以下解説。

.goimportsignoreを使う

最近の goimports には $GOPATH/src/.goimportsignore という除外リスト機能がある。ここにインポート対象外にしたいディレクトリを $GOPATH/src 以下の相対パスで記述すると、 goimports はそこを無視する。 ghq を使って、 $GOPATH/src 以下にGo言語以外のリポジトリもガンガンcloneしまくっている自分としては非常に助かる。

goimports-update-ignore で自動生成をする

もちろん、この .goimportsignore をイチイチ記述するのは現実的ではないので、 goimports-update-ignore というツールで自動生成する。go get でインストールできる。

% go get github.com/pwaller/goimports-update-ignore

goimports-update-ignore はGo以外の言語のディレクトリを抽出して、 .goimportsignore に追記してくれるコマンド。以下のように実行する。

% goimports-update-ignore -max-depth 5

-max-depth オプションはデフォルトで3だが、ちょっと足りないので5にしている。実行時間はちょっとかかるのでちょっと待とう。僕の手元だと40秒程度。

goimports-update-ignore の地味に良い点は、自動生成部分以外に自分が除外したいディレクトリの記述をしておけるところです。例えば、僕はGoのソースコードを読むために github.com/golang/go をcloneしていますが、これは当然 goimports の対象になって欲しくない。ただし、これは当たり前ですがGo言語のプロジェクトなので、 goimports-update-ignore には除外対象とはみなされません。

ですので、僕の .goimportsignore の冒頭は以下のようになっています。

github.com/golang
# Generated by goimports-update-ignore 2018-08-28 15:54:41.607268 +0000 UTC
anonscm.debian.org
cloud.google.com/go/bigtable/testdata
cloud.google.com/go/datastore/testdata
(snip)

先頭の github.com/golang は手で書き加えたものですが、この時 # Generated... 以前の部分は、 goimports-update-ignore を再度実行しても書き換わることがないのです。ですので、ここに自分が個別に除外したいディレクトリを書いておくことができます。

僕は goimports-update-ignore は気が向いたときに再実行して、 .goimportsignore を再生成するようにしています。これで .goimportsignore を活用できるようになり、 goimports が現実的な速度で動くようになりました。

最新の dragon-imports を使う

「現実的な速度」で動くようになったと前項で書きましたが、それでもGoライブラリの数が増えると挙動がもっさりしてきます。そこで、更に goimports を速くする方法として、 dragon-imports という裏技的なツールがあります。

これは、今ローカルに入っているライブラリ情報を抽出して、 goimports コマンド自体を作り直してしまうというなかなか豪快なツールです。詳しくは作者の「goimportsを高速化するdragon-importsコマンドをつくった」というエントリを参照ください。

こちらも go get でインストールして、 dragon-imports コマンドをおもむろに実行するだけです。

% go get github.com/monochromegane/dragon-imports/cmd/dragon-imports
% dragon-imports

これで、 goimports コマンドが作りなおされ、実行速度が爆速になりました。こちらも気が向いたタイミングで再実行して作り直すと良いでしょう。

実は、 dragon-imports は近年のGoのフィーチャーに追随できていないところがありました。そこで、最近僕が盆栽活動のように幾つかpull requestを送り、取り込んでもらいました。これで、 .goimportsignore を見るようになったり、 vendor/ を無視するようになり精度が向上しました。

結果、以前よりも安定しつつも更に爆速になり、出たばかりのGo1.11でもお使いいただけるようになっています。もし古いものをお使いの場合は最新をお試しください。

ということで、これでかなりGoの開発が快適になりました。

ちなみに、 dragon-imports は「みんなのGo言語」で僕が書いた章の中で紹介させてもらっていたので、開発にコントリビュートできたのは嬉しかったです。

みんなのGo言語【現場で使える実践テクニック】

中小規模のOSSにおけるバージョニング戦略について

TL;DR

本編

OSSのバージョニングについての個人的な意識について書きます。「中小規模」というのは、個人の趣味だったり、企業でやっていたりしてもそんなにリソースをかけられない場合くらいの規模感、つまり僕自身がメンテナンスに関わっているようなソフトウェアの規模感でもあります。とは言え、リソースが十分なプロジェクトなどほとんど無いと言えるので、多くの場合に当てはまるでしょう。

互換は壊すな!…なるべく

そもそも互換は壊すなよ、と言う話。互換は壊さないほうがいい。これに異を唱える人はいないでしょう。

「互換を壊したらメジャーバージョンを上げればいい」ではなくて「絶対に壊さない」くらいの意識でAPI設計をする。それくらい慎重に設計したとしても互換を壊さないといけない日は往々にしてやってくる。

非互換修正は自分に跳ね返ってくる

そもそも、互換を壊すことはユーザーを失うことです。非互換修正に追随するコストをユーザーに払わせる以上のメリットをユーザーに示せるかどうかという当然のトレードオフ以前に、これまでせっかく積み上げてきた既存ユーザーの一部を間違いなく失うという事実は受け止めなければならない。

メジャーなソフトウェアであればいざしらず、システムの片隅で使われるようなソフトウェア、つまり僕たちが作っているようなソフトウェアに非互換修正が入り、システム全体が動かなくなるとしたらたまったものではないでしょう。

それが、数少ないユーザーの全てを失う要因になるかもしれません。だからこそ、非互換修正をしないように注意を払う必要がある。これはソフトウェアとしての協調性の話でもあります。

もちろん、自分以外誰も使っていないようなソフトウェアであればその限りではありません。

それでも互換は壊れる

もちろん人間は間違えるのでイケてないインターフェースを作ってしまうことは避けられません。また、過去には良いとされていたインターフェースも、時代が変わってイマイチになることもなる。それに、人間が進化してより良いインターフェースを発明することもあります。

脱線しますが、そもそもインターフェース設計は難しいものです。semver的には、バージョン0の間は互換を壊してもいいことになっているので、そこで実際にユーザーに利用してもらい、トライアンドエラーを繰り返しながらインターフェースを固めていくのは良いやり方です。

話を戻すと、いくら初期設計時に慎重を期したとしても後方互換を壊したくなることはあります。とは言え、本当に壊す時にはそれ以上に慎重になるべきです。新インターフェースのほうが今より間違いなく良いという確信を持ってから互換を壊すべきです。

「新インターフェースのほうが今より間違いなく良いという確信」とはつまり、現行バージョンを使い続けるユーザーを最終的には切り捨てる覚悟をするということです。「新インターフェースのほうが間違いなく良いから、全員移行すべき」だと考える必要がある。新旧インターフェースどっちでも良いのであれば、混乱を招くだけなので、非互換修正すべきではないでしょう。

その際、移行パスを含めて事前アナウンスをしっかりやることが理想です。semver的にはメジャーバージョンを上げることになるでしょう。

複数バージョンをメンテしようなどとはゆめゆめ思うべからず

「メジャーバージョンを上げたとしても、旧バージョンもちゃんとメンテすればいい」という意見もあるかも知れませんが、それはほとんどの場合非現実的な理想論で、やるべきでもないと考えます。気軽に非互換変更をしてメジャーバージョンを上げ、逆にここを真面目に考えすぎて消耗してしまうケースが結構あると感じたことがこのエントリを書くモチベーションになっています。

ソフトウェア開発における再優先事項はメインラインに手を入れ続けることであり、旧バージョンのメンテナンスに労力を費やすことは、よほどリソースに余裕が無い限りは意味がありません。意味がないどころか、メンテナのモチベーションに逆効果になることも多いでしょう。

もちろん移行猶予期間として、一定の間、旧バージョンのメンテナンス期間を設けることは誠実ですし、ユーザーが減ることも防ぐでしょう。リソースに余裕があればやれるに越したことはありませんが、余裕があればの話です。旧バージョンのメンテナンスという、あまりモチベーションが上がらなさそうなタスクをやりたい人がいるかどうかも大事な観点です。

それ以外の理由で旧バージョンのメンテを期限を設けず継続することは無駄だと考えます。旧バージョンにも使い続けられる良さがあるのであれば、新バージョンは名前を変えるなどして、別ソフトウェアとしてプロジェクトを分けてしまった方が良いでしょう。

結果として古い方のソフトウェアのメンテナンスは滞るかも知れません。それでも使い続けたい人がいるのであれば、その誰かにメンテナンスを引き継いでしまうこともできます。そっちの方が、旧バージョンのメンテナンスを押し付けられることより、モチベーション高く引き受けてもらえるのではないでしょうか。

まとめ

話が戻ってきますが、結局の所、非互換修正は作者側も大きな痛みを伴う決断です。それらを踏まえて、非互換修正を入れるかどうかの判断をするべきでしょう。

セマンティックバージョニングに対しても思うところがあるのでなにか書こうと思ってましたが、今回はここまで。とは言え誤解を生まないように書いておくと、semver自体は悪いものだとは決して思っていません。

「UNIXという考え方」における「沈黙は金」は常に正しいか

みんな大好き「UNIXという考え方」に「沈黙は金」という項目があって「コマンドが成功した場合には余計な出力をすべきではない」ということが書いてあります。

ユーザーに会話調で話しかけることがユーザーを助けることだ、と考えているプログラマが多すぎる。UNIXは一般に「ドライ」で、ただ「事実」だけを伝える。

まず、この項目はこの本の中では「10の小定理」というものの中に含まれるもので、小定理については必ずしも常に正しいとは限らないというような注意書きが書かれていることを先に断っておきます。

コマンドラインツールはデフォルトでは余計な出力をすべきではないというのは基本同意です。詳細な途中経過のログを流したいのであれば、 --verbose オプションが実装され、それが利用されるべきでしょう。それに最近作られたツールは出力を出し過ぎで煩わしく感じることも多くなってきました。

ただ、それは少し偏屈な技術者の思考じゃないのかとも思うようにもなりました。とっつきやすさのために、少しだけフレンドリーなログを出してあげるのもアリなのではないでしょうか。実際、初めてコマンドラインを触ったとき、あまりにもぶっきらぼうでとっつきづらいと感じる人がほとんどでしょう。 svc とか初めて触ったときビビりませんでしたか?

なので、標準ではログを吐きつつ、 --silent オプションで出力を抑制するようなパターンもありだと思います。

ただ、ここで大事なのは「ログ的な出力には必ず標準エラー出力を使う」ということです。標準出力は他のプログラムに受け渡されうるデータを出力する目的のみに利用すべきだからです。これは、定理9の「すべてのプログラムをフィルタにする」にも書かれていることで、これは完全に正しいです。

ところで、最近Go製のツールによくあるんですが、 --help の出力が標準エラー出力に出るのはページャーに渡しづらくてめんどくないすか? 2>&1 | less とかやるのがダルい。これは「明示的にヘルプ出力が望まれているのだから標準出力に出すべき」派です。

UNIXという考え方

git-set-mtimeをリリースしました

https://github.com/Songmu/git-set-mtime

ローカルファイルとディレクトリのmtimeをgitの最新コミット日時に書き換えてくれるツール。rosylillyが作っていたやつを以前から使わせてもらっていたのだけど、それをforkしたままバイナリリリースもしました

% go get github.com/Songmu/git-set-mtime

でもインストール可能です。オリジナルに対する変更点は以下のとおりです。

特にruby版削除は大きな変更になってしまうので、fork側で別途メンテナンスさせてもらうことにしました。ライセンスなどは元のライセンスを残しつつ適宜アップデートしているつもりですが、変なところがあったらご指摘ください。

たまに便利です。ご利用ください。

blogsync v0.9.1をリリースしました

https://github.com/motemen/blogsync

はてなブログ用の便利コマンドラインツールであるところの、blogsync v0.9.1をリリースしました。最近は日本語READMEを載せたり、バイナリリリースも作ってあるので便利にご利用いただけます。

直近の大きなアップデートは以下のとおりです。

omit_domain によるダウンロードパスの変更

omit_domain という設定項目が追加されました。この設定をオンにすると、そのブログをダウンロードしてきた際に、ダウンロードパスにドメインが含まれなくなります。

yourblog.example.com:
  local_root: /path/to/myblog
  usename: xxx
  ...

例えば、上記の設定ですと、 /path/to/myblog/yourblog.example.com/ 配下にblogの内容がダウンロードされます。

このとき、 yourblog.example.com/ というディレクトリが掘られてしまうのですが、それが煩わしい場合は、 omit_domain の出番です。

yourblog.example.com:
  omit_domain: true
  local_root: /path/to/myblog
  usename: xxx
  ...

このように、omit_domain を設定すると、 /path/to/myblog/ 直下にブログをダウンロードできます。

設定ファイルの読み込みの改善

blogsyncは、ローカル設定(./blogsync.yaml)とグローバル設定(~/.config/blogsync/config.yaml)の2つの設定ファイルを持つことができます。

最新バージョンでは、ローカル設定とグローバル設定に同じブログドメインが設定されている場合、その設定がいい感じにマージされるようになりました。同一の設定キーが設定されている場合は、ローカル設定が優先となります。

まとめ

実は、Mackerel の各種ドキュメントは、はてなブログを利用しており、版管理のためにblogsyncとGitHubを組み合わせています

というように、blogsyncは一部ではかなり実績のあるツールですのでぜひお試しください。

もう少しテスト書くなどして、バージョンv1.0.0を出したいと考えています。

DBIx::Sunny 0.9991をリリースしました

https://metacpan.org/release/SONGMU/DBIx-Sunny-0.9991

みんなだいすき、安全安心のkazeburo wareであるところのDBIx::Sunnyですが、この度、バージョン0.9991をリリースしました。pull requestを送りまくっていたら、コラボを頂いたといういつもの流れです。

最近の大きな更新は以下の通りです。

特にNamed placeholderはアツくて、以下のようにクエリ発行系のメソッドの第2引数にhashrefを渡すと利用できます。便利。

$dbh->select_all('SELECT * FROM users WHERE id IN (:ids) AND status = :status', {
    ids    => [1,2,3],
    status => 'active',
});
#SQL: 'SELECT * FROM users WHERE id IN (?,?,?) AND status = ?'
#@BIND: (1, 2, 3, 'active')

内部的には、cho45氏作のSQL::NamedPlaceholderを利用しています。このモジュールは、はてなブログ内部でも開発当初から利用されていることもあり、長らく実績のあるモジュールなので安心してご利用いただけます。

最近大きめのリファクタリングを入れたので、一旦0.9991というバージョン番号で出してますが、しばらくおいて問題なさそうならバージョン1.0000を出そうと考えています。

より便利になった、DBIx::Sunnyをぜひご利用ください。

ちなみに、Perlでversion.pmを使わないでバージョン宣言をする場合、小数点以下は4桁にするのが良いんじゃないかと最近は考えています。ちなみにPlackもそうなってました。

それは、Perlのバージョン番号の変換ルールの仕様で、マイナーバージョンとパッチバージョンは小数点以下3桁ごとに区切られるからです。

つまり、0.9991は、v0.999.100と等価です。パッチバージョンをまともに3桁割り当てると、小数点以下6桁になって見づらくなってしまうので4桁にするのが穏当なのではないか。

ところで、version.pmを使わないでバージョン宣言をするのは、そのモジュールがPerl5.8系をサポート対象とする場合の話で、Perl5.8系を切り捨てて良いのであれば、素直に、version.pmを使ったほうが良いでしょう。

IoT時代の自宅ネットワーク

タイトルは釣りです。IPv6の話とかも出てきません。以下の様な話です。

OCNからNuro光に乗り換えた

10年近くOCNを使っていたのをNuro光に乗り換えた。自宅のマンションでキャンペーンやってて簡単に乗り換えられそうだし、大分安くなるので契約した。

ただ、正直、ブロッキング問題がなかったら今回の変更には至らなかったと思う。

ネット回線は金額よりも品質のほうが大事なのでこれまで選定はコンサバに振ってたし、それでOCNの回線には大きな不満もなかった。乗り換えることで品質がどうなるかは未知数でリスキーであることもあり、単に安くなるだけだったら今回の乗り換えにはいたらなかったと思う。

なので、ブロッキング問題が今回の背中を押すきっかけにはなったと思う。ただ、そこまで強いポリシーを持っているわけでもないので、キャンペーンをやってなかったらそのままOCNを使い続けていたと思う。

当たりルーターを引いた

Nuro光が回線工事時に持ってくるルーターは何種類かあるのだが、それを指定することはできない。工事が終わった後に別のやつに変えてもらうように手配をすることはできるらしい。ただし有料。

今回は、運良く当たりルーターを持ってきてもらうことができた。高速無線通信可能なIEEE802.11ac対応のEchoLife HG8045Qという新モデル。

もう一つ、ZXHN F660Aという、IEEE802.11ac対応の新しいルーターがあるのだけどこっちが来ていたらかなり困ることになって危なかった。というのも、こちらは同時接続機器数が10台なのだ。

ご家庭のIP機器の多さ

10台あれば充分と思われるかもしれないが、実は全然充分ではない。僕が職業柄多めだというのもありそうだが、とはいえガジェッターでもないのでメチャクチャ多いわけでもないと思う。以下に書き出してみる。

実に17台。全部が常時つながってるわけではないが、それでも10台では全然足りない。

ということで、今や色々なものが簡単にインターネットに繋がる時代なので、家庭内でもIPを持っている機器は想像しているよりずっと多い。ルーターのIP払い出し一覧を眺めていてびっくりした。家庭ですらこの有様なのですからオフィスネットワークはマジで大変そうですね…。

エンジニアHubに「Goらしさ」についての記事を寄稿しました

「Go言語らしさ」とは何か? Simplicityの哲学を理解し、Go Wayに沿った開発を進めることの良さ

こちら寄稿させていただきました。

「Goらしさ」についての記事を書いて欲しいという依頼を受けたときは、正直これはかなり身に余るなと思いました。同僚のid:motemenを始め、これを書くのにもっと相応しい人がいるように感じたし、難易度も非常に高いので。恐れ多さがすごい。

断ろうとも思ったのですが、依頼を受けているうちが華だというのもあるので、頑張って受けることにしました。妻には「なんでそんな執筆受けちゃったの…?」と詰められました。

案の定執筆は難航し、ストーリーが降りてくるまで本当に時間がかかった。編集の方々は、色々スケジュールを融通いただいて本当にありがとうございました。

なんとか原稿が書き上がり、評判も上々で一安心というところです。まだ未読の方でGoに興味ある方はぜひご覧ください。この度はいい機会をいただきまして、本当にありがとうございましたという気持ちです。

JSONを使ってコマンドラインを動的に組み立てて実行するjfillを作った

https://github.com/Songmu/jfill

標準入力からJSONを受け取り、その値を使ってコマンドラインを組み立てて実行するユーティリティです。以下のような具合です。

インストール

% go get github.com/Songmu/jfill/cmd/jfill

もしくはGitHub Releasesからご利用ください。

使い方

% echo '{"name":"jfill"}' | jfill echo Hello {{name}}!
Hello jfill!

{{name}} の部分がプレースホルダーです。それがJSONの入力を元に置換され実行されています。

プレースホルダー内には以下のようにデフォルト値を指定することも可能です。

% echo '{}' | jfill echo Hello {{name:jfill}}!
Hello jfill!

{{name:jfill}} のコロン以降の"jfill"の部分がデフォルト値です。プレースホルダーにデフォルト値が指定されておらず、置換対象のキーがJSON内に存在しない場合、jfillは失敗します。

深い構造のJSONに対応するために、プレースホルダー内にXPathのような文字列を指定することができます。内部的には github.com/mattn/go-scan を利用しています。

% echo '{"Songmu":{"age":38}}' | jfill echo Songmu is {{/Songmu/age}} years old.
Songmu is 38 years old.

作った動機

主にMackerelのチェック監視で、引数を動的に指定させる仕組みが欲しかったのが動機。何らかのパラメーターストアとjfillと連携させれば動的に引数を指定できます。以下はmackerel-agent.confのサンプル。

[plugin.checks.unicorn-worker]
command = '''
  echo '{"threshold":'$(getconf _NPROCESSORS_ONLN)'}' \
  | jfill check-procs -P unicorn --critical-under={{threshold:5}}
'''

少しわざとらしい例ですが、この場合、CPUコア数よりもワーカー数が少なくなった時にアラートが上がります。JSONが正しく出力できなかったときにエラーになるのを防ぐためにデフォルト値を指定するのが推奨です。

ConsulのKVSやMackerelのmetadata APIと組み合わせるのも賢い方法です。ちなみにMackerelのmetadata APIを利用する際に毎分アクセスするのは効率が悪いため、何らかのキャッシュ機構を使うことを強く推奨します。(お願いします…!

環境変数を動的にセットしたい場合

env コマンドと組み合わせると良いでしょう。

% echo '{"lang":"ja_JP.UTF-8"}' | jfill env LANG={{lang}} date
2018年 6月12日 火曜日 01時27分28秒 JST

fillinとの違い

https://github.com/itchyny/fillin というものがありまして、僕も便利に使っているのですが、ユースケースが違うため、別で作ることにしました。

まとめ

ぜひご利用ください!仕組み上、結構危ないことができてしまうため、ご利用は計画的に!

あわせて読みたい

WEB+DB PRESS Vol.104 の Perl Hackers Hubに「Minillaを使ったモダンなCPANモジュール開発」という記事を寄稿しました

記念すべきPerl Hackers Hub第50回にMinillaに関する記事を寄稿させてもらいました。Minillaは、日本国内ではデファクトとなっているCPANモジュールのオーサリングツールですが、まだ案外Perl Hackers Hubで取り上げられてなかったので、書かせてもらうことにしました。

WEB+DB PRESS Vol.104

Minillaはその作法に従えば非常に高速にCPANモジュールの開発が可能です。逆にお作法に従わないと躓いてしまうこともあるため、それらを丁寧に解説し、誰でもMinillaでCPANモジュール開発ができるようになることを目指した記事になっています。

内容は、Minillaを使ってモジュールを開発してCPANにアップロードするまでの一通りのフローの解説と、それに加えて社内モジュールのようなプライベートモジュールを開発時にMinillaを利用する方法について取り上げています。特にCPANモジュールのファイル配置ルールや、依存ライブラリの自動抽出に関しては力を入れて書きました。

Minilla解説の決定版とも言える内容となっております。案外知られていないこともあると思うので是非お手にとってみてご確認ください。

今回、牧さんのレビューにはかなり助けられました。特に初稿の段階では内容を詰め込もうとするあまり、ストーリがすっ飛んでしまっており、牧さんの的確な指摘のもとストーリーを肉付けすることができました。

また、稲尾さんの全体の進行や編集は非常にスムースで快適に執筆をおこなうことができました。4年前に執筆させていただいた時も十分にやりやすかったのですが、更に進化しており「快適」とも言えるレベルになっていたのは驚きました。

GitHub上のプライベートリポジトリ上でMarkdownで原稿執筆をおこなうのですが、まず、稲尾さんが注意書きを含めたプロジェクトの雛形一式をpushするところから始まります。あとは、稲尾さんが的確なタイミングで各執筆フェーズに対応するissueを挙げ、それにレビュアーや筆者が対応し、issueが全て閉じられるとなんと執筆が完了しているという具合でした。

さて、Minillaは基本的にはコンフィギュレーションフリーで使えますが、細かい設定を工夫することにより多くのユースケースをカバーでき、かゆいところにも手が届く作りになっています。今回の記事でもう少しそれらの細かいユースケースに対しても取り上げ、解説したかったのですが、とりとめもなくなってしまうので削ったものがあります。例えば以下の様なものがありますが、そのうちどこかで解説するかもしれません。