気ままに気ままのエンジニアブログ

日々学んだこと / 課題解決したこと / 思うことなど気軽にアップしております。

1年前に投稿した「公開コードレビュー会に参加してみての感想」を、1年後の自分がレビューしてみた

こんにちは。

不意にブログ名を変更してみました。

どうもハチマキです。

はじめに

今から1年ちょっと前に、未経験からエンジニアに転職致しました。当時はよく初学者向けの勉強会に参加して、そこで得た学びを記事に書いていました。

現在エンジニアになって約1年半、前提知識や経験は、当時から考え難いくらい成長してきたなと我ながら感じております。

この1年でどのようなことを感じるようになって、どのような視点を持つようになったのか、自分自身の成長加減が気になったこともあり、当時書いた勉強会の記事をベースにあたかもコードレビューかのように、コメントを加えていきながら振り返りを行ってみたいと思います。

▼この記事の内容にレビューを加えていきます
hachimaki37.hatenablog.com

対象

  • 実際エンジニア歴1年ってどんな感じなのかを知りたい
  • プログラミング挫折しそうな人
  • エンジニア成り立てってどんな感じなのかを知りたい

環境

当時参加した勉強会の概要

  • レビュー会概要
    • ポートフォリオレビュー者(3名)に対して、一人30分程度で現役エンジニアの方が公開レビューしていく
    • レビュー会のみ参加者:15名ほど(私はこちらで参加)
    • 平均年齢:おそらく20代中盤
  • 3名のポートフォリオ一覧(個人アプリケーション)
    • 1人目:昆虫食専門のサイト
    • 2人目:一人旅行で現地で遊んでくれる人を探すような掲示板サイト
    • 3人目:本のアウトプットアプリ

では、早速いきましょう!

総論に対するコメント

1番の学びポイントは、自身のマインド面に変化をもたらした。
同じような駆け出しエンジニアの人たちであろう、自身の作成したサービス(ポートフォリオ)に素直に驚かされた。

と同時に、レビューされる人はもちろん、レビュー会のみ参加している方々の温度感、熱量に焦りすら感じた。
自身を見直し、改めて計画して頑張ろってなった。

今振り返っても、この勉強会で得たマインドの変化は、良い緊張感と良い焦りを与えてくれた機会でした。

発表していたポートフォリオは、今思えばシンプルな作りではあるが、当時の自分には恐ろしいほどのレベルの差を感じました。
そこから得た学びで、目標や計画に落とすようになり、実行に移せたからこそ今の自分があるように感じます。
LGTM👍

テーブル名やカラム名、メソッド名の一つ一つに意図した意味をしっかり持たせながら設計していくことの重要性を学んだ。
ちゃんと一つ一つに開発者の意味がこめられていることを知らなかった。

メソッド名から変数名など、一つ一つの意図を考えながら実装していく必要があります。
意図した意味というよりも、そのコードを見た他者が迷いなく、意図を理解できるような設計にすることが重要です。

*保守のしやすさ(仕様理解や開発効率)に直結するため

学んだことに対するコメント

・レビューする側の視点を知れたこと
 ・現在、レビューしていただく側のため、レビューする側の観点や流れをざっくり知ることができた

・具体的に
 ・メソッド名 / テーブル名 / カラム名は、意図した(相手へのメッセージ)名称の定義を行なっているか
 ・テーブル名、カラム名が、UI画面と紐づくように意図して定義されているか
 ・コードに処理の意図を感じ取れるか
 ・コードは、シンプルにわかりやすく書かれているか

プログラムの可読性や開発効率を上げるための重要な観点だと感じます。
今はまだレビューして頂く側ではあるが、コードレビューのコメントで上記箇所は、良し悪しに関わらず必ずコメントを頂いているように感じます。

メソッド名 / テーブル名 / カラム名は、意図した(相手へのメッセージ)名称の定義を行なっているか

上記にも追記しましたが、保守性などに起因する重要なポイントです。

テーブル名、カラム名が、UI画面と紐づくように意図して定義されているか

どのようなデータを格納するためのカラムなのかを考えた上で、命名する必要があります。チーム内でも命名に関して相談をする機会が多々あります。

コードに処理の意図を感じ取れるか

自分にしか理解できないコード(スパゲティコード)になっていないかは重要な観点です。
スパゲティコードになることで、保守性が下がる。つまり開発効率が下がることに直結する点ですので、気をつける必要があります。

例) 
#バッドコード
@users = User.find(1) 

#グッドコード
@user = User.find(1)

#バッドコードの理由:ユーザidを取得しているのに、変数名が複数形になっている

コードは、シンプルにわかりやすく書かれているか

可読性を上げるために、不要(余計)なコードは書かないこと、コード短縮できる箇所は、短縮したコードを書くこと。

例)
#バッドコード
user = User.find(1)
user.id = 10
user.save

#グッドコード
user = User.find(1)
user.update(id: 10)

#バッドコードの理由:短縮できるコードは、リファクタリングしましょう。

・レビューする時のfile確認の流れ
 ・README(github) → schema → routes → models → controllers → ・・・

レビュー観点という面では、アプリケーションの全体像(README(github) → schema..)から具体(controllers →..)に確認を進めていくことで、スムーズなレビューにつながるように感じました。(まだレビュアーではありませんが・・)

各fileの確認ポイントに対するコメント

アプリケーション概要を把握するためのREADMEに詳しく書く
概要
技術ポイント
機能一覧
環境 / 技術構成
インフラ構成
テーブル設計
etc...
 ※インフラ構成や、テーブル設計を記載する際は、図で表現するとイメージがついて良い

アプリケーションの概要を理解するために、READMEを詳しく書いておくことは良い点かと思います。
READMEを見ることで、他社がアプリの全体像を掴めること=スムーズなコードレビューに繋がる要素かと思います。

schemaに対するコメント

migration file内に、null: falseを書く癖をつける

データの整合性を保つために必要です。null: falseを指定したカラムは、空の状態でDB保存できなくなるため、例えば、フォームの氏名を必須にした時に、null: falseを定義しないと、氏名は未記入のままDBに保存されてしまいます(データが不整合になる)

すると、思わぬバグになったりするので、必要な箇所にはnull: falseを追加するようにしましょう。

テーブル内で重複するデータを禁止するために、unique: trueを書く

unique: trueは、重複レコードを1つにまとめるためのメソッドです。
例えば、userモデルのemailカラムに、a@gmail.com、b@gamil.com、a@gmail.comのメールアドレスがあった場合、a@gmail.comの重複データをまとめてくれます。

定義する場合は、下記のようにindexにuniqueを貼る必要があります。

~~省略
add_index  :users, [:email], unique: true

*補足

  • uniqueメソッド:Rails5以降で非推奨
  • distinctメソッド:Rails5以降で正式メソッド

Rails 一意性制約のかけ方|Nori|note
Railsでreferencesを使用した外部キーに、同時にUnique属性を設定する - TechBox

命名に不可算名詞を使うことは、良し

理解に戸惑う可能性があるなら、出来るだけ不可算名詞は避けた方が良い。
不可算名詞の場合、rails g modelを実行した時にrails側で識別できず、余計なsやesがついてしまう恐れがあります。(config/initializers/inlrections.rbをいじれば問題は解消できる)
Railsの命名規則【単数形・複数形】 - 箱のプログラミング日記。

テーブルの紐付けは、リレーションシップをうまく使い分ける

テーブル同士の関連付けを、各モデルに定義(belongs_to、has_many)する事で、直感的なコードでデータ取得が可能になります。

#user.rb 
belongs_to :user

#schedule_result.rb
has_many :schedule_results

#レコード取得する時
% user.last.schedule_results
% schedule_results.last.user

【Rails】アソシエーションを図解形式で徹底的に理解しよう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

DBのテーブル名、カラム名をユーザー行動から考えて作成する

ユーザ操作に関連した、DB設計が重要です(ユーザ操作と全く違うデータを管理するのはナンセンスです)
また命名規則がいくつかあるため、確認しながら進めていきましょう。
Railsにおける命名規則 - Qiita
データベースオブジェクトの命名規約 - Qiita

テーブル数を意識して、作成する

テーブル数よりも、1テーブルあたりのカラム数を意識したほうが良いです。
とあるデータでは、1テーブルが持つべきカラム数は、10以下が妥当とのこと。10カラムを超える設計であれば、正規化できないか一度見直す必要があります。
先人達から学ぶRailsのテーブル設計 - Qiita

routesに対するコメント

ユーザーにわかりやすく、意図したURL設計をしていく

ユーザにわかりやすくというよりも、何に対して (URL)/処理内容に適した (HTTP Method)URL設計にすることが重要です。
*覚えやすく、どんな機能を持つURLなのかがわかるようにする
リソースの一部更新におけるURL設計 - Qiita
RESTful APIのURI設計(エンドポイント設計) - Qiita

resourcesメソッドを使ったroutes設定

RESTの原則に則って構築しましょう。resourcesを定義することで、railsですでに定義されている7つのアクションのHTTPメソッドを活用したroutes設定ができます。

*GET, POST, PUT, PATCH, DELETE → index, new, show, create, update, destoryアクションに対応
resources | Railsドキュメント
【Rails】resourcesメソッドを使ってルーティングを定義しよ | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

member routesを活用する → 初学者の場合、get / postのみで書いてしまうことがある

上記で書いた7つのアクション以外にもアクションを定義したい時に有効です。memberは、特定のデータ(users/:id/likesのように:idが入るアクション)に対するアクションを定義することができます。
railsのroutes.rbのmemberとcollectionの違いをわかりやすく解説してみた。〜rails初心者から中級者へ〜 - Qiita

URLに動詞を入れない(規約で決められている)

動詞ではなく、名詞を活用すること。
*動詞を使用することで、変数や関連を使う場合に不自然な命名になってしまうため
モデルやメソッドに名前を付けるときは英語の品詞に気をつけよう - Qiita

単数形リソースを使った方がわかりやすくなります。

idが不要な場合は、単数形リソース(resource)を活用した方が、シンプルでわかりやすくなる
*例えば、/profile/:idのようなパスを、/profileで割り当てることができます。
Rails のルーティング - Railsガイド

ホワイトリスト形式とブラックリスト形式の使い分け

形式を分けることによるセキュリティ対策です。

  • ホワイトリスト形式
    • 「安全な対象」を定義したリストです。リストで定義されているアプリケーションだけを実行させ、リストに定義されていないアプリケーションは実行できません。安全なものだけを利用し、それ以外はすべてブロックするために存在するリストです。
  • ブラックリスト形式
    • 警戒する必要がある「危険な対象」を定義したリストです。リストに定義されているアプリケーションは実行させません。使用してはいけない危険なアプリケーションやプログラムを定義し、危険だと分かっているものを利用しないために存在するリストです。

ホワイトリスト式セキュリティの仕組みとは?ブラックリストとの違い | DAiKO+PLUS(プラス)
ホワイトリスト方式セキュリティの仕組みと効果:株式会社 日立ソリューションズ・クリエイト

shallowオプションでroutesを整える

長くなるURL問題を解消してくれるオプションで、最小限の情報でリソースを一意に指定できるルーティングを作成できます。
ただ、デメリットもありそうなため、使用する際には、改めてよく調べてから使いましょう。
Railsのroutesのshallowは安易に使わないで欲しい – Matsubo Tech Blog
uRails のルーティング - Railsガイド

modelsに対するコメント

最大文字数が必要な箇所には、バリデーション(lengthオプション)を定義してあげる

ユーザビリティを高める一つの手段として、良いかと思います。文字数に制限を設けることで、最小文字数や最大文字数を設定することができます。
例えば、最大文字数を超えている場合や最小文字数に達しない場合にエラーを表示する事で、ユーザアクションの迷いが軽減されます。

#user.rb
validates :name, length: { maximum: 10, message: '氏名は10文字以下に設定して下さい' }

バリデーション例外を捕捉する "!"を加える

エラー原因の調査に役立ちます。
「!」の有無の違いは、保存出来なかった場合の振る舞いに違いがあります。
エラー内容を知りたい場合は「!」を追記し、例外を発生させる事でその内容に応じたエラー文を表示させましょう。

例)

if @user.save
# 保存が正常に完了すれば「true」、保存できない場合「false」を返します。

if @user.save!
# 保存が正常に完了すれば「true」、保存できない場合、例外ActiveRecord::RecordInvalidが発生します。

rails save! create! update!のバリデーション例外を捕捉する - Qiita

他のユーザーが編集不可能にするために、current_userと紐づける

current_userを使用することで、サインインしているユーザーを取得することができます。
例えば、current_userに紐付けず実装した場合、他のユーザが編集や削除などが出来てしまいます。そのため、current_userを用いてセキュアなアプリケーション構築を心がけましょう。

Active Modelを使用し、簡潔に書いていく

多くのモジュールを含むライブラリを用いることで、シンプルかつ簡単に実装が可能になります。
Active Model の基礎 - Railsガイド

複雑な処理は、controllersに記述していくのではなく、modelsにメソッドを用いて記述する

ロジックをControllerに記載することも可能ですが、ファットコントローラーになり、コード管理がしづらい状態になります。
そもそものControllerの役割は、ViewとModelを繋ぐ(ユーザーからの要求を処理し、モデルやビューと連携を行なう)ことです。データの表示はViewに、ロジックの実行はModelに切り分け、MVCアーキテクチャーをうまく活用した実装にしましょう。
【Rails】MVCフレームワークを初心者向けに丁寧に解説! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

スコープを積極的に使用し、意味のあるまとまりを作る

Active Recordの機能の一部で、モデルにscopeを定義することにより、複数のクエリを一つのメソッドにまとめることができます。
コントローラーで複数のクエリを書くよりもscopeを使えば、コードがスッキリする。

例)

scope :active, -> { where(is_active: true) }
default_scope  -> { user(name: "DESC") }

Railsのモデルのscopeを理解しよう - Qiita

has_manyを使って、追加されるメソッドを使う
アソシエーションを貼ることで、使えるメソッドが複数ある

has_manyを定義することで、class_name、source、foreign_keyなどのオプションが使えるようになるため、うまく活用しながら効率的に実装を進めていきましょう。
railsアソシエーションオプションのメモ - Qiita
has_many | Railsドキュメント
【Rails】Associationメソッドまとめ - Qiita

findとfind_byの使用を明確にする

  • find
    • idの値が分かっていて、そのidのデータを取得したい場合に使用する

*各モデルのidを検索キーとしてデータを取得するメソッド
*id以外の条件で検索不可

例)

User.find(1) 
  • find_by
    • idの値が不明で、id以外のカラムを検索条件としたい場合に使用する

*各モデルをid以外の条件で検索するメソッド(idでも検索可能)
*複数の検索条件を指定可能
*返ってくる結果は、最初にヒットした1件のみ

例)

User.find_by_email('test@gmail.com') 

find、find_by、whereの違い - Qiita

該当するレコードがあれば取得、無ければ作成してくれるfind_or_create_byメソッド

引数の条件に該当するレコードがあればそれを返し、もしなければ新規作成するメソッドです。

例)

User.find_or_create_by_name('テストベッカム')

controllerに対するコメント

controllersでの処理はシンプルに記述する
modelsに処理を切り分ける

上記同様のコメントを参照。
ロジックをControllerに記載することも可能ですが、ファットコントローラーになることで、コード管理がしづらい状態になるため、ロジックはmodelsに切り分けて書くようにしましょう。

最後に

これでレビューは以上になります。

当日は、〇〇は〇〇だ!というように、あまり細かな疑問を持つことはなかったように感じます。前提知識が徐々に積み上がってきたことで、このコードはこうした方がより良いのでは?などの疑問や考えが出てきたように思います。

まだまだ技術面はジュニアですが、スキル向上に向けて、引き続きコツコツと努力していきたいと思います。

*実は今回の記事レビューを思い立った理由は、チェリー本(Ruby)を読んだアウトプットでした。
ただ以前書いた記事の内容は、railsがメインだったなぁと思ったので、改めて別のアウトプットを考えてみようと思います。

                                            • -

日々勉強です。
以上、ハチマキでした。

【Git】使用中の作業ブランチに最新のmasterファイルをmergeするやり方

こんにちは。

エンジニア成り立てに読んでいた書籍をまた読み返すと、新たな気づきがあり嬉しい。

どうもハチマキです。

はじめに

git関連で知っておくべきことをテーマに、今回書いていきたいと思います。

今回のテーマは、使用中の作業ブランチに最新のmasterファイルをmergeするやり方です。
チーム開発をしていると、様々なシーンで多々行うことがあります。
やり方をわかっていれば簡単に解消できますが、初めて出くわすとなかなか大変なものです。

そのため、コマンド手順と合わせまして、自然言語の説明も書いていきたいと思います。

環境

事象

チームの方から最新のmasterブランチを作業ブランチにmergeしてもらって、検証環境にUPして欲しいという依頼がきた。

*つまりやることは、使用している作業ブランチを最新にするということです。

対応方法

*補足

  • 作業ブランチは、test_fileとします。
  • 作業ブランチに修正ファイルがある場合、事前に下記どちらかを実行してください
    • コミットはせずに変更を退避したい場合:git stashを実行
    • 編集ファイルが不要の場合:git checkout .を実行

git: git stashの使い方 - Qiita
git reset & git checkout 使い分けまとめ | DevelopersIO

▼言葉で表した手順です。

  1. 作業ブランチからmasterブランチに切り替える
  2. ローカル環境のmasterブランチを最新にする
  3. masterブランチから作業ブランチに切り替える
  4. 作業ブランチに最新のmasterブランチをmergeする
  5. mergeされたファイルを作業ブランチにpushする

*下記のようにGithubにMerge branch 'master' into test_fileがpushされていれば完了です。
f:id:hachimaki37:20210426142512p:plain

▼続いて、実行コマンドに変換した手順です。

1.  $ git checkout master
2.  $ git pull origin master
  remote:~~~~
  , done.

3.  $ git checkout test_file
4.  $ git merge master
5.  $ git push origin test_file

*下記のようにGithubにMerge branch 'master' into test_fileがpushされていれば完了です。
f:id:hachimaki37:20210426142512p:plain

以上です。お疲れ様でした。

                                            • -

日々勉強です。
以上、ハチマキでした。

【15分でざっくり理解を深める】つまり何なんだ!?歴史から考えるオブジェクト指向

こんにちは。

最近勉強会が再開されるようになって嬉しい。

どうもハチマキです。

はじめに

ちゃんと理解を深めていなかったオブジェクト指向。最近やっと1人称で開発ができるようになってきて、開発している中で様々な疑問(Why)が出るようになってきました。
例えば、

  • コードの可読性を上げるには、どういう記述にするのが良いのか?
  • なぜこういう処理、コード記述をする必要があるのか?
  • 記述メソッドにはどういう意味があるのか?適切なのか?
  • このコードだと、パフォーマンスはどうなるのか?

などなど、過去を振り返ると「プログラムが呪文に見える → プログラム理解はあまりできてないけど、なんとなくこんな処理が行われている → このプログラムは、〇〇処理を行なっている」と段階を踏んで理解が進んできました。

現在Railsをメインに開発を行なっており、疑問の中の一つにオブジェクト指向って結局何なんだ?どう活用していけば、より良いプログラムになるんだろうか?こんな疑問をふと持つようになりました。

今回はこの疑問に応えるべく、オブジェクト指向についてなるべく理解(自分の口で喋れるようになる)を上げるため、言葉を噛み砕いて書いてみました。

おそらく15分程度で読める内容になっているかと思います!(内容に誤りがありましたら申し訳ございません)

対象

*コードを踏まえた説明はしておりません。

目次

オブジェクト指向とは?オブジェクト指向言語とは?

まずは、Wikipedia先生に伺いましょう。

オブジェクト指向とは、ソフトウェア開発とコンピュータプログラミングのために用いられる考え方である。
元々は特定のプログラミングパラダイムを説明するために考案された言葉だった。明確な用語としては1970年代に誕生し、1980年代前半に知名度を得て、考案者の手を離れた自由で曖昧な定義のまま発展を続けた後に、1990年代に入るとソフトウェア工学の様々な分野にも応用されるようになった。ソフトウェア開発における一つの標語のような扱い方もされている。

ふむふむ。
つまりプログラミングの考え方や記述方法の枠組み(プログラミングパラダイムであるということですね。

次にオブジェクト指向言語とは何か?について、またWikipedia先生に伺います。

オブジェクト指向プログラミングとは、互いに密接な関連性を持つデータ(変数またはプロパティ)とコード(関数またはメソッド)をひとつにまとめてオブジェクトとし、それぞれ異なる性質と役割を持たせたオブジェクトの様々な定義と、それらオブジェクトを相互に作用させる様々なプロセスの設定を通して、プログラム全体を構築するソフトウェア開発手法である。

何言ってんだ?関連データと処理をまとめた集まり?オブジェクト?
早速何がなんだか理解できません。

オブジェクト指向言語について様々調べていると、

  • オブジェクト=モノ
  • 互いに関連するデータの集合とそれらに対する手続き群をひとまとめにしたオブジェクト

などの説明が多く見受けられました。正直なんとなくわかったようなわからんようなです。
もう少し理解を深めるため、少し歴史から順を追っていきました。

*今回様々調べて得た解は、オブジェクト指向言語とは、関連する情報(データ)の集まりと挙動をひとまとめにした「モノの集まり」だということです。

プログラミング言語の歴史

プログラミング言語の歴史は、1930年ごろに遡ります。(意外と最近にびっくりしました)

f:id:hachimaki37:20210410111552j:plain
f:id:hachimaki37:20210410111600j:plain

プログラミングの歴史は1900年代〜と、思っていた以上に歴史が浅い。にも関わらず進化のスピードは早いことを調べてる中で感じました。

オブジェクト指向自体の誕生は、1970年前後に生まれ、最初に提唱したのは、計算機科学者アラン・ケイ氏と言われております。1972年から開発公開を始めたSmalltalkの言語設計を説明する中でオブジェクト指向は発信され、1981年頃から知名度を得た概念です。

そして知名度を得た背景には、コンピュータの急激な進歩によって、より複雑なソフトウェアが求められ始めたこと(時代)が大きく関連しています。
それが、次に説明するソフトウェア危機というものです。簡単にどんなものなのか書いていきます。

ソフトウェア危機とは?

そもそもソフトウェア危機とは、ソフトウェア工学がまだ十分に確立していなかった頃、よく使われた言葉です。

▼詳しくはこちらから
ソフトウェア危機 - Wikipedia

端的にいうと、ソフトウェアの発展が、利用者(市場)の求める需要を満たすだけの生産量・品質・費用・時間の面で追いつかず、供給できないという問題や課題を指します。

それら問題により、具体的に開発プロジェクトでは下記のような実態が顕著に現れていました。

Wikipedia先生の引用

・予算を超過してしまったプロジェクト
・予定期間を超過してしまったプロジェクト
・品質の低いソフトウェア
・要求仕様を満たさないソフトウェア
・管理不能状態のプロジェクトと、保守困難となったコード

オブジェクト指向の成り立ち

ソフトウェア危機が叫ばれた背景には、プログラムの複雑化とバグ問題が密に関わっています。

これはつまりコンピュータの性能が向上したことで、従来より大規模なソフトウェアが書かれることになり、プログラムの複雑化やプログラム保守が難航(追加や修正がより困難)する問題へと発展しました。(プログラムは複雑化する一方なのに、管理手法もなければ、データ型は基本的な数値でしかなかったわけです)

これら問題を解決するために目指したことは、「開発効率の向上」「メンテナンス性の向上」です。

上記を目指す上で、様々な手法や方法論が開発される中考えられたのが、プログラムの独立性、再利用性、拡張性の3つを追求するということです。

そして、その3つの性質を兼ね備えた概念こそ、オブジェクト指向なのです。(継承・カプセル化ポリモーフィズム

やっとつながってきました。

オブジェクト指向の価値

では次に、オブジェクト指向の最大の価値は何かについてです。

早速結論ですが、それは「抽象化による、わかりやすさと利便性」にあります。

んーよくわからんですね。。なので、いきなりですが一例あげたいと思います。
我々の自然言語の世界では、例えばボール(Ball)を見ると、これはボールだ!と認識できます。
そして同様にリンゴ(Apple)を見れば、これはリンゴだ!と認識できるはずです。

プログラムの世界では、そうはいきません。ボールを見てもボールとは認識出来ませんし、リンゴを見ても同様に認識が出来ません。

では、どうするのか?
ボールは、転がる・跳る・丸い・皮膜や外殻に包まれている、リンゴは、赤い・丸い・甘い・食べるのように、関連する情報(データ)と挙動をひとまとめにしたモノ(オブジェクト)に名前をつけることで、ボールはボールになり、リンゴはリンゴになり、そして認識できるようになります。

我々の自然言語の世界では、ボールやリンゴは、すでに情報が抽象化されたモノの名前を使って、それらを認識し活用しています。
日常会話の中で、転がる・跳る・丸い・皮膜や外殻に包まれているのを買いに行くんだよね!というより、ボールを買いに行くと言った方が自然でわかりやすいですよね?

つまりこれが抽象化であり、このプログラミングパラダイム(概念)がオブジェクト指向です。

ただ抽象化だけでは、価値を実現させることができません。
次に説明する継承、カプセル化ポリモーフィズム多態性の3大要素をうまく活用する(組み合わせる)ことが重要です。

▼組み合わせることで、下記のようなメリットをもたらすことができます。

  • システムの柔軟性が増す
    • カスタマイズがしやすくなる
  • スムーズな作業につながる
    • 開発者同士での認識の共有がしやすくなる
  • 開発作業の効率化を図れる
    • 再利用がしやすい

*デメリット

  • オブジェクト指向の概念を習得・理解することに時間がかかる
    • 理解に時間がかかることで、作業進捗に影響が出がち
  • オブジェクト指向の概念の理解度が、人によって差が出ることがある
    • 理解度のバラツキが作業の停滞を生む
  • 一から開発する場合、コードが複雑になりやすい

オブジェクト指向の3大要素

1つ目:継承

Wikipedia先生から引用

継承(けいしょう、英: inheritance、インヘリタンス)とはオブジェクト指向を構成する概念の一つである。あるオブジェクトが他のオブジェクトの特性を引き継ぐ場合、両者の間に「継承関係」があると言われる。

主にクラスベースのオブジェクト指向言語で、既存クラスの機能、構造を共有する新たなクラス(派生クラス、派生型)を定義すること(subclassing、サブクラス化)ができる。またそのような派生クラスは「親クラス(スーパークラス)を継承した」という。具体的には変数定義(フィールド)や操作(メソッド)などが引き継がれる。またJavaC#などのインタフェース実装のように、機能セットの仕様(プロトコル)のみを引き継ぐ場合もある。

一般的に、BがAを継承する場合、"B is a A."(BはAの一種である)という意味的な関係(Is-a関係)が成り立つ。従って、同じふるまいを持つからと言って、意味的に無関係なクラス間に継承関係を持たせるのは適切でない場合が多い。

継承は、再利用の仕組みです。

継承を利用することで、親クラスを継承した新しいクラス(派生クラス)は、メッセージ・メソッド・属性といった親クラスの構造を受け継ぐことができます。つまり、派生クラスでは、親クラスの構造を再利用でき、新たに機能を実装することが可能になるということです。

もう少しわかりやすく、ボールを例に説明していきます。
例えば、サッカーボールを作る時、一からボールの情報を定義する。テニスボールを作る時もまた同様、一からボールの情報を定義する。これ結構めんどくさくないですか?
できればすでにあるボールという概念(情報)にサッカーボール特有の情報を加えるだけで、サッカーボールにできた方が良くないですか?

プログラムの世界でも同様です。
サッカーボールとテニスボールの抽象度をあげると、どちらもボールです。定義したボールの情報を継承すると、ボールに関連する情報や挙動を再利用することが出来ます。
つまりサッカーボールを作りたければ、継承したボールの情報に黒と白の色の情報、大きさの情報などを加えるだけで、サッカーボールにすることができるのです。

このボールに関連する情報や挙動を使うことこそ、再利用の仕組みであり、継承なのです。

▼メリット

  • コードの追加・修正といったメンテナンスが容易になる
  • より抽象的な共通する機能を親クラスにまとめることができる
  • コードが読みやすくなる(可読性が上がる)

*継承を利用する上で重要なことは、クラス同士の関係が派生クラス is a 親クラス(派生クラスは親クラスの一種である)という意味的な関係(Is-a関係)を築く必要があります。
*今回の例だと、サッカーボール is a ボール(サッカーボールはボールの一種である)という関係です。

逆転の発想でオブジェクト指向の「継承」を使いこなす | IT × マーケティング コラム
【オブジェクト指向】クラスの継承とは?【プログラミング】 | 初心者向け完全無料プログラミング入門
【第3回】継承 (1/4):CodeZine(コードジン)

2つ目:カプセル化

Wikipedia先生から引用

カプセル化(カプセルか、英: encapsulation)とは、データ(属性)とメソッド(手続き)を一つのオブジェクトにまとめ、その内容を隠蔽することを言う。

カプセル化は、一つのオブジェクトとしての独立性を高める仕組みです。

カプセル化を利用することで、データのアクセスを制限(不整合を引き起こすような操作をできなくする)し、データを保護することで、オブジェクトの安全性を高めることができます。

データアクセスの可否はアクセス指定子(private/public)と呼ばれるものによって決められ、クラスやオブジェクト外からのデータアクセスに制限(拒否)をかけることができます。

つまり!先ほどの例を使うと、サッカーボールの情報を勝手に、色を白と緑に、大きさを拳くらいに変えられたら困りませんか?
これサッカーボールなのに、なんかテニスボールみたいになってるやん!ってなりますよね。。(情報が壊れる)

これが一つのオブジェクト(サッカーボール)としての独立性を高める仕組みであり、カプセル化なのです。

▼メリット

  • 仮に異常なデータ変更が来た場合は、適切な値に戻したり、変更を拒否することができる
  • 予期せぬバグを防止できる
  • 情報を直接操作することをできなくし、情報が壊れてしまうことを防止できる

カプセル化とはなにか?超わかりやすく解説します! | じゃぱざむ
オブジェクト志向のカプセル化を、総合医療病院で例えると「窓口」という説を語ってみる。 | IT × マーケティング コラム

3つ目:ポリモーフィズム多態性

Wikipedia先生から引用

ポリモーフィズム(英: Polymorphism)とは、プログラミング言語の型システムの性質を表すもので、プログラミング言語の各要素(定数、変数、式、オブジェクト、関数、メソッドなど)についてそれらが複数の型に属することを許すという性質を指す。ポリモルフィズム多態性、多相性、多様性とも呼ばれる。対義語はモノモーフィズム (Monomorphism)、単態性、単相性で、プログラミング言語の各要素が唯一つの型に属するという性質を指す。

ポリモーフィズムは、拡張性の仕組みです。

異なる型のオブジェクトを同一視し、そのオブジェクトの型によって動作を切り替えることが可能になります。
もう少し細かくいうと、物事を抽象概念として捉え、一つの命令に対し、各々が持った別の動き(振る舞い)を可能にします。

例でいうと、ボール・サッカーボール・テニスボールってどれも跳ね方が異なります。この「跳ねる」という挙動を、サッカーボールとテニスボールに合わせて、別々の跳ね方に書き換える(オーバーライド)と、その跳ね方に変えてくれます。(呼び出し先に応じて、適切なメソッドが実行されることで、良しなに振る舞いが変化する)

つまり跳ねるという挙動(同じメソッド名)でも、ボール・サッカーボール・テニスボール(オブジェクト)の変化に応じて、結果が変わることはポリモーフィズムの拡張性の仕組みなのです。

▼メリット

  • 何度も同じコードを書く必要がなくなるため、コードがきれいになる
  • プログラムの読みやすさが向上する
  • プログラムの再利用のしやすさが増す

【オブジェクト指向とは何ぞや】ポリモーフィズムとダックタイピングを理解しよう | shin>>media
ポリモーフィズム(多相性)とは - IT用語辞典 e-Words
プログラマー1年生がポリモーフィズムについて学んだのでRPGで説明する。 - Qiita

まとめ

以上で、一通りオブジェクト指向の説明が終わりました。いかがだったでしょうか。

様々調べて得た私の解は、オブジェクト指向言語とは、関連する情報(データ)の集まりと挙動をひとまとめにした「モノの集まり」だということです。
そしてオブジェクト指向言語において特に重要なことは、継承・カプセル化ポリモーフィズムをうまく組み合わせるということです。

今回を通して、オブジェクト指向の理解は、だいぶ進んだように思います。
ただあくまで、オブジェクトを使ったプログラム全体を構築する手法であるということ、そして概念だけを理解したからといって、実際のプログラムに実装できるかはまた別問題だというです。

なので、次は実践に活かせるようオブジェクト指向設計実践ガイドを読んでみようと思います。

参考資料

                                            • -

日々勉強です。
以上、ハチマキでした。

【Git/GitHub/エラー解消法】git pushしたらConflicting filesが発生した時の解消方法

こんにちは。

いつでもLGTMは嬉しい。

どうもハチマキです。

はじめに

今となっては問題なく解消できるようになったコンフリクト。
エンジニア成り立ての時は、コンフリクトが発生した時点で重い腰をあげるように解消に挑んだものです。

今回は、発生したコンフリクトの発生原因とその解決法について書いていこうと思います。

環境

問題

作業ブランチで進めていた開発作業が完了し、いざGithubにgit pushしたら一部のファイルでコンフリクト(競合)が発生した。。
f:id:hachimaki37:20210413194138p:plain

コンフリクトってなに?発生原因は??

端的にいうとコンフリクトは、ファイル同士の競合です。
https://wa3.i-3-i.info/word11329.html

コンフリクトの発生原因は、同じもの(ファイルやコード)を同じタイミングで使う(修正や追加)ことにより、整合性がとれなくなることで発生します。

例えば、AさんがTODO登録機能を開発するために、最新のmasterファイルをgit pullして、作業ブランチを作成します。

一方Bさんは、TODO更新機能を作成するため、Aさんと同様の手順を踏んで、作業ブランチを作成し、開発を進めています。

1週間後、Aさんは開発が完了し、ファイルZをgithubにpushして作業ブランチをmasterファイルにmergeします。(*現時点で最新のmasterファイルが更新されました)
その2日後、Bさんも進めていた開発が完了したため、修正したファイルZとファイルYの2つのファイルをgithubにpushします。

おそらくここで、今回テーマであるコンフリクトが発生するはずです。

Aさんの編集ファイルZが、masterブランチにmergeされたことで、Bさんが現在作業ブランチで使用しているファイルZとの整合性(Aさんが編集したファイルZの情報がない)が取れないことが理由でコンフリクトが発生します。

*補足
開発現場では、時よりコンフリクトが発生します(僕は何度も出くわしました)
理由はご察しの通り、複数人で開発を進めているためです。そのため、上記例にあげたことが常に起こっているため、タイミングよく、同ファイルを修正し、誰かがmasterファイルにmergeすると、今回のように整合性が取れずコンフリクトが発生するのです。

ただ、解消方法自体は、至って簡単ですので、順を追って解消していきましょう!

解消方法

*作業ブランチは、test_fileとします。
github上でコンフリクトしたファイルを確認しておく(下の方にファイルパスがあるはず)とコンフリクトファイルが事前に把握できます。

▼言葉で表した手順です。

  1. 作業ブランチからmasterブランチに切り替える
  2. ローカル環境のmasterブランチを最新にする
  3. masterブランチから作業ブランチに切り替える
  4. 作業ブランチに最新のmasterブランチをmergeする(mergeすると、ターミナルやファイル内で競合したファイルパス、コードの箇所を教えてくれると思います)
  5. conflictが発生した箇所(コード)を修正する
  6. 編集ファイルをgit addする
  7. 編集ファイルをgit commitする
  8. 編集ファイルをgit pushする

以上です。

▼続いて、実行コマンドに変換した手順です。

1.  $ git checkout master
2.  $ git pull origin master
3.  $ git checkout test_file
4.  $ git merge master
Auto-merging ファイルパス
CONFLICT (content): Merge conflict in ファイルパス
...
Automatic merge failed; fix conflicts and then commit the result.

# 補足
# $ git stateでファイルを一度確認

5. conflictが発生した箇所(コード)を修正する
6.  $ git add test_file
7.  $ git commit -m 'fix Conflicting files'
8.  $ git push origin test_file

以上です。

これで、コンフリクトは解消できるはずです。
コンフリクトが発生すると、最初はなかなか焦りますが、一旦深呼吸して実行できれば意外とすんなり解消できるものです。

【書籍】「信頼の原則」を拝読し、考えに変化があったこと

こんにちは。

ひどい花粉症が薬飲んだら良くなりました。(それはそうか)

どうもハチマキです。

はじめに

今回「信頼の原則」という書籍を拝読しました。拝読の目的は、信頼ある組織(高信頼組織)を構築する必要性がそもそもあるのか、そして高信頼組織ではどういったメリットが存在するのかを知ることです。

そもそも組織では、メンバー層の視点やマネジメント層の視点、経営者層の視点、その他(投資者視点、採用者視点)など、様々な視点や考えが入り混じっていると考えております。
拝読した結果、互いを信頼、尊重する組織こそ、目標(ビジョン)に対し、最大限の価値を創出できる可能性を秘めている組織であるということを大きく学びました。

そんなこんなで一通り読み終えたため、定着と実行のためにアウトプットしていこうと思います。

総論

最初に購読の目的がどうだったのか、結論を述べていきます。

結論は、組織(個人)では、高い信頼のある組織(高信頼組織)である必要があると考えました。
その理由としてあげられることは、高信頼組織こそ最大限の価値を創出できる可能性があるからです。つまりこれが最大のメリットだと感じました。

高信頼組織(互いを信頼、尊敬し合える組織)構築により、仕事へのモチベーションやアイディア創造などに波及され、結果的に業績の向上などの効果が現れてくるということです。

正直拝読する前は、「誰とするか」よりも「何をするか(どのような価値を世に生み出していくのか)」の方が、組織としては重要な要素ではないかと考えていました(目的に対して集まる組織)。
なぜならその目的を達成することで、人々が抱えている問題や社会課題を取り除くことでき、人々の生活、人生に変化をもたらすことができると考えていたからです。

しかし、組織で「何をするか(目的)」という価値を最大限に創出していくためには、「誰とするか」がもっとも重要な要素であることを今回学びました。

つまり高信頼組織を構築できる人たち(組織)でなければ、目的に向かって走り出しても、最大限の価値を創出できないというわけです。
組織でどんな価値を生み出していくのかと同様に、誰とその目的を目指していくかが重要だということです。

高信頼組織を構築するための必要な要素を、3つほどピックアップしてみました。

高信頼組織を築く、重要な要素

・公私ともに行動に一貫性を

1つ目は、組織で信頼を構築していくためには、人格だけではNG。つまりいい人ということだけでは、組織の中では信頼を築けないということです。
善意と実行力、公と私、言葉と行動の一致が重要であり、言葉だけでなく、自らの行動と体現で示していく必要があります。

また、周囲の意見を傾聴し、常に自らを客観視し、改善していく謙虚さも重要な要素でありました。

・利己的ではなく、利他的である

2つ目は、個々人が利己的ではなく、利他的であることです。自分のためではなく、相手のために誠意を尽くすこと。そのためには相手に関心をもち、ひたすら相手の言うことを理解することが重要な要素です。その結果、尊敬が生まれ、信頼へと繋がっていく。

つまり利益を自分のことのように大切にする人格を持つことが重要ということです。

また個々人だけでなく、組織から利他的である重要性を発信していき、浸透を促すことも重要な要素として挙げられておりました(例えば、組織の評価軸に加え、高信頼組織である必要性を評価軸というメッセージで組織全体に伝えていく)

・相手の可能性に期待し、信じ続ける

3つ目は、人と人とが信頼を構築するためには、権限を相手に委ねる(任せる)必要があるということ。そもそも信頼は0から立上げ、育て、評価しながら修復し、苦労して構築していくものと考える必要があります。
相手に権限を委譲し、フィードバック(悩みなどに耳を傾け)を通じ、相手の可能性に期待し、信じ続けることが重要です。

また、権限委譲される側も相手の期待に反する行動は避ける、常に期待以上の結果を生み出していくことで、相互の信頼関係が積み上がっていくではないかと考えました。

改善手段の一部紹介

課題に対する様々な改善案が紹介されていました。全てをここで記載することは難しいため、一部紹介する形にしたいと思います。

重要なことは、現状の組織に合わせた改善手段を講じることです。
そのためには、今どういった組織課題を抱えており、何を改善すれば歯車が周り始めるのかを分析し、知る必要があります。

・現状の信頼レベルを知る

今いるメンバーが、どのような動機で仕事に従事しているのかを知る。(相互理解を深める)
相互を理解、知ることができなければ、適正なアクションをしても効果は希薄になるため。

・20210330追記
組織として、どの程度の信頼レベル(宗教レベル?家族レベル?など)を目指していくのか認識を合わせておく必要があると感じた。
理由は、各々が考える信頼レベルで進むことで、組織として目指す信頼レベルの齟齬が発生するため

・適切な情報の透明化

同じビジョンがあるからこそ、良いことも悪いこともファクトの情報で透明化を行う。
正しい情報が透明化されないと、立ちはだかる難問に組織で取り組むことができず、信頼残高を積み上げていくことはできないため。

・組織全体の浸透

行動指針の浸透を目指す手段として、組織全体へ行動指針の重要性を発信していく。
例えば、組織の目標を追求した人を称賛する、ミッションを体現した人を表彰するなど、重要性を組織全体に発信していき、浸透を促していく必要がある。

自らの課題と改善項目

直近は、下記2つを改善して行こうと考えました。

・情報の伝達

情報解釈による粒度のばらつきを極力平す。
必要性や重要性、良い情報や悪い情報など、自分の粒度が相手に伝わるように改善していく。

そのためには、相手の求めることを理解し、何が伝わって何が伝わっていないかを自ら理解、確認していく必要がある。

・信頼し続ける勇気

今回大きく学んだ要素として、信頼し続ける勇気を持つこと。
どうしても合意した期待が下回った場合に、信頼残高が減少しまう傾向がある。ただ誰も好んで裏切られたいとは思わないし、裏切りたいと思っている人も少ないはず。

失敗のあとの信頼、そして失敗しても再チャレンジしようとする気持ちを促進させること(失敗した人に手を差し伸べる)に力を注いでいきたいと思う。

どんな人であろうと権限、チャンスをあたえ、軌道に乗るまでは、目をつぶって信頼し続ける勇気が、自分には必要だと感じた。

まとめ

将来的には、プロダクトマネジメントやチームマネジメントなどに携わって行きたいと考えている。
そのためには、相手を尊重し常に信頼し、高信頼組織を構築するスキルの習得が必要であると感じた。

時に信頼を裏切られることがあるかもしれない。しかし信頼を裏切られても、信頼は修復できるということ。
心配性がすぎると、成功のチャンスもなく、人間関係をも豊かにならないということ。
不信感より信頼、単独行動より相互依存、神経質に守りにはいるより信頼の原則に従ったほうを選択するということ。

注意深く管理することをリスクと考え、とにかくまずは相手を信頼し続けることから始めてみようと考えました。

書籍紹介

                                            • -

日々勉強です。
以上、ハチマキでした。

【Rails/環境構築(M1)】Could not find a JavaScript runtime. (ExecJS::RuntimeUnavailable)エラーの対処方法

こんにちは。

最近SQLにハマってます。

どうもハチマキです。

はじめに

最近MacBook Air(M1,2020)を購入したため、開発環境の構築を行いました。その時に発生したエラーの対処方法についてです。

早速結論になりますが、この記事で対象にしているエラーExecJS::RuntimeUnavailableですが、下記記載した解消方法では根本的に解消できませんでした(サーバ起動時にエラーが発生する)

ですので、もしこの記事で書かれた内容で解消できない場合は、こちらの記事と合わせて参考にして頂けばと思います。
【Rails/環境構築(M1)】ローカルサーバ起動後にアクセスすると「dyld: lazy symbol binding failed: Symbol not found」エラーが発生し、サーバが落ちる時の対処方法 - めがね屋のエンジニアブログ

環境

問題

rspecのテストコマンドを実行すると、JS関連でエラーが発生する。

% bundle exec rspec

An error occurred while loading ./spec/features/~.rb
Failure/Error: require File.expand_path('../config/environment', __dir__)

ExecJS::RuntimeUnavailable:
  Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.
# ./vendor/ruby/2.6.0/gems/execjs-2.7.0/lib/execjs/runtimes.rb:58:in `autodetect'
# ./vendor/ruby/2.6.0/gems/execjs-2.7.0/lib/execjs.rb:5:in `<module:ExecJS>'
# ./vendor/ruby/2.6.0/gems/execjs-2.7.0/lib/execjs.rb:4:in `<main>'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
# ./vendor/ruby/2.6.0/gems/uglifier-4.2.0/lib/uglifier.rb:5:in `<main>'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
# ./vendor/ruby/2.6.0/gems/bootsnap-1.7.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
# ./config/application.rb:7:in `<top (required)>'
# ./config/environment.rb:2:in `require_relative'
# ./config/environment.rb:2:in `<top (required)>'
# ./spec/rails_helper.rb:5:in `require'
# ./spec/rails_helper.rb:5:in `<top (required)>'
# ./spec/features/~.rb:1:in `require'
# ./spec/features/~.rb:1:in `<top (required)>'
No examples found.


Finished in 0.00004 seconds (files took 3.01 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples

原因

Javascriptを実行するために必要なランタイム(ソフトウェアの実行に必要なプログラム)がないことでエラーが発生

解決方法

必要なランタイムをインストールすれば解消です。
rails newで生成されたGemfileのmini_racerコメントアウトを外し、bundle installを行うことで解消できます。

Gemfileファイル

#gem 'mini_racer', platforms: :ruby #コメントアウト外す
gem 'mini_racer', platforms: :ruby

ターミナル(コメントアウト外した後に)

% bundle install

*追記(20210317)
結局この対応方法では解消できませんでした。
解消するためにやったことを簡潔に申しますと、mini_racerの代わりとなる必要なランタイムをインストールする対応です。

▼こちらの記事も合わせて、ご参考にしてください。
【Rails/環境構築(M1)】ローカルサーバ起動後にアクセスすると「dyld: lazy symbol binding failed: Symbol not found」エラーが発生し、サーバが落ちる時の対処方法 - めがね屋のエンジニアブログ

【Rails/環境構築(M1)】Could not find Firefox binary (os=macosx)エラー解消方法

こんにちは。

春風邪でしょうか。急に体調が悪くなりました、。

どうもハチマキです。

はじめに

最近MacBook Air(M1,2020)を購入したため、開発環境の構築を行いました。
環境構築にいくつかはまった箇所がありましたが、今回はコマンド一つで解消できます。
忘れないようメモしていきます。

環境

やりたいこと

発生したCould not find Firefox binary (os=macosx)エラーを解消したい

問題

rspecのテストコマンドを実行すると、下記エラーが発生する。
▽エラー事例

 Selenium::WebDriver::Error::WebDriverError:
       Could not find Firefox binary (os=macosx). Make sure Firefox is installed or set the path manually with Selenium::WebDriver::Firefox::Binary.path=

解決方法

カスタムアプリパスを指定し、Firefoxをインストールすることで解決にいたりました。
※設定変更することで、chromeなどでも対応可

% brew  install --appdir="/Applications" firefox 

==> Downloading https://download-installer.cdn.mozilla.net/pub/firefox/releases/85.0.1/mac/
######################################################################## 100.0%
==> Installing Cask firefox
==> Moving App 'Firefox.app' to '/Applications/Firefox.app'
🍺  firefox was successfully installed!

参考資料

RSpecでCould not find Firefox binary (os=macosx)というエラーが発生した件 - Qiita

                                            • -

日々勉強です。
以上、ハチマキでした。