まえがき
なぜこの記事を書くのか
前職で、初めて「ソフトウエアエンジニア」として参画させていただき、今年、さらに挑戦したくなり、転職しました。
転職してから改めて思ったことは、前職で教わったことは、今も、これからも、とても役に立つことが多いということです。
これらを、忘れないうちに残しておきたいと思いました。
ここに書いてあることは、日々の業務の中で、他のメンバーから教わったこと・学んだことで、今も私がとても大事にしているものたちです。
前職はどんな環境だったのか
私は文系の大学を卒業して、数年程、開発とは全く関係のない営業などをしていました。
空いた時間に、個人開発をする間に、ソフトウェア開発を仕事にしたくなって、ソフトウェアエンジニアとして雇っていただくことになりました。
前職の開発チームは、全員で数人の小さなチームでしたが、皆さんが本当に本当に優秀なエンジニアでした。
フロントエンドとバックエンドができるのは当然で、インフラやセキュリティもできたり、ハードからソフトまでできたり、アルゴリズムが超得意だったり、私以外のメンバーは学生時代からずっとバリバリ開発を行なってきた人たちでした。
全員がフルスタックで、要件定義からテストもこなし、お互いに助け合って進めていくようなチームでした。
そんな中に1人、HTTPリクエストが何かもわからない私が参画して、0から鍛えてもらいました。
その方法は、手取り足取り時間をとって「教えてもらう」というスタイルではなく、「とにかくこれをやってみて。わからないことは何でも聞いて」というスタイルでした。
質問をすると、私が「理解できました」というまで、徹底的に教えていただきました。
また、コードを書いたり設計したりして間違っていた時、それを正しい方法で修正してもらうではなく、私が理解できるまで「説明」していただき、それを元にまた自分で書くいうことをしていました。
このように「まずは自分で考えて、わからないことは徹底的にわかるまで聞ける」「教えてもらったことを元に、自力で作る」という環境で、一つずつ教わったこと「自分のもの」にできたのだと思います。
本題: 前職で教えてもらったこと
1. 日々の業務編
1-1. 15分考えて分からないことは、他のメンバーに聞くこと
15分考えて分からないことは、他のメンバーに聞いた方が圧倒的に早いことが多いです。
特に小さなチームや、歴史が長いシステムの場合、ドキュメント化されていないことも多いので、いくらコードを読んでも分からないことはあります。
1-2. とにかくドキュメント化すること
新しい機能を作る時はもちろん、インフラの設定がわからなくて他のメンバーに聞いた時等、まだドキュメントされていないことは、どんどんドキュメント化します。
忘れてしまってまた教えてもらうとまた工数がかかりますし、他のメンバーが入ってくる度に同じ教える工数がかかることになります。
自分が分からなかったことは、他のメンバーも分からないことかもしれません。
1-3. 明日の自分は自分じゃないので、必ずどこかに書いておくこと
人間みんな忘れます!
自分が忘れてしまったことは、自分が思い出すのはもちろん、他のメンバーが改めて理解しようとすると、何倍も時間がかかります。
今日の自分が「絶対に忘れない」と思ったことも、明日とは言わずともいつか忘れてしまうので、必ずコード内にメモを残すか、チケットを切るか、ドキュメントにするかなど、とにかく残るようにします。
「明日の自分は自分じゃない」と思って、自分と、他のメンバーのために、どこかに残します。
(私は昨日やったことを大抵忘れます!笑)
1-4. 「レビューで見てもらおう」・「テストでバグを見つけてもらおう」と思わないこと
レビュー・テストする人よりも、コードを書く人の方がバグに気付ける可能性が圧倒的に高いです。
レビューする人やテストをする人がバグを見つけるコストは、コードを書いた人の何倍もかかります。
自分で書いたコードはレビューしてもらう前に自分で見直します。
面倒でも、コードを書いた人が動作確認をします。
動作確認が手間な時は、単体テストを書いたり、cURLのシェルスクリプトを書いたりして、工夫します。
2. コードの書き方編
2-1. コメントは極力控え、リファクタリングすること
大抵のコメントは、コードをリファクタリングすれば不要になることが多いです。
コメントを読むよりもコードを読む方が正確です。
変数名を工夫したり、関数を分けたり、クラス化したりと、リファクタリングをすれば、コメントを書かなくてもコードに意味を持たせることができます。
リファクタリングしても、コメントを残した方がわかりやすい時は、その上でコメントを残します。
関数に関するコメントは関数ごとのDocsに極力含めてIDEで補完できるようにします。
2-2. 長い関数はNG、必ずリファクタリングして関数を分けること
長い関数はデメリットをたくさん持っています。
- レビューする時や後からコードを読んだ時に、理解をするのに時間がかかる
- 長い関数は大量の変数名を持っていることが多く、読む時にすべて覚えている必要がある
- 複数の処理を持ち合わせていることが多く、すべて読むまでそれぞれの関係が理解ができない
- 単体テストを書くのも難しい
長い関数は、処理ごとに、複数の関数にリファクタリングします。そうすれば、
- コメントをつけなくても関数名をつけられるのでコード上に意味を持たせらる
- 変数も関数内に留めることができるので、読む側がたくさんの変数を覚えておく必要がない
- 関数ごとにテストを書ける
- 後で再利用できるかもしれない
- 関数ごとにDocsでコメントを残せ、IDEでも参照しやすくなります
再利用をするためというよりも、Readableにするため、関数は複数に分けます。
2-3. 命名は省略しないこと
省略してわかりにくくなるなら、長い名前の方が良いです。
2-4. 1ファイル1クラス・コンポーネント
1ファイルにいくつも処理を書かないようにします。
これにより、ディレクトリ構成を見るだけで、どんな処理が置かれているのかがわかるようになります。
2-5. 関数の戻り値をtuple型を返すのは避けること
tuple型で戻り値を返すと、使用する側で、戻り値の順番を気にする必要があります。
また可読性も下がります。
可能であれば、クラスやオブジェクトにして返します。
2-6. コードを読めば仕様がわかるようにコードを書くこと
仕様書やドキュメントを詳細に書くよりも、コードを丁寧に・後から読んでも理解しやすいように書いた方が、メンテナンスコストが圧倒的に安いです。
2-7. できるだけ、読みやすく、分かりやすく
書いた自分が分かりにくいと思うコードは、他の人にとってはもっと分かりにくいです。
自分でも明日読んだら理解できないかもしれないです。
ここまで書かれてきた長い関数はNG、命名の省略はNGなども、すべてここに理由があります。
3. データベース設計編
3-1. 自分が明日入社して、理解できないテーブル設計はしないこと
データベースのマイグレーションするのが面倒だからという理由で、用途に一致しない既存のテーブルやカラムを使ってはいけません。
後で見たときに、何が保存されているのか分からなくなるテーブル設計はしてはいけません。
何も知らない人が見ても、テーブル名とカラム名を読めば、何が保存されているのか、おおよそ理解できるような、テーブル設計・命名をします。
データベースは、自分がチームから外れた後も残るものなので、コード以上に丁寧に作成します。
3-2. user
テーブルは極力小さくすること
どんなアプリでも、user
テーブルはどんどん大きくなりがちです。
役割ごとにテーブルを分けます。
例えば、認証に関わるもの、権限に関わるもの、ユーザーが任意に設定できるものなど、それぞれ別のテーブルにします。
3-3. 不要なカラムの追加は避けること
カラムの追加は簡単ですが、後で削除・マイグレーションするのはとても大変です。
カラムを追加する前に、本当にそのカラムが必要か、まとめられないかを考えます。
例えば、is_canceled
やis_deleted
はstatus
カラムにまとめられるかも知れません。
3-4. Booleanのカラムは極力避けること
大抵の場合のBooleanのカラムは、まとめるか、より多くの意味を持つカラムにできます。
is_canceled
やis_deleted
のように用途ごとにBooleanのカラムを追加し続けると、テーブルが肥大しがちです。
status
カラムにまとめたり、canceled_date
やdeleted_date
のように、より意味のあるカラムにした方が良いかもしれません。
4. Git編
4-1. こまめにコミットすること
これは、複数のPull Requestを作るという意味ではなく、1つのPull Requestの中でも複数のコミットを作るという意味です。
意味や作業ごとなど、区切りが良いタイミングで、定期的にコミットをします。
実際に実装する時も実際に、大枠のフローを作って、モデルを定義して、実際にロジックを書いてと、ある程度手順があると思います。
それごとに、コミットを切ります。
そうすると、
- 後で特定の箇所だけRevertしやすい
- 後でコードを見直すときにPull Requestのコミットのコメントを見れば、ロジックが理解しやすい
- コードを書く人は、書く手順をあらかじめ頭の中で整理してから書くので、全体として綺麗なコードになる
- レビューする人にとっては、なぜそのように書いているのかわかりやすい
- レビューをするのも、コミットごとにレビューができて楽
など多くのメリットがあります。
4-2. コミットのコメントはわかりやすく書くこと
コミットのコメントにちゃんと意味を持たせます。
そうすると、
- Treeから特定のコードを探しやすい(すでに削除したコードを含む)
- レビューをするときに、そのコミットが何をしているのかがわかる
というメリットがあります。
4-3. それぞれのコミットがちゃんと動作するようにする
それぞれのコミットをPushする前に、最低でもアプリケーションが起動できるかは確認します。
これは、特定のコミットにRevertしたときにも、他の箇所は問題なく動作できるようにするためです。
(といっても、実装しながら細かくコミットをしていると、後で間違っている箇所が見つかって、修正する必要があることもよくあります。その時は、fix
プレフィックスをつけてコミットにコメントをつけておき、後でRevertすることになったとき、どのコミットも取り込まないといけないのかがわかりやすいです。)
4-4. 関係のないコード修正は同じPull Requestに含まないこと
小さな修正であっても、今回の目的と関係のないコードはちゃんと別のPull Requestにします。
面倒であっても、意味ごとにPull Requestを分けます。
これにより、
- 1つのPull Request単体が意味を持つので、Pull Requestを見たときに後で理解がしやすい
- 何かをRevertしないといけない時も、1つのPull RequestをRevertすれば良い
などのメリットがあります。
関係のないコードが入っていると、
- 本来は関係の無いはずだった不具合が生み出される可能性がある
- レビューをする人がなぜそこに差分があるのか理解できず混乱する
などの問題が、起きることがあります。
4-5. 差分が多すぎるPull Requestは出さないこと
面倒であっても、細かくPull Requestを分けて、それぞれでレビューをしてもらいます。
次のPull Requestで対応する箇所にはTODOをつけて、何がこの時点で不足しているのかを明示します。
例えば、CRUDのAPIを実装するとき、4つのAPIをまとめたPull Requestを出すのではなく、APIごとにPull Requestを作ります。
例えば、新しい機能のUIを作る時も、見た目だけを先にPull Requestし、次のPull Requestでロジックを実装したりするなど、Pull Requestを分けます。
複数のページがあるときやロジックが長すぎるときも、ページごとや、一定のロジックのまとまりごとなど、工夫をしてPull Requestを分けます。
理由は、Pull Requestが長いと、
- レビューにも一定のまとまった時間と工数がかかる
- レビューする人の心的負担が高まる
- 不具合を見落とす可能性がある
などのデメリットがたくさんあります。
Pull Requestを分けるという場合、いくつか懸念があるかもしれません。
- 複数のPull Requestにすると、それぞれレビューをしてもらう必要があるので、時間がかかってしまうのでは(?)
→ Pull Requestが長すぎるとレビューする側も心理的負担が高く、時間の確保も必要なので、何日もレビューされないということも多々あったので、同じ時間がかかるのであれば、メンバーの負担が少なく、不具合の可能性も低い「細切れにPull Requestを出す」というのが良いと思っています。
- Pull Requestを分けると、1つのPull Requestがレビューされるまで、次の開発をしたり、Pull Requestを出したりできないのでは(?)
→ レビュー待機中にも開発する前のPull Requestのブランチから、さらに新しいブランチを切って次の作業を続けることができます。Pull Requestをさらに出す時は、新しいブランチから前のPull Requestのブランチ向けにPull Requestを出すようにします。
唯一、本当にPull Requestを分ける時のデメリットなのは、
- 前のPull Requestでレビューを受けてコードを修正したときに、rebaseやmergeをするとコンフリクトが発生し、解消しないといけないことがある
ということです。ただ、このデメリット以上に、Pull Requestを分けた時のメリットが圧倒的に多いです。
5. 工数見積もり編
5-1. 自分だったらX時間ではなく、「新しいメンバーが入ったらX時間」で見積もること
プロジェクトが進んで別の人が担当することになったりした時に、見積もりの確度がズレます。
大抵の場合、早くなることは問題がないのですが、遅くなることは問題があります。
急に他の人にお願いすることになっても、問題のない工数で見積もっておきます。
5-2. 工数を見積もるときは、必ずバッファーを持つこと
当たり前かもしれませんがとても大事です。
最後に
振り返ってみると、とても丁寧にレビューをしてもらえる環境でした。
設計書のレビューも、コードレビューも、大抵1回目のレビューでApproveはもらえず、何度もレビューしていただいたこともありました。
メンバーも少なかったので、後からフォローし合えるように、理解しやすい・保守しやすいものを作ることがとても大切にされていたのだと思います。
これはチームが変わっても、チームが大きくなっても、非常に重要なことだと思いました。