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

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

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レポにスターをつけていただけるとめっちゃよろこびますのでどうぞよろしくお願いします!!

FOLIOからfreeeに転職します

2019年1月20日付で株式会社FOLIOを退職し、2019年1月21日付でfreee株式会社に入社します。

f:id:itohiro73:20190121080854j:plain

FOLIOで何をしてきたか

FOLIOでは2017年4月にテックリードとしてジョインし、その後Head of Engineeringへと名称が変わり、その後病気による休職を経て復職し、シニアソリューションアーキテクトとして働いてきました。 たった1年8ヶ月ほどの在籍でしたが、非常に密度の濃い日々を過ごしてきた気がします。

今回はせっかくなので生々しい話も含めて在籍中の思い出を語ってみたいと思います。

FOLIOとの出会い

FOLIOを知ったのは2017年の2月頃、この記事を見つけたのがきっかけでした。

newspicks.com

前職の先輩であるbitFlyerのCEO加納さんのこのようなコメントをみて、プロダクトリリース前でのこの期待値はすごいな、面白そうだな、と思った記憶があります。

f:id:itohiro73:20190120165104p:plain

さっそくコーポレートサイトのトップページにあった「採用に興味がある方はこちら」のフォームから応募しました(意外に思われる方も多いかもしれませんが、立ち上げ期のスタートアップに入りたいのであれば、このやり方が一番手っ取り早いです。)。

面談ではCEOとCDO、そして全てのエンジニアたちと話をしました。とにかく皆優秀なエンジニアたちで、話をしていてほんとうに面白かった。話をしている中で、βリリースに向けての課題は山積みで、とくに証券会社の複雑なシステムを作り上げる上で、エンジニアリング上の課題を理解しつつビジネス側の要求を整理し、期待値を調整しつつ開発プロジェクトを推進していけるようなシニアな人材が当時おらず、自分がそこにフィットしそうだと感じました。

初日でほぼ気持ちはかたまり、2日目の面談で条件面をすり合わせ、オファーをいただきその場で承諾しました。

βリリースに向けた戦い

当時は2017年の5月8日がβリリース日として設定されていました。私の正式入社日が5月17日の予定だったのですが、正直そんな悠長なことを言っていられないくらい開発が切羽詰まっており、2017年の4月6日から有給消化期間を利用して開発のお手伝い(という名の怒涛のプロジェクトマネジメント)を始めます。

思い出深いのが4日6日の初出社日。とにかく現状把握のため、βリリースに関わるエンジニア全員と1 on 1をしました。そこでわかったことは惨憺たるものでした。

  • そもそもの機能要件からして未確定なものがある
  • インフラ人員がアプリケーション開発を手伝っており、肝心のインフラ整備が追いついていない
  • 開発スケジュールのつくりかたも積み上げ式の見積もりではなく、5月7日ありきの逆算型でつくられている
  • その他細々した問題が山積み

Webサービスとはいえ、お客様のお金を預かる証券会社のサービスです。たった1日のヒアリングでしたが、正直1ヶ月後にβリリースを控えた開発状況とはとうてい言えない肌感を得ました。もうこの時点で既にデスマーチの様相を呈しており、エンジニアたちも疲弊している状況でした。早速CEOをつかまえて1ヶ月後のβリリースは非常に危険な状態であることを伝えます。が、このとき自分のコミュニケーションの浅はかさに気づきます。

自分としては当然危険な状況であることが肌感としてわかったのでそれをそのままCEOに伝えたのですが、CEOにしてみればそのころはバーンレートも急激に上がってきた段階で、ビジネスプランのフィージビリティも投資家からガツガツつめられたりするような情勢の中、安易なスケジュール遅延は到底受け入れられません。初日に来たばかりの新参者のたかが肌感で、「1ヶ月後のリリースは無理だ、遅らせる必要がある」と言われても、はいそうですかと言えるわけがないのです。

私としてもこの時点では開発の責任者を請け負っているので、安易にデスマーチを受け入れるわけにはいきません。それこそ無理な開発スケジュールで開発を強行することによってビジネスが破綻してしまう可能性さえあるのです。初日からガチバトルです(健全なバトルだったと思います)。

とにもかくにも、CEOからは現状の精査をもう少し解像度の高い形でしてくれとの要請を得て、その日はおさまりました。

ここから怒涛の日々が始まります(*注:入社前)。

3日後には開発の現状をまとめたプレゼンを経営陣と開発PM陣にし、今の開発状況で技術的負債がどのような形で蓄積されているのか、このまま1ヶ月後のリリースを見据えて開発を続けた場合にどのようなネガティブインパクトが起こりうるのかを切々と語りました。

経営陣にはある程度納得してもらったところで、今度は10日後の取締役会(実態は投資家全員が参加する株主総会)で投資家たちにスケジュール変更の提案をするように要請されます。

さらに怒涛の日々を過ごし、見積もりを精緻化させ、取締役会に備えます(*注:入社前)。

取締役会では、開発現状のポジティブ面ネガティブ面の両面をうまく見せながら、積立方式の見積もりでゼロバッファーで見積もった場合に6月19日が最短のスケジュール(ただし達成確立は50%以下)、そこをマイルストーンとして置いて開発を進めると結果として2週間後の7月3日のリリースを目指すのがおおよそ70%ラインというお話をしました。

いろいろ突っ込まれるかとヒヤヒヤしていましたが、投資家達はおしなべて理解を示してくれて、このスケジュールで進めていこうという合意がなされました。

しかしながら、この時点では「絶対無理なデスマーチ」が、「普通のデスマーチ」に変化しただけなので、全く油断ができません。ここからもエンジニア達とともに開発にいそしむ怒涛の日々が始まります(*注:入社前)。

その後5月17日の入社日も無事迎えることができ、エンジニアたちとβリリースに向けて走り続けます。

結果としては、達成確立70%の見積もりラインを4日だけ超えて無事に7月7日にβリリースを迎えることができました。(せっかくのリリースなので、7月7日の七夕かつゾロ目がよいのではという判断なので実際はもうすくし早く開発は終えてました)。これに関しては、見積もりとほぼ違わずにリリースできたというサクセスストーリーでもあるのですが、無理な見積もりを強行せざるを得なかったというバッドストーリーでもあるので両面から語り継いでいきたい話ではあります。

https://story.folio-sec.com/n/n6576f573e5b6

非常に産みの苦しみの大きい最初のリリースでしたが、ただでは転ばないのかFOLIOのエンジニア達のすごいところ。チームとしてβリリース後も大小さまざまなプロジェクトを経験してきましたが、まっちゃらはそれらから学んだ見積もりの手法の重要性を理解し、下のような素晴らしい資料をつくって社内での勉強会を開いてくれたりしました。

matsu-chara.hatenablog.com

とにかくFOLIOのエンジニアたちはさまざまな困難を踏み抜いても、それを自分たちの血肉にしていく強さを持ち合わせていると感じます。とても貪欲でかつ知的好奇心に溢れる技術集団で、自分としても今後も見習っていきたいなと思います。

証券会社としての業務システム「PORT」という概念の導入

もともと証券会社でエンジニアとして12年間働いていた経験から、証券会社におけるバックオフィスのシステムの重要性は身にしみて理解していました。証券サービスを提供するにあたっては、口座開設のワークフロー、顧客や自己ポジションの管理、現金残高の管理、コンプライアンスによる売買審査等々、さまざまな証券業務を支えるバックオフィスシステムを開発・運用する必要があります。さらに、バックオフィスシステムのユーザーは社内の同僚であり、これらの業務システムを協力して大切に育てていきたい性質のものだという考えを持っていました。

バイモーダル戦略で言うところのSoE(System of Engagement)がFOLIOWebサービスやモバイルアプリに相当するシステムであり、SoR(System of Record)がこれらバックオフィスシステムに相当します。

FOLIOにジョインした当時は、リリース前でそもそも社内の証券業務部の人員も存在しない時期だったため、経営陣もクリエイター陣もとくにこの辺の肌感や重要性が共有されておらず、バックオフィスの業務システム自体が(意図せず)軽視されていました。開発見積もりにおいても単に「管理画面」と呼ばれ、非常にモチベーションの低い対応がなされていました。

このような状況を改善するためバックオフィスの業務システムの名付けからスタートすべき、と言う話をして(*注:入社前です)、「PORT」という名前が付いたと言う話を下のブログに書いています。 itohiro73.hatenablog.com

ここからさらに、シンプルにSoEとSoRの2分割では足りないよーといろいろ掘り下げてくれたのが現バックエンドエンジニアリードのむらみんです。詳しくは下のブログをご覧ください。

t-and-p.hatenablog.com

クリエイターブランディングチームの導入とScala人材採用へのインパク

私がFOLIOにジョインした2017年4月当時は、全社員が20人そこそこ、半分の10人ほどがクリエイター(デザイナー+エンジニア)と少人数で、証券会社をつくりあげていくにはとにかく人手が足りない状況でした。今後組織を拡大していくにあたって、目先の開発のみに注力するだけではなく採用を強化するためにも、クリエイターのブランディングを確立していくことは非常に重要だという認識を持っていました。デザイナーに関してはCDOである広野氏が当時でも十分すぎるくらいのブランディング力を発揮していましたが、当時はまだ確立していなかったFOLIOのエンジニアのブランディングも強化していきたい想いがありました。

フロントエンドエンジニア、バックエンドエンジニア、インフラエンジニア(現在はSREと呼んでいます)、アプリエンジニア等々さまざまな職能で組織を拡大していかなければいけない中で、一番採用に関してチャレンジングであった領域が、バックエンドを支えるScalaエンジニアでした。Scalaエンジニアはとにかく人材市場規模が小さく、まだ知名度のないFOLIOにとっては採用をしかけていくのが非常に大変だったのです。

当時モヤモヤと思い描いていたブランディング戦略としては、バックエンドエンジニアの何名かがJavaのコミュニティ内である程度の訴求力をもっていたため、Scalaユーザーも一定数いるJavaコミュニティでの認知を増やしていければいずれScalaコミュニティにも波及し、Scalaエンジニアの採用へとつながるのではないかという漠然とした考えを持っていました。

漠然とはしていましたが、持てる力はすべて出し尽くそうという考えのもと、入社直後からアクセル全開で攻めていきました。

とれる戦術の順序としては、『Javaコミュニティでの認知の拡大』→『技術的な実績づくり』→『Scalaコミュニティでのさらなる認知の拡大』です。

当時ほぼ同時期に入社したJava女子部部長であるよこなさんとともに、まずはバズりそうな入社エントリーをダブルで出稿します。

ihcomega.hatenadiary.com

itohiro73.hatenablog.com

この時点でJavaコミュニティに対しては、「おっ?FOLIOってなんだ?」という小さな認知の拡大をもたらせた気がしています。

この後のJJUG CCCで話したのが、技術的にも非常に面白いテーマであるテンポラルデータモデルとReladomoのお話。これが見事にバズります(407はてブ)。これは狙ってもなかなかできるものではないので、自分でもビックリしました。 b.hatena.ne.jp

そして、ここで奇跡が起きます。

なんとScalikeJDBCの開発者である瀬良さんが興味を持ってくださいました!

FOLIOでは証券会社として履歴にまつわる機能要件が非常に多く、もともとReladomoをScalaから使えるようなライブラリーをつくっていきたいと考えていました。しかし、上記のようにβリリースに向けての多忙に見舞われる中で、自分で開発に携わるのは相当厳しい情勢でした。そんな中、瀬良さんに手伝っていただければScalaのラッパーを書けるのでは!と盛り上がります。

速攻DMで連絡し、いろいろとお話をさせていただいたのちに業務委託として「ReladomoのScalaラッパーをOSSとして開発」していただくことになりました🎉

このころ、Java/Scalaだけにとどまらず、フロントやインフラ、アプリなどの様々なエンジニアリング領域、そしてクリエーター全体でのブランディングを底上げするために、「クリエイターブランディングチーム」を立ち上げます。メインのリードは前述のよこなさんです。ブランディングチームの戦略について、CEOを巻き込んで重要性を理解してもらうことにも注力しました。

人事部とも協力して、社内のエンジニアが働いている様子のWantedlyの記事化にも取り組みました。

そうこうしているうちに、秋口のScala関西Summitが近づいてきます。

ここで、Scala関西Summitの2日前にScalaエンジニアの紹介記事をぶちこみます。 www.wantedly.com

これが功を奏し、Scala関西Summitに参加するエンジニアたちにFOLIOの名前をほのかに認知させることができました。そして、Scala関西Summit当日、瀬良さんと共同でreladomo-scalaのセッションで登壇し、OSSのリアルタイム公開に踏み切ります。

www.wantedly.com

Reladomoの持つ技術的な面白さに相まって、OSSをリアルタイムで公開すると言う試みでこのセッションがいい感じにバズりました。

そして、なんと、ここでの懇親会で採用につながったのが、先述の、現在FOLIOでバックエンドのリードをつとめるむらみんです。

2017年のScala関西Summitで採用された彼が2018年の同イベントで登壇するという流れもできたので、非常にポジティブなブランディングサイクルになっています。

www.wantedly.com

さて、Scala関西Summitでの直接採用は一人だけですが、ブランディング施策のインパクトとしてはこれにとどまりません。

時系列でみてみると、私がジョインした2017年5月からScala関西Summitが開催された2017年9月までのバックエンドエンジニアの採用は0人なのですが、2017年9月から2018年8月までのバックエンドエンジニア採用実績はなんと9人リファラル等も含まれるので単純なブランディングだけのインパクトは計れないですが、間違いなく効いてきています。

最初は漠然とした戦略として始めたものが、様々な偶然も重なった上で実際にブランディングの結果として目に見える形で成果が現れたのは非常に感慨深いものがあります。

ここで紹介した施策は主に私が関わっていたScalaにまつわるものですが、それ以外の領域でもクリエイターブランディングチームはエンジニア達の対外活躍を支援するとりくみをしてくれています。例えばアドベントカレンダーFOLIO Advent Calendar 2017 - QiitaFOLIO Advent Calendar 2018 - Adventar)、証券会社が2年連続で完走するのは非常に珍しいと思います。今後もFOLIOの技術領域の動向をウォッチしていきたい次第です。

エンジニアの組織のブランディングを確立していく上で重要なのは、対外的な訴求とともに対内的な訴求も強めていくということだと思っています。つまり、外部の人への認知を拡大するための施策をとるだけでなく、社内のエンジニアたちも自分たちの持つ技術力や開発力に自信を持って、もっとこの環境で働きつづけたい、自分の知り合いにも勧めたいと思えるような、正のフィードバックループが働くブランディングをしていくことが重要だと思います。そのためにも、DX(Developer Experience)の改善や社内勉強会の充実、OSSや外部コミュニティへの貢献の促進等、環境を整えていくことががとても大切になってくると考えます。この辺は今後関わっていくであろう様々なエンジニアリング組織でも突き詰めていきたいところです。

なぜ辞めるのか

FOLIOのビジネスはものすごく面白く、意義のあるものだと思っています。日本の個人の金融資産が約1800兆円あるといわれており、そのうちの半分以上である約960兆円が現金・預金が占めています(参考)。これまで様々な有識者や金融機関が「貯蓄から投資へ」を声高に叫んできましたが、この個人金融資産の半分以上を占める現金預金比率はここ20年以上変わってきていません。

フォリオのようなワクワクする、楽しく投資をするサービスが広まることで、今まで投資を選択肢に入れてこなかったような大勢の人たちが投資をしはじめる。それによって今まで循環してこなかった何兆円もの資金が投資に流れ、結果として経済を活性化させるという世界観は、ほんとうにエキサイティングなものであり、証券会社にエンジニアとして長年勤めてきた身としては今後はこのビジネスにコミットし続けていくものだと考えていました。最初の会社では12年働いたので、漠然と次の12年はFOLIOへコミットし続けていきたいなと考えていました。しかし、世の中そううまくはいきませんでした。

ここから急に重い話題になります。

2017年の11月、様々な複合要因から「抑うつ状態」と診断され、会社を休職しました。ほんとうに様々なストレス要因がからみあって引き起こされた病気だったため、ひとつずつストレス要因を解決していく必要がありました。

そこからは地獄のような日々が始まります。

最初は12月には復帰できそうな情勢でいたのが、復帰直前に再発、その後最悪な産業医面談による病状の悪化(詳細は省く)、リハビリによる回復等の紆余曲折を経てなんとか回復し、2018年の4月にようやく復職します。

この頃には当初の要因となった問題はほとんど解決されていたのですが、ひとつだけ大きなストレス要因が解決できていませんでした。これに関しては、もはや自分で解決できる問題ではなかったため、カウンセリング等を通じてその問題に対する見方を変えるしか方法はありませんでした。

休職中はベッドから起き上がれないくらい体が動かないような日々もあったのですが、復職後は身体への影響はもはやほとんどなく、仕事や日常生活は普通にこなせる状況まで回復していました。はたから見たら普通となんら変わらないように見えたと思います。

しかし、上記のひとつだけ残っていたストレス要因への対処がまったくうまくできず、精神的にはまだヤバい状態が続いていました。

どれだけヤバかったかというと、2018年のおおよそ5月〜9月くらいのTwitterの下書きがこんな感じで残っています。

f:id:itohiro73:20190120170732j:plain
復職後にも精神的に苦しんでいる様子がTwitterの下書きから伺える

下書きにとどまっていて、実際にはツイートしていないのがポイントですね。これはつぶやいたらアカンという葛藤がみえる。

このような精神状態は、もはやカウンセリングでは全く改善されず、問題に対する見方も変わる気配がありませんでした。このころから環境を変えざるを得ないのではないかという考えが自分の中で大きく占め始めます。

とはいえ、とにかくFOLIOの目指す世界観、FOLIOの同僚たちも仕事も大好きだったので、どうにかしてこのままFOLIOでの仕事を続けらないか模索していました。自分の精神状態を悪化させている明確な要因はFOLIOの環境下に存在しており、これは私にとって非常にセンシティブな問題なので公表できないのですが、取締役を含む数人にのみ共有していました。会社は私の抱えている問題に対して真摯に対応してくれており、相当な時間をかけて解決に向けて協力してくれていました。

しかしながら、このように周りの支えもあったのですが、残念ながら復職から半年経ってもどうにも改善できない状況を鑑みて、退職を決意せざるをえませんでした。

自分としてはあまりにもつらい決断で、最初に退職の申し出をした際も、社長の部屋でわんわん泣いてしまいました。実はこの際に一度強く引きとめられており、一旦保留としていたのですが、数週間ほどぐるぐると終わりのない考えを繰り返し、それでもにっちもさっちもいかない状態になり、やはり最終的に退職を決断した、という流れになります。

【2019年1月30日追記】 ここに書かれている事は、私個人に特有の事情によるところが大きく、FOLIO自体には何ら問題はありません。むしろ問題は私の内面の方にあると言えます。FOLIOはとても良い会社で、前述のようにとても面白いビジネスモデルを持ってます。同僚も優秀で働いていて楽しいですし、特にエンジニアにとっては、複雑なドメインと技術領域にチャレンジできるとても面白い職場だと思います。私も自分の抱えている問題が解決できたらいつか戻りたいとも思える素敵な場所なので、そこのところは勘案しつつ読んでいただけると幸いです。

大丈夫なの?

現在はFOLIOでの最終出社日から約1ヶ月経っており、ストレス要因からは離れてゆっくり休むことができました。

今となってはそれまでの精神的な苦悩がほんとうに嘘かのように消え去っており、現在は毎日を晴れ晴れしい気分で過ごしています。新しい職場での新しい仕事に対しても、ものすごくポジティブなワクワク感を持っていて、環境を変えるということのインパクトの大きさを身にしみて感じております。

freeeとの出会いとこれから何をするのか

実はfreeeとの出会いは約4年半前にさかのぼります。当時ちょっと外の世界をのぞきにいっていた時期があって、Wantedlyを通じて当時まだ2,30人くらいの規模感だったfreeeのオフィス(当時は麻布十番と赤羽橋の間あたりにありました)に遊びに行かせてもらいました。CEOの佐々木さんとは中小企業のバックオフィスを強化したいというビジネスの話で盛り上がり、その後お話を聞かせていただいたCTOの横路さんとは技術的な話でもりあがったものの、「伊藤さんがフィットしそうなのはもうちょっとfreeeが成長して組織的な課題が出てきた頃な気がしますね」というお言葉をいただき、自分もそんな気がするなーという感覚をもってその場はお開きになった覚えがあります。

その4年後である2018年、Wantedly経由で再度お声がかかり、久しぶりにキャッチアップをしませんかというメッセージがとどきました。五反田のオフィスに遊びに行かせていただいて、その際は基盤エンジニアの求人でお話しさせてもらったんですが、面白そうなポジションではあったものの特にお互いピンと来る感じではなく、またkeep in touchしましょうねという流れになりました。しかし、その1ヶ月後くらいに「freeeの新規部署・組織開発チームの担当者を募集!」という別のポジションで求人が出されているのをネットでたまたま見つけました。これは非常に面白そうと思い、再度何人かの経営陣やエンジニアとお話しさせていただいて、エンジニア組織が拡大していく中でのさまざまな課題感を聞かせてもらいました。なんたる偶然か、まさに組織が成長したことによる課題が4年越しでいろいろと顕在化してきたところで自分が色々と貢献できそうな仕事です。こちらは順調に選考が進み、無事内定をいただくことができました。

freeeで取り組むのは「開発組織企画」という新しいチームで、エンジニアの組織的な課題を解決していくポジションになります。全く新しいポジションなので裁量をもっていろいろおもしろい取り組みができそうで、今からワクワクしております。

自分はかねてから、自分自身が普通の人の1.5倍のパフォーマンスを出すよりも、チームの皆がそれぞれ1.2倍のパフォーマンスを出せるような環境を整備してあげることで、10人の組織であれば12人分、100人の組織であれば120人分、といったスケーラブルな形で大きなインパクトをもたらす仕事に興味をもっているので、このポジションは非常に面白いことができるのではないかと思っています。

FOLIOでも取り組んでいたようなエンジニアのブランディング活動も、組織を拡大して強くするために必要な施策だと思うので、freeeでも同様な取り組みを牽引していければと思っています。

今後のもろもろに、ご期待いただければと思います。

Wish List

ほしい物リスト、置いておきますね🙏

www.amazon.co.jp

Scala関西Summitでブロックチェーンについて話してきました #scala_ks

昨年に引き続き、Scala関西Summitに参加してきました。

今年もFOLIOがスポンサーしており、自分含め二人で登壇!

自分のセッションではScalaで簡易ブロックチェーンを実装してみる話をしました。ブロックチェーンの基本的な部分の解説と、その実装例をライブコーディングを交えて紹介しました。

高嶋さんによるうれしいフィードバックもいただき、Day2の打ち上げの際もご本人から「Scala以外の技術的領域に興味を広げられてよかったです」といううれしいコメントをいただきました。感謝。

Day1の終盤では去年のScala関西Summitで引き抜いた(?) id:mura-_-miFOLIOスポンサーセッションとして登壇していたので、巣を立つヒナをみる親鳥のような感覚で見ておりました。おかげさまで本セッションは多方面に好評をいただいており、自分ごとのように嬉しい限りでした。いやー、むらみん入ってなかったらほんとにFOLIOのバックエンドチームは今ほど体系だった組織に成長してなかったであろうと思うので、本当にScala関西Summitが与えてくれた出会いには感謝しております。

togetter.com

Day2のアンカンファレンスではとーますさんのAirframeのハンズオンに参加したり、IntelliJハンズオンのつまみ食いをしたり、おえさんのsbtタスクグラフのお話を聞いたり、Akkaに入門したりしました!とってもためになったし楽しかった!

大阪最後の夜は何食べようかなと思ってたら、Day2の打ち上げをするということで、参加させていただきました。 2次会では噂には聞いていた、待望のイタリアバルSCALAに初めて行ってきました。美味しかったし楽しかった!

とにかく最初から最後まで熱量高く、楽しい2日間でした。

いろいろとモチベーションが上がったし、来年もまたぜひ参加したいと思える素敵なイベントでした!!