フロントエンドの歴史変遷

どうも!Baseconnect のエンジニアの山本です。

さて、今回はBaseconnectのプロダクトである、営業リスト作成から営業管理までを一括管理できる法人営業支援データベース「Musubu」のフロントエンドの歴史についてご紹介させていただきます。 創業当初の様子はわかりませんので、物語は私が入社した2018年から始まります。

大雑把にフロントエンドの技術スタックの変遷を絵にすると、こんな感じです。

2018年 入社当時

私が入社したタイミングのムードと言えば、プロダクトが成功する兆しが見えてきたぞ...という感じでした。 ヨシ、プロダクトに新機能を追加していくんだってね。

当時は代表がすべてを掌握し、指示を出し、レビューをするという超中央集権的指揮系統で、デザイナーがデザインからコーディングまでを担当し、エンジニアもフルスタックで様々な業務をこなしていました。

こりゃえげつないフローだなぁとビックリしましたが、一気にプロダクトを具現化するタイミングで必要なダイナミックさでもあったなぁ。

React / Redux で スタイリングは Sass、バックエンドは Ruby on Rails という当時としては鉄板構成だったんじゃないかな? でもまぁ、フロントエンドのコードのカオスぶりはナカナカのものでした。様々なスキルレベルのエンジニアが、コードを追加しているような感じ。特にCSSの上書き合戦による膨大化には遠目になりました。

膨大なコードを目の前に、どうしたものかと躊躇しながら、Typescriptを採用し、JSX(js) から TSX(ts) への移行が始まっていました。

Atomic Design / Storybook

フロントエンドのコードは、それぞれが一つ一つ心を込めて作っている工芸品のようでした。とても似ているボタンが無数にあり、デザインルールも明確ではありませんでした。

まず、UIコンポーネントを整理・掌握し、再利用性を向上させる必要がありました。 当時、コンポーネントの分割手法として盛り上がっていた Atomic Design、コンポーネントカタログとしてStorybookを採用しました。

フロントエンドエンジニアとしての専業化も、ジワジワ進んでいきます。

2019年 : プロダクトリリース期

しかし、コンポーネントの整理はあまり進捗しなかったかもしれません。 プロダクトとして方向性を変える、いくつもの機能を実装することに奔走していたからです。

Styled-Components

Sass でスタイリングをコントロールすることが難しくなっていました。人力による命名規則でスタイリングのスコープをコントロールするってのは無理になってきたのです。Styled-Components を採用し CSS in JS へ移行していきました。

そしてついに、2019年11月11日に「Musubu」をリリースしました。

2020年

2020年は、あるべき姿にしていこうという動きが徐々に活発になってきていた。 これまで自由なインスピレーションと創意工夫でこさえてきたが、世のベストプラクティスを学び、適用していこうという流れでしょうか。

Redux Toolkit

Reduxにおいて、より効率的に開発を行うためのライブラリで、Reduxの公式サイトでも強く推奨されています。「Reduxを簡潔に書くためのスターターキットである」というよく見る解説も納得できますが、Reduxにおけるベストプラクティスが取り入れられた、Reduxを利用する時の間違いを防止し、正しく利用するための知恵の集合とも言えます。これはプロダクトをより安全・堅牢にしてくれるでしょう。

React Query

データ同期については長らくフロントエンジニアの悩みのタネでした。 取得、キャッシュの扱い、更新、ポーリング、エラー処理... 「SPAあるある」な、それらの憂鬱を解消するライブラリとして登場したのが、おなじみ TanStack の React Query でした。

React Hook Form

バリデーションルールは、整理・共通化されていたり、そうでない箇所もあったりと、とっ散らかっていました。React Hook Form を採用し、そのバリデーションスキーマに Yup を使用し統合・再利用性を向上しました。

xstyled

CSS in JS ライブラリとして既に Styled-Components を採用していましたが、互換性のある props ベースのフレームワークを採用しました。参照:[CSSフレームワーク「xstyled」について]

Styled-Components はなんだかんだ言って、ベタなCSSの記述が必要です。あぁ、本当はこう書けたらかっちょいいのになぁ、を実現してくれるのが「xstyled」です。

プロダクトのテーマカラーを刷新するというプロジェクトで、広範囲で適用され、しぶとく残っていた Sass によるスタイル指定を大量に排除することができました。

Atomic Design を破棄

Atomic Design に沿ったコンポーネント分割を放棄しました。 理念にはとても共感するのですが、多数のレイヤーに分類する方法は、私たちにはマッチしませんでした。

2021年 : 主要機能のUI刷新とリファクタリング

フロントエンドのリファクタリングがとても進捗した年です。

プロダクトのビジョンを具現化するための大規模改修が計画されていました。 その要件化を緻密・詳細に行った結果、フロントエンドとしては停滞したタイミングがありましたが、その時間を利用して大規模なリファクタリングに着手したのです。(タイミングを虎視眈々と狙ってた)

リファクタリング

一定のリソースを担保し、コツコツと負債を解消に取り組むことで、劇的にコードの透明性が向上しました。

  • RTK Query & Aspida 組み込み
  • テスト改善
  • ディレクトリ構成
  • FC 化

...などなど

RTK Query

APIとの通信やキャッシュ管理に React Query の適用に着手していましたが、Redux Toolkit との相性が抜群に良い RTK Query に仕切り直すことにしました。

Aspida

また、HTTPクライアントのラッパーである Aspida を採用し、APIリクエスト/レスポンスが型安全になりました。 パス/URL、クエリ、ヘッダー、ボディ、レスポンス...全てに型を指定でき、axios にも対応しています。

UI & Frontend Meeting

デザインチームとのディスカッションの機会で、アイデアや意図が共有され、早期からその実現性やレンダリングの負荷、使い勝手について検証しました。 そのためのツールの模索も行われ、デザインツールは figma に変更。実装の共有・ディスカッションツールとして chromatic を採用しました

主要機能のUI刷新

主要機能のUI刷新は無事にリリースされ、ユーザーに好意的に受け入れられました。

  • リファクタリングという地ならしによって、コードの透明性・開発体験透明性が向上した。
  • デザインの目的・意図を汲み取り、その実現方法についてデザインナーとエンジニアが知恵を出し合った。

難工事に取り組んでくれたメンバーに感謝しています。

現在

現在、Musubuの新機軸となる機能群を、モリモリ実装中です。

その中で、これまで模索してきたことをさらに昇華し、より良い体験をユーザーに提供できるよう取り組んでいます。

  • デザインシステムの構築 / DesignOps
  • ユーザーの視点に立った継続的改善
  • より良い方法・手法を積極的に採用していく

一流のフロントエンドと胸を張るには、あと何ステップか成長しなければなりません。 ご興味を持たれた方は、ぜひ覗いてみてください。

参照

求人機能の実装の概要について(AWS Startup Tech Meetup 関西 #1より)

こんにちは、エンジニアのAlvinです!今回は、AWS Startup Tech Meetup 関西 #1の登壇でお話しした「サーバレスを使って大量データ処理を実装した話」の概要について書いていきたいと思います。

背景

少し背景を説明すると、Baseconnectが提供する企業情報データベース「Musubu」は、企業の営業活動を支える高品質なデータを提供するために、技術と人力を組み合わせ、単なるスクレイピングやクローリング技術では実現できない独自のデータ製造に取り組んでいます。

しかし、日々更新されるジョブデータや外部媒体からの企業データの取得には限界があります。特に情報の更新性、網羅性に課題がありました。

そこで今回、パートナー企業様と連携することになりました。パートナー企業様の求人データとMusubuのデータベースを連携させることで、求人媒体に掲載されている企業の検索が可能になります。ターゲティング精度を高めることで、求職者の検索ニーズを最適化することを目指しました。

やろうとしたこと

システムを設計する場合、まずは要件を知ることが必要不可欠だと思います。そこで弊社が保持している情報を精査すると、以下のようなことを考慮する必要がありました。

  • データはBaseconnectが作成したAPI Endpointに送信される
  • 求人データは、パートナー企業様から、週1回受信する(数万件のデータ)
  • どの会社の情報なのか特定する必要がある
  • 企業が特定された求人データはMusubuのデータベースに追加する必要がある

これらの内容を実現するために、まず最初に思い浮かんだのはサーバーで構築することです。求人データを受信するエンドポイントとして機能し、それを処理してMusubuのデータベースに挿入するための常時稼働のサーバーです。

しかし、週1回実行される処理の場合、インスタンスを構築するのは無駄が多いと考え、別の選択肢を検討することにしました。

サーバーレスの活用

次に考えたのがサーバーレスについてです。 エンドポイントには、AWS API GatewayAWS Lambdaを使うのはどうかと考えました。しかし、ここで厄介なことがありました。

それは、送られてきた求人データをどのように処理するかです。理想としては、受け取ったものを、そのまま処理することですが、ここでいくつかの問題がありました。

問題点

  • 忙しい時間帯には、Musubuのデータベースが負荷に耐えられない恐れがある。
  • 処理にかかる時間は少なくとも3時間が必要と予測される。対して、AWS Lambdaは15分しか起動しない。

そのため、これらの問題点をクリアにするために、新たなサービスを探しました。そこで見つけたサービスが「AWS Batch」です。サーバーレスでの実行ができ、好きなタイミングでのトリガー設定や、長時間実行できるという、まさに求めていた内容にぴったりの優れたサービスでした。

開発プロセス

ここでは、そのAWS Batchを使った開発過程を簡単にご紹介します。

  1. まず、ローカルにDockerイメージを構築し、gitでコードを管理します。コンテナを作って、その中でコードを実行できれば問題ありません。
  2. コーディング後、AWS Elastic Container Registryでイメージリポジトリを作成し、そこにプッシュしました。毎回ローカル環境からプッシュするのは運用効率が悪いので、CI/CDを設定しています。Githubのgitリポジトリを更新すると、CI/CDパイプラインがAWS ECRの更新を代行してくれます。
  3. 最後に、週1回バッチを実行させたいので、AWS Eventbridgeを利用しています。AWS EventbridgeはAWS Batchと統合されており、どのジョブをいつ実行するかを選択するだけでできます。これでScheduledジョブは完了です。

※実装の詳細については省略します。

まとめ

全体として、このような仕組みになりました。

  1. パートナー企業様から求人データを受け取って、当社のストレージ(AWS S3)に格納します。
  2. 翌日、AWS Batchのジョブが実行され、処理されます。まず、CSVからのデータを抽出し、弊社のデータベーススキーマに変換します。
  3. 次に、どの求人データがどの会社のものかを弊社の名寄せシステムで識別します。
  4. 最後に、品質を保証するため、データのチェックを行います。問題がなければ「Musubu」で公開し、お客様に提供します。

終わりに

Baseconnectでは「これは本当にベストな方法なのか?」と常に新しいやり方を模索することを大事にしています。今後もより良いユーザーエクスペリエンスを実現するために、日々取り組んでいきたいと思います。ありがとうございました。 今回の取り組みの詳細はこちらからご覧下さい!

speakerdeck.com

AWS Startup Tech Meetup 関西 #1 で登壇しました

こんにちは、エンジニアのAlvinです!

今年の9月、私は、AWS Startup Communityというイベントに登壇させて頂いたので、当日の発表までを振り返ってみたいと思います。

きっかけ

そもそも登壇のきっかけは、開発部門責任者から「AWSさんが主催する開催したスタートアップコミュニティで登壇をしませんか?」と声を掛けてもらったことです。私は、エンジニアとしての登壇経験がありませんでしたが、思い切って登壇をすることに決めました。

準備

登壇を決めた後、早速準備期間に入っていきました。テーマとしては、技術系のイベントだったので、自分が担当したAWSを使ったサーバーのアーキテクトの話を選びました。

AWSさんのサービスを使い、それで素晴らしい結果を出すことができたので、きっと興味を持ってくださる方がいるのではと思いました。

スライド構成案作りが終わって、エンジニアメンバー数名に集まってもらい、構成案を見てもらいました。リハーサルも兼ねていたので、発表の仕方や技術的な内容について、多くのフィードバックをもらうことができました。Baseconnectのメンバーが背中を押してくれてとても心強かったです。

最後に、デザインチームに頼んで、スライドをデザインしてもらいました。とても綺麗で読みやすくなりました。最終のスライドはこちらになります!

speakerdeck.com

内容は主にAWSのサーバーレスサービス(Lambda、API GatewayAWS Batch)を活用し、高可用性かつ低コストなサービスを作ることについてお話しました。

当日

イベントは大阪で開催されました。ちょうど私はインドネシアにいたため、私以外の登壇者の方は全員現地で参加し、私はリモートで、オンラインとオフラインのハイブリットで開催されました。

エンジニアとしての初登壇だけでなく、他の登壇者の方々は錚々たるメンバーばかりです。プロダクト開発の責任者、CTO、Co-Founderの方が登壇していました。オマケに、いざ自分の登壇の番になると、PCの不調で順番が最後になりました。またしても緊張しました。

ところが、いざ自分の出番の数分前になると、なぜか分かりませんが、落ち着くことが出来ました。 心拍数が低くなって、頭がクリアになり、普通に話せるようになったんです。プレゼンは無事に上手く終えることができました。

こちらは実際の登壇の様子です。

youtu.be

数日後、参加者の方からのアンケートのコメントを共有いただけました。自分のLTに興味を持ってもらえたこと分かってとても嬉しかったです。

終わりに

今回、はじめてイベントに登壇をして、多くの人の前で発表する機会を頂きました。とても緊張しましたし、勇気のいるチャレンジでしたが、このようなチャンスは何回もあるわけではないので、今後もあらゆる機会を最大限に活用していきたいと思います!

色々とお世話になりこのような場を設けて下さったAWSさん、たくさんのサポートをくれた開発チームメンバーの皆さん、本当にありがとうございました!!

herp.careers

レゴを使ったスクラムワークショップを開催しました

こんにちは。プロダクト開発チーム サブマネージャーの富田です! 主に社内の「データ製造部門(DMF)」向けの内部システムの開発を担当しています。

弊社ではMusubuという企業データ情報サービスを提供していますが、この企業データを作成する部門が「DMF」です。 (Data Manufacturingの略です。)

開催したきっかけ

先日、DMFで組織改編が行われ、プロダクトオーナー(PO)が変更になりました。 開発部門でも新しい方が入社されたので、いい機会かと思い、アジャイルのワークショップを開催することにしました。

以前、紙飛行機を使ったワークショップを開催したのですが、今回は、新しいPOの方をメインターゲットにしたかったので、開発よりもプロダクトを意識したものにしたいと考えました。

そこで「レゴスクラム」をやってみることにしました。

どんな感じで開催したの?

レゴスクラムは、LEGOで街を作っていくワークショップです。

このワークショップ、どんな感じのものなのかは知っていたのですが、実際に体験したことが無かったので、lego4scrumやネット上の記事を読んだりしたものの、若干手探りな状態となってしまいました。 (いつかちゃんと参加者として体験してみたい!)

ネットの記事を見ると、チームに分かれて、それぞれのチームが作りたい街を別のチームが作る、という形で進めることが多いようでしたが、今回は私の方で予め作りたい街を用意しておくことにしました。

ずばり「休日に運動が出来る街」です!

街にどんな施設が欲しいかという要望は、下記のような感じで作成しておきました。

お父さんとして、ジョギングできる道が欲しい。
なぜならば、リモートワークの運動不足を解消したいからだ。
会社員として、スキー場が欲しい。
なぜならば、スノボが趣味で、毎年冬は泊りがけでスキー場に行くからだ。

参加者は下記のような感じでした。

  • エンジニアメンバー2名
  • DMFメンバー4名
  • 採用メンバー1名

また、今回は「見積もり」は行いませんでした。 私自身がこのワークを体験したことが無く、あまり要素を盛り込みたく無かったからです。

ワークショップの進め方

最初にアジャイルスクラムの座学を10分程行い、今回のワークショップの狙いについて共有しました。 今回はプロダクトを意識した感じです。

その後、下記のように進めました。

  • どう作っていくのかの検討(リリース計画)
  • 実際に作ってみる(スプリント)
  • ワークショップのふりかえり

リリースを計画してみる

プロダクトのゴールを意識してもらいたかったので、最初にどう作っていくかの計画を考えてもらうことにしました。

作りたい街の要望をどんな順番で実現していくのか、POと開発チームが相談しながら計画を練っていきます。

例えば、↓の要望に関しては、「ラウンドワンは必要なのか?色々なスポーツが出来る施設があれば良いのでは?」というように、目的部分から作るべきものを検討していたりしました。

高校生として、ラウンドワンが欲しい。
なぜならば、色々なスポーツを楽しめるからだ

10分間、色々と検討して、なんとなくまとまってきたようです、、、

作ってみる!

1スプリントは下記のような感じで、3スプリント実施しました。

  • プランニング(3分)
  • 実施(7分)
  • レビュー(3分)
  • ふりかえり(3分)

プランニング

プランニングでは、POから何をスプリント内で実現したいのか開発に伝えてもらい、開発はどうやったら実現出来るのか考えてもらいました。

たとえば、POが↓の要望を出した時、すでにプールは作っていたので、開発チームから「プールの水を凍らせるマシンを設置することで実現すればどうか」という提案が出ました。

高校生として、スケート場が欲しい。
なぜならば、冬にスケートを上達したいからだ。

POと開発チームが話し合い、凍結マシーン(っぽいLEGOブロック)を設置するということで実施内容が決定しました。

実施

レゴの種類を分ける人が出てきたり、レゴ置き場近くで仮組みをしてみたりと、スプリントを進めるに従って、色々な改善を行っていました。

コミュニケーションと距離

また、POと開発チームの「距離」も意識することが出来ました。

最初、POは開発チームが作業している時、少し離れた場所に居たのですが、3スプリント目では開発チームの近くに居るようになりました。

作業中、力を入れすぎて「屋根」が崩れてしまったのですが、POが横で見ていたので「障害が発生した」状況もリアルタイムに知ることが出来ました。 また、開発チームからの質問にもすぐに答えることが出来るようになっていたようです。

リモートベースになって難しくなってしまいましたが、「全員同室」の利点を感じてもらえたのではないかなと思います。

真ん中の後ろ向きの女性がPO。開発チームの様子を見に来ています。

エンジニアメンバー以外の開発体験

今回、エンジニア以外のメンバーが半数以上だったのですが、要件に沿って何かを期間内に作るという体験が出来たのではないかと思います。

欲しい形のLEGOが見つからなかった時「技術的にちょっと厳しいです」と、エンジニア的な会話も飛び出したりしていました!

レビュー

レビューでは、POがプランニング時に立てたゴールを実現出来ているか確認します。 開発チームは、説明やデモ(扉を開けたりとか)を行います。

たいてい要望どおりのものが出来ていたのですが、↓の要望に対して、開発チームが「馬屋」作ってしまうことがありました。

馬として、乗馬場が欲しい。
なぜならば、人を乗せて全速で走りたいからだ。

POとしては、「馬屋では全速で走れない」ということで、この成果物は「却下」されました。 プランニング時にしっかりゴールを共有する必要があるという気付きになったようです。

街が出来上がりました!


やってみてどうだったか?

ワークショップを終えてみて、簡単にふりかえりを行いました。

よかったこと
  • もっと要件を擦り合わせる必要があるという気付きがあった
  • 1サイクル内で出来る事・出来ない事を試す事が出来た
  • 最初に要件を読み合わせてイメージを共有することが出来た
  • スプリントを進めると、作るものが増えた
もっとよくできそうなこと
  • コミュニケーションをもっと工夫すればよかった
  • 「何のために必要か」をもっと意識する必要があった
  • (今回はオフラインだったが)リモートだとコミュニケーションの工夫が必要に感じた

POをしてみた感想

POからも参加した感想を頂きました!

プロダクトオーナーがやるべきことについて座学では概要を把握していたものの、身近なテーマとレゴを使って進めていくことでよりスクラム開発への理解を深めることができました。

今回は誰でも理解できる身近なテーマだったので、「この要望とこの要望は近い」「この施設はこれに変換できる」といった判断が比較的イメージしやすかったですが、実際の業務においてはステークホルダーの求める像の理解と開発の方と会話できる下地がとても重要で、プロダクトオーナーを務めるには幅広い知識とコミュニケーションを継続して行わなければいけないなと改めて痛感しました。

とても記憶に残るワークショップだったので、時々思い出しながら普段の業務に活かしていきたいと思います!

概ね好評そうだったので良かったです。 狙っていたプロダクトゴールを意識した開発の体験も、ある程度出来ていたのではないかと思います。

ただ、ワークショップ自体のふりかえりとして「リリース計画とスプリントプランニングの棲み分けが分かりにくかった」ということも挙がっていたので、次回開催時はもう少し改善出来ればと思います!

herp.careers

とある言語オタクエンジニアの平凡な1日

おはようございます。こんにちは、こんばんは。 Baseconnectエンジニアの御園です。

今回の記事は、タイトルの通り、私御園が、Baseconnectで働きながら、どのような平日を過ごしているかの1例を文章化してみたものです。

新社会人の皆さんや、歴戦の皆さんも、他の会社で働いている人はどんな暮らしをしているんだろうと気になりがちなのが人間の性というものです。 後は単純に、他の会社のエンジニアはどういう生活をしているんだろう、などと気になっている方もいるかもしれません。

自分以外の人間の生活から得られる刺激、発見、あるいは共感。そういったものは時に、自分の生活のふとした寂しさを紛らわせてくれたり、あるいは単なる笑いを提供してくれたりしますね。

私の生活を覗いてみた時、あなたが感じるものは何でしょうか? 特に何の変哲もない独身男性のエンジニアの1日ですが、これを読み終えた時に少しでもあなたの印象に残るような記録になっている事を祈りながら、文章を書いてみるとします。

起床

私は残念ながら朝が得意ではありません。 おそらく、Baseconnectで働いている人達の朝が苦手ランキング上位に食い込むでしょう。

なので、私の朝は必ず何度かのアラームと一緒に始まる事になります。 「後n分も寝れる」という安心のまどろみが大好きなので、だいたい起きなければいけない時刻の1時間〜20分前にアラームをかけます。

そこから、起きなければいけない時刻に対して、徐々にスヌーズの間隔を狭くアラームをかけていくのです。

9:00
9:20
9:25
9:28
9:30 <= デッドポイント

ポイントは「必ずこれだけは飛び起きる」というアラームの音を自分の中で修業によって作り出し、デッドポイントにはそれで飛び起きるようにしています。 というわけで、いつも9:30頃に私は起床するわけです。

Baseconnectはフルフレックスです。6:00〜22:00の中で自分が働きやすい時間を選択する事が出来ます。極端な話もっと遅めに起きてもよいわけですが、バランスが崩れないように意識しつつ、前職の流れからだいたい10:00には仕事にIN出来るように起床します。 朝が得意ではないとの代名詞の如く、目覚めた直後はめちゃくちゃ眠たい顔をしています。

朝のルーティン

モーニングルーティンなんて動画が流行りましたね。 残念ながら、私はごく平凡な一般独身男性ですから、 has_special_morning_routine()false な訳です。 まず、トイレに行き、洗面台に向かいます。眠たい顔を冷たい水で洗い、少し目を覚ました後、歯を磨くのです。

私は20代前半までは、朝の歯を磨く時間すら睡眠に充てたくてサボっていましたが、死ぬほど虫歯が出来て治療し、そして割とそれが痛かった経験から、ちゃんと朝もしっかり歯を磨くようにしています。

だいたい洗面台の前で歯磨きを始めますが、歯を磨いている間はリビングに戻り、テレビを付けてYouTubeを垂れ流します。内容はほぼ頭に入ってきませんが、歯を磨く手を動かしながら、人の声を聞くことで、徐々に頭を覚醒状態に持っていくのです。

そして歯を磨き終えたら……、おっと今日は特別です。 最近雨続きで洗濯物が溜まっていました。今日は曇りで怪しいですが、洗濯物を干さなければ限界そうです。 洗濯機のスイッチを入れて、洗濯のタスクを洗濯機に委譲します。引数は洗濯用洗剤です。

ここまでが、朝私がいつも行うことです。特に何も面白くなかったでしょ?見事に return falseでしたね。 

午前のワーク

ミニクロワッサンと、健康を意識して豆乳を朝食にとります。 リビングのテーブルにMac Bookを開いたら、仕事モードに頭を切り替えます。

Baseconnectは、リモートベースという、最もパフォーマンスが発揮できる場所を選択出来るという働き方を導入しているので、オフィスもありますが、基本的には自宅の人が多いです。私はオフィスまで徒歩数分の所に住んでいるのですが、本日は洗濯もしたいのでリモートで作業をすることにしました。

Slackの勤怠連絡チャンネルで開始を告げ、freeeで出勤のボタンを押した後、1日のスケジュールをGoogleカレンダーで確認しておきます。また、タスク管理ツールで自身のタスクを再確認し、1日に何をするかを組み立てておきます。

人によって違うと思うのですが、私は午前中はだいたい確認作業や、各種連絡、資料作成の時間に充てることが多いです。あまり午前中にコードを書くことはしません。 この日は、GitHubでレビューを行い、またそれ以外でも技術レビューがあったのでそれを行います。

各種更新されている重要な資料を確認し、連絡が急務なものがないかを振り返ります。 リモートベースでは連絡の取りこぼしがあると仕事に差し支えるので、気をつけないといけないところな訳です。

私の所属している開発チームは、スクラムを取り入れています。1日の始まりの時間に、「デイリースクラム」が行われます。そこでは、開発チームメンバー内で、昨日やったことと今日やること、困っていることを共有します。

場合によっては、困っている事を解消するために、直後ペアプロやモブレビューなどが差し込まれます。この日は11時から。だいたいMeetで行われます。 デイリースクラムに出席し、私も上記の事を共有し、その日は何事もなくデイリースクラムは終了しました。

おっと、洗濯機の音が

デイリースクラムを終えた後、洗濯機の洗濯タスクが end していました。例外を吐くこと無く無事に終了しているようです。離席する旨を勤怠連絡チャンネルに書き込み、freeeで休憩処理をした後、洗濯物を干しに向かいます。

バスタオルを干している時に、外に居る学生が傘をさしているのが見えました。小雨が降ってきたようです……。しかし、今日干さねば限界です。これ以上洗濯物をstackqueue出来ないため、洗濯物を干すことを敢行します。

結果として、干しきった時に雨はやんでおり、曇に戻るどころか晴れ間が見えていたので勝利です。私は全体的に晴れ男なのです。

昼食

洗濯物を干した後、またワークに戻っていましたが、お腹が空いてきました。お昼時です。お腹が空くと人間は能力を十分に発揮できません。なので、私はしっかりとお昼は取るようにしています。

一人暮らしだからといって横着する事はせず、自炊していますが、お昼はそんなに手の混んだものはつくりません。この日のメニューは、明太子のお茶漬け、ウィンナー、スクランブルエッグ、チンゲン菜のごま油炒めです。

is_alonetrue なので、見栄えなど特に考えず、洗い物効率を考えて食事をします。この日は炒めものしかしていないので、そのまま菜箸で食べました。だいたい昼食のお供はYouTubeです。

昼食を食べ終えると、しばらくそのままYouTubeを見、時間になると洗い物をします。 そして時間になったら午後のワークを開始します。気分を変えるために、リビングのテーブルから、デスクトップPCを展開しているテーブルに移動するなどします。

午後のワーク

休憩から戻った時に私がやることは、Slackの一通りの確認です。メンションなど以外も、割と細かく確認します。自身に緊急でない連絡なども、他のチームの仕事の流れを掴むのに目を通したり、あるいは他愛もない雑談が出来そうであれば、即座に突っ込んだりします。

リモートベースではFace to Faceで話す事が少ない分、雑談のきっかけも生まれず、結果として絡みが少なくなったりするので、そこは積極的に意識をしていきます。確認が終わると、午後はコーディングを含めた作業に重きを置いてワークしていきます。

技術的知識のインプットや、比較的かなり頭を使う作業を午後に置くことが多いです。 私は最近関わっているプロジェクトの都合上、かなりの量新しい知識を仕入れることが多く、この日もTypeScript関連の知識を膨大に習得しつつ、プロジェクトへのアウトプットを進めていきます。

ところで私は音楽を聞きながらノリノリでコーディングをすることが多いです。 聞いてる歌詞を口パクで歌ったりしてしまうことがあるのですが、オフィスでそれをやると不審者に思われるので、リモート冥利に尽きます。ただ、さらに深部にまで思考が潜る時は、逆に何も聞かず、雨の音等を聞いたりします。

そして空中をただずっと見つめたりする訳です。何も考えてないわけではなく、見つめている空中に様々な情報が空想でビュンビュン飛び交っているわけですから、やはりこれも他の人からすれば不審者です。つまり仕事をしている時の私は不審者な訳です。 セキュリティソフトから言わせれば、 suspicious object な訳ですね。ですが、エンジニアならきっと誰もが経験してるはず。

仕事に詰まった時は...

一人で仕事をしていると、煮詰まってしまってどうしようもない時があります。BaseconnectのSlackには面白い機能があり、「いでよ」と発言すると即席のMeet URLが発行されるのです。

MTGが入っていなければ、チームメンバーが助けてくれます。良く詰まった時はモブプロやモブレビューをします。また、オフィスに居る時は、近くに居るチームメンバーとホワイトボードを使って議論したりします。

チームメンバーと話しながら、考えを言語化するというのはとても良いことです。 頭の中のもやもやが整理され、結論が整頓されます。

合間の休憩

作業の一区切りがついた段階など、作業の合間には小休止を挟むことがあります。 伸びをしたり、水分補給したり。 Baseconnectのオフィスには共有スペースに誰もが飲んで良いコーヒーや紅茶、あるいはチロルチョコなどが用意されています。

乾かぬ洗濯物

日が沈み始めたので、洗濯物を取り入れるときです。晴れというよりかは曇りでしたので、やはり洗濯物が乾ききることはありませんでした。晴れ男敗北です。

statewet ですが、仕方なくCtrl+Cで洗濯物を取り込みます。 私の部屋の浴槽は洗濯乾燥室も兼ねていますから、そこに干して2時間ほど追加で乾燥をかけることにしました。

1日のワークの締め

私の所属している開発チームでは、夕方頃に「夕会」というMTGを行っています。 これはMTGといってもそんなにお硬いものではなく、チームでのコミュニケーションを深める目的のものとなっています。

普段はトークテーマを決め、それについて雑多に話す場になっていますが、週1でこの夕会の時間でCode Golf(いかに短くコードを記述出来るかを競う)を行うことになっています。

そしてこの日はCode Golf、テーマを適当なサイトから持ってきます。チームで業務に関係ないコードを書いて、お互いに見合うのは面白いです。また、汚くてもいいので短く書くという観点は、普段の業務では得られないような気づきを時に提供してくれるので、いい刺激になります。

夕会が終わったら、取り掛かっていたタスクに一区切りついたところで、大体その日のワークを終えます。

勤怠連絡チャンネルに書き込み、freeeで処理をして終了です。だいたい私は19:00〜20:00頃にワークを終えることが多いですが、これも人によってまちまちですし、用事がある日は17:00頃にはもう終えて外出、なんてこともあります。

整骨院

いつもはお腹がもうペコペコなので夕食を作り始めるときですが、この日は整骨院の予定があったので整骨院へ向かいます。私はずっと姿勢が悪い状態で仕事をしてしまっていたので、整骨院で調整をしてもらうのです。私自身がプログラミングコードだとしたら、整骨院はコードフォーマッターですね。

また、この日は鍼も打ってもらいました。私はエンジニアですから、自分の身体が治っていくのを客観的に観察するのが凄く面白いので、私自身に針が刺さっている様子を写真で撮って見せてもらいました。

夕食

整骨院から帰ってきました。もうお腹がペコペコで今私の身体にガーベジコレクションを走らせても何も起こらないでしょう。夕食の支度に取り掛かります。 予め解凍して ready状態にしておいた鶏ささみを使用し、この日は鶏ささみの唐揚げ、豆腐、レタス、トマトを夕食として摂りました。

いつもはトマトはちゃんと切ってるのですが、たまにまるごとかじりつきたくなるときがあり、この日はまさにそれでした。身体の事を考え、よく噛んで食事をします。やはり食事のお供は、だいたいYouTubeです。

夜のルーティン

夜にやることは大体決まっています。

  • プログラミングか技術的な勉強
  • 作曲
  • ゲーム
  • 筋トレ/散歩

私はだいたいこんなものです。この日は整骨院に行った後なので、筋トレと散歩はしません。食器洗いや洗濯物を整理した後、技術ブログ等に目を通すことにしました。

is_alonetrue なので、気兼ねなく一人の時間を過ごせます。プログラミングし始めると楽しくなっちゃって時間が飛ぶので、程々にします。一方、寂しくなった時は時折Discordで通話しながらゲームなどをやります。別に何にも特別な事はなく、Twitterもダラダラと見たりしますし、YouTubeを垂れ流しながら過ごします。 やはり、夜も has_special_night_routine()false でしたね。

就寝

だいたい1時頃には必ずベッドに入っています。寝付きがあまり良くない方なので、早めに就寝体制に入っておくことが重要なのです。これは、Mac Bookを開いた直後にdocker-compose upをするのに似ていますね。

ベットに入った後は、少しだけスマホを触ります。といってもそんなにガッツリは触らず、連絡等の確認や、Twitterを少し見るだけです。私は雑音があった方が寝れるタイプなので、30分ぐらいの動画を音を小さく流しておきます。

寝る前は割と1日のうちにあった反省点を強く反芻しがちなのですが、これだと寝付けないケースがあるので、出来るだけ楽しいことを考えるようにしています。睡眠へのコンパイルが通ったら、後は夢の中。楽しい夢を見れるように願いつつ、私の1日は終わりです。

Outro

どうでしたか? 共感するところ、出来ないところ、はたまたいろいろな感想があったと思います。 少しでも皆さんの脳裏に関心を残せたなら、私も記事にした意味があるというものです。

ここで紹介したのは私の平凡な1日ですが、Baseconnectでは序盤に述べたようにフルフレックスフルリモートなので、実に様々な生活体系があります。理想の生活を実現するためにフィットする職場を探しているエンジニアは、以下の採用ページを覗いて見ましょう。

herp.careers

TDDで開発すべき理由 Pourquoi il faudrait toujours développer en TDD

こんにちは、フロントエンドエンジニアのCouthouis Paulです。メンバーの皆からはクーさんと呼ばれています。

今回、TDDについてお話ししてみたいと思います。私はフランス出身なので、母国語のフランス語を少しまじえながら記事を書きました。 挿絵イラストは同じチームの山本さんが描いてくれています! よければ、ぜひご覧下さい。

introduction

一つに統制された開発手法によって仕様が実現されることが理想的ですが、現実的には開発手法は開発者と同じくらい多くあります。 そんな状況で、過去の開発手法によって仕様の変更に対応することが難しくなる、という問題に直面します。

そしてその変更自体がさらに次の変更を困難にしてしまい、保守がどんどん困難になるコードが引き継がれるという悪循環が発生します。 これは開発者にとってよく知られた問題です。

TDD : Test Driven Development

開発手法としてのTDDで特徴的に感じるのは「テスト」です。 ですからTestが注目され「TDDはテストのための手法である」と言われることがあります。

しかしそれは本質的ではありません。 TDDで一番注目すべき文字は、Test ではなく Drive なのです!

TDDにおいて、テストは開発にありがちな無理や徒労、脱線を防止してくれる、あなたのためのガイドなのです。

原理は「ベイビーステップ(小さなステップ)」です。

  • テストを書く
  • 最初はテストに失敗するコードを書く
  • できるだけシンプルにコードを修正し、テストが成功するように修正します(この時点でコードが美しい必要はありません)
  • テストを通過したら、充分に時間をかけて、コードを美しく、読みやすく、リファクタリングします。

これで次のステップに進むことができ、TDDの輪をさらに広げることができます。

これを実践することで、常にテストカバレッジが100%になります。 (そうでなければ、何かがおかしい)

メリットは他にもあります。

開発者が全体を慮り、予想・想像しながら開発し、結果的に迷走や蛇行運転となり、徒労となっている状態はよくあります(YANGI の原則から逸脱する)。

TDDは、現在の「ベイビーステップ」にのみ焦点を当てます。 いま行おうとしている次のステップは自ずと明らかになっていて、あらかじめ思考しておく必要もありません。

つまり、TDDはあなたのガイドであり、ドライバーでさえあるのです。 まるで、車のGPSが正しい方向か間違った方向かを教えてくれるように、アシストしてくれるのです。

それがTDDの醍醐味です。

TDD:Une discipline exigeante

しかしTDDを実践するには、厄介な問題があります。 TDDを成功させるためには、今までの習慣を捨て、いくつかの必須ルールを守る必要があるのです。

  • テストを合格するためだけに、コードを追加する
  • テストに合格するため以外のコードを書いてはならない。「必要になるかも」等の予想は無用です。
  • テストを失敗したままにしてはいけない。

また、テスト対象のレイヤーが他のレイヤーに依存している時に、記述することが難しくなります。 依存性の注入(DI)と Clean Architecture は、TDDに取り組むために知っておくべき重要な概念です。

お勧めは、TDDの鍛錬に役立つ様々な「KATA」がインターネット上にありますから、参照し練習することです。

※ 「KATA」・・・日本の武道などで伝統的に使われるKATAの意味で使っています。

Développer en TDD dans une base de code existante

また、既存のコードをベースにしてTDDを始めることに難しさを感じるでしょう。私が Baseconnect に入社したとき、まさにそんな状況でした。

ゼロから立ち上げるプロジェクトにTDDで取り組むことが理想的ですが、実際の現場にそんな機会はなかなか恵まれません。対峙しなければならないコードには、独自のテストが実装されていたり、テストが無かったりするのです。

私はTDD無しではやっていけないことに気づき、解決策を模索し続け、ようやくその方法に辿り着きました。 いわば、私が編み出した「KATA」です。

その方法とは最小限のコードを既存のコードに注入することです。

  • まず、新しいスタンドアロンモジュールにTDDを使用して新しいコードを実装します。
  • 完了したら、依存性注入の原則を使用して、このモジュールを既存のコードに注入します。

実際にやってみましょう!

TDDで書かれたコードはすべて「new_src」フォルダに、古いコードは「src」フォルダに収められています。

まずディレクトリ構成です。(Frontendの例で説明します) 古いコードは「src」フォルダに収められており、TDDで書かれたコードはすべて「new_src」フォルダに収められるものとします。 最終的に「src」のコードは「new_src」に移動することを目指します。

> new_src
> node_modules
> src

「new_src」フォルダは、独立したアプリケーションとして機能します。 Reactコンポーネントがあります。

export const Header: React.FC = () => {
  return (
    <x.div data-testid="header" display="flex" justifyContent="space-between">
      <div>
        <Responsibles />
        <TagManager />
      </div>
      <div>
        <StatusManager />
      </div>
    </x.div>
  )
}

また、Redux Toolkitのスライスもあります。

import { createSlice } from '@reduxjs/toolkit'
import { CurrentUserEntity } from './entities'
import { retrieveCurrentUser } from './use-cases'

const initialCurrentUser: CurrentUserEntity = {
  uuid: '',
  authorityName: null,
  branchOfficesStatusOptions: [],
  statusOptions: [],
}

export const currentUserSlice = createSlice({
  name: 'currentUser',
  initialState: initialCurrentUser,
  reducers: {},
  extraReducers: builder => {
    builder.addCase(
      retrieveCurrentUser.fulfilled,
      (_, { payload: { currentUser } }) => ({
        ...currentUser,
      })
    )
  },
})

もちろんテスト用ファイルも。

it('should retrieve status options', async () => {
    const {
      dispatchRetrieveCurrentUser,
      selectStatusOptions,
    } = retrieveCurrentUserSUT().withCurrentUser(fakeAdmin).build()

    await dispatchRetrieveCurrentUser()

    expect(selectStatusOptions()).toEqual<CurrentUserEntity['statusOptions']>([
      {
        label: 'label1',
        value: 'value1',
      },
      {
        label: 'label2',
        value: 'value2',
      },
    ])
  })

最後に、モジュール全体を構築するメインファイルを用意します。 実際のアプリケーションとの唯一の違いは、注入に使用するキーワード 'export' の存在です。

import { Provider as ReduxProvider } from 'react-redux'
import { storeQueries } from './api/queries'
import { AppShell } from './app/AppShell'
import { createStore } from './core/store'

const store = createStore(storeQueries)

export const MainShell: React.FC = ({ children }) => {
  return (
    <ReduxProvider store={store}>
      <AppShell>{children}</AppShell>
    </ReduxProvider>
  )
}

目標は「new_src」のファイルを「src」のファイルに注入できるようにすることです。 そのため、MainShellは "src "のAppShellコンポーネントにインジェクションされます。

import { ThemeProvider } from '@xstyled/styled-components'
import { QueryClientProvider } from 'react-query'
import { NotificationProvider } from '~/components/Notification'
import { queryClient } from '~/queries/queryClient'
import { theme } from '~/style/theme'
import { MainShell } from 'new_src/main'

export const AppShell: React.FC = ({ children }) => {
  return (
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={theme}>
        <NotificationProvider>
          <MainShell>{children}</MainShell>
        </NotificationProvider>
      </ThemeProvider>
    </QueryClientProvider>
  )
}

これにより「src」フォルダ内のファイルであっても、すべてのアクションとreduxセレクタにアクセスすることができるようになります。 ただし、既存の「src」テンプレートにReactコンポーネントを注入する必要があることに変わりはありません。

import { Header } from 'new_src/app/design/Header/Header'
~
~
~
 <FormProvider {...methods}>
      <Box backgroundColor={'mainBg'} marginTop="13.5rem" px="1rem">
        <input name="currentFocus" ref={register} style={{ display: 'none' }} />
        <Header />
        ~
        ~

こうすることで、コードのどの部分が100%テストでカバーされているかが分かります。 また、コードをリファクタリングして「src」から「new_src」にコードを移せば、いつか100%TDDのコードを手に入れられるでしょう。

Conclusion

開発者の誰もが、膨大で理解し難い、またメンテナンスが困難な開発を経験しています。

でも、最初から最後まですべきことをシンプルに保ち、メンテナンスの問題も無く、アプリケーション開発を成功させる解決策があるのです。それがTDDです。

最初、TDDはとっつきにくい開発方法です。 しかし、一度マスターすれば、あなたとそのチームを幸せにすることができます。

納得できない?ぜひ一度試してみてください!!最後まで読んでいただきありがとうございました!

herp.careers

React18のAutomatic Batchingを試してみてわかったこと

こんにちは!フロントエンドエンジニアの川瀬です。

少し前に、SuspenseやTransitionなど楽しみにしていたReact18がリリースされたのですが、 自動バッチングというのも新要素としてあったのでどういったものかなと試してみました。

自動バッチング https://ja.reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching

バッチングとは React がパフォーマンスのために複数のステート更新をグループ化して、単一の再レンダーにまとめることを指します

試してみたreact18とreact17のソース

// App.tsx 18も17も同じ
import React from "react"

function App() {
  const [count, setCount] = React.useState(0)
  const [flag, setFlag] = React.useState(false)
  const render = React.useRef(0)  //レンダー回数を数えます
  
  render.current++

  const update = () => {
    setCount(c=>c+1)
    setFlag(f=>!f)
  }
  
  React.useEffect(()=> {
    const id = setTimeout(()=>{
      setCount(c=>c+1)
      setFlag(f=>!f)
    } ,1000)
    return ()=> clearTimeout(id)
  },[])
  

  return (
    <div style={{
      display: 'flex',
      height: '100vh',
      justifyContent: 'center',
      alignItems: 'center',
      background: '#777',
      color: '#fff'
    }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10}}>
        <div>Version: (18 or 17)</div>
        <div>render:{render.current}</div>
        <div>count:{count}</div>
        <div>flag:{flag? 'true': 'false'}</div>
        <button onClick={update}>update</button>
      </div>
    </div>
  )
}

export default App;
// index.tsx
import React from 'react';
import App from './App';
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
// 18はcreateRootを使います
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
// StrictModeにすると開発環境ではrenderが2回走ってしまうのでやらない
root.render(
  // <React.StrictMode>
    <App />
  // </React.StrictMode>
);
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
//17
import { render } from 'react-dom';

// StrictModeにすると開発環境ではrenderが2回走ってしまうのでやらない
render(
  // <React.StrictMode>
    <App />
  // </React.StrictMode>
,document.getElementById('root') as HTMLElement);

動き確認

ブラウザ表示したばかりでは、どちらもレンダー1回目、countやflagの更新はなしです。

useEffectで、setTime内の処理が1秒後に実行され、countとflagが更新されます。 この時、React18ではrender回数は1回更新されのに対し、React17では2回更新されました。 中でstateを更新している分だけrenderが走ってしまいます。

続けてUpdateボタンで更新してみます。

ボタンで更新した場合はどちらも1回のrenderで済みました。

※公式より抜粋

自動バッチング以前は、React のイベントハンドラ内での更新のみバッチ処理されていました。promise や setTimeout、ネイティブのイベントハンドラやその他あらゆるイベント内で起きる更新はデフォルトではバッチ処理されていませんでした。

参考:https://github.com/reactwg/react-18/discussions/21 より

// promise
fetch(/*...*/).then(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
})

// つまりasync awaitでも起こる
const update = async () => {
  await console.log('promise!')
  setCount(c=>c+1)
  setFlag(f=>!f)
}
// ネイティブイベント
elm.addEventListener('click', () => {
  setCount(c => c + 1);
  setFlag(f => !f);
});

非同期とかaddEventListenerとかよく使ってる、気をつけねば・・

つまり?

React17の場合、上記条件でstateが複数更新される場合、その分renderが走ってしまいます。 onClickなどのReactのイベントハンドラであれば問題なし☆

React18からは、予期せぬrenderが走らない => パフォーマンスがよくなっている、ということですね。

React17でどうしてもsetTimeoutやpromiseの中でstateを更新したい場合は、stateをまとめてみるしか?

const [state, setState] = React.useState({count:0, flag:false})

React.useEffect(()=>{
  setTimeout(()=> setState(v=>{ count: v.count+1, flag: !v.flag}),1000)
},[])

また、色々と試していきたいと思います! 最後まで読んでいただきありがとうございました。

herp.careers