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

主夫ときどきプログラマ

プログラミング、Webエンジニアリング、etc

スプーキーズの開発合宿に参加してきた

3月下旬に淡路島で行われたスプーキーズの開発合宿に参加してきました。 合宿所はこちらのLa Terrasse Awajiで、とても広くて美しい素晴らしい施設でした。

合宿の目的はこちらの2つ。

合宿を通じてメンバー同士が普段とは違ったコミュニケーションをとり親睦を深めること、その中で生まれる発想や試行錯誤をボードゲームという成果に落とし込むことの2つを目指しての2泊3日でした。

とまあ、合宿の詳しい内容はオフィシャルブログに譲るとして合宿の様子を写真中心で紹介しようと思います。

合宿所に到着。きれいなエントランスです。

f:id:masayuki14:20170401115011j:plain

早速メンバーが集まって今回の趣旨の説明。

f:id:masayuki14:20170331152049j:plain

自己紹介をして和やかなムード。

f:id:masayuki14:20170331153023j:plain

僕はガジェットが好きなんですよ。

f:id:masayuki14:20170331152759j:plain

僕はロクロを回すんですよ。

f:id:masayuki14:20170331153234j:plain

4人✕3チームに分かれてボードゲームを作りますよ!

f:id:masayuki14:20170331154042j:plain

f:id:masayuki14:20170331154414j:plain

さて、どうしようか。

f:id:masayuki14:20170331161435j:plain

コマ使うやつとか。

f:id:masayuki14:20170331180652j:plain

f:id:masayuki14:20170331173533j:plain

議論も白熱します。

f:id:masayuki14:20170331161505j:plain

f:id:masayuki14:20170331160412j:plain

とりあえずボードゲームで遊びますか。

f:id:masayuki14:20170331155845j:plain

えー、まじでー。

f:id:masayuki14:20170331161531j:plain

山盛りいちごで休憩。

f:id:masayuki14:20170331163734j:plain

薪がなくて使えなかった暖炉。

f:id:masayuki14:20170331163039j:plain

外はあいにくの雨模様。

f:id:masayuki14:20170331161727j:plain

廊下の奥には

f:id:masayuki14:20170331173712j:plain

きれいなベッドルーム。

f:id:masayuki14:20170331173826j:plain

ちょっと1人で考え中。

f:id:masayuki14:20170331180643j:plain

そろそろ夕食に行きますよ〜。

f:id:masayuki14:20170331174038j:plain

なんだか楽しそう。

f:id:masayuki14:20170331193842j:plain

淡路の海の幸、山の幸

f:id:masayuki14:20170331194747j:plain

f:id:masayuki14:20170331200536j:plain

夜が更けても煮詰まらない!

f:id:masayuki14:20170331220636j:plain

やっと形になってきたか!?

f:id:masayuki14:20170401004659j:plain

我々はもう出来たのでカタンで遊びますよ。

f:id:masayuki14:20170331234521j:plain

翌朝チームごとに制作したゲームを発表してテストプレイ。

f:id:masayuki14:20170401112008j:plain

プレゼンを聞く方も真剣。

f:id:masayuki14:20170401113024j:plain

コマを使ったパズルゲーム!!

f:id:masayuki14:20170401112721j:plain

頭をつかうゲームなのか!?

f:id:masayuki14:20170401113750j:plain

f:id:masayuki14:20170401113913j:plain

こちらのチームはアイドルがモチーフのよう。

f:id:masayuki14:20170401115743j:plain

f:id:masayuki14:20170401115247j:plain

次のチームはカードとチップを使ったゲーム

f:id:masayuki14:20170401124517j:plain

絵のクオリティが。

f:id:masayuki14:20170401123435j:plain

盛り上がってます!

f:id:masayuki14:20170401125543j:plain

ここで第一部が終了。 各人がおもしろいと思ったボードゲームに投票し、優勝チームにはご褒美となりました。

f:id:masayuki14:20170401131359j:plain

お昼休みを挟んでチーム替え。3人✕4チームに分かれて第二部の開始!

f:id:masayuki14:20170401151857j:plain

f:id:masayuki14:20170401151926j:plain

f:id:masayuki14:20170401151939j:plain

夜はデッキでバーベキュー!

f:id:masayuki14:20170401202656j:plain

食後に最後のプレゼンです!

f:id:masayuki14:20170401215725j:plain

盛り上がってますね。

f:id:masayuki14:20170401221125j:plain

f:id:masayuki14:20170401221143j:plain

第二部はカードゲームが多いようです。

f:id:masayuki14:20170401223238j:plain

f:id:masayuki14:20170401223551j:plain

f:id:masayuki14:20170401224857j:plain

優勝おめでとう! このあと夜遅くまで宴会&ボードゲームパーティーが続くのでした。

f:id:masayuki14:20170401231552j:plain

合宿おつかれさまでした!

f:id:masayuki14:20170402101928j:plain

Bash初心者から初級者へのステップアップするためのTips10選

CLIでいろいろとコマンドは使っているんだけど、bashスクリプトが書けるかというと・・・。 という人がbashの文法や機能を知って初級者へとレベルアップするためのTipsを紹介します。

1. ${変数}

シェルで変数を保持することができます。変数には文字列や数値を代入することができ、値を参照する場合は変数名の前に$を付けます。変数値は $VAL でも ${VAL} でも同じように参照出来ますが ${} の方が可読性も上がるのでおすすめです。

$ A=10
$ echo ${A}
10

変数に代入する時 = の前後にスペースを入れてはいけません。変数がコマンドとして解釈されてしまいます。

$ B = 10
bash: B: command not found

bashのバージョン4では ${VAL^} で単語の先頭を大文字に、 ${VAL^^} で単語全体を大文字に変換してくれます。

$ A='hello world'
$ echo ${A}
hello world
$ echo ${A^}
Hello world
$ echo ${A^^}
HELLO WORLD

変数の値は基本的にテキストとして扱われますので一般的なプログラミング言語のように四則演算はできません。

$ A=10
$ B=15
$ echo ${A} + ${B}
10 + 15

この様な場合は後述する算術式展開 $(( 計算式 )) を使います。

2. while read n; do [command] $n; done

パイプから受け取った標準入力を1つずつ変数に保持して引数として別のコマンドに渡したい。そんな時によく使う方法が while read ... のイディオムです。 X.txt Y.txt をそれぞれ X.txt.bak Y.txt.bak との差分をみたい場合、ワンライナー(1行スクリプト)で書くとこのようになります。

$ ls *.txt | while read f ; do diff ${f} ${f}.bak; done

# 通常の書き方
$ ls *.txt |
while read f
do
  diff ${f} ${f}.bak
done

ls の出力を1行ずつ読み込み変数 f に保持し diff コマンドの引数として与えています。 羊を100匹数える場合はこのようになります。

$ seq 100 | while read n; do sleep 1 ; echo "羊が${n}"; done

3. $(( 計算式 )) 算術式展開

$(( 計算式 )) は算術式展開と呼ばれるbashの機能でいろいろな計算ができます。四則演算はもちろんのこと論理積論理和などのビット演算もできます。

# 四則演算
$ A=10
$ B=15
$ echo $(( ${A} + ${B} ))
25
$ echo $(( ${A} * ${B} ))
150

# べき乗
$ echo $(( ${A} ** ${B} ))
1000000000000000
$ echo $(( 2 ** 16 ))
65536

# modulo
$ M=$(( ${A} % ${B} ))
$ echo ${M}
10
$ echo $(( 13 % 8 ))
5

# 論理積
$ echo $(( 15 & 4 )) # 1111 と 0100 の論理積
4

# 論理和
$ echo $(( 10 | 5 )) # 1010 と 0101 の論理和
15

# 排他的論理和
$ echo $(( 13 ^ 11 )) # 1101 と 1011 の排他的論理和
6

4. || && 終了ステータス判定

コマンドはその動作が成功すると「終了ステータス」として0を返します。失敗すると0以外の数字が返ります。直前のコマンドの終了のステータスは $? 変数に保持されます。

# wget コマンドの例
$ wget 'http://google.co.jp' 2> /dev/null
$ echo ${?}
0
$ wget 'http://foo.bar.baz.co.jp' 2> /dev/null
$ echo ${?}
4

0は成功のステータス、 4は失敗のステータスで Network failure. の意味です。 2> /dev/null標準エラー出力を捨てて表示されないようにしています。

&&|| はこの終了ステータスを判定するbash演算子です。&& で成功した時の処理、|| で失敗した時の処理を書きます。

$ wget 'http://google.co.jp' 2> /dev/null && echo 'success!' || echo 'failure...'
success!
$ wget 'http://foo.bar.baz.co.jp' 2> /dev/null && echo 'success!' || echo 'failure...'
failure...

gerp は何かがパターンマッチすれば0を、何もマッチしなければ1を返します。そこで || 演算子を使うと処理を分岐することができます。

$ ls -aF | grep 'git' || echo 'no git file or dir'
no git file or dir


$  ls -aF | grep 'git' || echo 'no git file or dir'
.git/
.gitignore

5. -- オプションの打ち止め

-Rf~ というようなディレクトリが何かの拍子にできてしまいまった場合どうすればよいでしょうか。

$ ls -F
-Rf -f/ ~/

まちがっても $ rm -Rf ~ なんてコマンドを打ってはいけません。 -Rf はオプションとして解釈されてしまいます。このような場合は -- を使います。-- は特別な引数でそれ以降のオプションが打ち止めとなります。

$ rm -- -Rf
$ rmdif -- -f

~ は通常ホームディレクトリと解釈さるので、 -- を利用しても意味がありません。シングルクォートでくくる、\ でエスケープする、./ でカレントディレクトリを明示するなどが必要です。

$ rm '~'
$ rm \~
$ rm ./~

6. ヒアドキュメント

複数行のテキストをプロセスの入力にします。 << 終了文字列 を記述し複数行の文字列を入力後に 終了文字列 を記載すると入力終了の合図となります。

$ cat -n << EOF
I have a pen.
I have a apple.
EOF
     1 I have a pen.
     2 I have a apple.

標準入力に流し込むので echo ではなく cat で出力しています。この出力をリダイレクトやパイプに渡す場合は、1行目に書く必要があります。

# 出力をリダイレクト
$ cat << EOF > output.txt
I have a pen.
I have a apple.
EOF

$ cat output.txt 
I have a pen.
I have a apple.
# パイプで渡す
$ cat << EOF | jq
[{
"name":"Matz","age":51,"from":"Japan"},
{"name":"Dhh","age":36,"from":"Denmark"}
]
EOF
[
  {
    "name": "Matz",
    "age": 29,
    "from": "Japan"
  },
  {
    "name": "Dhh",
    "age": 36,
    "from": "Denmark"
  }
]

標準入力に流し込むので while イディオムの入力としても利用できます。

$ while read n; do echo $(( ${n} * 2 )); done << EOF
1
2
3
4
5
EOF
2
4
6
8
10

7. <<< ヒアストリング

<<< の右側に書いた文字列や変数の値をコマンドの標準入力に流し込みます。 ヒアドキュメントと同じように扱えます。パイプとリダイレクトも通常通り利用できます。

$ cat <<< 'Hello World!!'
Hello World!!

$ JSON='[{
"name":"Matz","age":51,"from":"Japan"},
{"name":"Dhh","age":36,"from":"Denmark"}
]'

# パイプ |
$ jq . <<< ${JSON}} | grep -i 'matz'
    "name": "Matz",

# リダイレクト >
$ jq .[].name <<< ${JSON} > name.txt
$ cat name.txt
"Matz"
"Dhh"

8. $( コマンド ) コマンド置換

コマンドの実行結果を文字列として利用するための機能です。 コマンドの引数として展開したり引数に保持したりします。

# dateコマンドの結果が文字列に展開される
$ JSON="{\"time\":\"$(date +%s)\"}"
$ echo $JSON
{"time":"1489992386"}

# yamlファイルだけを抽出して処理
$ for f in $( find . -type f | grep '\.yml$' )
do
    [command] ${f} # 何らかの処理
done

9. $(< ファイル )コマンド置換

コマンド置換の拡張的な機能でファイルの中身に置き換えられます。

# cpコマンドを使わずbashの機能だけでファイルをコピー
$ echo "$(< /tmp/foo )" > /tmp/foo.bak

コマンドの処理結果を一時ファイルに保持することで可読性が向上します。

$ find . -type f | grep '\.yml$' > /tmp/yaml.list.txt
$ for f in $(< /tmp/yaml.list.txt )
do
    [command] ${f} # 何らかの処理
done

10. <( コマンド ) プロセス置換

コマンドの入出力をファイルとして扱う機能です。引数にファイルを受け取るコマンドの入力として利用できます。

# cat は引数としてファイルを受け取る
$ cat -n <( echo 'Hello World' ) <( echo 'Dreams come true' )
     1 Hello World
     2 Dreams come true
# ファイルリストの差分を diff で得る
$ ls
a.txt      c.txt      e.txt      h.txt       j.txt      l.txt      o.txt      q.txt
a.txt.bak  c.txt.bak  f.txt      i.txt       j.txt.bak  m.txt      o.txt.bak  q.txt.bak
b.txt      d.txt      g.txt      i.txt.bak   k.txt      n.txt      p.txt      r.txt
b.txt.bak  d.txt.bak  g.txt.bak  index.html  k.txt.bak  n.txt.bak  p.txt.bak  r.txt.bak

$ diff <( ls | grep '.txt$' | cut -d. -f1 ) <( ls | grep '.txt.bak$' | cut -d. -f1 )
5,6d4
< e
< f
8d5
< h
12,13d8
< l
< m

シェルプログラミング実用テクニック 入門bash 第3版 ソフトウェアデザイン 2016年 06 月号 ソフトウェアデザイン 2017年 01 月号

2017年の目標

年始からいろいろと考えていたことをまとめるためにも今年の目標を書き出します。

セルフブランディング

Rubykaigi2016に参加したことで改めて個人として活躍するには必要なことだと感じた。 まずは情熱プログラマーにもあるように「Googleで検索し最初の4ページのリンクだけを見てどんな人物だと想像するだろうか」という部分に注視して見ようと思う。 情熱プログラマーは改めて読むとほんとに良い本だ。

コミュニティでの登壇

勉強会やカンファレンスで登壇する。 3年後にはRubykaigiに登壇することを目指す。 オリンピックイヤーか・・・。

ブログ

Qiitaも含めてアウトプットを増やす。 最低でも月1回は書く。

OSSにコミット

GitHubでPullRequestを送る。 今年こそ!!

シェルプログラミング

シェル芸から入って bash が面白くなってきたのでシェルプログラミングでいろいろ出来るようになる。

Elixir

途中で止まってしまったプログラミングElixirをちゃんと読む。 演習問題もちゃんとやって手を動かす。

運用・監視まわり

Mackerelで独自のメトリクスを取りたい、という課題があるのでちゃんと勉強して達成する。 あわせてFuluentdとか使ってログ解析も出来るような基盤を考える。

新春bash書き初めをやった

SoftwareDesignの2017/1号の第1特集である新春bash書き初め シェル30本ノックをやった。
結果は 19/30 でまぁまぁの出来だと思う。
コマンドやbashについていろいろと新しい発見があったのでまとめておく。

各問題の自分なりの回答は GitHub においてある。
雑誌はこちら

コマンドについて

sed awk

抽出や変換など知れば知るほどなんでもできそうなコマンド。

  • sed -n '/開始パターン/,/終了パターン/p'
  • sed 'p' | sed '1d;$d' | paste
  • awk '{print}{fflush()}'

while read n; do [command] $n; done

繰り返しのイディオムで頻出。xargs とうまく使い分けよう。

openssl

暗号関連のいろんなことができるコマンド。使うことはほとんどなさそう。

crontab

コマンドというか @reboot コマンド起動時に1度、指定のコマンドを実行するという設定ができることを知った。

grep

grep -q . とすると、マッチの有無で終了ステータスが変わるので処理を分岐できる。
$ (何らかの処理) | grep -q . && (出力がある場合の処理) || (出力がない場合の処理) と出来る。
-q は標準出力に何も出さないオプション。

paste

それぞれのファイルの行を並列に結合する。

$ paste file1 file2
file1の1行目 file2の1行目
file1の2行目 file2の2行目
...

標準入力も扱える。

$ seq 1 10 | paste - -
1   2
3   4
5   6
7   8
9   10

file

ファイルの種類を推定してくれるコマンド。

tee

標準出力に出力しつつ、内容をファイルに保存する。
リダイレクトでは特権(sudo)が必要なファイルは書き込めないためteeを使うとうまくいく。
$ echo 'foo bar' | sudo tee special_file.txt

rev

反転してくれる。

$ seq 1 10 | xargs
1 2 3 4 5 6 7 8 9 10
$ seq 1 10 | xargs | rev
01 9 8 7 6 5 4 3 2 1

[

test コマンドの別名。if [ xxxx ]; でよく使われるのでオプションを覚えたほうが良い。

set env

set は現在のシェル変数、 env環境変数のみを出力する。

bashについて

${変数}

変数へ代入する時はスペースを入れてはいけない。
変数を使う時は$をつける。

$ n='hello world'
$ echo ${n}
hello world

|| && 終了ステータス判定

直前に実行したコマンド(処理)の終了ステータス($0)を判定するbash演算子
$ コマンド && コマンドが成功した時の処理 || コマンドが失敗した時の処理

<<< 標準入力へ流し込む

<<< の右側に書いた文字列や変数の値を標準入力に流し込む。

$ cat <<< 'hello world'
hello world
$ cat < 'hello world'
bash: no such file or directory: hello world

$(( 計算式 )) 算術式展開

いろいろな計算ができる。四則演算はもちろんのこと、論理和論理積、ビット演算も可能。

$ echo $(( 2 * 3 + 4 ))
10

$( コマンド ) コマンド置換

コマンドの実行結果を文字列として扱える。

$ for n in $( seq 1 5 ); do echo "羊が$n匹"; done
羊が1匹
羊が2匹
羊が3匹
羊が4匹
羊が5匹

:- 変数展開

難しいのでこちら
デフォルト値への置換ができる。

--

オプションの打ち止め。 $ mkdir -- -RF-RFというディレクトリが作成される。--以降はオプションと認識されなくなる。

$(<ファイル) コマンド置換2

コマンド置換の拡張的な機能で「ファイル」の中身に置き換えられる。

$ echo "$(<ファイル)" > foo$ cp ファイル foo と同じ。

<( コマンド ) プロセス置換

コマンドの入出力をファイルとして扱うことが出来る。

$ cat < <(echo 'hello world')
hello world

さいごに

各コマンドやbashの詳しい使い方や実例は雑誌を読んでくれ。

SoftwareDesign2017.01

2016年をふりかえる

Facebookにポストしてた2016年の目標をブログに移したのでそれをベースに2016年を振り返ります。

masayuki14.hatenablog.com

ひとつひとつ見ていきましょう。さほど意識しないで1年を過ごしてきたわけですが・・・。

2016年にたてた目標

Swift2

swift2を試す。iOsネイティブのアプリを作れるようになろう。年内にクソゲーがリリースできたらバンザイ\(^o^)/

2016年の目標 - masayuki14’s diary

宣言することは大事なもので、2月に参加したスプーキーズ沖縄合宿iOSアプリチームとなりSwiftにチャレンジすることとなりました。
夏頃からアプリ開発の案件にも参加することとなり年間通じて触れる機会に恵まれました。 Swiftの言語仕様はほぼ把握したのでコーディング自体には問題ありませんが UIKit 周りが全然わかってなくて、一人でアプリ開発することはまだまだできない感じです。

Redux

Reactで作ったアプリにReduxを導入する。

2016年の目標 - masayuki14’s diary

React 0.13 で開発したのでバージョンアップにも結構コストかかりそうだったし、サービス自体もあまり変更なく稼働していたのでそのまま。
Redux導入までには至りませんでした。今後も予定はありません。そして1年通じてほとんど React に触れませんでした。

Hubot

なんかつくる。くだらないものからやっていこう。みさわの画像出すとか。すでにありそうだけど。

2016年の目標 - masayuki14’s diary

忘れてください。ごめんなさい。

Meckerel

サーバーにMackerel入れてみる。APIコールして独自のメトリクスとりたい。

2016年の目標 - masayuki14’s diary

ごめんなさい。忘れてください。

ブログ

月に1回はブログを書く。ちゃんとアウトプットしよう。

2016年の目標 - masayuki14’s diary

月1とは言わずとも少しは書きました。せっかくなのでリンクを貼っておこう。

Rubyでメタプログラミング - masayuki14’s diary

スプーキーズの勉強会イベント一般公開します。 - スプーキーズの中の人。

おじさんの嫉妬は犬も喰わない - スプーキーズの中の人。

RubyKaigi2016 に行ってきました。 - masayuki14’s diary

暖かい季節にはモチベーションも上がるようです。

IPA試験

IPAの試験を受けてなんか合格したい。DBとネットワークあたりか。春の試験は受付中。

2016年の目標 - masayuki14’s diary

データベーススペシャリスト合格しました!おめでとう!
ネットワークスペシャリストは不合格でした!次回がんばろう!

OSSコミッタ

になろう。規模の大小は問わずGitHubでPullRequestを送ってみる。

2016年の目標 - masayuki14’s diary

https://github.com/ash1day/Copy-link-as-markdown/pull/3

知人のリポジトリですがPullRequest送ったのでこれでいいんだ!はじめの一歩はこんなもんだ!

コミュニティ活動

勉強会とか参加してみよう。

2016年の目標 - masayuki14’s diary

ブログにも書きましたがRubyKaigi2016にHelper Staff として参加しました。
今年1番の大きな出来事だと思っています。今後も京都や関西のコミュニティで活動を継続していきます。

総括

本当にやりたいと思っていることは目標にしてもしなくてもやるし、 そんなに思っても見ないことを目標にしてもあんまり達成しないのかな、という印象です。

とはいえそれほど思っても見ないことを達成するにはより目標とスケジュールを一緒に設定すればいいのかな、とも思います。 2017年もしっかり目標立てて1年後ふりかえれるように頑張ろう。

RubyKaigi2016 に行ってきました。

RubyKaigi2016が京都で開催されました。

RubyKaigiが地元で開催されるのなら行くっきゃない、ということで申し込みました。
早い段階で申し込みをしたこともあり、HelperStaff募集にも応募することができ、 HelperStaffでの初参加と相成りました。

今年はHelperの応募が少なかったとのことなので、スタッフになるとこんなにいいことがあるよ、 ということを紹介したいと思います。
再び関西で開催される時には応募が増えるといいなと思います。

開催中の様子はこちらのブログにすばらしい写真がたくさんあったので御覧ください。 hazi.jp

参加することのメリット

Rubyistの仲間が増える

会場にはほぼRuby関係者が集まりますので、自然とRubyistの仲間が増えます。 熟練のRubyistも数多くいますので、見習うべき先輩達にも出会えます。

モチベーションが上がる

Rubyはもちろんのこと、Rubyで先端技術をどう扱うかといったようなセッションが数多いので自然とモチベーションがあがります。 仕事や技術に対する意識も変わってくることでしょう。

お弁当とTシャツがついてくる

参加費は早割でも10000円しますが、3日間の昼食弁当とTシャツ(数量限定)がついてくることを考えると安いと思います。 たくさんのステッカーもゲットできるのでラップトップに貼っていい感じにしましょう。

Helper Staff のメリット

スタッフTシャツもらえる

スタッフは紺色のTシャツでした。 もちろんお弁当もおなじ。

Ruby界隈のことがわかってくる

OrganizerやStaffにはRubyコミッターの方々がいます。(今回初めて知った。)
もちろん著名なコミッターの方の基調講演やセッションも行われるのでそういった人達を知ることができます。
オフィシャルパーティーや打ち上げで彼らと話す機会もありますし、 Twitterなどをフォローすればさらにいろいろな情報を得られるでしょう。

知らない人に話しかけやすい

オフィシャルパーティーやブースでもスタッフだと話しかけやすいです。
特にパーティーで知らない人と話をするときに
「スタッフなのでぜんぜんセッション聞けてないんで、今日のおすすめあれば教えて下さい。」
というのが常套手段のようで、私もパーティーではこれで話を始めることが多かったです。

変化する運営方法が間近で見られる

発生する課題にその都度対応し、翌日にはより良くなるように皆さん動いていて 変化する運営の中にいられるのはとても刺激的でおもしろい体験でした。
同時通訳レシーバーの貸出を担当しましたが、いい感じで運営できたと思います。
やはりオフラインコミュニケーションの方が改善のスピードは圧倒的に速いかもしれせん。

オフィシャルパーティーに参加できる

定員のあるオフィシャルパーティーにスタッフ枠で参加できますし、 もちろんスタッフの打ち上げにも参加できます。
今回は京都ということもあり最終日に打ち上げを行ったようです。
セミオフィシャルな形でスタッフ以外の参加もありましたが、 東京では後日にスタッフだけで打ち上げするみたいです。

ステージに上がれる

クロージングで壇上にあがりました。
会場とても広かったしこんなところでセッションできる人って やっぱりすごいんだなーと思います。

Helper Staff のデメリット

HelperStaffになるのは基本的にいいことずくめなんですが、 やはり少しだけデメリットもあります。
でも総合的にみたらメリットしかないです。

セッションが聞けない

担当にもよりますが、セッションはあまり聞けません。
特に受付担当になると会場からも遠いのでかなり難しい状況になります。
逆に会場担当だとけっこう聞けると思います。

朝が早い

開場前に集合するので当然朝が早いです。毎日8時頃にはみなさん集まっていました。

立ちっぱなし

立ちっぱなしでいる時間が長いので疲れます。筋トレをしておきましょう。

仕事がたまる

やはり平日開催なので仕事ができません。とうぜん仕事が溜まります。
空いた時間に仕事している方もいました。

交通費・宿泊費がかかる

東京から来ている方は当然それなりの費用がかかります。
このあたりをバックアップいただける企業さんは本当にありがたいと思います。
だいたいスタッフさんはこういった企業で活躍されている方のようでした。

変なテンションになった

仕事したくない
Rubyコード書きたい
のんびりしたい
でもお金欲しい

欲望に素直になりました。
2週間たっても変わらない。

おわりに

公式サイトで各セッションの動画が配信されています。
すばらしいセッションの数々を御覧ください!

おすすめはJustinさんの基調講演で、リファクタリングがしたくなります。
絵文字の使い方がとてもじょうず。

www.youtube.com

RubyKaigi はOrganizerの皆さん、多くのスポンサー企業、そしてこれまでの実績を積み上げてきた 過去に尽力頂いたたくさんの方のおかげで今年も開催できているのだなと感じました。
すべての皆さんに感謝したいと思います。ありがとうございました。

f:id:masayuki14:20160910174716j:plain

また京都で開催されますよーに。

Rubyでメタプログラミング

勉強会でRubyについて話す機会があったのでその資料を公開します。
勉強会についてのエントリはこちら。 スプーキーズの勉強会イベント一般公開します。 - スプーキーズの中の人。

メタプログラミングとは?

コードを記述するコードを記述すること

メタプログラミング (metaprogramming) とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法、またその高位ロジックを定義する方法のこと。主に対象言語に埋め込まれたマクロ言語によって行われる。

by wikipedia

It's Magic!

Rubyを使ってDBにアクセスするプログラムを書いてみよう。 今回書くのはActiveRecordを使ってmoviesテーブルにアクセスするクラスだ。

class Movie < ActiveRecord::Base
end

Wow, たったこれだけ!

movie = Movie.new
movei.title = '燃えよドラゴンズ!'
movie.title                  # => '燃えよドラゴンズ!'
movie.save

ActiveRecord はテーブルを知ってるの?

知りません

Movie#title()Movie#title=() を呼び出しているが、これらのソースコードはどこにもない。 どこにも定義されてないとするとどうやって存在するんだい?

これこそメタプログラミングがやってくれるんだ!!

実行時にメソッドを定義している

ActiveRecord は実行時にクラス名を調べ簡単な規約を適用します。 例えば Movie クラスであれば movies テーブルにマッピングします。 そしてデータベーススキーマを読み取り title カラムがあることを見つけ、この属性のアクセッサメソッドを定義しています。

プログラムの実行中にこっそりと定義してるのだ!!

メタプログラミングするには

魔法のようなメタプログラミングをするには魔術書が必要!

オープンクラス

文字列からアルファベットと数字以外を除外したい。

def to_alphanumeric(s)
    s.gsub /[^\w\s]/, ''
end

to_alphanumeric("#hello, it's magic number 3*?")     # => "hello its magic number 3"

でもこのメソッドはオブジェクト指向的じゃないよね?

文字列自身に変換してもらおう

class String
  def to_alphanumeric
    gsub /[^\w\s]/, ''
  end
end

"#hello, it's magic number 3*?".to_alphanumeric()    # => "hello its magic number 3"

いつだってクラスを再オープンできる

class A
  def x; 'x'; end
end

class A
  def y; 'y'; end
end

obj = A.new
obj.x       # => 'x'
obj.y       # => 'y'

class キーワードはクラス宣言というよりはスコープ演算子のようなもので、class を使ってクラスコンテキストに行きそこでメソッドを定義する。つまりいつでもクラスを再オープンしてその場で修正ができる。

モンキーパッチ

同じ要領で配列要素を置換するメソッドを定義しよう

def replace(array, from, to)
  array.each_with_index do |e, i|
    array[i] = to if e == from
  end
end
# Arrayクラスに再定義
class Array
  def replace(from, to)
    each_with_index do |e, i|
      self[i] = to if e == from
  end
end

これをプロダクトのコードでやったらおかしなことになってしまうぞ!

既存のメソッドを上書きしてしまった

> [].methods.grep /^re/
 => [:reverse_each, :reverse, :reverse!, :reject, :reject!, :replace, .....

独自の replace() メソッドでうっかり元の replace() メソッドを上書きしてしまった。 こうしたクラスへの安易なパッチに否定的な人には蔑称で モンキーパッチ って言われちゃうぞ!

これが オープンクラスのダークサイドだ。

メソッドを知る

Rubyでは動的なメソッド呼び出しやメソッド定義が可能だ。

Object#send() メソッド

メソッドを呼び出すには通常ドット記法を使う。

class Person
  def saba(my_age)
    my_age - 5
  end
end

obj = Person.new
obj.saba(35)           # => 30

Object#send() メソッドでも呼び出すことができる。

obj.send(:saba, 45)    # => 40

動的ディスパッチ

obj.saba(35)           # => 30
obj.send(:saba, 45)    # => 40

どちらのコードも saba() を呼び出してるが、後者は send() を使っている。これはメソッドで第1引数はメソッド名、その他の引数(とブロック引数)はそのままメソッドに渡される。

つまりメソッドを実行するメソッドというわけだ。

呼び出したいメソッド名が通常の引数になるから実行時に呼び出すメソッドを直前に決められる。これを動的ディスパッチと呼ぶ。

メソッドを動的に定義する

Module#define_method() を使えば、メソッドをその場で定義することができる。メソッド名とブロックを渡す必要があり、ブロックがメソッドの本体となる。

class Spookies
  define_method :muscle do |name|
    "#{name}, さぁ今日も筋トレだ!"
  end
end

spoo = Spookies.new
spoo.muscle("アッシー")        # => アッシー, さぁ今日も筋トレだ!

動的メソッド

通常defキーワードを使って定義するメソッドをModule#define_method()で定義している。これはメソッドを定義するメソッドだ。

define_method()Spookiesのなかで実行され、muscle()Spookiesインスタンスメソッドとして定義される。実行時にメソッドを定義するこのテクニックは動的メソッドと呼ばれる。

エイリアス

エイリアスのメソッドもまたメソッドだ

def deserves_a_look?(book)
  amazon = Amazon.new
  amazon.reviews_of(book).size > 20
end  

例外処理が考慮されてないメソッドをなんとかしたい。 こんな時はこのメソッドをラップして、機能を追加して、全てのクライアントが新しく追加した機能を自動的に使えるようにしたらいい。

メソッドエイリアス

alias キーワードを使えば、 Rubyのメソッドにエイリアス(別名)をつけられる。

class Pizza
  def cheese
    'cheese !'
  end
  alias :formaggio :cheese


  def tomato; 'tomato!'; end
  alias pomodori tomato
end

p = Pizza.new
p.cheese         # => "cheese !"
p.formaggio      # => "cheese !"
p.pomodori       # => "tomato!"
  • aliasはキーワード
  • 新しい名前が前、古い名前が後でカンマ不要
  • シンボルでもいいし素の名前でもいい
  • メソッドではない
  • 同じ動きをするModule#alias_method()メソッド
  • String#size()String#lengthエイリアス

メソッドを alias して再定義すると何が起きるだろうか?

class String
  alias :real_length :length

  def length
    real_length > 5 ? 'long' : 'short'
  end
end

"I'm so hungry".length            # => "long"
"I'm so happy!".real_length       # => 13

このコードはString#length()を再定義している。しかしエイリアスは元のメソッドを参照している。 メソッドの再定義とは、元のメソッドを変更するのではなく、新しいメソッドを定義して、元のメソッドの名前をつけているわけだ。

アラウンドエイリアス

class Fixnum
  alias :old_plus :+

  def +(value)
    self.old_plus(value).old_plus(1)
  end
end

3 + 5        # => 9
4 + 10       # => 15

新しい +()old_plus()の周りをラップしている。 これをアラウンドエイリアスと呼ぶ。

  1. メソッドにエイリアスをつける
  2. 新しいメソッドを定義する
  3. 新しいメソッドから古いメソッドを呼び出す

でもどんなときに使ったらいいの?

事例紹介

HTMLページを量産したい

  • 飲食チェーンの広告ランディングページ
  • 100店舗すべて別ページを作る
  • 基本デザインは同じ
  • 店舗固有のデータ(店名や住所など)はCSVで管理

テンプレートとデータを読み込んで各店舗のページを出力するようなツールが欲しい

イメージはこんな感じ

f:id:masayuki14:20160428121521p:plain

まずはCSVを読み込もう

新米プログラマの例

store_id store_name address
102 おばけベーカリー 新宿店 新宿区新宿2丁目・・・
103 おばけベーカリー 渋谷店 渋谷区宇田川町・・・
110 おばけベーカリー 池袋店 豊島区東池袋1丁目・・・
line_number, rows = [ 0, {} ]
CSV.foreach('data.csv') do |line|
  line_number += 1

  rows << {
    store_id: line[0],
    store_name: line[1],
    address: line[2],
  }
end

これってイケてないよね

  • データの追加や順番が変わるたびにコードの修正が必要
  • store_id などの項目名が生かせてない

こんな感じにしたい!

row = SupponRecord.new(line)

row.store_id       # => 102
row.store_name     # => おばけベーカリー 新宿店
row.address        # => 新宿区新宿2丁目・・・

オープンクラスと動的メソッド

CSVファイルの1行目(項目名)を使ってアクセッサを動的に定義してやればいい!

class SpooRecord

  ### オープンクラスの同類
  # 特定のインスタンスを再オープンして定義を追加します
  # この中で定義されたメソッドはクラスメソッドになります
  class << self

      ### 属性の定義
      # attributes = ['store_id', 'store_name'] の場合
      # store_id, store_id=, store_name, store_name= のアクセッサを定義する
      def define_attributes(attributes)

        attributes.each do |attr|
          attr_accessor attr # attrへのアクセッサを定義
        end
    end
  end
end

irb で実行してみよう

2.2.1 :020 >   SpooRecord.define_attributes( ['id', 'name', 'address'] )
 => ["id", "name", "address"]
2.2.1 :021 > r = SpooRecord.new
 => #<SpooRecord:0x007ffcc1046378>
2.2.1 :022 > r.id = 100
 => 100
2.2.1 :023 > r.name = 'Spookies'
 => "Spookies"
2.2.1 :024 > r.address = '京都市'
 => "京都市"
2.2.1 :025 > p r
#<SpooRecord:0x007ffcc1046378 @id=100, @name="Spookies", @address="京都市">
 => #<SpooRecord:0x007ffcc1046378 @id=100, @name="Spookies", @address="京都市">
2.2.1 :026 >

動的ディスパッチで値を設定する

2行目以降のデータを対応する項目値として設定する

class SpooRecord
  class << self

    # indexと項目名の対応を保持, 1 => store_id
    # クラスにアクセッサが定義される
    # SpooRecord.index_to_attr にアクセスできる
    attr_accessor :index_to_attr 

    def define_attributes(attributes)
      @index_to_attr ||= {}

      attributes.each_with_index do |attr, index|
        attr_accessor attr # attrへのアクセッサ
        @index_to_attr[index] = attr # indexと項目名の対応
      end

    end
  end

  # コンストラクタ
  def initialize(data)
    data.each_with_index do |value, index|
      attr = self.class.index_to_attr[index] # クラスメソッドの `index_to_attr` で読込
      self.send("#{attr}=", value) # 動的ディスパッチ!!
    end
  end
end

irb で実行してみよう

2.2.1 :023 >   SpooRecord.define_attributes( ['id', 'name', 'address'] )
 => ["id", "name", "address"]
2.2.1 :024 > r = SpooRecord.new( [100, 'Spookies', '京都市'] )
 => #<SpooRecord:0x007fd9c10945c8 @id=100, @name="Spookies", @address="京都市">
2.2.1 :025 > p r
#<SpooRecord:0x007fd9c10945c8 @id=100, @name="Spookies", @address="京都市">
 => #<SpooRecord:0x007fd9c10945c8 @id=100, @name="Spookies", @address="京都市">

出力は ERB へバインド

例えばテンプレートがERBだったらそのままバインドしちゃいましょう

<body>
  <ul>
    <li>店舗: <%= store_name %></li>
    <li>住所: <%= address %></li>
  </ul>
</body>
class SpooRecord

  ### テンプレートにデータをバインドしてブロックに返す
  def bind_erb(template_path)
    File.open(template_path, 'r') do |file|
      yield(ERB.new(file.read).result(binding))
    end
  end
end

SpooRecord を使ってみた

line_number = 0

CSV.foreach('data.csv') do |line|

  if line_number == 1
    SupponRecord.define_attributes( line ) 
    next
  end

  row = SupponRecord.new(line)
  row.bind_erb( 'template.erb') do |binded|
    File.open("#{row.store_id}/index.html", 'w') { |file| file.write(binded) }
  end
end

他にもいろいろ機能をついかしていくと。。。

https://gist.github.com/masayuki14/9d1e2aace3b75369c6ec5d15cdd6a642

メタプログラミングRuby