OOPに対するもやもやがだいぶ晴れてきた話
こんにちは、ogaworksといいます。半年ほど前から、フィヨルドブートキャンプに参加しています。
フィヨルドブートキャンプでは、カリキュラムの折り返し地点に差し掛かったあたりに「オブジェクト指向プログラミング(ruby)」というプラクティスが用意されています。自分はこのプラクティスに取り組むまで、特に「オブジェクト指向」についてしっかり勉強をしたことはなかったこともあって、直前の日報にも
これから取り組むオブジェクト指向の課題、難易度高いらしく、まるでカイラスのように見えています。
と書く程度に漠然とした壁の高さを感じていました。※「カイラス」はチベットにある標高6656mの未踏峰です
実際、オブジェクト指向のプラクティスで推奨されている参考図書や参考サイトには(技術書や抽象的な概念への理解があまり得意でない自分にとって)ハードルの高いものが多く、ひとつひとつ取り組みつつも「本当に進んでいるのかな?」と、不安になることもしばしばでした。
それでもなんとか課題を形にしてコードを提出し、幾度となくコードレビューと修正を繰り返してようやく「OK」が出てプラクティスを乗り越えた頃、
OOPに対するもやもやがだいぶ晴れてきた感。
という感想をもち、コミュニティのDiscordにポストしました。
半分ぐらいはプラクティスを無事終えた安堵感によるものですが、OOPの考え方やメリットも少しずつ見えてきたのかも?そんな実感もありました。
このアドベントカレンダーのきっかけは、その(もやもやだいぶ晴れた)ポストを目にしたメンターさんに「それ、アウトプットに期待!」と背中を押して頂いたのがきっかけです。
とはいえ、まだまだ完全理解には程遠いし、内容的にハードルを上げてしまうとアウトプット自体できなくなるので、今回は自分がプラクティス「lsコマンドを作るオブジェクト指向版」に取り組んで、もやもやが晴れるに至った「オブジェクト指向(OOP)に取り組む際のポイント」を自分の復習とプラクティスで壁に当たっている人への参考を兼ねたメモとしたいと思います。
今回取り組んだのは、プラクティス「lsコマンドを作るオブジェクト指向版」です。
ブートキャンプでは、カリキュラムの前半の方にも「lsコマンドを作る」というプラクティスがあり、そこでは本物のlsコマンドと同じような動きをする「コマンドラインで動作するプログラム」をrubyで実装します。一見シンプルなlsコマンドですが、色々なオプション(-r -l -a)を取得してファイル操作やフォーマット整形したり、マルチバイトファイル名やシンボリックリンク、拡張属性の表示などいろいろな処理を実装しました。その時点のプラクティスで書いたコードはOOPを意識したものではなく、機能や操作をひとつひとつdef でメソッド定義し、必要に応じてそれらを呼び出して実行するというものでした。
今回取り組んだのははそのOOP版を実装するということで、まずはクラス分けから考えてみました。
ここまでのOOPのプラクティスで
属性や振る舞いによって複数の異なるモノがある分類をクラスとして定義する
同じクラスから作られたオブジェクトは同じデータ項目やメソッドを持つ
と覚えました。
これらの考えをもとに、ひとつ前のプラクティス「OOP版ボウリングスコア計算」では、たくさんあるけど値は異なる「投球」や「フレーム」をクラスとして定義しました。
lsコマンドでも同様に考えてみると、lsコマンドの対象となる「ファイル」が「同じ属性を持つ複数の異なるモノ」に該当しそうです。なので、まずはこれをひとつのクラスとして扱うことにしました。それから、オプションなどによって異なる表示フォーマット処理を行うリストクラスと、コマンド実行の起点となるクラス、その3つをOOP版のクラス構成にしました。
ファイルの情報を扱うContentクラス
リストのフォーマットや表示を行うクラス
コマンドを実行するクラス
次に着手したのは、この3つのクラスへのメソッドの実装です。
メソッドについては「OOP版も非OOP版も、ファイル操作やフォーマット等の細かい処理自体はそんなに大きくは変わらないだろう」「まずは動くものを作ってからOOPに近づけたい」と考え、0からメソッドを書くのではなく、非OOP版lsで実装したメソッドを流用し「OOP版だとどのクラスの振る舞いもしくはデータであるべき?」を考えながら、OOP版の3つのクラスに振り分けていきました。OOPな考え方が身体に浸透した暁には、0からクラス設計やメソッド定義ができるようになっていることを期待しつつ。
すべてのメソッドをクラスに振り分けて、ひと通り「クラスとメソッド」の構成でとりあえず動作するようにした後は、テストコードを書きました。
もちろん、この時点のコードではプラクティス的にOKをもらえるOOPにはなっていないので、完成には程遠いものです。これからレビューを経てコードはどんどん修正されていくので、「テストはもう少し後でもいいかな」と一瞬迷ったのですが、「書いたらテストしろ、テストしないなら書くな!」というメンターさんのアドバイスが脳裏に焼き付いていたこともあり、早めにテストを用意しておくことにしました。実際、この段階で(本格的にコードの書き換えを始める前に)テストコードを用意しておけば、コードの変更によるデグレの有無がすぐに確認できるので、安心してコードを書き換えられる(動かなくなったら戻せばいい)というメリットもあり、テストコードのありがたみを改めて実感することになりました。ちなみにテストコードの実装には、非OOP版lsコマンドのメンターさんの提出物お手本がとても参考になりました。
ここまでが、最初の課題提出までに取り組んだ流れです。
その後、レビューと再提出を何度も重ねた後にOKをいただいて「OOPに対するもやもやがだいぶ晴れてきた感」を得るに至ったのですが、その途中の過程をすべてまとめるには時間も足りない(これを書いている今日がまさにアドベントカレンダーの当日でだいぶ焦っている)ので、山程指摘を頂いた中で「これはOOP版に必要なポイント」と感じた点をまとめておきます。
クラスは「専門家」という考え方
「ファイルの情報を扱う」ために作ったContentクラスは、ファイルに関する専門家。ファイルに関するあらゆる情報はすべてContentクラスの仕事と考える。
表示に必要な「ファイルの属性」を提供するのもファイルに関する専門家の仕事。Contentsクラスにメソッドを定義し、それを呼んで取得させる形にする。
「表示用の整形や型変更など、フォーマット操作」に関する処理は、フォーマットの専門家(フォーマットクラス)の仕事と考える。
では、「ファイル一覧の取得」や「コンテンツオブジェクトを集める」のは誰の仕事か?コマンドを実行する専門家の仕事にするという考えもできる。
専門家以外のクラスで処理や操作をしないことで、DRYでシンプルなコードになる。
他のクラスから「その処理は僕の仕事ですよ」と言われないかどうか、各専門家の仕事と処理を強く意識する。専門家同士は、引数で値やオブジェクトを渡して仕事を依頼する。
メソッドやインスタンス変数について
OOPでは、メソッド間の引数でなく、クラス内のインスタンス変数をより活用する。
インスタンスの属性を外から呼ぶには attr_reader を使う。
クラスやメソッドはなるべく再利用性を高くする。
再利用性が高い例)RubyのターミナルでもRailsのアプリでもどこでも使える。
起動時引数(ARGV)はコントロールしにくい=再利用性が低いので、記述箇所は最低限にしてインスタンス変数などを利用する。
initialize メソッドではあまり重い処理をしない。new した瞬間はコンパクトなオブジェクトができるだけ、必要な処理はメソッドを呼んだら実行されるイメージ。
クラス.newすることにあまり意味のない場合は、クラスメソッドで直接呼べるようにしてもよい。
code: rb
class LsOop
def self.hoge
new.hoge
end
def hoge
# 処理
end
end
LsOop.hoge # ここ、LsOop.new していない。
命名について
処理を実行するメソッド名は動詞から始める。
名には既存のクラス名とかぶらなそうな名称を採用する。
これら以外にも、レビューではたくさんの指摘をいただきましたが、中でも特にOOPのヒントになりそうという内容をピックアップしてみました。
とはいえ、プラクティスのどこかで「オブジェクト指向は人によって考え方が結構異なる」というコメントも見かけた気がするので、あまり気張って敷居をあげてしまわずに「専門家」や「再利用性」など自分が理解しやすかった考え方から今後のコーディングに取り入れていきたいと感じました。
最後に、今回のプラクティスでは初めて動画でレビューをしてもらいました。文字によるレビュー&コメントだけでは伝わりにくい細かいニュアンスや経緯などがとても理解しやすかったことに加えて、温かい安心感もありました。動画の中のメンターさんから「はい、ogaworksさんこんにちはー」と呼びかけてもらったときは、なんだかとても不思議な感覚でした。
ブートキャンプのカリキュラムの中には正直「これ自力で読んで理解するの大変!」と感じる課題が時々登場します(個人の感想です)。読書苦手な自分はその都度「うーん…」と意識が遠のきそうになります。それでも、同じプラクティスを学習して乗り越えてきた他の参加者さんたちの膨大なアウトプットは質の高いヒントの宝庫であり、メンターさんたちのサポートや参加者同士のコミュニティを活用すれば、ひとりでなんとなくググって検索結果に表示された記事を試して解消できず八方塞がりに陥って時間が溶けて心折れるという非効率なスパイラルもだいぶ回避することができると思っています。昨日のアドベントカレンダーの/june29/学習中のみなさんに送る「アンラーニング」の話もなるほど!と思いました。無理せず継続して取り組めば確実に前に進むし、自分もここまで70あまりのプラクティスをこなしてきて少し眺めが変わった気もするので、引き続き楽しみながら取り組んでいきたいと思います! というわけで、OOP版プラクティスの参考となる内容になっているかどうかまったく自信は有りませんが、とりあえず初めてのアドベントカレンダーに遅れず参加できてよかったです😆
https://gyazo.com/985b72608b1cf96aaf2ef3b143f1d296
最後まで読んでいただき、ありがとうございました!
※写真は近所の海です。