駆け出しエンジニアはPMFの夢を見るか?

駆け出しエンジニアの記録

アラサーのソフトウェアエンジニアが半年ほど無職で過ごした話

2023年2月末で前職を退職し、半年ほど無職で過ごした。無職になるにあたって色んな人に相談していた時に、今後のキャリアを意識した戦略的な無職ではなく、自分がやりたいことをやるために一時的に無職の選択肢をとった人が意外と周りにいて、参考になった。 10月から働くところが決まり、社会復帰もできそうなので、同じような悩みを抱えている人に参考情報になれば良いなと思い、書いた。

簡単な経歴

  • 大学院では生物系の研究をしていた。データ分析のためのスクリプトPythonやRで書いたりはしていたが、Webアプリケーションの開発経験はほぼなし(講義でRoRでToDoアプリを作ったことがある程度)。
  • 大学院修了後にSIerに入社、ソフトウェア開発の部署を希望していたが、ネットワーク系の部署に配属になり、1年半で退職。
    • プログラミング経験のない同期がソフトウェア開発の部署に配属され、成長していく様子を横目で見ていて一番つらい時期だった。
    • 社内システムを使った定型業務がほとんどで、キャリアの危機を感じたため、転職活動をして退職した。
  • 第二新卒枠で自社サービス会社の新規事業部署に入社。4年半ほど、Webアプリケーションの開発をしていた。最後の1年は役職が変わりチームリーダーのロールになり、いわゆる上流に関わる業務が主となっていた(ちょっとした雑務でスクリプト書いたり、人手が足りないときに実装を手伝う程度になった)。

背景や動機

  • 大学数学に挫折して詰め込み暗記型で単位を取ってしまったことに対する後悔と学び直しをしたいという気持ちがずっとあった。当時と比べて、YouTubeで数学の講義が配信されていたり、高校数学と大学数学の架け橋となる書籍などが多く出版され、今なら学び直しができるのではないかと思った。
  • 役職が変わり、いわゆる上流に関わる業務が主業務になってしまった。もう少し設計や実装を中心に業務をして経験を積みたかったが、難しい状況であった。
  • 自分のためだけに時間やお金を使える時期の終わりが見えてきた。

2022年頭の転職活動で自分が満足できる程度には市場価値があることが分かり1、講義もまとめて受講してしまいたいという気持ちもあったので、無職期間をネガティブに評価されるリスクはあったが、仕事をやめて放送大学に入学することにした。データサイエンティストや機械学習エンジニアにジョブチェンジしたいわけではなかったので、データサイエンス系の学部がある大学ではなく、放送大学で科目履修生として学部生レベルの数学の講義を受講することにした。2022年の4月くらいから考えていて、退職を切り出したのが2022年の11月だったので、割と長い期間考えていた。

無職の間に何をしていたか?

放送大学入学前(3月)

業務で気になっていたが手をつけれていなかったものを調べたり、技術書を読んだり、大学数学に入門するための本を読んだりした。

数学書の読みかた|森北出版株式会社

大学数学ほんとうに必要なのは「集合」 - いつも、学ぶ人の近くに【ベレ出版】

放送大学(4月〜8月下旬)

7講義取っていたので、1日1講義受けるようにしていた。講義を受けて、教科書の章末問題を解いた。 講義だけでは理解できない内容もあったので、YouTubeで同じテーマの授業を受けたり、図書館で同じテーマの本を借りて理解不足を補った。

試験前は過去問と試験を模した課題を解き直して、試験に備えた。7科目中6科目Ⓐ(100~90点)を取ることができた。

授業科目名 評価
線型代数学('17)
解析入門('18)
微分方程式('23)
記号論理学('14)
入門線型代数('19)
入門微分積分('22)
演習微分積分('19) A

講義に集中できない時は技術書を読んだり、読みたかった本を読むなどして気分転換を図っていた。また、ログとして「無職日記」というタグでブログを書いていた。

就職活動(7月中旬〜)

単位認定試験が始まる7月中旬からカジュアル面談を何社か受けて、その中から5社選考に進んだ。3社は内定、1社は最終面接落ち、1社は1次面接落ちの結果だった。選考プロセスにプログラミング試験があったり、先方の都合やお盆休みなどもあり期間としては1.5ヶ月くらいかかった。選考を受ける会社の基準は決めていたので、内定をもらった会社の中で一番マッチしそうな会社の内定を承諾した。

どの会社も無職期間については質問あったものの、特に深掘りはされなかった。結果から考えても、そこまでネガティブに評価されていなかったと考えている。

振り返ると、前職で昇格するきっかけになった業務の話のウケが良かった企業は内定もらえた印象がある。最終面接で落ちた会社はキャリアパスに関する質問で会社の方向性とマッチした回答ができなかったことが原因なのでは?と分析している。

1次面接で落ちた会社は、質問内容がどういう背景なのか想像しにくく、これはダメかもなと思っていたら、案の定落ちた。面接中に「面接はお互いがジャッジする場なので対等です」と繰り返していたのに、選考結果の通知が一方的で少しモヤモヤするものが残った。本当に対等と思っているなら、同じタイミングでこちらにも次の面接に進みたいかどうか確認して欲しいし、「面接はお互いがジャッジする場なので対等です」といった言葉はでてこないのでは?と思った2

おわりに

退職後は今後の不安で夜に寝れなくなったりするかもなと思っていたが、そんなこともなかった。半年くらい悩んで、色んな人に相談して出した結論だったからかもしれない。親身に相談に乗ってくれた人たちには本当に感謝したい。相談する過程で自分が思っていたことが詳細に言語化でき、就職活動時にも役立った。 生活リズムも大きく変わっていないので、社会復帰にも心配はほぼない(通勤中に腹痛になったらどうしようくらい?)。試用期間が終わって生き残れたら、またブログを更新したいと思う。


  1. たまたまカンファレンスで面白うだなと思い選考を受けたらすんなり通ってしまった。残業時間や休日の条件が合わなかったので、結果的には辞退することなった。
  2. 飲み会などで立場が下の人が立場が上の人に「今日は立場などは考えずに喋りましょう」などと絶対に言わないと思う。

無職日記(07/10 ~ 07/16)

はじめに

今年の8月で32歳になる。今のところ介護や育児がなく、自分の都合で自分のためだけに時間が使えるラストチャンスだと感じ、2023年の2月末で一旦退職した。2023年の10月までやりたいことをやって、復職予定である。毎日何かしらアウトプットが出せれば良いが、そういうわけにもいかないので振り返りも兼ねて週に1回ほど日記を書こうと思う。

やったこと

所感

  • 息抜きで小説を読むことが多かった週になった。
  • 試験期間中なので更新頻度が下がるかも?単位を取れるように全力を尽くす。

無職日記(07/03 ~ 07/09)

はじめに

今年の8月で32歳になる。今のところ介護や育児がなく、自分の都合で自分のためだけに時間が使えるラストチャンスだと感じ、2023年の2月末で一旦退職した。2023年の10月までやりたいことをやって、復職予定である。毎日何かしらアウトプットが出せれば良いが、そういうわけにもいかないので振り返りも兼ねて週に1回ほど日記を書こうと思う。

やったこと

所感

  • 来週の週末からスタートする単位認定試験が迫ってきた。「微分方程式」と「記号論理学」以外は大やらかししなければ単位が取れそう。来週は過去問を解いて、試験に備える。
  • 採用面接に進もうと思っている企業のカジュアル面談だったので、IR資料や業界の動向を調べるのに時間がかかった + 面談でもちょっと緊張したので疲れた。やっぱり採用面接は単位認定試験が終わってから臨みたい。
  • FCバルセロナ 常勝の組織学はちょっと前のバルセロナの話だが、キーマン(CEO・強化担当者・監督)が今のマンチェスターシティのメンバーになっていて、バルセロナがというよりも経営陣が重要なのでは?とちょっと思った。

『ふつうのLinuxプログラミング 第2版』を読んだ

www.sbcr.jp

Linuxの仕組みから始まり、よく使うコマンドの簡易版を自作しながらLinuxプログラミングについて学び、最後にHTTPサーバを作ってネットワークプログラミングについて学べる本。Spring でHTTPリクエストがどのようにルーティングされるのかについて知りたくなり、そもそもHTTPサーバってどんな仕組みなんだっけ?と知りたくなったので読んだ。

「プロセス」と「ファイルシステム」と「ストリーム」の3つの概念でLinuxの構造が説明でき、ファイルの読み書き・デバイスとのやりとり・パイプ・ネットワーク通信やプロセス間通信が全て同じ枠組みで動いていることを、よく使うコマンドの簡易版を自作やHTTPサーバを作ることで体感もできた。駆け出し当初にLinuxプログラミングをかじったときはピンと来なかったが、シンプルなインタフェースで汎用的な機能を実現しており、経験年数を積んで凄さを理解できた。

本書を読んだ後に、Javaが自分に一番馴染みがある言語なので、Javaで写経してみた。C言語のコードではプロセスをforkして並列化に対応したが、Javaで写経したものはスレッドを使って並列化させてみた。HTTPサーバを実装すると、並列化とかネットワークプログラミングをどうやるかも勉強できて、新しい言語に入門時の素振りにいい方法だと思った。

GitHub - genkiFurukawa/my-http-server: 「ふつうの Linux プログラミング 第 2 版」(ISBN: 978-4-7973-8647-9)のHTTPサーバをJavaで写経したリポジトリ

次はこの本を読んで、プログラムの実行時にメモリがどうなっているかなどを勉強していきたい。

Cプログラミング入門以前 [第3版] | マイナビブックス

無職日記(06/26 ~ 07/02)

はじめに

今年の8月で32歳になる。今のところ介護や育児がなく、自分の都合で自分のためだけに時間が使えるラストチャンスだと感じ、2023年の2月末で一旦退職した。2023年の10月までやりたいことをやって、復職予定である。毎日何かしらアウトプットが出せれば良いが、そういうわけにもいかないので振り返りも兼ねて週に1回ほど日記を書こうと思う。

やったこと

所感

  • 単位認定試験が近いので、講義の復習がメインの週となった。過去問と提出型課題と自習型課題を解くと1日があっという間に終わるので、プログラミングに関するインプットはほぼできなかった。来週は提出型課題と自習型課題の解き直し、再来週は過去問を解き直すことで、単位認定試験への準備を進めていく。
  • Kindleフロントエンド開発のためのセキュリティ入門が安かったので購入した。来週は単位認定試験対策と並行して、読み進める。

無職日記(06/18 ~ 06/25)

はじめに

今年の8月で32歳になる。今のところ介護や育児がなく、自分の都合で自分のためだけに時間が使えるラストチャンスだと感じ、2023年の2月末で一旦退職した。2023年の10月までやりたいことをやって、復職予定である。毎日何かしらアウトプットが出せれば良いが、そういうわけにもいかないので振り返りも兼ねて週に1回ほど日記を書こうと思う。

やったこと

所感

  • 今週は大学の講義が中心の週だった。7月の中旬から単位認定試験が始まるので、来週も同じ感じになりそう。
  • 放送大学の「微分方程式」の講義の受講が完了した。この講義の後半は自習型課題を解いてからの受講だったので理解が比較的スムーズだった。

『Javaによる関数型プログラミング』をさらっと読んだ

www.oreilly.co.jp

前半はJavaラムダ式とStream APIの概要と使い方)、後半は遅延評価・再帰処理・最適化・関数合成についてJavaで紹介してくれる本。 自分がプログラムを書き始めた時には、既にJavaラムダ式とStream APIが導入されていた。気づけばラムダ式とStream APIを使わずにfor文を使っているコードの方が気持ち悪いなと思うようにくらいには経験を積んだようなので、復習と新たな知見を得るためにさらっと読んだ。

4章の『ラムダ式で設計する』で、ラムダ式で主要な関心を分離するところのテクニックが現場でもよく使えそうで勉強になった(恥ずかしながら本のリファクタリング前のようなメソッドを書いてしまっていた)。JavaScriptで関数を引数で渡して再利用性を高めるテクニックがJava側でも使えることを再認識した。

ラムダ式で主要な関心を分離するサンプルコード Assetのリストのvalueの合計値を計算するメソッドを実装したい場面を想定

public class Asset {
    public enum AssetType { BOND, STOCK };

    private final AssetType type;
    private final int value;

    public Asset(final AssetType assetType, final int asserValue) {
        this.type = assetType; this.value = asserValue;
    }

    public AssetType getType() { return type; }
    public int getValue() { return value; }
}

public class AssetUtil {
    // NOTE: 慣れていないとそれぞれのAssetのtypeごとに合計するメソッドを書いてしまいそう。
    // NOTE: fileterに使う関数を引数で渡すことで再利用性を高めることができる
    public static int totalAssetValues(final List<Asset> assets, final Predicate<Asset> assetSelector) {
        return assets.stream()
                .filter(assetSelector)
                .mapToInt(Asset::getValue)
                .sum();
    }

    // NOTE: 頻繁に使われるセレクタであれば、Utilに定義しておいて使えるようにしておいてもいいかもしれない?
    public static Predicate<Asset> ALL_SELECTOR = asset -> true;
    public static Predicate<Asset> BOND_SELECTOR = asset -> asset.getType() == Asset.AssetType.BOND;
    public static Predicate<Asset> STOCK_SELECTOR = asset -> asset.getType() == Asset.AssetType.STOCK;
}

テストコードはこんな感じ

class AssetUtilTest {
    final List<Asset> assets = Arrays.asList(
            new Asset(Asset.AssetType.BOND, 1000),
            new Asset(Asset.AssetType.BOND, 2000),
            new Asset(Asset.AssetType.STOCK, 3000),
            new Asset(Asset.AssetType.STOCK, 4000)
    );

    @Test
    void assetsの中でassetTypeがBondのものが合計され3_000となること() {
        int res = AssetUtil.totalAssetValues(assets, asset -> asset.getType() == Asset.AssetType.BOND);
        assertEquals(3_000, res);
    }

    @Test
    void assetsの中でassetTypeがStockのものが合計され7_000となること() {
        int res = AssetUtil.totalAssetValues(assets, asset -> asset.getType() == Asset.AssetType.STOCK);
        assertEquals(7_000, res);
    }

    @Test
    void assetsの全てが合計され10_000となること() {
        int res = AssetUtil.totalAssetValues(assets, asset -> true);
        assertEquals(10_000, res);
    }
}

6章の『「遅延させる」ということ』でStreamのメソッド評価順が自分の想像と違ってfor文で書き下すより効率的であることを知ってへ〜となった。 下記のようなコードだと、

1. リストの要素を大文字化 
2. リストの要素をチェックして3文字のものを探す
3. リストの最初の要素を返却する

という動きをすると思っていたが、実際の動きは

1. `Brad`を大文字にして、3文字かチェック 
2. `Kate`を大文字にして、3文字かチェック
3. `Kim`を大文字にして、3文字かチェック 
4. `KIM`を返す

というかなり効率のいい挙動となっていた。 for文を使って素直にこの処理を書こうとすると3回for文を回す処理を書くことになるが、Streamを使うと裏で色々頑張ってくれてかなり効率の良い処理になることが分かった1

# NOTE: サンプルコードを適当に改変したもの
public static void main(String[ ] args) {
    List<String> names = Arrays.asList("Brad", "Kate", "Kim", "Jack", "Joe", "Mike", "Susan");
    Optional<String> a = names.stream()
            .map(name -> {
                System.out.println("toUppercase(" + name + ")");
                return name.toUpperCase();
            })
            .filter(name -> {
                System.out.println("length(" + name + ")");
                return name.length() == 3;
            })
            .findFirst();
}

後半の章については、再帰処理の最適化(末尾再帰やメモ化)やparalellStream()を使うと並列化でき実行時間が短くなる可能性があるといったようなことが書かれていた。

(余談)本書を読んでいて、Utilクラスに実装されたstaticメソッドや定数に対してimport staticを使えば、{クラス名}.{メソッド名}{クラス名}.{定数名}ではなく、{メソッド名}{定数名}だけで済み、各コードの行数が減らせることに今更気づいた。ユニットテストでは当たり前のようにimport staticを使っていたので、なんで気づかなかったのだろうという感じ。ただし、メソッド名や定数名がクラス名なしで明白なもの以外に使うと混乱を招きそう。身近なものだとMath.PIとかはPI単独で使っても大丈夫かなと思った。


  1. 簡潔に書けるからという以外の理由でも、この書き方しましょうと言えるような気がする。