【エンジニアの日常】エンジニア達の自慢の作業環境を大公開 Part4

こんにちは。 今年の4月よりFindy Toolsの開発をしている林です!

この記事は自慢の作業環境を大公開シリーズの第4弾になります。

今回は3名のエンジニアの作業環境を紹介します!

作業環境を大公開

私はオフィスへの出社と在宅のハイブリッドで勤務しており、自宅にも快適な作業環境を備えています。

デスク周りの全体像はこのようになっており、シンプルで機能性を重視した構成にしています。

ポイントは昇降デスクと34インチのウルトラワイドモニターです。

昇降デスクはFlexiSpotのEF1を使っています。1日中座っていると体が凝るのと、疲れた時に気分転換で立ち作業をできて必要不可欠なものになっています。

ディスプレイはLGの34WN780-Bを使っています。横に広いことでコードやウィンドウを3つくらい並べることが出来て作業が捗ります。また、ディスプレイアームが付属しており角度や位置を調整しやすいです。

さらに、14型のMacbook ProをノートPCスタンドに載せることでディスプレイの高さを揃えられて、Webカメラの位置もちょうど良くなるのが気に入ってます。

他にはメモや紙をつかって思考整理するためのノートとペン、iPadやKindle Paperwhite、私用のMacbook Airを横に置いてあります。

入力機器はメリハリがつくことや気分転換のため私用と社用で分けており、上段のが個人利用、下段が社用です。

私用はKeychron K1のタクタイルスイッチとLogicoolのトラックボールERGO M575を使っています。業務でもFigmaで図を書くときなどは、たまにトラックボールを使うこともあります。

社用はLOFREE Flow84というリニアスイッチのキーボードとAppleのMagic Trackpadを使っています。元々、Keychron K1というキーボードだけを使っていましたが、リニアスイッチのキーボードも試してみたくFlow 84を今年になってから購入しました。打鍵感がスムーズでタイプ音も心地よく気に入っています。

昇降デスクが動く際、ケーブルに余裕を持たせておく必要があるので、天板にケーブルトレーを設置し、垂れるケーブルを最小限にしています。また、電源タップやUSB-Cのドッキングステーションを設置し、ここにケーブル類を集約することで、デスク上のケーブルがほとんど見えなくなりスッキリします。

以上、林の作業環境でした。

Findyでデータエンジニアやってる開です。 僕もハイブリットで働いているため最低限デスク周りは整えています。

デスクはFlexiSpotなんですが、手動で回すタイプのものです。 回す作業がちょっとした運動になるので気に入っています。 椅子はCOFO Chair Premiumを使っています。

ディスプレイは2枚を左右からアームで持ち上げて使っています。 1枚は新卒の頃から使っているものなので新しくしたく、4Kディスプレイを買うために家庭内稟議中です。

デスク上はこんな感じ。

キーボードは茶軸のMISTEL BAROCCO MD770で、マウスはLogicool ERGO M575S ワイヤレストラックボールを使っています。 ハイブリットなので出社した際もなるべく同じ環境で働きたくキーボードとマウスは同じものを会社にも置いてます。 前はReal Forceが好きだったのですが、整体の先生に進められて2,3年前から分離キーボードを使うようにしています。

またWAVLINKのドッキングステーションを使っており電源や周辺機器への接続をこれ1つにまとめています。 仕事ではMacBook Pro 14インチを使っててクラムシェルで置いてます。

左上にはFine-day(Findyの全社総会)で頂いたPC拭きの上にイヤホンやドラゴンボール(三星球)を置いています。 いつか他の6つが見つかったときのために大切に保管しています。

デスク下はプライベートで使っているデスクトップPCを置いています。 GPU(RTX 4090)積んでるのでデータコンペに参加したりローカルLLMを動かしたりして遊んでます。

また引き出しやコードを収納するためのトレー、バックとヘッドホンを掛けるためのフックを用意して物を収納できるようにしています。

マイクにはFIFNE K670を使っています。 個人でポッドキャストやっているので吐息が入らないように風防はスポンジとポップガードの2重でやっています。 アームによって持ち上げることでタイピングした際の振動がマイクに伝わりづらくしています。

以上、開のデスク紹介でした!

古田

前のお二人の記事を見て昇降デスクが欲しくなった古田です。
Findy Team+でプロダクト開発を担当しています。

自分も出社と在宅のハイブリッドで働いていますが、一時期リモート中心だった時期に椎間板ヘルニアを患い、それ以来作業環境では「健康」に気を遣っています。
そんな作業環境がざっと次のようなかんじです。

デスク

モニタに関しては以前は大きめのデスクにトリプルディスプレイで配置をしていたんですがヘルニアになってからは首の可動域をあまり増やしたくないので、ノートPCの上部にモニタを配置してそれに併せてデスクも約90cmほどのコンパクトな物にしました。

キーボードはBAROCCO MD600 Alpha BT RGBという分割キーボードを使用しています。
分割キーボードを使うまでは整体に行くと「肩の筋肉ガチガチに固まってますね...」と言われるくらいに肩が凝り固まっていましたが、分割キーボードを使うようになってから肩周りは大分ラクになってきました。
現状の分割キーボードでも充分に満足なのですが、社内のエンジニアの中には分割キーボードにして右側キーボードの端にトラックボールを配置する自作キーボードとかを作っているメンバーも居たりしていつか理想の自作キーボードを作ってみたいと憧れる今日このごろです。

分割キーボード

カーソル移動にはMagic Trackpadを使用しています。
トラックパッドの配置場所は最初、収まりの良さから分割キーボードで挟んで置いていましたが、どうしてもトラックパッドを触る腕の肩が内巻きになりがちでした。
なので現在は分割キーボードの外側に配置しています。

トラックパッド

作業環境なのか?と質問されたら回答に困りますが、作業時は常に着用しているということでアクティビティトラッカーのvívosmart 5も紹介します。 アクティビティートラッカーなので用途としては色々あるのですが、作業をする上で一番ありがたいのがMoveアラートという一定時間身体を動かしていないとアラート通知をしてくれる機能です。 このアラート通知が来たら立ち上がってちょっとウォーキングをしたり、ストレッチをしたりして身体をほぐしています。

アクティビティトラッカー

ストレッチの際はハンドルチューブなどを使って胸を大きく開くようなストレッチをすると首や肩周りの凝りが軽減されます。

ハンドルチューブ

また座席にはBackJoyのサポートクッションを必ず敷いて作業しています。これがあるかないかでは長時間作業した後の腰回りの疲労感が大分違ってきます。

以上、作業環境紹介という健康グッズ紹介みたいでしたが、古田の作業環境でした!

まとめ

いかがでしたでしょうか?

分割キーボードや昇降デスク、ストレッチグッズなど身体の健康に気を遣った環境が多かったですね!

やはりエンジニアは1日の大半をデスクで仕事をするので、作業環境の大切さを再実感しました。 何か参考になるものがあれば幸いです。

現在、ファインディでは一緒に働くメンバーを募集中です。

興味がある方はこちらから ↓ herp.careers

障害対応におけるポストモーテムのご紹介

こんにちは、ファインディ株式会社で機械学習エンジニアをしていますsasanoshouta(@Edyyyyon)です。この記事は、ファインディでインシデントが発生した際に行なっているポストモーテムの運用とその様子について、先日発生したインシデントを元に紹介をする記事となっています。

今回発生したインシデントについて

まず、今回発生したインシデントについて軽く紹介をさせていただきます。一言で表現すると、サービスの機能の1つを一時的に停止させてしまいました。

ポストモーテムの様子

弊社ではインシデントが発生した際にポストモーテムを実施して再発防止に努めております。

ポストモーテムとは?

そもそもポストモーテムとはなんだ?と言う方もおられるかもしれませんので、簡単にご紹介いたします。

ポストモーテムは、インシデントとそのインパクト、その緩和や解消のために行われたアクション、根本原因(群)、インシデントの再発を避けるためのフォローアップのアクションを記録するために書かれるものです。 *1

一般的にポストモーテムを実施するには一定の基準があり、基準を超えた障害が起きたときに実施するものです。

しかし弊社ではまだポストモーテムに対する知見や歴史が浅く、文化として根付かせるために、現在は練習も兼ねて小さい障害でもポストモーテムを実施するようにしています。

ポストモーテムの流れ

今回のインシデントを例に、ポストモーテムの流れを見る形で実施の様子をご紹介します。弊社の場合のポストモーテムは、以下の順序で実施されます。

  1. 当事者が事前に障害までのアクションを振り返る
  2. ミーティングで集まりアクションについてのフィードバックを実施する
  3. ネクストアクション決定とウォッチ

1. 当事者が事前に障害までのアクションを振り返る

この手順について特筆する事はありませんが、弊社の場合はGoogle docsにてインシデントごとに管理をしているので、以下画像のように主催者からdocsの案内が来たタイミングで覚えている事が新鮮なうちに時系列での出来事の詳細を記載しています。

ポストモーテム実施前の連絡

2. ミーティングで集まりアクションについてのフィードバックを実施する

以下画像のような形で、事前に当事者が書き記した時系列でのイベントを振り返りながら、各イベントに関連するアクションの中で良かった点と改善点について参加者で話し合います。

ポストモーテム実施時の雰囲気例

少しだけインシデントの事について触れますが、「機能が利用不可になっている事が発覚してから復旧までをスムーズに行えた」と言う振り返りがありました。当日はゴールデンウィークの狭間時期と言う事もあり、本来のバックエンド担当者が不在の間に起きた出来事でもありました。しかし、日頃からサービス上で提供している機能のIaC化をコツコツ進めていた事で担当者不在の中でもスムーズな復旧対応が行えました。

3. ネクストアクション決定とウォッチ

2で行なったイベント・アクションに対する振り返りをもとに、ネクストアクションを決定します。

以下は今回のインシデントにおけるネクストアクションにもなります。今回起きた事は、機能開発者が開発に必要なサービスとその権限をバックエンド運用者と十分な意思疎通をしきれなかった事で起きたインシデントだと捉えています。ヒューマンエラーを起こしてしまった開発者目線のネクストアクションを今回は以下のように定めました。

  • 開発に着手する前に管理者側と十分に意思疎通を図れるフローを制定し、運用する
    • フローの中で定める項目
      • 開発したい要件について運用者に事前共有する
      • 開発の為に必要最低限の適切な権限を要求する
  • 上記フローをポストモーテム実施後3日以内に作成し、次回週次の全体確認会で案内。

また、運用者目線でも開発時に「今自分がどちらの環境を開いているのか目視で分かりづらいのでは」との意見もあり、「どの環境にいるのか分かりやすくする為の視覚情報を導入」する事で同様のヒューマンエラーを防ぐ仕組みを導入する事になりました。

ポストモーテム後のネクストアクションを作成はしたものの、形骸化するものも中には少なからず存在すると思います。

なので、弊社ではネクストアクションを議論する際に意識するポイントとして、

  • 具体性を持たせて明確なアクションにすること
  • それを定期的にウォッチして進んだのか進んでないのかを確認し続けて放置しないこと

の2点を常に意識してポストモーテムを実施するようにしています。

余談ですが、弊社では全エンジニアが直近の取り組みを持ち回りで定期的に共有する全体会のようなものを設けています。

その全体会の場でポストモーテムの内容と得られた知見を包み隠さず共有し、エンジニア組織全体の共有知見とすることを文化としています。

ネクストアクションにはインシデントを受けてどんな対策を打つか?を考える事に焦点が集中しがちだと思いますが、「当事者でないエンジニアも含む組織全体に対して共有する」という事も再発防止策の一つとして機能している事を実感しています。

ポストモーテム実施時に気をつけている事

上記弊社でのポストモーテムの様子を実際のインシデントを例に紹介しました。最後に、実施の際に気をつけている事について共有します。

犯人探し、批判をしない

ポストモーテムで最も重要視している項目です。 ポストモーテムの目的は学びにあり、犯人探し、批判を行うとそれを恐れ当事者が真実を語らなくなり、その背後にある本当の原因に気づくことができなくなる為です。 批判ではなく、根本原因の追求と分析に徹する事を心がけるようにしています。

障害の根本原因を分析し、理解する

原因の分析が不十分だと、誤った再発防止策になり、結果として同じ障害が再発することに繋がりかねません。 客観的な視点で、原因がシステムやプロセスにあることを意識して分析をするようにしています。

人ではなく、仕組みで解決する

再発防止策が頑張る、気をつけるだけだと、再発防止を人に依存することになりますが、人はミスをする生き物です。 システムやプロセスを修正して、人がミスしても被害を最小限にくい止めるように努めるようにしています。

問題点だけでなく、良かった点も指摘、称賛する

犯人探し、批判はご法度ですが、良かった点があれば何回でも称賛する事も大切にしています。

さいごに

改めまして、ご不便をおかけしましたユーザーの皆様に深くお詫びをするとともに、再発防止に努めてまいります。 今回のポストモーテムを通じて、開発を進める上で足りていなかった観点・過剰だった点を浮き彫りにする事ができたことは非常に個人として学びになりました。 この学びを活かして、より良いサービス創りに貢献できるよう個人・組織として精進してまいります。

Findyの爆速開発を支えるテクニック

こんにちは。

Findy で Tech Lead をやらせてもらってる戸田です。

早速ですが、これは弊社のとあるチームの1ヶ月のサイクルタイムです。

最初のコミットからマージされるまで平均3.6時間程度と、開発に着手したらその日のうちにリリースされるのがデフォルトとなっています。

今回はこの開発スピードを継続し、更に速くするために弊社で実践しているテクニックを紹介していきます。

それでは見ていきましょう!

タスク分解

開発タスクをアサインされた時、まず最初にタスク分解をします。

タスク分解をすることによるメリットとしては、

  • 工数見積もりの精度が上がる
  • 対応方針の認識を他メンバーと合わせやすくなる
  • 対応漏れに気づきやすくなり、手戻りの発生が少なくなる
  • Pull requestの粒度を適切に保つことができる
  • 他メンバーへの引き継ぎをしやすくなる

などが挙げられます。

分解したタスク毎にPull requestを作成することで、Pull requestを小さく作り続けることが可能になります。

マークダウン記法のチェックボックスを使ってIssueにタスクリストを洗い出します。

終わったタスクから順にチェックボックスにチェックを入れ、タスク管理をします。

良いタスクリストを作成するためのコツは、最初から完璧なタスクリストを作ろうとしないことです。

まず最初に大枠のタスクを考え、そこから分解していくように意識してタスクリストを作成すると、結果的に詳細なタスクリストが完成しています。

完成したタスクリストを元にPull requestを作成していくと、結果的に適切な粒度でPull requestを作成できるようになるはずです。

例えば、何かしらのデータの一覧を返すREST APIを追加する場合、次のようなタスクに分解できます。

- [ ] APIの仮実装を行う
    - [ ] APIのエンドポイントを決める
    - [ ] APIのresponseの形を決める
    - [ ] モックデータを返す
- [ ] データベースからデータを取得して返す
- [ ] 検索条件に対応する
    - [ ] id
    - [ ] nameの部分一致
- [ ] ソートに対応する

小さくコツコツと修正を上乗せしていき、最終的に完成形に近づけていくような分解の仕方が良いでしょう。

大きな機能の実装担当になった際に、一度に全ての機能を実装するのではなく、小さい機能追加を何回も繰り返して結果的に大きな機能を完成させるようなイメージを持つと良いです。

まずタスク分解をして作成したタスクリストを他のメンバーにレビューしてもらいます。そこで認識を合わせることで、開発の進行がスムーズになります。

分解したタスクに沿って開発を進めていくと、そのタスクも更に分解したほうが良いことに気づくこともあります。

その際はどんどん分解していきます。結果的に分解されたタスクそのものが知見となり、今後の開発の参考になるからです。

Pull requestの粒度

Pull requestを小さくすることで開発生産性が改善することは既に知られている事実ですが、小さすぎても問題が出ます。

じゃあどうするのか?という話になりがちですが、これはPull requestのサイズを気にしすぎていて粒度を考えていないために起こる問題です。

Pull requestを 小さくするのではなく、粒度を考える ことが、小さいPull requestを作り続けるための秘訣です。

では適切な粒度とはどういったものなのでしょうか?それは、一つのことだけに注力している Pull requestです。

具体的に幾つかの例を挙げましょう。

例えば、「関数名を変更したので、それを利用してる1万行を一括置換したPull request」があるとします。

これは適切な粒度だと言えます。変更行数は1万行でサイズは大きいかもしれませんが、「特定の関数名を一括置換する」という1つのことしかしていないため、粒度としては適切です。

Pull requestの概要欄に一斉置換した旨を書いておき、CI通れば即mergeでOKです。 ※後述するように自動化テストが充実しており、守れるテストになっている前提です

では「画面の開発中に別の画面のリファクタを入れ、変更行数は20行程度だったPull request」はどうでしょうか?

これは適切な粒度とは言えません。変更行数は20行でサイズは小さいかもしれませんが、「画面の開発」と「別の画面のリファクタ」という2つの事を同時にしているため、粒度としては不適切です。

仮に画面の開発の部分で不具合が発生しrevertが必要になった場合、画面の開発部分だけではなく別の画面のリファクタの部分も同時にrevertされてしまいます。

適切な粒度を考える上で、Pull requestの存在意義が多岐に渡っていないかどうか? ということを考えると良いです。

粒度が大きすぎると出てくる問題は色々とありますが、

  • レビューに時間が掛かり、レビュワー目線で考えるとレビューに対して精神的に負荷がかかる
  • どこをどうレビューしたら良いのか分かりづらいため、レビューの質が下がり、結果的に不具合の発生率が高くなる
  • Pull request上でのコミュニケーションが必要以上に増えてしまう
  • 不具合発生時の影響範囲が広くなり、原因の特定に時間がかかる
  • etc

などが挙げられます。

Pull requestが大きすぎること自体の原因は組織によって異なりますが、Pull requestの粒度が大きすぎることは、システム開発において何のメリットも生み出さないのです。

適切な粒度を維持し続けることにより、Pull requestのレビューに対する負担が減るためレビュー自体の質が上がることに加え、レビューの優先度が上がることに繋がり、結果的に開発スピードと品質の両方を担保できます。

テスト

実装コードに加えて、それの動作を保証するテストコードを同じPull request内で用意することをマストとしています。

実装コードよりもテストケースやテストの内容に対するレビューの方が多いこともあります。

弊社ではテストのカバレッジに対しての大きな拘りは特にありませんが、カバレッジよりも重要視していることがあります。

それは、そのテストが「守る」テストになっているかどうかです。通るべき時に通り、コケるべき時にコケるテストになっているかどうかが重要なのです。

例えば正常系の次のようなテストコードがあるとします。

const mockOnSuccessCallback = jest.fn();
const mockOnErrorCallback = jest.fn();
const { result } = renderHook(() => useHoge({ onSuccessCallback: mockOnSuccessCallback, onErrorCallback: mockOnErrorCallback }));

act(() => {
  result.current.handleSubmit();
});

expect(mockOnSuccessCallback).toHaveBeenCalledWith('test');

hooksの関数を実行し、成功時に引数で渡したコールバック関数が実行されることを確認しています。

一見何の変哲もないテストコードですが、このテストコードには次の視点が抜けており、守るテストとしては不十分なのです。

  • 成功時の関数が複数回実行されたとしても通ってしまう
  • エラー時のコールバック関数が実行されたとしても通ってしまう

こういったケースでは、弊社では次のようなテストコードが良いとされています。

const mockOnSuccessCallback = jest.fn();
const mockOnErrorCallback = jest.fn();
const { result } = renderHook(() => useHoge({ onSuccessCallback: mockOnSuccessCallback, onErrorCallback: mockOnErrorCallback }));

act(() => {
  result.current.handleSubmit();
});

expect(mockOnSuccessCallback).toHaveBeenCalledTimes(1);
expect(mockOnSuccessCallback).toHaveBeenCalledWith('test');
expect(mockOnErrorCallback).not.toHaveBeenCalled();

このテストコードにより、「成功時に1回だけコールバック関数がパラメータ付きで実行され、エラーのコールバック関数が実行されない」ことを守ることができます。

この2つのテストコードのカバレッジは変わりませんが、テストとして守ることができる内容が異なります。

弊社ではカバレッジよりもテストが守る内容の方に重きを置いており、結果としてカバレッジが上がっているだけなのです。

弊社のリポジトリではテストカバレッジが90%を超えているものも珍しくないですが、それはカバレッジを意識してテストを書いたのではなく、守るテストを書き続けることによって勝手に上がったものなのです。

このような一定の品質以上のテストコードが存在することにより、ライブラリのバージョンアップや既存処理のリファクタなどは「CI通れば即mergeでOK」という文化が根付いています。

全ての修正に対して動作確認を行うことは無く、テストコードのお陰でスピードと品質の両方を担保する状況を維持し続けることを実現しているのです。

CI/CD

弊社のCIのほとんどはGitHub Actionsで統一しています。

弊社ではPull requestが作成されるたびにテストやLinterなどを実行し、レビューの効率化やコードの品質を一定に保つようにしています。

高速化

CIの速度には非常に拘っており、様々な高速化の工夫をしています。CIの実行速度が遅いと、Pull requestをmergeするまでの時間が長くなってしまい、結果的にクリティカルパスになってしまうからです。

1つ目はキャッシュの活用です。GitHub Actionsが提供している各種パッケージをキャッシュする仕組みを利用することで、CIのセットアップに必要な時間を削減することが出来ます。

弊社ではいくつかのプロジェクトに Nx を導入しており、Nxが持つ 変更検知機能リモートキャッシュ機能 によってCIを大幅に高速化しました。

2つ目はテストコードの実行を並列化することです。GitHub Actionsのmatrixと呼ばれる機能 を利用し、CIのワークフローそのものを並列実行できるようにしています。

テストファイルをいくつかのグループに分類し、それぞれのワークフローにグループごとのテストファイルを割り当てる独自のスクリプトを用意しています。

1つのワークフロー内で全てのテストファイルを実行してしまうと、テストファイルの数に比例してCIの実行時間が長くなっていきます。

しかしグループ分けをしてワークフローを並列実行することで、CIのトータルでの実行時間はほとんど変わりませんが、CIが完了するまでの時間を一定に保つことが出来るようになります。

詳細は 別記事 の方でも紹介しているので、興味がある方はそちらもご覧ください。

3つ目はGitHub Actionsのワークフローが実行される runnerのスペックを上げる ことです。

runnerのCPUのコア数やメモリを増やしたマシンを利用できるように設定し、テストコードの実行コマンドを見直して、同じワークフロー内でもテストコードを並列実行できるようにしています。

つまりワークフローそのものと、ワークフロー内の両方の並列化を実現しています。これにより、弊社では最大で40個のテストファイルを並列実行しているリポジトリもあります。

runnerのスペックを上げることにより利用料金の大幅な増額が予想されますが、GitHub Actionsの課金はrunnerの総実行時間に対して行われており、ワークフロー自体の実行が高速化される分、結果的に総実行時間が短くなり、課金額が必要以上に膨らんでしまうことを防いでいます。

自動化

CI高速化以外にも業務の効率化のため、ほぼ全てのプロジェクトにリリース作業を自動化する仕組みを取り入れています。GitHub Actionsのワークフローを手動実行するだけで、リリース用のPull requestが自動生成され、それをmergeするだけで自動的にstaging/production環境へのデプロイが実行されるようになっています。

更にPull requestのLabelsやAssigneesを自動で設定するようにもしています。

このように手間とコストを掛けてでもCI/CDの高速化と自動化の両方を実現させています。

通知

何かしらの異変、変化、依頼があった際に、そのタイミングでSlackに通知が飛ぶようになっています。

まずエラーや障害の検知です。本番環境でエラーや障害、各種負荷の増加が発生した際に、SentryやDatadogを通じてSlackに通知を飛ばすようになっています。この仕組みによって不具合や障害に最初に気づくまでの時間が短縮され、修正コードを本番環境にデプロイされるまでの時間が短縮されます。

次に開発時の通知です。GitHubのWebhookを通じて、Issueが作られた時やコメントが追加された際にSlackにメンション付きでリアルタイムで通知するようにしました。

特に効果が大きかったのはPull requestのレビュー依頼をメンション付きで通知することです。この仕組みによりファーストレビューまでの時間が短縮され、結果的にPull requestがマージされるまでの時間が短縮されました。

本来であればGitHubの公式APPを利用して通知を送信すればよいのですが、弊社では独自に開発したスクリプトを利用しています。

公式APPは通知内容の情報も入ってくるので一見すると便利ですが、弊社の開発スピードだと通知が多すぎて、逆に通知の内容が流れすぎてしまうことがありました。

そのため、もっとシンプルな内容を通知してくれる独自スクリプトを用いて通知を送信しています。

不具合や障害、レビュー依頼もコメントも、気づかないとそこから何も動きがありません。気づかないことがクリティカルパスになるのです。

まとめ

いかがでしたでしょうか?

弊社では開発生産性や開発スピードに重きを置いており、そのためには手間とコストを惜しまず日々改善を続けています。

現在、ファインディでは一緒に働くメンバーを募集中です。

興味がある方はこちらから ↓ herp.careers

また、6月28日(金)・29日(土)に『LeanとDevOpsの科学』の著者であるNicole Forsgrenの来日、テスラ共同創業者元CTOの登壇など、国内外の開発生産性に関する最新の知見が集まるConferenceを開催します。

開発生産性に関する他の企業の取り組みや海外の事例に興味がある方は、ぜひお申し込みください!

dev-productivity-con.findy-code.io

RubyKaigi 2024で感じた、Rubyを書くことが楽しい、という想いが将来のキャリアに絶対につながる、という話

こんにちは。ファインディでVPoEをしている神谷です。

沖縄、楽しかったですね、

観光、しましたか?

私は龍が如く3の世界観を感じようと最終日に国際通りを1時間ほど散策しました。(そしてすぐに飛行機の時間・・・ )

龍が如く8の名所巡りもしたいので、再来年のRubyKaigiは是非ハワイ開催にならないかな・・・

この記事では、RubyKaigi 2024に参加した皆さんがそれぞれ感じた、

Rubyを書くことが楽しい

Rubyコミュニティに参加することが楽しい

これらの想いが将来のキャリアに絶対につながる、という話を、普段採用に関わる私の目線で書こうと思います。

もちろんRubyKaigi不参加の方でも、将来のキャリアに悩んでいる方に読んでいただきたい内容です。

筆者はどんな人?

私のRuby歴はRailsがバージョン1.0の時代からなので、18年ほどになります。

RubyKaigiは2014年に初参加したのを覚えています。

ファインディでは2018年からスポンサーをする機会があり、2022年から3年連続ブース出展をしています。ファインディブースで話して頂けた方々、ありがとうございます。

ファインディのブースの取り組みは、DevRelのまっきーがnoteに公開しているので、そちらを参考にしていただけると嬉しいです。

note.com

普段の業務はVPoEとして組織作りがミッションであり、採用はその中心となります。

2023年では私1人の面談・面接の回数が1年間で450を越え、入社予定も含めると40名規模のエンジニア組織になりました。お時間頂いた皆様、ありがとうございます。

その中で、Rubyistとの面談も3割ほどはあるのですが、RubyKaigi参加したことありますか?という質問に対して、参加していますと回答されるのは体感10%ほどです。

ご家庭事情や業務都合で参加できない方も多いと思いますが、今回のRubyKaigiに初参加の方も多数いらっしゃいました。初参加の方に参加理由を聞いてみたところ、「スポンサー企業に転職したのでついに参加できました」という方も何名かいらっしゃいました。

1000人以上のキャリアと向き合った中で感じること

RubyKaigiのようなカンファレンスに参加されない方が多い中で感じる課題感となりますが、経験年数が長い方でもRailsのソースコードを読む経験が一度も無いという方や、gemのソースコードを追った経験が無い方も多くいました。Rubyは業務で使う言語なので、プライベートでは書かないようにしています、と宣言される方もいました。

私が面接の中でよく聞く質問として、「メタプログラミングの中で好きなテクニックはありますか?」があります。

この質問に対する回答として

「先輩からメタプログラミングは絶対に使ってはいけないと教わったので、それ以来触っていません」

「Ruby始めた頃に使ってはいけないと教えられました。それ以外、業務で使うことが無いので、特に勉強する機会がありませんでした」

このような回答が多いです。

メタプログラミングは使い方を誤ると後で苦労することは実際にありますが、だからと言って勉強しないと決めてしまうのは非常に残念です。

技術として難しくもあるが楽しいものであり、理解が深まるたびにプログラミングを好きになる可能性もあると思っています。

Railsや様々なgemにはこの技術が有効利用されており、知らないとソースコードを読むのも非常に苦労するでしょう。

逆に、シンプルにプログラミングや技術を楽しんでいる方とお話するのはこちらも嬉しい気持ちになります。

余談:「okuribito」gemの開発者であり弊社社員の@shakemurasanさんと面接した時は、method_missing の使い方のディスカッションが中心となりました。めちゃめちゃ楽しかったです。

そんな彼の記事はこちら↓

tech.findy.co.jp

弊社で活躍しているエンジニア、私が過去にご一緒した素晴らしいエンジニアの方々、全員に共通して言えることは、仕事とか関係なく技術そのものを楽しみながら探求している方が多いなと感じています。

楽しめることによって技術力が向上し、その技術力を使って課題解決し、顧客に価値提供していくという良いスパイラルが生まれているのではないでしょうか。

RubyKaigi 2024で感じたこと

今回のRubyKaigi 2024で感じたことは、裏テーマとして「Ruby楽しもうぜ」があったのかな?と思うくらい、Rubyや技術を全力で楽しんでいる方々の登壇が多かった印象です。

特に初日の@tompngさんの「Writing Weird Code」のキーノート。

最後に感動すら待っている、非常に素晴らしいキーノートでした。

何をやっているか全く分からなかったが(褒め言葉)、Rubyが楽しい言語だということが分かった、そう感じた方も多くいたと思います。

普段業務で使うプログラミング言語の1つ、多くの方にはそれだけのことかもしれませんが、言語そのものを楽しめることによって、エンジニアとしての成長速度が早くなると思っています。楽しめている方と楽しめていない方、長期スパンで見れば見るほどこの差は大きくなるのではないでしょうか。

また、コミュニティに参加することによって自分自身のスキルの立ち位置も把握しやすくなり、スキルの立ち位置を把握することこそが、スキルアップの近道だと思っています。

Ruby/Rails書籍の著者の方やgemの作者、コミッターの方々、本当に様々な方と(drink upなどを通して)お話する機会が多くあり、それがモチベーションにもつながっていきます。

Rubyを書くことが楽しい

Rubyコミュニティに参加することが楽しい

これらのシンプルなことが今後のキャリア形成において非常に重要なのでは、とあらためて感じたのでこのような記事にしてみました。

将来のキャリアに悩んでいる方、初心に立ち返り、自分が技術を楽しめているか振り返ってみるのも良いかもしれません。

最後に

5/28(火)に、「After RubyKaigi 2024〜メドピア、ZOZO、Findy〜」として、メドピア株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2024の振り返りを行います。

オンライン・オフラインどちらもあり、LTやパネルディスカッションなどコンテンツ盛りだくさんなのでぜひご参加ください!!

findy.connpass.com

ファインディでは、これからもRubyを積極的に活用して、Rubyとともに成長していければと考えております。

そして、一緒に働くメンバーを絶賛募集中です。

興味がある方はこちらから ↓

herp.careers

RubyKaigi 2024をきっかけにQuineに入門してみた

ハイサイ、ファインディでTeam+を開発しているEND(@aiandrox)です。

RubyKaigi 2024最高でしたね!!私は2度目の参加でしたが、去年よりもみんなが笑っているところで笑えるようになり、各種イベントなどでいろんな方と話すことができたのでさらに楽しめました。

今回特に印象に残ったのは初日のKeynoteの「Writing Weird Code」でした。

「なるほどわからん」という感じで、正直半分以上わからなかったです。ただ、描画されたコードが格好よくてめちゃくちゃ感動しました。何が起きているのかはわからなかったけど、なんか浪漫のようなものを感じました。

せっかくRubyKaigiでQuineというものを知ったのだから、自分にどこまでできるのかはわからないけどやってみたい!ということで、Quineに挑戦してみました。

Quine(クワイン)とは

Quineとは、自分自身のソースコードを出力するプログラムのことです。

すごい見た目でなければいけないのかと思いきや、Quine自体にデザインはなく、catと実行時の出力結果が同一になるものをQuineと呼びます。

eval$s=%w'o="eval$s=%w"<<39<<$s<<39<<".join";puts(o)'.join

しかし、私がやりたいのは見た目が格好いいやつなので、今回はデザインQuineを作るのを目標にしました。

いざ実践

まずは何からすればいいのか?ですが、最初に読むには「あなたの知らない超絶技巧プログラミングの世界」が一番わかりやすかったです。とりあえずこれを読んで手元で実行しながら、ざっくりとQuineの概要と考え方を頭に入れます。半分くらいは理解できていませんが気にせず進めました。

その後、他の実装記事なども読みつつ実際にコードを書いてみました。RubyでうどんげQuine(とAA型Quineの作り方講座)で、AAのQuineを作る方法が紹介されていたので、それを参考にしました。詳細な作り方はこちらの記事に書いてあります。

まず、使いたい画像をAAに変換します。今回はFindyのロゴを使用しました。

その後、形を整えつつAAを01に置き換え、Quineを作成するためのbuild.rbを作成しました。

細かいロジックは参考元*1の記事にあるので省略しますが、簡潔に書くと以下の通りです。

  1. 圧縮したAAデータbinを元に、空白またはコード1文字分をoに追加していく
  2. 最初にeval$s=%w'、最後に'.joinがあるので、その中のコードは空白を削除されたうえでRubyコードとして実行可能
# build.rb

aa = <<~END
  00000000000111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
  00000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
  00000111111111111111111100000000000001111111111111111100011111100000000000000000000000000000000000011111000000000000000000000
  00011111110000000001111111000000000001111111111111111100011111100000000000000000000000000000000000011111000000000000000000000
  00111111000000000000011111100000000001111110000000000000000000000001110000000000000000000000000000111110000111000000000111000
  00111110011110000000000111110000000001111110000000000000011111100001111111111111110000000111111111111111000111111000001111110
  01111111111100000000000111110000000001111110000000000000011111100001111111111111111100001111111111111111000011111000011111100
  01111111111000000000000011110000000001111111111111110000011111100001111110000011111100011111110000111111000011111100111111000
  01111111000000000000000111110000000001111111111111110000011111100001111100000011111100011111000000011111000001111111111110000
  00111110000000000000000111110000000001111111111111110000011111100001111100000001111100011111000000011111000000111111111100000
  00111111000000000000011111100000000001111110000000000000011111100001111100000001111100011111100000111111000000011111111000000
  00011111111000000011111111000000000001111110000000000000011111100001111100000001111100001111111111111111000000001111110000000
  00000111111111111111111111110000000001111110000000000000011111100001111100000001111100000111111111111111000000001111100000000
  00000000111111111111100011111000000000111110000000000000001111100000111100000001111000000000111111001111000000011111100000000
  00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111111000000000
END

start_text = 'eval$s=%w'
end_text = '.join'

aa_data = aa.split("\n")
x_length = aa_data.first.length
y_length = aa_data.length
last_point = aa_data.last.split('1').last.length # 最終行に1がないパターンは考慮しない

bits = aa.gsub("\n", '').reverse.to_i(2)

bin = [Marshal.dump(bits)].pack('m').gsub("\n", '')

code = <<~CODE
  b="#{bin}"
  n=Marshal.load(b.unpack("m")[0])
  e="#{start_text}"<<39<<($s*3)
  o=""
  j=-1
  0.upto(#{y_length}*#{x_length}-1){|i|
    o<<((n[i]==1)?e[j+=1]:32)
    o<<((i%#{x_length}==(#{x_length - 1}))?10:"")
  }
  o[-#{last_point + end_text.length + 2},6]=""<<39<<"#{end_text}"
  puts(o)
CODE

code = code.split("\n").join(';')
code << '#'

file = File.new('quine_base.rb', 'w')
file.puts "#{start_text}'#{code}'#{end_text}"

これによって作成されたコードを実行すると、以下の出力を得られます。出力結果をquine.rbに記述して、ruby quine.rbを実行すると、同じ結果になります。

これにてQuineの完成です🎉

           eval$s=
       %w'b="BAhsK3oA+
     AMAAAAAAAAAAAAAAAAA             8P8HAAAAAAAAAAAAA   AAAgP/                                    /A4D/
   //gBAAA         A4AMAAP           wB/AHw/x8/AAAAAHw   AAMAPA                                    H4Afg
  AAgAMA             AMCHAz          j4PAAf                        wA8                            A/PD/    B/z         /8Y
  Of/wP  gA/g          BgB/+         /8P/P3              z48T8A    eAD/f/DDD3784Ye       fH/4AgA/g/w9++M   CPD/jg     /4EPAP
 AB/P/BDx/w8           QEf+B         /wA4Af              gB8A+O    EDPn7wA/4B/AP+AfA    DAD98wIf/f4AfAP7    //wB+    AOCHD/
 jg/w/wAQD+             Pz6A         DwD44AEP4OcBPwA     AAAAAA    AAAAAA     AAAAA8   AM=";n=    Marsha    l.load  (b.unp
 ack("m"               )[0])         ;e="eval$s=%w"<     <39<<(    $s*3)      ;o="";   j=-1;       0.upt     o(15*125-1){
  |i|;o                <<((n         [i]==1)?e[j+=1]     :32);o    <<((i       %125=   =(124       ))?10      :"");};o[-
  16,6]=             ""<<39          <<".jo              in";pu    ts(o)       #b="B   AhsK3o     A+AMAA       AAAAAAAA
   AAAAAAA8       P8HAAAAA           AAAAAA              AAAAAg    P//A4       D///g    BAAAA4AMAAPwB/AH        w/x8/A
     AAAAHwAAMAPAH4AfgAAgAMA         AMCHAz              j4PAAf    wA8A/       PD/B/     z/8YOf/wPgA/gBg        B/+/8
        P/P3z48T8AeAD   /f/DD         D3784               YefH/     4AgA       /g/w         9++MCP  D/jg       /4EPAP
                                                                                                              '.join

ここまではわりとスムーズにできました。eval$s=%w'から'.join'まではRubyコードなので、#の後はコメントアウトとして好きな文字を入れることができます。例えば、code変数内のeを変更すると、puts(0)以降を#で埋めることができます。

e="#{start_text}"<<39<<($s+"#"*500)
           eval$s=                                                                                                           
       %w'b="BAhsK3oA+                                                                                                       
     AMAAAAAAAAAAAAAAAAA             8P8HAAAAAAAAAAAAA   AAAgP/                                    /A4D/                     
   //gBAAA         A4AMAAP           wB/AHw/x8/AAAAAHw   AAMAPA                                    H4Afg                     
  AAgAMA             AMCHAz          j4PAAf                        wA8                            A/PD/    B/z         /8Y   
  Of/wP  gA/g          BgB/+         /8P/P3              z48T8A    eAD/f/DDD3784Ye       fH/4AgA/g/w9++M   CPD/jg     /4EPAP 
 AB/P/BDx/w8           QEf+B         /wA4Af              gB8A+O    EDPn7wA/4B/AP+AfA    DAD98wIf/f4AfAP7    //wB+    AOCHD/  
 jg/w/wAQD+             Pz6A         DwD44AEP4OcBPwA     AAAAAA    AAAAAA     AAAAA8   AM=";n=    Marsha    l.load  (b.unp   
 ack("m"               )[0])         ;e="eval$s=%w"<     <39<<(    $s+"#      "*500)   ;o=""       ;j=-1     ;0.upto(15*1    
  25-1)                {|i|;         o<<((n[i]==1)?e     [j+=1]    :32);       o<<((   i%125       ==(12      4))?10:"")     
  ;};o[-             16,6]=          ""<<39              <<".jo    in";p       uts(o   )#####     ######       ########      
   ########       ########           ######              ######    #####       #####    ################        ######       
     #######################         ######              ######    #####       #####     ###############        #####        
        #############   #####         #####               #####     ####       ####         ######  ####       ######        
                                                                                                              '.join 

色を付けてみる

とりあえず、ほぼコピペをすることでQuineが作成できたので、次は色を付けてみたいと思いました。evalの中は自由にコードが書けるので、わりと簡単にできるのでは?と思いましたが、そんなことはなく、かなり苦労しました。

こちらが今回作成した色付きQuineです。

実際のロゴと重ねるとこんな感じ。

コードはこちら↓ github.com

以下に、実際に作ってみて自分が感じた点について記載します。

データを圧縮するのが難しい

このQuineでは、AAを成形するためのデザインコードと色付けロジックに関するコードを持っています。また、それらを描画するコードもあるため、すべてをQuine内に収める必要があります。つまり、AAの字数 - (AAを描画するロジックの字数 + 色付けロジックの字数 + 描画コードの字数) > 0にしなければなりません。

Findyロゴをできるだけ大きくすることでコード文字数を確保し、色付けロジックをstringにして実行コード内でeval展開することで対応しました。

irb(main):018> colors_bin = [Marshal.dump(colors)].pack('m').gsub("\n", '')
=> "BAhbFG86ClJhbmdlCDoJZXhjbEY6CmJlZ2luaRI6CGVuZGkCZAFvOwAIOwZGOwdpAvQBOwhpAggCbzsACDsGRjsHaQKUAjsIaQKoAm87AAg7BkY7B2kCPgM7CGkCUgNvOwAIOwZGOwdpAuMDOwhpAvwDbzsACDsGRjsHaQKKBDsIaQKcBG87AAg7BkY7..."
irb(main):019> colors_text = colors.to_s.gsub(' ', '').gsub("\n", '')
=> "[13..356,500..520,660..680,830..850,995..1020,1162..1180,1328..1345,1495..1510,1660..1675,1825..1840,1990..2005,2155..2170,2320..2336,2490..2506,2655..2669]"
irb(main):020> colors_bin.length
=> 408
irb(main):021> colors_text.length
=> 156 # 252字の削減!

「Ruby Committers and the World」で`frozen string literalの話題のときにmametterさんが話していた「1byteを切り詰めている」とはこういうことかと思いました。

また、AAロジックをQuine内で持つことで、AA成形コードを削減することができるようですが、これについてはどうすればいいのかわからず断念しました。

文字コードの意識が難しい

出力する文字列を、実行コード・出力両方として扱う関係上、そのまま使うことができない文字もあります。そういったものは、ASCIIコードを使うことでエラーを回避します。

例えば、"\e[34m"などのエスケープシーケンスを使うことで出力文字に色を付けることができます。

しかし、\eをそのまま記述すると文字列として解釈されます。そのため、生成されたQuineを実行するとそのまま文字列が出力されてしまいます。

(1敗)

しかし、27.chr"\e"と同値を出力することができます。*2ちなみに、#chrで文字列に変換しているのは+メソッドを使うためなので、<<で文字連結する場合は文字コードの整数のままで問題ありません。

エスケープシーケンスを追加することにより、細かい位置の調整が難しくなる

$sに代入する%wで空白を許容した文字列を改めて実行可能なコードにするために、AAのコードの終わりの箇所に'.joinを挿入しています。しかし、エスケープシーケンスのそれぞれの文字列が1文字として扱われるため、最後の調整が難しかったです。

今回は以下のようにしましたが、AAが変わると若干崩れるので、最後は手作業で地道に調整することになりそうです。

# build.rb

end_text = '.join'
end_text_start_point = last_point + end_text.length
output_text_length = "\e[0m#\e[m".length # エスケープシーケンスを使って白字を1字出力する必要な字数(`#`は仮の文字列)
real_output_text_length = end_text.length * output_text_length # エスケープシーケンスを考慮した上で確保すべき文字数

# (一部省略)
code = <<CODE
  l=#{"'".ord}.chr
  o[-#{end_text_start_point + real_output_text_length},#{(end_text.length + 1) * output_text_length}]=l+"#{end_text}"
CODE

感想

eval内ではRubyのコードを素直に書くことができるので、超絶技巧を使わなくてもQuineでAAを書くことはできました。

ある程度完成しないとコード自体が動かないのでデバッグしづらいし、理解できていない部分もたくさんあります(irbとはずっと付きっきりでした) 。でも、コードを書いていい感じの見た目ができるのがとても楽しかったです!!

おまけ

この記事をレビューしてもらったところ、早速カスタマイズLGTMをいただきました🙌

最後に

5/28(火)に、「After RubyKaigi 2024〜メドピア、ZOZO、Findy〜」として、メドピア株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2024の振り返りを行います。

オンライン・オフラインどちらもあり、LTやパネルディスカッションなどコンテンツ盛りだくさんなのでぜひご参加ください!!

findy.connpass.com

また、ファインディでは、これからも新しいものを取り入れつつ、Rubyを積極的に活用して、Rubyとともに成長していければと考えております。

一緒に働くメンバーも絶賛募集中なので、興味がある方はぜひこちらから ↓

herp.careers

RubyKaigiで紹介されたクリエイティブコーディングを試してみた

こんにちは!ファインディでTeam+開発チームのエンジニアメンバーの西村です。

この記事では、私が聞いたRubyKaigi 2024のセッション「Lightning Talks」より「Enjoy Creative Coding with Ruby」で紹介されたクリエイティブコーディングを試してみたので共有します。

クリエイティブコーディングとは

クリエイティブコーディングとは、アプリケーションのような機能的なソフトウェアを作るのではなく、プログラミング言語を使ってビジュアルアートを創作することです。

クリエイティブコーディングをはじめるまでの背景

私は、RubyKaigi 2024の「Lightning Talks」より「Enjoy Creative Coding with Ruby」で、初めてクリエイティブコーディングについて知りました。

Miyuki Koshibaさんのスライド資料を引用させていただいています。

スライド内にある作例を見て、自分も創りたい!!と思う素敵LTでした!!

speakerdeck.com

DAY2の終わりに、クリエイティブコーディングを試しました。

すると、LTの内容通り、数行のコードでブラウザ上に図形を描画できたので、とてもワクワクしました!

もう少し凝ったものを創りたいと思い、ファインディのロゴを創ることにしました。

セットアップ

index.htmlに必要なスクリプトを読み込ませるだけで始めることができます。ローカルPCにRubyやほかのライブラリをインストールすることは不要です。

これをもとに、index.htmlを作成します。

github.com

scriptタグには、次のライブラリを読み込ませる必要があります。

  • p5jsライブラリ
    • ブラウザ上で円、四角や三角関数を使った図形等を描画できるようにするライブラリ
  • p5rbライブラリ
    • p5jsとRubyを橋渡ししてくれるライブラリ
  • ruby.wasm
    • ブラウザ上でRubyを実行できるライブラリ

<script type="text/ruby">タグ内に書いたRubyコードが実行され、<main>タグ内に描画されます。

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@next/dist/browser.script.iife.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
    <script type="text/ruby" src="p5.rb"></script>
    <script type="text/ruby">
      # ここにRubyで処理を書く
    </script>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>

創ったもの

ファインディのロゴをクリエイティブコーディングで描きました。

創作物は外部公開しています。

リポジトリは、こちら↓

github.com

どう創ったのか

ライブラリが対応している図形を組み合せることで、ファインディのロゴを創れそうだなぁと思いました。

ひとつずつ解説していきます。

まずは、外円部分を描画します。

def draw
  # 色設定
  gray = color(165, 165, 164)
  white = color(255, 255, 255)
  blue = color(26, 90, 164)
  background(white)

  # 輪郭を消す
  noStroke()

  # 外円
  fill(gray)
  arc(200, 200, 300, 300, radians(0), radians(360), PIE)
  fill(blue)
  arc(200, 200, 300, 300, radians(130), radians(300), PIE)
  # くり抜き
  fill(white)
  circle(200, 200, 210)
end

つづいて、外円部分の青い部分の端を鋭角にします。

外円とくり抜きの間に下のコードを追加します。

# 外円
...
fill(gray)
arc(284, 115.5, 150, 90, radians(190), radians(260), PIE)
fill(blue)
arc(96, 270.3, 150, 90, radians(-60), radians(80), PIE)

# くり抜き

つづいて、外円から横に伸びる青い部分を描画します。

※ 青い部分はFindyのFを表しています。

このFの横棒が一番難しかったです。

複数の円を組み合わせて描画する必要があり、それぞれの図形の位置を調整することが大変でした。

# くり抜き
...
# 外円から横に伸びる青い部分
fill(blue)
rotate(-0.15)
arc(127, 270, 200, 200, radians(200), radians(290), PIE)
fill(white)
arc(127, 270, 132, 132, radians(200), radians(360), PIE)
arc(190, 231, 125, 125, radians(200), radians(250), PIE)

最後に、外円の右下に四角形を描画すれば、完成です!🎉

# 外円から横に伸びる青い部分
...
# 外円から伸びる四角形
fill(gray)
translate(width / 1.25, height / 1.25)
rotate(radians(55))
rectMode(CENTER)
rect(-5, 75, 70, 45)

このように部品ごとにコーディングしていくと、簡単に創ることができます!

デザインチェック

本物のロゴと並べて、デザイナーにチェックしてもらいましたが、Fの文字の形を中心にたくさん突っ込まれました。

いただいたフィールドバックの一例です!

  • 「Fの2本の横棒の右端の角度がいまひとつ」
  • 「外円から横に伸びる青い部分の描画が甘い」(画像を拡大しながら)
本物のファインディのロゴ
クリエイティブコーディング作成ロゴ

自分のクリエイティブコーディングの技術力はまだまだなので、今後向上させていきたいです!

終わりに

Xの#creativecodingを見ると、様々なクリエイティブコーディングが投稿されています。

気になる方は是非見てみてください。

また、少しでもクリエイティブコーディングに興味を持っていただけたら、嬉しいです!


5/28には「After RubyKaigi 2024〜メドピア、ZOZO、Findy〜」として、メドピア株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2024の振り返りを行います。

LTやパネルディスカッションなどコンテンツ盛りだくさんなのでぜひご参加ください!!

findy.connpass.com

ファインディでは、これからも新しいものを取り入れつつ、Rubyを積極的に活用して、Rubyとともに成長していければと考えております。

そして、一緒に働くメンバーを絶賛募集中です。

興味がある方はこちらから ↓

herp.careers

RubyKaigi 2024のファインディブースで出したRuby Code Quiz解説

こんにちは、あるいはこんばんは。 @gessy0129です。

沖縄、行ってきました!

観光、しませんでした!

とても楽しかったです!

今回、ファインディブースでは、Ruby歴×Ruby関連のカンファレンス参加回数は?というアンケートと日替わりでRuby Code Quizを実施しました! 全体的なブースの話はDevRelのまっきーがまとめているのでそちらをご覧ください!

note.com

本記事では、エンジニアなら気になるであろうFindy Ruby Code Quizの解説をしていきたいと思います!では、早速見ていきましょう!

一日目 - Rubyバージョンの組み合わせ

問題

下記、実行結果のRubyバージョンの組み合わせを答えてください。 なお、ひとつのバージョンはひとつの選択肢にのみ回答できます。

Ruby versions: 3.3.0 or 3.2.0 or 3.1.0 or 3.0.0

irb> Time.new("2024-05-15T11:11:11Z")
=> 2024-05-15 11:11:11 UTC

irb> Time.new("2024-05-15T11:11:11Z")
=> 2024-01-01 00:00:00 +0000

irb> Time.new("2024-05-15T11:11:11Z")
<internal:timev>:310:in `initialize': invalid value for Integer(): "2024-05-15T11:11:11Z" (ArgumentError)

irb> Time.new("2024-05-15")
<internal:timev>:409:in `initialize': no time information (ArgumentError)

選択肢

  1. ①: 3.2.0、②: 3.0.0、③: 3.1.0、④: 3.3.0
  2. ①: 3.3.0、②: 3.2.0、③: 3.1.0、④: 3.0.0
  3. ①: 3.1.0、②: 3.0.0、③: 3.2.0、④: 3.3.0
  4. ①: 3.0.0、②: 3.1.0、③: 3.3.0、④: 3.2.0

正解と回答率

正解

  • ✅ 1. ①: 3.2.0、②: 3.0.0、③: 3.1.0、④: 3.3.0

回答率

  1. 14.15%
  2. 53.77%
  3. 19.81%
  4. 12.26%

解説

RubyのバージョンアップのNEWSを追いかけていれば解けるという問題です。

Ruby3.3.0のNEWS
https://github.com/ruby/ruby/blob/v3_3_0/NEWS.md

Time.new('2023-12-20')
#  no time information (ArgumentError)

これを知っていたら④が3.3.0だとわかります。

Ruby3.2.0のNEWS
https://github.com/ruby/ruby/blob/v3_2_0/NEWS.md

Time.new now can parse a string like generated by Time#inspect and return a Time instance based on the given argument. 

Time.newはinspectが出力するstringからパースできるようになった。これで、①は3.2.0以上とわかります。

Ruby3.1.0のNEWS
https://github.com/ruby/ruby/blob/v3_1_0/NEWS.md

At the same time, time component strings are converted to integers more strictly now.
Time.new(2021, 12, 25, "+07:30")
#=> invalid value for Integer(): "+07:30" (ArgumentError)
Ruby 3.0 or earlier returned probably unexpected result 2021-12-25 07:00:00, not 2021-12-25 07:30:00 nor 2021-12-25 00:00:00 +07:30.

そもそも3.1.0までは文字列だけをいれるユースケースはなかったようです。3.0.0以前は文字列だけだとよくわからない挙動になっていたけど、この対応でチェックを厳密にしたことで文字列だけだとエラーになったのではないかと推測します。変更意図などはCommiterの方々に解説求めたい部分です。

ただ、このNEWSの内容を全てを記憶してるのは難しすぎになってしまうので、回答の選択肢を4択にしています。4択なので3.3.0の挙動を知っていたら1番目と3番目の2択に絞れると思います。
これに加えて、3.2.0の挙動を知っていたら①は3.2.0以上とわかるので1番目が答えだとわかります。また、3.2.0の挙動を知らなかったとしても、①〜③は全く同じコマンドを実行しており①が正しく挙動しているので①の方が新しいバージョンだと推測できます。これでも1番目が答えだとわかるようになってます。

二日目 - 代入されているオブジェクトはどれ?

問題

fooに代入されているオブジェクトは次のうちどれでしょうか? (Ruby: 3.3.0で実行した場合)

irb(main):001> foo = ???
irb(main):002> foo.include?(3/2)
=> true
irb(main):003> (foo + [3]).inject(:+)
=> 9
irb(main):004> foo.last
=> NoMethodError

選択肢

  1. 1..3
  2. [1, 2, 3]
  3. Set.new([1, 2, 3])
  4. Enumerator.new { |y| [1, 2, 3].each { |i| y << i } }

正解と回答率

正解

  • ✅ 4. Enumerator.new { |y| [1, 2, 3].each { |i| y << i } }

回答率

  1. 25.37%
  2. 4.88%
  3. 39.02%
  4. 30.73%

解説

メソッドに対しての出力結果から元の値を当てるという問題形式を使ってみたかった問題です。 そこから逆算して、ちょっと似ているクラス(Range, Array, Set, Enumerator)を当てるようにしてみました。

irb(main):002 の出力は引っ掛けで、3/2 => 1になるのですべてtrueになる。
が、直感的には3/2 => 1.5という考えを利用して、1..3では?と思わせて問題を難しくしました。

irb(main):003 は、同じ値は重複しないというSetの特徴を利用から、Setを除外できるようになっている。また、Rangeには+メソッドがないのでエラーになる。

irb(main):004 は、Setには順番がなく、Enumeratorは外部イテレータなので最後の要素が特定できないという特徴を利用している。

実行結果を見るのが一番早いですね。

選択肢1

irb(main):001> foo = 1..3
=> 1..3
irb(main):002> foo.include?(3/2)
=> true
irb(main):003> (foo + [3]).inject(:+)
(irb):3:in `<main>': undefined method `+' for an instance of Range (NoMethodError)
irb(main):004> foo.last
=> 3

Rangeには+メソッドがないのでNoMethodErrorになるのと、lastメソッドがあるので結果が返ってきます。

選択肢2

irb(main):001> foo = [1, 2, 3]
=> [1, 2, 3]
irb(main):002> foo.include?(3/2)
=> true
irb(main):003> (foo + [3]).inject(:+)
=> 9
irb(main):004> foo.last
=> 3

lastメソッドがあるので結果が返ってきます。

選択肢3

irb(main):001> foo = Set.new([1, 2, 3])
=> #<Set: {1, 2, 3}>
irb(main):002> foo.include?(3/2)
=> true
irb(main):003> (foo + [3]).inject(:+)
=> 6
irb(main):004> foo.last
(irb):4:in `<main>': undefined method `last' for an instance of Set (NoMethodError)

同じ値は重複しないというSetの特徴から003の結果が6になります。

選択肢4

irb(main):001> foo = Enumerator.new { |y| [1, 2, 3].each { |i| y << i } }
=> #<Enumerator: ...>
irb(main):002> foo.include?(3/2)
=> true
irb(main):003>  (foo + [3]).inject(:+)
=> 9
irb(main):004>  foo.last
(irb):4:in `<main>': undefined method `last' for an instance of Enumerator (NoMethodError)

正解ですね!!

有識者の方に解説モトム

Setには順番がないと書きましたが、firstはあります。なので、こうなります。

irb(main):001> foo = Set.new([1, 2, 3])
=> #<Set: {1, 2, 3}>
irb(main):002> foo.first
=> 1

なんでfirstがあるのか、詳しい方のコメントを待っています。Enumerableを継承しているからと言われたらそれまでと言えばそれまでですがw

三日目 - 正しい出力結果は?

問題

次のコードを実行した出力結果で正しいのはどれでしょう? (文字コード UTF-8、Ruby: 3.3.0で実行した場合)

hoge = [?1,?2].zip([?3,?4], [?5,?6]).transpose.flatten
hoge.reject!
hoge = hoge.map{|n| n.ord}.sort
puts hoge.join(", ")

選択肢

  1. 1, 2, 3, 4, 5, 6
  2. 6, 5, 4, 3, 2, 1
  3. 49, 50, 51, 52, 53, 54
  4. 54, 53, 52, 51, 50, 49

正解と回答率

正解

  • ✅ 3. 49, 50, 51, 52, 53, 54

回答率

  1. 17.19%
  2. 14.06%
  3. 60.94%
  4. 7.81%

 解説

問題文のコードを読みにくくするため、わざとインデントやスペースを入れていない。というちょっとイジワルをしました。
[Integer, Integer]に対して、なにかしら変換するのはひっかけるポイントが少ないので、String→Integerへ変換するようにして、複雑さを増すようにしています。

# zipメソッドを使う人はあまりいなそうなので、採用
> hoge = [?1,?2].zip([?3,?4],[?5,?6])
=> [["1", "3", "5"], ["2", "4", "6"]]

# ノーマルなRubyistは2次元配列を扱うことが少ないはずなので、transposeメソッドを採用
irb(main):003> hoge = hoge.transpose
=> [["1", "2"], ["3", "4"], ["5", "6"]]

# Rubyistはflattenを結構使っている気がする。ここは配列を入れ子にしたくないから、採用
irb(main):004> hoge = hoge.flatten
=> ["1", "2", "3", "4", "5", "6"]

# なにかやっていそうだけど、Enumeratorに変えているだけ
irb(main):006> hoge = hoge.reject!
=> #<Enumerator: ...>

# ordメソッドが、StringからIntgerへ変換することを知っているかチェック
# sortメソッドが降順 or 昇順どちらで並び替えているか試す
irb(main):007> hoge = hoge.map{|n|n.ord}.sort
=> [49, 50, 51, 52, 53, 54]

irb(main):008> puts hoge.join(", ")
49, 50, 51, 52, 53, 54

さいごに

4/1 からクイズ作成を開始して、約4日ほどでほぼほぼ今の形まで出来上がりました。ラフで色んな案を出して、煮詰めていく過程がとても楽しかった(らしい)です。

また、 knuさんがブースに遊びに来てくれてPostしてくれていたのがとても嬉しかったです!来ていただいてありがとうございました!

問題は@hamchance0215@aiandrox@sontixyouが作成してくれました!ありがとうございました!

5/28には「After RubyKaigi 2024〜メドピア、ZOZO、Findy〜」として、メドピア株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2024の振り返りを行います。

LTやパネルディスカッションなどコンテンツ盛りだくさんなのでぜひご参加ください!!

findy.connpass.com

ファインディでは、これからもRubyを積極的に活用して、Rubyとともに成長していければと考えております。

そして、一緒に働くメンバーを絶賛募集中です。

興味がある方はこちらから ↓

herp.careers