新卒未経験でエンジニアになってしまった人にとりあえず最初に学んで欲しいこと
はじめまして。
ブログ開幕アドカレのたぶん29日目の記事になります。
今回は@tuからお送りします。
少し前にこちらやこちらの記事で、勉強会や経験の浅いメンバーに対して弊社ではこういうことをやっているよーということをお話しています(みてね)
今回はその辺りを若干まとめて、表題の通り「新卒未経験でエンジニア(プログラマ)になったけど何から始めればいいのかわからん!」という人の助けになればいいと思います。
かく言う自分も社会人ではじめて本格的にプログラミングを始めた身でもあるので、自身の経験も踏まえてお話ししたいと思います。
その0: if, for, classなどの基礎文法を学ぶ
これはどこでも新卒で入れば必ず研修で教わると思いますので飛ばします。
まずはどの言語でもいいので「Hello,World!」の応用だったり改変をして自力でclassがかける程度にはなりましょう。
言語はPHP, Ruby, Pythonあたりが楽に準備できるのでおすすめです(弊社は主にPHPを使ってるのでPHPから始めてくれるといいな)
その1: 「リーダブルコード」を読む
こちらの記事でも紹介していますが、まずは「リーダブルコード」だけでも読んでください。
自身もこの本を最初に読んで新しく知ることがとても多かったです。
例えば、最初の方はいくつかサンプルコードを読んでいると思いますが、その影響でreturn
は必ず最後に書かないといけないのかと思い込んでいました。
しかし、この本では「returnはいつでも書いていい。むしろ早めに書いたほうが余計な脳みそのメモリを使わないので読むのが楽になる」(超意訳)と書いてありました。
ページ数もとても少ないので、自分みたいにどうしても長い文章読むと眠くなるんだよな〜という人もまずはこの本から読み始めてみてください。
コードを書くのは、他の誰かに読んでもらうためでもあるし、記憶喪失になった自分が読むためにも、読みやすく書くと言うのが基本です。
ちなみに言語はPythonで書かれていますが、他言語でもなんとなく読めます。
もし読めなくてもやっていることが文章で解説されているので流し読みでOK。
その2: 「SOLID原則」を学ぶ
こちらの記事でも取り上げていますが、まずはこの原則を知ること。
オブジェクト指向プログラミングをする上で欠かせないものになってきます。
「法則」ではなく「原則」となっており、これを知っている知っていないで明らかに書くコードの質が変わります。
「じゃあ、まずはオブジェクト指向プログラミングを勉強しなきゃ...」と思う方がいると思いますが、
正直そちらから入ると沼る・混乱する可能性があるので、基礎文法ができていればこちらから学ぶことをおすすめします。
簡単にこれらを噛み砕いて紹介していきます。
"S": Single Responsibility Principle / 単一責任の原則
これは比較的わかりやすいと思います。
その名の通り、「ひとつのクラスにはひとつの役割をさせること」ということです。
例えば以下のようなクラスがあったとします。
class GodClass {
public function createFile() { /* ...省略... */ }
public function readDataFromFile() { /* ...省略... */ }
public function readDataFromDB() { /* ...省略... */ }
}
これをみると「ファイルの作成」「ファイルからデータ読み取り」「DBからデータの読み取り」という3つのことをやっています。
なのでこの原則から外れており、外れると「管理・修正のしにくいコード」が生まれてしまいます。
今は問題なくても将来的にキツくなります。これは何十年もコードを書いている大先輩が定めたものなので、いつかはその通りになるはずです。
(上級者向け: ファイルだとかDBとかはコンテキスト依存じゃねーか!もっと抽象化しろ!というツッコミはなしでお願いします)
さて、これをみると
- 「なるほど!ではクラスを3つにすればいいのか!」
- 「なるほど!では”ファイルを扱う”ってことで2つのクラスに分ければ良さそうだな!」
- 「いやいや、このクラスで扱うデータが同じ種類のものならば、”〇〇のデータを扱う”って視点では単一責任では?」
のいずれかの意見を持つ人が出てくるはずです。
どれが正解でしょうか?
おめでとうございます、どれも正解です。
正直この「単一責任」の判断軸は人によって異なります。
なので、あくまでこの原則は判断材料の1つであり、他原則やクラス設計によって”正解”が変わります。
それでももし単一責任かどうか悩んだ際は単純にコードが
- 長すぎる(2-300行以上かな)
- => このクラスが管理しづらい、読みづらい
- 短すぎる(2-30行以下かも)
- => ファイルが多くなるので管理しづらい
- => いまはまだ別クラスで切り分けるのが早すぎる
で判断してもいいと思います。
"O": Open/Closed Principle / オープンクローズドの原則(開放閉鎖の原則)
大事な原則ですが、名前から何言ってるかわかりませんね。
わかりやすく言うと
「変更・修正で直していくのではなくて、拡張・追加で対応できるようにしろ」
ということです。
そう考えると特に難しい原則ではないです。
もし、頻繁な変更が想定される、または変更が外部依存(どこかのWebAPIを叩いているなど)している場合は、あらかじめ"バージョン"を決めて、バージョンごとのファイルを作るようにするといいでしょう。
"L": Liskov Substitution Principle / リスコフの置換原則
こいつも情報が人の名前ぐらいしかないのでパッと見わかりにくいですが、簡単に言うと
「継承を使う場合は、ちゃんと親子兄弟が入れ替わっても、役割が同じになるようにしろ」
ということです。
「え?役割が同じなら継承のメリットないんじゃ...」と思うかもしれませんが、「全く同じ動作をしろ」ということではないです。
例えば、こちらのイラストで理解するSOLID原則という記事の例を使わせていただくと
「コーヒーを持ってきて!」という要求に対して
- 親「ブレンド持ってきました」
- 子1「カプチーノ持ってきました」
- 子2「アメリカーノ持ってきました」
- 子3「フラペ◯ーノ キャラメルソース追加 チョコチップ追加 ホイップクリーム増量を持ってきました」
というように「コーヒー」ではあるものの、別の種類のコーヒーを持ってきているので「役割が同じ」になり、「置換可能」ということになります。
※さすがフラペ◯ーノはコーヒーではなさそう
この例のように「コーヒーを持ってくる」に対し水をもってくるような、親の挙動と全く違う意味の動きをしてしまう場合は間違った継承をしている可能性が高いです。
もし継承を使う場合は「親と同じ役割をしているか?」を再考してみてください。
"I": Interface Segregation Principle / インターフェース分離の原則
これもわかりそうですがわかりにくいですね。
要は「インターフェイスを作るなら、小分けにしろ」
ということ...ですが、まだちょっとわかりにくいのでサンプルを書いてみます。
// こっちではなくて
interface FileIOInterface {
public function read();
public function write();
}
// この2つにしろ
interface FileReaderInterface {
public function read();
}
interface FileWriterInterface {
public function write();
}
こうすることによって
class File implements FileReaderInterface, FileWriterInterface { /* ...省略... */ }
class ReadOnlyFile implements FileReaderInterface { /* ...省略... */ }
のように作成されるメソッドを制限しつつも、より柔軟にクラスがかけるようになり、インターフェイスのメリットを最大限に活かせるようになります。
「いやいや、それ抽象クラス使って継承でもいけるじゃん?」と思った方もいるかもしれません。
確かに抽象クラスでも実装することはできます。その場合、上記の例のようなReadOnlyFile
クラスを作成したらどうなるでしょうか?
おそらくpublic function write(){}
のように「(親と違って)何もしないメソッド」を作らないといけなくなると思います。
または「Write()だけを子クラスに...」となるかもしれません。
お気づきかもしれませんが、そうするとまず前項のリスコフの置換原則から外れるため、後々煩雑なコードを生み出すポイントになってしまいます。
また、後で説明する"D"の原則でもインターフェイスがあることによって恩恵を受けることができます。
言語によっては多重継承というものも可能ですが、サポートしていない言語もありますし、超特殊なケースを除けばOOPできてない場合がほとんどなのでインターフェイスを用いるほうが良いです。
"D": Dependency Inversion Principle / 依存性逆転の原則
名前はわかるもののどう実装するかわかりにくい原則です。
これはサンプルを見てもらうのが早いと思います。
📁 File
┣ 📄 FileReaderInterface.php
┣ 📄 FileWriterInterface.php
┣ 📄 File.php
┗ 📄 ReadOnlyFile.php
📁 Item
┗ 📄 Item.php
このようなディレクトリ構造になっていて、File.php
の中でItem.php
に書かれたクラスを受け取ってその内容を書き出したい処理があったとします。
class Item { /* ...省略... */ }
class File {
public function writeItem(Item $item) { /* ...省略... */ }
}
この場合「FileがItemを利用している」ので「FileがItemに依存している」ということになり、 Itemが変更になるとFileも変更しなければならなく なります。
なので、これを"逆転"することで、 Itemが変更になってもFileを変えない ようにします。
こうします
📁 File
┣ 📄 FileReaderInterface.php
┣ 📄 FileWriterInterface.php
┣ 📄 File.php
┣ 📄 ReadOnlyFile.php
┗ 📄 WritableItemInterface.php <- NEW!!
📁 Item
┗ 📄 Item.php
class Item implements WritableItemInterface { /* ...省略... */ }
class File {
public function writeItem(WritableItemInterface $item) { /* ...省略... */ }
}
このように「Fileが扱えるItemの定義をFileの領域において、Item側がそれに則る」ようにすると「ItemがFile(の領域)に依存している」ということになり、ちょっと屁理屈っぽいですが"逆転"させることができました。
こうすることで、WritableItemInterface
はその名の通りインターフェイスなので実装を書く必要がなく、Item側がどう中身を変更しようがFileは指定したメソッドを呼び出すだけで扱うことができるようになりました。
また、Itemを今度はDBに保存したくなったとしても同様にDBのインターフェイスを追加で実装するだけで良くなります。
こうやって、依存関係があっちこっちに向いている場合は方向を揃えることでシンプルな構造にすることができる、という原則でした。
まとめ
ざっと最初に読んで欲しい本と学んで欲しいSOLID原則について触れました。
これを最初に学んでおくことで「あーあのときこう書いていればな・・・」ということも減らせますし、後々誰かまたは自分を苦しめるコードを書かなくて済むのでおすすめです。
また、後々問題となりそうなコードの"におい"もわかるようになってきます。
今回からわかるように、クラスを書く場合は「役割」を意識することとなるので、
「ここにあるからもう書かなくていいじゃん」とか「これ使えるじゃん」で実装してしまうとどんどんカオスとなってしまいますので注意しましょう。
それではまた次回。
参考
2023/11/27追記:
今年発売された「Good Code, Bad Code」を読みましたが、初心者がステップアップできるかつ平易な文章で書かれているため非常におすすめです!
上記でいうその1からその2の間くらいで読んでおくとさらにいいかもしれません。
NE株式会社のエンジニアを中心に更新していくPublicationです。 NEでは、「コマースに熱狂を。」をパーパスに掲げ、ECやその周辺領域の事業に取り組んでいます。 Homepage: ne-inc.jp/
Discussion