インターンに迷っている方へ!Natureにおけるチーム開発の魅力

こんにちは。ファームウェアエンジニアインターン生の後藤です。 ちなみに以下の記事を書いた後藤さんとは違う後藤です。

engineering.nature.global

時が立つのは非常に早いもので、入社してから10ヶ月ほど立ったみたいです。 今年度で就職のため、ちょうど折り返し地点に来ました。
そこで中間報告として、新しくインターンに入るか迷っている方をターゲットに、入ったきっかけ、この会社のいいところ、コードレビューで学んだことを書いていこうと思います。

以下目次

入ろうと思ったきっかけ

もともとは飲食や塾講師といったバイトをしていたのですが、予定が合いづらくなりやめることになりました。
これを契機として、「自身の専門に紐付いたことをしてみようかな?」と感じたことや、当時就職活動が始まりつつあり、「ソフトウェアエンジニアを仕事として楽しめるのかどうかの見極めをしたほうが良いかな?」と感じたことから、インターンを探すことにしました。初インターンだったのでどきどきわくわくだったのを覚えています。

インターンを探す際に、当時2種類の開発を探していました。
1つ目はアルゴリズム開発です。 この頃の研究でグラフ理論を扱っており、その延長でアルゴリズム開発に興味がありました。
2つ目が組み込み開発です。 インターンを探し始める直前だったと思いますが、本屋でふと手に取った雑誌でマイコンの開発を知り、そこから組み込み開発という職種があることを知りました。 もともとHDL等を用いたハードウェア実装や、低レイヤでのソフトウェア実装に興味があったので、すごく面白そうな職種だなと感じたことを覚えています。

これらそれぞれ一社ずつ受けたのですが、最終的な決め手は当時読んでいた本の著者がいらっしゃったこと(!)です。 全く予想していなかった展開だったのですごく驚いたのを覚えています。*1

基礎から学ぶ 組込みRust | 中林 智之, 井田 健太 |本 | 通販 | Amazon

こちらの書籍、とても面白かったのでぜひ!!
業界的には資産的な事情から、まだまだC++が使われているようですが、Rustの組み込み開発も、特にembedded-halの安定化(v1)により実用に近づいてきているのではないでしょうか。クロスプラットフォームで開発環境の構築も最高に楽ですし、最初から抽象化を考えたフレームワークがあるのでコードの移植性も高いです。 (なにより言語がいいです!あくまで個人の感想ですが)

少し脱線しましたが、ここからは実際に入ってからの話になります。

Natureエンジニアインターンのいいところ

そんなこんなでありがたいことにインターンとして働くことになりました。 エンジニアインターンとして、この会社のいいところだなと感じていることを抜粋してみます。

シフトが自由

例えば大学院生だと論文投稿前はあまり時間が取れない場合があります。また、こうした時間の見積もりはなかなか計算通りには行かないものです。
通常のバイトですと、人が一人抜けると調整がかなり大変な場合が多いですが、Natureではこうした心配が一切なく振替などを柔軟にすることができます。
またオンラインでの業務が可能なので、インターンの前後に自分の作業が可能な点もすごく良い点だと感じます。

実際のプロダクトコードが見れる

ファームウェアエンジニアの場合、ファームウェアのメインリポジトリのコードは全部見ることができます。 どのようにしてサーバーと通信しているのか、どのようにしてセットアップしているのか等、 組み込み開発をやったことがなかった身として、いろいろな実装を眺めることができるのは非常に面白く参考になることばかりです。

経験豊富なエンジニアからのコードレビュー

最後にして最大の利点です。 私はチーム開発をしたことがなかったため、コードレビューはおろか人に見せるコードを書いたことがありませんでした。 もちろんそういった「良いコードの書き方」のような本は読んだことがあるものの、実践レベルでの理解などが追いついておらず 客観的には良くないコードだったのかなと感じています。

このような状態で望んだため、案の定レビューで(もちろんいい意味で)ボコボコにされることになりました(されています)。 レビューでは多くのことを現在進行系で学んでいます。
私が至らない点とその改善策をわかりやすく言語化していただけるため、 どうすれば良くなるのかの道筋があり、非常にありがたい環境だと感じています。

学んできた中で、最近意識していること

インターンをするとどういったことが学べるのかということが気になる方は多いと思います。
ありがたいことに、私はこの10ヶ月ほどメンターさんやファームチームの方々からこれまで様々なフィードバックをいただきました。 そこで、あくまで私個人の一例にはなりますが、フィードバックをうけて意識していることを、PRを出すときに意識していること、可読性のあるコードを書く上で意識していること、その他意識していることに分けて書いていきたいと思います。

PRを出すときに意識していること

私はPRの作り方が下手だったので、お時間をいただき以下の資料をもとに教えていただきました。

Gitのワークフローについての私のスタンス | おそらくはそれさえも平凡な日々
Design Doc の書き方 / How to Write a Design Doc (Ja ver.) - Speaker Deck

PRを小さくする、余計なものをなるべく混ぜない

実践している内容

  1. 周辺リファクタができそうな場合はまず行う
  2. 最小の一手を考え、ミニマムなテストとともにPRを出す
  3. 大きくなったときは一度リセットして、再度PRを出す

理由

Natureでは新機能の設計から任せていただける機会があります。 ですが、特に新機能開発でなにも考えずに実装すると、1つのPRに変更が乗りすぎてしまうということが起こります。
実体験ですが、大きなPRになってしまったゆえに、長い間マージされず、多くの依存関係をもったブランチが発生してしまいました。 こうしたことを防ぐためにも、PRをすぐにマージさせるということは大事になってきます。

ではどうすればよいのかという話ですが、これが結論の内容になります。
1つ目は準備段階です。
実装を書いていると、周辺のコードで気になる箇所が出てくる場合が多いです。 これは以前は気が付かなかったことや、追加を前提として書いていないことなど様々な事情があると思います。 このようなときに現在のPRとともに出してしまうと、本質的に関係のないことが乗ってしまいます。 このため、ある程度いじる箇所がわかっている場合はその周辺を見ておき、リファクタをしておくとうまく行くことが多いです。

2つ目は実装前段階です。
私は機能追加では、多くの変更をPRに乗せてしまいがちでした。 しかし、後から考えると切り離せる場合も多いです。 そして、これらは実装前によく考えていれば分離できていたようなことも多いです。 このことから本当に切り離すことが出来ないのかだったり、どのような手順でPRを出していこうかなどを一度立ち止まって考えるようにしています。 3つ目は、実装後や、PRを出してからの段階です。
もちろんすべて事前に考えることができるわけではありません。 PRを作ってから、もしくはレビューを頂いて直している時に、気がつくこともたくさんあります。 このような場合でも、スタックしそうな場合は一度リセットして、再度PRを出すようにしています。 結果的に、リセットしてしまうほうがクリティカルなレビューをいただけることが多く、早くマージされることが多いと感じています。

大きな設計方針の合意をある程度取ってからPRを作る(design docs)

実践している内容

  1. PRはあくまでミクロな詳細実装
  2. 機能するものを作るにあたり、全体の合意をとれてるといい。
  3. 各機能がどのようにして結びつくのかといった簡単なクラス図を作成
  4. 具体的すぎないように

理由

前述したとおり、PRは小さくすべきです。しかし、小さくしようと考えると、どうしてもミクロな視点になりがちです。 この視点のままでは全体のイメージがつかめないまま個々の実装を進めてしまい、組み合わせたときにうまく行かないといったことが発生してしまいます。
これを防ぐためにマクロな視点を並行して考えておくというのも大事になってきます。 もちろんマクロな設計は最初からうまくできるわけではないので、抽象度を高くしドキュメントの消費期限を長くすることが大事です。 抽象度の目安としては、各モジュールの役割や、それらがどのように結びつくのかがわかる程度が適切だと教わりました。

可読性のあるコードを書くという点で意識していること

機能が実装できることはもちろんですが、継続して保守できるかということがかなり強く求められているように感じます。 このため、コードレベルでわかりやすいを実現することが大事なのかなと感じています。

最適化をしない

実践している内容

  1. 関数レベルでの最適化をしない*2
  2. 設計レベルでの最適化をしない
  3. 必要になるまで最適化をしない

理由

計算量を落とすために複雑なアルゴリズムを組まないということはわかりやすい一例ですが、 全体の設計として最適化しないということも大事です。
私個人の例としてはコンテキストの非同期更新を行う設計を書こうとしたことがあります。 この非同期更新ではコンテキスト参照を行う箇所での安全性など、様々なことを考える必要があり、複雑な所有権移動や排他制御を必要としていました。 このような処理は書いた本人は気持ちがいいのですが、事前情報なしでは非常にわかりづらいです。
より簡単な処理はないのか、より簡単な設計はないのかを考え、コードを読むだけで理解できるようなものを作ることが大事です。 本当に問題が生じたときに初めて最適化を行うことが良いのだなと強く感じています。

型や名前をケチらない

実践している内容

  1. 型やフィールド名でどのような役割があるのかを表現する
  2. LLMを頼る

理由

型に正確な名前付けをすることで、見るだけでどのような処理が求められているのかざっくりわかることが多いです。 コメントがなくてもある程度理解できるような名前を考え実装することがわかりやすさにつながります。 このことは広く周知されており、私自身個人開発でも意識はしていたのですが、客観的なわかりやすいかどうかというのは非常に難しいなと感じております。

私は名前付けのセンスが絶望的だなと感じており、実際に考えていても直されてしまうということが大半でした。 このようなときにchatgpt等のLLMを頼ることは有効なのかなと感じています。*3 レビューやLLMでの案を参考にしていくうちに、少しずつ名前付けのセンスがついてくるのかなと感じています。

また正確な語を使うことも重要で、例えば以前、decrypt(復号)の処理関数に対してdecode(複合)とつけてしまったことがありました。 先輩方の正確性というのはどこから来ているのかと思うほど、正確にレビューをくださる反面、私が用法を間違えるとそこで齟齬が生じてしまいます。 意思疎通を正しく行うためには、自分が正しい意味で語を使えているのか、曖昧な語彙を用いていないのかという観点も非常に大事だと感じています。

プログラミングでよく使う英単語のまとめ【随時更新】 #英語 - Qiita

エラーはわけ、伝搬させる

実践している内容

  1. エラーは惜しまず分ける
  2. できるだけ伝搬させ、情報量を落とさないようにする

理由

例えばC++ではstd::optional等を用いて、成功と失敗を簡易的に表現可能です。 しかし関数で出るエラーが1つだからといって、安直に簡易的な表現を用いると何によるエラーなのか後々分かりづらい場合もあります。 このためerrorをきちんと実装し、Result型を実装し伝搬させていくほうが、後々デバッグで助かることが多いです。 また、エラーはきちんと実装してあげると、単体テスト等の可読性も上がってくると感じています。

その他、意識していること

動くときだけではなく、動かないときもコミットに残しておく

実践している内容

  1. 動かない理由をコードレベルで残しておく

理由

なにが原因で動かないのか、何が原因で動くようになったのか、これらの把握は大事です。 例えば、C++を書いていると、一見同じように見えても異なる実装になってしまい、std::variant等のテンプレートの事前条件に引っかかるといったことがあったりしました。 また並行処理のようなバグでは、決定的にバグが起こるわけではない場合もあり、直った原因が変更によるものなのか、それとも確率的にうまく行っているだけなのかという判断が必要な場合もあります。 実際に、以前直したバグの中で数百回に一回程度しか起こらないようなバグがありました。 このようなときに、推測ではなく、実際に今どうなっているかといった状況を残しておくことや、どのような変更をしてどうなったということを具体的に見られるようにしておくことが大事なのかなと感じています。

おわりに

色々書いてきましたが、まだまだすんなりマージできることはなく、発展途上もいいところです。 加えてまだまだC++弱者で、言語に悩まされることも多いです。 試行錯誤の最中ですが、いろいろなことを吸収して残りの半分、楽しく苦しみながら実装できればいいなと考えています。

エンジニア、インターン積極採用中です

Natureではスマートホームとエネルギーマネジメントの事業を拡大していく仲間を募集しています! カジュアル面談も歓迎なので、ぜひお申し込みください。

https://nature.global/careers/job/

Natureのミッション、サービス、組織や文化、福利厚生についてご興味のある方は、ぜひCulture Deckをご覧ください。

speakerdeck.com

*1:ちなみに一つ残念な点として、Natureのファームチームのメイン言語はC++なので、Rustを触れる機会はあまりありません... (中の人が一番残念そう)

*2:適切な用語なのかはわかりませんが、ここでは関数の中で行うような、競技プログラミング的な計算量を落とすような最適化のことを関数レベルでの最適化とよび、モジュールの依存関係などを最適化するようなことを設計レベルでの最適化とよんでいます。

*3:github copilotの命名はあまりいいものが出てこない