xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

平成最後なのでソーシャル就活で転職してみた

「平成最後なので」というのは何の意味もない枕詞ですが、今風な感じ (?) を出すためだけに付けてみました。それ以上の理由はありません!タイトルの通り今回ソーシャル就活というのもをやってみたのですが、どんな感じだったかを書いてみようと思います。

f:id:xin9le:20190203190632p:plain

ソーシャル就活という選択

今回退職に伴って、次の就職先をどうやって探そうかと悩んでいました。これまで転職先を探すとなったとき、大きく 3 パターンほどあったように思います。

  1. 転職/求人サイトの活用
  2. 転職エージェントに紹介してもらう
  3. 知人の会社にアプローチする

僕自身これまでの転職は「3」しか行ったことがなく、求人サイトや転職エージェントを利用したことはありませんでした。求人サイトを活用するのは重要な方法だとは思いますが、無数にある企業の中から、しかもどれも似通った募集要項の中から自分自身のスキルセット / やりたいこと / 希望年収レベルにマッチした条件を探し出す難しさはかなりのものだと思います。そして何よりも必死に探し当てた企業様が本当に自分を必要としてくれているのかどうかがどうしても分からない、という辛さがあります。(必要とされていない場合は落とされるだけだと思いますが...)

そう考えたとき「そもそも自分は市場に求められている人材なのか?」「そもそも市場価値があるのか?」という疑問でいっぱいになり、それを確認してみたいという気持ちが強くなりました。最近少しずつ #Twitter転職 というハッシュタグも見かけるようにもなりましたし、SNS を利用して転職活動中であることをアピールしてみるのもありかもしれない。そうすると自分の市場価値も分かるかもしれない。RT もいいねもされなければ拡散されないのでその点はかなり運なのですが、一切声がかからなければ市場価値なしと判断できます。そうすれば生き方 / 活動の仕方を改める必要があると分かるので、それもまたよい仮説検証だろうと前向きに捉えてやってみることにしました。

レジュメの作成

ただ単純に「仕事探してます!声掛けてください!」だけで反応してくれるとは全く思わなかったので、自分のスキルセットや経験 (= いわゆる職務経歴書) を見せて僕自身を知ってもらう必要がありました。なので GitHub にレジュメを作って公開しました。これは Grani 時代の仲間である @neuecc さんや @guitarrapc_tech さんがやっていたので、素晴らしい方法だなぁと思って真似させてもらいました。

あれもこれも文章だと読んでもらえないので、スキルセットをキャッチーにイイ感じに表現できないかなぁと考えて☆で表現してみました。自分が持っている技術とその理解度を知るだけでなく、改めて弱点を把握する時間にもなりとても有意義でした。これは職務経歴書を作るでなくてもやってみて損しないと思います。レジュメ作って本当に良かった。

反響

Twitter / Facebook の両方に「転職先探してます」の投稿をしました。Facebook は友達限定公開の投稿なのでお見せすることができませんが、Twitter は以下のような感じで投稿しました。

大変ありがたいことに多数の RT といいねをいただきまして、ツイートアクティビティは (執筆時点で) 概ね以下のような感じになっています。職務経歴書を 2640 回も見てもらえるなんて普通に就活してたら絶対無理

f:id:xin9le:20190203185447p:plain

たくさんの方の目に触れる結果となったことで、自分が把握してる範囲で大小合わせて 33 もの個人/企業様からお声がけいただきました。もし自分で会社探しをしていたとしてもこんなにたくさんの候補先を見つけることは絶対に無理でしたし、逆にこんなにも自分を必要としてくれている企業様があるんだと知って自信がつきました。僕の周りには「遠く及ばない」と思う尊敬できる方々がたくさんいますし、おかげで劣等感に苛まれることも多々あるのですが、この結果は本当に気分を上向かせてくれました。

一晩にして 30 近くの連絡があったのですが、とりあえず返信をするのにとんでもなく時間がかかりました…。A さんに返事を書き、続けて B さんに返事を書いている間にまた A さんからの返信が来るみたいな感じで対応に追われまくり、6 時間かかっても全員に返事をすることができませんでした。話をしていくと多くのケースで「一度食事でもしながらお話しませんか?」という流れになるものです。そんなこんなで 1 月中旬から 1 月末まで毎日誰かと食事するという経験したこともないものすごいスケジュールになってしまいました。飲み会以外での飲酒は一切しないので、体が持つか正直不安でしたw

余談

おまけですが、ディライトワークスさんにお声がけいただいた事が 2ch まとめに載りました。さすがの大人気ゲーム FGO*1 (= Fate/Grand Order) 効果!載っていることを友人から教えてもらったのですが、まさか拾われると思ってなかったのでさすがに笑ってしまいましたw

転職先の決定

身に余るほどたくさんの候補先が見つかりましたが、有名どころで言うと以下のようなところがありました。なんというかネームバリューだけでも圧倒されてしまいますね...。

今回僕は転職における第 1 条件として「リモートワークを許可してくれること」を掲げていたので、それが理由で多くのところが条件から外れていきました。「そういうのは最初に書けよ」と言われそうですが、希望年収 / 希望配属先 / 希望役職などと同様に求める条件のひとつでしかないと思っていたので書きませんでした。現在 3 年半ほど東京に単身赴任をしている身なのですが、この期間は家族と過ごす時間とのトレードオフとしてエンジニア人生を充実させてきました。これは悪くない選択だったと思っていますが、その分今度は家族との時間をこれまでより多く確保したいという気持ちが強くなり、それがリモートワークを求めた理由です。

そんな僕のある種のワガママのようなライフスタイル / ワークスタイルを受け入れてくれた会社さんが数社あり、その中でも以下の 3 社でかなり迷いました。必須条件をクリアしたあとは年収レベル / その会社で体得できること / 福利厚生などのバランスで決定することになりますが、どの会社さんも甲乙付けがたいくらいに素晴らしかったです。

これまでのキャリアではデスクトップアプリから Web アプリ、XR アプリまで幅広く手がけてきましたが、クラウドインフラの構築はそこまで詳しくなく (できないわけではないけれど) まだ得意と言えるほどではありません。特定のクラウド信者ではないので Azure / AWS / GCP などどれでも特に抵抗はないのですが、クラウド領域のスキル強化は今後のエンジニア人生として重要なものになると考え、今回は Azure の分野で最前線を走る会社のひとつであるシグマコンサルティングさんを選択することにしました。社長の橋本さんが僕のワークスタイルや今後の目標に共感してくれたことも、大きな決め手になりました。

長々と書いてきましたが、今回のソーシャル就活は拡散運にも恵まれつつ、個人的には非常に良い経験/結果になったと思っています。お声掛けくださった皆様、本当にありがとうございました!

おまけ : ソーシャル就活するまで 1 年間の活動

1 年前の今頃は株式会社グラニというゲーム会社に勤めていたのですが、諸般の事情で事業売却することとなり、それに伴って 2018 年 4 月より MYNET グループ配下の株式会社 GMG に承継転籍していました。そこも 2018 年 6 月で退職し、以降は知人のスタートアップ企業に勤めていました。そこではリードエンジニアとして以下のような多岐にわたる活動をしていました。

  • コアライブラリの作成
  • CI などの開発インフラの整備
  • クラウドインフラの整備
  • アプリケーション実装
  • デザイン/サービス仕様の検討
  • チームビルディング
  • 技術選定
  • etc...

このときの転職先としてスタートアップ企業を選んだ理由は、ひとり当たりに占める責任の割合の大きさを重視することでした。仕事における責任範囲とは会社規模が大きくなるにつれ明確化されていくもので、そういったある程度の規模のチームに join するとほぼ確実に個々人の能力が最大化されそうな場所に配置/配属されるものです。これは採用側として何ひとつ間違っていない采配ですが、ことスタートアップのような少人数チームとなるとそうは行かず個々人の責任範囲が必然的に広くなります。当時の僕は「自分の weak point (= 未経験な技術領域) を少しでも業務を通じて克服したい」という気持ちが強く、そのためなら投資を惜しまないという考えでした。

ここで言う投資とは時間的な投資と金銭的な投資の両方の意味を含みます。スタートアップは半年 ~ 1 年で芽を出せるかどうかが肝心です。極めて短期勝負。その期間広い責任を持ちつつ全力で突っ走ることは否応がなく短期間でスキルを伸ばすことに繋がる、と考えていました。またそのような経験を得られるのであれば金銭的にそこまで恵まれなくても、たとえ毎月赤字になろうとも、十分価値のあるものになると考えていました。実際ひとりの娘とパートをしている妻がいる身ではありますが、たとえ僕が 1 円も稼がなくても数年生きることができる程度には貯金していたので、短期的な勝負であれば金銭的にも問題ないと判断してスタートアップに join することにしました。それでスタートアップ事業が成功すれば +α で win です。

結果としてはこの思惑はほぼ計画通りに進み、僕は実質 8 ヵ月程度の在籍ながらかなりの経験を積むことができたと思います。残念にも事業の方向性がブレ続けて定まらなかったため、ずっと付き合うことが技術的にも金銭的にも自分の win にならないと判断して退職することにしました。事業の成功という +α を得ることができなかったのは残念ですが、スタートアップ企業に実際に勤めてみてその難しさを直に体感できたこともまた良い経験でした。

*1:Apple Store で年間 1000 億円売り上げたモンスタータイトル

キャリッジリターン (CR) を無視する ModelBinder を適用する

タイトルの通りです、それ以上の情報がないのですが...!下記のドキュメントを参考に、そういうものを作りました。

動機

iOS の Safari から改行を含む <textarea> のデータを POST すると勝手に CR が付与されるという問題がありました。Form の Submit イベントを JavaScript でハンドリングして CR を除外してから送信しても勝手に付与される不思議!なんでだ!

もはやクライアント側では回避不能なのではないかということになり、サーバー側で CR を削除するしかないということで対応することにしました。

実装

ASP.NET Core MVC には (ASP.NET MVC にも) ModelBinder という POST されたデータをプロパティや変数に値を詰める処理をカスタマイズする拡張ポイントがあるので、それを使っています。案外簡単ですね!

public class IgnoreCarriageReturnStringBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        //--- 値を取り出す
        var modelName = bindingContext.ModelName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
        if (valueProviderResult == ValueProviderResult.None)
            return Task.CompletedTask;

        //--- ModelState を更新
        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        //--- CR を削除
        var value = valueProviderResult.FirstValue;
        if (value != null)
            value = value.Replace("\r", string.Empty);

        //--- バインディング成功
        bindingContext.Result = ModelBindingResult.Success(value);
        return Task.CompletedTask;
    }
}

あとは適用したいプロパティにピンポイントで属性を貼れば OK です。サービス全体で適用する場合は Startup.cs で処理しても良いですが、良し悪しあるので適宜判断してください。

public class HogeModel
{
    //--- textarea とマップされるプロパティ
    [ModelBinder(typeof(IgnoreCarriageReturnStringBinder))]
    public string Text { get; set; }
}

Microsoft Connect(); 2018 Japan で登壇しました & Visual Studio 2019 新機能フォローアップ

Microsoft Connect(); 2018日本版 Wrap Up イベントに参加し、ちょまど (@chomado) ちゃん枠のトップバッターとして Visual Studio 2019 (Preview) の新機能について登壇/解説させていただきました。いろいろドタバタしつつもとても楽しかったですw

イベントは YouTube Live でストリーミング配信されていて、アーカイブも残っているのでいつでも見直すことができます。僕の担当部分は 42:40 あたりから。また、デモで利用していたプロジェクトは GitHub で公開しています。

解説した機能

イベント中に解説したのは以下の機能です。10 分ちょっとのデモにしてはだいぶ詰め込んだ感がありますが、こんなことができるようになったというのを上記動画から少しでも感じていただけていれば幸いです。

  • Look & Feel の変更
  • Document Health Indicator
  • Code Clean-up
  • .editorconfig のエクスポート
  • Convert to LINQ
  • IntelliCode

ちなみに Microsoft 公式の What's New 解説は以下にあります。

解説すれば良かった機能 / し忘れた部分

「やればよかった」「言い忘れた」というものももちろんあります。フォローアップとして以下の 2 つを取り上げて解説します。

IntelliCode の導入方法

デモでは「あたかも IntelliCode が Visual Studio 2019 に標準搭載されている」ような説明をしてしまいました。これは間違い & とても反省していて、IntelliCode は Visual Studio の拡張機能としてインストールする必要があります。

f:id:xin9le:20181223204732p:plain

Marketplace サイトからダウンロードしていただいても大丈夫です。

また IntelliCode は Visual Studio 2019 からの限定機能ではないので Visual Studio 2017 でも利用することができます!業務で Visual Studio 2017 を利用している方は、今すぐ IntelliCode で検索!

デバッガーの強化

Visual Studio 2019 ではデバッグ機能がまたひとつ強化され、ウォッチウィンドウで変数や値の検索ができるようになりました!これを紹介しなかったことを後悔しています。超便利!(語彙力

f:id:xin9le:20181223205411p:plain

これまである時点で特定の変数に特定の値が入っているかどうかを調べようと思ったら、僕の知る範囲では以下の 2 つしか方法がありませんでした。

  1. ウォッチウィンドウで変数を掘り返して特定の値が入っているかを確認する
  2. 条件付き Break Point で止める

Visual Studio 2019 からは Break Point で止めてから検索することができるようになるので、非常に捗るのではないかと思います。

ちょっとした裏話

事前に僕に割り当てられていた時間は 12 分でした。なのですが会場のディスプレイに Mac が軒並み接続できないというトラブル *1 があって、そのせいで開始が 4 分も遅れてしまいました。一応そんなこともあろうかとバックアッププランとしてちょまどちゃんの Surface Pro にもデモ環境を準備しておいてよかった...。

だけどそもそもカツカツの 12 分!という中で 4 分を失ってテンパる僕。普段から日本人らしく (?) 日本語キーボードを使っているのですが、ちょまどちゃんは英語キーボードを使っているのでミスタイプするわするわ...!そしてデモ中に隣でマイクを持ってもらっていたちょまどちゃんには、僕の手が震えていたのをしっかり目撃されていて...w

結果として僕が解説自体に使った時間は 11:30 だったのですが、接続トラブル込みで 15:30 を使ってしまったのでのっけからイベント全体のタイムスケジュールを崩す羽目になってしまい本当に申し訳なく...

関連記事 / スライド

*1:僕だけでなく他の方も接続が不安定だった

AcrInsight - Azure Container Registry のビューワーを公開しました

タイトルの通りですが、Azure Container Registry (= ACR) のリポジトリにあるイメージを閲覧するツールを作成/公開しました。

なんと (?) 4 日前の 2018/12/05 に Microsoft Connect(); 2018 で公開されたばかりの WPF on .NET Core 3.0 でできています!WPF がオープンソースになった記念にやってみたかったんや...w 画面は以下のような感じで、超ピュアな WPF です。

f:id:xin9le:20181209184606p:plain

モチベーション (= ACR の不便なところ)

最近仕事で ASP.NET Core MVC 製の Web サービスを開発しています。ホスティング先は Web Apps for Containers で、コンテナイメージの格納先としては Azure Container Registry を利用しています。やりたいことが一通りできてそれなりに満足してます。なのですが、そんな利用中の ACR にも不便なところが...!

以下は Azure Portal で Azure Container Registry のリポジトリを見ている画面のキャプチャです。

f:id:xin9le:20181209182448p:plain

リポジトリに格納されているコンテナイメージに紐付いているタグの一覧が閲覧できる特に何てことなさそうな画面ですが、よく見ると個々のイメージとタグの関連付けが全く分かりません。イメージとタグの関係は例えば以下のように 1 対多になっていて、Docker コマンドを叩くときもそうですがイメージ ID (= ACR で言うところの Digest) はよく利用します。

イメージ ID (= Digest) タグ
sha256:abc123 build-1024
latest
sha256:xyz789 build-1023

ここで ACR の課金体系について考えてみます。ACR の課金体系は格納しているコンテナイメージの容量に依存していて、プランに応じた一定範囲を超えるとドンドン増えていきます。なので、一定容量の範囲に抑えようと思うのであれば定期的にイメージを削除しなければなりません。

ではどうやってイメージを削除するのかと言うと、現状は Azure CLI を叩くしかありません。Azure Portal 上からはタグの削除はできるのですが、それはイメージについているタグを消しているだけでイメージ自体の削除にはなっていません。つまり、イメージに関連付いているタグをすべて削除してしまうとタグのついていない宙ぶらりんなコンテナイメージが残ってしまうことになります。Portal 上から見えていないのにひっそりと課金対象だと...??

そんなこんな、イメージとタグの関係をしっかりと把握して適切に削除なり操作をしたいという気持ちが強くなりました。Azure CLI を使えばこれらの詳細な情報は見られるのですが、やっぱり視覚化するのが良いなと思ってツールとして作ることにしました。

WPF on .NET Core 3.0 の始め方

まだプロジェクトテンプレートがないのでほんの少しだけ手間ですが、実は全然難しくありません。

  1. Visual Studio 2019 Preview 1 (執筆時点) をインストール
  2. .NET Core 3.0 をインストール
  3. コマンドラインで dotnet new wpf と入力してプロジェクトを作成

この 3 を実行するとき、プロジェクト名のフォルダをカレントディレクトリとして実行してください。そうするとイイ感じにテンプレートからプロジェクトが生成されます。

ACR の .NET SDK

実は ACR のリポジトリ情報を閲覧/操作する .NET SDK は NuGet に公開されていません。おかげで .NET から API を叩くのが地味に厳しい...。なのですが、幸いにも (?) GitHub にまだ作業途中のような感じのプロジェクトがあったので、それを使って C# から REST API を叩いています。

この SDK がまだ閲覧関連しかサポートしていないので、残念ながらイメージの削除を実装できませんでした。これは追々なんとか対応したいなーと思ってはいます。

おまけ

WPF 開発と言えば MVVM (?) ということで ReactiveProperty を使っています。ちゃんと .NET Core 3.0 でも動きます。ぜひ今後ともご贔屓によろしくお願いします :)

Slack.Integration - Slack 連携ライブラリを公開しました

もはや 2 番煎じ、3 番煎じどころか 10 番煎じくらいのものですが、.NET アプリと Slack を繋ぐライブラリ Slack.Integration を作成し、公開しました!現時点では Incoming Webhook *1 にだけ対応していますが、そのうち Slash Command にも対応させる予定です。

どうして車輪の再発明を?

Incoming Webhook において以下の機能を満たしたものが欲しかった、というのが主な理由です。

  • ドキュメントで見た Incoming Webhook のメッセージ投稿の表現を可能な限り使いたい
  • ASP.NET Core MVC 上で扱うときに HttpClientFactory の TypedClient を使った Dependency Injection に準拠したい
  • 標準の絵文字定義を使いたい
  • 特別扱いされている既定のカラー定義を使いたい

また今後 Slash Command を作るときに、同一のライブラリ内に Slash Command の機能も閉じ込めておきたいなぁという思惑もあります。

メッセージ表現の網羅

ドキュメントからの起こし漏れがなければですが、たぶん提供されているすべての表現ができます。たとえば下記のような感じです。REST API で飛ばす必要がある JSON に忠実なプロパティ名なので、困ったとしてもドキュメントとの対比が容易かと思います。

var payload = new Payload
{
    UserName = "Incoming Webhook",
    Text = "Hello, @xin9le !!\n\nThis posts is test for _Incoming WebHook_ .\nDocument is <https://api.slack.com/incoming-webhooks|here>.",
    Markdown = true,
    Channel = "#random",  // override channel
    LinkNames = true,  // linkify names and channels 
    IconEmoji = KnownEmoji.PlusOne,
    Attachments = new []
    {
        new Attachment
        {
            Fallback = "脱・読みづらいコード!今日から一段上のプログラマーになる方法 5 選",
            Color = Color.AliceBlue.ToHex(),
            PreText = "This text is optional that is displayed at the top of _attachment block_ .",
            AuthorName = "xin9le",
            AuthorLink = "http://xin9le.net",
            AuthorIcon = "https://pbs.twimg.com/profile_images/1047114972118114305/vw07RO7H_normal.jpg",
            Title = "脱・読みづらいコード!今日から一段上のプログラマーになる方法 5 選",
            TitleLink = "http://blog.xin9le.net/entry/2016/02/26/043557",
            Text = "「ソースコードを綺麗に書く」というのは、プログラマーであれば誰しもが心掛けたいと思っている *極めて重要な事柄* です。そもそも「綺麗なコードってなんぞ?」という感じですが、いくつかあると思います。",
            ImageUrl = "http://cdn-ak.f.st-hatena.com/images/fotolife/x/xin9le/20160226/20160226040749.png",
            ThumbUrl = "https://pbs.twimg.com/profile_images/1047114972118114305/vw07RO7H_normal.jpg",
            Footer = "xin9le",
            FooterIcon = "https://pbs.twimg.com/profile_images/1047114972118114305/vw07RO7H_normal.jpg",
            Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
            Fields = new []
            {
                new Field
                {
                    Title = "Unique User",
                    Value = "345",
                    Short = true,
                },
                new Field
                {
                    Title = "Page View",
                    Value = "12345",
                    Short = true,
                },
            },
            Actions = new []
            {
                new Slack.Integration.IncomingWebhook.Action
                {
                    Type = ActionType.Button,
                    Text = "GitHub",
                    Url = "https://github.com/xin9le",
                    Style = ActionStyle.Default,
                },
                new Slack.Integration.IncomingWebhook.Action
                {
                    Type = ActionType.Button,
                    Text = "Blog",
                    Url = "https://blog.xin9le.net",
                    Style = ActionStyle.Primary,
                },
                new Slack.Integration.IncomingWebhook.Action
                {
                    Type = ActionType.Button,
                    Text = "Twitter",
                    Url = "https://twitter.com/xin9le",
                    Style = ActionStyle.Danger,
                },
            },
        },
    },
};

表示サンプル

ASP.NET Core MVC の DI に対応

ASP.NET Core MVC 2.1 から HttpClientFactory がサポートされるようになり、HttpClient のインスタンスをフレームワークに管理させつつ効率的に利用することができるようになりました。ASP.NET Core MVC なサービスから直接 Slack にメッセージを飛ばすようなケースでは、自分でインスタンスを作るのではなく先の HttpClientFactory の機構に乗っかるのが良いだろうと思い、それに準拠させられるようにしました。

以下のような感じで DI に登録し、利用することができます。

// Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<WebhookClient>();  // supports Typed Client pattern
}
// XxxController.cs

public async Task<IActionResult> DoSomethingAction([FromServices] WebhookClient client)
{
    await client.SendAsync(url, payload);
}

ご利用は NuGet から

すでに NuGet からダウンロードして利用することができます。Slack にメッセージを飛ばしたいという方は、ぜひ使ってみてください。お手軽で便利です。

PM> Install-Package Slack.Integration

*1:Slack への投稿