Engineering Managerとしての技術ブランディングへの取り組み方 [実践編]

本記事はEngineering Manager Advent Calendar 2021の最終日、25日目の記事です。

あなたは誰?

READYFOR株式会社でVP of Engineeringをしております、いとひろ( itohiro73 )と申します。2021年は副業としてプロのコーチとしての活動も始めたので、もし本記事を読んで(?)この人にコーチングを受けてみたいなと感じた方がいたら、お気軽にツイッターからDMをください。コーチングはエンジニアの方でもそうでない方でも受け付けており、無料体験から始めていただけます。ちなみに本記事はコーチングとは全く関係がありませんw

本エントリーの趣旨

実は、全く同じ題名のエントリーを2年前の2019年のアドベントカレンダーで公開しておりました。この内容自体は【理論編】としての立て付けで自分自身で見返してもなかなかよく書けているなと感じているので、本記事を読む前にまずはぜひ一通り目を通していただけると幸いです。

itohiro73.hatenablog.com

さて、上記で言いたかったことは、こちらの文章に集約されています。

この記事で本質的に伝えたかったことは。 技術ブランディングを醸成するための本質的なプロセスは、Engineering Managerが日々対峙している課題解決の積み重ねと同義である ということです。「サービスの課題」であったり「エンジニア組織の課題」であったり「技術的な課題」であったり。それらの課題を解決していくプロセスの中で、実際に解決していった「実績」を「マーケティング」「広告」「PR」していくことで、自然と自社の技術ブランディングは醸成していく。そう私は信じています。

実はこの2年前に記事を公開した少し前にREADYFORでTech Brandingチームと称して技術ブランディングチームを立ち上げたのですが、そこから2年経っての成果が出てきたので、今回は【実践編】として紹介できればと考えています。

まずは成果から

そもそもこの記事を読む価値があるのか、それを感じていただくために、実際に出ている定量的・定性的な成果からご紹介させてもらえればと思います。

(この成果のセクション、思いのほか長くなってしまったのでザーッとスクロールしてもらって「 技術ブランディング醸成、実践編」まで一気に流し見してもらった方が良いかもしれません。 「成果出てるんだね」と感じてもらうのが趣旨なので)

2019年12月時点での状態確認

2019年の時点では、READYFORはこれからエンジニアリングの力でプロダクトを伸ばしていきたい、とエンジニアリングに注力し始めた頃です。この時点ではまだブランディグも確立されておらず、READYFOR=エンジニアリングという認知は社内でも社外でもまだ醸成されていない頃です。

  • エンジニア正社員数: 10人
  • 技術記事アウトプット: 0記事
  • イベント開催: 0回
  • メディア取材: 0回

2021年12月時点でのアウトカム・アウトプット

2021年1年間の定量的な成果としては以下になります。

  • エンジニアリング本部 正社員数: 27人(TPM/デザイナー含む)
  • 技術記事アウトプット: 48記事
  • イベント開催・登壇・その他アクティビティ: 14回
  • メディア取材: 12回

採用活動の成果

2019年10月に私が入社した時点では私含めてエンジニア正社員がまだ10人でした。2年3ヶ月経った2021年12月現在、エンジニア組織の正社員(TPM/デザイナー含む)が27人となっており、2.7倍の成長を遂げています。

アドベントカレンダーLGTM数

2021年Qiitaアドベントカレンダー企業・学校・団体の部 LGTM数ランキングにて、4位以下を大きく離して第3位! そして、Qiitaアドベントカレンダー全体でも4位の成績です。

このLGTM数はQiitaで書かれた記事のみがカウントされており、今回半分弱ほどの記事が自社のはてなブログや個人ブログ上で書かれていてLGTMカウントされていないことを鑑みると、かなりの成績といって良いのではないでしょうか。

f:id:itohiro73:20211225030653p:plain

READYFORのエンジニアリングに対してのポジティブな形での言及

READYFORで技術広報活動のアウトカムとしては、実際にポジティブなフィードバックをいただいており、客観的な評価として認識してよいかなと考えています。

例えば2021年1月開催の「【READYFOR】実践!フロントエンド分離戦略」ではものすごいポジティブな反響が多かったです。このイベントはREADYFORで初の技術イベント開催だったわけなのですが、オンライン開催で最大240人を超える方が視聴してくださいました。

togetter.com

また、READYFORでは今年からドメイン駆動設計を活用してのコンテキストマップ作成・モデリング活動・変更容易性向上・エンジニアの設計力向上・技術的負債の解消に取り組み始めています。

その活動の一つであるDDD社内勉強会の入門編をYouTubeで公開したところ1000回を超える再生数を叩き出しました。

www.youtube.com

ここでもポジティブな反響が多く見られました。

また、READYFORでのDDDに関する発信を好意的に取り上げてくださるブログや記事が上がったりもしました。

shinkufencer.hateblo.jp

zenn.dev

技術記事アウトプット(48記事)

見ていただけるとわかりますが、単に記事を量産しているというわけではなく、質の高いアウトプットになっているという自負があります。 tech.readyfor.jp qiita.com

イベント開催・登壇等(14回)

メディア取材(12回)

技術ブランディング醸成、実践編

さて、成果をリストアップするだけでもかなりの量になってしまいましたが、いかがでしょうか。たった2年間で、ほぼ技術的な強みを持っていない組織からここまで強みを表出し認識してもらえるエンジニア組織までどうやって成長してきたのか、気になりませんか?採用にもインパクトがでているので実質は半年くらいから成果は出てきています。技術的なブランディング活動と認知を形成するまでに実際に実践してきたことをここからはご紹介します。

まずやったこと

まず、一番最初にやった大切なことは「技術ブランディング」が目指す状態を明確化することでした。

READYFORでは、Tech Brandingチーム立ち上げの際に明確なビジョン・ミッションを設定しました。

f:id:itohiro73:20211225195138p:plain
Tech Branding Vision/MIssion

このビジョンに近づくために、ミッションに則さないことはやらないという意思決定をする。これがとても大事だと考えました。

ミッションにある「内外にとって」というのはとても大切です。社外の人を魅了するために、実態に則さないことをブランディングするのは全く意味がありません。課題であることは課題として認識し、改善していくことで、その改善プロセスが強みへと昇華していく。強みは強みとして魅力として発信することで、社内のエンジニアにとっても、社外のエンジニアにとっても、魅力的な場となっていく。その地道な積み重ねがとても大切なので、一朝一夕で発信さえすればブランディングが醸成されると考えては本末転倒です。

実際、2019年のTech Brandingチーム立ち上げ直前である11月頃、「エンジニアmeet-upを開催しよう」というアイデアが立ち上がり動き始めていましたが、まさに冒頭の記事で書いたような理由で一時延期としました。

「こうありたい」という想いはあるが、まだそこに達していない領域のブランディング

世の中で技術ブランディングに困っている方たちはこの領域が広いのでは無いかと想像します。だからこそ困っているのでしょう。

「こうありたい」という想いがあるということは、最終的にそこをブランディングできるようになっていきたいということは間違い無いと思います。しかし、まだそこに達していない状態で「マーケティング」「広告」「PR」をしてしまうと、中身が伴わないブランディング活動になってしまいます。この時点でむやみにコミュニティ登壇やブログ記事、OSSなど出しても、無駄死にするだけです。

実際、当時はまだREADYFORは技術的な強みと言えるものは特に言語化されておらず、実際に内情としては技術的負債も多く積み重なり、フロントエンドとバックエンドも分離されておらず密結合な状態かつ体制も整備されておらず、meet-upを開催したところで何も発信することも得ることもできなかったでしょう。

さて、先の記事では「こうありたい」という理想と見比べた時に「現在の状態」を言語化してみましょうと言っていました。

ここでEMの出番です。Engineering Managerの諸氏におかれましては、「こうありたい」という理想と見比べた時の「現在の状態」を言語化してみましょう。そこで言語化されるのは「サービスの課題」かもしれませんし、「エンジニア組織の課題」かもしれませんし、「技術的な課題」かもしれません。

ここからEMが主導する課題解決・技術ブランディングの基本的な型が導き出されます。

  • 理想と現状の差分を言語化する
  • その差分を解消していくための目標設定をする
  • 設定した目標を達成するための体制をつくり実践する
  • 目標が達成できたらそれを自社の強み・成果として発信する 

では、実際にREADYFORで行ってきた課題発見・目標設定・課題解決と技術ブランディングの事例を見ていきましょう。

体制の整備: 職能型組織からスクワッド型組織へ

READYFORでは、2019年の初め頃よりチームや個人の目標設定にOKRを活用していました。

2019年時点のREADYFORではエンジニア10名、ようやくフロントエンドチームとバックエンドチームが分かれ始めたところでした。しかし、これからプロダクト開発にアクセルを踏んでいこうというところでしたが、プロダクト開発のミッションと各チームのOKRとの接続があまりできていないことに気づきました。これは、5人規模のワンチームだったエンジニアチームが10人規模に成長したことでフロントエンドチームとバックエンドチームに分かれてきたことから生じ始めた課題でした。

f:id:itohiro73:20211225202704p:plain
5人規模から10人規模へのエンジニア組織の変遷によるOKRのひずみ

はい、理想と現実の乖離がここで現れましたね。

  • 理想: プロダクト開発として追うべきミッションを最短で達成するために、チームで一貫した目標設定をしたい
  • 現実: 開発のミッションと各チームのOKRがバラバラで歪みのある状態になってしまっている

ここからはEMとしての課題解決ターン、CTOやPdMを巻き込み、現在READYFORが採用しているスクワッド体制の原型となるようなチーム構成の組み立てをしました。

f:id:itohiro73:20211225203129p:plain
スクワッド体制の導入によるミッションとOKRのアライン

これをベースにCTO @amachino が経営陣に対し全社的なスクワッド体制の導入を提案し、2020年から正式な体制制度として採用されたのが現行のREADYFORのスクワッド体制です。

f:id:itohiro73:20211225203848p:plain
2020年1月〜のスクワッド体制

こういった一連の課題解決は、READYFORが大切にしている「乳化」の概念とともに紹介させてもらいました。

tech.readyfor.jp

この時点でいくつかの課題解決の実績とともに、強みとしてのブランディング醸成が積み重なっています。

フロントエンドとバックエンドの分離

さて、体制がととのったので、もうプロダクト開発にアクセルを踏めるでしょうか?いやいや、そうは簡単にはいきません。 この頃は、まだREADYFORの全ての機能がRails上でモノリシックなサービスとして実装されており、フロントエンドもReact on RailsとしてRails上に密結合している状態でした。このままだと、今後エンジニアが増えていったとしても生産性が上がらない状態となってしまいます。はい、来ました、理想と現実の差分。

  • 理想: エンジニアが増えればフロントエンドエンジニアとバックエンドエンジニアが独立して生産性高い開発ができる
  • 現実: フロントエンドとバックエンドが密結合なモノリシックアーキテクチャーになっており、エンジニアが増えても生産性が上がらない

ここからの課題解決はEMが主導するターンですね。実際にプロダクトの優先事項として「クラウドファンディングのプロジェクト編集画面」の機能開発が計画されていたのですが、ここでフロントエンドの分離も合わせて推し進めることが中長期的なプロダクト全体の生産性底上げに寄与すると考え、実際に今ではいくつかの新画面はReact/TypeScript/Next.jsのフロントエンドとRailsのバックエンドが分離された状態で実装されています。

これはまさに冒頭の成果で紹介したポジティブな反響が多かった「フロントエンド分離戦略」がこのストーリーそのままです。実際の課題発見 => 目標設定 => 課題解決 => 成果の発信が一連の流れとして一貫しているからこそ、発信の内容が視聴者に刺さったんだと思います。

tech.readyfor.jp

複雑なドメインとの対峙

READYFORでは、サービスとして表出している画面構成から想像する以上に複雑なドメインを扱っています。

クラウドファンディングにおける「プロジェクト」という概念ひとつとっても、さまざまなコンテキストで扱われています。

f:id:itohiro73:20211225215404p:plain
クラウドファンディングの「プロジェクト」をめぐるさまざまなコンテキスト

このような複雑な概念が、ドメインに潜む深いモデルを適切に蒸留しないまま実装されてしまうとどうなるかというと、こうなりますね。

f:id:itohiro73:20211225215617p:plain
巨大なmodelコード

さあ、これは大きな課題です。理想と現実の差分を言語化してみるとこんな感じでしょうか。

  • 理想: 複雑なコアドメインが深く分析・モデリングされており、変更容易性の高い設計で実装され生産性高いコードになっている
  • 現実: 3700行に及ぶ巨大なモデルコード

この差分は大きいですね。既存のチームにはこれをできるリソースも専門性もまだない状態で、一筋縄ではいかなそうです。

EMとしてはどういった動きをすればよいでしょうか?

実際にOOC(Object Oriented Conference)というカンファレンスのランチスポンサーセッションでやったのは、これです。

f:id:itohiro73:20211225220036p:plain
仲間大募集

READYFORが実際に抱えている課題について公表し、その課題を一緒に解決してくれる仲間を募集したのです。詳しくは下の記事にまとめました。

tech.readyfor.jp

さて、ここからかくかくしかじかあってどうなったかというと、DDD界隈では名高い ミノ駆動 さんや t2kob さんにジョインいただきました。

tech.readyfor.jp

お二人にはREADYFORではまさにOOCで課題として公表した内容について取り組んで下さっています。実際にチャレンジングな課題をあけっぴろげに公表して、取り組みがいのある課題として認識してくださったからこそジョインしてくださったんだと思います(大感謝!!!)。

技術ブランディングの型をドッグフーディングしながら自社の強みを成長させる

さて、EMが取り組む技術ブランディングの型を再掲すると、こんな感じでしたね。

  • 理想と現状の差分を言語化する
  • その差分を解消していくための目標設定をする
  • 設定した目標を達成するための体制をつくり実践する
  • 目標が達成できたらそれを自社の強み・成果として発信する 

EMは普段から4半期や半期で目標設定しているはずなので、どうせなら1年単位で成果を振り返りつつ、翌年の展望も公表しちゃいます。

tech.readyfor.jp

そうすると翌年はEMはその展望に向かって目標設定・実践をどのみちするわけなので、年末に再度振り返ることでさらなるブランディング醸成につなげることができてしまうという仕組みです。

tech.readyfor.jp

冒頭の記事から引用したこれを再度思い出してください。

この記事で本質的に伝えたかったことは。 技術ブランディングを醸成するための本質的なプロセスは、Engineering Managerが日々対峙している課題解決の積み重ねと同義である ということです。「サービスの課題」であったり「エンジニア組織の課題」であったり「技術的な課題」であったり。それらの課題を解決していくプロセスの中で、実際に解決していった「実績」を「マーケティング」「広告」「PR」していくことで、自然と自社の技術ブランディングは醸成していく。そう私は信じています。

ここまで読んでいただいて、このプロセスの力強さを感じていただけたでしょうか?READYFORでは実際にこれらのことを地道に実践し続けた結果、前半にリストしたような成果を出すことができてきています。

また、実践編に細かくは書いていないですが、エンジニアたちが自発的に発信したくなるような環境づくり、場づくりもとても大切になってきます。このあたりはまた別の記事でいつか書いてみようかなと思います。

それでは長くなりましたが、Engineering Manager Advent Calendar 2021の25日目記事としては以上にしたいと思います。

長い記事を最後までご覧いただきありがとうございました!メリークリスマス!

Engineering Managerとしての技術ブランディングへの取り組み方

本記事はEngineering Manager Advent Calendar 2019の23日目の記事です。

自己紹介

2019年10月からREADYFORというクラウドファンディングの会社でVP of Engineeringを務めております、伊藤と申します。「いとひろ」と呼ばれることが多いです。2005年から12年ほど外資系金融の会社でソフトウェアエンジニアを務めたのち、FinTech系のスタートアップ2社を経て現職に就いております。

Engineering Managerと技術ブランディング

Engineering Manager(以降EM)が取り組むひとつの課題として、エンジニア組織をいかに成長させていくかという課題があると思います。そのためにも採用数を伸ばしていかないといけなかったり、人材流出を防ぐためにリテンション施策をとらないといけないと思います。その中のひとつの施策として技術ブランディングがあります。今回はこの技術ブランディングへのEMとしての取り組み方を記事にしたいと思います。

私と技術ブランディング

私はキャリアの時々でSoftware Developerだったり、Project Managerだったり、Technical Architectだったり、Head of Engineeringだったり、Senior Solution Architectだったり、エンジニアリング戦略だったり、VP of Engineeringだったりと、いろんなロールを経てきているのですが、ひとつ一貫しているのが、全ての会社で技術ブランディングにまつわる活動をしてきたことです。

1社目ではTechnology Marketing、2社目ではクリエータブランディング、3社目ではdev-branding、4社目(現職)ではTech Brandingという、それぞれ違う名前で技術ブランディング活動に関わってきています。

なぜEMが技術ブランディングに関わるの?それって採用チームや広報チームの仕事じゃないの?

なぜ自分が関わるかというと、働いている会社が大好きなので、エンジニアの皆にもその会社やその会社のエンジニアリングを大好きになってもらいたいという想いがあるからです。

技術ブランディングは、もちろん採用チームや広報チームも関わっていくべきものです。しかし、技術ブランディングの本質的なプロセスは、EMが重要な役割を占めると思っています(ここで言うEMは、CTOやVPoEを含む、エンジニア組織を引っ張っていくロールの人たちすべてを想定しています)。本記事ではここを主眼にお話しできればと思います。

そもそもブランディングとは

ブランディングと、マーケティング/広告/PRなどの施策の違いは下の図によく表されています。

f:id:itohiro73:20191223000913p:plain
出典:「意外と知らないマーケティングブランディングの違い」 Mao Kawashima, 2015, btrax https://blog.btrax.com/jp/marketingvsbranding/

マーケティング」や、「広告」や、「PR」は、自らもしくは外部の媒体を通じて、自分の魅力を伝えていく施策になります。対して「ブランディング」とは、他者から自らの魅力を認知してもらえた状態をつくる活動すべてを表しています。

では、単にマーケティングや広告やPRをしていけばブランディングは醸成されるのか?そうではないと思います。上記の絵の例で言えば、実際に「頭が良い」という中身や実績が伴って認知され、それらの信頼が積み重なって初めてブランディングは醸成されるものだと思います。

技術ブランディングを醸成するのに大切なポイント

では、企業が取り組む技術ブランディングとは、何をブランディングすればよいのでしょうか?それは言葉通り百者百様でしょう。「何を」ブランディングしていけばよいか理解するために、下記の様な質問にまずは答えてみると良いと思っています。ここでは主にWeb系のサービスを提供している会社の技術ブランディングを主眼と置いています。

  • 自社のサービスの強みはどこにあるのか、どうありたいのかがしっかり言語化されているか?
  • 上記の自社サービスの強みを実現するために、エンジニア組織全体としてのカルチャーの強みはどこにあるのか、もしくはどうありたいのかがしっかり言語化されているか?
  • 上記の自社サービスの強みを実現するために、細分化された技術領域で、自社の強みはどこにあるのか、もしくはどうありたいのかがしっかり言語化されているか?
    • プロダクトマネジメントの技術
    • UXデザインの技術
    • UIデザインの技術
    • モバイル技術
    • フロントエンド技術
    • バックエンド技術
    • インフラ・SRE技術
    • ドメイン知識をシステムへ落とし込む技術(要求分析、要件定義、仕様策定、設計、等々)
    • 開発プロセスの技術(アジャイルスクラム等々)
    • テスト、ソフトウェア品質向上の技術
    • セキュリティの技術
    • 等々

上記の質問にそれぞれ答えてみて、もし言語化がされていない場合は、まずは言語化を試みましょう。そこがスターティングポイントです。

次に、それぞれの質問に対して言語化を試みてみると、「自社サービス」「自社のエンジニアリング組織」「自社の技術領域」のそれぞれに対して、大まかに2つの領域が存在することが見えてくると思います

  • 明確に強みであると言える領域
  • 「こうありたい」という想いはあるが、まだそこに達していない領域

もちろん、会社によってこの2つの領域のグラデーションはさまざまで、前者が多い会社もあれば、まだまだ後者の方が多い会社もあるでしょう。

ではそれぞれどうブランディングしていけば良いか見ていきましょう。

明確に強みと言える領域のブランディング

明確に強みと言える領域では、ブランディングは比較的簡単です。明確に強みと言えると言うことは、ほぼ間違いなくその領域でなにかしらの実績を持っていることでしょう。その実績に対して、ひたすら上の「マーケティング」「広告」「PR」をくり返していけば良いのです。

中身は伴っているので、これらのブランディング施策は如実に効いてきます。臆することなく、技術コミュニティでの登壇、ブログ記事やnoteでの記事公開、採用イベントでのPR、OSSの公開等、どんどんやっていきましょう。中身が伴っているのであれば、強みや実績をひたすら売り出せば、自然と皆の信用・信頼は積み重なっていき、認知の拡大につながるでしょう。

本稿ではここに関しては深掘りしません。すでにつよつよなのであればどんどん認知拡大してくださいwというしかありません。もし題名を見てこれらの方法論について知りたいと思っていた方は、期待外れになってしまい申し訳ありません。

「こうありたい」という想いはあるが、まだそこに達していない領域のブランディング

世の中で技術ブランディングに困っている方たちはこの領域が広いのでは無いかと想像します。だからこそ困っているのでしょう。

「こうありたい」という想いがあるということは、最終的にそこをブランディングできるようになっていきたいということは間違い無いと思います。しかし、まだそこに達していない状態で「マーケティング」「広告」「PR」をしてしまうと、中身が伴わないブランディング活動になってしまいます。この時点でむやみにコミュニティ登壇やブログ記事、OSSなど出しても、無駄死にするだけです。

では、どうすれば良いのでしょうか。

ここでEMの出番です。Engineering Managerの諸氏におかれましては、「こうありたい」という理想と見比べた時の「現在の状態」を言語化してみましょう。そこで言語化されるのは「サービスの課題」かもしれませんし、「エンジニア組織の課題」かもしれませんし、「技術的な課題」かもしれません。

「サービスの課題」「エンジニア組織の課題」「技術的な課題」。あれ、これって、どこかで聞いたことありませんか?そう、Engineering Managerの方々が普段解決しようとしている問題領域に他なりません。

つまり、これらの問題領域はEngineering Managerのみなさんが普段取り組んでいる業務そのものなのです。ということは、みなさん、1年なり半期なり4半期なり、目標設定しますよね?「こういう問題領域を課題と思っていて、われわれエンジニアチームはそこを解決するんだ」という目標設定。

で、ここからは逆説的なんですが、これらの課題を公表してしまいましょう。「自分たちはこうありたいと思っているんだけど、こう言う課題があります。でも、それを解決していきたいと思っています!」と高らかに宣言するのです。もし解決の道筋がついているのであればそれも一緒に公表しましょう。ここは「マーケティング」でも「広告」でも「PR」 でもありません。自分たちの課題を共有しているだけです。そうするとどうなるのか。

世の中のエンジニアの大半は、課題が解決したくてしょうがありません。そして、自分が強みを持っている課題解決、または自分がこれから伸ばしたいと思っている課題解決を見つけると、のどから手が出るほどやってみたくなります。

このアプローチの良い点は以下の2点です。

  • もし上記の様なエンジニアが課題に興味を持って、会社やエンジニア組織のビジョンにも興味を持ってジョインしてくれれば最強の戦力になります
  • どのみちEMが目標設定している課題領域なので、エンジニアチームは取り組む必要があります

どちらの道をたどったとしても、数ヶ月後には何かしらの実績を出していることでしょう。実績が出なかった場合はまた頑張ってください。実績が出た時点で、実際に解決した問題領域(サービスかもしれないし、組織かもしれないし、技術かもしれない)とその解決プロセスを「マーケティング」「広告」「PR」 してしまいましょう。この時点では「中身のある実績」となっているため、その点においてはコミュニティ登壇や、ブログ記事公表等々が、ブランディングとして効いてきます。エンジニアの多くは「〇〇という課題をこうやって解決した」という話が大好きですから。

さて、「こうありたい」という姿に一歩近づいたところで、再び立ち止まって振り返ってみましょう。「こうありたい」という理想と見比べた時の「現在の状態」を再度言語化し、「サービス」や「エンジニア組織」や「技術」の課題をあぶり出していきます。そこからはまたひたすら上記の繰り返しです。

このプロセスを繰り返していると、いつのまにか「こうありたい」という姿に限りなく近づいていて、それが「自社サービス」「自社のエンジニア組織」「自社の技術」の強みに変わっていることでしょう。そして、あなたは合間合間にこれらの強みを中身のある実績として「マーケティング」「広告」「PR」 してきているはずです。

この頃にはすでにあなたの目指していた技術ブランディングが完成し、皆が自社のサービスやエンジニア組織や技術領域の強みを認知してくれている状態をつくれているはずです。

そんなにうまくいくのだろうか?

いえ、現実はそんなにうまくいきません。ここまで読んでいただいて大変申し訳ないですが、上記はすべて私の仮説です。一部私のキャリアを通じて実証できた部分もありますが、いまだに最後の「こうありたいと言う姿にたどりついて技術ブランディングが完成した」という段階まで達したことはありません。

しかし、この記事で本質的に伝えたかったことは。 技術ブランディングを醸成するための本質的なプロセスは、Engineering Managerが日々対峙している課題解決の積み重ねと同義である ということです。「サービスの課題」であったり「エンジニア組織の課題」であったり「技術的な課題」であったり。それらの課題を解決していくプロセスの中で、実際に解決していった「実績」を「マーケティング」「広告」「PR」していくことで、自然と自社の技術ブランディングは醸成していく。そう私は信じています。

おまけ

READYFORでは、「誰もがやりたいことを実現できる世の中をつくる」というビジョンの元、「想いの乗ったお金の流れを増やす」ためのプラットフォームづくりに取り組んでいます。2019年1月の町野CTOのジョインから1年でエンジニア組織が2倍になるくらい急成長していますが、やりたいビジョンの大きさに対して、まだまだエンジニアが足りていません。

READYFORというクラウドファンディング のプラットフォームが稼働し始めてからすでに8年以上たっており、正直な話、レガシーかつモノリシックなつくりになってしまっています。それでもやりたいことの実現のため、スケーラブルなシステムへの変遷を進めつつ、サービス開発を推し進めていきたいと考えています。

課題を解決するのが大好きなあなた、下記のようなキーワードにもしちらっとでも興味を持った方は、ぜひTwitteritohiro73 までDM等でお気軽にご連絡ください!我々が抱えている課題についてざっくばらんにお話しさせていただきたいと思っています。

  • 密結合してしまっているフロントエンド(React)とバックエンド(Rails)の分離を推し進め、React/TypeScriptを用いて疎結合で凝縮度の高いフロントエンドコンポーネントを設計しつつデザインシステムの構築をリードしていきたいフロントエンドテックリード
  • 戦略的設計を用いた複雑なドメインユビキタス言語の発見や境界づけられたコンテクストの設計、型に守られていないRuby on Railsの複雑なモノリシックサービスの大規模リファクタリング等に取り組んだことのあるバックエンドテックリード
  • いわゆるSystem of Recordと呼ばれる、サービスのお金の流れが行き着く先の決済・会計領域の堅牢なシステムを構築したいバックエンドエンジニア
  • 言語化されていないサービス仕様を明確にし、リファクタリングに備えるためのテスト設計・実施を推し進めることが得意なQAエンジニア
  • 他、われこそはという腕利きのエンジニア

ではでは、本記事は以上とさせていただきます。

情報弱者を食い物にする情報商材に炎上はまったく痛手ではないということを知って欲しい話 #意地でも炎上させない情弱ビジネス

普段こう言った話題には言及しないのですが、ほんとうに悲しいし憤りを覚えているのでちゃんとした啓蒙活動をしたいと思い筆(キーボード)をとっています。

TL;DR

倫理観の欠如したような情報商材を扱う経営者やサービスの名前を絶対に 言及してはいけません。もう一回いいます、 絶対に言及してはいけません。炎上したら勝ちではなく、炎上したら負け です。もう一回いいます、 負け です。

f:id:itohiro73:20191021024332p:plain

何が起きた

一昨日(2019-10-19)あたりから、とあるプログラミングスクールとその経営者が炎上しています。

けっこうな勢いで燃えているので何のことかわかる方はすぐいると思いますし、それなりにネットリテラシーの高い人であればすぐ見つけられるでしょう。

今回はリテラシーの高い方に向けて書いているので、あえて細かい情報は出しません。このブログを読んで何のことかわからない方は、ご自身の検索能力を駆使して探してみてください。細かい情報(特にサービス名)を出してしまうと、情報弱者たる人たちの被害が増えてしまうので、絶対に言及してはいけないと考えます。このレベルの情報をもとに探せる人は 充分にリテラシー が高いので大丈夫でしょう(たぶん)。

問題は、 今回の問題の根底を理解できないレベルの情報弱者がサービス名をもとに当該スクールを認知し、発見し、まんまと登録してしまう ことなのです。

リテラシーの高い人は、周りもリテラシーの高い人たちに囲まれて生活している可能性が高いです。そうすると、世の中には相当な数の情報弱者がいる ということをほとんど認知できないため、今回のような炎上による結果が実際に思っているのとは全く違う方向に行くことがなかなか想像できません。今回はこの点についてなるべくわかりやすく紐解いていきたいです。

今回の流れをざっとまとめると

  • 著作権を有していない画像を無断でスクールのブログで使用
  • Twitter上で当該画像の著作権保有者によって無断使用が言及される
  • スクールの経営者が「そしたら、削除しときますね〜」とリプライ
  • その後、同経営者から著作権軽視の発言がなされる
  • さらに文章やソースコードの盗用も指摘される
  • 同経営者は、悪びれるそぶりも見せず、むしろ挑発的な発言を繰り返す
  • Togetterや、ブログに著作権盗用がまとめられ、炎上
  • その後、リプやリツイートの嵐で燃え続ける
  • GoodpatchのCEOにより言及され、当該スクール卒業者の採用をしない旨のツイートがなされる
  • 同経営者は厚生労働省の公正な採用選考に関わるリンクに言及し、Goodpatchの倫理観に関して反論
  • 同経営者がQiitaに著作権侵害騒動についてまとめる

典型的炎上案件です。今回とにかく目立ったのが、当該経営者が倫理的に問題のある発言を繰り返し、さらに挑発的な態度で炎上をあおったことでした。なぜこのような態度をしたのでしょう。普通の人は理解に苦しみます。だって、信用失うじゃないですか?実際、今回の炎上で当該スクールには絶対近づきたくないと思った方が非常に多いと思います。

しかし、そのような感覚とはうらはらに、炎上してしまったことで当該スクールはより登録者を増やし、ビジネスを拡大していく可能性が高くなってしまったと私は考えます。

炎上したので、このスクールはもうすぐ経営難に陥るんじゃないの?

残念ながらそうはならないでしょう。むしろこのままではこれからさらに売り上げを伸ばしていく可能性が高いです。

もうね、ほんと今回腹立たしいのは、炎上元の本人がしっかり種明かししてるんですよ。それなのに燃えてしまっているのが本当にくやしい。本人のツイートにこう書いてあります

マイナスイメージを持つのはそもそもそういう類に元からマイナスイメージ持ってる人だけなんだよね。 そこをうちはターゲットにすらしてないから何ら問題がない。 そして面白いもんで認知の数の方が多いんだよ。

どういうことなの?マーケットのセグメントから理解しよう

説明を単純にするために、マーケットを単純化して、2つのセグメントしか存在しないと仮定します。情報リテラシーの高い人たちのセグメントと、情報リテラシーの低い人たちのセグメントです。

今回の騒動で憤っているほとんど人たちは、情報リテラシーの高いセグメントに属しています。そして、この層の人たちは、今回の騒動を炎上させることによって、当該スクールの法的問題点や倫理的問題点をつまびらかにしたいと考えます。そうすることによって、マーケットに存在する皆が当該スクールの問題点を認識し、当該スクールがどんどん追い詰められていくと想像します。

では本当にそうなるのでしょうか。実際は、情報は拡散されればされるほど、多様な層に拡散されていきます。つまり、情報リテラシーの高いセグメント、情報リテラシーの低いセグメント両方に拡散されていきます。では拡散された情報は同様に理解されていくのでしょうか?残念ながら違います。情報リテラシーの高いセグメントに属する人たちは、もともと拡散した人たちと同様に憤ります。しかし、情報リテラシーの低いセグメントに属する人たちは、そもそも問題の論点がよくわかりません。著作権とか倫理観とかあまりピンとこないけど、ただただ〇〇というスクールのサービス名が目に入ってきます。

当該スクールのLPに訪れると、怪しげな文言が踊っています。しかし、情報リテラシーの低い人たちの一部には、これらの文言がどうも魅力的に映り、高い値段にも関わらずなんだか登録したくなってしまいます。このようなセグメントは人口に対して一定の割合存在しています。そして、このセグメントは、残念ながら今回の著作権侵害騒動に関して、そもそもの論点や倫理的問題点についてほとんどと言っていいほど理解ができない人たちです。拡散された情報から不幸にも当該サービスのLanding Pageにたどり着いてしまった人は、当該ページの文言に引き寄せられ、一定のコンバージョンレートで登録していってしまいます。そして、ここの登録数は、シンプルに 情報が拡散されればされるほど一定の割合で増えていってしまう のです。

リテラシーの人は正しい情報が拡散されればされるほど当該スクールに打撃を与えると信じて行動を起こすのだが、実際は逆のことが起きてしまうのです。

今回説明をわかりやすくするために高リテラシーセグメントと低リテラシーセグメントという2種類にざっくり分類しましたが、実際のマーケットはもっと複雑かと思います。ただ、炎上の力学と、情報弱者を対象とする情報商材のマーケットを理解するにはこの単純化が有効かと思います。

情報商材を扱う相手に自分と同じ価値観の土俵で戦うのは筋が悪すぎる

今回の炎上で憤りを露わにしているのは、明確にリテラシーの高い、エンジニアやビジネスマンとしての倫理感を持った、非常にまっとうな方々です。だからこそ、今回のような著作権を蔑ろにするような経営者の対応に怒りを覚え、なかには炎上させることで当該スクールの品質や倫理観に関する警鐘をならし、当該スクールの被害者を減らしたいと思った方も多いのではないでしょうか。

しかし、上で説明した通り、これは残念ながら全くの逆効果です。

当該経営者がプログラミングスクールを経営しているという事実から、おそらく多くのエンジニアや経営者、ビジネスマンが自分たちと同等の倫理観や正義感を前提に、相手がこれをしたら観念するだろうとか、これをやられたら痛いだろうと想像すると思います(自分もそうでした)。そういった意味でいろんな方が、法的問題点や、信用に関する問題、倫理的な問題を突っついていると思います。

しかし、当該経営者は、稼ぐこと以外まったく興味がないため、まったく意に介していない様子です。こちらも自分で種明かししている。

稼ぎ方が分かってないんだよね〜 単純なイラストやら画像なんてありふれててどこでも使い回されてんのに。 まぁ、だからお金が稼げないという。

通常であれば信用問題はビジネスの根幹を揺るがすのですが、当該スクールはそもそも信用が問題となるような取引はしていない模様

元々、取引しようと思っていたらこの方向性でブランディングしてないしね〜

そして、どうやら彼はもともとエンジニアではなく、売り上げをあげる系のプロフェッショナル。こうなるとほんと無敵です。技術的な倫理感を持たないことになんの躊躇もなく、とにかく稼ぐことだけを考えている。おそらく彼は今のビジネスに対しても別に愛着も何もないのでしょう。炎上して信用を失っても痛くも痒くもないのかなと想像します。もし元ビジネスで問題になるレベルで信用を失ってもまた別のドメインでビジネスを始めればいい。彼は情報弱者を食い物にして稼ぐ方法を理解しているので、ドメインがなんであろうと稼ぐことができてしまうんです。

さらに手強い事実

今回の騒動で特徴的だったのが、スクール内部の方々がこの経営者に対して好意的な態度を示していたことでした。おそらく、スクールの満足度自体は相当高いんだと想像します。そして、元ホストということで人心掌握術に長けている可能性が高いです。炎上しても内部からは信用を失わない自信もあるのかもしれません。

もう一点、今回私が注目したのは、GoodpatchのCEOが倫理観について言及した際に、それまでの悪者スタンス・煽りスタンスとは一転して、相手の倫理観を逆に攻める方向に転じたことです。これははたから見ると自分を棚に上げてなにいっとんねん案件なんですが、スクール在籍者に対して守りの姿勢を見せることで、内部の忠誠がズドーンと上がります。さらに、ちょっと高度な論戦を繰り広げることで、議論の内容がそこまでよく理解できない人たちに、〇〇スクールのトップはなんかすごい人と論戦して言い負かしてる(してないんだけど...)。やっぱりすごい人なんだ。という印象を植え付けることができます。

完全に、セグメントのターゲット(低リテラシー層)に対してのみブランディングを仕掛けているわけで、それ以外からはどう思われようが痛くも痒くもないんです... ここを理解しないと炎上の力学に負けてしまいます。

炎上すれば懲らしめられると思っているあなた、大間違いです

世の中のリテラシーの高い方のほとんどは、信用を糧にしてビジネスをおこなっていますし、また、プログラミングを武器にして活躍するエンジニアの多くは技術倫理にとても敏感です。だからこそ、信用や倫理感に反することに対し、過敏に反応してしまいます。そして、自分の土壌で戦えば懲らしめられると思ってしまうかもしれませんが、現状はどうも分が悪そうです...

情報弱者の被害者を減らすためにも、サービス名や経営者の名前を公表して啓蒙活動をしなければいけないのでは?

残酷な話かもしれませんが、これはまずもってうまくいかないことを歴史が証明しています。高知県在住のとあるインフルエンサーでこれまで散々炎上してきている方がいらっしゃいます。最近もこの方の、自明なレベルで費用対効果が低いであろう高額のオンラインサロンに関し、登録する人が後をたたないことをご存知でしょうか?こちらもリテラシーの高い方であれば少し調べればわかるのであえて言及しませんが、そういうことです。

どうしたらよいねん?

とにかくこういったケースでは情報を拡散しない、情報に言及しないのが最善の策だと思ってます。

ただこれ、情報感度の高い人たちの間で問題の存在を共有できないという問題がありまして... なんとかうまい方法ないかなと考えてるんですが、私の拙いクリエイティビティでは #意地でも炎上させない情弱ビジネス みたいなタグをつけて共有するくらいしか思いつかない...

レギュレーションとしては、

  • このタグを見たら絶対に固有名詞に言及しない
  • 問題点や事実だけを共有し、リテラシーの高い人であればその固有名詞にたどり着けるようにする
  • 情弱ビジネスにまつわる固有名詞を拡散しないためだけのタグなので、私刑やイジメにつかっちゃダメよ

とかとか。クリエイティブな方々、ぜひ良いアイデアを思いついたらコメントやはてブで共有してくださいませんでしょうか!!!?

めっちゃリプとかリツイートとか言及とかしちゃった、どうしよう...

今からでも遅くありません、あなたが本当に正しい倫理観を持っているならば 全部消しましょう!!! もう一回いいます、全部消しましょう!!!

ほんとにそうなのかなー?

ぜったい炎上させた方が悪徳業者に痛手を与えられるんじゃないかと思っている方、このブログを読んだ後に当該経営者の発言をもう一回追ってみてください。うわっ、マジだーと思う発言がそこかしこで見受けられると思います。当該経営者は今回の騒動に全く意を介していませんし、むしろ喜んでいます。そして、それはビジネスにおける信用やコンプライアンスの重要性を理解していないからではなく、そこを主戦場にしていないだけなのです。

Eclipse Collections 10.0の新機能 - Part 3 #EclipseCollections

本記事はDonald Raab氏による New Features of Eclipse Collections 10.0 — Part 3 - Donald Raab - Medium を元にした日本語版の記事です。日本語でわかりやすい情報となるように適宜編集しており、日本語訳とは違って必ずしも元の文章を訳したものではありません(本人にrewriteの許可をもらっております)。原文を参考にしたい場合は上記リンクからどうぞ。

Eclipse Collections 10.0の新機能を用いたコード例を紹介していきます。 f:id:itohiro73:20190804234137p:plain

f:id:itohiro73:20190804234228p:plain
Eclipse Collections 10.0の新機能、最後の6個

サマリー

本記事では、Eclipse Collections 10.0がリリースされました #EclipseCollections - itohiro73’s blog で挙げた26個の新機能のうち、最後の6個を紹介します。Part 1では、最初の10個、Part 2では次の10個の新機能を例とともに紹介しました。

21. RichIterable.getAnyの実装

getAny メソッドは、順序の保証なくコレクションの最初の要素を返します。 このメソッドは、もともと RichIterable で提供されていて、順序保証がされていないコレクションで非推奨となった getFirst メソッドの代替となります。

@Test
public void getAny()
{
    Interval interval = Interval.fromTo(100, 1);
    Integer anyList =
            Lists.mutable.withAll(interval).getAny();
    Integer anySet =
            Sets.mutable.withAll(interval).getAny();
    Integer anyBag =
            Bags.mutable.withAll(interval).getAny();
    Integer anyStack =
            Stacks.mutable.withAll(interval).getAny();
    Assert.assertEquals(Integer.valueOf(100), anyList);
    Assert.assertEquals(Integer.valueOf(1), anySet);
    Assert.assertEquals(Integer.valueOf(1), anyBag);
    Assert.assertEquals(Integer.valueOf(1), anyStack);
}

22. プリミティブのハッシュデータ構造におけるresize/rehashメソッドの刷新と標準化

これは新機能というよりは改善ですが、全てのプリミティブのハッシュデータ構造に影響するので、ハイライトするに値するものかと思います。 変更の大部分はハッシュデータ構造の rehashAndGrow メソッドにあります。

バージョン9.2

private void rehashAndGrow()
{
    this.rehash(this.table.length << 1);
}

バージョン10.0

private void rehashAndGrow()
{
    int max = this.maxOccupiedWithData();
    int newCapacity = Math.max(max, smallestPowerOfTwoGreaterThan((this.occupiedWithData + 1) << 1));
    if (this.occupiedWithSentinels > 0 && (max >> 1) + (max >> 2) < this.occupiedWithData)
    {
        newCapacity <<= 1;
    }
    this.rehash(newCapacity);
}

23. Iterable<BoxedPrimitive>からPrimitiveStack/Bag/List/Setへ変換するファクトリメソッドの実装

Eclipse Collections 10.0以前では、ボックスされたプリミティブ型の Iterable をプリミティブコレクションに変換するには、まずは IterableCollection または Stream に変換する必要がありました。 Stream の場合は、 Collectors2 で提供されていたプリミティブCollectorでプリミティブのコレクションに変換する必要がありました。 バージョン10.0からは、以下のようなファクトリメソッドを用いて Iterable からプリミティブコレクションを直接生成することができます。 この機能は、全てのボックスされたプリミティブ型 Iterable から、全てのプリミティブのコレクション型に対応しています。

@Test
public void convertFromIterableToPrimitiveCollection()
{
    Iterable<Integer> iterable = Interval.oneTo(5);
    IntInterval intInterval = IntInterval.oneTo(5);
    MutableIntList mIntList = 
            IntLists.mutable.withAll(iterable);
    ImmutableIntList iIntList = 
            IntLists.immutable.withAll(iterable);
    Assert.assertEquals(intInterval, mIntList);
    Assert.assertEquals(intInterval, iIntList);
    MutableIntSet mIntSet = 
            IntSets.mutable.withAll(iterable);
    ImmutableIntSet iIntSet = 
            IntSets.immutable.withAll(iterable);
    Assert.assertEquals(intInterval.toSet(), mIntSet);
    Assert.assertEquals(intInterval.toSet(), iIntSet);
    MutableIntBag mIntBag = 
            IntBags.mutable.withAll(iterable);
    ImmutableIntBag iIntBag = 
            IntBags.immutable.withAll(iterable);
    Assert.assertEquals(intInterval.toBag(), mIntBag);
    Assert.assertEquals(intInterval.toBag(), iIntBag);
}

24. Multimapsファクトリクラス内の'ImmutableSortedBagMultimapFactory`の実装

Multimaps クラスには、しばらく ImmutableSortedBagMultimapFactory が提供されていませんでした。バージョン10.0でこちらの機能がついに追加されましたが、名前付けに問題があり現在issueが挙げられています。

Variable name in Multimaps class for ImmutableSortedBagFactory is incorrect · Issue #737 · eclipse/eclipse-collections · GitHub

25. MapをパラメータにとるMapファクトリメソッドの実装

Map もしくは MapIterableMaps ファクトリメソッドにわたして MutableMap を生成することができるようになりました。

@Test
public void mapFactoryThatTakesMapAsParameter()
{
    MutableMap<Integer, Integer> mutableSource =
            Maps.mutable.with(1, 1, 2, 2, 3, 3);
    ImmutableMap<Integer, Integer> immutableSource =
            Maps.immutable.with(1, 1, 2, 2, 3, 3);
    Assert.assertEquals(mutableSource, immutableSource);
    MutableMap<Integer, Integer> mutableOf =
            Maps.mutable.ofMap(mutableSource);
    MutableMap<Integer, Integer> mutableWith =
            Maps.mutable.withMap(mutableSource);
    Assert.assertEquals(mutableSource, mutableOf);
    Assert.assertEquals(immutableSource, mutableWith);
    MutableMap<Integer, Integer> mutableOfMI =
            Maps.mutable.ofMapIterable(immutableSource);
    MutableMap<Integer, Integer> mutableWithMI =
            Maps.mutable.withMapIterable(immutableSource);
    Assert.assertEquals(immutableSource, mutableOfMI);
    Assert.assertEquals(immutableSource, mutableWithMI);
}

26. MultableMultimap.putAllPairs/addメソッドパラメーターへのワイルドカード型の導入

こちらはふたつの putAllPairs メソッドと、 add(Pair) メソッドのジェネリクスの変更になります。 Pair型に ? extendsワイルドカード型が足されました。

boolean add(Pair<? extends K, ? extends V> keyValuePair)
boolean putAllPairs(Pair<? extends K, ? extends V>... pairs)
boolean putAllPairs(Iterable<? extends Pair<? extends K, ? extends V>> pairs)

使用例

@Test
public void wildcardInMutableMultimapPutAllPairs()
{
    MutableListMultimap<CharSequence, CharSequence> multimap =
            Multimaps.mutable.list.empty();
    multimap.add(Tuples.pair("1-5", "1"));
    multimap.putAllPairs(
            Tuples.pair("1-5", "2"),
            Tuples.pair("1-5", "3"));
    multimap.putAllPairs(
            Lists.mutable.with(
                    Tuples.pair("1-5", "4"),
                    Tuples.pair("1-5", "5")));
    Multimap<String, String> expected =
            Interval.oneTo(5)
                    .collect(Object::toString)
                    .groupBy(e -> "1-5");
    Assert.assertEquals(expected, multimap);
}

以上です

以上でEclipse Collections 10.0の新機能をすべてご紹介しました。

Eclipse Collections 10.0の新機能、楽しんでいただけると幸いです!!

Eclipse Collectionsコントリビューションを受け付けています!どこから始めていいかよくわからないという方はitohiro73までDMいただければ喜んでガイドさせていただきます。

もしライブラリを気に入っていただけて、われわれに知らせたいという方は、Twitterハッシュタグ#EclipseCollectionsをつけてつぶやいていただいたり、もしくはGitHubレポにスターをつけていただけるとめっちゃよろこびますのでどうぞよろしくお願いします!!

Eclipse Collections 10.0の新機能 - Part 2 #EclipseCollections

本記事はDonald Raab氏による New Features of Eclipse Collections 10.0 — Part 2 - Donald Raab - Medium を元にした日本語版の記事です。日本語でわかりやすい情報となるように適宜編集しており、日本語訳とは違って必ずしも元の文章を訳したものではありません(本人にrewriteの許可をもらっております)。原文を参考にしたい場合は上記リンクからどうぞ。

Eclipse Collections 10.0の新機能を用いたコード例を紹介していきます。

f:id:itohiro73:20190804234649p:plain
Eclipse Collections 10.0の新機能10個

サマリー

本記事では、Eclipse Collections 10.0がリリースされました #EclipseCollections - itohiro73’s blog で挙げた26個の新機能のうち、さらに10個を紹介します。Part 1では、最初の10個の新機能を例とともに紹介しました。

11. Bag.collectWithOccurrencesの実装

このメソッドを用いると、Bag データ構造のなかの全てのユニークな要素とその個数を用いてコレクションを変換することができます。引数には要素とその個数を別のオブジェクトに変換する ObjectIntToObjectFunction を指定します。次の例では、要素とその個数を ObjectIntPairインスタンスに変換しています。

@Test
public void collectWithOccurences()
{
    MutableBag<String> source =
            Bags.mutable.with("1", "2", "2", "3", "3", "3");

    MutableBag<ObjectIntPair<String>> targetBag =
            source.collectWithOccurrences(PrimitiveTuples::pair);

    MutableBag<ObjectIntPair<String>> expected =
            Bags.mutable.with(
                    PrimitiveTuples.pair("1", 1),
                    PrimitiveTuples.pair("2", 2),
                    PrimitiveTuples.pair("3", 3));

    Assert.assertEquals(expected, targetBag);
}

12. プリミティブのIterableへのreducereduceIfEmptyの追加

例えば IntList の場合、この reduce メソッドは、longint を受け取り long を返す関数をコレクションの各要素に適用していきます。返り値をワイドニング変換することによって例えば sum のような関数でオーバーフローが起きるのを防ぐことができます。 空のコレクションの場合は NoSuchElementException が投げられるようになっています。

@Test
public void reducePrimitiveIterables()
{
    MutableIntList list =
            IntLists.mutable.with(1, 2, 3, 4, 5);

    long sum = list.reduce(Long::sum);

    Assert.assertEquals(15L, sum);
    Assert.assertEquals(list.sum(), sum);

    Verify.assertThrows(
            NoSuchElementException.class,
            ()-> IntLists.mutable.empty().reduce(Long::sum));
}

空コレクションの場合にも安全に対処したい場合には、 reduceIfEmpty メソッドを使ってデフォルトの返り値を指定することができます。

@Test
public void reduceIfEmptyPrimitiveIterables()
{
    MutableIntList list =
            IntLists.mutable.with(1, 2, 3, 4, 5);

    long sum = list.reduceIfEmpty(Long::sum, 0L);

    Assert.assertEquals(15L, sum);
    Assert.assertEquals(list.sum(), sum);

    Assert.assertEquals(0L,
            IntLists.mutable.empty()
                    .reduceIfEmpty(Long::sum, 0L));
}

13. プリミティブ用の<type1><type2>To<type1>Functionの追加

上の reduce メソッドの例では、IntIterablereduce メソッドは LongIntToLongFunction という新しいインターフェースを引数として受け取っていました。 リリース10.0からは、全てのプリミティブ型の組み合わせに対して2引数/1返り値の関数型インターフェースが用意されています。

@Test
public void newLongTypeToLongFunctions()
{
    MutableIntList intList =
            IntLists.mutable.with(1, 2, 3, 4, 5);

    LongIntToLongFunction sumFunction1 = Long::sum;
    long sum1 = intList.reduceIfEmpty(sumFunction1, 0L);

    Assert.assertEquals(15L, sum1);

    MutableByteList byteList =
            ByteLists.mutable.with((byte) 1, (byte) 2, (byte) 3);

    LongByteToLongFunction sumFunction2 = Long::sum;
    long sum2 = byteList.reduceIfEmpty(sumFunction2, 0L);

    Assert.assertEquals(6L, sum2);
}

14. プリミティブMapへのof/withInitialCapacityメソッドの追加

プリミティブMapのファクトリメソッドで、 ofInitialCapacity または withInitialCapacity を用いて初期サイズを指定することができるようになりました。

@Test
public void ofAndWithInitialCapacity()
{
    MutableIntIntMap map1 =
            IntIntMaps.mutable.ofInitialCapacity(100);
    
    MutableIntIntMap map2 =
            IntIntMaps.mutable.withInitialCapacity(100);
    
    Assert.assertEquals(map1, map2);
}

15. RichIterable.countByEachの実装

新しいメソッド countByEach は、 groupByEachflatCollect と似たものになります。これらは全てなんらかの関数型インターフェースを受け取り Iterable を返します。countByEach の場合の返り値の型は Bag になります。次の例では、3つのクラス(RichIterable.classMutableList.classImmutableList.class)から、全てのメソッドに対して同じ名前を持つメソッドを数えています。ここではオーバーロードやオーバーライドされているメソッドもカウントされます。

@Test
public void countByEach()
{
    MutableList<Class<?>> classes =
            Lists.mutable.with(
                    RichIterable.class,
                    MutableList.class,
                    ImmutableList.class);

    Bag<String> methodNames =
            classes.countByEach(each ->
                    ArrayAdapter.adapt(each.getMethods())
                            .collect(Method::getName));

    Assert.assertEquals(8, 
            methodNames.occurrencesOf("countByEach"));
    Assert.assertEquals(16, 
            methodNames.occurrencesOf("groupByEach"));
    Assert.assertEquals(2, 
            methodNames.occurrencesOf("sortThis"));
}

16. UnifiedSetWithHashingStrategy.addOrReplaceの実装

UnifiedSetWithHashingStrategyaddOrReplace メソッドは、集合内に同じ値が存在した場合に置き換えます。 これは、値を書き換えない add メソッドとは違う挙動をします。

@Test
public void addOrReplace()
{
    UnifiedSetWithHashingStrategy<Integer> set =
            new UnifiedSetWithHashingStrategy<>(
                    HashingStrategies.defaultStrategy());
    Integer one = new Integer(1);
    set.addOrReplace(one);
    Assert.assertSame(one, set.get(1));
    set.add(new Integer(1));
    Assert.assertSame(one, set.get(1));
    Integer otherOne = new Integer(1);
    set.addOrReplace(otherOne);
    Integer integer = set.get(otherOne);
    Assert.assertSame(otherOne, integer);
    Assert.assertNotSame(one, integer);
}

17. UnmodifiableMutableOrderedMapの実装

MutableOrderedMapasUnmodifiable メソッドを呼び出すと、 UnmodifiableMutableOrderedMap インスタンスが返されます。

@Test
public void unmodifiableMutableOrderedMap()
{
    MutableOrderedMap<Object, Object> map =
            OrderedMaps.adapt(new LinkedHashMap<>())
                    .asUnmodifiable();
    
    Verify.assertInstanceOf(
            UnmodifiableMutableOrderedMap.class, 
            map);
}

18. ミュータブルなプリミティブMap上でのwithAllKeyValuesの実装

ミュータブルなプリミティブMapに複数のキーバリューペアをまとめて追加することができるようになりました。

@Test
public void withAllKeyValues()
{
    MutableIntIntMap map =
            IntIntMaps.mutable.empty()
                    .withAllKeyValues(
                            Lists.mutable.with(
                                    PrimitiveTuples.pair(1, 1),
                                    PrimitiveTuples.pair(2, 2),
                                    PrimitiveTuples.pair(3, 3)
                            ))
                    .withAllKeyValues(
                            Lists.mutable.with(
                                    PrimitiveTuples.pair(4, 4),
                                    PrimitiveTuples.pair(5, 5)
                            ));

    MutableIntIntMap expected = IntIntMaps.mutable.empty()
            .withKeyValue(1, 1)
            .withKeyValue(2, 2)
            .withKeyValue(3, 3)
            .withKeyValue(4, 4)
            .withKeyValue(5, 5);

    Assert.assertEquals(expected, map);
}

19. IterableからPrimitivePrimitive/PrimitiveObject/ObjectPrimitiveMapを生成する機能の実装

Iterable に2つの関数型インターフェースを与えることによって、さまざまなプリミティブMapを生成することができるようになりました。1つ目の関数型インターフェースはキーを導出し、2つ目の関数型インターフェースは値を導出するのに使われます。このメソッドは、RichIterabletoMap と非常に似ています。違いは全てのプリミティブMapに対して動作するという点です。

@Test
public void createPrimitiveMapFromIterable()
{
    Iterable<Integer> integers = Interval.oneTo(5);
    MutableIntIntMap map =
            IntIntMaps.mutable.from(
                    integers,
                    key -> key,
                    value -> value);

    MutableIntIntMap expected = IntIntMaps.mutable.empty()
            .withKeyValue(1, 1)
            .withKeyValue(2, 2)
            .withKeyValue(3, 3)
            .withKeyValue(4, 4)
            .withKeyValue(5, 5);

    Assert.assertEquals(expected, map);
}

20. HashingStrategySets.of/withInitialCapacityの実装

HashingStrategySetsofInitialCapacity もしくは withInitialCapacity メソッドを用いて HashingStrategySet を生成することができるようになりました。

@Test
public void hashingStrategySetsOfOrWithInitialCapacity()
{
    MutableSet<Object> set1 =
            HashingStrategySets.mutable.ofInitialCapacity(
                    HashingStrategies.defaultStrategy(),
                    100);

    MutableSet<Object> set2 =
            HashingStrategySets.mutable.withInitialCapacity(
                    HashingStrategies.defaultStrategy(),
                    100);

    Assert.assertEquals(set1, set2);
}

まだまだあります

Eclipse Collections 10.0にはまだ6個の機能が残っています。これらも記事として公開しますのでお楽しみに。

Eclipse Collections 10.0の新機能、楽しんでいただけると幸いです!!

Eclipse Collectionsコントリビューションを受け付けています!どこから始めていいかよくわからないという方はitohiro73までDMいただければ喜んでガイドさせていただきます。

もしライブラリを気に入っていただけて、われわれに知らせたいという方は、Twitterハッシュタグ#EclipseCollectionsをつけてつぶやいていただいたり、もしくはGitHubレポにスターをつけていただけるとめっちゃよろこびますのでどうぞよろしくお願いします!!

Eclipse Collections 10.0の新機能 - Part 1 #EclipseCollections

本記事はDonald Raab氏による New Features of Eclipse Collections 10.0 — Part 1 - Donald Raab - Medium を元にした日本語版の記事です。日本語でわかりやすい情報となるように適宜編集しており、日本語訳とは違って必ずしも元の文章を訳したものではありません(本人にrewriteの許可をもらっております)。原文を参考にしたい場合は上記リンクからどうぞ。

Eclipse Collections 10.0の新機能を用いたコード例を紹介していきます。

f:id:itohiro73:20190804213414p:plain
Eclipse Collections 10.0の新機能10個

サマリー

本記事では、Eclipse Collections 10.0がリリースされました #EclipseCollections - itohiro73’s blog で挙げた26個の新機能のうち最初の10個を紹介します。

1. MultiReaderList/Bag/Set に特化したInterface

Eclipse Collectionsには、長らくmulti-reader対応のコレクション実装が存在していましたが、それらに対応するインターフェースは存在していませんでした。10.0以降ではこれらのインターフェースが利用できるようになっています。

f:id:itohiro73:20190804214204p:plain
MultiReaderList/Set/Bagインターフェース

@Test
public void multiReaderList()
{
    MultiReaderList<String> list = 
        Lists.multiReader.with("1", "2", "3");

    list.withWriteLockAndDelegate(backingList -> {
        Iterator<String> iterator = backingList.iterator();
        iterator.next();
        iterator.remove();
    });
    Assert.assertEquals(Lists.mutable.with("2", "3"), list);
}

2. プリミティブListからプリミティブStreamを生成する実装

普通のListからStreamを生成するのは簡単ですが、Eclipse Collections 9.xまではプリミティブListからStreamを生成するのは簡単ではありませんでした。10.0からは下記のようにプリミティブListからプリミティブStreamを簡単に生成できます。

@Test
public void primitiveListToPrimitiveStream()
{
    IntStream intStream1 =
            IntLists.mutable.with(1, 2, 3, 4, 5)
                    .primitiveStream();
    IntStream intStream2 =
            IntLists.immutable.with(1, 2, 3, 4, 5)
                    .primitiveStream();

    LongStream longStream1 =
            LongLists.mutable.with(1L, 2L, 3L, 4L, 5L)
                    .primitiveStream();
    LongStream longStream2 =
            LongLists.immutable.with(1L, 2L, 3L, 4L, 5L)
                    .primitiveStream();

    DoubleStream doubleStream1 =
            DoubleLists.mutable.with(1.0, 2.0, 3.0, 4.0, 5.0)
                    .primitiveStream();
    DoubleStream doubleStream2 =
            DoubleLists.immutable.with(1.0, 2.0, 3.0, 4.0, 5.0)
                    .primitiveStream();
}

3. toMapにおけるターゲットMapのサポート

toMapオーバーロードとして、ターゲットのMapを引数としてとるメソッドが追加されました。

@Test
public void toMapWithTarget()
{
    MutableList<Integer> list =
            Lists.mutable.with(1, 2, 3, 4, 5);

    Map<String, Integer> map =
            list.toMap(String::valueOf,
                       each -> each,
                       new LinkedHashMap<>());

    Map<String, Integer> expected = new LinkedHashMap<>();
    expected.put("1", 1);
    expected.put("2", 2);
    expected.put("3", 3);
    expected.put("4", 4);
    expected.put("5", 5);

    Assert.assertEquals(expected, map);
}

4. MutableMapIterable.removeAllKeysの実装

Mapに対して、あるSetに含まれる要素に対応するキーを削除する removeAllKeys メソッドが利用できます。

@Test
public void removeAllKeys()
{
    MutableMap<Integer, String> map = 
        Maps.mutable.with(1, "1", 2, "2", 3, "3");

    map.removeAllKeys(Sets.mutable.with(1, 3));

    Assert.assertEquals(Maps.mutable.with(2, "2"), map);
}

5 RichIterable.toBiMapの実装

Eclipse Collections 10.0では、RichIterableBiMapに変換することができます。

@Test
public void toBiMap()
{
    MutableBiMap<String, Integer> expected = 
      BiMaps.mutable.with("1", 1, "2", 2, "3", 3);

    MutableBiMap<String, Integer> biMap = 
      Lists.mutable.with(1, 2, 3).toBiMap(String::valueOf, i -> i);

    Assert.assertEquals(expected, biMap);
}

6 Multimap.collectKeyMultiValues の実装

Multimapに対して、keyとvalueの両方に対してfunctionを適用して変換できるcollectKeyMultiValuesメソッドが追加になりました。

@Test
public void collecKeyMultiValues()
{
    MutableListMultimap<String, String> multimap =
            Multimaps.mutable.list.with(
                    "nj", "Monmouth",
                    "nj", "Bergen",
                    "nj", "Union");

    MutableBagMultimap<String, String> transformed =
            multimap.collectKeyMultiValues(
                    String::toUpperCase, 
                    String::toUpperCase);

    Assert.assertEquals(Multimaps.mutable.bag.with(
            "NJ", "MONMOUTH",
            "NJ", "BERGEN",
            "NJ", "UNION"), transformed);
}

7 コレクションファクトリにおけるfromStream(Stream)の実装

ListSetBagStackのそれぞれのファクトリクラスにおいて、Streamからコレクションを生成するfromStreamメソッドが利用できるようになりました。

@Test
public void fromStreamOnCollectionFactories()
{
    MutableList<Integer> 
        list = Lists.mutable.fromStream(Stream.of(1, 2, 3, 4, 5));
    Assert.assertEquals(
        Lists.mutable.with(1, 2, 3, 4, 5), list);

    MutableSet<Integer> set = 
        Sets.mutable.fromStream(Stream.of(1, 2, 3, 4, 5));
    Assert.assertEquals(
        Sets.mutable.with(1, 2, 3, 4, 5), set);

    MutableBag<Integer> bag = 
        Bags.mutable.fromStream(Stream.of(1, 2, 3, 4, 5));
    Assert.assertEquals(
        Bags.mutable.with(1, 2, 3, 4, 5), bag);

    MutableStack<Integer> stack = 
        Stacks.mutable.fromStream(Stream.of(1, 2, 3, 4, 5));
    Assert.assertEquals(
        Stacks.mutable.with(1, 2, 3, 4, 5), stack);
}

8 LazyIterate.cartesianProductの実装

直積集合を求めるcartesianProductメソッドはもともとユーティリティメソッドとして存在していましたが、Setのみをパラメーターとしてとっていたため、他のIterableに対してはは明示的にSetに変換してから呼び出す必要がありました。今回導入された新しい LazyIterate.cartesianProductではどのようなIterableからも直積集合を求めることができます。

@Test
public void cartesianProduct()
{
    MutableList<Integer> numbers = Lists.mutable.with(1, 2, 3);
    MutableList<String> letters = Lists.mutable.with("A", "B", "C");

    MutableList<Pair<String, Integer>> pairs =
            LazyIterate.cartesianProduct(letters, numbers).toList();

    MutableList<Pair<String, Integer>> expected =
            Lists.mutable.with(
                    Tuples.pair("A", 1),
                    Tuples.pair("A", 2),
                    Tuples.pair("A", 3),
                    Tuples.pair("B", 1),
                    Tuples.pair("B", 2),
                    Tuples.pair("B", 3),
                    Tuples.pair("C", 1),
                    Tuples.pair("C", 2),
                    Tuples.pair("C", 3));

    Assert.assertEquals(expected, pairs);
}

9 プリミティブMapへのupdateValuesメソッドの追加

プリミティブMapに対して全てのvalueを更新したい場合、 updateValuesを使うことができます。

@Test
public void updateValues()
{
    MutableIntIntMap map = IntIntMaps.mutable.empty()
            .withKeyValue(1, 5)
            .withKeyValue(2, 3)
            .withKeyValue(3, 10);

    map.updateValues((k, v) -> v + 1);

    MutableIntIntMap expected = IntIntMaps.mutable.empty()
            .withKeyValue(1, 6)
            .withKeyValue(2, 4)
            .withKeyValue(3, 11);

    Assert.assertEquals(expected, map);
}

10 MutableMultimap.getIfAbsentPutAllの実装

MutableMultimap.getIfAbsentPutAllは、MutableMap.getIfAbsentPutと同様の機能を持ちます。違いとしては、MutableMultimap.getIfAbsentPutAllでは複数のvalueをコレクションとしてputすることができる点にあります。

@Test
public void getIfAbsentPutAll()
{
    MutableListMultimap<Integer, Integer> multimap =
            Multimaps.mutable.list.with(2, 1);

    ImmutableList<Integer> defaultValue = 
            Lists.immutable.with(1, 2, 3);
    MutableList<Integer> oneValue = 
            multimap.getIfAbsentPutAll(1, defaultValue);
    MutableList<Integer> twoValue = 
            multimap.getIfAbsentPutAll(2, defaultValue);

    Assert.assertEquals(defaultValue, oneValue);
    Assert.assertEquals(Lists.mutable.with(1), twoValue);
}

まだまだあります

Eclipse Collections 10.0にはまだ16個の機能が残っています。これらも記事として公開していきますのでお楽しみに。

Eclipse Collections 10.0の新機能、楽しんでいただけると幸いです!!

Eclipse Collectionsコントリビューションを受け付けています!どこから始めていいかよくわからないという方はitohiro73までDMいただければ喜んでガイドさせていただきます。

もしライブラリを気に入っていただけて、われわれに知らせたいという方は、Twitterハッシュタグ#EclipseCollectionsをつけてつぶやいていただいたり、もしくはGitHubレポにスターをつけていただけるとめっちゃよろこびますのでどうぞよろしくお願いします!!

Eclipse Collections 10.0がリリースされました #EclipseCollections

本記事はDonald Raab氏による Eclipse Collections 10.0 Released - Oracle Developers - Medium を元にした日本語版の記事です。日本語でわかりやすい情報となるように適宜編集しており、日本語訳とは違って必ずしも元の文章を訳したものではありません(本人にrewriteの許可をもらっております)。原文を参考にしたい場合は上記リンクからどうぞ。

f:id:itohiro73:20190804203215p:plain
The features you want, with the collections you need.

コントリビューターの皆さんありがとうございました

Eclipse Collections の10.0は2019年の7月にリリースされ、前回の9.2リリースから1年4ヶ月となるメジャーバージョンのリリースとなります。9.xバージョンのリリースは非常に機能が豊富で、コミュニティからたくさんのコントリビューションがあったんですが、10.0ではそれを超えるものになりました!なんと、10.0のリリースでは実に18人ものコントリビューターが機能追加をしてくれており、着実にOSSとしての成長を歩んでおります。貴重な時間を費やしてEclipse Collectionsの機能追加や品質改善にご協力いただいたコントリビューター皆さんに感謝の気持ちでいっぱいです!ありがとうございました!!

ひとつの記事にまとめるには機能が多すぎるw

Eclipse Collectionsの10.0は非常に多くの新機能が含まれており、ひとつの記事に収めるには多すぎるため、今後3つの記事に分けて機能の詳細を紹介していきます。本記事では、新機能のサマリーを紹介するにとどめます。

新機能のサマリー

  1. MultiReaderList/Bag/Set に特化したInterface
  2. プリミティブListからプリミティブStreamを生成する実装
  3. toMapにおけるターゲットMapのサポート
  4. MutableMapIterable.removeAllKeysの実装
  5. RichIterable.toBiMapの実装
  6. Multimap.collectKeyMultiValues の実装
  7. コレクションファクトリにおけるfromStream(Stream)の実装
  8. LazyIterate.cartesianProductの実装
  9. プリミティブMapへのupdateValuesメソッドの追加
  10. MutableMultimap.getIfAbsentPutAllの実装
  11. Bag.collectWithOccurrencesの実装
  12. プリミティブのIterableへのreducereduceIfEmptyの追加
  13. プリミティブ用の<type1><type2>To<type1>Functionの追加
  14. プリミティブMapへのof/withInitialCapacityメソッドの追加
  15. RichIterable.countByEachの実装
  16. UnifiedSetWithHashingStrategy.addOrReplaceの実装
  17. UnmodifiableMutableOrderedMapの実装
  18. ミュータブルなプリミティブMap上でのwithAllKeyValuesの実装
  19. IterableからPrimitivePrimitive/PrimitiveObject/ObjectPrimitiveMapを生成する機能の実装
  20. HashingStrategySets.of/withInitialCapacityの実装
  21. RichIterable.getAnyの実装
  22. プリミティブのハッシュデータ構造におけるresize/rehashメソッドの刷新と標準化
  23. Iterable<BoxedPrimitive>からPrimitiveStack/Bag/List/Setへ変換するファクトリメソッドの実装
  24. Multimapsファクトリクラス内の'ImmutableSortedBagMultimapFactory`の実装
  25. MapをパラメータにとるMapファクトリメソッドの実装
  26. MultableMultimap.putAllPairs/addメソッドパラメーターへのワイルドカード型の導入

新機能については 最新のJavaDocもご覧ください。

機能外での改善点

ライブラリ本体の機能追加以外にも、さまざまな改善がなされています。 1. テストカバレッジの改善 2. ビルドの改善 3. コードの重複の削減 4. 非推奨なクラスの削除 5. ジェネリクスの改善 6. 新しいベンチマークテストの追加 7. その他いろいろ!

ありがとうございます

すべてのコントリビューターとコミッターを代表して... Eclipse Collectionsを使っていただきありがとうございます!! 10.0リリースでの新しい機能と改善を楽しんでいただけると幸いです。

これら新機能の詳細は例をつけて追って記事化していきますのでお楽しみに!

Eclipse Collectionsコントリビューションを受け付けています!どこから始めていいかよくわからないという方はitohiro73までDMいただければ喜んでガイドさせていただきます。

もしライブラリを気に入っていただけて、われわれに知らせたいという方は、Twitterハッシュタグ#EclipseCollectionsをつけてつぶやいていただいたり、もしくはGitHubレポにスターをつけていただけるとめっちゃよろこびますのでどうぞよろしくお願いします!!