wiprog

C#, .NET, Scala ... について勉強したことのメモ

2019年をふりかえって。ピアノをやめた。

今年も無事終わりそうなので振り返ってみます。

2019 年、26 歳になって、生まれて初めてピアノをやめました。今まで見ていた世界が全部崩れて、新しい世界に出会ったみたいです。

赤ちゃんのころから音の出るおもちゃが好きで、耳が良かったのか歌もすぐ覚えるし、まだ立てるかどうかのときから、知らない大人ともペラペラ喋っていたそうです。 4 歳のころ、保育所の先生の猛烈な後押しに両親が説得されてピアノを習い始めました(父親は、男にピアノなんて、と反対したそうです)。 それから 26 歳まで、ぼくの人生にはつねに音楽が溢れていました。 大切なことは全部音楽が教えてくれました。音楽がない人生なんて考えられませんでした。

最初、練習することなんて知りませんでした。 実家は田舎の漁師で裕福な家庭ではなかったので、ピアノは買ってもらえず、どこからか譲り受けてきた古いオルガンを与えられました。 ぼくにとって、オルガンは練習するための楽器ではなく、最高に楽しい遊び道具でした。 家ではレッスンで与えられた曲は一切練習せず、好き勝手に弾いていたようにおもいます。 知らないうちに絶対音感というものがついていました。いまでも、楽音はほとんどすべてドレミで聞こえます(シャープやフラットに関してはすこし濁ったようなイメージで入ってきます)。 教本にのっている曲で、聞いたことのある曲なら練習しないで弾けました。 そうでない曲は、先生がレッスンでお手本を弾いてくれるので、それを聴いて覚えて弾きました。

オルガンはかなり古かったので、小学校に上がる前に壊れたとおもいます。 次は、小さい鍵盤が光るキーボードが家にやってきました。 キーボードはこれまたぼくの新しい遊び道具になりました。よくエレクトーンごっことか、木琴ごっことかをしていました。

そのうち、(どうやって習ったか覚えていませんが)少しずつ楽譜が読めるようになりました。 楽譜が読めるようになると、ピアノに触らなくても、頭の中で楽譜通りの音を再生できるようになりました。 そんなこんなで初見でも勝手に指が動くような状態になり、中学校に上がるぐらいまでは練習なんてほとんどしませんでした。 レッスンで初見で弾いてちょっと直されるか丸をもらって帰ってくる、というのがふつうでした。 小学生のとき(時期は覚えていない)両親は金銭的な悩みもあったでしょうが、一番安い中古のアップライトを買ってくれました。 楽器としては決してスペックがよいとはいえないピアノでしたし、音色もあまり良いものではありませんでしたが、それでも、初めて買ってもらった生の楽器には、思い出がたくさん詰まっています。

中学生にもなると、古典のソナタやバッハの勉強に入って、さすがに初見ではつらくなり家で練習するようになりました。 練習の楽しさがわかると、もっと勉強したいとか、そんなふうに思うようになりました。 他に特別な取り柄もなかったので、人に認められる手段として、音楽以外考えられなくなっていました。 中学校後半になると、変声障害によって人と話すのが嫌になりました。 毎週遊んでいた友達とも疎遠になりました。 そんなとき、感情をぶつける唯一の相手はピアノでした。 このときから、どんどん音楽にのめり込み、ピアノだけがぼくの唯一の理解者となっていました。

中学を卒業する頃のぼくは、それなりに賢明な選択をしました。 まじめに頑張って勉強すれば就職して食べていけることがほとんど保証されているような、釧路高専に入りました。 声のこともあって、音大に行きたい、ピアニストになりたいなんて夢はそっと殺しました。 歌の試験があることぐらい、中学生のぼくでもわかっていました。

高専に入ってから、少ないながらも友達はできました。 でも、遊びには参加しませんでした。意図的に深くかかわらないように逃げました。カラオケが嫌でした。歌うのも嫌だし、気を遣われるのも嫌でした。 寮にも部活にも入っていなかったので、人とのつながりは最低限のものでした。

吹奏楽、というのはなんとなく聞いたことがある言葉、ぐらいのものでした。 母親や叔母が学生時代にやっていた。高校の時の先生がなんとか先生っていう有名な先生だった。とかそのぐらいの認識でした。 入学当初はスルーしていましたが、夏の吹奏楽コンクールまで 1 ヶ月切った頃でしょうか。 ミス・サイゴンを演奏するのに、あの印象的なピアノを弾く部員がいないようでした。 部活の掲示板に「ピアノ急募!!」と書かれているのをみました。 そこで、思い切って吹奏楽部に入りました。

吹奏楽部に入ってから、とりあえず人手のたりない打楽器に配属されました。 とにかく練習しました。先輩たちや同期はみんな優しく接してくれました。 音楽の力で、人とつながるという経験をしました。 みんなでひとつの音楽をつくりあげる楽しさも知りました。 頑張っていれば、周りが助けてくれる、ということも学びました。 集団で生きることの難しさも学びました。

音楽の面でも大変な学びを得ました。 顧問の先生は素晴らしい先生でした。音楽に情熱をもち、音楽を深く学ぶことの楽しさを教えてくれました。 毎年、冬になるとソロコンテストの伴奏をしていました。 ここでは、ふだんのピアノのレッスンでは触れることのできないフランスの現代曲なんかにも触れることができました。

どんどん音楽が好きになりました。

でも、どんどん音楽の世界に閉じ込められていきました。

高専に在学中、金銭的な問題から、ピアノをやめるように両親に何度も言われました。 ピアノを辞めることは、当時のぼくにとって、声を奪われるのと等しい苦痛でした。 泣いてしがみついて続けさせてもらえるように頼みました。 今思えば、両親としては、金銭的な理由以外にも、音楽に依存してしまったぼくを外の世界に出したい、というのもあったのかもしれません。

高専での 5 年間はあっという間に過ぎていきました。 卒業式では泣きました。 就職したら音楽がいままでのようには続けられないということはわかっていました。

2013 年に就職してから、少しだけ音楽と距離ができました。 それでも、中古の安い電子ピアノを家に置いて毎日弾き(苦情が来ました笑)、週末にはレッスンに通いました。 作曲の先生を紹介してもらって、作曲の指導も受け始めました。 音大も目指しておらず、ただ好きでやっているだけだから、という理由で、作曲のほうはレッスン料なしで、和声や対位法や作曲についてたくさん教えていただきました。 作曲をしたり、それを発表したり、高専時代の同期(フルートが上手な女の子でした)といっしょに演奏活動をしたりするうちに、どうしても東京でピアノを頑張ってみたい、という気持ちになりました。

生活を徹底的に切り詰め(ごはんが 1 食りんご半分だったり、ごはんに醤油をちょっとかけただけだったりしました)上京資金を 150 万円ほどためたぼくは、 2 年半勤めた会社を辞め、2015 年の秋に東京にやってきました。 東京にやってきたぼくは、 IT エンジニアとしての就職先(現在の職場)を見つけるやいなや、まず防音マンションの契約料に 30 万円ほど、のこりの 120 万円は全額グランドピアノの購入資金にあて、家具の購入は必要最低限に抑えてリボ払いでなんとかしました。 今考えたら狂っています。

上京前にピアノの先生も見つけていたぼくは、上京後すぐにレッスンに通い始めました。 東京でのレッスンは、いままでうけてきたものとは質がちがいました。 1 時間のレッスンで数小節も進まない、そんなレッスンをはじめて経験しました。 コンクールにも参加して、最初の年は全国大会に出場しました。 このことが、のちのちプレッシャーになっていきます。

上京後の生活は決して楽ではありませんでした。 厳しいレッスン、慣れない仕事、決して高くない給料、ピアノの維持費、防音マンションの家賃、レッスン代やコンクール、演奏会の参加料。 いつもギリギリで生活していました。

ある日、本番で失敗をしました。その年、全国大会にはいけませんでした。 ふっと、糸が切れました。 金銭的にも精神的にも肉体的にも、すべてが限界でした。 病院に行くと、中等度のうつ状態と診断されました。 そのときからピアノはおやすみしますと伝え、会社も休職することになりました。 2018 年末のことでした。

休職中は実家に帰りましたが、北海道では心療内科が十分になく、転院がままならない状況だったので 2 週間に 1 度上京していました。 このころ、ピアノはほとんど触りませんでした。 2 ヶ月ほどたって、回復したぼくは職場に復帰しました。

このとき、気づいてしまいました。ピアノを弾かなくても生きてていいんだって。

2019 年春のある日、整体に行きました。 首がものすごく歪んでいるとのことで、骨格の調整をされました。 直接の関係はわかりませんが、この頃から、以前に増して声の違和感がありました。 でも、なんだかちょっとずつ低い声が出るようになって、気づいたら普通の声になっていました。

気づいたら、あらゆることを自由に表現できる声を手に入れていました。 長年身につけていた重い鎖がほどけて落ちていきました。

そこから、音楽に対する情熱が一気に失われました。 声が出るのに、ピアノを弾く意味がわからなくなりました。 またピアノを弾いて、追い詰められるのが怖くなりました。

そうして、 2019 年はほとんどピアノを弾かずに過ごしました。

嫌いになったわけではありません。ただ、必要がなくなってしまいました。 これまではずっと、生きるためにピアノが必要でした。でも、ピアノがなくても生きられるようになってしまいました。

今はまだ、ピアノを弾く意味を見つけられていない状態です。 これから長い年月をかけて、少しずつ音楽に向き合って、情熱を取り戻していけたら。。そんなふうに思っています。

読書メモ: 起業の科学 #1 スタートアップにとっての「良いアイデア」とは

いかに課題にフォーカスするか

解決する課題の質を高めよ

「スタートアップの生死を分けるのは、 Product Market Fit (PMF, 市場で顧客から熱狂的に愛される製品のこと) を達成できるかできないかだ」 - Mark Andreessen, Founder of Andreessen Horowitz

いくら優れたプロダクトを生み出しても、市場に受け入れられなければ成長はできない。 PMF を達成するためにはまず、自分たちのビジネスアイデアが市場から求められているものなのかを検証すること。 スタートアップにおいては 課題の質 にフォーカスすることが最も重要である。 スタートアップのアイデアは、 課題ドリブン (課題ありき) ではなく、ソリューションドリブン技術ドリブン であることがあまりに多い。

「バリューのある仕事をしようと思えば、取り組むテーマは イシュー度解の質 が両方高くなければならない」 - 安宅和人, CSO of Yahoo

バリューのあるアイデアを見つけるには、「課題の質を上げてから、ソリューションの質を上げる」というパスしかない。 よって、スタートアップを始めるにあたって真っ先に注力すべきは、解決を目指す課題の質を向上させることである。

  • 今検討しているアイデアは、顧客にとって本当に痛みのある課題なのか?
  • このアイデアの妥当な代替案が、既に市場に存在していないか?

のように、様々な角度からアイデアの深堀りを繰り返していくことで、課題の質が上がる。 それができたら、その課題に対する解決策を検討し、磨きをかけていくことで、初めて価値のある「良いアイデア」に至る。

課題を軽視して大失敗

課題の質ではなく、ソリューションの質にこだわると、顧客にはほとんど使われずに終わる。

グーグルグラスや Apple Watch は膨大な資金力とブランド力を背景に、 PMF ではなく力技を選んだ。 しかし結局、ユーザの期待に応えることはできず、思うようには売れなかった。

課題を意識しないのは自殺行為

先の例が示しているのは、 Google や Apple のような実績や資金力のある彼らですら、課題の質を軽視して、プロダクトありき、ソリューションありきで考えると容易に失敗するということ。 資金も人材も知名度もないスタートアップが課題を軽視することは、ただの自殺行為である。

課題の質を決める 3 つの要素

課題の質は、ファウンダー(創業者)個人が次の 3 つの要素をどれだけ持つかに依存する。

  • 高い専門性
  • 業界(現場)の知識
  • 市場環境の変化 (PEST: Political, Economic, Social, Technological) に対する理解度

これらが極めて重要であり、ファウンダー個人に求められる。

自分ごとの課題を解決せよ

課題の質を高めるもう一つの要素は、ターゲットとする課題が「自分ごと」であるかどうか。 Airbnb (創業者が家賃を払えないという切迫した状況から生まれた) や、ダイソン (従来の紙パックの掃除機の吸い込みの弱さやパックの交換が面倒で大きな憤りを覚えた) は、自分が痛みを感じている具体的な課題から始まり、その課題を自分ならどう解決するかという順番でビジネスアイデアが形成されている。

魔法のランプクエスチョン (もし魔法のランプがあって、課題を解決するためにソリューションを出してくれるとしたら、どんなものがいいか?) から導き出される答えの中に、ダイヤの原石があるはず。 自分ごとの課題といっても必ずしも本人が当事者である必要はなく、周りの身近な人が抱えている課題でも、その課題の痛みをしっかり理解しているのであれば OK。

課題の発見は、まだ「始まり」

課題の発見は始まりにすぎない。重要なのはその後の磨きこみである。 このときに自分ごとの課題でなければ、本気で磨きこみができない。

なぜあなたが、それをするのか?

スタートアップでは、 第三者の課題 (自分がそこまで共感や思い入れがない他人の課題) を解決しようとすることは避けなければならない。 強い共感が持てない課題は自分ごとにならず、痛みの検証がどうしても表面的になり、結果的に真の課題にたどり着くことが困難になる。 また、説得力もないので周囲からの協力も得られにくい。

「YC のインタビューでは『誰がその製品を心の底から欲しがっているのか?』を聞く。ベストの答えは起業家自身であることで、次に良いのは、ターゲットユーザをものすごく理解しているのがわかる解答だ」 - Sam Altman, President of Y Combinator

ファウンダーの課題に人は集まる

課題への強い共感や思いが、スタートアップのビジョン、ミッションに翻訳されていく。 プロダクトを市場に本格投入するまでのフェーズにおいて、ビジョンやミッションはスタートアップの最大の競合優位性になる。 ユーザ、起業メンバー、投資家はいずれもファウンダーが語るビジョンとミッションに惹かれて集まる。

スタートアップが歩む道のりが想像以上につらいことも、自分ごとの課題から始める必要がある理由である。 最初に立てた仮設はほぼ覆ってしまい、限りある資金がどんどん減っていく。 この時、ファウンダーが自分ごとの課題を持ち、将来的にこうあるべきだという強いビジョンやミッションを掲げていれば、高いレジリエンス(メンタルの回復力)の源になる。

課題に出会った原体験は何か

「自分ごとの課題になっているかどうか」を別の言い方で自問するなら、「その課題にストーリー(原体験)があるか」

誰が聞いても良いアイデアは避ける

アイデアは crazy か

誰が聞いても良いと思えるアイデアは、スタートアップにとっては選んではならないアイデアだ。 ネガティブなフィードバックが周囲から集まる状態を「マーケットが定義されていない状態」ととらえて、今のタイミングで事業を手掛けることがチャンスだと考える。

「一見アンセクシーだが、実はセクシーなアイデアを見つけることが決め手となる」 - George Kellerman, COO of Yamaha Motor Ventures & Laboratory Silicon Valley

例: 排せつ予測デバイス D Free

こうした crazy でアンセクシーなアイデアは、人に話すのが恥ずかしいはずだ。 人に話すのが恥ずかしい段階とは、その課題を言語化して説明するフレームワークを入手できていないために、人に伝えるのが困難だということ。 当然、その課題に目をつけている企業はまだ存在しないか、ごくわずかである。

一方、言語化して人に伝えられるような課題をターゲットにした場合は、既に課題が認識されており、妥当な代替案がある場合が多い。 このようなビジネスは投入できるリソースの勝負や価格勝負になるため、スタートアップが狙うのは賢明ではない。

「競争は負け犬がすることだ」 - Peter Thiel, Founder of PayPal

市場のシェアを激しく奪い合う消耗戦になると、リソースの多い大企業が圧倒的に有利になり、スタートアップに勝ち目はない。

大企業の意思決定の仕方

大企業が新規事業を始めるときは、取締役会でほとんどの役員が賛同しないと稟議の承認が下りない。 判断の際に役員が気にするのは課題の質などではなく、その事業がもたらす売り上げや利益の見通し、蓋然性、既存のコアビジネスと競合しないか、といった点である。 そのため、アンセクシーなアイデアには挑戦しにくい。

「スタートアップではハードなことをするほうが実は近道である。簡単な道を選ぶことは結果として遠回りになる」 - Sam Altman, President of Y Combinator, lecture in Stanford University

一見悪いアイデアが世界を変えた

crazy なアイデアで大成功を収めたスタートアップの代表格は Airbnb だ。 犯罪大国のアメリカで赤の他人の家に泊まる、他人を自宅に泊めるという行為は、まさにバッドアイデアそのものだった。

「多くの人が、 Airbnb はうまくいった最悪のビジネスアイデアだと言っている」 - Brian Chesky, CEO of Airbnb

Uber のように、だれもが当たり前だと思ってきたこと(タクシーを捕まえるために何分も手を挙げて待ち、英語が片言のドライバーに目的地を伝えるのも大変で、支払いは基本的に現金)に疑問を投げかけられるかどうかが、スタートアップが世界を変える存在になれるかどうかの最初の分かれ道になる。

他の人が知らない秘密を知っているか?

成功する人は、ほかの人が知らない秘密を知っている。 ここではインスタカート(スマホなどで注文すると近所のスーパーでの買い物を代行してくれるサービス)の例を見ていく。

インスタカートが大成功した理由

インスタカートは店舗と商品を選ぶと、「ショッパー」と呼ばれる一般人が、注文した人の代わりに店まで買い物に行き、 45 分以内に自宅まで届けるという仕組み。 創業者の Apoorva Mehta はアマゾンの物流システムを開発するエンジニアで、物流と小売りに関して高い専門性があり、市場の流れをつかんでいた。 しかし、彼自身が glossary shopping (食料品の買い物) は自分で行うもの、という通例に疑問を持った視点こそが重要。

インスタカートは経歴チェックやトレーニングプログラム、カスタマーレビューの仕組みを導入し、見知らぬ人に直接口委に入れる食料品の買い出しを頼んだり、住所を教えたりすることの不安を払拭した。 このサービスは全米に一気に広がった。

Mehta は Amazon 在職中に新規事業としてインスタカートのアイデアを提案したが、自社ビジネスと競合する奇抜な試みでしかないと却下された。 Amazon が膨大な資金を投資してきたインフラ (自社倉庫、外部の物流会社との契約) を否定することになるからだ。 この却下によって Amazon が現状のシステムでこのサービスに対応できないことが明確になり、Mehta はアマゾンを脅かすほどのビジネスチャンスがあると気付いた。

小売りとの win-win 関係

Amazon の台頭はスーパー等の小売店にとって脅威である(カスタマーの予算を奪い合う)が、インスタカートの場合は Win-Win の関係が成り立つ。これが Mehta が気付いた 秘密 である。

なぜ crazy なアイデアが求められるのか

crazy なアイデアが求められる背景には、 IT の進歩で、マーケットのパラダイムシフトが高速化していることがある。 2014 年ごろから、ユニコーン企業が続々と生まれるようになり、評価額の上昇カーブもすさまじいものになった。 この事実は、プロダクトやサービスの が短くなっていることも意味する。 これだけの速さだと、 First Mover (最初の市場参入会社) が出てきたのを後追いしても遅い場合があり、だからこそ、誰よりも先に PMF を達成することが重要である。 このときにベースになるのが、「一見すると悪いが、本当は良いアイデア」である。

イノベーションカーブの変化

従来のイノベーションカーブは、innovator (革新者) がいて、Early Adopter (新たに登場した商品、サービス、ライフスタイルなどを、比較的早期に受け入れ、それによって他の消費者・ユーザーへ大きな影響を与えるとされる利用者層) がいて、Chasm(深い裂け目、の意。Earyly Adopter と Early Majority の間の大きな溝) を超えるとようやく Early Majority (Early Adopter に追従する形で受容し始める利用者層)に到達する、といった話だった。 このイノベーションカーブであれば、ある程度の時間をかけてプロダクトを検証することができたが、スマホなどの普及による情報伝達スピードの加速で、このモデルは古くなっている。 現在は Trial Customer (新しもの好きの顧客) と Burst Majority (爆発的に広がる一般層) の 2 段階できわめて速く浸透する。 ユーザ数がある値を超えるのを待たず、一定数の Trial Customer のフィードバックをベースにプロダクトを磨きこむという新時代は、スタートアップにとっては好機ともいえる。

代替案のないアイデアを探せ

「新規事業を考えるときには無消費をターゲットにせよ」 - Clayton M. Christensen

無消費とは、顧客が何も持たない状態のこと。つまり、前例もなければ、既存の消費者もいない、代替案のない状態のこと。 こうした場所を発見して PMF を達成できれば、大きな成長を見込める。

ロイヤルティーループの劇的変化

Royalty loop とは、製品を知った人がそれを気に入り、ユーザとして定着して使い続けてくれるまでの流れを輪のような形で示したもの。

従来の Royalty loop は、 AIDMA(Attention - Interest - Desire - Memory - Action) モデルであり、ユーザは一つのプロダクトやサービスを選ぶために時間をかけて、それを使う価値があるかを検証していた。 最近はほとんどのサービスで、「最初の 1 カ月は無料」といったフリーミアムが定番化した。まず使ってみて、有用だと思ったら購入する。気軽に始め、使っていく中で本格的にユーザがそのサービスに定着する (他の代替案を捨てる) というあらたな Royalty loop が生まれた。

また、継続的に使用する顧客との接点が多いフリーミアムやサブスクリプションモデルの利点はユーザの囲い込みだけではなく、フィードバックを高速に集められるという点にもある。 それをもとにプロダクトを絶えず改善していくことで、ファンの量と質 (高い LTV、低い解約率) を増やしていくことができる。

スタートアップが避けるべき 7 つのアイデア

1. 誰が見ても、最初から良いアイデアに見えるもの

一見よさそうに見えるアイデアはすでに誰かが手掛け、たいていは失敗している。

2. ニッチすぎる

あまりにも市場がニッチすぎると、将来的な成長が見込めず、スケールしない。

3. 自分が欲しいものではなく、作れるものを作る

技術ドリブンではいけない。技術的に作れたから作っただけのプロダクトは、課題から生まれたものではないので市場に受け入れられない。

4. 根拠のない想像上の課題

クラウドファンディングなどを使うと、たまたま出したコンセプト動画が受けて必要以上の金額が集まり、後戻りできなくなるケースがある。 Customer Problem Fit を実現しないうちに、表面的に PMF を達成したような状態。 もしビジネスをスケールするつもりなら、どんな課題を解決できるプロダクトになっているかを検証すべき。

5. 分析から生まれたアイデア

一時期あふれかえってどれも失敗したグルーポンの模倣サービスは、市場を俯瞰して空いている部分を狙うというロジカルなトップダウンアプローチでビジネスを展開しただけで、スケールするストーリーや、ファウンダー自身の思いが欠けていた。

6. 激しい競争に切り込むアイデア

スタートアップは「競争を避けること」が戦略の第一義であると考えるべき。

7. 一言では表せないアイデア

「誰 (customer) の何 (課題) をどのように解決するか」を一言で表せないアイデアは磨きこみが足りない。

読書メモ: Peopleware #1 人材を活用する (1) - プロジェクト失敗の原因

トム・デマルコの名著、 Peopleware を読み始めたので自分の意見も交えて読書メモ。 本の要約と自分の意見が半々ぐらいな感じです。ここ数年の話題だったり、主語が「私」となっているものは私個人の意見です。

今日もどこかでトラブルが

筆者は 10 年間にわたり、500 以上の開発プロジェクトとその結果について調査した。 調査の結果、プロジェクトの 15 % が水泡に帰している(中止、延期、納入後に使用されない等)ことがわかり、また、大きなプロジェクトほど失敗する可能性が高いこともわかった。 25 人年以上を注ぎ込んだプロジェクトのうち、実に 25 % ものプロジェクトが完成しなかった。 プロジェクトが失敗した原因の圧倒的多数は、 単なる技術的な問題として片付けられないものばかり だった。

問題の本質

失敗の原因を関係者に尋ねると、異口同音に 政治的要因 と言われたが、この言葉はいい加減に使われていた。 実際には、意思疎通の問題、要因の問題、マネージャーや顧客への幻滅感、意欲の欠如、高い退職率等の 社会学的な問題 であった。 政治的な問題ではなく、プロジェクトとチームの社会学的問題として本質をとらえると、もっと取り組みやすくなるはずだ、というのが筆者の主張。

実際のところ、ソフトウェア開発上の問題の多くは、技術的というより社会学的なものである。

多くのマネージャは実際には技術だけに関心があるというようなマネジメントをしている。 最も大切なのは人間中心に考えることなのに、いつもないがしろにされている(これは企業の教育に原因があるとのこと)。

そういえば今となってはすっかり忘れ去られた 7pay 事件(事故?)のときも、失敗の表面的な原因は技術的にあまりにもお粗末な品質の成果物に見えたが、現場の実態を暴露する twitter での書き込みや、社長の記者会見における発言を見ていると本質はやはり社会学的な問題だったように思う。 普段からコンビニ経営において、一緒に働く仲間をを軽視した本部の姿勢が批判されていることと、この失敗は無関係ではないと思う。

ハイテクの幻影

多少なりとも最新技術に関係している人(これは日本でいうところの『エンジニア』を名乗る人たちに相当すると思う)は、 ハイテクの幻影 に取り憑かれている。 他人の研究成果を応用しているに過ぎないのに、自分がハイテクビジネスの旗手だと錯覚している(日本の twitter や Qiita を見ているとよくわかります)。 ソフトウェアは、多数のチームやプロジェクト、固く結束した作業グループで開発するので、ハイテクビジネスではなく人間関係ビジネスに携わっていると言える。 プロジェクトの成功は関係者の緊密な対人関係によって生まれ、失敗は疎遠な対人関係の結果である。

マネージャが技術的問題にうつつを抜かす理由は、重要だからではなく、単にやりやすいからだ。

ここで、私のような テックリード と呼ばれるポジションの人は特に気をつけたほうが良いと思う。 テックリードは技術的な面でもチームをリードする必要があるが、テックリードになれた人は技術的な面ではすでに強みを持っているはずなので、社会学的な問題のマネジメントに割く労力を意図的に増やしたほうが良い。 これは私も経験済みだが、どうしても取り掛かりやすく仕事をしている気分になれる技術的な問題にフォーカスしがちである。

C# で messagepack vs json 比較

Messagepack の良さを社内に布教するためにベンチマークを取ったので転載しておく。

個人的には MessagePack + LZ4 の Typeless がおすすめ。 messagepack は可読性が・・・と言われることが多いけど、 Typeless なシリアライズなら型情報がつくので、 dynamic にデシリアライズして json を吐くような小さいツールさえ作っておけば gzip 圧縮した json とそこまで使い勝手は変わらないのでは?と思う。 パフォーマンスの面では messagepack が有利。json + gzip はファイルサイズは小さくなったけど、パフォーマンスが許容できるかどうか。

ベンチマークに使ったコードは こちら

ファイルサイズ

json:                     137,248,217 bytes
json + gzip:              045,170,640 bytes
msgpack:                  084,959,431 bytes
msgpack + lz4:            061,381,468 bytes
msgpack (typeless):       083,949,384 bytes
msgpack (typeless) + lz4: 060,776,211 bytes

パフォーマンス

BenchmarkDotNet=v0.12.0, OS=ubuntu 19.04
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.0.100
  [Host]   : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT
  ShortRun : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  
|                 Method |       Mean |     Error |   StdDev | Ratio | RatioSD |     Gen 0 |     Gen 1 |     Gen 2 | Allocated | Completed Work Items | Lock Contentions |
|----------------------- |-----------:|----------:|---------:|------:|--------:|----------:|----------:|----------:|----------:|---------------------:|-----------------:|
|       Utf8JsonWithType | 1,001.4 ms | 337.02 ms | 18.47 ms |  1.00 |    0.00 | 1000.0000 | 1000.0000 |         - | 642.99 MB |               3.0000 |                - |
|     Utf8JsonWithTypeGz | 4,134.0 ms | 987.83 ms | 54.15 ms |  4.13 |    0.13 | 7000.0000 | 7000.0000 | 2000.0000 | 814.09 MB |               3.0000 |                - |
|       Utf8JsonTypeless |   984.2 ms |  50.38 ms |  2.76 ms |  0.98 |    0.02 | 1000.0000 | 1000.0000 |         - | 642.99 MB |               2.0000 |                - |
|     Utf8JsonTypelessGz | 4,194.6 ms | 777.21 ms | 42.60 ms |  4.19 |    0.04 | 7000.0000 | 7000.0000 | 2000.0000 | 814.08 MB |               3.0000 |                - |
|    MessagePackWithType |   390.3 ms | 231.84 ms | 12.71 ms |  0.39 |    0.02 |         - |         - |         - |  336.9 MB |               2.0000 |                - |
| MessagePackWithTypeLz4 |   537.4 ms |  66.94 ms |  3.67 ms |  0.54 |    0.01 | 2000.0000 | 2000.0000 |         - | 395.79 MB |               2.0000 |                - |
|    MessagePackTypeless |   368.1 ms |   2.59 ms |  0.14 ms |  0.37 |    0.01 |         - |         - |         - | 335.94 MB |               2.0000 |                - |
| MessagePackTypelessLz4 |   515.2 ms |  75.83 ms |  4.16 ms |  0.51 |    0.01 | 2000.0000 | 2000.0000 |         - | 394.22 MB |               2.0000 |                - |
// * Legends *
  Mean                 : Arithmetic mean of all measurements
  Error                : Half of 99.9% confidence interval
  StdDev               : Standard deviation of all measurements
  Ratio                : Mean of the ratio distribution ([Current]/[Baseline])
  RatioSD              : Standard deviation of the ratio distribution ([Current]/[Baseline])
  Gen 0                : GC Generation 0 collects per 1000 operations
  Gen 1                : GC Generation 1 collects per 1000 operations
  Gen 2                : GC Generation 2 collects per 1000 operations
  Allocated            : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  Completed Work Items : The number of work items that have been processed in ThreadPool (per single operation)
  Lock Contentions     : The number of times there was contention upon trying to take a Monitor's lock (per single operation)
  1 ms                 : 1 Millisecond (0.001 sec)

今からできる、速くシンプルに LINQ を書くためのコツ 3 個

たびたび 「LINQ が遅い」 と言われているのを見かけるので、どうやったら速く書けるのか、どう書くと遅くなるのかについてまとめてみます。
LINQ は非常に強力で、ぼくが出会った「LINQ 遅い」のほとんどは、 実装の仕方がまずいものばかりです。
LINQ はいくつかポイントを意識するだけでかなり安全に速く書けるようになります。
いろいろとポイントはありますが、 3 つだけに絞って書いてみました。

私自身 C# 書き始めてようやく 2 年たったぐらいなので、間違っているところはバシバシご指摘いただけると嬉しいです。

こちらの記事も参考になるので、合わせてご覧ください。

blog.okazuki.jp

「LINQ 遅い」の 3 パターン

まあ LINQ は速くないこともたまにあるのですが、大抵の場合は十分なパフォーマンスを提供してくれます。
「LINQ が遅い」 と言う時はだいたい以下のどれかかなと思います。

  1. よくわからないけどイメージで「遅そう」と言っている (要するに使いたくない、覚えたくない)
  2. LINQ のしくみがよくわからず、誤った使い方、明らかに遅い書き方をしている (例: なんとなく Count(), ToArray() などしてしまう)
  3. LINQ の内部実装をよく知っているプロフェッショナルが LINQ が適していない場合に「遅い」と言う

1 の場合はどうにもできないです。 3 の場合は必要に応じて LINQ 以外の方法を取ることもできますし、 LINQ を使うと判断した場合は最も適切なメソッドを選んで使用できるので遅くなりません。プログラマとしてはここを目指したい。
問題は 2 の場合で、「なんとなく動くものは書けるけど遅い、どこが遅いのかよくわからない」というのが多いです。
今回は最も多いと思われる 2 のパターンをターゲットに書いていきます。

1. 不用意に要素数を取得しない (Count メソッド)

一番良く見かけるのがこれです。簡単に書けてしまいますが非常に危険です。

IEnumerable<Hoge> source = // ...

if (source.Count() > 0)
{
    // 要素があるときの処理
}

IEnumerable<T> はカウントを持ちません。いくつ要素があるのかも全部列挙してみないとわかりません。 もしかしたら非常に長かったり、列挙におおきなコストがかかったり、無限につづくシーケンスでそもそもカウントできないかもしれません。
Count メソッドは基本的には 100 万個要素があったら 100 万個全部を 1 個ずつ列挙して数えていくため非常に遅く、ほとんどの場合に意図していない列挙を発生させます。
もちろん Count の前に SelectWhere をかけていて、列挙の際になんらかの計算が発生するような場合にはその計算のコストもかかります。 実体が Count プロパティをもつコレクションである場合には Count プロパティの値を取得するような最適化は入っているものの、本当に「何個あるか知りたい時」以外使うべきでないです。

source.Count() > 0 と書くのであれば、代わりに source.Any() と書きましょう。

「ある条件を満たす要素が n 個以上あるかどうかを判定したいとき」には下記のように Skip や Take を使用することで、一部だけの列挙におさえることができます。

var filtered = source.Where(predicateFunc);

// 列挙した要素を一切使わず、ただ n 個以上あることをたしかめたいとき
// 列挙したものを保存する必要がないのであれば、この方法で無駄な配列確保を避けられる
if (filtered.Skip(n - 1).Any())
{
    // n 個あった時の処理
}

// 列挙した要素を n 個使用するとき
// 複数回同じものに対しての列挙を避けるには、ToArray() が有効。
// ただし、 n が大きくなった場合、それだけ大きい配列が確保されるので注意。これは ToList() でも同様
var part = filtered.Take(n).ToArray();

if (part.Length == n)
{
    foreach (var item in part)
    {
        // なにか処理
    }
}

2. なんとなく配列やリストに突っ込まない。ライブラリを作るときはなるべく IEnumerable<T> で受ける。

ToArray()ToList() は非常に便利なメソッドですが、ほとんどの場合 LINQ の途中で呼ぶ必要はありません。 たとえば、下記のような ToArray は意味がないばかりか、無駄な配列のためのメモリを確保してパフォーマンスを著しく低下させます。

IEnumerable<Hoge> source = // ...

var array = source.ToArray() // むだな配列生成
    .Select(x => ごにょごにょ)
    .Where(x => ごにょごにょ)
    .Distinct()
    .ToArray(); // むだな配列生成

// foreach するだけならむだな array はいらない
foreach (var x in array)
{
    // なにかする
}

下記のように書いてもまったく動作上問題がなく、パフォーマンスがよくなります。

IEnumerable<Hoge> source = // ...

var array = source
    .Select(x => ごにょごにょ)
    .Where(x => ごにょごにょ)
    .Distinct();

// foreach するだけならむだな array はいらない
foreach (var x in array)
{
    // なにかする
}

また、配列である必要がないのに配列で引数を要求するメソッドを書くのはやめましょう。 IEnumerable<T> で受けましょう。 本当は 1 個ずつ処理するだけのメソッドなのに、渡す側で ToArray() して渡す必要があるのは無駄です。

3. 巨大なファイルを 1 行ずつ処理するときも、リストはいらない。

たとえば、こんなコードを書いたことはありませんか?これはほんとうに無駄なのでやめましょう。

// 1 行ずつ処理したいけど、 LINQ つかうから IEnumerable<T> がほしい。 List<T> に Add していこう

List<Hoge> sourceList = new List<Hoge>();

using (var reader = new StreamReader(stream))
{
    string line;
    while ((line = reader.ReadLine()) != null
    {
        sourceList.Add(MapToHoge(line));
    }
}

sourceList.Select(xxx).Where(xxx). // ...

こんなふうに書けば、一気に全部読んでしまう必要はまったくありません。

IEnumerable<Hoge> Read()
{
    using (var reader = new StreamReader(stream))
    {
        string line;
        while ((line = reader.ReadLine()) != null
        {
            yield return MapToHoge(line);
        }
    }
}

Read().Select(xxx).Where(xxx) // ...

あるいは、ファイルから読むことがわかっているならこれでも良いです。

File.ReadLines("filepath")
    .Select(xxx)
    .Where(xxx)
    . // ...

File.ReadAllLines メソッドもありますが、こちらはすべてを読んで配列に入れてから返してくるので気をつけましょう。巨大なファイルを読む場合に大量のメモリが必要になります。

まとめ

たくさん書きましたが、要するにおなじシーケンスに対しての複数回の列挙や、必要のないものの列挙、巨大なメモリ確保にもっと慎重になりましょうということです。
LINQ は遅延評価が基本ですが、誤った使い方をすればそのメリットを活かせないばかりか、非常に遅いコードが簡単に出来上がってしまいます。
遅いのは LINQ のバグでも、 .NET Core 開発チームの怠慢でも、マシンのスペックが足りないからでもなく、ただ遅くなるように書いたからです。そうでないこともありますが、だいたいそうです。

自分で書いたコードが遅かったり、すっきり書けなかったりして困ったときに、Qiita や twitter に投稿するといろんな人のアドバイスが受けられて楽しいです。
読む人にとってはその投稿についたコメントや、反応の記事のほうが役にたつことがたくさんありますが、最初の投稿がなければそれらの記事も生まれません。

LINQ の内部の実装を読むことは非常に勉強になります。
GitHub で .NET Core の完全な実装を読むことができます。
難しく見えるかもしれませんが、単純なもの、きになるものから読んでいくといいと思います。 また、簡単そうなメソッドを自分で書いてみるのも理解するには効果がありました。
LINQ を書く際に気をつけることはこれがすべてではありませんが、ドキュメントやコードをきちんと読んだり、信頼できる先輩にアドバイスをもらったりしながらだんだんと身についていくものだと思います。

github.com

あと、 ReSharper などのツールを入れると multiple enumeration の注意を出してくれたりもします。

www.jetbrains.com

(ReSharper は他にもいろいろなことを教えてくれる素晴らしい先生になるのでめちゃくちゃおすすめです)

(おまけ) 記事を書いたきっかけ

もともと微妙なコードをちょくちょく見かけていて、 LINQ ってそんなに難しいのかなーと思っていたのですが、 twitter で
C#でLinqを使うよりPythonの方が2倍速かったのでベンチマークをしてみた - Qiita
という記事が回ってきました。

公開から短時間でいいねが複数ついていたことから、ほんとうは速くシンプルに書けるにもかかわらず、 LINQ が遅いと判断してしまう人は多いのではないかと思い、 急いで記事を書いている次第です (そもそもこのブログ自体そんなに見られてないのでどの程度効果があるかは疑問ですが)。

この記事の具体的な内容についてはもう十分他の方が指摘などされていると思いますので、ここでは細かく言及しません。 ただ、もし初心者の方が読まれる場合には下記の点だけは頭においてほしいです。

  1. (意図的にそうしているとは思いませんが、結果として) 検証用のコードが非常に遅くなるように書かれています。 検証用のコードを改善することでだいぶ速い結果になる、という記事がすでにあります。GroupBy が遅いように書かれていますが、それよりも 無駄な ToList 何度も呼んでいるめにループの回数が増えてしまっていることのほうがはるかに大きな問題です。C# の Linq が python の2倍遅い、は嘘 - Qiita
  2. Span<T> に関しては用途が違うように思います。 Qiita の記事とは全く無関係ですが、 日本語で読める Span<T> の記事としては Span<T>構造体 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C がとても参考になります。
  3. 実際には、LINQ に限らず .NET のパフォーマンスの改善や新機能の開発に多くの優秀なエンジニアが取り組んでいます。また、明らかに遅かったり問題があり、改善方法があることが本当にわかっているなら、 issue をあげたり、プルリクエストを送ってみたりする手段は誰にでも開かれています。 GitHub - dotnet/corefx: This repo contains the .NET Core foundational libraries, called CoreFX. It includes classes for collections, file systems, console, XML, async and many others. We welcome contributions.

BenchmarkDotnet を使って LINQ の部分のベンチマークをとってみたところ、標準の LINQ メソッドだけで 3 倍以上高速になりました。

SlowLinq が Qiita の元記事、 NormalLinq がそれを書き直したもの、 UseGroupSum が GroupBy を使わないものです。

Method Mean Error Gen 0 Gen 1 Gen 2 Allocated
SlowLinq 321.59 ms NA 12400.0000 6200.0000 800.0000 78916.81 KB
NormalLinq 85.77 ms NA 1400.0000 600.0000 - 8415.32 KB
UseGroupSum 62.12 ms NA - - - 147.43 KB