2024年の振り返り

もう年末かい!本当に最近時間が過ぎるのが早いです。 と言う訳で、2024年の振り返りです。

TL;DR

  • リードフロントエンドエンジニアとして引き続き頑張っている
  • Golangもインプット・アウトプットともに結構できました
  • EMの職責が追加されました
  • ランニングが新しい趣味になりました、ランニングはいいぞ

フロントエンド

2024年は、現職で行ったアーキテクチャ刷新の効果を非常に感じる1年になりました。コードの見通しは良くなり、開発速度は大きく向上し、本番障害の数もほぼゼロまで下がり…と言った感じです。顧客体験・オペレーション改善に紐づく機能追加も沢山実施することができました。

元々現職の入社動機の1つに

  • アーリーなスタートアップで0 -> 1のフロントエンド開発(環境構築からディレクトリ構成・設計、状態管理戦略あたりを全て考える)がしたい

というものがあったんですが、この自分の欲求を満たせたし、アーキテクチャ刷新のようなPJが開発生産性・品質にどれだけ良い影響をもたらすのか、と言うことを肌で感じることができたのがとても良かったです。フロントエンドエンジニアとしてアーキテクチャ刷新 => 保守フェーズでのアーキテクチャ評価みたいなことが仕事で出来たのはとても貴重な経験になりました。

構成も綺麗にしたし監視も入れたし、ドキュメントに構成の背景もしっかり書けたので
今後フロントエンドで入ってくれるようなメンバーがいても「ふ〜ん、なかなか綺麗にできてるじゃん」と思ってくれるはず。とにかく、ここに関しては自信を持って会社に貢献できたな、と思えます。どうしようそんなことなかったら。

バックエンド

2023年の振り返りで「Golangも業務で書いてフルスタック寄りの動きを…」と書いたのですが、こちらもそれなりに達成できました。会社の個人目標としても書いたし、業務外の勉強によるインプットも積極的に行うことができたと思います。元々人材系メガベンチャーでLaravel/PHPを書くバックエンドエンジニアをやっていたのでいつかは貢献範囲をフルスタックに寄せようと思っていましたが、その経験を活かすことができたかなと思います。

ちなみに、A Tour of Goの次に取り組んだのは以下のUdemyの講座です。 クリーンアーキテクチャの丁寧な説明から入った上で、Echo(Go)を使用したREST APIの実装方法を学べます。非常にいい教材でした。

www.udemy.com

今は実用 Go言語を読み進めています。Goのベストプラクティスが細かく書かれてて、現場でも活かせそうなTipsが詰まった本です。まだ読み終えてないですが圧倒的におすすめ。

www.oreilly.co.jp

あと、他には以下のような本を読み直しました。どちらもWebアプリケーションを作るエンジニアなら鉄板の名著だと思います。3年前くらいに読んだことがありますが、今読むからこそ発見した新しい気づきがボチボチあったのが良かったです。

www.shoeisha.co.jp

www.oreilly.co.jp

EM業務

2024年12月に体制変更に伴い、おそらく業界で呼ばれるエンジニアリングマネージャ(EM)のポジションになりました。おそらく大人の事情で職場ではEMとは呼ばれていないのですが、採用・メンバーのモチベーション管理(1 on 1)・メンバーの人事評価にガッツリコミットしていくのでもう社外向けにはEMと説明しています。

まずマネージャーとして取り組んでいきたいのは「属人化の排除とフルスタックに動けるメンバーを増やすこと」です。

スタートアップと言う組織において過度な属人化は非常に大きな問題を生みます。例えばData pipelineの整備と最適化を行うデータエンジニアリングの有識者が社内に1人しかいなかったとして、この担当者の業務を他の開発メンバーが一切把握してなかったとします。

そして仮に該当のメンバーが突然体調不良で倒れる、もしくは急に退職することになったとします。めちゃくちゃピンチですよね?()

エンジニアが多い企業であれば「他のチームからヘルプで来てもらう」、採用力が強い有名企業なら「すぐ採用をかけて補填する」など打ち手はありますがスタートアップはそんな簡単にいきません。事前に対策できていないとゲームオーバーです。

と言う訳で、こういったことが起きた時のダメージを極力抑えられる体制にする、と言うのが自分の短期的な目標です。また、この体制変更を通してメンバーにフルスタックなスタンスで動けるようにしてもらう、というところを長期的には狙っていきたいと考えています。

また、軽くメンバーにこの方針についてヒアリングしたところ「○○の領域に興味があったので、この体制変更は嬉しい」とのFBをもらえたので、会社とメンバー両方にWin-Winな施策になるんじゃないかな、と思っています。

と、まぁ頭の中にぼんやりと抱えている解消したい課題は沢山あるのですが、全てが出来る訳ではないので1つずつ優先順位をつけて解消していけたらと思います。

ランニングにハマった

現在、週2-3回の頻度で5-6kmほど走ることが習慣になりました。きっかけは運動脳と言う本です。全てのホワイトワーカーが読むべき名著だと思います。

www.flierinc.com

簡単に本書の主張を箇条書きすると

  • 運動は脳の老化を防ぐ
  • 運動で集中力が高まる
  • 運動によりストレスが減る
  • 理想は週3回45分以上のランニング

といった感じです。まさにエンジニアに必要な副作用ではないですか?

走りさえすれば、ITエンジニア35歳定年説なんか関係ないんじゃないか⁉️」とさえ思わせてくれる本でした。

私の場合、2024年8月ごろに2-3kmくらいをゆっくり走るところからスタートさせました。

そして2ヶ月ほど継続するうちに上記のメリットを実感値として感じるようになり、走り続けるうちに距離・スピード共に伸びていき、今は週2-3回の頻度で1度に5-6kmほど走れるようになりました。今はなんなら走らないと気持ち悪ささえ感じます!

2025年の目標

また別で記事を書こうと思いますが、以下のような感じになると思います。
2024年もとても楽しかったです!

  • EM業務関連のインプット・アウトプットの強化
  • フルスタックに動けるようにもっと修行。データエンジニアリングも職場で必要になるのでそちらも。
  • Computer Scienceを体系的に学んでいく!UoPeople入学を前提にCourseraなどを活用していく?
  • 英語勉強継続するぞ。そろそろTOEFLで実力をスコアリングしたい?
  • ガンガン走るぞ。ハーフマラソン出るか!?

2023年の振り返り

明けましておめでとうございます!

TL;DR

  • 転職して楽しく働いてます
  • 関東に帰りました
  • 今年は引き続き技術と英語を頑張ります

転職

2023年4月に現職である株式会社Yoiiにフロントエンドエンジニアとして転職しました!入社して1年弱経ちますが楽しく働いています。 転職の動機は

  • アーリーなスタートアップで0 -> 1のフロントエンド開発(環境構築からディレクトリ構成・設計、状態管理戦略あたりを全て考える)がしたい

  • 今後のキャリアを社内公用語が英語の環境で積みたく、それを満たす会社に行きたい

というところでしたが、今年についてはこれらの欲求を全て満たすことができたので楽しく働けました。

また、現在社内のフロントエンド専任のエンジニアが1人という環境なのもあり、「ほぼ全て自分が責任を持って決めなきゃいけない」という環境がより自分を成長させてくれたかな、と振り返ると思います。社内のコンポーネントライブラリのレポジトリを0ベースから構築できたことも良い経験になりました。

これはカルチャー的なところになりますが、弊社は現在週3出社がルールとなってますが、ブロッカーとなる予定がない限りはみんなで外でランチに行くことが多く個人的にはこれが楽しくて出社が楽しみになってるところがグッドです。

関東に戻った

2023年8月に1年間住んでいた石川県から関東に戻りました。

家庭の事情が理由の100%ですので、特に詳細は語りませんが、関東に帰ってから会いたかった友人に会えるようになったことや、会社にも出社できるようになったので戻ってきてよかったなと思うことが多いです。

今年の抱負

引き続き当分は社内で1人のフロントエンドエンジニアとして働くことが想定されるので、自発的に最新技術のキャッチアップを行うことや、社内ドキュメントの整備なども意識的にコミットすることでチーム全体のフロントエンド周りに貢献できたらなぁと考えています。

また、バックエンド開発に関してもそろそろ業務でコミットしたい、とも最近感じてます。もう2年以上は業務で触っていないのですが、最近UdemyなどでGolangでのバックエンド開発の勘所は学習したので、チャンスがあり次第こちらも貢献したいです。

また組織体制の話になりますが現在PdMが不在です。自分としてはプロダクトのリファクリングPJを通してドメイン知識についてはかなり詳しくなったのもあり、自分が要件定義の上流部分にコミットすることが増えるのでPdMのような仕事も担当すると思います。この辺りの業務内容は、NRIで仕事をしていた際に英語で要件定義を行いドキュメントを書く経験はある程度積んだので、積極的に取り組んでいこうかなと思います(というか去年の末からこの辺りはすでに手を出し始めました。笑)

あとAtCoderも引き続き頑張ります。

長期的なキャリアでいうと、当分は社内公用語が英語で多国籍な会社でのキャリアを積んでいこうかなぁと考えているので、英語力についても今年の1年でもっと改善できたらなと考えています。英会話スクールに通うかオンライン英会話を始めるかなど、選択肢が多すぎてどれを始めるか悩んでいるところ…

仕事でも英語を使っているので少しずつは改善していくとは思いますが、意識的に勉強することの必要性を最近感じることが多いです。また、日本人同士での会話で日本語を使ってしまう癖があるので、なるべくEnglish-Only Policyを自分に課していく必要がありますね🥺

【ChatGPT】【GraphQL】ChatGPT-4に教えてもらいながらポートフォリオにGraphQLを爆速導入してページ追加してみた【20分で完了】

はじめに

この記事では、ChatGPT-4を利用した開発がどれだけの生産性向上に寄与するのか、というのをざっくり試したものになります。 今回は、あまり自分が詳しくないGraphQLで試してみました。

やってみたこと

試したことは以下の通りです。前提として私は業務上でのGraphQLの利用経験は一度もありませんし、そもそもGraphQLを個人開発などでも利用したことがありません。 また、一応事前にPlaygroundを通してGraphQLのクエリの書き方などは簡単に学びました(その際も疑問点はほぼ全てChatGPT-4に聞いた・・)


最終的に出来上がったページが以下です。ちなみにコンテンツ以外の部分は元々存在していたコンポーネントを再利用しています。 なお実装に要した時間は20分ほどです。


追加したページ
追加したページ

https://lamechang-dev.vercel.app/playground/pokemon_graphql

ざっくり振り返り

仮に1から自分が取り組むのであれば様々ググりながら進めていくとして、見積もりに1時間半-2時間ほどを設定すると思います。

が、今回 20分ほど で完了してしまいました。はっきり言って強力すぎますね。。。 しかもChatGPT-4って回答に周辺ライブラリの利用背景とかも完結に説明してくれていて非常に分かりやすいんですよね。開発体験がとても良かったです。とんでもないものが誕生してしまった気がします。

【英語】私の英語学習のこれまでとこれからを晒す

TL;DR

私は自分に甘く、興味が湧かないものについては必然性がないと努力をし続けることができない愚かな人間なのです…

これまで

〜高校卒業

大学受験をしているので英語は予備校を通ってちゃんと(?)勉強をしていました。参考になるか謎ですが、河合塾の最後の模試では英語の偏差値で65前後だったと記憶しています。

なお、学習時間は読む書くにほぼ全て突っ込んでました。英語自体は特別得意だったり好きという訳ではなかったですが、嫌いではなかった、といった感じです。

大学在学中

大学1年次に入学と同時にTOEICを受けたのですが、確か500点くらいでした。大学の講義の中にも英語関連のものはありましたが、当時はモンスターハンターがとても楽しかったので事前学習や予習・復習などは特にしませんでした。そのまま大学3年次まで過ごします。

大学4年次に、英語を集中的に勉強したいと思うきっかけが生まれ、フィリピン留学に4ヶ月ほど行きました。ちなみにそれまで海外経験はほぼゼロで、物心ついてから以降の初海外でした。特に行きのフライトは信じられないくらい揺れて本当に死ぬのかと思った

留学先から事前に課された勉強スケジュールを日本でこなしてからフィリピンに行ったので、現地に着く直前に受けたTOEIC800点ほどを取ることができ、なんだかんだで大学入学直後から300点ほどスコアを上げることができました。読む書くは受験勉強の資産もあってかキャッチアップしやすく、留学期間は苦手だった聞く話すにかなりフォーカスできたことが振り返ると良かったです。

留学を通して日常会話レベルなら聞く話すはできるようになり、また現地で受けたTOEIC900点ほどを取れました🥶TOEICのスコアに満足してからは、フィリピン人講師との授業はフリートークをメインにしたり適当にご飯食べに遊びに行ったりTOEFL対策などをしたりしていました。なお結局学士で卒業 => 就職を選んだので結局TOEFLは受けてません。

フィリピン留学は私にとって本当に大切な思い出です🌝🌝

英語力が上がったことももちろん理由の1つですが何より現地での生活は楽しかったし、ちょっとクレイジー社会人の先輩がたくさんおり、そういった方々から聞けた話から学生ながら価値観を大きく広げることができました。

まぁ簡単にいうと「人生って大体どうにかなるんだなぁ」ってことを理解できたと思います🌝なおこの留学をきっかけに海外旅行にドップリハマるようになります。

社会人

SIer在籍時

留学経験をよく話して就職活動をしたので、1社目のSIerでは運良く英語を使う部署に配属されました。開発パートナーはインド人のメンバーで構成され、業務の半分弱ほどを英語で2年半ほど行いました。

正直新卒入社時点で英語力は日常会話程度であったので入社直後は結構苦労しました。が、インドメンバーが日本人特有のブロークンイングリッシュに理解があるメンバーだったので、業務で絶望するほど困ったことはなかったです。

2年半の業務期間中、英語を集中的にインプットすることは一度もなく業務をしながら「あれ、こういう時ってどう表現すればいいんだろう?」という場面に出くわすたびにググる => 使うを繰り返してちょっとずつ話せる内容のバリエーションを増やした感じかと思います。こういったプロセスのお陰か、システム周りの細かい議論が業務特性上多かったのでその辺りについては苦手意識はなくすことができたのかな、と思います。

Webエンジニア転向以降

聞く話すでは一切使わなくなってしまいました。また、読む書くについても頻度が落ちたことが影響してスピード・理解度ともに下落傾向にあることを感じています。英語で書かれた技術系のリファレンスをたまに読みますが毎回衰えを感じてちょっと凹みます。。。

ただ、映画を英語音声で見るときに「あれ、今の単語なんて意味だ?」みたいなことを都度ググったり聴き直ししたりはしたので、完全に使わなくなった訳ではないかもです。

すいません、「ゆーてサボってた訳じゃなくて映画を通して活きた英語学習をしてたで?」みたいなことを突然書いてしまいました。本当はこの期間も本腰を入れて英語学習をする余暇の時間はいくらでもありましたし、正直勉強をあまりしてこなかったことを後悔してます。。

これから

と、ダラダラとこれまでの英語学習の経緯を話してきたんですが結局今回のエントリーで何を伝えたかったというと私は自分に甘いので、あまり興味が湧かないものについては強烈な必然性がないと努力をし続けることができない人間であり、これまでも必然性が生まれる環境に身を置くことでどうにかして何かを習得してきたということです。

ちなみに英語学習はぶっちゃけ好きではないです。が、私は1社目やフィリピンでの経験から多国籍な組織・文化での活動や就労にずっと興味があり、それはWebエンジニアに転向してからも変わりません。

実は海外就職にもずっと興味はありましたが、運悪くコロナが大流行したり結婚を機に価値観が変わったりと、そう言った理由で少し興味が薄れていました。

ただ、日本にいながらも多国籍 & 英語環境で自分が好きな領域でのアプリケーション開発に没頭できるような組織は実はあるんじゃないかとフワッと最近思っていたのですが、最近運良くこれに該当するスタートアップさんからお誘いいただき、入社することになりました。

今回の記事は転職エントリーだとは思ってないので細かい転職理由は触れませんが、運良くまた英語環境でのエンジニアの業務ができることを非常に楽しみにしています。

また、改めて自分のスタイルは わざと自分を追い込んで、背水の陣の状態でどうにか頑張って成長するというところにあるな、と思ったので今後も英語学習はそう言ったスタイルで継続していこうと思います🤢

補足

ちなみに そもそもなぜ英語を学習するのかですが、これは以下のような理由になります。上から優先度が高いです🙏🏻

  • エンジニアにおける技術や情報のインプット・アウトプットに関して、 読む書く聞く話すの4技能全てにおいて英語でストレスなく行えるようにしたい
  • 年齢も性別も国籍も人種も関係ないような多様性・透明性のある組織での仕事に興味があるので、そう言った組織で採用されてるであろう英語を自由に扱えるようになりたい
  • 2024年公開予定の ストレンジャー・シングス 未知の世界 シーズン5 を英語音声・英語字幕で楽しみたい♥️

自然言語人工言語も頑張っていきましょう🤢

【React】【Recoil】ポートフォリオ作成でRecoilを安全かつ綺麗に使おうとしたのでその構成を紹介する

はじめに

昨日、ポートフォリオサイトをvercelでデプロイ・公開しました。

lamechang-dev.vercel.app

レポジトリも公開しています。

github.com

公開の背景としては、以下のようなところが理由になります。

  • 技術的な検証ができるなんでもできるレポジトリが欲しかった
  • せっかくだから公開できるようなものにしたかった

といったところになります🤢

また今回、実装に入る前にアーキテクチャを図に起こしつつで着手しながら進めております。 その時の図を元にどう言った意図があって今の構成にしているかについて、今回はglobalStateにフォーカスして整理してみたいと思います。

レポジトリでのRecoilの運用ルール

  • 1つの単位(ドメイン・UIの状態など)に対して index.tsactions.tsselector.tsをそれぞれ用意する
  • recoilで定義したstatesetStateそのものを直接露出させず、write hookactions.tsread hookselector.tsの中に定義してそれらのカスタムフックのみを利用できるようにする(できる限りライブラリの知識・ルールを隠蔽する)
  • state の定義自体は index.tsの中に集約する
  • stateのkeyは必ず一元管理
  • ドメイン関連のglobalStateは domain/ の下に、 ui関連のglobalStateは ui/ の下に置いていく

実際のコードを以下のサンプルコードで明示していきます。今回実装したものの中で movie と言うドメインがあるので、そちらの構成を見ていきます。

サンプルコード

src/context/model/movies/index.ts

import { atom, selector } from "recoil";
import { GLOBAL_STATE_KEYS } from "src/context/constants";
import { MovieList, Movie } from "../../../domain/movies/model";
import { stateSelectedGenreIds } from "../genres";

export const stateMyFavoriteMovieList = atom<MovieList>({
  key: GLOBAL_STATE_KEYS.DOMAIN.MOVIE.MY_FAVORITE_MOVIE_LIST,
  default: [],
});

export const stateSelectedMovie = atom<Movie | undefined>({
  key: GLOBAL_STATE_KEYS.DOMAIN.MOVIE.SELECTED_MOVIE,
  default: undefined,
});

export const stateSelectedMovieList = selector<MovieList>({
  key: GLOBAL_STATE_KEYS.DOMAIN.MOVIE.SEELCTED_MOVIE_LIST,
  get: ({ get }) => {
    return get(stateMyFavoriteMovieList).filter((movie) => {
      return get(stateSelectedGenreIds).some((id) => {
        return movie.genres?.map((genre) => genre.id).includes(id);
      });
    });
  },
});

ここには純粋な atomselectorの箱だけを置いておくイメージです。特筆する点は特にないとは思います。

src/context/model/movies/selector.ts

import {
  stateMyFavoriteMovieList,
  stateSelectedMovie,
  stateSelectedMovieList,
} from ".";
import { useGlobalValue } from "../../hooks";

export const useStateMyFavoriteMovieList = () => {
  return useGlobalValue(stateMyFavoriteMovieList);
};

export const useStateSelectedMovie = () => {
  return useGlobalValue(stateSelectedMovie);
};

export const useStateSelectedMovieList = () => {
  return useGlobalValue(stateSelectedMovieList);
};

こちらが、globalStateの read hookになっており、単純にatom・selectorを参照し返すものになります。各々のhookは先ほどの index.ts で定義したstateと1対1対応となっており、これらのhooksを view層usecase層 で呼ぶイメージです。

実際の参照する側の記述では

const myFavoriteMovieList = useStateMyFavoriteMovieList();

と言った具合に、recoilの仕様などを意識せずに直感的にhookを通してglobalStateを参照できるようになっています。

ちなみに、useGlobalValue と言うものが登場してますが、こちらは useRecoilValue を単純に代入した関数になっています。recoilに対する直接的な依存を避けるために、間に挟んでいるイメージです。以下のようなhookを用意して利用しています。

■ src/context/hooks/index.ts

import {
  useRecoilCallback,
  useRecoilState,
  useRecoilValue,
  useRecoilValueLoadable,
  useSetRecoilState,
} from "recoil";

export const useGlobalValue = useRecoilValue;

export const useSetGlobalState = useSetRecoilState;

export const useGlobalState = useRecoilState;

export const useGlobalValueLoadable = useRecoilValueLoadable;

export const useGlobalCallback = useRecoilCallback;

src/context/model/movies/actions.ts

import { useRecoilCallback } from "recoil";
import { Movie, MovieList } from "src/domain/movies/model";
import { stateMyFavoriteMovieList, stateSelectedMovie } from ".";

export const useStateMyFavoriteMovieListActions = () => {
  const setStateMyFavoriteMovieList = useRecoilCallback(
    ({ set }) =>
      (movieList: MovieList) => {
        set(stateMyFavoriteMovieList, () => movieList);
      }
  );

  return { setStateMyFavoriteMovieList };
};

export const useStateSelectedMovieActions = () => {
  const setStateSelectedMovie = useRecoilCallback(
    ({ set }) =>
      (movie: Movie) => {
        set(stateSelectedMovie, () => movie);
      }
  );

  const resetStateSelectedMovie = useRecoilCallback(({ set }) => () => {
    set(stateSelectedMovie, () => undefined);
  });

  return { setStateSelectedMovie, resetStateSelectedMovie };
};

こちらが actions.ts の定義になります。いわゆる write hook の置き場所になっており、RecoilのAPIを直接露出させずに更新処理を行えることを狙っています。 また、globalStateに対するwrite処理をこのようにカスタムフックに集約することで、発生しうるwrite処理が網羅的にここで確認できるようになることも大きなメリットであり、テストを書く組織ではテストカバレッジの向上に貢献すると思います。

こういったAPIの隠蔽は、今後Recoil以外のよりナイスな状態管理ライブラリが誕生した際に、そのリプレースの影響範囲を限定できますし、アプリケーションに対するリスクをグッと抑えられるのではないかなと思います。

実際の呼び出し側のコードサンプルも置いておきます。

const { resetStateSelectedMovie } =
    useStateSelectedMovieActions();

const handleClickCloseIconButton = useCallback(() => {
    resetStateSelectedMovie();
  }, [resetStateSelectedMovie]);

その他補足

ドメイン関連のglobalStateは domain/ の下に、 ui関連のglobalStateは ui/ の下に置いていくと言う規約についてですが、これは単純にグルーピングできるものはしていこう、と言う発想からこのようにしました👼🏻が、今後ポートフォリオの機能を増やしていくうちにこの構成が扱いづらいとなったら他の構成に変えていくかもしれませんし、そもそも現段階で domainui の2つのグルーピングが最適解だとは全然思えてないので、作っていきながら調節したいです。

あと、このレポジトリは前述した通り色々なことを実験していくために用意したもので、例えば上述した Recoil以外のよりナイスな状態管理ライブラリが誕生したというシチュエーションも実際に試運転できたらなぁと思ったりしてます。 つまり、この後 Jotai に状態管理を載せ替えてみる、とかも試してみたいなぁなんて思ってたりしますね。Jotaiの方がRecoilより優れている、と言いたい訳ではないです。

jotai.org

【ESlint】フロントエンドアーキテクチャ上で決めた依存ルールをESlintで制約として表現する

この記事は Wano Group Advent Calendar 2022の23日目の記事となります。

はじめに

改めてですが、私は音楽ディストリビューションサービス「TuneCore Japan」のフロントエンドエンジニアを担当しております。みなさん、フロントエンド開発楽しんでますか。私は相変わらず楽しくやっております🥹

さて、プロダクトTuneCore Japanは クリーンアーキテクチャ を参考にしたアーキテクチャを採用しております(ここについての詳細は割愛)。新規機能の実装などはその構成によせ、また適宜既存箇所の修正をする場合はボーイスカウトルールに乗っ取ってなるべく新しい構成に寄せられるように頑張っております。

各レイヤーの責務・依存関係をチームで擦り合わせた上で実装を進めることができており、バグ発生時の影響範囲の特定がしやすくなったことや、レイヤーごとの責務が明確になったことからコード品質が改善し、スムーズに実装に取り組めるようになったと思います。 

そして、当たり前のことですが実際の実装フェーズにおいては、レビュー担当・実装者は「実装がアーキテクチャに沿っているか」がレビュー観点に含まれることになります。もちろん、各メンバーがアーキテクチャを理解した上で日々の実装に取り組んでいるので大きく構成から外れるようなことはないですが、もちろん人間ですのでミスはします。例えば、APIの型定義をそのままアプリケーション固有のロジック・定義が存在する層で参照してしまうことなど、例を挙げればキリがありません。

ということで、どうにかしてそういった問題を自動検知できないか、と調べていた時にimportプラグインに定義されている no-restricted-paths を見つけました。今回は、その紹介をしようと思います。

import/no-restricted-pathsの設定

no-restricted-paths の実際の記述例を以下に紹介します。

"import/no-restricted-paths": [
      "warn", // or error
      {
        zones: [
          {
            from: "./src/domain/**/*",
            target: "./src/components/!(organisms)/**/*",
            message:
              "The organisms components can only depend on the domain layer",
          },
          {
            from: "./node_modules/openapi",
            target: "./src/components/**/*",
            message:
              "Components layer can not depend on OpenAPI type definition ",
          },
        ],
      },
    ],

まず1つ目の例について。例えば、Atomic Designを採用しているプロダクトにおいては、しばしばcomponents層においてドメインに依存する箇所を制限させたい、というようなルールを取り入れているかもしれません。そのような場合、上記のように記述することで、 organisms以外のコンポーネントにおいて src/domain/配下からのimportを行った場合にアラートを出すことができます。

そして2つ目の例は、node_modules下にレポジトリの依存パッケージとして存在するOpenAPI上の型定義の参照を、components層で許さないことを表現したものになります。 このように、複数個のルールを定義することが可能であり、チームで取り決めた構成に沿った依存ルールを柔軟に表現できることがわかるのではないでしょうか?

(補足)no-restricted-importsについて

no-restricted-imports は上で紹介したルールと似たような名前ですが、こちらは文字通り指定したモジュールのimportを禁止することが可能です。実際のFEプロジェクトでのユースケースとしては、サイズの大きいライブラリ(例えばlodash)の静的なimportを禁止することや、プロダクトとして非推奨となったライブラリの利用を制限させることなどができそうです。こちらもチーム内で取り決めたルールを表現する手段としては非常に有効だと思います。

"@typescript-eslint/no-restricted-imports": [
      "warn",
      {
        paths: [
          {
            name: "lodash",
           "message": "lodash is deprecated, please use 'just'",
          },
        ],
      },
    ],

最後に

チームで取り決めたルール・運用をESlint上に表現し自動検知できるようにすることで、開発をする上での実装者・レビュワーの工数・負担を軽減させることは間違いありません。弊社プロダクト TuneCore Japan もお陰様でフロントエンド開発に携わってくれるメンバーが少しずつ増えてきており、こういった改善策の恩恵をより受けることができているのではないかな、と思います。

また、TuneCore Japan では 一緒に働くメンバーを募集しています。

エンジニアだけでなく、Director等の各種ポジションをオープンしているので興味がわいた方はぜひご覧ください!

www.tunecore.co.jp

【React】ちゃんと知って使うReact.memo

この記事は Wano Group Advent Calendar 2022の10日目の記事となります。

はじめに

改めてですが、私は音楽ディストリビューションサービス「TuneCore Japan」のフロントエンドエンジニアを担当しております。そしてこの2022年も1年通してReactをゴリゴリ書き、コードレビューも何度もしてきました。TailWind CSSだけをひたすら書いてた時期もちょっとありましたが

そんな中、自分の中のもやもやとして上がっていたのがReact.memoによるパフォーマンス改善をやるべきコンポーネントとやるべきでないコンポーネントの明確な定義ができていないというところでした。この点にフォーカスして今回は React.memoをちゃんと知り、正しく使えるようにするために自分が調べたこと を整理したいと思います。

React.memoに関する公式での説明

以下、Facebookが提供している公式Docから引用します。

React.memo は高階コンポーネントです。

もしあるコンポーネントが同じ props を与えられたときに同じ結果をレンダーするなら、結果を記憶してパフォーマンスを向上させるためにそれを React.memo でラップすることができます。つまり、React はコンポーネントのレンダーをスキップし、最後のレンダー結果を再利用します。

React.memo は props の変更のみをチェックします。React.memo でラップしているあなたのコンポーネントがその実装内で useState、useReducer や useContext フックを使っている場合、state やコンテクストの変化に応じた再レンダーは発生します。

デフォルトでは props オブジェクト内の複雑なオブジェクトは浅い比較のみが行われます。比較を制御したい場合は 2 番目の引数でカスタム比較関数を指定できます。

これはパフォーマンス最適化のためだけの方法です。バグを引き起こす可能性があるため、レンダーを「抑止する」ために使用しないでください。

自分なりの言葉で上記の説明を補足すると

  • React.memoは、propsオブジェクト内の値に対して、 Object.isを通した値の比較を適用して、仮にtrutyだった場合はレンダーフェーズにおける仮想DOMの差異チェックをスキップする

  • React.memo でラップしているコンポーネント内部のstateの変更に対しては、従来通り再レンダーが走る

といった感じでしょうか。ここで注意したいことは、React.memoコンポーネントをラップした場合、追加で Object.is によるprops同士の浅い比較がレンダリングごとの計算処理として追加されていることになります。

なので、仮に各レンダーフェーズにおいて毎回Propsの変更が行われるコンポーネントがあった場合は、React.memoでラップしても毎回再レンダリングされた上、追加でObject.is によるprops同士の浅い比較が行われるため結局総合的な計算量は増え、パフォーマンスに悪影響を及ぼすことになります。このことからも、慎重に利用するべきライブラリであることは間違いありません。

ではどういった時にReact.memoを使えば良いのか

上記のような仮説を自分なりに持って調査をしている時に出会った記事が

Use React.memo() wisely

になります。記事の第二章「2. When to use React.memo」が今回の疑問の解決に役立ちました。

詳細は記事を参照していただきたいですが、基本的な方針は 同じPropsで再レンダリングされる頻度が高い場合に React.memo を適用する、というところです。

筆者が記事の中で紹介している コンポーネント MovieMovieViewsRealtime のケースが理解しやすいと思います。

MovieViewsRealtimeコンポーネントはリアルタイムに情報が変わる頻度が高い箇所とstaticな情報をもつ箇所(titlereleaseDate)にDOM要素が分かれており

staticな情報をもつ箇所をMovieコンポーネントとして切り出して React.memoでラップすることで無駄な再レンダリングを防ぐ、と言うアプローチです。

このように、親コンポーネントの再レンダリングにつられて不必要な再レンダリングが子コンポーネントで発生するようなケースは多くの現場でのReactを通したUI開発でも肌感でよく起きている印象なので、このような場面においてはReact.memoが有効だと思いました。

また、筆者が「3. When to avoid React.memo」において指摘しているように、React.memoが悪影響を及ぼすパターンも多くあります。個人的にありがちだと思ったのが、コールバックのメモ化を怠った結果、常にReact.memoによるObject.is の値がfalseを返し意図しない再レンダリングが毎回行われてしまっていたと言うケースです。

さいごに

Reactの文脈においては、レンダリングの回数が減ったことが、そのままパフォーマンス向上につながったと考えられる傾向にあると予想しますが、コンポーネントによっては、React.memoによるpropsの比較が悪影響を及ぼし結果としてパフォーマンスに問題を起こすことは、今回の調査によって注意しなければならない、と改めて思いました。

Reactの公式サイトにも書かれていますが、React.memoに上記のようなリスクがあることは明らかなので、やはりレンダリング頻度や計算時間などを確認していくことは間違いなく必要になってくると思います。自分もProfilingを運用上で適切に活用して、より高品質なUI開発ができるように気をつけて行きたいところです。