完全未経験からWebエンジニアになってみた。の「その後」
初めまして、PortalKey Webエンジニアのshariです。
2024年5月に弊社に入社するまではいわゆる未経験者でした。
8ヶ月の実務を通して感じたことや独学の時にこうすべきだったな、ということをお話したいと思います。
誰に向けて書いたの?
- 他業界からITエンジニア(特にwebエンジニア)になるべくアプリ開発をしている方
- 未経験独学がWebエンジニアになったその後が気になる方
はじめに
実は私、2年半前までこんな感じでした
「javaとjavascriptは全然違うってネットでよく見る」「HTMLって何?」
そこからプログラミングを始め、1000~1200時間くらいかけてWebアプリをMVPリリースし転職活動を始めました。
2か月間の転職活動を経て自社開発企業5社、SES企業1社から内定をいただき、このPortalKeyへ入社を決めました。
転職活動の経緯
また、弊社技術をCTOが、ゲーム業界とWeb業界のカルチャーショックを先輩がまとめてくださったので、私は未経験独学が実務に入って感じたことをまとめてみます。
CTO:PortalKeyを支える技術 - WebRTCを使ったコミュニケーションプラットフォーム実装への挑戦の振り返り
先輩:ゲームエンジニアからWebエンジニアになってみた
独学時代の負債
まずは、未経験で勉強中(過去の自分)に方向けたトピックです。
独学時代の自分にとっては全てが技術的なチャレンジでそこが限界でした。
しかし、これに時間を費やしたのは失敗だったな、これをやった/やらなかったせいで今苦労しているなということは多々あるので上げていきます。
※転職してから自分で調べたことや感じたことを多く含みます。
※会社の方針とは無関係です。
REST APIの設計はリソース指向で
私の個人開発アプリ(GitHub)ではアプリのページ単位でリソースを設計してしまいました。
リソースの考え方を知らなかったこと、通信料のことを気にしたためです。
func routingV1(r *gin.Engine) {
controller := controllers1.NewController(dbInit())
ver := r.Group("/v1")
{
vcontents := ver.Group("/vcontents")
{
vcontents.GET("/", controller.ReturnTopPageData)
vcontents.GET("/vtuber/:kana", controller.ReturnVtuberPageData)
vcontents.GET("/sings", controller.GetJoinVtubersMoviesKaraokes)
vcontents.GET("/original-song", controller.ReturnOriginalSongPage)
しかし、調べてみるとAWSはプライベート IP アドレスを使用してトラフィックが同じアベイラビリティーゾーンにとどまる
場合の通信料は無料でした。バックエンドとフロントのSSRで先に取得するデータ分はこれに該当すると思われるので全く気にする必要がありませんでした。
そもそも誤差な気もしますし。
「リソース指向なにそれ!」な方向け
-
Cloud APIs Cloud API Design Guide (弊社CTO上司イチオシ)
protoのことも書いてあるので得られる知識量は◎
その分、REST APIを書きたいだけの未経験者には難しそうです。
また、Opera特有かもしれないが目次を押しても画面遷移しないのでややこしい。 -
Microsoft Learn Challenge RESTful Web API の設計
未経験はこっちの方がシンプルで読みやすいかも
AWSのAZはアメリカを選ぶ
東京にするか米国西部(オレゴン)にするかで値段が3割くらい違うようです。
少なくとも日本は高い方です。(ただしデータセンターが国内にあるので通信が早いです)。
デプロイ→転職活動→就職を経て落ち着いたころにはAWSのことはかなり頭から抜けています。その後でAZから作り直す時間は取りたくないです。
portは1024以上にする
linuxにおける1024未満は特権portのため非推奨です。
デプロイ時にフロントエンドサーバーを80番にしてしまい、特権portということに気づくまでマルチステージビルド(後述)で躓きました。また、root権限を持たせる必要があるのでセキュリティ上のリスク懸念が増えます。
transactionを付けよう
1人で動作確認してる間はリスク0でしょうが、select句以外では整合性を保つために必須です。
クリーンアーキテクチャは×
※クリーンアーキテクチャ自体は凄い技術です。
未経験がやるものではありませんでした。今でも理解できてない上に未経験時に無理に取り入れようとしたのでメリットを享受できていないと思います。
しかもこの導入に1か月程かかっため、無職期間1か月延長=1か月分の給料を損しました。
色々経験した人が勉強して初めて意味のあるものなんじゃないかと思います。
ただし、SOLID原則のインターフェース分離の原則は最低でも知っておくべきだと思います。
クリーンアーキテクチャよりもまずは基礎の3層アーキテクチャやMVC系を実践して理解度を深めた方が良かっただろうと思います。
バックエンドのテスト
テストはその結果として開発コストを下げることが目的だと思います。
そのため、絶対に書くべきテストと、書いても割に合わないテストがあると思っています。
- 強く推奨
- handlerに対する結合テスト(ブラックボックステスト)
独学時には全くやってなかったですが、これがサービスを保証し更には仕様書にもなると思えば最重要でした。
実体験として、会員登録の後にログアウトしてしまうと再ログインできない不具合がありました。常にログイン状態である自分は気付かず発見が遅れました。もし、結合テストしていれば未然に防げたのではないかと思います。
(GitHub Actions CIを構築しているためpull request時にgo testが実行されます。このためmerge前に自動で検出できます。) - ピュア関数に対する単体テスト(ホワイトボックステスト)
- これは処理が複雑な部分にだけやってました。それで良かったと今でも思っています。
- Goのテーブル駆動テストをわかりやすく書きたいを参考に書きました。
のようなコードも、初めてfor range文を書いた時であり全く自信がありませんでした。しかし、テストで動作保証できたおかげで安心できました。// 期待通り動くか不安なコード func AddIsFavToKaraokeWithFav(kasWithFavCnts []domain.TransmitKaraoke, myFavs []domain.ReceivedFavorite) []domain.TransmitKaraoke { var transmitData []domain.TransmitKaraoke for _, kaWithFavCnt := range kasWithFavCnts { isFav := returnIsFavEachKaraokeIdByListenerId(myFavs, kaWithFavCnt.KaraokeId) karaokeWithLikeCnt := domain.TransmitKaraoke{ VtuberId: kaWithFavCnt.VtuberId, MovieUrl: kaWithFavCnt.MovieUrl, KaraokeId: kaWithFavCnt.KaraokeId, Vtuber: kaWithFavCnt.Vtuber, Movie: kaWithFavCnt.Movie, Karaoke: kaWithFavCnt.Karaoke, Count: kaWithFavCnt.Count, IsFav: isFav, } transmitData = append(transmitData, karaokeWithLikeCnt) } return transmitData } func returnIsFavEachKaraokeIdByListenerId(myFavs []domain.ReceivedFavorite, KaraokeId domain.KaraokeId) bool { for _, myFav := range myFavs { if myFav.KaraokeId == KaraokeId { return true } } return false }
実際に、数か月後にもっと親側のコードを変えた時にこのテストが落ちたおかげで難を逃れました。
- handlerに対する結合テスト(ブラックボックステスト)
(そもそもexist句を使えればこのコードは不要なのですが…)
- 非推奨
- DBクエリの単体テスト
GORMをwrapしたcreateメソッドとdeleteメソッドが期待通りのクエリを発行している保証としてそれぞれにテストを書きました。理由はクリーンアーキテクチャを採用しており、GORMの採用をやめた場合に意図しないコードにならないように気付けるようにするためです。
(GORMのCreateメソッドはcreated_at, updated_atカラムに対して自動で日時を入れてinsertしてくれ、Deleteメソッドはdeleted_atカラムがある場合には自動で論理削除してくれます)infra/db_test.gofunc TestSqlHandler_Delete1(t *testing.T) { db, mock, err := getMockDB() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } r := SqlHandler{Conn: db} logicallBody := getLogicalDeleteUser() now := time.Now() logicallBody.DeletedAt = gorm.DeletedAt{Time: now, Valid: true} logicallyQuery := "UPDATE `listeners` SET `deleted_at`=? WHERE listener_id = ? AND `listeners`.`deleted_at` IS NULL" mock.ExpectExec(regexp.QuoteMeta(logicallyQuery)). WithArgs(sqlmock.AnyArg(), logicallBody.ListenerId). WillReturnResult(sqlmock.NewResult(1, 1)) if err != nil { t.Log(err) t.Log("mockdb is down") t.Fail() } if err := r.Delete(&domain.Listener{}, "listener_id = ?", logicallBody.ListenerId).Error; err != nil { t.Log(err) t.Log("logically delete is down") t.Fail() } }
- DBクエリの単体テスト
いいね機能は物理削除で簡単に軽く実装(諸説有り?)
いいね機能(Github issue)を論理削除で実装してしまいました。理由はいいねと取り消しが交互に行われたらidがincrementされ続けて困るのではないかと思ったためです。
しかし、MySQLのint型は上限4,294,967,295なので気にしなくても良い気がします。もしも気になるならprimary keyとしてのidを使わず、unique keyに設定したものをPrimary keyにしておけば良かったものと思います。
全てのテーブルに日付情報を付ける
どのtableにもcreated_at, updated_atはとりあえず設定したほうが良さそうです(戒め)。
公式ドキュメントを愛読書にする
- 完全未経験のくせにぼちぼちしか読まずに書いた結果、公式ドキュメントで警告されているような書き方をしたり、使い勝手の良い機能を知らずにアプリを完成させてしまいました。
以下のリンクは日本語の取り扱い説明書兼仕様書みたいなものなので少しでも気になることや知らないことがあれば見に行った方が良かったです。
お金を気にするなら
私はAWS Fargate on ECS, RDBでデプロイし約$80/月かかっていました。しかし以下を全てやると約$40/月になる見込みで、約6000円/月近い削減ができます。大して時間かからない上に技術アピールでも損しないのでコスパ最強です。
浮いたお金でUberEatsを頼んで勉強した方がお得だったかもしれません。
実際のデータも少しあります
ただ、私の場合は転職と機能開発を早める方が目先のインフラ費用よりもお得だと思ったので、実際にやったのは比較的最近です。
- マルチステージビルド
思ったより簡単でした。ほぼネットの記事のコピペでAIに頼れば未経験者でもデプロイ段階まで来てる人なら1日、2日でできる気がします。特にnext.jsは開発元のvercelがマルチステージビルド用の見本dockerfileを公開したので簡単でした。
私の書いたマルチステージビルド:Go、 Next.js
入社後に学んだlinuxコマンドを置いておくので参考になればと思います - フロントはVercelにデプロイ
AWSにデプロイしてるコードそのままでGithubとVercelを10クリックくらいで連携させてデプロイできた。無料なのにアプリが軽く、AWSと比べてコンマ数秒の差しか感じなかった。 - AWSのAZはアメリカを選ぶ(上述)
以上が転職時の評価、転職後の自分の楽さ、その後の個人開発のコストに影響するだろうと思っています。
業界入りして大変なこと
ここまで読んでくださった方なら恐らく気になっていますよね
「IT未経験から開発実務って大変じゃないの?」と。
大変です。
未経験独学中は動けばok、コードも自分が分かれば良かったので。
それもそのはずで、ユーザーは自分のみ、Go, AWS(東京リージョン), SPAなので恐らくどんなひどい処理を書いても重さを感じることは無かったでしょう。開発者も自分のみで一番古いコードでも1年前なため、知ってるコードしかありませんでした。
これが実務入りして一転しました。
ナンモワカラン
いくらスタートアップの自社開発とはいえ無作法なコードは許されません。言語仕様やプログラミングのお作法すら素人状態だと、コードレビューや普段の相談で設計や命名から指摘されることも多いです。こうなると思うように仕事が進みません…。
書くことよりも読むことが重要っぽい
自作アプリではどこに何が書いてあるか、どんな意図で書いてあるかも大体分かるので、特に開発の後半は書くことに時間を使っていました。コーディングの終盤でも日常的に読んでたのはGORMとtailwindcssの公式ドキュメント、Goのコードジャンプで読める部分くらいだけだった気がします。
しかし、他人のコード、ドキュメントや記事を調べて読まないとロクなコードが書けないことを知りました。実務ではこれが重要なため読むのが苦手な自分はこれが大変です…。
公私の境目が曖昧に
フルリモなため仕事も趣味も同じデスクでやっています。出勤してた頃よりは確実にメリハリが無くなっています。
体力の衰え
転職してからは体を動かさないので体力と筋肉が目に見えて衰えました。座りっぱなしで作業していたら1度腰痛を起こしました。
また、通勤すら無いので股関節が以前より硬くなった気がします。
業界入りして楽しいこと
ここまでは未経験転職を考えている人にとって大変な現実を列挙してしまいました。
IT業界入りして楽しくなったことも書いてバランスを取ってみます。
コードを考えて、書いて、動く
想像通り、とても楽しいです。
お金を貰いながら勉強してるようなもので、最高です。
趣味
前職、前々職ともに周囲は「VTuber?LoL?何それ?」状態だったのが、現職では誰にでも大体伝わります。それどころか周囲からその話題が出ます。
ITエンジニア自体がゲームやVTuberに他職よりは近く、また、私の転職活動が好きなことのWEBアプリを作ったことが功を奏した気がします。
個人開発
実務が学びになるので、個人開発の開発効率が上がりました。
生活
- 前職よりも年収がチョット上がりました。
- フルリモなので都会付近に住む必要が無くなり家賃を下げる選択が取りやすくなりました。都会より田舎が好きなこともあり嬉しい限りです。
帰省した際はそのまま1か月くらい居座ったりもしています。実家で飼っているお猫様が身近に感じられるようになりました。
さいごに
未経験時代に自分が知りたかった情報を中心に、Zennの記事投稿コンテスト「今年の最も大きなチャレンジ」に沿うような内容を書きました。
ここまで読んでくださった皆さんにとって有益性
、独自性
はありましたでしょうか?
「ここを深堀りしてほしい」「それは間違ってる!」というのがあれば是非ともコメントしていただけると嬉しいです。
独学は想像以上に大変でしたが、転職は想像よりも楽しいものでした。
プログラミング自体は転職のための勉強ではなく、絵を描く、工作をすると同じモノづくりのための手段です。今はAIの発達もすさまじく無料で勉強をサポートしてくれます。
未経験の方も気になったらとりあえず手を出してみても良いかもしれません。
弊社のことでも未経験転職のことでも気になったことがあればご気軽にDMください。
弊社技術にご興味のある方はこちらの記事もお読みいただければ幸いです。
PortalKeyを支える技術 - WebRTCを使ったコミュニケーションプラットフォーム実装への挑戦の振り返り
次の記事でもお会いできると嬉しいです。
良いお年をお迎え下さい。
Discussion