読者です 読者をやめる 読者になる 読者になる

bundle execを殺す

「バグの希釈」の項目を見て思い出した。

2015年に書いたコードの中で、一番意味不明で気が利いてたなーと自分では思っているやつは「lib/bundler/setup.rbに空のファイルを置く」というものだったりする。会社で少し説明したんだけど、あまりうけなかったのでここに書いておこう。

ユビレジで販売している製品の中にユビレジエクステンションというのがあって、簡単に言うとRaspberry Piに電源とWi-FiアダプタとUSBハブを付けたもので、HTTPでJSONをPOSTするとレシートが印刷できる機械になっている。ふつーにDebianが動いていて、Rubyで書いたHTTPサーバとかLED点滅のためのdaemonとかレシート印刷のためのdaemonとかが動いている。問題は、特定の処理でHTTPサーバとかを再起動するときに、なんか上手く行かない子がいることが、出荷後に判明したことだった。調べてみると、再起動が上手くいかないというのは、タイムアウトしていることがわかった。

なんで上手くいく子と上手くいかない子がいるのかは良くわかっていない。SDカードを読み書きしながら動作しているので、なんかの拍子にSDカードがなんかダメになるんだと思う。SDカードをddでコピーして試すと平気で動いたりする。(これは本題ではない。)

さて、再起動と一言で言ってもなにに時間がかかっているのだろうか。主にprintfデバッグなどを繰り返したところ、Rubyプログラムの先頭にたどり着くまでに時間がかかっていることがわかった。どうしようもないじゃんそれ……とかいいつついろいろ試行錯誤していると、

$ bundle exec ruby -e "puts :hello"

と、

$ ruby -e "puts :hello"

で実行時間が大きく違うことがわかる。後者の方が速い。まあ当たり前感がある。Bundlerを起動してからrubyを起動するのと、rubyだけ起動するのでは、後者の方が速いのは道理だ。そして、出荷済のバージョンではうっかりbundle execしているのだった。

また、普通にGemfileとかGemfile.lockとかがある場所でbunde exec複数同時に実行するとめちゃくちゃ遅くなることもわかった。きっと、複数rubyが一斉にbundler経由でgemをロードすると、CPUを使いすぎるとかディスクアクセスが多すぎるとかで、なかなか終わらないんだろう。ちなみにbundle execしないで3つのrubyを実行すると、あんまり遅くない。(「めちゃくちゃ」というのは「3つ同時に起動すると10倍時間がかかるようになる」くらいの感じ。「あんまり」というのは、3〜4倍くらいの感じ。)

$ bundle exec ruby -e "puts :hello" & bundle exec ruby -e "puts :hello" & bundle exec ruby -e "puts :hello"

これで、「普通に起動したときには上手く起動するのに、(特定の場面で)サービスを再起動したときには上手くいかない」理由もわかった。普通に起動するときには、LED点滅daemonだけは先に起動してから(起動中にLEDを点滅させたいので)レシート印刷daemonとHTTPサーバを起動するが、問題となっている再起動のときには3つを一斉に起動している。2つ同時起動の場合はなんとかタイムアウトせずにすむが、3つだとダメなことがある、そんなラインにタイムアウトを設定してしまったのだろう。

どうやって直したら良いだろうか。いくつか方法がある。

  • サービス起動時のbundle execをやめる
  • サービスを3つ同時に再起動しないようにする
  • タイムアウトを長くする

どれでも良い。簡単に直せる。

……まだ出荷してなかったらね。そして既に出荷は開始されているのだった。

実は、こういうこともあろうかと、最近のバージョンにはiPadアプリ経由で更新ができる仕組みを仕込んである。あるんだけど、上に挙げたやつは、どれもソフトウェアアップデートでは更新ができないファイルに書かれたプログラムなのだった。うーむ。

どうなっているのか、もう少しちゃんと説明しよう。

  • アプリケーションの本体は1個のディレクトリにすべて格納されている。binとかlibとかvendorとかがあって、/var/ubiregi/1.3とかに保存されて、/var/ubiregi/currentみたいなリンクを張る。
  • /etc/init.dに置かれた起動スクリプトbundle exec ruby -I /var/ubiregi/current/lib /var/ubiregi/current/bin/a.rbなどとしてサービスを起動する
  • ソフトウェアアップデートは/usr/local/ubiregi以下に展開すると良い感じになるようなディレクトリをtgzにしたものの形で処理される
  • ソフトウェアアップデートはファイルの展開しかしないので、/etc/init.d以下のファイルを変更することはできない
  • ソフトウェアアップデートはソフトウェアアップデートdaemonによって処理され、このソフトウェアアップデートdaemonをソフトウェアアップデートすることはできない

そういうわけで「bundle execで実行されたアプリケーションが、rubyが実行されるまでの間に、bundle execをなかったことにする」そんな方法がないか考えることになった。(回答は、LOAD_PATHが通っている場所にbundler/setup.rbを置くことである。最初に書いた通り。)

bundle execがやっていることを見ると、次の二つである。

  1. bundle execで実行する実行ファイルをGemfile.lockを見ながら探す
  2. RUBYOPT環境変数-rbundler/setupを設定する

今回は1.は関係ない。RUBYOPTってなんだったっけ。

Rubyインタプリタにデフォルトで渡すオプションを指定します。

環境変数 (Ruby 2.1.0)

なるほど。そして-rrequireだ。つまり、bundle execすると

require 'bundler/setup'

と書かなくて良くなるわけだ。ってことはlib/bundler/setup.rbに空のファイルを置いておけば、Bundlerの処理をスキップできるのではないだろうか。問題は、このRUBYOPTコマンドライン引数の-Iのどっちが先に処理されるかである。(サービス起動のコマンドをもう一度。)

$ bundle exec ruby -I /var/ubiregi/current/lib /var/ubiregi/current/bin/a.rb

-Iの処理がRUBYOPTよりも後なら、-rbundler/setupは標準ライブラリを見に行ってしまうので、本物のbundler/setup.rbをロードしてしまう。-Iの処理が先なら、/var/ubiregi/current/lib/bundler/setup.rbをロードするので、アプリケーションで乗っ取ることができる。rubyのマニュアルにはなんとも書いてないので、実際に実行して試してみるしかない。

-Iが先だった。

というわけで、無事Bundlerを無効にする方法が見つかって、ソフトウェアアップデートによってこの問題は解決されたのだった。bundler/setupがなくなるので、Bundlerの手を借りずにgemをロードしないといけないけど、それはStandaloneモードで解決できる。

--standalone[=<list>]

bundle-install(1) - Install the dependencies specified in your Gemfile

(ちなみにStandaloneモードだと起動がかなり速いので、Gemfile.lockの処理がなんか変なことやってて遅い気がする。わからないけど。)

そういうわけで、空のファイルを追加する謎のコミットによって問題が解決されたのだった。(うっかり不必要にbundle execしてたのが本当の問題なんだけど。)あと、あんまりやらないとは思うけど、うっかりbundler/setup.rbみたいなファイルを作るとbundle execが動かなくなるかもしれないので、注意しましょう。

コーディング面接の例

プログラマの面接をするときには実際にコーディングをしてもらうべきという話は良く聞くが、もうちょっと細かくどういうお題を出したら良いかとか、どういう風に評価したら良いかとかの話はあんまり聞かない気がする。せっかくなので、ユビレジでの面接で私がコーディングについて確認するときのパターンを、いくつか紹介してみようと思う。

実際にコードを書いてもらうパターン

候補者がどのくらいプログラミングできそうかの予備情報がない場合に、簡単なアルゴリズムを書いてもらうことが多い。例としては、

  • Linked Listを書いてください
  • Stackを書いてください

など。ここで、おもむろに

int main(int argc, char* argv[]) {

などと書き始める人は、あまり良い印象をもたれない。

class Stack

などと書き始める人は上よりは期待できる。

このとき、わざと出題で詳細をあまり明らかにしないことも多い。言語や環境によって、事前にもっと細かい仕様を確認する必要があることもあるはずだけど、そういうのに気が回ることを示せるととても良いと思う。けど、あんまり重視はしていない。基本的には、プログラムを書くことができるかどうかに注目している。

RubyRailsが書けることが前提の候補者には、典型的な構造を持つRailsのモデルについて聞くこともある。「お会計に対応するCheckoutというモデルがあって、そこに顧客を保存したい」といったお題を出す。

ここで、

  • checkouts.customer_idを追加する

みたいな議論ができるかどうかを確認する。その後、「Rubyで書けます?」みたいな話をして、

class Checkout
  belongs_to :customer
end

などとすぐにそらで書ければ良い。これは基本的なレベルなので、3秒以上詰まる人は低評価。

さらに、「でも顧客が二人いることもあるんだけど」みたいな発展をする。

  • checkouts_customersみたいな関連テーブルを追加する

みたいな話ができれば良い。has_many :throughがすぐにそらで書けなくても許容範囲内という判断は、しても良いと思う。

その人が書いたコードについて質問するパターン

GitHubなどで意味があるコードが見られる人の場合は、コードレビュー的なことをやることがある。(「Railsの練習」みたいなリポジトリは相手にしない。)

例えば、

  • この正規表現はどういう意味?この{1,3}ってなに?
  • ここのsetTimeoutいる?

などと聞く。

ちゃんと自分の書いたコードの意味をわかっているかどうかを聞くのが目的。

  • 全然見当違いの回答をする人
  • なにも答えられない人

は評価が悪い。

  • ちゃんと説明できる人
  • 忘れていたとしても(古いコードだったら「今ならそうは書かない」みたいなことはよくあることだ)、他の正しい書き方を説明できる人
  • @soutaroの勘違いであることを説得できる人

は良い評価ができる。

ユビレジのデバッグをやってもらうパターン

面接の最初に製品と会社の説明をしているときなど、じっくりユビレジの挙動を見ていると、変な挙動に気付くことがある。(もしくは、原因が判明している不具合を含むバージョンを使ってデモをする。)

こういうときに「これ変ですよねー」みたいに聞いてみて、どういう返答が得られるかを調べることがある。あんまり変なバグだと難しすぎるので、典型的な間違いを犯しているコードであると推測できるような不具合について聞くのが良い。(実際の間違いが予測できないものであったとしてもかまわないが、挙動を見た瞬間に予想できるものではないと難しすぎる。)

私の例では、

  • Ajaxのリクエストが連続して発行された場合に、先に発行されたリクエストで結果が上書きされる
  • イベントハンドラを二重に登録してしまったために、イベントへのアクションが二重に実行される

などの不具合で議論したことがある。

これらのありがちな間違いの確認を提案できる人は、当該の環境での経験がある程度あると期待できる。(本当の原因が全然違うもので、推測が見当違いだったとしても、それ自体はマイナスにはならない。)

次に「どうやって確認したらいいですかねー」などと、開発環境やデバッグの手順について聞くこともある。デバッグプリントよりもデバッガの使用を提案できる方が、評価が高い。

こういう方法をやっているという話を他に聞いたことがないので、適切な方法でないかもしれない。

コーディングについて聞かないパターン

GitHubでスターを集めまくってる候補者の場合や、よく知っている開発者からの強い推薦がある候補者の場合など、候補者が十分にコードを書く能力があることに確信が持てる場合は、コーディングをスキップすることがある。

この場合は、ユビレジで抱えている技術的な問題について議論したり、適当なライブラリについて質問したりして、一緒に上手く仕事ができそうかどうか、気分よく仕事をしてもらえそうかどうかについて考えることになる。仕事で発生するコミュニケーションのほとんどは、

  • Backboneが辛いんだけど、どうしたら良いか
  • Railsparamsが壊れている気がするんだけど、どうなってんのこれ?
  • MySQLが遅いのをなんとかする方法はあるか

みたいな感じで議論することなので、ある程度抽象的な議論をして上手くやれそうか見るのには意味があると思う。

基本的には議論になるかどうか、好みが合うかどうかを見れば良い。議論にならないパターンはいくつかあるけど、抽象化のレベルが合わないものを見落とさないように注意した方が良い。一般的すぎる議論とか具体的すぎる議論になる場合は、その候補者とあなたの興味の範囲が違いすぎている。好みが合わない場合はしょうがない。

なにがわかるのか

結局のところ、いつもやっている仕事を上手く一緒にできそうかどうか予想している。面接では、

  1. 十分なスピードでコードを書くことができるか
  2. コードレビューについてはどうか
  3. 典型的な誤りを正しい手順で調査できるか
  4. 開発の過程で実際に発生する議論が上手くかみ合うかどうか

などについて予想する材料を集めている。

ちなみに、1.については実は明らかにテストが不十分で、いつも私が使っている問題は簡単すぎてその人の腕力・体力がどのくらいあるかは上手く予想するのが難しい。

  • もうちょっと難しい問題でコーディングしてもらう
  • もうちょっと複雑な構造を持つプログラムを、あらかじめ書いてきてもらう

といった方法が考えられると思う。

参考文献

こういう本もある。例題を探すために買った。(英語版を買ったけど、和訳も結構前に出てたことに気付いた……)

世界で闘うプログラミング力を鍛える150問 ~トップIT企業のプログラマになるための本~

世界で闘うプログラミング力を鍛える150問 ~トップIT企業のプログラマになるための本~


この記事は最初Qiita Teamに公開してたけど、社内から好意的な反応をもらえたような気がしたので、@k_katsumiの提案もあって公開することにした。

UX DAY TOKYO 2015

2015.uxdaystokyo.com

行ってきた。直接に関係がある話はあんまりしません。

エクストリームプログラミングが発表されたのは1999年だったという(Wikipediaより)。

www.amazon.co.jp www.amazon.co.jp

(日本語版は2000年。英語の1st Editionがあると良かったのだけど、Amazonにあったのは2004年の2nd Editionだった。)

Embrace Change(変化を抱擁せよ)

ソフトウェア開発の失敗は、事前に仕様と設計を凍結することから発生する。仕様の決定をできるだけ遅らせることによって、開発する製品の陳腐化を防ぐ。そのためには何をすれば良い?仕様を決定できる人間が実装を行うチームの近くにいなくてはいけない。オンサイトカスタマー。そもそも完成されたソフトウェアでないと発見できない類の問題がある。反復的な開発。もしも新しい変更が失敗だったときのために、ソースコードはいつでも過去のものを参照できるようにする。バージョン管理。全てのXPのプラクティスはソフトウェアの変更をリリースされる最後の瞬間まで続けられるようにするためのものである。(どれもちょう雑な説明なので、ちゃんとした資料を確認してください。)

このアイディアは広く受け入れられ、アジャイルスクラムと呼ばれる開発手法として発展を続けた(雑な説明)。アジャイルスクラムは、XPよりもコラボレーションやプロセスに意識が移っているように見える。ストーリーカード、イテレーション、デイリースタンダップ、見積もり、振り返り。どうすれば気むずかしくて内向的なプログラマーたちを、チーム作業に適応させることができるのか。

現在ではプログラマは、品質の高いソフトウェア製品をチームで開発する方法について、一定の知見を有するようになった(もちろんXPがカバーする範囲で)。


さて、2010年になって周りを見てみると、取り残された人々がいる。より正確には、XPが生まれた時点ではソフトウェア製品の開発で重要な役割を果たすと認識されていなかった、そして現在では当時よりもはるかに重要であると認識されているデザイナと呼ばれる人たちがいる。UIデザイン、情報デザイン、ビジュアルデザイン、マイクロインタラクション。デザイナが貢献する分野は広範囲にわたっていて、どれも今日のソフトウェア製品では重要な分野として認識されている。

が、いまいちXPやアジャイルの流れで発見されてきたプラクティスは共有されていない。

使っているツールに起因する困難はある。PNGをGitで管理することにはほとんど意味がない。なぜかというとそれらはPhotoshopで編集した結果を出力したものだから。PSDを無理矢理Gitで管理することはできるがdiffをとったり、マージしたりするのはほぼ不可能である。マージできないファイルを複数人で同時に変更することはできない。プログラミングの対象となるソースコードとはかなり状況が違う。*1

ツールについては発展を待とう。しかし、プロセスにも問題がある。反復的な開発プロセスにどうやって適応すれば良いか。デザインはそれだけでは意味を持たず、ユーザーに価値を届けることが本質である。デザイナだけで議論することには意味がない。他の人たちと、どうやってコミュニケーションすれば良いか?

UX design is a team sport.

チームの関係者を集めて一緒にデザインするには、紙とペンで絵を描いて壁に貼る。その前に人間の描き方を説明しよう。UXの改善とはなにか?課題はどう定義すれば良いのか?問題があることを説得するにはどうすれば良いか?関係者みんながアクセスできるデータ共有方法はなにか?


今よりもソフトウェア製品を使ったときの体験を向上するにはどうすれば良いか?ボトルネックはデザインの技量ではなくて、別のところにある 。つまりコラボレーションでありプロセスである。そういう意識を感じた。*2

かなり良いイベントでした。

*1:多分、あと5〜10年でこの状況は改善されると思う。PhotoshopIllustratorが支配的な立場を保てるのは、紙や印刷物と計算機上で使う画像を両方サポートしているからであり、印刷屋さんとコミュニケーションするためのプラットフォームとしての役割があるからである。Sketchのような、計算機上で扱う画像のためだけのソフトウェアが一定の成功を収めることがわかった今、この分野の競争が激化することは間違いがない。そのときに重要であると考えられるに違いない(と私が信じている)のはプログラマとの協業を容易にすることであり、つまりGitで変更を管理してみんなで編集して後でマージするというワークフローとの親和性である。

*2:と言っても、そもそも私の問題意識があってこそ勝手に匂いをかぎ取っているだけかもしれないし、特に目新しい話ではないのかもしれない。プログラミングの話は大まかな流れはざっくりとは理解しているつもりだけど、この界隈の流れは良くわからないので、これはあまり意味のある言明ではない。

ファイルを保存しない人生

私は学生の頃はEmacsを使っていて、何かのときに知ったのがauto-save-buffersだった。

これは、キー入力が一定時間発生しないと自動でファイルを保存するというelispで、とても気に入ってずっと使っていた。こういう拡張ができるEmacsというエディタはなんてすばらしいのかと感動していたものだ(誇張)。

実際にはいくつか欠点があって、誰もが指摘するであろう「でかいファイルや遅いストレージでは、自動保存はあまり嬉しくない」という問題以外に「Emacsの中からビルドしようとすると、保存より先にビルドを始めてしまうことがある」というのがあった。EmacsではCtrl-C Ctrl-Cのキー入力でビルドを行う用に設定していたのだけど、プログラムをわーっと書いて、そのままCtrl-Cを二回叩くと、ファイルの保存よりも先にコンパイルされて、結局保存を忘れたのと同じ効果が得られることになってしまい、悲しい思いを何度もしたことを覚えている。私はかなりアグレッシブな設定にしていて、確か0.1秒キー入力がないと保存するようにしていたのだけど、それでは遅かったのだった。*1

さて、今日ではファイルの保存をしないシステムというのは意外と多い。私が常用しているものでも、

では、保存なんてしたことがない。

Xcodeは、ビルドとかアプリ実行のタイミングで保存するようになっている*2。ビルドの操作は必要だけど、保存はする必要がない。プログラミングしていて、ビルドはともかく、「アプリを実行する」というのは、なかなか無くすのが難しい操作のように思えるので、これ以上操作を少なくすることは難しいんじゃないかなーと思う。Playgroundとかの新機能で、もしかしたらもう一歩先に進めるかもしれない。

RubyMineは、よくわからない謎のタイミングでファイルを保存する。プログラミングした後で、

  • ブラウザに切り替えてページをリロードする
  • ターミナルからrakeなどを実行する
  • RubyMine内でテストを実行する

などの操作をすると、なんかよくわからないけどいつのまにかファイルが保存されている。実際には、ページリロードの前に一呼吸おいた方が、より確実に保存されたファイルにアクセスできるようになるが、まあ誤差の範囲である。(先述の通り、これはauto-save-buffersのころからの儀式だ。)

Sublime Textは、かなり保守的で、ここに挙げた3つの中で唯一、明示的に設定をしないと自動でファイルを保存してくれない。save_on_focus_lostという項目をtrueに設定すると、アプリケーション切り替えのタイミングで自動で保存してくれる。これはまあまあ良いタイミングだと思うし、大体思った通りのタイミングで保存してくれる感じである。*3

エディタなんかとはちょっと印象が違うけど、iPhotoやApertureなんかも「保存」という操作は存在しない気がする。操作が非破壊的で、操作の積み重ねがどこかで途切れることがないというのが*4、自動での保存を可能にしているんだろうか。写真というのはけっこうでかいデータだと思うんだけど、保存なしでいけるのはすごいなぁ。

そういうわけで、なんだかんだ言ってファイルを保存する機会というのはだんだんと減っているように思えるので、人類が進歩を続けているのは確かだと思う。

*1:コンパイルを始める前にファイルを保存すれば良いというのは気付いていたのだけど、Elisp力が足りなかったし、0.1秒待てば良いので諦めていた。

*2:より正確に言えば、ビルドのタイミングを含む謎のタイミングでファイルをいつのまにか保存している

*3:ちなみに、Sublime Textのすばらしいところをもう一つ挙げると、いろいろな設定項目がかなり設定した瞬間に反映されるところで、これはとても納得感のある動作だと思う。(常に便利であるとは言っていない。)

*4:いつでも任意の時点、、、かどうかは自信がないけど、少なくとも一番最初の状態に巻き戻すことができる。

Rubyの::演算子について

ruby*1では、A::Bという形式の式は、NODE_COLON2という名前になる。::という演算子の式なので、コロンが二つでCOLON2だろうか。とても素直な名前である。::の両端には普通はAとかBとかの定数名を書くが、callメソッドを呼ぶための構文でも使う。

irb(main):001:0> self::(3)
NoMethodError: undefined method `call' for main:Object
irb(main):002:0> (:succ.to_proc)::(3)
=> 4

これいつからある機能なんだろ……

さて、rubyソースコードを見ていると、NODE_COLON3という定義もある。これはコロンが三つ並んだ演算子ではなく、トップレベルの定数を参照するための演算子で、::Objectとか書くと作ることができる。なんで3にしちゃったのかあまり合理的な理由がなさそうな気がする、とても投げやりな感じの、見つけると楽しくなる名前である。*2

なんで、NODE_COLON2を流用するのではダメかというと、

node.h:#define NEW_COLON2(c,i) NEW_NODE(NODE_COLON2,c,i,0)
node.h:#define NEW_COLON3(i) NEW_NODE(NODE_COLON3,0,i,0)

となっていて、さらに

parse.y:                        $$ = NEW_COLON2(0, $$);

というのがあって、つまりNEW_COLON3(n)NEW_COLON2(0, n)を区別するためなんだけど、なんで区別する必要があるのかはよくわからない。NODE_COLON2を流用しようとしていろいろ試行錯誤したけどうまくいかなくて、力尽きて良い名前も思いつかずにいつのまにかNODE_COLON3になったんだろうと想像している。

ちなみにNODE_COLONNODE_COLON1は定義されていない。Rubyではコロンはシンボル:symbolやハッシュ{ key: value }に使われるが、それらは字句解析か構文解析の時点で、他の構文に取り込まれてしまう。

*1:Matzの実装したRuby処理系の一つ

*2:合理的でなくてかっこよくもないものを見つけると、さぞかし苦労したに違いないと思って、ちょっと楽しくなる。

Rubyの定数は難しい

ネストしたモジュールとかクラスの定義方法には、まあ二つくらい書き方がある。

バカ正直にネストしたモジュールを並べる方法。

module A
  module B
    module C
      ...
    end
  end
end

::演算子を使う方法。

module A::B::C
  ...
end

この二つは同じだと思っていたのだけど、同じではなかったことに気付いたという話。

私は、これまでは後者の方を好んで書いていた。なぜかというと、定数のネストの途中に出てくるAA::Bがモジュールなのかクラスなのかを考えなくて良いからだ。純然たる名前空間として使うときはモジュールが使われるが、クラスのこともある。考えたくない。

この二つの違いは、定数を参照するときである。具体的に言うと、次のような定数A::B::Xを参照するときに違いが生まれる。

module A
  module B
    X = "A::B::X"
  end
end

module A
  module B
    module C
      p X           # => ちゃんと動く
    end
  end
end

module A::B::C
  p X               # => const_missing
end

これはかなり難しい……

  • 定数はネストした構造を持つことがある(これは実行時に値から参照できる構造)
  • 一方でプログラム中の定数の参照は、構文的な構造に依存するもので、実行時の構造は関係がない(Lexical scopeというやつであってる?)
  • Aの中のBの中のCの中に書いたXは、それぞれのモジュール式をさかのぼっていって、A::B::C::XA::B::XA::X::Xが探索されて、この場合はA::B::Xに解決される
  • 一方でA::B::Cという名前のモジュール式の中に書いたXは、モジュール式をさかのぼることができないので、A::B::C::X::Xのどちらかになるのでconst_missing

うーむ。

Effective Ruby

Effective Ruby

これはRailsアプリを書いている最中に発生した問題でなぜか、なぜかモジュールのautoloadに失敗するという問題で、普通に二晩くらい悩んだ結果*1、そういえばEffective Rubyに「定数の参照はレキシカルスコープだ」って書いてあった話を思い出したりとかして問題が解決したので、これはとても良い本であると言える。*2

*1:大げさに言っている。30分悩んで、忘れて二晩寝て、思い出して20分調べて解決した。

*2:冗談。良い本だとは思うけど、この問題は多分out of scopeだろう。

ヒラギノの縦位置を揃えるちょうべんりなSketchプラグインの紹介

Sketch 3 Advent Calendar 2014の18日目の記事です。

Sketchでボタン的なものを作ろうとすると、テキストレイヤーの位置決めに苦労するのはよく知られた話だと思います。ボタンの外側を作って、内側にテキストレイヤーを追加して、Align Horizontallyして左右を揃えるところまでは良いのですが、次に縦を揃えようとしてAlign Verticallyすると、テキストレイヤーが意味不明な位置に移動して、悲しい思いをすることになります。

なにが起きているかというと、テキストレイヤーの形がおかしくなっていて、文字の下に謎の空白ができています。

これは多分ヒラギノの余白の計算が壊れているのだと思います。下の余白が予想外の大きさになった結果として、テキストレイヤーの矩形が意図しない感じになり、矩形同士を中央に合わせたら文字がすごい上に表示された。

ちなみに、欧文のフォントは全体的にちゃんと処理されていて、いい感じになります。テキストレイヤーを挿入した時点で、矩形の中心にテキストがくるように行の高さ(Lineの項目)が設定されてるように見えます。下の画像はAlign Verticallyしただけ。

いずれにせよ、私たちは日本語を書きたいので、この問題をなんとかしないといけません。なんとかするプラグインを作りました。

テキストレイヤーと外側のレイヤーを選択して、プラグインを実行すると、テキストレイヤーを中央に移動させます。ただし、テキストレイヤーの矩形を見て位置を揃えるわけではなく、フォントの設定を見て文字の位置を揃えます。ヒラギノでもいい感じに中央に来るようになっています。(なんか見てたらもう1px上でもいい気がしてきたけど。)

このエントリを書きながら、じっくり欧文フォントの振る舞いを確認したところ、このプラグインは日本人しか嬉しくないことに気づきましたが、でも日本人は嬉しいと思うので、みなさん試しに使ってみてください。

明日のSketch 3 Advent Calendar 2014のご担当はnukos_さんです。