Quantcast
Channel: 文系プログラマによるTIPSブログ
Viewing all 140 articles
Browse latest View live

プログラマはもっと文字・絵・図を手で書いた方がいいと思う

$
0
0

手書きを侮ってはいけない。
f:id:treeapps:20151214235753p:plain
blog.livedoor.jp

この記事を見ていて、ちょっと思うところがありました。

8:名刺は切らしておりまして 2015/12/13(日) 17:51:39.07 ID:gwbm/i0E.net
スケジュール管理はスマホ 
メモはメモ帳 
手帳はいらないな・・・

スケジュールはgoogleカレンダー等でいいんですが、メモ帳への手書きは見直した方がいいんじゃないかと思っています。特に自分用のメモではなく、他人に何かを説明する時のメモ書き等についてです。今回はその他人に何かを説明する時のメモ・走り書き・お絵かきについてのお話です。

この記事の前提事項

綺麗で正しくきちんとした資料作りを目的としておらず、使い捨て、走り書きの絵・図を書く、というのが目的となっています。

もしきちんとした図をツールで書きたいのであれば、以下がオススメです。

Flow Chart Maker & Online Diagram Software
Cacoo(カクー): アイデアを今すぐこのキャンバスに

綺麗に図を書けるし、共有も簡単にできます。

漢字が本気で書けなくなってきた

昔「キーボードで文字を打ちすぎると漢字が書けなくなるよ」という話はよく聞きました。その頃はまだ私もバリバリ漢字が手書きできたのですが、最近やたら漢字が書けなくなってきました。(単にボケ・老化が進んだのだと思いますが)

頭で書こうとしている漢字は一瞬で思い浮かぶのに、いざそれを書こうとすると筆が止まってしまうのですよね。本気で思い出せなくなってきています。大雑把なイメージはすぐ湧いてくるのに、細かい部分がどうしても思い出せないのです。今こうしてブログで記事を書いてますが、これを手書きしろと言われると、多分書き方を忘れている漢字がたーくさん出てきます。

私のようなおっさんがそうなる分にはいいんですが、スマホの驚異的な普及で若年層まで漢字が書けなくなってきています。
www.nikkei.com
恐らく将来的には「手で書く」という行為が消滅するのだと思いますが、今書けなくなるのは本当にヤバイですね。

特にプログラマーは普段プログラム言語という機械の言葉ばかり扱うので、ますます日本語から離れていってしまいます。プログラマーの中には健康診断や年末調整や社内資料くらいしか手書きで文字を書かない人も多いのではないでしょうか。会社では他人に何かを説明する機会は結構多いので、その機会に自分の手で文字を書く事で多少は漢字忘れも防げると思います。気休め程度かもしれませんが・・

簡単なお絵かきで概要を説明できるか

www.bunkei-programmer.net

以前書いたこの記事の補足になりますが、説明がやたら下手な人に、「簡単なお絵かきで私にそれを説明して下さい」とお願いしてみると、これが全く筆が進まない。口頭で説明して貰うと↑この記事のように全く駄目だし、かといって文章だと説明の粒度が細かすぎるので、お絵かきで概要を説明して貰おうとすると、書けない人は多いです。

パワーポイントでカジュアルな感じのスライドを作れという訳ではなく、走り書きでサッと書いてサッと説明して欲しいのですが、それができないんですよね。物事を構造的に把握しているのであれば、絵で書く事は容易いと思うのですが、断片しか説明できない人が多く、ほとんど筆が進まない人が多いです。

今はペーパーレスの時代だろ。老害かよ。

そうですね。確かにタブレット等でも書き書きできますが、遅い、遅すぎるのです。excelや何らかのツールでお絵かきすると、非常に遅い場合がほとんどです。ツールを使うとどうしてもグリッドの幅や位置を気にしてしまったり、アイコンの大きさを揃えたくなったり、修正する時に削除要素をポチポチ削除する作業が必要だったり、本質的でない部分に時間を取られがちです。紙にザーッと走り書きで図や絵を書く方が圧倒的に速いです。マウスやトラックパッドでポチポチするより手で雑に走り書いた方が遥かに速い場合が多いです。

私もできればペーパーレスの方がいいんですが、とにかく電子機器へのメモだと速度が遅くて、時間が勿体無いのです。使い捨てのお絵かきで概要を説明するのにいちいち5分とか10分かけるのは時間の使い方が勿体無過ぎる。10〜30秒程度で超大雑把なお絵かきをするだけでも相手に伝わります。

一応iPad proとiPad Pencil等で手書きを再現する事はできますが、デバイスが場所を取り過ぎて邪魔だし、充電も時間かかるし、何より高価過ぎます。こんなものに万円単位のお金をかけていられません。使い捨て・走り書きのお絵かきのためにこういった電子機器を導入するのは正直現実感無いのではないかと思います。

絵が書けないという事は断片しか理解していないという事

前述で少し書きましたが、絵が書けるという事は、構造的に物事を把握できています。絵が書けないという事は、断片しか理解できていません。綺麗な絵を書けなくても構いません。ガタガタの図でもいいんです。書くことができるかどうかが問題です。

断片と断片を繋ぎ合わせると、絵になります。断片が書ければあと一歩です。もう少しでその断片は構造を表す絵になります。そこまで書ければ深く理解している可能性が高いです。

言葉で説明して伝わるだろ?アホなの?

お絵かきで説明する事は、沢山の人に汎用的に伝えやすいというメリットがあると思っています。

例えば技術職の人が営業職の人に、論理的な構造を説明するのに口頭で説明してもほぼ伝わりません。それどころか、技術者が技術者へ説明しても、認識の齟齬が生まれる事は日常茶飯事です。言葉で伝わらなくても、簡単なお絵かきを見せると相手にイメージが湧きやすくなるので、伝わり易くなります。

言葉だけで物事の構造を伝える事は中々難しいので、どうしても認識がズレていってしまう事もあります。まずはお絵かきで脱線しないように大枠・概要を理解して貰って、細かい話はそこから掘り下げていかないと、「あ、そういう話だったのか。てっきり◯◯の話だと思っていたよ」とか、入り口から間違ってしまうすれ違いが起きる事もあります。そのすれ違いを防ぐ意味でも、走り書きのお絵かきがあるといいと思います。

私が思うツールのイケてないところ

  • マウスポチポチの動作が遅すぎる。
  • 要素から要素へ繋げる矢印。ノートならうねうね超スピードで自由に書けるのに、ツールだと直角だったり決まった角度でカーブする場合が多く融通がきかない。その調整をする時間が勿体無い。
  • どうしても絵やアイコンを綺麗にしたくなってしまう。そして位置をグリッドにピッタリ揃えたくなる衝動に駆られる。
  • 絵と表を同時に扱いにくい。
  • フォントサイズの調整の手間。
  • サイドバーから目的のアイコンや図を探し、目的の位置にドラッグ・移動する手間。
  • 作業領域の調節の手間。確かvisio等は標準のキャンパスサイズが非常に狭く、ほとんど絵が書けなかった記憶があります。

雑感

最近とにかく絵やDBの図をノートに書きまくるので、キャンパスノートの消費が激しいです。1冊2ヶ月くらいで使いきってしまいます。(少ないかな)

図や絵は大きく、表も大きめに書くので、ますます消費スピードは早くなります。1回の説明で1ページ使ってしまう程の雑さ加減です。勿体無いノートの使い方をしてる自覚はあるのですが、勿体云々よりも「もっと速さを・・!もっともっと速く!!」という事を中二っぽく心がけているので、雑に沢山書いています。英単語も数文字書けば後は口頭でフォローできるから単語の末尾部分はもう何書いてるか解らないし、漢字もごにょごにょって書いて何書いてるか解らない部分も多々有りますが、重要なのはそこではなく、概要・構造・全体像をお絵かきで把握して貰う事にあります。

以前は全くお絵かき説明をせず口頭ばかりだったのですが、試しにお絵かきしてみたら、これが全く何も書けなくて愕然としました。単語をポロポロ並べるくらいしかできないのです。そこで始めて「ああ、自分は構造というか、全体像を他人に説明できる程理解して無かったんだ」という事をはっきり認識しました。それから他人に説明する時はノートを使ってババっと手書きで説明する機会が増えました。実際絵を書いてみると、「この位置にこの表を書いてしまうと、他の要素が書けない」等、複数の要素の関連を意識するようになって、自然と全体像を意識するようになります。


最近有名なベンチャー系のIT企業でもトヨタのカンバン方式を採用していたり、ホワイトボードに付箋をペタペタしたり、結構アナログな方式も見直されてきていますね。
www.publickey1.jp
タスク管理ツールのredmineやjiraにもかんばんが現れましたが、ツールに頼りきるのではなく、こうしたホワイトボードに付箋を張る企業も沢山あります。

ツールを使おうとすると「こういう設定にしておかないと後で拡張する時に困る!」だの「よし!皆で最強のワークフローを最初に作ろう!運用はそれからだ!」だの「他のデータとの連携が云々!」とかいってそもそもスタートすらせず終わってしまう場合が多いので、アナログ方式で「とりあえずやってみる」というのも重要かもしれませんね。特にプログラマーの場合は「真っ先に行動に移す」よりもそもそもの設計から考えだしてしまい、収集がつかずに企画がポシャる可能性が高いので尚更です。

世間でも言われていますが、デジタル一辺倒にならず、デジタルとアナログを上手く使い分けて取捨選択していきたいですね。特にIT企業ではデジタル色で染まっている傾向が強いので、ノートに文字や図や絵を書いたり、付箋で管理してみたり、アナログのメリットについてもう一度考えていきたいですね。


「大至急」という言葉は無能さのバロメーターになり得る

$
0
0

そのうち誰も相手にしてくれず孤立します
f:id:treeapps:20151219232359p:plain
皆さんのまわりに「大至急」という言葉が大好きな人っていますか?今回はこの大至急についてがお題です。

私は「大至急」という言葉を多く使えば使う程、「私は無能です」と言っているようなものだと思っています。

まずはいくつか大至急のパターンをみてみましょう。

大至急パターン

主な登場人物

( `Д´)

名前は「大至急(ダイシキュウ) 太郎(タロウ)」。彼は大至急という言葉が大好きである。大至急と言いすぎて狼少年状態で会社で孤立している。彼が1日に発した「大至急」という言葉を数えたところ、カウンターストップしたらしい。

(;´〜`)

名前は「損名野(ソンナノ) 無理駄(ムリダ)」。彼は大至急太郎の部下で、いつも大至急太郎から「大至急」と言われている。

( 上司)

大至急太郎の上司。

( -_- )

損名野の同僚。同僚A。

大至急太郎の上司。

大至急の理由を説明しないパターン

( `Д´)「おい損名野!これ今日中にやっといて!大至急!」
(;´〜`)「え?何をすればいいんですか?」
( `Д´)「同僚Aに聞け!アイツなら解る筈だ!急げ!大至急!大至急ぅぅぅぅ!」
(;´〜`)「同僚Aさん、大至急太郎さんから◯◯を大至急やれって言われたんですが、内容を同僚Aさんに聞けって言われたのですが、知ってますか?」
( -_- )「ああ、あれね。でもこれ急いでも全く意味ないよ」
(;´〜`)「え?でも大至急さんが大至急って・・・」

このように、「何故急ぐのかを説明しない」パターンがあります。このパターンの人は、理由なんて無い、急ぐのが当たり前だから等と特に理由もなく大至急と言っているだけの可能性があります。急いで仕事しても「あっそう。そこ置いといて」と興味無さそうに返され、1週間その資料が放置されている事に気づき、実は全く急ぐ意味が無かった事を知って怒りを覚える、なんて事もあります。

枕詞パターン

( `Д´)「これやっといて!大至急!」
( `Д´)「これ見積もりくれ!大至急!」
( `Д´)「はやく直せ!大至急!」

とりあえず言葉の末尾に「大至急」という言葉をつけているだけのパターンです。そういう人は、↓こういう事です。

( `Д´)「今日天気いいな!大至急!」
( `Д´)「眠いな!大至急!」
( `Д´)「腹減った!大至急!」

このように、言葉の末尾に大至急とつけているだけで、実は大至急でも何でもない場合があります。このパターンの人は、とりあえず大至急と言っておけば、周りが勝手に頑張ってくれて、自分にとって良いこと尽くめだと安直に考えている可能性があります。

急かして遅れを取り戻すぞパターン

( `Д´)「FAXピポパっと」

「大至急見積もりくれ。すぐに必要だ。急いでくれ。」

(;´〜`)「お、FAXだ。・・・なんだこれ・・・大至急かよ・・」
(;´〜`)「ぜぇ・・ぜぇ・・・で、できた。結果を返信っと・・・」

・・・1週間音沙汰無し・・・

( `Д´)「依頼してたの忘れてた。やべえもう時間ないわ。なんか期待してたのと違うから返信すっぞ。FAXピポパっと」

「想像してたのと内容が違う。今すぐにやり直せ。急げ。大至急だ!」

(;´〜`)「・・またFAXか。・・・おいおい、急いでるって書いてたのに1週間音沙汰ないじゃん。急ぐ気ないだろこれ・・」

このように、自分は相手を急かすのに、当人が急いでいるようには見えないパターンがあります。このパターンの人は、自分の仕事の遅れを相手を急かして取り戻そうとしている可能性があります。

五月雨大至急パターン

( `Д´)「損名野、これやって!大至急!」
(;´〜`)「は、はい。」
・・・30分後・・・
( `Д´)「損名野、これもやって!大至急!」
(;´〜`)「は、はい。」
・・・30分後・・・
( `Д´)「損名野、それもやって!大至急!」
(;´〜`)「・・・・(なんで五月雨で依頼するんだ。まとめてくれれば効率よくこなせたのに)」

このように、五月雨で次から次へと細かく依頼してくる人がいます。このパターンの場合、効率の問題じゃない!仕事とは常に全力で急ぐものだ!そうしていく事こそが最大の努力である!、等と精神論を振りかざしているだけの可能性があります。

left to rightパターン

( 上司)「大至急太郎君、これをやっておいてくれたまえ。」
( `Д´)「は、はい!」
( `Д´)「おい損名野!!これを今日中にやっておけ!大至急だ!!!」
(;´〜`)「は、はぁ・・・」

このパターンは、単に上司から依頼されたのでそれを左から右に流しているだけです。しかも急いで結果を出せば上司からの評価が上がると安易に考えている可能性があります。

矛盾パターン

( `Д´)「おい損名野、これ大至急できないか?無理なら来週でもいいから!」
(;´〜`)「??????(来週でいいって・・・急いでないだろお前)」

大至急と依頼しているのに、来週でも構わないと矛盾した事を発するパターンです。このパターンの場合、時間の感覚が分からなくなっている可能性があります。

普通とは一体……うごごごパターン

( `Д´)「おい大至急見積もってくれ!明日中に必要だ!」
(;´〜`)「それでしたら見積もるのに大体1週間程度必要になります。」
( `Д´)「はぁぁぁ!!??普通1日でできんだろ!!お前馬鹿だな!もうお名前には頼まない!!!」

普通の 法則が 乱れる!

このパターンの場合、自分の経験則がこの世全てに通用すると考えている可能性があります。

大至急は何故無能なのか

さて、簡単に大至急パターンを見たところで、何故大至急が無能なのかを考えてみます。

「大至急」はスケジュール管理を放棄している

物事には目標があって、その目標に到達するために計画を練ります。それが例え短期間であっても、「いつまでに完了しておきたいか」という期日を設定し、そこから逆算して、いつまでに何をしておかないとならない、といった事を検討します。

「大至急」という言葉をそれを吹き飛ばします。いつまでに何をするかを一切考えず、「単に全部急げば期日に間に合う筈」という浅はかで計画性の無さを露呈しているだけなのです。

全て大至急という事は1つも大至急のタスクが無い

全て◯◯というのは一つも◯◯ではないと言い換える事ができます。例えば「何でもできます!」という人は「何にもできません!」と言い換える事ができてしまいます。本当に何かができるなら「◯◯ができます!」と言います。

それと同様に、全てのタスクを「大至急」と表現することは、一つも大至急のタスクが無いと言い換える事ができます。タスクというものには優先度があって、先にやっておくもの、後でも構わないもの、等を切り分けて優先度を付けます。そうしてタスクが流れるように消化されるようなスケジュールができます。

全てが大至急の場合、優先度が無いと言っているようなものです。優先度が無いと言うことは、本当に急いでいるものは一つも無いのです。どれから始めてもいいし、期日に間に合えばそれでいいということです。結果としてスケジュールが遅延する可能性が大幅に上がります。

自分の計画性の無さを他人に押し付けている

大至急を口にする人は、大抵「相手は急がせるけど、自分は急げない」場合がほとんどなのではないでしょうか。自分の仕事の効率の悪さや段取りの悪さで遅れたスケジュールを取り戻そうと、相手を急がせる人がいます。大至急回答したのに、数週間放置するなんて事もあります。

有能な人ほど大至急と言わない

有能な人は突発的な急ぎのタスクが現れても、期日の設定と逆算したタスク消化スケジュールを瞬時に考え、メンバーにタスクを振り分け、トラブルを乗り越えます。不用意にメンバーを急がせないようにし、効率よく無理のないようにタスクを捌ける状態を作り出します。すると「大至急」等と言う必要が無くなるので、大至急という言葉を口にする事はありません。

一方無能な人は、全て大至急のタスクを設定し、「全部急げ!」等とスケジュール管理を開始早々に放棄し、丸投げ状態を作り出します。当然現場は混乱し、スケジュール遅延します。そして遅延しても「急いで!急いで!今すぐ!大至急!」とメンバーをまくしたてるばかりで、何もしません。正確には何もできないから、急かす事しかできないのです。

評価されるのは大至急と言わない方

( `Д´)「お前たち!大至急このタスクを片付けろ!!急げぇ!今すぐだ!!大至急ぅぅ!!!」

(;´〜`)「急いでいる時程冷静に計画をたてるべきだな。期日を◯日に設定して、あのタスクを優先して・・・・」

さて、あなたが評価する立場であった時、どちらを評価しますか?急げ急げと急かすだけで無計画な大至急太郎に対して、損名野は記事と優先度付をして冷静に捌こうとしています。

大至急太郎の方は無計画なので、上司への報告も「大至急対応しました!」と中身がすっからかんになってしまうのに対し、損名野の場合はチームのメンバーがどういう日程でどう対応したのかがはっきり見える状態になっています。中身が見えない大至急太郎の方は困難な局面だったのかどうかすら解らないので、評価のしようがありません。一方損名野の方は解りやすいので評価できるポイントがある筈です。

上司の評価だけでなく、チームのメンバーからの評価も大きな差がつきます。無意味にメンバーを急がせて無理をさせ、マイナス発言を大声で連呼するだけの大至急太郎はメンバーからの信頼を完全に失います。一方損何野の方はメンバーに対して今どんな状態で、誰が何をどうするのかが見える状態だったので、メンバー同士で協力しあう事もでき、メンバーから信頼も向上しているでしょう。

こんな事があったら要注意

  • タスクの優先度全てが「至急」や「急ぎ」しか設定しない。
  • 定期的に音沙汰が無くなり、ある日突然大至急と依頼をする。
  • メールの件名に「大至急」等と付けてしまう。
  • 枕詞が「大至急」。
  • 我が社は「急ぐ社風」です。

こんな場合は要注意です。IT業界の場合は、redmine等のタスクが全部「急いで」等となっている人は確実に「大至急先輩」です。

大至急合戦

B to Bの場合にクライアントに担当者が複数人いて、その方たちが全く連携せずにタスクを投げてくる場合があります。

例えばクライアント担当者の1人目が「大至急!」とタスクを投げてきているが、他の担当者は優先度を「普通」でタスクを投げてきているとします。こちらとしては1人に目が「大至急」なので、それを再優先で作業し、他の担当者のタスクの優先度を下げます。1人目が大至急パイセンなので段取りも最悪で手戻りも非常に多く、他担当者のタスクのスケジュールが遅延します。

こちらが「大至急パイセンからのご依頼が大至急に設定されていたので」と説明すると、他担当者は納得します。が、次回から様子が変わりました。大至急パイセンがいつも「大至急」でタスクを投げてきて自分のタスクの優先度が下がる事を心配し、他担当者も自分のタスクの優先度を下げられないように我先に「大至急」タスクしか振らなくなりましたとさ。

一見するとクライアント側の連携が取れていないのが問題だ、と片付けてしまいたくなりますが、クライアント目線で考えると見えてきます。向こうでは「大至急パイセンが現場をかき回しているせいで皆困っている」事が想像できますね。大至急パイセンにはそんな連携等無縁で、自分が常に最優先です。そんな人と連携する事は無理なのでしょう。そして段々と他担当者さん達は諦めムードになっていきましたとさ。

大至急と言われたら

大至急の理由を説明して貰おうとしても無駄な場合がほとんどです。そもそも管理ができないから「大至急」と口にしているので、急ぐ正当な理由をまともに説明できません。

大至急と言われた場合は、いつも通りに仕事をすればいいのです。相手は「大至急」という言葉に安心感を覚える場合が多いので、いつも通りのスピードで仕事をして「◯◯様のご依頼だったので、特別に大至急対応させて頂きました!」等とプレミア感を醸しだして返すだけで満足してしまう場合もあります。そもそも大至急仕事をしてまともな品質になる訳がないので、いつも通りの仕事をすれば、結果としてそれが最速で高品質になる場合がほとんどです。どうしても正当な理由があって急がざるを得ない場合は、◯◯という要件を削るか、ここを次期フェーズに遅らせる事ができれば速くなります、等と条件を付けて交渉するといいかと思います。

↑のように対応しましたと上司に報告して、もし上司が「もっと急げるだろ!大至急ぅぅぅ!」と返してきても「この作業は十分最適化されており、これ以上急ぐと不用意に品質を劣化させるだけでクレームが来ます。仮に急げてもコストに見合いません」等と冷静に感情的にならずに返しましょう。それで上司が納得しないのであれば、某企業の「チャレンジをお願いしたい」のと同じなので、最悪な結果が待っているかもしれません。

雑感

客商売の場合は大至急パイセンはクレーマーとして対応したりで大変なようですね。下手な対応するとクレームが入って今度は上司から「大至急!」と依頼されたりするようで、本当に大変そうです。


大至急という言葉を口にすればする程、自分の無能さを強める結果になります。いつも「大至急!」と言ってしまうと、周りから「あいついつも急いでんな」という評判が広まるので、「はいはい急ぐ急ぐwww」等と徐々に雑な対応されるようになり、狼少年のような状態になります。終いには本当に急ぐ必要に迫られた時に「大至急!」と依頼しても「大至急パイセンが急いでいらっしゃいまーすwwwプププwww」と相手にされなくなり、破滅するしかない状況に愕然とするかもしれません。

このように「大至急」という言葉は「悪」や「マイナス」のイメージを連想させるので周りへの心証も非常に悪く、周りからの評価を下げたり孤立したりする事は必至です。まさに百害あって一利なしなのが「大至急」なのです。

大至急と思わず言ってしまう場合はまず「何故大至急なのか」を考え、大至急にならないために状況を整理したり、逆算してスケジュールを検討したり、急ぎタスクが発生しそうかどうかを事前に検討して動くようにすると、徐々に「大至急」の理由がなくなっていき、最後には言わなくなります。その頃には無能から有能にクラスチェンジしている可能性が高いので、きっと周りからの信頼も勝ち取り、狼少年ではなくなっているはずです。

vue.jsのブラウザ毎のcheckboxクリック時の取得値の違い

$
0
0

違うんですね
f:id:treeapps:20151226131907p:plain

最近vue.jsを勉強しているのですが、どうもcheckboxのクリックイベントで取得できる値がブラウザ毎に異なる事に気付きました。

恐らく超が付くほどの初歩的な内容だと思いますが、忘れないようメモしておきます。

clickイベントで取得できる値

ブラウザ毎に挙動が・・

いろいろ簡単なサンプルを作っていたところ、checkboxにv-on:clickでイベントを設定した時、イベント内で取得できるチェックボックスの選択値が、safariとchrome・firefoxで異なる事に気づきました。

以下をsafariと、chromeまたはfirefoxでそれぞれ実行してみて下さい。value値が異なる事が確認できます。

safariはcheckboxをチェックした後の値が取得できますが、chromeとfirefoxはチェックする前の値が取得されます。

vue.jsのバグでは無さそう

一瞬バグかと思ったのですが、どうも違うようです。

update, this is not a bug. The problem is with how the events are being triggered by each browser. Firefox/Chrome are firing the click even before the change event so Vue doesn't pick it up.

http://forum.vuejs.org/topic/596/how-can-i-get-the-latest-dom-status-on-both-firefox-and-chrome/3

ブラウザの実装によって返る値が違う、ということのようですね。

対応するにはmethodsに定義せずwatchに定義する


このように、checkboxModelをwatchで定義して状態変化を監視すると、変更前と変更後の値を取得する事ができます。状態変化=checkboxのクリックとほぼ同義なので、これで代用できそうです。

雑感

ブラウザの実装の問題だとすると、この問題は恐らく他のFWにも同じ事が言えると思うのですが、どうなのでしょうね。

正直こういうブラウザの実装によって挙動が変わるとか、IE6問題の再来のようで死ぬほどアホくさいです。本来開発者が集中するべきところはそこではないので、なんとかして欲しいものですが、どうにもならないのでしょうね・・・

AngularJS アプリケーションプログラミング

AngularJS アプリケーションプログラミング

速習ECMAScript6: 次世代の標準JavaScriptを今すぐマスター!

速習ECMAScript6: 次世代の標準JavaScriptを今すぐマスター!

gmailで一定時間経過したら自動削除するスクリプトを定期実行してメール数を減らそう!

$
0
0

メール数を少なくすると、imapによるメール同期が軽くなったり検索が高速になったりするのでオススメです。
f:id:treeapps:20160102020833p:plain

新年あけましておめでとうございます。今年も文系プログラマによるTIPSブログとtreeをよろしくお願いします。


さて、新年1発目のお題は「メールの世代管理」です。

プログラマな皆さんなら各種ログはcron等で世代管理していたりしますよね。では、gmailのメールはちゃんと世代管理していますか?会社のメールならともかく、個人メールだと乱雑に不要なメールが溜まりがちです。これを機にメールの世代管理もしましょう!

世代管理は以下のようなメールに有効に機能します。

  • セール情報のメール
  • 電気代やクレジットカード等の請求メール
  • 佐川やヤマトの配達通知メール
  • twitterやfacebookの通知メール

一定期間過ぎてしまうと無意味になってしまうメール、セキュリティ的に長期間保存しておきたくないもの、各種通知系等が主な世代管理対象になります。

では早速やってみましょう。

1, gmailを開く

まずは対象のgmailをwebブラウザで開きます。

f:id:treeapps:20160102012822p:plain

2, 世代管理用のラベルを作成する

世代管理専用のラベルを作成します。ここでは「30日で削除」という名前でラベルを作ります。

f:id:treeapps:20160102012918p:plain

3, 世代管理したいメールをフィルタで「30日で削除」ラベルに振り分ける

世代管理したいメールのフィルタを作成し、前述で作成したラベルに振り分け設定をします。

f:id:treeapps:20160102013001p:plain

4, Googleドライブを開く

先ほど作成したフィルタに対して、Apps Scriptを作成・適用したいので、Googleドライブを開きます。

f:id:treeapps:20160102013110p:plain

5, Googleスプレッドシートを新規作成する

Apps Scriptを作成するのにGoogleスプレッドシートが必要なので、新規作成します。

f:id:treeapps:20160102013208p:plain

6, スプレッドシートのファイル名を付ける

初期状態だと「無題のドキュメント」となっているので、適当にファイル名をつけます。

f:id:treeapps:20160102034829p:plain

7, スクリプトエディタを開く

メニューバー → ツール → スクリプトエディタ と開きます。

f:id:treeapps:20160102013339p:plain

8, スクリプトエディタが開く

スクリプトエディタを開くと、以下のような画面になります。既に雛形のメソッド「MyFunction」がありますが、これは使いません。

f:id:treeapps:20160102013432p:plain

9, スクリプトを編集して保存する

f:id:treeapps:20160102013523p:plain

以下のスクリプトをコピペして貼り付けて下さい。

function expire30days() {var day = 30;
  var label = '30日で削除';
  var threads = GmailApp.search('older_than:' + day + 'd -is:starred label:' + label);
  for (var i = 0; i < threads.length; i++) {
    threads[i].moveToTrash();
  }}

もし「30日で削除」というラベル名にしなかった場合は任意に「var label = 'xxx'」の部分を編集して下さい。

編集できたら「保存」ボタンをクリックします。

10, プロジェクト名を設定して保存

プロジェクト名の入力を求められるので、適当に「gmail」と入力してOKボタンをクリックします。

f:id:treeapps:20160102013727p:plain

11, スクリプトを実行してみる

作成したスクリプトを早速実行してみましょう。▶ボタンをクリックするとスクリプトを実行できます。

f:id:treeapps:20160102013817p:plain

12, 実行権限を付与する

Apps Scriptからgmailを操作するには、gmailに対して「Apps scriptで操作される事を許可する」必要があります。

f:id:treeapps:20160102013904p:plain

13, 実行を許可する

「許可」ボタンをクリックして許可します。

f:id:treeapps:20160102014011p:plain

14, スクリプトが実行される

許可ボタン押下後、元のスクリプト画面に戻り、自動的にスクリプトが実行されます。「30日で削除」ラベルに沢山メールがある場合、削除に時間がかかります。

f:id:treeapps:20160102014040p:plain

2016/01/02時点ではApps Scriptは6分までしか実行できないようです。スクリプトを実行してから6分経過すると強制的にスクリプトが停止します。
Quotas for Google Services  |  Apps Script  |  Google Developers

15, トリガーを作成する

作成したApps Scriptを定期的に自動実行して欲しいので、トリガー(cronみたいなの)を作成します。

f:id:treeapps:20160102014644p:plain

16, トリガーを追加する

初期状態だとトリガーが空っぽなので、リンク部分をクリックしてトリガーを追加します。

f:id:treeapps:20160102014733p:plain

17, トリガーを作成して完了

どのメソッドを、どんなタイミングで定期実行するかを決める事ができます。日タイマーを選択すると、毎日自動実行してくれます。

f:id:treeapps:20160102014820p:plain

これでgmailに作成した「30日で削除」ラベルに振り分けられたメールを、expire30daysメソッドを毎日定時実行して削除する世代管理が設定できました。

雑感

会社の場合は使用するメールアドレスが1個の場合がほとんどだと思いますが、個人で使うメアドは複数の場合が多いですよね。すると必然的にMail.appやThunderbird等のメーラーを使う機会が増えます。

最近だと大抵imapでメールを同期すると思いますが、メール数が多いと同期に時間がかかったりしてPCが重くなったりする事もあります。なので、可能な限り不要なメールは削除していく事をオススメします。

少ないメールアカウントで少ないメール数なのが理想なのですが、私の場合は個人で使うメールアドレスが5アカウントもあるので、大量のメールをimapで同期しています。これが最近メールの掃除をしていなかったせいか、Mail.appが非常に重く困っていました。(メモリは32GByte搭載)そこで今回メールの大掃除をするべく、この記事を書いたわけです。

正直gmailが正式にマルチアカウントの同時表示に対応してくれれば、もうローカルでメーラーを起動する機会もなくなるので、対応して欲しいですね。

Spring bootでjsonpの先頭に謎の/**/が混入する問題とその対応

$
0
0

なんでデフォルトの挙動がこんな事になってるんですかね・・・
f:id:treeapps:20160113231034p:plain

現象

以下はチュートリアルのjsonを返すコントローラーです。

import java.util.concurrent.atomic.AtomicLong;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.music.anime.tree.json.Greeting;

@RestControllerpublicclass GreetingController {

    privatestaticfinal String template = "Hello, %s!";
    privatefinal AtomicLong counter = new AtomicLong();

    @RequestMapping(value = "/greeting", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
        returnnew Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}

しかし、jsonではクロスドメインの壁を超えられないので、jsonpを返したいところです。

そこで以下のようなクラスとメソッドを用意すると、親クラスのjsonpQueryParamNamesに「callback」というパラメータが設定されます。この値が設定されると「callback」というパラメータがある場合はjsonpになるよ、という処理がされます。

import java.io.IOException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

@ControllerAdvicepublicclass JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

しかしこの状態でjsonpを出力してみると、何故か以下のようなjsonpが返ってしまいます。
http://localhost:8080/greeting?callback=test

/**/test({"id":1,"content":"Hello, World!"});

何故か先頭に「/**/」というコメントが挿入されてしまいます。

正直これではカッコ悪過ぎますね。

対応

何故先頭に謎ものコメントが挿入されるのかあまり解ってないのですが、以下のようなメソッドを定義しておくと、謎の「/**/」を削除する事ができます。

import java.io.IOException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

@ControllerAdvicepublicclass JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }

    @Beanpublic MappingJackson2HttpMessageConverter MappingJackson2HttpMessageConverter(ApplicationContext ctx) {
        ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().applicationContext(ctx).build();
        returnnew MappingJackson2HttpMessageConverter(mapper) {
            @Overrideprotectedvoid writePrefix(JsonGenerator generator, Object obj) throws IOException {
                if (!(obj instanceof MappingJacksonValue))
                    return;
                String funcName = ((MappingJacksonValue) obj).getJsonpFunction();
                if (funcName != null)
                    generator.writeRaw(funcName + "(");
            }
        };
    }
}

MappingJackson2HttpMessageConverterメソッドがあると、
http://localhost:8080/greeting?callback=test
のURLで以下の正常なjsonpが返るようになります。

test({"id":1,"content":"Hello, World!"});

これでようやくまともなjsonpを生成する事ができます。

www.bunkei-programmer.net

オフィスグリコやオフィスおかんで赤字が出てしまう残念な現状

$
0
0

誰なんだお金払わずに勝手に持って行ってるやつは
f:id:treeapps:20160117003302p:plain
オフィスグリコやオフィスおかん、最近色々な会社で見かけますね。

私が在籍中の会社にも両方あります。

オフィスグリコとは

オフィスグリコのサービススタッフが定期的にオフィスを訪問し、代金の回収や商品の補充・賞味期限管理等を行います。 オフィスの従業員の方が各自で利用したい時に、BOX、冷凍冷蔵庫から商品を取り出し、代金を備え付けの貯金箱に投入して頂くシステムになります。 BOX、冷蔵庫の設置費用は不要、また盗難、紛失などによる金銭的補償も求めませんので、安心してご利用頂けます。

https://www.glico.com/jp/enjoy/service/officeglico

オフィスおかんとは

オフィスおかんはオフィスにいながら美味しくて、健康的な食事を
24時間いつでも食べることができるぷち社食サービスです!

https://office.okan.jp/service/

両方ともセルフレジ形式で、商品を持っていく時に集金ボックスにお金を投入するシステムです。

しかしですね・・・・


利用者の支払額が明らかに少ない


という状態なのです。なんとほぼ毎月赤字が出ているという驚愕の状態です。

オフィスグリコの場合、例えば100円のおかしが10個有るとして、10個売れたらカエルの口の集金箱に合計 10 ✕ 100円 = 1,000円 入ってないといけませんよね?しかし1,000円以下しか入っていないのです。

という事は、誰かがお金を払わずに持って行ってしまっている、という事になります。なんという残念な事なのか・・・


では、何故赤字になるのでしょう?

環境

原因の推測の前に、環境の説明です。

一般的なセルフレジとの違い

最大の違いは、利用者が従業員のみ、という点です。

スーパーのセルフレジとは違い、子供がいたずらで万引きするような環境ではなく、いい年した社会人しかいない環境での出来事なのです。

利用者層

平均年齢は30歳前後で、若者も多いです。おかし食べながら仕事しても問題無いくらいに緩い職場です。

赤字理由の推測

オフィスグリコ・オフィスおかんが有料だと知らない

単に知らないのかもしれません。

しかし、知らないといっても集金箱が大体目につく場所にあるし、オフィスグリコの場合は「100円入れてね!」とカエルが口を開いているし、オフィスおかんは「お金はこちらへ」とピンクの集金箱があります。これが目につかないという事は流石に無さそう。

だとすると外国人従業員が日本語を読めず、無料だと思って持って行ってしまうのか?流石にそれは考えにくいですね。流石にそこまで日本語力が低い外国人従業員は存在しないというか、そんな人はそもそも雇われません。

従業員以外の侵入者

オフィス内に入るのに電子式の入館カードが無い会社の場合、自由に社内に入る事ができます。その侵入者が勝手に持って行っているのかもしれません。

しかし、わざわざ侵入してまでおかしとか惣菜を盗みますかね・・・?

後で払うから!!

「あ、やっべー!今日財布忘れたわ!でもおかし食べたいから、明日払うよ!」 → 忘れる

アホか。せめて誰かにお金借りろ。

ツケといて!

ここはバーじゃない。

疲労困憊による支払いの忘却

「徹夜2日目だぜ・・・スゲー眠くて疲れた・・空腹に耐えられないからなんか食べたい・・・でもコンビニに行く体力も無い・・・おかんでお惣菜喰うか・・・」 → モグモグ → 「すまん、ちょっと寝るわ・・・」 → グガー zzzz → あれ、俺お金払ったっけ?まさか払ってないわけないよな!hahaha!

疲労困憊により、お金を払わないといけない事すら忘却してしまう心身の状態、そんな状態なのに食事だけは無意識に行ってしまう。

私は重役だぞ?

「私は重役だ。お金を払う義務はない。」

通報しますよ。

来客者に御裾分け

来客者におもてなししたい気持ちは解る。だがな、おかしや惣菜を来客者に御裾分けするのか?しかも会社にツケ?色々おかしい。

あれ?今いくら投入したっけ?

パターン1

おかし食おう → カエルの口に投入! → ジャラジャラ → あれ?今いくら入れた?100円✕3枚入れたよな? → 2枚しか入ってない

パターン2

おかし食おう → カエルの口に投入! → ジャラジャラ → よし、100円✕3枚入れたぞ! →250円しか入っておらず、50円玉を間違えて投入。

これらのパターンって電車の自動券売機でお年寄りが「今私は500円入れたぞ!」と駅委員に怒鳴りつけているが、実際は100円しか入れてない、みたいな状況に似てますね。

動物説

( =・ェ・=)「おなかすいたニャー おそうざい冷蔵庫から貰うニャー」

かわいい

でもね、ここは地上5階以上なんだよ。エレベータにのって自分で冷蔵庫開けたのかにゃ? まあ曲芸猫は結構いるけど、流石につまみだされるでしょう。
f:id:treeapps:20160117140555p:plain

横領

利用者はキッチリお金を払った。しかし金額が合わない。そういえばお金の管理者のお金の管理ってちゃんとしてるのかな。あっ・・(察し)

どうせやるなら巨額の内部留保を10億使い込むくらいしてくれ。スケールが小さすぎる。

雑感

あまりに赤字が続くので、オフィスグリコとオフィスおかんとの契約を解除せざるを得ないような危機的状況になってしまっています。

100円玉と50円玉を間違えて投入するケースはあり得るんですが、それなら逆に商品価格より多い金額を投入する間違いがあっても良いはずですよね。しかし、多かった事は一度もないようです。必ず少なくなります。そう考えると、うっかり投入ミスの可能性は低いかな、と。なら、「後で払うから!」等の幼稚な悪意満載のミスしか無いのではないか、と考えています。

これらの対策として「対面式販売に限定する」等の意見がでると思いますが、対面式にすると、誰かがレジ係りをする=人件費が発生するので、おかし・惣菜の値段以上のコストが発生するのですよね。また、レジ係りの人がいない場合、例えば定時後・病欠・お昼休憩中・深夜・早朝の場合はコンビニに行かざるを得なくなってしまいます。それだとオフィスグリコ・おかんのメリットが薄れてしまいます。

自販機タイプもありますが、自販機を置くスペースって結構が必要になるし、ランニングコストが高かったりするので、導入のハードルは高い。
www.glico.com

最小限の人件費・ランニングコストでおかし・惣菜等を気軽に購入できるのがこういうサービスの強みなので、できるだけルールは緩く、便利であって欲しいですね。みなさんの職場では赤字は発生して無いですかね?もし赤字を解消するいい方法があれば、是非教えて欲しいです!

MySQLのIllegal mix of collations (latin1_swedish_ci,IMPLICIT)のエラーについて

$
0
0

以外と原因に気づかなかったりするんですよねこれ。
f:id:treeapps:20160125001242p:plain
MySQLを使っていて、たまに以下のようなエラーが起きる事があります。

[SQLで例外(Message=[Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation 'like'], ErrorCode=1267, SQLState=HY000)が発生しました。

エラーメッセージ通りなら、文字コードが何か誤っている、という事なのですが、具体的に何が原因なのでしょう。

よくあるエラーケース

MySQLのバージョンは5.5〜5.7が対象になります。この記事の事を実際に試したい場合は、docker-machineでmysqlのコンテナを作るとよいです。以前書いたこちらの記事も合わせてご覧下さい。
www.bunkei-programmer.net

MySQLの設定はこんな感じです。

 mysql> showvariableslike'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary|
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

文字コードがutf8mb4のデータベースを作成します。

createdatabase hoge charset utf8mb4;

テーブルがutf8mb4以外のケース

文字コードがlatin1のテーブルを作ります。

droptable if exists tbl_table_charset;
createtable tbl_table_charset(
    v varchar(10)
)engine=innodb charset=latin1;

このテーブルでwhere句にマルチバイト文字列を使おうとすると、このエラーが起きます。

mysql> select * from tbl_table_charset where v = 'あ';
ERROR 1267 (HY000): Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8mb4_general_ci,COERCIBLE) for operation '='

insertでマルチバイトを使おうとすると、また違ったエラーになります。

mysql> insertinto tbl_table_charset values('あ');
ERROR 1366 (HY000): Incorrect string value: '\xE3\x81\x82'forcolumn'v' at row1

データが無い状態でupdateした場合は、エラーになりません。

mysql> update tbl_table_charset set v = 'あ';
Query OK, 0rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

データが有る状態でupdataした場合は、先ほどと同じエラーになります。

mysql> insertinto tbl_table_charset values('a');
Query OK, 1row affected (0.00 sec)

mysql> update tbl_table_charset set v = 'あ';
ERROR 1366 (HY000): Incorrect string value: '\xE3\x81\x82'forcolumn'v' at row1

カラムがutf8mb4以外のケース

utf8mb4のテーブルに、latin1のカラムを混ぜ込みます。

droptable if exists tbl_col_charset;
createtable tbl_col_charset(
    v varchar(10)character set latin1
)engine=innodb charset=utf8mb4;

そしてlatin1のカラムに大して前述と同様にwhere句にマルチバイトを使ってしまうと、やはりエラーになります。

mysql> select * from tbl_col_charset where v = 'あ';
ERROR 1267 (HY000): Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8mb4_general_ci,COERCIBLE) for operation '='

こんなケース滅多に遭遇しないだろ、と思いますか?以外とそうでもありません。

ケース1)大きすぎるテーブル

InnoDBのファイルフォーマットがAntelopeになっている。その状態でInnoDBのテーブルを作成し、カラムに大きな値のカラムを沢山作ってしまい、大量のデータをinsertしようとして

ERROR 1118 (42000) at line 1: Row size too large. The maximum row size for the used tabletype, not counting BLOBs, is8126. You have tochange some columnstoTEXTor BLOBs

こんなエラーが出てしまい、テーブル容量の節約をしようとして、シングルバイトしか入らないカラムを片っ端からlatin1にしてしまった場合です。

カラムの文字コードを個別に設定した場合、そういう設定がされている事を調べるには「show create table xxx」するしかありません。「desc xxx」では解らないのです。なので、「え!?このカラムlatin1なの!?」と後から知って愕然とするケースもあります。

ケース2)メールアドレス

メールアドレスのカラムを作る時、最も問題となるのはフィールド長です。

文字コードをutf8mb4にすると、インデックスが貼れるのは最大で191byteまでです。191文字で済む要件の場合は問題無いのですが、「500文字まで受け付けたい!」等と言われると、途中までしかインデックス化できません。そこでよく使われる手法として、メアドのカラムを複数に分割して、ユニークキーのインデックスを貼る事で解決を図ります。その点については大分前に記事を書いたので、合わせてご覧下さい。

www.bunkei-programmer.net

こうしてlatin1のカラムが生まれたりします。

で、何が問題なのか

先ほどのコンソールを見て貰うと解りますが、selectしただけでエラーが発生してしまう点にあります。つまり、アプリケーションからlatin1等のマルチバイト非対応カラムに対してマルチバイトで検索するwhere句を発行されると即エラーになるのです。と言うことは、バリデーション処理が必須になるという事に繋がります。

例えば「商品コード」というvarcharで英数字が入るカラムがあったとして、通常ならそのカラムが「あああ」等と検索されてもエラーにならず、0件の結果が返ります。しかしこの商品コードがlatin1等である場合、「あああ」と検索された場合はSQLExceptionが確定してしまうので、シングルバイトの入力チェックバリデーションが必須になります。商品コードは最初はutf8mb4だったがいつからかlatin1に変更されていて、最近「Illegal mix of collations (latin1_swedish_ci,IMPLICIT)」なんてエラーが出るんだけど、なんぞこれ?というケースがあります。今まで必要無かったバリデーションが必要になってしまうのです。

雑感

このお話、残念ながらノンフィクションです・・・

antelopeとbarracudaの違いも知らず、row_type?何それ?という時代に設計したテーブルが段々と肥大化してしまい、ついにantelope+utf8mb4のInnoDBテーブルのサイズの限界に達してしまい、大きいデータが来た時にのみエラーが出てしまうという状況に遭遇し、以下のエラーに悩まされました。

ERROR 1118 (42000) at line 1: Row size too large. The maximum row size for the used tabletype, not counting BLOBs, is8126. You have tochange some columnstoTEXTor BLOBs

この制限は確かMySQL5.5以降から発生するので、「joinするの面倒だから1テーブルに全項目を押し込めようぜ!」等と横着をしたりせず、適宜テーブルを分割するようにすれば、このエラーは起きにくくなります。また、もしInnoDBファイルフォーマットがBarracudaではなくAntelopeになっているのであれば、できるならBarracudaに変更し、create tableする際は「row_format=dynamic」を付けると、こういった制限に引っかかりにくくなります。現在のInnoDBファイルフォーマットは以下で確認可能です。

mysql> showvariableslike'innodb_file_format';
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| innodb_file_format | Antelope  |
+--------------------+-----------+

この辺りを疎かにしていると私のようにアホな目に合うので、手遅れにならない内に確認した方がいいでしょう。

spring boot v1.3のdevtoolsのlive reloadとremote updateを試す

$
0
0

あともう一歩頑張ってくれればくっそ有用な機能なのにな〜
f:id:treeapps:20160113231034p:plain
spring boot v1.3がリリースされて、ライブリロードとリモートアップデートという機能が追加されました。

詳細はリファレンスを見て下さい。
20. Developer tools

ちょっと使ってみて、それぞれ気づいた事がありました。

※ この記事ではLiveReloadとRemoteUpdateの設定の仕方等については言及しません。リファレンス通りに設定すれば動くので省略します。

検証環境

  • iMac Retina 5K
  • idk v1.8.0_25
  • Spring Tool Suite Version: 3.7.2.RELEASE
  • Spring boot v1.3.1.RELEASE
  • docker toolbox(docker v1.9、docker-compose v1.5.2)
  • Google chrome + Live reload plugin(LiveReload - Chrome Web Store

Live Reload

変更部分のみ差分読み込みする形ではなくリスタートなので、プロジェクトが大きくなるとリスタート処理がちょっと遅くなりそうです。今後に期待ですね。

使い勝手に関しては「ほ〜ん、凄いな」と思ったのですが、ちょっと困った事がありました。

それはですね・・・

今ライブリロードがonなのかoffなのか解りづらい

点です。アイコンが非常に解りづらいのです。以下をご覧下さい。真ん中のアイコンがライブリロードpluginです。

f:id:treeapps:20160126222926p:plain
f:id:treeapps:20160126222932p:plain

1個目の画像がオンで、2個目の画像がオフです。アイコン真ん中が◯か●かの違いです。これは見辛い・・・・

Remote update

個人的に非常に気になる機能だったので、ちょっと試してみました。

気づいた点は以下です。

  • 更新速度は遅い。
  • 更新対象URLが1個しか設定できない。

という点です。

リモートのjarを更新するために、ローカルのソースが更新されたかどうかをポーリングしているようで、このポーリングのデフォルト値は1000ms、つまり1秒です。
Appendix A. Common application properties
「spring.devtools.restart.poll-interval=100」等と設定すれば、ポーリング速度を早める事が可能です。ポーリングは調節可能ですが、問題なのは更新速度です。スッカスカのプロジェクトで1文字変更して、ローカルのdockerコンテナにリモートアップデートするのに以下のように約5秒かかりました。ちょっと遅いかな?と感じました。ローカルのdockerに対してこの更新速度だと、外部サーバに対して行ったら更に遅くなる事は間違いないでしょう。

22:42:45.790 [File Watcher] INFO  o.s.b.d.r.c.ClassPathChangeUploader - Uploaded 2 class resources
22:42:50.820 [pool-1-thread-1] INFO  o.s.b.d.r.c.DelayedLiveReloadTrigger - Remote server has changed, triggering LiveReload

更新速度よりも「更新対象URLが1個しか設定できない」という方が問題かと思います。

docker と spring boot v1.3

v1.3でリモートアップデートが可能になったという事は、ローカルのdockerのコンテナに対してリモートアップデートすれば、都度ビルドしなくてもそこそこの速度でコンテナ上のjarを更新できる!!とか考えていたのですが、ちょっと問題がありました。

リモートアップデートは以下のようにdocker-composeで試しています。

nginx-proxy:image: jwilder/nginx-proxy
  ports:- "80:80"- "443:443"volumes:- "/var/run/docker.sock:/tmp/docker.sock:ro"restart: always
redis:image: redis
  expose:- 6379h2:image: making/h2-server
web-pc:image: tree-tips/web-pc
  links:- redis
    - h2
  expose:- "8080"environment:- VIRTUAL_HOST=www.hoge.local
    - VIRTUAL_PORT=8080
  command:>
    --spring.datasource.url=jdbc:h2:tcp://${H2_PORT_1521_TCP_ADDR}:${H2_PORT_1521_TCP_PORT}/~/hoge

docker-machineでホストを作成し、そのホスト上に上記コンテナを作成しています。nginx -> spring-boot とリバースプロキシして、h2をDBサーバとし、redisをセッションレプリケーションで使用する、という一般的な構成です。

このdocker-compose.ymlは「web-pc」というコンテナが「1個」しか定義していません。この状態でリモートアップデートすると、問題無く更新できます。

しかし、「docker-compose scale web-pc=3」等とスケールする、もしくはdocker-compose.ymlに複数の「web-pc」を定義したらどうでしょう?devtoolsのリモートアップデートは1件のURLしか設定できないので、複数コンテナを同時に更新する事ができません。これは困りました。困ったので、とりあえず上記のように「web-pc」コンテナを1個に限定しています。

しかしこれだとローカルだけ初期インスタンス数が1、プロダクションだと3、等とステージ毎に環境を変えないといけなかったりして、非常によろしくありませんね。全ての環境で極力同じ環境にした方が、環境依存のエラーを減らす事ができます。インスタンス数が1個だと、セッションレプリケーションが効いてるのか効いてないのか解らなかったりするので、やはり環境は合わせたいものです。


更に、これはspring bootの問題ではないのですが、jwilder/nginx-proxyにもちょっと問題がありました。

jwilder/nginx-proxyとオレオレ証明書と301リダイレクト

webアプリはSSL通信したいので、以下のようにcertsフォルダにオレオレ証明書を配置してnginx-proxyにマウントします。

nginx-proxy:image: jwilder/nginx-proxy
  ports:- "80:80"- "443:443"volumes:- "/Users/tree/docker/hoge/certs:/etc/nginx/certs:rw"- "/var/run/docker.sock:/tmp/docker.sock:ro"restart: always

nginx-proxyは/etc/nginx/certsに所定のファイル名で証明書があると、docker-genによって「/etc/nginx/conf.d/default.conf」内が動的に以下のように書き換えられます。(一部抜粋)

server {
    server_name _; # This is just an invalid value which will never trigger on a real hostname.
    listen 80;
    access_log /var/log/nginx/access.log vhost;
    return 503;
}
upstream www.hoge.local {
            server 172.17.0.5:8080;
}
server {
    server_name www.hoge.local;
    listen 80 ;
    access_log /var/log/nginx/access.log vhost;
    return 301 https://$host$request_uri;
}
server {
    server_name www.hoge.local;
    listen 443 ssl http2 ;
    access_log /var/log/nginx/access.log vhost;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;
    ssl_prefer_server_ciphers on;
    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:50m;
    ssl_certificate /etc/nginx/certs/www.hoge.local.crt;
    ssl_certificate_key /etc/nginx/certs/www.hoge.local.key;
    add_header Strict-Transport-Security "max-age=31536000";
    location / {
        proxy_pass http://www.hoge.local;
    }
}

しかしこのコードには問題があります。以下の部分です。

server {
    server_name www.hoge.local;
    listen 80 ;
    access_log /var/log/nginx/access.log vhost;
    return 301 https://$host$request_uri;
}

SSL証明書を置いてしまうと、自動的にこのように80(http) -> 443(https)の301リダイレクトのコードが設定されてしまいます。

If a container has a usable cert, port 80 will redirect to 443 for that container so that HTTPS is always preferred when available.

https://github.com/jwilder/nginx-proxy

これが問題なのです。devtoolsのリモートアップデートは、ローカル -> nginx-proxy -> web-pc(spring-boot)という経路でアップデートできるのですが、ここにSSLが絡むとオレオレ証明書の場合は問題が起きます。

パターン1)Program Argumentsにhttpsを指定する場合

リモートアップデートする際は、Program ArgumentsにURLを指定し、Mainにorg.springframework.boot.devtools.RemoteSpringApplicationを指定します。このURLにhttps://www.hoge.com等とhttpsを指定すると、当然オレオレ証明書なので以下のように証明書のベリファイエラーになります。

23:21:53.024 [File Watcher] INFO  o.s.b.d.r.c.ClassPathChangeUploader - Uploaded 2class resources
Exception in thread "File Watcher" java.lang.IllegalStateException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at org.springframework.boot.devtools.remote.client.ClassPathChangeUploader.onApplicationEvent(ClassPathChangeUploader.java:107)
	at org.springframework.boot.devtools.remote.client.ClassPathChangeUploader.onApplicationEvent(ClassPathChangeUploader.java:56)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:163)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:136)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:381)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:335)
	at org.springframework.boot.devtools.classpath.ClassPathFileChangeListener.publishEvent(ClassPathFileChangeListener.java:68)
	at org.springframework.boot.devtools.classpath.ClassPathFileChangeListener.onChange(ClassPathFileChangeListener.java:64)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher.fireListeners(FileSystemWatcher.java:230)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher.updateSnapshots(FileSystemWatcher.java:223)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher.scan(FileSystemWatcher.java:183)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher.access$100(FileSystemWatcher.java:41)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher$1.run(FileSystemWatcher.java:150)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1917)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:301)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:295)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1471)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:212)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:936)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:871)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1043)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1343)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1371)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1355)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:153)
	at org.springframework.http.client.SimpleBufferingClientHttpRequest.executeInternal(SimpleBufferingClientHttpRequest.java:80)
	at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
	at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:93)
	at org.springframework.boot.devtools.remote.client.HttpHeaderInterceptor.intercept(HttpHeaderInterceptor.java:57)
	at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:85)
	at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:69)
	at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
	at org.springframework.boot.devtools.remote.client.ClassPathChangeUploader.onApplicationEvent(ClassPathChangeUploader.java:102)
	...12 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
	at sun.security.validator.Validator.validate(Validator.java:260)
	at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1453)
	...32 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:145)
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
	...38 more
パターン2)Program Argumentsにhttpを指定する場合

では、Program Argumentsにはhttp://www.hoge.com等とhttpを指定して、リモートアップデートをhttpで行うとどうでしょう。

リモートアップデートを実行すると、先ほどの301リダイレクトが動いてしまって、リモートアップデートのhttpリクエスト -> nginx-proxyがhttpsに301リダイレクトする -> web-pcに届く、となります。実際に試してみると以下の結果になりました。

23:24:42.310 [File Watcher] INFO  o.s.b.d.r.c.ClassPathChangeUploader - Uploaded 2class resources
Exception in thread "File Watcher" java.lang.IllegalStateException: Unexpected 301 response uploading class files
	at org.springframework.util.Assert.state(Assert.java:392)
	at org.springframework.boot.devtools.remote.client.ClassPathChangeUploader.onApplicationEvent(ClassPathChangeUploader.java:103)
	at org.springframework.boot.devtools.remote.client.ClassPathChangeUploader.onApplicationEvent(ClassPathChangeUploader.java:56)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:163)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:136)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:381)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:335)
	at org.springframework.boot.devtools.classpath.ClassPathFileChangeListener.publishEvent(ClassPathFileChangeListener.java:68)
	at org.springframework.boot.devtools.classpath.ClassPathFileChangeListener.onChange(ClassPathFileChangeListener.java:64)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher.fireListeners(FileSystemWatcher.java:230)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher.updateSnapshots(FileSystemWatcher.java:223)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher.scan(FileSystemWatcher.java:183)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher.access$100(FileSystemWatcher.java:41)
	at org.springframework.boot.devtools.filewatch.FileSystemWatcher$1.run(FileSystemWatcher.java:150)

301は想定外だよ〜、と怒られます。

パターン1でもパターン2でも怒られてしまうので、結局SSL証明書の配置をやめました。

nginx-proxy:image: jwilder/nginx-proxy
  ports:- "80:80"- "443:443"volumes:#    - "/Users/tree/docker/anime-music/certs:/etc/nginx/certs:rw"- "/var/run/docker.sock:/tmp/docker.sock:ro"restart: always

このdockerとオレオレ証明書の件はkeytoolで承認してしまったり、let's encryptで有効な証明書を作ってしまえば何とかなりそうですね。もしくは証明書のベリファイを無視するようカスタマイズするか、でしょうか。どうするのがスマートな解決方法なのでしょう。知りたいです。

雑感

リモートアップデート先が1URLしか設定できない件は何とかして欲しいですね。ローカルであってもdocker等で複数のインスタンスに対して更新したい要件って昨今では多いと思うので、今後に期待ですね。

もしかしたら、起動時は複数インスタンスを起動して、リモートアップデート時に1インスタンスにスケールダウンしてから更新させ、更新が終わったら再び元のインスタンス数にスケールアップする、とかしたら複数インスタンスの更新できそうな気がしないでもないですが、簡単に実現できるようになれば最高なんですけどね。。。これが実現できればローカルからプロダクションまでjavaでフルdocker環境とかできそうな予感がします。

とりあえずローカルのdockerの場合は1インスタンスで我慢すればリモートアップデートがちゃんと動くので、色々試していきたいと思います。


Spring bootでjsonpの先頭に謎の/**/が混入する問題とその対応:その2

$
0
0

今度はObjectMapperの設定と組み合わせた場合の例ですよ〜
f:id:treeapps:20160113231034p:plain

www.bunkei-programmer.net
前回の記事でjsonp出力時に謎のコメントが混入する件の解決方法を書きました。しかし前回はObjectMapperにインデントの設定をしたり、null値の属性出力の抑制等の設定をしていなかったため、あまり実践的なコードではありませんでした。

今回はjacksonのObjectMapperに各種設定をしつつ謎のコメントが入らないようにする設定についてまとめます。

環境

  • JDK1.8
  • Spring boot 1.3.2.RELEASE

☓ 駄目な例

@Configurationpublicclass WebAppConfig extends WebMvcConfigurerAdapter {

    @Overridepublicvoid configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.indentOutput(true).serializationInclusion(JsonInclude.Include.NON_NULL);
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
    }
}

これは駄目でした。インデントが有効になってnull値の場合に属性が出力されないようになったのですが、以下のように謎の「/**/」が混入してしまいます。

/**/test({
  "head" : {
    "code" : 200
  },
  "body" : {
    "cd" : "コードです",
    "name" : "名前です"
  }
});

◯ 正しい例

@Configurationpublicclass WebAppConfig extends WebMvcConfigurerAdapter {

    @Overridepublicvoid configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.indentOutput(true).serializationInclusion(JsonInclude.Include.NON_NULL);
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()) {

            @Overrideprotectedvoid writePrefix(JsonGenerator generator, Object obj) throws IOException {
                if (!(obj instanceof MappingJacksonValue))
                    return;
                String funcName = ((MappingJacksonValue) obj).getJsonpFunction();
                if (funcName != null)
                    generator.writeRaw(funcName + "(");
            }
        });
    }
}

答えは簡単でした。前回の記事と今回のJackson2ObjectMapperBuilderを組み合わせるだけです。これで以下のように先頭の謎のコメントは削除されます。

test({
  "head" : {
    "code" : 200
  },
  "body" : {
    "cd" : "コードです",
    "name" : "名前です"
  }
});

yumを翻訳すると・・・ΩΩΩ

$
0
0

皆大好きyum、実は◯◯◯◯だった!?
f:id:treeapps:20160207001859p:plain
先日githubのとあるissueを見ていて、英語のやりとりでよく解らない表現があったのでwebページ全体の機械翻訳を試してみたのですが、これがまた何ともいえない結果になってしまいました。


yumの翻訳結果がね・・・


ということで今回は「yum」の翻訳を各種翻訳サイトにお願いしてみました。

各社どう翻訳したか

Yahoo

f:id:treeapps:20160206234459p:plain

weblio

f:id:treeapps:20160206234518p:plain

infoseek

f:id:treeapps:20160206234543p:plain

So-net

f:id:treeapps:20160206234604p:plain


( ^p^) おいちい




yum install httpd = おいちい、httpdをインストールしてください

これが宇宙の真理か


待て、結論を急ぐな。まだ皆大好きgoogle先生とか、結果を見ていないぞ


おう、google先生よぉ、見せてみろよ

Google先生

f:id:treeapps:20160206234628p:plain

excite先生

f:id:treeapps:20160206234650p:plain


真面目か!!


なんだよ結局google先生とexcite先生が最強かよ


そうでもないと思うぞ。google翻訳とかはどうにも機械学習のせいか、メカメカした翻訳結果で以外と賢くなかったりするぞ


でも他の翻訳は「おいちい」じゃねーか。マジつっかえ


確かに今回はそうなってしまったが、個人的にはweblioが賢い翻訳結果になる事が多く、重宝しているぞ。weblioはいいぞ。ちょっとサイト重いけど。


しかしプログラム言語とか、ITに特化した無料の翻訳とかそろそろ出てもいいんじゃないかと思う。


然るべきところに投資というか、お金を払おうとしないからお前はいつまで経っても「おいちい」んだろうが・・

プログラマはもっと些細な事に疑問をもっていい

$
0
0

皆子供の頃「なんで〜?」「どうして〜?」と先生や親を困らせていたのに、大人になると疑問に思う事をやめてしまいます。なんででしょうね
f:id:treeapps:20160214005029p:plain
simplearchitect.hatenablog.com

こちらの記事を読みました。簡単に概要だけお話すると、「日本人は他人に聞く事に関して重く考えすぎだろオラーン!!!」という記事(コナミカン です。

この記事を見て私はこう思いました。

他人に聞く以前に、「何故?」と疑問にすら思わない人が増えているのではないか?疑問にすら思わないから、他人に聞いてみたり質問したりするきっかけがないのではないか?

と。

例えばプログラマといえば環境構築は避けて通れませんね。

環境設定の例を挙げると、gradleはbuild.gradleでプロジェクトに親子関係を持たせる事ができます。そこで「なんでその設定で親子関係が持たせられるの?」「build.gradleは具体的にどこにどういう設定をして親子設定したの?」と疑問に思う人と、思わない人がいました。

疑問に思った人の意見

  • ちゃんと挙動を確認しておきたい
  • 単純に好奇心
  • 自分の目で確認しないと信用ならない
  • 知っておいて損は無い

疑問に思わなかった人の意見

  • ツールのやる事なんて知らなくていい
  • 自分そういうの詳しくないんで
  • そういう事を意識していたらそれを使う意味ない
  • 訳が解らないから

ざっくりこんな感じでした。

疑問に思わない人の意見は尤もな意見です。しかし、裏で何が行われるか知っているけど、それを意識しないのと、何も知らないし考えもしないのは違いますね。

完全に把握する必要はないのですが、ほんの少しでも知っておいた方がいいですね。そうしないと、「こんな事しらなくていい」→「それを知るきっかけが訪れない」→「一生知る機会が生まれない」と繋がってしまうかもしれません。

他人に言われるがままで疑問に思わない

凄く極端な例ですが、昔こんな方がいました。

  • 環境構築は完全に手順任せで手順をコピペするだけのマシーンと化している。
  • デプロイ等の作業は、メモ帳にメモっているコマンドをコピペするだけ。エラーが起きても無関心。
  • 何か質問ありますか?と聞いて質問してきた試しがない。
  • 知識の源泉が、その職場でやっている事だけに限定されてしまい、外の世界を知らない。

この方は、日々の開発の中で、「このロジック、なんでこういう回りくどい事してるんだろう?何か特別な意味があるのかな?」とか、「この設定でなんでこうなるの?」など、ほとんど何も疑問に思わない人でした。完全に手順・指示に沿って、ロボットのごとく作業するだけでした。その作業内容も、基本他のロジックのコピペで、そのロジックが中で何をしているのかを考えもしないようでした。
www.bunkei-programmer.net

例えばこの方はこんなミスをしていました。あるITに微妙に心得のある風のクライアントから、日付のフォーマットをハイフン区切りにしたいので「YYYY-MM-DD」にして下さいと依頼を受け、その方はこんなコードを書きました。(クライアントはハイフンにしたいだけで、ちょっと頑張ってYYYY-MM-DDと書いてみたが、間違いだった事に気づいていない)

// ↓これが修正前のコード// SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");// ↓これが修正後のコード
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-DD");
Date date = sdf.parse(createDate);

普通に考えれば、よほど特殊な事情が無ければ「YYYY-MM-DD」は明らかに間違いで、「yyyy-MM-dd」が正解だと解ります。しかし彼はクライアントの言われるがまま設定し、見事バグとして起票されてしまいました。何故YYYY-MM-DDにしたの?と聞いてみると「言われた通りにやりました」と返答されました。確かにその通りなのですけどね。。。しかしここで「明らかにおかしくないかこれ?」と疑問に思わない点が、危険だなあと思いました。

たとえクライアントに言われた事であろうと、常に「ん?これ本当にあってる?」と考えたり、「これ言われた通りに反映するとヤバイよな・・?」と考える事は重要だと思うのです。言われるがまま作業をするだけだと、全く何も疑問に思わず、何か新しい事を知ったり知識を深めるきっかけがつかみにくいと思います。

疑問に思わない事を続けた末路

更に極端な例ですが、こんな感じに日々の作業で何かを疑問に思う事が無いタイプの方は、「他人に完璧にセットアップして貰った環境の上で、ビジネスロジックを書く事しかできない。しかもそのビジネスロジックは他の処理をコピペするだけ、という状況に陥っていました。他人が敷いてくれたレールの上を、他人が作った電車をコピーして走らせる事しかできない、というわけです。

そう、ビジネスロジックしか書けない症候群に陥ってしまっているのです。

そんな感じでできる範囲があまりにも狭く、何年経っても局所的な作業しかできないため、徐々にその人に仕事を振りにくくなっていくという事が起きてしまいました。

局所的な知識しか無いと設計の幅が狭まる

ビジネスロジック以外に考えられる事ができないと、設計の幅が致命的に狭くなる事があります。

ロジックで頑張ろうとせず、インフラ側で対応した方が効率が良くメンテナンスフリーにできたりするのに、知識が無いが故にそういう発想に至らない。ビルド・デプロイを工夫すればそんなロジック不要になるのに、知識が無いが故にそういう発想に至らない。発想が「ビジネスロジック」という狭い範囲に閉じてしまうが故、より効率の良い設計ができない場合があります。

そしてそういう状況に陥っている事を本人は気づく事ができません。そんな世界がある事を知らないからです。そしてそれを知るきっかけが生み出せないでいます。

もっと疑問に思って欲しい

プログラマはもっと日々行っている事を疑問に思ってもいいと思います。

「なんでそういうプロジェクト名にしたの?」「この設定すると何が起きるの?」「このファイルなんでここに置いてるの?」など、些細な事すら疑問に思って欲しいと私は思っています。ほぼ全ての事にはそうしている理由があるので、疑問に思う事ができれば、それを知るきっかけが生まれます。そこで疑問に思わなければ、一生それを知るきっかけが無いかもしれません。

「些細な事を質問するなんて恥ずかしい」と思っているなら大間違いです。「些細な事を知ろうともしない事が恥ずかしい」のです


これは、社内だけでなく、社外の話にも言えます。「クライアントはなんでいつもこんな非効率な事やろうとしてるんだろう?何か理由があるのか?」「クライアントがこんな事を言うのは、向こうに何か事情があるのだろうか?」など、相手側の行動に対して疑問に思うと、相手側が抱えている問題を解決するきっかけが生まれる事もあります。

雑感

私の個人的な意見ですが、知識が増えるきっかけは大抵「疑問に思う」事から始まっていると思っています。

例えばネットでIT系のニュースを日々見ていて、「お?この記事に書いてあるライブラリなんだ?凄そう」と疑問や興味を持ち、実際それを使ってみたり勉強してみたりする事に繋がります。そしてそこで出てくる専門用語や技術が解らず、初めて「他人に聞いてみよう」「気になるから自分で調べてみるか!」という行動を起こすのではないでしょうか。

そうやって少しづつ知識を増やすきっかけを増やさないと、「え?それを知らずにその作業してたの!?嘘でしょ・・・」と言われる人間になってしまう可能性があります。そうなってしまうとgithub等の外部から自分の知らない未知の知識を取り入れる事もできないので、自分の知識源が自分の周りの非常に狭い範囲に限定されるリスクもあります。そうなってしまうと、将来がちょっと怖くなりますね。。


今回は非常に極端な例を挙げましたが、疑問に思わない人、は本当に増えてきていると感じています。最近はIDE・フレームワークが何でもかんでも自動でやってくれるので、余計に考えなくてよくなってきているのですよね。高機能になればなるほど利用者は考える事をやめてしまいがちなので、思考停止しないよう常に疑問に思う事を意識し、裏で何が行われているのか、今やっている事は正しいのか、を意識していきたいですね。


ちなみに冒頭の記事の「気軽に聞けない事」に関して私の意見を言うと、「ガンガン質問していいけど、同じ質問するのは最大3回くらいにして欲しい」です。同じ質問を何回もされると、流石に「覚える気ねーな・・・」と思ってしまうので、嫌な感じがしますね。質問をする事自体は凄く良いことだと思います。然るべき人に聞けばググるより速く回答が貰える可能性があるし、回答する側にとっても、他人に説明するという練習をするきっかけにもなります。

最近ではもう本当に疑問に思って質問してくれる人が減ってきているので、バシバシ質問して欲しいですね。

docker上のhubotでslackのincoming webhookからhubotを反応させる

$
0
0

ちょっと特殊な書き方が必要なのですよね
f:id:treeapps:20160216013218p:plain
本題の前に、ちょっとしたDockerfileとdockerイメージを公開してみました。

dockerイメージ

生のDockerfile

github.com

このdockerイメージは何ができるの?

できる事は、「sudo可能」「yum可能」「viが初期インストール済み」「npm可能」というだけです。ベースイメージがCentOS6系なので、当然alpine linux等と比較すると巨大なファイルサイズ(190MByte)です。しかしviがあってsudo可能でyumが利用可能でnpmで追加モジュールインストール可能な状態なので、docker上での開発が非常にやりやすい筈です。というか何でもできます。

hubotのスクリプト開発用と割り切っているので、プロダクション環境では使い物にならないので、悪しからず。

なんでこれ作ったの?

私は以前からhubot+slackのスクリプトの開発が非常にやりにくいと思っていました。

  • hubotとslackを連携しないとスクリプトの動作確認ができない。
  • docker hubによくあるイメージはviも入っていない、sudoもできない、yumもできないものが多く、productionではなく開発に特化したイメージが無い。
  • 会社でやろうとするとhubotを頻繁に再起動するので、ChatOpsに影響を与えてしまう。
  • 会社のslackアカウントは管理権限を持っていないので、テストでhubot等が投稿したメッセージを削除する事ができない。
  • できれば使い慣れたCentOS上で開発したい。

既存のdockerイメージは環境をいじらない前提の最小構成になっている場合がほとんどで、docker上でストレス無しに開発できそうなイメージは見つかりませんでした。まあそもそもdockerがイミュータブルな環境前提なのでそれは正しいのですが、開発にフォーカスした場合、そのイミュータブルさが逆に仇となって開発しにくくストレスが溜まるものでした。それが嫌だったので、もういっそイミュータブル要素を排除して、CentOSベースでviもsudoもyumもnpmもできるもの用意しちゃいました。

それならVagrantでいいじゃんという意見もあると思いますが、やはりdockerのコンテナの起動速度は魅力的なので、docker上で開発したいと考えていました。

hubotをwebhookに反応させる

さて、では本題です

slackアカウント作成等

github.com

ここに色々書きましたが、個人用のslackアカウントを作成し、App Directoryでhubotとincoming webhookを追加し、HUBOT_SLACK_TOKENをコピーし、「/invite hubot」でチャットルームにhubotを招待しておいて下さい。

dockerコンテナの起動

# イメージをpullする
docker pull treetips/dockerfile-centos-hubot-slack:latest

# コンテナを起動する
docker run -dit--name hubot-slack -eHUBOT_SLACK_TOKEN=xoxb-XXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXX treetips/centos-hubot-slack:latest

# コンテナにsshする
docker exec-it hubot-slack bash

# hubotをslack連携して起動(フォアグランド起動)
./bin/hubot --adapter slack

これでチャットルームのhubotはオンライン状態になります。
f:id:treeapps:20160215233620p:plain

incoming webhookに反応するhubotスクリプト

incoming webhookからhubotを呼び、mentionしたり色々したい事ってありますよね。あるよね・・・?

しかしhubot-slackのスクリプトは、robots.hear等ではincoming webhookの呼びかけに反応してくれません。

class SlackBotListener extends Listener
# SlackBotListeners receive SlackBotMessages from the Slack adapter
# and decide if they want to act on it. SlackBotListener will only
# match instances of SlackBotMessage.
#
# robot - A Robot instance.
# regex - A Regex that determines if this listener should trigger the
# callback.
# callback - A Function that is triggered if the incoming message matches.
#
# To use this listener in your own script, you can say
#
# robot.listeners.push new SlackBotListener(robot, regex, callback)
constructor: (@robot, @regex, @callback) ->
@matcher = (message) =>
if message instanceof SlackBotMessage
message.match @regex

https://github.com/slackhq/hubot-slack/blob/master/src/listener.coffee

こんな事がソースに書いてあって、何やら
「To use this listener in your own script, you can say」
「robot.listeners.push new SlackBotListener(robot, regex, callback)」
等とコメントに書いてあります。単純にrobots.hear等では駄目との事です。

$HUBOT_HOME/scripts/test.coffee を作成

では早速スクリプトを書いてみます。以下はincoming webhookでhubotに対して「おい」と呼びかけると、「なんだよ」と反応させるものです。

HubotSlack= require 'hubot-slack'
module.exports =(robot)->
  robot.listeners.push newHubotSlack.SlackBotListener robot,/^おい/i,(res)->
    res.send "なんだよ"

$HUBOT_HOME/scripts/test.coffee に配置すると起動時に自動的に読み込んでくれるので、一旦フォアグランドで起動していたhubotをCtrl + cで終了し、test.coffeeを作成して下さい。続いて「./bin/hubot --adapter slack」で再びhubotをフォアグラウンド起動します。これで反応する準備はできました。

incoming webhookで呼びかけてみる

以下のような感じでPOSTで呼びます。呼び出すURLは、App DirectoryのIncoming WebHooksの設定の「Webhook URL」からコピって下さい。

curl -X POST --form'payload={"channel": "#bot-room", "username": "ぼっと", "mrkdwn": true, "text": "おい"}' https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXX

では呼んでみます。

f:id:treeapps:20160215235836g:plain

このGIFアニメだとちょっと解りにくいと思いますが、ターミナルで前述のcurlコマンドを叩いて、incoming webhookが「おい」と発言し、hubotがすぐに「なんだよ」と反応しています。

これができるという事は何か色々使えそうな気がしますね!

雑感

hubot-slackのスクリプト開発、正直辛いですね。とりあえずカッとなってdockerイメージ作ったので大分開発しやすくなりましたが、まだまだ開発しにくい感が否めません。

というか、hubotって「あの呼びかけにあれが反応しない」等が非常に解りにくいんですよね。hubotがhubot宛(自分宛)にmentionしても反応しなかったり(多分無限ループになるから)、その辺がいちいち面倒臭くて、あれこれ試してるうちにチャットルームがテスト投稿でビッシリ埋まってしまい、「てめー何チャットルーム荒らしてんだコラ」と怒られたりします。

そうならないためにも、個人でslackアカウント用意してしまって、dockerfile-centos-hubot-slackのイメージであれこれ試行錯誤すると、チャットルーム荒らしの汚名を着せられる事もなくなります。きっと・・・

ImageMagickの最新版をソースからコンパイルしてインストールする

$
0
0

yumだと古いバージョンしかインストールできないので、ソースコードをコンパイルして最新版をインストールしますよ。
f:id:treeapps:20160219001058p:plain

今回はImageMagicについて、ソースコードから最新版をコンパイルしてインストールしてみようと思います。

ImageMagic

ソースをDL

http://www.imagemagick.org/download/

ここからダウンロードできます。しかしここでちょっとした問題があります。

この中で常に最新版のアーカイブは例えば「ImageMagick.tar.gz」です。例えばこれを解凍すると、以下のようになります。

[vagrant@node1 ~]$ ll
total 12956
-rw-rw-r-- 1 vagrant vagrant 13266141 Feb 1222:23 ImageMagick.tar.gz
[vagrant@node1 ~]$ tar zxf ./ImageMagick.tar.gz
[vagrant@node1 ~]$ ll
total 12960
drwxrwxr-x 15 vagrant vagrant     4096 Feb 1222:23 ImageMagick-6.9.3-4
-rw-rw-r--  1 vagrant vagrant 13266141 Feb 1222:23 ImageMagick.tar.gz

アーカイブ名が「ImageMagick.tar.gz」なのに解凍すると「ImageMagick-6.9.3-4」等とフォルダ名にバージョンが入ってしまうのです。これだと自動化しようとすると、バージョン番号が事前に解っていないといけないので、メンテが面倒になってしまいます。それに加えて、ImageMagickのソースコードは古いものはどんどん削除されて404になっていくのです。つまり、プロビジョニングする際にバージョン番号を指定してダウンロードしようとしても、時間が経つとそのバージョンはサイトから消えている、なんて事が起きます。

それを解決するため、tarで頑張って解凍後のフォルダ名を変更します。以下のようにすると「ImageMagick.tar.gz」というアーカイブを「ImageMagick-latest」というフォルダに解凍する事ができます。これでImageMagickのバージョンを知らなくても、常に最新のソースコードを扱うことができるようになります。

mkdir-p ImageMagick-latest && tar zxf ImageMagick.tar.gz -C ImageMagick-latest --strip-components1

後はconfigure、make、make installです。

まとめ

cd /usr/local/src
# DL
sudo wget http://www.imagemagick.org/download/ImageMagick.tar.gz
# フォルダ名を固定化して解凍
sudo mkdir-p ImageMagick-latest && tar xzvf ImageMagick.tar.gz -C ImageMagick-latest --strip-components1# コンパイル・インストールcd ImageMagick
sudo ./configure
sudo make
sudo make install

コンパイル時にエラー

makeした際に「autoconfのバージョンxxx以上をインストールしてからコンパイルし直せ」と言われる場合があります。その場合はImageMagickの時と同様に、以下のようにソースをコンパイルしてインストールします。

cd /usr/local/src
sudo wget http://ftp.gnu.org/gnu/autoconf/autoconf-latest.tar.gz
sudo mkdir-p autoconf-latest && tar xzvf autoconf-latest.tar.gz -C autoconf-latest --strip-components1cd autoconf-latest
sudo ./configure
sudo make
sudo make install

例によってautoconfもアーカイブ名が「autoconf-latest.tar.gz」なのに、解凍するとフォルダ名にバージョンが混入するので、フォルダ名を固定化しています。

バージョンの確認

2016/02/18時点の最新バージョンをインストールすると、以下のバージョンになりました。

[vagrant@node1 src]$ autoconf --version
autoconf (GNU Autoconf) 2.69
Copyright (C)2012 Free Software Foundation, Inc.
License GPLv3+/Autoconf: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>, <http://gnu.org/licenses/exceptions.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by David J. MacKenzie and Akim Demaille.
[vagrant@node1 src]$ convert --version
Version: ImageMagick 6.9.3-4 Q16 x86_64 2016-02-18 http://www.imagemagick.org
Copyright: Copyright (C)1999-2016 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Features: Cipher DPC OpenMP
Delegates (built-in):

ImageMagickのPlaybook

ansibleのplaybookです。ansible v2.0.0.2 で実装していて、becomeやblock等の新機能を使っているため、旧バージョンでは動きません。

github.com

雑感

今回はvagrantを使っていますが、最近はローカルでdockerを使う機会が増えました。

ImageMagickはyumを使えばもっと簡単にインストールできますが、やはり最新版と比較するとインストールできるバージョンが古いのですよね。アップデートを考えるとyumの方が遥かに楽ですが、dockerの場合はパッケージの更新の考慮が不要になる(イメージ自体を更新)ので、最新バージョンのソースコードからコンパイルしてインストール機会が増えていくんじゃないかと思っています。

本当はrpmがあると楽てきて嬉しいのですが、ImageMagickのサイトで配布されているrpmを「sudo rpm -ivh xxx.rpm」とやっても、依存が付属していないのでエラーになるのですよね・・・あれはちょっと使い物にならないので、ソースからインストールした方が速いです。

redmineを使ったチケット管理の失敗のさせ方

$
0
0

Redmineを使ったチケット管理をしている方必見!チケット管理の失敗のさせ方をこっそり教えちゃいます!
f:id:treeapps:20160226022423p:plain

チケット管理のアンチパターンとベストプラクティス - Javaプログラマーのはしくれダイアリー

こちらの記事を見て、「こうすると失敗するよ」という実例を挙げてみようと思います。私はほぼredmineしか触っていないので、今回はredmineでのお話となります。

※ 冒頭の記事と被る内容も多々有りますが、気にせず列挙していきます。
※ 主にSI業界に関するチケット管理のお話です。
※ 大規模プロジェクトは想定していません。多くても20人以内程度を想定した記事です。

これであなたも無事、失敗できる!

進捗率をユーザの裁量で更新させる

進捗率は人によって設定するさじ加減がバラッバラなので、全くあてになりません。例えば「俺は着手開始の印として進捗率を10%にしてから作業する!」という人もいれば「俺は50%単位でしか更新しない!」「いやいや俺は5%刻みでいくぜ!?」という具合にバラバラです。これは非常に悪しき風習です。

今のredmineには、「チケットのステータスで進捗率を更新する」という機能があるので、是非これを使うべきです。
小技(0.9): チケットのステータスで進捗率を更新する | Redmine.JP Blog

この機能を使う事で、ステータスを選択すると自動的に進捗率が設定でき、ユーザが任意に進捗率を設定できなくなるため、オレオレ進捗管理を避ける事ができます。

対象バージョン「なるはや」

いつまでにリリースしたいか決まっていないが、なるべく速い方がいい、そんな思いから作られた対象バージョン「なるはや」。

お仕事なのだから「速いほうがいいのは当たり前」だし、曖昧な表現を用いてはいけません。人によっては「なるはや=1週間以内くらい?」と考えていたり、「なるはや=今月中」という人もいます。

本当にいつリリースしてもいいのなら「実装待ち」みたいにして、いつのリリースに対応してもいいよ〜、くらいに割り切った方がよいです。

チケットの内容が途中から変わる

仕様に関するチケットなのに、チケット内で仕様が決まると「この仕様で実装お願いします!」等という激励コメントと共に、そのチケットを使いまわしてそのまま開発側に投げてしまうパターン。

途中から実装チケットに早変わりしてしまい、仕様チケットの筈がデザイン崩れ等のデザインに関する内容まで飛び交う事も有り。

横着してチケットの使い回しをすると、重要な情報がどんどん流れてしまうし、「本当に知りたい情報」は一体どのチケットを見たらいいのか解らなくなります。ちゃんと役割ごとにチケット分け、関連のあるチケットを関連付けしておいた方がよいです。

増殖するプロジェクト固有のトラッカー

redmineはサブプロジェクト形式で複数のプロジェクトを1つのredmineに相乗りさせる事ができます。相乗りしているredmineでは必ずと言ってもいいほど、そのプロジェクトでしか使われないトラッカーが作られます。例えば「クライアント確認(XXXプロジェクト)」といった具合に作られます。

一度でもプロジェクト固有のトラッカーを使ったら最後、「あ、そのプロジェクトでしかトラッカー作っていいんや!!おっしゃ!!」→ 設定画面のトラッカー一覧にプロジェクト固有トラッカーがズラ~・・・・

「本日は打ち合わせありがとうございました」

社外redmineでありがちだと思いますが、チケットに「本日は打ち合わせありがとうございました」等と、チケットの内容と一切関係ないコメントを書いてしまう。気持ちは解るがやめましょう。挙句の果てにはそのチケット内で次のMTGの日程決めのやりとりをベンダー数社でやり始めたりして。

おいおいキミたち。このチケットは仕様確認のチケットでしょうが。何してるの。

対象バージョンの書式がバラバラ

SIの場合はリリースが毎週一回ある等、定期的にスケジューリングされている場合がありますね。その場合、対象バージョンに日付を設定する場合があります。例えば「2016/02/26」のように。しかしですね、日付の書式がバラバラなんですよ。「2016/1/1」と設定する人もいれば、「2016/01/01」とする人もいます。書式は合わせましょうよ。

redmineのソートは文字列ソートである場合が結構あるので、「2016/1/1」と「2016/01/01」ではソート結果がめちゃくちゃになります。「2016-1-1」と「2016/1/1」の違いも同様です。書式は統一した方が後々トラブルが起きにくくなります。

チケットに添付されまくる仕様書

エクセルに走り書いた仕様書や画面仕様書等をチケットに添付するの、やめません?

その資料に更新が必要になった時にいちいちゴミ箱ボタンで既に添付されたファイルを全部手動で削除していって新たに更新版を添付する、それを10回繰り返すの、何とも思わないのかな?

gitやsvnで管理して、コミットされてる場所書くようにしませんかね。

チケットの題名が「仕様調査依頼」

そのチケットの題名見て内容が想像できる人、いないよ?

redmineのチケットは、gitのコミットメッセージ考える時くらい真剣に、要点を抑えて短く書きましょうよ。

社内redmineと社外redmineのテーマが同じ

ぼーっとしてる時、うっかり社内redmineと間違えて社外redmineのチケットを更新しちゃいますよ。注意しててもいつか誰かがやっちゃいます。しかもそんな時に限って「鈴木の糞野郎(クライアントの担当者の名前)がふざけた事ぬかしてたあの件ですが、ちょっとアイツの仕様じゃ全く駄目なんで、一旦整理した方がいいよwwww」みたいな事を書いてしまったりして、それがクライアントに見られたり。

社内redmineと社外redmineは、ぱっと見で違いが解るように、色がかなり異なるテーマを適用した方が事故が減ります。

既に退場した人のユーザが沢山残っている

チケットの担当者のコンボボックスをクリックすると、退職者や今プロジェクトに参画していない人の名前がズラ~っと並んでしまい、目的の人を選択するのに時間がかかってしまう。これはマジで時間が勿体無いので、極力今関わっている人のみになるようメンテした方がいいです。

例えば「鈴木一郎」さんと「鈴木二郎」さんがいた場合、二郎さんの方は既に退職しているのに、一郎さんと二郎さんの区別がまだ付いていない新規参画者が、退職している二郎さんにチケットを回してしまうなんて事故が起きる事もあります。

ステータス「保留」

気持ちは凄く解る。途中まで進んでたのに、別の優先タスクが発生してしまって一時中断したチケット。保留したいですね。しかしその保留チケットは放置されずっと残り続けるゾンビチケットになる可能性を秘めています。

redmineはゾンビチケットが残りやすいので、思い切って一旦そのチケットを終了してしまうのも手です。保留を残し続けると、「このゾンビチケット群、どっかのタイミングで棚卸しないと、そろそろやばいよね〜」←そんなタイミングは来ない場合が多い

wikiにある「チケットの更新フロー」

超細かい更新フロー、超細かい進捗率の更新基準、wikiにまとめないといけない程のフローになってしまった時点で、その運用フローは破綻しています。そのまま運用しても失敗するか、「煩雑過ぎるから今日から簡略化したフローに変えます!あ、でも設定変えてしまうと他のプロジェクトに影響しておくので設定はそのままにします!◯◯のステータスは無視して下さい!」みたいな事が始まります。

フローなんて見なくても解るくらいシンプルで、ワークフローで次の行動をできる限り制限して、プロジェクトに新規参入した人でもすぐ運用できるようにすべきです。

何故か私はこのチケットが終了できないんですよね

ワークフローを適当に設定するのをやめなさい

乱立するredmine

「あのredmine、相乗りしてるプロジェクトが無茶苦茶な数のプロジェクト固有のトラッカーとステータス作りやがるので、別途新規redmine立てました!」←別のプロジェクトが同じ事を繰り返す ← 無限ループって怖い

他にも「あのredmineバージョン低いんだよね。でもバージョンアップさせる人でもないし、別途redmine立てるか」等のケースもあります。こうして徐々に乱立していくとredmineに派閥みたいなものができあがり、「あのredmineに相乗りしてる奴らはできるグループだ。急いであっちに引っ越すぞ!」みたいな頭の悪い事をしだす人がいたりします。

雑感

いかがでしたでしょうか。多分こんなような事は皆やっちゃってるんじゃないでしょうか。

色々なプロジェクトを見てきて、チケット管理が成功しているのは、やはり選択肢が非常にシンプルで少ない、ワークフローを上手く使って行動を制限できているプロジェクトの成功率が高いように思えました。

特に、トラッカーはマジでシンプルで選択肢が少ない方が上手く行きやすいように思えました。要件定義・設計・実装・単体テスト・結合テスト・・・・みたいに細かくしたい気持ちはよく解るのですが、それ、間違いなく失敗します。皆そんな綺麗に運用できません。設計チケットで実装のコメント合戦を始めるし、単体テストチケットで仕様調整の話を始めます。細かすぎです。目茶苦茶少ないくらいが丁度いいです。

進捗率に関しては、私は全く要らない機能だと思っています。例えば調査系のチケットの場合、調査を完了したら進捗率はいくつなの?等と困ってしまったり、進捗率が0%のまま最後まで進んでチケットを終了するときに仕方なく100%にするようなチケットも結構あります。「ここで進捗率を更新しないといけない」みたいなルールを設けても、細かすぎて覚えきれないし、誰も守りません。他にも「実装着手したら進捗率を5%に更新してから実装して下さい」等もアホ臭いです。そのスケジュール管理、おかしいと思いませんかね?もっといい方法探したほうがいいのでは?と思ってます。今だったらgitのコミット頻度とか見れば、本当に実装が進んでそうなのか等が解ったりしますよね。作業者の申告制みたいな管理やめて、機械的に管理できる仕組みを模索した方がいいと思います。


redmineのチケット管理は徐々に色々増殖して破綻していく傾向があるので、「どれだけできる事を減らせるか」「どれだけ行動を制限できるか」の2点にかかっているかと思います。プログラマはどうしても細かく分類して、あらゆるものを無駄に管理したがり、最後は収集がつかなくなって放置されるものが増える傾向があるので、どれだけシンプルにできるかが成功するための鍵になる、と私は考えています。

ついにプリンタを捨ててやった

$
0
0

やったぜ
f:id:treeapps:20160306232213p:plain

つい先日、ちょっと家にある不要物を売却しようと思い、手続きとして買い取り証明書みたいなのを印刷する必要がありました。一応家には CanonのMP630という悪い意味ででっかいプリンタがあるのですが、起動するとエラー画面になってしまい、以降の操作が全くできない状況に陥りました。

エラー画面には「エラーが発生」として書かれておらず、何が原因でエラーなのかを書いてくれない心折設計。という事でオフィシャルサイトのヘルプを見ることにしました。

cweb.canon.jp



こんなにエラーのパターンあるんか


なめとんのか!!おおおん!?

これ全部試せと言ってるのか・・・何という苦行。

そもそも今の時代に個人でプリンタって必要か?

今回の私のケースでは、ネットで物を買い取って貰いました。ネットでの買い取りは紙媒体が必要になる場合があります(古物営業法の関係?)。私の場合はここ数年で紙媒体への印刷が必要になった機会はネットでの買い取りサービス利用時だけです。印刷する機会は完全に無くなる訳ではありませんが、どうしても紙媒体が必要になるケースはありますね。

紙媒体への印刷が必要なのは解ります。しかし、個人でプリンタを所有する意味はあるのか?という点を疑問に思いました。プリンタのインクは高価だし、印刷用紙も常備しないといけないし、プリンタ自体の設置スペースもかなり必要になります。これを自宅に常備する必要はあるでしょうか?プリンタに関する事全てをプロに任せる事はできないでしょうか?

それ、ネットプリントでできるよ

www.printing.ne.jp

PCやスマホから専用アプリ等で印刷の予約をすると、予約番号が貰えます。コンビニのプリンタに出向き、予約番号を入力すると、カラー・モノクロ等で印刷する事ができるサービスです。以下のような感じです。

セブン-イレブン ネットプリント操作方法

ネットプリントにはファイルのアップロード速度やアップロード枚数等の制限はあるものの、自宅に年に1回使うか使わないかのでっかいプリンタを常備させるより、これで済ませた方がいいのではないか?と思います。

雑感

昔からごくごく稀に使うから、中々プリンタ処分できないんだよな〜と思ってました。長期間プリンタを稼働しない事も普通にあり、すぐインクが固まってしまいます。固まったインクノズルをメンテする機能がプリンタに内蔵されていたりしますが、これがまた何故かごっそりインクが減らされるんですよね・・・理不尽だと思いつつ、プリンタを捨てる事ができませんでした。

自宅のプリンタはもうとにかく使う機会が少ないから、まず間違い無く埃を被っているし、電源+USBケーブルどれだっけ!?と探しまわる事も多し。しかも今回の私の例のように、いざ使おうとしたら起動時にエラーが起きて何もできなくなるパターンもあります。

プリンタというのはそもそも定期的にメンテナンスが必要なデリケートな機械であり、快適に使い続けるには素人には非常に難しいものです。会社で「ただいま◯階のプリンタの紙詰まりの対応を業者に依頼中です」「ただいま◯階のトナーを・・(略」等、定期的に不具合が出ては業者を呼んで、という光景を目にしますね。プリンタに纏わるトラブルは本当に多いものです。それを個人で快適に使い続けるために定期的にメンテナンスするのは、ちょっと難しいと私は思っています。

このようにプリンタトラブルに嵌って時間を消耗し、精神をすり減らし、埃をかぶらないように注意したり、設置スペースに泣いたりする。正直もうこうやって消耗する時代じゃなくなってきましたね。著名なコンビニは確実にネットプリントサービスをやっているので、自宅に機材を置かず、外部のサービスに頼るべきですね。ネットプリントであれば、クラウドサービスのように使った分しか請求されないので、コストを抑える事もできます。時は金なりといいますし、自宅にデカイプリンタを置いて時間を消費するのは非常に勿体無い。その時間をもっと別の事に活用すべきです。

今回のように、いざ使おうと思った時にエラーが起きたり、インクが切れたりしてすぐ使えない場合も普通にあるので、自宅に常備しているからといって、すぐに使えるかどうかは日々のメンテにかかっているという状態になりがちなので、皆さんも自宅からプリンタを排除して、インク残量を気にしたり設置スペースを気にしたり駆動音を気にしたりするのを止めて、ネットプリント等の外部サービスに移行し、楽になりましょう。


郵便受けにスパムフィルターを適用したい

$
0
0

なんぞこれ・・・
f:id:treeapps:20160307000552p:plain
最近お仕事の事で頭が一杯で、郵便受けを2週間ほど放置していました。

するとどうでしょう。↓これは2週間放置した郵便受けの中身を全部ゴミ箱に投げ捨てたものです。

f:id:treeapps:20160306233735p:plain


こんなに沢山紙があるのに99%がゴミじゃねえか・・

もうとにかくチラシ、新聞、チラシ、新聞・・・・本当に必要なものは、ほぼありません。

このゴミどもは本当に大量に放り込まれます。こちらの意思とは無関係に放り込まれるのがマジでイラッとしますね・・・

lmedia.jp

どうやら通常はチラシの投函は違法ではないとの事なので、簡単にこれを阻止する事はできないのですよね・・・

ネットではスパム行為をすれば恐ろしいスピードで強烈な制裁をくらうのですが、現実世界では完全に無法地帯です。新聞社や不動産会社がやりたい放題です。ネットでスパムするのマジ大変だからリアルでスパム(チラシ投函)した方が遥かに効率がよくね?と考え、投函しているのでしょうね。チラシの内容的にも違法性が無いものなので、中々違法性を訴えにくく、訴えるにしても労力が掛かり過ぎる。そんな現実世界ならではの弱みに漬け込んで、スパマーのやりたい放題なリアル。

正直チラシの99%はまともに見られる事もなくゴミ箱いきでしょうし、完全に紙資源とインクの無駄だと思うのです。中には「おめでとうございます!豪華景品に当選しました!」のような、ワンクリック詐欺かよと思いたくなるような詐欺広告もあり、マジで無法地帯です。完全にスパム天国です。

雑感

このスパムチラシの量は、「ネットだとgoogle先生の速攻制裁されるから、抜け穴だらけでガバガバの現実世界でスパムしたるで〜」等と考えているようにしか思えませんね。

もしここにgmailのフィルターみたいなものがかけられたら、1ヶ月に一体何通届くのでしょうね。。。私の場合は恐らく佐川急便とクロネコヤマトからの不在票しか届かないでしょう。電気もガスも水道も、検針票はネットに移行したので届きませんし、スマホもMVNOなのでそもそもWEB明細オンリーです。届くものいったら不在票かクレカ会社からのDMくらいです。

こうして徐々に現実世界にネット世界のスパムの波が押し寄せてくるのは正直嫌ですね。ネットと違って機械的に排除する事も容易ではありません。もういっその事郵便受けの口を塞いでやろうか

プログラマの私が今まで言われた困ったセリフ集

$
0
0

色々困った事を言われてきた私です。
f:id:treeapps:20160310004932p:plain

ブログタイトル通り、プログラマーな私ですが、今まで言われた「え・・・」と絶句してしまうようなセリフを紹介したいと思います。

※ 登場人物は私とAさんです。Aさんは一人ではなく、複数人の総称とします。

どうでもいいんですよ!

DBの検索部分を全文検索エンジンでの検索にリプレースするタスクで言われた一言。

Aさん「全文検索エンジンの事なんて知りませんよ!treeさん(私)がやってくださいよ!私はメソッドの中身なんてどうでもいいんです!SQLと同じ結果が返ればどうでもいいんですよ!速く作って下さいよ!」

いやね、それを実装するのがあなたの役目なんですが。

私はスキルが低いんです!

突然言われたこの一言。

Aさん「私はスキルが低いんですよ!?そんな事できませんよ!!」

テスターからやり直して出直してくれ。

捨てます

バッチの実装を任せた時、データの状態について聞いた時の返答。

私「この時こういうゴミデータができてしまうのですが、これらデータはどう処理されますか?」
Aさん「大事なデータじゃないし、捨てますけど。」

待て。大事かどうかを決めるのはあなたじゃないし、捨てるとか、何考えているんだ・・・

鉄壁の前提人間

URLパラメータをキーにDBを検索する画面で、パラメータ値が未設定の時に500エラーが返るバグについて実装内容を確認した時の一言。

Aさん「ここは絶対にURLパラメータが設定される想定です」

待て待て、URLパラメータ(QueryString)が未設定とか、普通にあるぞ。そんな前提条件が通るWEBアプリなんてまず無いでしょうよ。

私「ここにバリデーション入れてくれませんか?」
Aさん「いや、ここは絶対に値が来る前提です。話が違うからやりません。やるならお金下さい」

・・・・

ちなみにその前提は彼が勝手に作った前提で、全く合意が取れていないという・・

ループの鬼

都道府県毎に処理するSQLを実装して貰うというお仕事を依頼した時のやりとり。

私「ここ、47都道府県を処理する部分ですが、47回SQLを実行するのは負荷かかるので、SQL1回で済ませられませんか?」
Aさん「え?47回実行するのマズイんですか?」

単純にスキルが低過ぎるが故の返答でした。大した処理内容でもないのに、1つの処理で47回ループして47回SQLを投げてしまうという異常性に気づけるスキルを身につけて頑張って欲しい。

そこまでする必要無い

私「◯◯を格納するクラスなのに、◯◯に関連の無いフィールドが混じっているんですが、何か意図はありますか?」
Aさん「ありません。動けばいいじゃないですか。」
私「良い設計をしたいと考えているので、そのフィールドを別のクラスに移動してくれませんか?」
Aさん「そんな事どうでもいいじゃないですか。そこまでする意味あるんですか?」
私「そういう事の積み重ねで今の酷いコードができるんです。いつもあなたが苦しみながら修正しているコードはそういう設計思想の元、出来上がったのです。」
Aさん「そうなんですか。でも私はそこまでする必要は無いと判断したので修正しません。」

・・・1ヶ月後・・・

Aさん「うぅ〜・・・・うぅ〜・・・(今にも死にそうで苦しそうな呻き声)」

だから言っただろ・・・あの時の自信は一体なんだったんだ。

私が悪いんで!!

私「この装置、Aさんが既に利用実績があるから採用したと聞いたのですが、使い方ざっくり教えて貰えますか?」
Aさん「ああ、あれですね。あれは私がそれを使ったPJを聞いた事があるってだけなんですよね」
私「え?マジですか・・・保守も契約してないし、もう納品まで時間が無いですね・・・」
Aさん「解りました!私が悪いんで!始末書でも何でも書きますよ!」

oh・・・・開き直るとか、人間のする事じゃないな・・・

私には権限が無い

私「◯◯の作業をお願いしたいのですが、できますか?」
Aさん「私には権限が無いので、できません。」
私「では私がサポートするという条件で権限を与えますので、お願いします。」
Aさん「え・・・すいません実はやった事が無いのでできません」

何故か結構いるのですよね、「未経験である事」を「権限が無いからできない」という事にすり替えようとする方が。素直に「やった事が無いですがお願いします」みたいに言って貰えればいくらでもサポートするのになあ。

書籍に書いてあるんで

私「なんだこの処理。修正するか」

・・・・Aさんがその修正を元に戻してコミット・・・

私「は・・・?なんかrevertされてるな。」
私「Aさん、ここ、解放する処理入れないとエラーになるので、入れてくれませんか?」
Aさん「私のコードは書籍通りのコードです。これで正しいです。」

????書籍に書いてあるサンプルと、今やってる処理が同じ?そんな訳ないだろう・・・どういう理屈なんだ。

雑感

思い返してみると、色んな事言われたなあ・・・どれもこれも私を驚かせる発言ばかりでした。本当はもっと沢山言われてきてる筈ですが、今回はすぐに思い出せるかなりショッキングな発言をピックアップした形となりました。(単に思い出せないだけですが・・)

ビックリするようなセリフを口走ってしまう人に共通していた事は、「今が良ければそれでいい」「無駄に頑固」「打たれ弱く衝動的な行動をしがち」「どうでもいいと言いがち」という感じでした。彼らの大分部は、ビックリ行動のしっぺ返しを喰らうという自業自得な状況でした。結果として自分で撒いた種に自分で苦しみ、早々に去って行く人が多いように思えました。私の「そのやり方だとこういうデメリットがあり、後々こういう事になるかもしれませんよ」という言葉も届かない場合がほとんどで、自分の考えを全く変えようとしませんでした。悪い意味での頑固一徹というやつです。

逆に、無駄なプライドがなく、素直で、自分の行動を見つめ直せるタイプの人はあっという間に伸びていき、すぐに巣立っていきました。その調子でどんどん吸収して巣立って欲しいですね。

やはり素直さを失ってしまうと、引くに引けない状況になって絶対どこかで詰んでしまうので、頑固にならないように気をつけていきたいですね。

歩行者と自転車と自動車の終わりなき戦い

$
0
0

このネタは、理想主義者と現実主義者が果てなき戦いを繰り広げます。
f:id:treeapps:20160312142820p:plain
今回はほぼ確実に炎上する、歩行者と自転車についてです。

匿名ダイアリー、通称増田にこんな記事がありました。
anond.hatelabo.jp

増田概要

一言でまとめると、「歩道を歩く歩行者も、自衛はした方がいいよ」というお話です。

そういうお話ではあるのですが、書き方が悪いせいで、「歩行者は自転車乗りの俺達に合わせリスク回避策取れや」といった上から目線な印象を強く感じてしまうため、記事の本質を無視して読み手が「自転車=無条件に悪。歩行者=無条件に神」という論理を展開し、炎上に至っているわけです。

書き方が悪いせいで本題から逸れてしまっている、残念な増田なのです。

増田の本題

この記事の本題を以下だと仮定します。

人間はロボットでは無いので、どれだけ法令遵守をこころがけても自転車の法律違反は起きてしまう。どんなに注意しても、ヒューマンエラーを100%防ぐ事はできません。法律的に自転車が100%悪い事は解っている、だが、だからといって歩行者が何もせず「自転車が悪い!」と叫んで無策な状態になるよりは、何かしらの安全策を講じた方が、理不尽な危険から身を守る可能性はあるよ、と。

こんな感じでしょうか。要は「理想は道路交通法を100%順守で100%事故が起きない事だが、現実的に考えると100%事故を防ぐ手段は無いから対策しようぜ!」という事ですね。

未だ変わらない自転車乗りの意識

道路交通法が改正され、自転車は「軽車両」扱いとなりました。それに伴い、今までの自由過ぎた状態から一気に法的なルールが追加されました。
自転車の交通ルール :警視庁
自転車はルールを守って安全運転~自転車は「車のなかま」~|警察庁
http://www.mlit.go.jp/road/road/bicycle/pdf/guideline.pdf

私は東京都内に住んでおり、自転車通勤をしています。原則車道の端っこを走っているのですが、自転車レーンも無く車の交通量が非常に多いので、どうしても自転車通行可の標識がある歩道を通らざるをえない場合や、自転車通行可の標識が無いが安全のためやむを得ず歩道を通過する事もあります。

実際に自転車に乗っている私から見て、道路交通法を守らずに危険運転する人は多いなあと感じています。

保育園に向かうママさん

自転車の後部座席に子供を乗せて保育園に向かうであろうママさんは、危険な運転をしがちに見えました。恐らく急いでいるせいなのだと思いますが、歩道を結構なスピードで走り、対向自転車・対向歩行者が来ても減速をほとんどせず、強引に避けようとする運転が見受けられます。勿論全員がそういうわけではありません。

後部座席に子供を乗せている人と乗せてない人では、出すスピードは後者の方が遅い場合がほとんどなので、やはり忙しい朝に少しでも早く保育園に送りたいが故、危険運転になりがちなのだと思います。

複数台の作業服を来たリーマン

会社の作業服を着た数人のリーマンが、お仕事で急いでいるためか、結構なスピードで複数の車列を作って歩道を走っている姿を朝方によく目にします。特に複数車両の場合、前を走る車両から遅れたくないという強迫観念のようなものが働くせいか、後続車両が危険運転をする事が多いように見えました。

鈍足でフラフラと蛇行する自転車

自転車は2輪なので、速度が遅すぎると蛇行してしまいます。特に自転車通行可能な歩道で鈍足で蛇行している自転車を見ることがありますが、これは危険ですね。いっその事一旦足をついて止まってしまうか、車道を走るか、等をした方がいいかもしれません。特に、超鈍足で蛇行しながら歩行者と同じスピードで走っていると、後続の自転車も同じスピードで蛇行する事を強いられるので、どうしても追い越したくなってしまい、そこで事故が起きる事が多くなります。蛇行する程のスピードでしか自転車に乗らないのであれば、いっそ乗らない方が健康にもいいでしょう。

傘さし運転

未だに多い自転車での傘さし運転。以前私も傘さし運転をしていましたが、道路交通法が改正されたのを機に、レインコートを着るようにしました。

傘さし運転者がレインコートを着ない一番の理由は、レインコートのデザインがくっそダサいからですよね。レインコート着ると髪型が乱れる、という理由もあるかもしれません。

最善なのは「雨の日は自転車に乗らない」なのでしょうが、距離がそこそこ近かったりする場合は、自転車に乗りたいですね。そういう場合はウォーキングやジョギング等で着るような、フード付きで撥水効果の高いウインドブレーカーを着るといいと思います。足は濡れてしまいますが、足が濡れるのと傘さし運転のリスクを負うのとどちらがいいでしょうか。ポンチョ型のレインコートもありますが、女性にはいいですが、男性にはちょっとどうかな?と躊躇してしまいますね。

傘さし運転は、ダサくないレインコートさえあれば解決しそうな気がしますね。

自転車+サングラス+イヤホン+スマホで下を向いて片手運転

一度だけ私は見たことがあります・・・

恐ろしいですね。恐らくスマホにイヤホンを繋いで音楽を危機、スマホをしているのだと思いますが、これは一発NG級の危険度でした。

なかなか進まないインフラ整備

海外では自転車専用レーンの整備が進みつつ有りますが、日本ではまだまだ整備が進んでいません。整備されていない訳ではないのですが、こんな状況だったりするようです。

これは極端な例ですが、明らかに「どや?対応したぜ?」みたいな役人のやりましたアピールのために、ただ青線を引いただけのように感じてしまいますね。まあ青い線があるだけでも車道を走る車に自転車の存在を意識させる事はできるのですけどね。


自転車王国オランダでは、自転車レーンのインフラ整備が非常に進んでいるようです。
【まとめ】世界一の自転車先進国オランダは自転車専用レーンが当たり前! | Actionなう!

一方アメリカ・ニューヨークでは、自転車レーンはあるものの、意識がおいついていないせいか、自転車レーンに車が駐車している事もあるようです。
matome.naver.jp

少なくとも私の通勤ルートでは自転車レーンの青い目印を見ることができないので、非常に残念です。

歩行者の意識はどうだろう?

もし自転車が歩道を100%走る事が無い状況があったとして、歩行者は交通ルールを守っているでしょうか?
道路交通法,(略)道交法
歩行者側にもきちんと交通ルールはあります。もし冒頭の増田で「歩行者は神。100%優先されるから何しても良い」と考えているなら、それは誤っています。歩行者側にも交通ルールは有り、それを破ってはいけません。

ほーら歩行者もルール守ってないだろ?お互い様だろwww」等と言いたい訳ではなく、他人の違反を指摘するだけでなく、自分自身も適切に交通ルールを守るべきだと思うのです。そのルールも守らずに他人に「お前は100%間違っている」理論を振りかざしても、「お前も違反してるだろ」等と水掛け論になってしまい、解決には至りませんね。

ちなみに、youtubeで歩行者の飛び出しによる事故を探してみたのですが、ワラワラ出てきました。
www.youtube.com
※ ショッキングな映像を含むため、耐性の無い方は閲覧しない事をオススメします。

ついでに自転車の事故です。
www.youtube.com
※ ショッキングな映像を含むため、耐性の無い方は閲覧しない事をオススメします。

行き場の無い自転車

f:id:treeapps:20160312142820p:plain

現実的にはこんな感じで、自転車は自動車からも歩行者からも疎まれていますね。道路交通法的には自転車は原則車道を通っていい筈なのですが、自動車からは「危険だから通るな!」と言われる事が多いのが現状です。そして自転車通行可の標識がある歩道でも、歩行者から「自転車は危険だから歩道を走るな!」と言われます。正直どこを走っても誰かに何かを言われてしまうのが自転車の現状ですね。

自転車事故の大分部は都内で起きている

都内自転車の交通事故発生状況 :警視庁
警視庁の資料を見ると、やはりというか、自転車事故前提の発生場所は都内が31.1%を占めていると書かれています。47都道府県のうち、都内だけで3割を占めてしまう程に一極集中していますね。

自転車についての議論

交通事故における、自転車と自動車の割合

https://www.itarda.or.jp/itardainfomation/info94.pdf
少し古いデータですが、どうやら交通事故における歩行者の死傷者は圧倒的に自動車の割合が高いようです。

勿論都内と田舎では結果も変わるでしょうが、平均的には圧倒的に自動車が危険のようです。不当に自転車のみを悪とするのは、数値的に合っていないように見えます。

自転車の免許制

当館利用者より「世界で自転車の免許制をとっている国、州など、どんなところがあってどのくらいの数か、ま... | レファレンス協同データベース
世界的に見ても、正式に自転車を免許制にしている国は無いようです。独自に免許制を行っている地域もあるそうなのですが、法的な拘束力はありません。

まだ正式な実例が無いため、免許制にした結果、どういう事になるかは未知数ですね。単純に考えると「交通ルールの意識が高くないと自転車に乗れなくなるので、自転車事故は減るはず」ではあるのですが、本当にそれだけで終わるでしょうか。

免許制にした事で、免許が取れない人(試験で落ちた、年齢制限にひっかかった等)は自転車という交通手段を失うと、今度は自動車に乗る事もあるでしょう。そういう人達が今度は自動車で同じ事をしてしまうのではなないか?という議論もあるようです。また、そうして自動車の利用が増える事で、更なる環境汚染等に繋がったり、交通量増加に伴い救急車や消防車等の緊急車両の通行を阻害してしまう等を危惧している声もあります。

「ならばバスや電車を利用すれば良い」という考えもありますが、その場合はそれらの交通量が増える結果となります。増えた結果、バスや電車で別の問題が噴出し、それが事故を起こしてしまう結果になるかもしれません。

自転車とイヤホンと

blog.jablaw.org
最近騒がれている自転車に乗りながらのイヤホンですが、周囲の音が聞こえている状況ならOKのようですね。イヤホンをする=即座にNG、というわけではないようです。まあでも個人的にはイヤホンをしていると音楽に注意が向いてしまい事故る可能性があるので、周囲が聞こえていてもNGでいいと思うのですけどね。

自転車+イヤホンについての議論ですが、同時に自動車+カーオーディオの議論が噴出する事が多いようです。「自動車だってカーオーディオで周囲聞こえて無いじゃん!お互い様だろ!」といった水掛け論です。更に言うと、「歩行者は歩道でイヤホンするのは有りなのか?」という議論に発展する事も有り得そうです。

自転車先進国のオランダではどうなのか

自転車先進国として名高いオランダを参考に、インフラや意識改革を行っていくのがこれら問題を解決近道になると思いますが、オランダでも問題はあるようです。

action-now.jp
広大な駐輪場が必要になっているようです。また、自転車が増えると相対的に高価な自転車も増えるため、自転車の盗難事件が増える傾向があるようです。この記事には書かれていませんが、自転車が増えれば相対的に自転車同士の事故が増えると思われます。果たしてオランダは自転車先進国としてどう解決するつもりなのか、注目ですね。

雑感

増田のコメントにあるような「道路交通法を破る奴が悪い。だから俺は無策でいく。」となるより、「いつどんな理不尽に巻き込まれるかも解らない。個人で何かしらの防衛策を講じた方が不利益は少なく済む」と考えた方がいいと私は思っています。コメ主の主張は確かに正しいです。物事が100%理想通りに動く世界があれば、その主張は最高です。しかし、世の中には理屈が正しいのに、突如理不尽によって被害を被る事があります。相手側に過失が100%あったとしても、被害を受ける場合もあります。それを考えると、私は痛いのも怖いのも嫌なので、対策をしても損は無いと思っています。

IT業界で例えると、「負荷でサイトがダウンしたぞ!?ブルートフォースの総当り攻撃を受けてるようだ。でもアタックしてる方が悪いんだから、俺は何もしない。相手が悪い」という対応に利益があるのか、不利益を生むのか。この場合は誰が攻撃してきているのかを詳細に特定できない場合があり、損害賠償の請求先もありません。ここで「相手が悪い!」と叫ぶよりも「攻撃されないよう防衛して不利益を減らそう」と考えた方が、最終的に自分にふりかかる不利益は減らせます。

今回自転車と交通事故について色々調べてみましたが、「自転車=悪」「自転車を免許制にすれば解決・改善」等と一概には言えない問題なんだな、と再認識しました。自転車の問題は、自転車先進国のオランダでさえ問題を抱えています。自動車と違って、問題表面化・認知されて、それを改善しようとする歴史がまだ浅いので、まだ探り探り解決しようと努力しているように見えました。そんな状態なので、一方的には「あれは悪だ」と決めつけても何も解決しないので、冷静に現状の問題を認識して、まずは個人でできる防衛策を考えていきたいですね。


最近youtubeで「ドライブレコーダー」で検索した動画をよく見るのですが、自動車・自転車に乗りたくなくなる程怖くなります。「どういう状況で事故が起きているのか」を知る事もできるので、事故動画に耐性がある方は、是非見てみる事をおすすめます。恐らく「俺、もう危険運転やめるわ・・」と思うことでしょう・・
www.youtube.com

dockerでローカルに複数バージョンのMySQLサーバを一括で生成する!

$
0
0

前回手動でdocker runしてたので、今回はdocker-composeで一括で複数バージョンを用意しちゃいますよ〜
f:id:treeapps:20151128225319p:plain

www.bunkei-programmer.net
↑前回の記事で、docker runで頑張ってMySQLサーバをたてる事ができましたね。

今回はMySQLのバージョン5.5、5.6、5.7を一気に一括で作成してみます。

docker-compose

実はもう準備してあります。
github.com
英語ですが、使い方も全部書きました。

簡単に言うと、git cloneし、docker-compose upするだけで、MySQLのv5.5, 5.6, 5.7の環境が準備できてしまいます。

しかも、それぞれのバージョンごとにmy.cnfを用意してあるので、バージョン毎に設定を変える事もできます。

使用しているimageはMySQL公式のimageです。
https://hub.docker.com/_/mysql/

環境変数を設定すると、MySQL起動時にスキーマ、ユーザ、パスワード、rootパスワードを作成・設定した状態で起動してくれます。その変もバージョン毎に変更できるようにしてあります。

雑感

初回はdocker pullが走ってしまうので、イメージのダウンロードにちょっと時間がかかってしまいますが、これで無茶苦茶簡単にMySQLサーバをフルセットで準備する事ができます。

これでローカル環境にはMySQLサーバではなくMySQLクライアントだけインストールするだけで済むようになります。重たくメモリを喰うMySQLサーバをローカルに起動したり、複数バージョン運用するのに職人芸で対応したりする必要も無くなります。もしまだDMGやexeでローカルにMySQLサーバをインストールしている方がいれば、これを機にdockerに移行してみませんか?

Spring batch, Flyway, Jooq code generatorからJooq DSLでSQLを発行する!

$
0
0

Spring boot、Spring batch、Flyway、Jooq code generator、Jooq DSLの組み合わせですよ〜
f:id:treeapps:20160402175154p:plain

Jooqとは

www.jooq.org
詳細は公式サイトを見た方が早いのですが、簡単に言うとJavaでタイプセーフにSQL発行できるライブラリです。

言葉で説明しても伝わりにくいので、実際のjavaのコードを見るとイメージできると思います。

List<PrefectureRecord> results = dsl.selectFrom(Prefecture.PREFECTURE)
    .where(Prefecture.PREFECTURE.PREFECTURE_CD.eq(Byte.parseByte("13")))
    .fetchInto(PrefectureRecord.class);
results.forEach(System.out::println);
↓
+-------+-------------+---------------+
|area_cd|prefecture_cd|prefecture_name|
+-------+-------------+---------------+
|      3|           13|東京都          |
+-------+-------------+---------------+

※ static importすればもっとシンプルな記述ができます。

こんな感じでSQLファイルにSQLを記述するタイプではなく、javaのコードでSQLを発行する事ができます。

S2JDBCのような見た目ですが、S2JDBCと違い、group byやhaving等、高度なSQLを扱う事ができます。その変わり、SQLファイルにSQLを書く事は基本的にできません。

Jooqのサンプル

今回も例によって既にモノはできています。

github.com

環境

環境バージョン
OSmac el capitan
IDESpring tool suite(STS) 3.7.3
docker1.10.0
MySQL5.7
java1.8
gradle2.10
Spring boot1.3.3
Spring batch1.3.3
Flyway3.2.1
Jooq3.7.3

プロジェクト構成

以下のように、マルチプロジェクト構成で、base(共通親プロジェクト)に自動生成したクラスを配置するようにしています。実際のプロジェクトではマルチプロジェクトの場合がほとんでしょうから、そうしてみました。

root
├ master(gradle, docker関連)
└ base(共通親プロジェクト)
  └ batch(バッチプロジェクト)

このサンプルで行われる事

このサンプルコードは、Spring batchを起動すると、javaから任意のタイミングでFlywayでマイグレーションを行ってテーブル作成+データ登録をし、Jooq code generatorでモデルクラス等を自動生成し、そのままjooq DSLでSQLを発行しています。FlywayもJooq code generatorもJooq DSLも全てjavaから任意のタイミングで実行しているサンプルです

DBについてはdocker上のMySQL5.7を使っています。docker上にMySQLサーバを作成する手順は前述のgithubを見るか、以下の記事をご覧下さい。
www.bunkei-programmer.net

最初はgradleからflywayとjooq code generatorを実行しようとしましたが、DB接続情報をgradleでも管理する必要があり、2重管理を避けたいと思ったので、全てjavaから実行しています。また、Spring bootを使っているのは、DB接続に必要なデータソース系のオブジェクトを簡単にDIできるためです。

Jooqの簡単なexampleについては、以下のソースをご覧下さい(この記事で詳細な解説はしません)
spring-boot-flyway-jooq-example/GenerateMain.java at master · treetips/spring-boot-flyway-jooq-example · GitHub

Flaywayの場合
@Autowiredprivate DataSource dataSource;

・・・中略・・・
    Flyway flyway = new Flyway();
    flyway.setDataSource(dataSource);
Jooq DSL の場合
@Autowiredprivate DSLContext dsl;

・・・中略・・・
    List<PrefectureRecord> results = dsl.selectFrom(Prefecture.PREFECTURE)
        .where(Prefecture.PREFECTURE.PREFECTURE_CD.eq(Byte.parseByte("13")))
        .fetchInto(PrefectureRecord.class);

Jooqを触ってみて

といっても業務で使っているわけではなく、私個人でちょっと触った程度なのですが。。。

良かった点

集約関数

distinctやgroup byやhavingが書けてしまうのは凄いと思いました。例えば

List<Record2<Byte, Integer>> results =
    dsl.select(Prefecture.PREFECTURE.AREA_CD, DSL.count())
    .from(Prefecture.PREFECTURE)
    .groupBy(Prefecture.PREFECTURE.AREA_CD).fetch();

こう書くと、

select
        prefecture.area_cd
        ,count(*)
    from
        prefecture prefecture
    groupby
        prefecture.area_cd

というSQLになります。これがDSLで書けるのは嬉しいですね。

DDL

また、create table、create index、drop table、drop index、alter table等のDDLも書けてしまいます。ストアドファンクション・ストアドプロシージャにも勿論対応しています。

sysout

例えば

List<PrefectureRecord> results = dsl.selectFrom(Prefecture.PREFECTURE)
    .where(Prefecture.PREFECTURE.PREFECTURE_CD.eq(Byte.parseByte("13")))
    .fetchInto(PrefectureRecord.class);
results.forEach(System.out::println);

こう書くと、以下のようにコンソールに出力されます。

+-------+-------------+---------------+
|area_cd|prefecture_cd|prefecture_name|
+-------+-------------+---------------+
|      3|           13|東京都          |
+-------+-------------+---------------+

ただし、1件毎にリッチな出力がされてしまうので、複数件sysoutするとヘッダが何回も出力されてちょっとウザいです。

悪かった点

複雑なSQL

やはり複雑なSQLを書こうとした場合の懸念です。

select
        sum(cnt)
        ,
    from (
        select
                count(*) as'cnt'
                ,xxx_cd
            from
                aaa
            groupby
                xxx_cd
    ) tmp
    inner join bbb
        on tmp.xxx = bbb.xxx
;

例えばこんな感じの「グルーピングした件数の合計値を区分毎に取得したい」といったSQLを書きたい場合、Jooqでどうやって記述するんだろう?と悩んでしまいます。書けるのかもしれませんが、パッと思いつきません。リファレンスをじっくり読んでようやく書けるという感じになると思います。また、joinの数が増えた時に、「これ、実行するとどんなSQLが発行されのだろう?」と悩んでしまいそうです。

DSLを書くときにインデントを頑張って調整すれば、きっとどんなSQLが発行されるか想像しやすくなると思いますが、フォーマッターの自動整形でお目当ての整形をさせるのは無理でしょうから、可読性は心配です。

Jooq code generatorの設定
Configuration configuration = new Configuration().withJdbc(new Jdbc() //
        .withDriver(dataSourceProperties.getDriverClassName()) //
        .withUrl(dataSourceProperties.getUrl()) //
        .withUser(dataSourceProperties.getUsername()) //
        .withPassword(dataSourceProperties.getPassword()) //
).withGenerator(new Generator().withDatabase(new Database() //
        .withName(generatorClassName) //
        .withIncludes(".*") //
        .withExcludes("(batch_.*|schema_version)") //
        .withInputSchema(dataSourceProperties.getSchema()) //
).withTarget(new Target() //
        .withPackageName("com.github.treetips.domain.model") //
        .withDirectory("../base/src/main/java")));
GenerationTool.generate(configuration);

こんな感じでcodeをgenerateするのですが、withDriver・withUrl ・withUser・withPassword の部分のDB接続情報はDIしたDataSourceをセットする等して簡略化できないものですかね。このためだけにDataSourcePropertiesをDIしたりする必要があるし、コード量が無駄に増えるのでスマートではないなあと思いました。

未知数な点

今後メンテされてアップデートし続けていくのかどうかと、SQLのパフォーマンス(SQLからモデルクラスへのマッピング速度)等ですね。

おまけ

MySQLとjavaの型のマッピング一覧

今回githubにアップしたサンプルはMySQLを使っています。MySQLのそれぞれの型は、Jooq code generatorで自動生成した場合、javaだとどの型に変換されるのか?を簡単にまとめてみます。

使用するDDLは以下の通りです。

droptable if exists java_type;
createtable java_type (
	char_type char(10)default'char'
	,varchar_type varchar(10)default'varchar'
	,tinytext_type tinytext
	,text_type text
	,mediumtext_type mediumtext
	,longtext_type longtext
	,tinyint_type tinyintdefault1
	,smallint_type smallintdefault1
	,mediumint_type mediumintdefault1
	,int_type intdefault1
	,bigint_type bigintdefault1
	,float_type float default1.1
	,double_type double default1.1
	,date_type datedefault'2001-01-01'
	,datetime_type datetimedefault'2001-01-01 00:00:00'
	,timestamp_type timestamp default'2001-01-01 00:00:00'
	,time_stype timedefault'00:00:00'
	,year_type year default'2001'
	,binary_type binary
	,varbinary_type varbinary(10)
	,tinyblob_type tinyblob
	,blob_type blob
	,mediumblob_type mediumblob
	,longblog_type longblob
	,enum_type enum('red','blue','yellow')default'red'
	,set_type set('green', 'orange', 'pink')default'green'
	,geometry_type geometry
	,point_type point
	,linestring_type linestring
	,polygon_type polygon
	,multipoint_type multipoint
	,multilinestring_type multilinestring
	,multipolygon_type multipolygon
	,geometrycollection_type geometrycollection
) engine=innodb charset=utf8mb4 row_format=dynamic comment='java型確認マスタ';

これをJooq code generatorはどのようにjavaの型にマッピングしたのでしょうか。

MySQL Java
char(10) String
varchar(10) String
tinytext String
text String
mediumtext String
longtext String
tinyint Byte
smallint Short
mediumint Integer
int Integer
bigint Long
float Double
double Double
date java.sql.Date
datetime java.sql.Timestamp
timestamp java.sql.Timestamp
time java.sql.Time
year java.sql.Date
binary byte[]
varbinary(10) byte[]
tinyblob byte[]
blob byte[]
mediumblob byte[]
longblob byte[]
enum('red''blue''yellow') JavaTypeEnumType
set('green', 'orange', 'pink') String
geometry Object
point Object
linestring Object
polygon Object
multipoint Object
multilinestring Object
multipolygon Object
geometrycollection Object

概ね想像通りです。(位置情報系は全部Object)

enum型に関しては、javaのenumクラスが生成されていました。

/** * This class is generated by jOOQ. */@Generated(
    value = {
        "http://www.jooq.org",
        "jOOQ version:3.7.3"
    },
    comments = "This class is generated by jOOQ"
)
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
publicenum JavaTypeEnumType implements EnumType {

    red("red"),
    blue("blue"),
    yellow("yellow");

    privatefinal String literal;

    private JavaTypeEnumType(String literal) {
        this.literal = literal;
    }

    @Overridepublic Schema getSchema() {
        returnnull;
    }

    @Overridepublic String getName() {
        return"java_type_enum_type";
    }

    @Overridepublic String getLiteral() {
        return literal;
    }
}

bigint型とLong型

しかし、どのORMの自動生成のマッピングを見ても、Bigint -> Long とマッピングする事が多いように思えます。

MySQL Bigint型 符号なし18446744073709551615
Java Long型9223372036854775807

このように、javaがLong型だと、bigintの符号無しの場合は桁あふれしてしまうんですよね。そう考えるとjava側はBigDecimalにしてもいいんじゃないかと思います。auto_incrementなカラムは大抵bigint unsignedで定義するので、いつか桁あふれするよなこれ・・・でも桁あふれする程このシステム使われないから大丈夫なんだろうけど・・・等といつも思っています。

雑感

複雑なSQLを書こうとすると大変になりそうなデメリットを持つ一方で、DDLが書けてしまうメリットもある。そんな良い点と悪い点の両方を併せ持つJooq。使い所さえ間違わなければかなり使えそうな感じですね。

私は業務で未だにS2JDBCを使っていますが、DSLで集約関数は書けないし、truncateも書けないし、SQLファイル内でDDLは書けない、それができてしまうJooqは凄いなと思いました(コナミカン
ただ、一方でJooqはSQLファイルによる記述ができないので、そこが業務ではデメリットになりそうです。そう考えるとS2JDBCはDSLもそこそこ書けるしSQLファイルで2way SQLもいけるし、結構バランスがいいんだなあと思いました(更にコナミカン

Jooqの記事を書いておいてアレですが、seasarプロダクトのDomaはSQLファイルオンリーで高機能なORMですが、個人的には次に業務で使うならdoma2がいいなと思っています。例えばS2JDBCの場合、generatorでentityとnamesクラスを自動生成しますが、カラムが変更された時にコンパイルエラーが出るから変更に追従し易い、といったメリットがありますが、私は一度もカラム名変更に追従できて助かった記憶が無いのです。そもそもカラム名を変更する機会が無いのです。そう考えると、もういっそ全部SQLファイルの方がいいんじゃない?とか思いました。

ただ、全部SQLファイルになると、どんなSQLを発行しようとしてるのかはSQLファイルを1件づつ開いてみないと確認できない点だけが不安ですね。


ん?Hibernate?なにそれぼくしらない

Viewing all 140 articles
Browse latest View live