おさとの雑記帳

日々の学習のアウトプットに使っていきます!

【リーダブルコード】第三章  誤解されない名前 の要約

最善の名前とは誤解されない名前をつける!

ある変数や関数名をつけた際には「他の意味に取られることはないか?」を自問するべきです。
以下では自問・検討の仕方を見ていきます。

曖昧な名前を避ける

変数に対して代入されるものが、何かに処理をくわえて残ったものか、それとも除外されたものなのか。
ここではfilterと言うメソッドが例に挙げられてました。
filterは絞り込んだ結果を選択するのか、除外するのかがわかりにくいですね。
なので、選択するならselect、除外するならexcludeのような名前にした方が意味が伝わりやすいですね。

ちなみにRubyではfilterselectエイリアスなので実行結果は同じになります。意味を考えて使い分けられるといいですね。
除外の場合はrejectというメソッドが用意されています。

arr = [1, 2, 3, 4, 5]

arr.select { |num| num.even? }
#=> [2, 4]

arr
#=> [1, 2, 3, 4, 5]

arr.filter { |num| num.even? }
#=> [2, 4]

arr.reject { |num| num.even? }
#=> [1, 3, 5]

限界値を含めるときはmin, maxを使う

あるグループへの参加は20人までだとした時に以下のようにしてみます。

TOO_MANY_MEMBERS_LIMIT = 20

if @group.members > TOO_MANY_MEMBERS_LIMIT
  flash[:danger] = "グループに追加できるメンバー数を超過しています"
end

このままだと20を含むか含まないかが曖昧です。
限界値を明確にする時には、min_max_を頭につけてあげましょう。
MAX_MEMBERS_TO_GROUPなどとすると限界値であることがわかりやすいですね👀
(余談: TOにするかINにするかで悩みました、、INだと語気が強いかもと思い、、)

bool値の名前

変数であれば頭にis_つけたり、メソッドであればお尻に?つけたりするとわかりやすいよねって話ですね👀
Rubyの組み込みメソッドであるinclude?なども真偽値を返すメソッドですね。

arr = [1, 2, 3, 4, 5]

arr.include?(1)
#=> true
arr.include?(6)
#=> false

複数の名前を検討する

命名するときは複数の名前をあげて、それぞれの長所と短所を比べてよりいい名前を選定しましょう、ということでした。
状況に応じてlengthが適切だったり、sizeが適切だったりするのが命名の面白いところですね💪

【リーダブルコード】第二章 名前に情報を詰め込む の要約

初めに

変数名や関数名には情報を詰め込むべきです。
例えば、 datatmp などは情報があまり含まれていないといえます(経験としてdataを使った際にレビューをいただいたことが)。
情報を詰め込んだ名前とはどのようなものでしょうか。
命名するポイントの中で特に重要だと感じたのは

  • 明確な単語かどうか
  • 汎用的な名前ではない
  • 抽象的より具体的な名前

の三つです。

明確な単語かどうか

get と言う単語には 「受け取る」「手に入れる」「身につける」などの意味があります。
get関数があるとき、いかにして上記いずれかの意味と知ることができるでしょうか。
仮に receive と言う関数名であれば 何かしらを「受け取る」関数であることを推測できそうです。
fetchであれば何かしらを取ってくる関数であるように感じます。

このように明確な単語を変数や関数名につけてあげることで、より読みやすいコードになります。
コツとして類語辞典をひく、と言うのも挙げられていました。

汎用的な名前ではない

# A
data = {
  first_name: "Rails",
  last_name: "太郎",
  age: 20
}

Aの変数名では中身を見るまでどんなデータが入っているかわかりません。

# B
user_info = {
  first_name: "Rails",
  last_name: "太郎",
  age: 20
}

Bはどうでしょうか?
中身を見るまでもなく、「userに関する情報なんだな」とわかりますね💪

抽象的な名前より具体的な名前

「昨日ラケットでボールを打つスポーツをして筋肉痛なんだよね〜」
...ラケットでボールを打つスポーツ?卓球かな?テニスかな?スカッシュかな?
抽象的すぎる表現を使うとわかりにくいお話になってしまいます。

例えば send_message_to(contact) のようなメソッドがあるとします。このメソッドが「問い合わせに対して何らかのメッセージを送る」のは想像に難くないですね。
ただし、send はいささか抽象的ではないでしょうか?送られてきたものに対して送信はしてるのですが、新規送信のない、返信のみである今回の例の場合 reply_to(conntact) のようにしてあげると、
問い合わせに対して返信していることが明確になりそうです 💪

【リーダブルコード】第一章 理解しやすいコード の要約

「簡潔さ」と「安心」のどちらが大切か、またどのようにその判断を行うか

以下のコードを見比べて見ましょう。

# A
n = 3
puts n % 2 == 0 ? "#{n}は偶数です" : "#{n}は奇数です"

#=> 3は奇数です


# B
n = 3
if n % 2 ==0
  puts "#{n}は偶数です"
else
  puts "#{n}は奇数です"
end

#=> 3は奇数です

Aの方が簡潔で、Bの方が実行したいことがわかりやすい気がします。
おそらくBであればプログラミングをやっていない人にもなんとなく意味が伝わるのではないでしょうか?

例のような簡単な条件分岐であれば大きな差はないかもしれませんが、条件や処理が複雑になったときに重要となる考え方が、コードは他人が見たときに最短で理解できるように書く必要があるというものです。

2通りで書いた処理を誰かに見せたときに、より早く理解してもらった方の処理がより読みやすいコードと言えそうです。ここでいう「誰か」はレビュアーだったりあるいは未来の自分かもしれませんね 🤓

極端な例で言うとBは以下のように書けそうです。

# B2
n = 3
if n % 2  == 0; puts "#{n}は偶数です"; else; puts "#{n}は奇数です"; end

B2よりBの方が理解にかかる時間は短そうですね 🧐
より処理が長く複雑になれば、理解にかかる時間は増えていくのは想像に難くなさそうです 👀

このようにわかりやすいコードを目指す際には理解するまでに最短になっているか確認するのが良さそうです 💪

リーダブルなテストコードについて考えように参加しました!

veriserve-event.connpass.com

こちらのイベントに参加させていただいたので感想や特に学びになったことについて書いていこうと思います 💪

概要

以下イベントページからのコピペです.

読みやすいテストコードは自動テストを長期で運用するために重要な技術の1つです。 しかし、テストコードを読みやすくするにはどうすれば良いかといった議論はユニットテスト以外ではあまり行われていないと感じています。 そこで今回、さまざまなテストレベルとロールで活躍されている方々からテストコードをリーダブルにする方法について語っていただき、それぞれの違いや共通点について議論していただきます。

特に印象に残ったこと

テストコードは過度にDRYにし過ぎない

基本的にコードはDRYなほどいいと思っていましたが、ことテストコードに関してはDRYになりすぎないように気をつける必要があるとのことでした。
例えば変数を過度に使用することはあまり好ましいとは言えないようです。

「わかるようでわからんコード、だがしかしパスするテスト」はApproveしない

このようなコードはきっと読みにくかったり、DRYすぎるコードである可能性があるので、変更をお願いする方がいいようです。
その時のコツは自分を棚に上げることが大事であるとのことでした 笑

cssセレクター使いすぎない

button.primary といった要素の指定の仕方はcssを知らないと何を書いているかわからないでしょう。
テストコードの理想は非エンジニアでも理解できるくらいわかりやすく記述されていることなので、できる限りセマンティックな書き方、ユーザーにとって意味のある書き方ができるといいとのことでした。

ハイレベルテストケース、ローレベルテストケース

具体度を上げた(ローレベル)テストを書く前に、何をテストしているか少し抽象度の高い概念(ハイレベル)で宣言してあげると見通しが良くなる、とのことだと解釈しました。

まとめ

上記の他にも Three Amigos や 種々のツール、意図したテストをどのように表現するかなどが紹介されており学びが多く、聞いてて楽しい時間となりました。
「脳内メモリを使わない」ようなテストコードをかけていけたらなと思います ! 次のイベントに向けてテストについての理解、学びを深めておきたい所存です 💪

vscodeでrubyのフォーマットを有効にしたい

目標

vscoderubyのフォーマッターである rufo を使って自動整形を行えるようにする

手順

  1. vscodecommand + shift + x を押して拡張機能を開く
  2. RubyRufo をインストールする image.png image.png

  3. gem install rufo を実行する(permissionのエラーが出たらsudoつけるなどして対応してください💪)

  4. command + shift + p を押して検索窓に setting と入力して Preferences: Open Settings (JSON) を選択する image.png

  5. settings.json というファイルが開くので以下を貼り付ける settings.json { "editor.formatOnSave": true, "rufo.exe": "rufo", "rufo.useBundler": false, }

  6. vscodecommand + qvscodeを閉じて、再度vscodeを開く

  7. ruby のファイルを開いてsaveした時に整形されたらフォーマッターのインストールの完了です 🙆‍♂️

    image.png image.png

まとめ

vscoderubyの自動整形ができるようになりました 💪

Railsチュートリアル4章やってみた

はじめに

Railsチュートリアルの演習を進めつつ、わからなかったところ、調べたところをアウトプットしてしこうと思います。

4.1

4.2

4.2.1

演習
>> city = "那覇市"
=> "那覇市"
>> prefecture = "沖縄県"
=> "沖縄県"
>> puts "#{prefecture} #{city}"
沖縄県 那覇市
=> nil
>> puts "#{prefecture}\  #{city}"
沖縄県  那覇市
=> nil
>> puts '#{prefecture}\  #{city}'
#{prefecture}\  #{city}
=> nil

4.2.2

演習
>> "racecar".length
=> 7
>> "racecar".reverse
=> "racecar"
>> s = "racecar"
=> "racecar"
>> s == s.reverse
=> true
>> puts "It is a palindrome!" if s == s.reverse
It is a palindrome!
=> nil
>> s = "onomatopoeia"
=> "onomatopoeia"
>> puts "It is a palindrome!" if s == s.reverse
=> nil

4.2.3

演習
>> def palindrome_tester(s)
?>   if s == s.reverse
?>     puts "It's a palindrome!"
?>   else
?>     puts "It's not a palindrome."
?>   end
?> end
=> :palindrome_tester
>> palindrome_tester("racecar")
It's a palindrome!
=> nil
>> palindrome_tester("onomatopoeia")
It's not a palindrome.
=> nil
>> palindrome_tester("racecar").nil?
It's a palindrome!
=> true

4.2.4

def full_title(page_title = '')
end

ここのpage_titleはオプション引数ではなく、デフォルト引数だと思っていたのですが、どうなのでしょうか、、🤔

ちなみにオプション引数は

def full_title(**args)
end

のような形で、任意のキー、バリューを引数に取れるものかと思っています.

このあたり整理してみたいですね 💪

4.3

4.3.1

演習
>> a = "A man, a plan, a canal, Panama".split(", ")
=> ["A man", "a plan", "a canal", "Panama"]

>> s = a.join
=> "A mana plana canalPanama"
>> s = s.split(" ").join
=> "AmanaplanacanalPanama"
>> palindrome_tester(s)
It's not a palindrome.
=> nil
>> palindrome_tester(s.downcase)
It's a palindrome!
=> nil

>> ("a".."z").to_a[6]
=> "g"
>> ("a".."z").to_a[-7]
=> "t"

4.3.2

演習
>> (0..16).map{|i| i**2}
=> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256]

>> def yeller(array)
?>   array.map(&:upcase).join
?> end
=> :yeller
>> yeller(['o', 'l', 'd'])
=> "OLD"

>> def random_subdomain
?>   ('a'..'z').to_a.shuffle[0..7].join
?> end
=> :random_subdomain
>> random_subdomain
=> "rvuxqobh"

>> def string_shuffle(s)
?>   s.split('').shuffle.join
?> end
=> :string_shuffle
>> string_shuffle("foobar")
=> "frooab"

4.3.3

演習
>> hash = {'one' => 'uno', 'two' => 'dos', 'three' =>  'tres'}
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}
>> hash['one']
=> "uno"
>> hash.each {|key, value| puts "#{key}はスペイン語で#{value}"}
oneはスペイン語でuno
twoはスペイン語でdos
threeはスペイン語でtres
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}

>> person1 = {first: '太郎', last: '山田'}
=> {:first=>"太郎", :last=>"山田"}
>> person2 = {first: '二郎', last: '山田'}
=> {:first=>"二郎", :last=>"山田"}
>> person3 = {first: '三郎', last: '山田'}
=> {:first=>"三郎", :last=>"山田"}
>> params = {}
=> {}
>> params[:father] = person1
=> {:first=>"太郎", :last=>"山田"}
>> params[:mother] = person1
=> {:first=>"太郎", :last=>"山田"}
>> params[:mother] = person2
=> {:first=>"二郎", :last=>"山田"}
>> params[:child] = person3
=> {:first=>"三郎", :last=>"山田"}
>> params[:father][:first] == person1[:first]
=> true
>> params[:mother][:first] == person2[:first]
=> true
>> params[:child][:first] == person3[:first]
=> true

>> password_digest = ('a'..'z').to_a.shuffle[0..15].join
=> "cfhogwqsbyuaitrk"
>> user = {name: '沖縄太郎', email: 'test@example.com', password_digest: password_digest}
=> {:name=>"沖縄太郎", :email=>"test@example.com", :password_digest=>"cfhogwqsbyuaitrk"}

# マージの引数で上書きされると予想
>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 })
=> {"a"=>100, "b"=>300}

4.4.1

  • コンストラクタ: RubyのリファレンスでInitializeについて見てみると以下のような記述がありました.

    このメソッドは Class#new から新しく生成されたオブジェクトの初期化のために呼び出されます。他の言語のコンストラクタに相当します。デフォルトの動作ではなにもしません。

つまり、RubyでいうInitializeが他の言語のコンストラクタにあたる、ということでしょうか、、 🤔

s = "foobar""" は暗黙的に String.new をおこなっているという認識です.

演習
>> (1..10)
=> 1..10

>> Range.new(1, 10)
=> 1..10

>> (1..10) == Range.new(1, 10)
=> true

4.4.2

演習
>> Hash.superclass
=> Object
>> Hash.superclass.superclass
=> BasicObject
>> Symbol.superclass
=> Object
>> Symbol.superclass.superclass
=> BasicObject

>> class Word < String
?>   def palindrome?
?>     self == reverse
?>   end
?> end
=> :palindrome?
>> s = Word.new('level')
=> "level"
>> s.palindrome?
=> true

4.4.3

演習
>> s = Word.new("racecar")
=> "racecar"
>> s.palindrome?
=> true
>> s = Word.new("onomatopeia")
=> "onomatopeia"
>> s.palindrome?
=> false
>> s = Word.new("Malayalam")
=> "Malayalam"
>> s.downcase.palindrome?
=> true

>> class String
?>   def shuffle
?>     self.split('').shuffle.join
?>   end
?> end
=> :shuffle
>> "foobar".shuffle
=> "bfraoo"

>> class String
?>   def shuffle
?>     split('').shuffle.join
?>   end
?> end
=> :shuffle
>> "foobar".shuffle
=> "orofba"

4.4.4

演習

toyアプリケーションを作ってないので本演習は飛ばします

4.4.5

attr_accessor :nameとすることでname の呼び出し、更新ができるようになります.

演習
class User
  attr_accessor :first_name, :last_name, :email

  def initialize(attributes = {})
    @first_name = attributes[:first_name]
    @last_name = attributes[:last_name]
    @email = attributes[:email]
  end

  def formatted_email
    "#{full_name} <#{@email}>"
  end

  def full_name
    "#{@first_name} #{@last_name}"
  end

  def alphabetical_name
    "#{@last_name}, #{@first_name}"
  end
end
>> require './example_user'
=> true
>> user = User.new(first_name: 'Michael', last_name: 'Hartl', email: 'test@example.com')
=> #<User:0x000000015775c760 @first_name="Michael", @last_name="Hartl", @email="test@example.com">
>> user.formatted_email
=> "Michael Hartl <test@example.com>"

>> user.alphabetical_name
=> "Hartl, Michael"

>> user.full_name.split == user.alphabetical_name.split(', ').reverse
=> true

まとめ

オブジェクト指向の考え方、Classの使い方がしっかり落とし込めました💪

解答に間違いがありましたらお手数ですがコメントいただけると幸いです 🙇

GitHub Actions でテストとデプロイを自動化してみた

こんにちは. おさとです💪

当エントリーではRailsプロダクトをGitHub Actionsによるテスト、デプロイの自動化をおこなっていきます.

やりたいこと

  • push時にテストを走らせる
  • mainにマージする時にテストを走らせる
    • GREENならデプロイ

背景

Railsで開発を進める中で、テストとデプロイの自動化によって開発効率が上がると思い、実装に至ります.

実装

GitHub リポジトリの Actions から Ruby on Rails を選択し、workflowを作成します.

commitしてみると、テストが走りましたが、エラーが出てしまいました.

ActionView::Template::Error:         ActionView::Template::Error: Webpacker can't find application.js

Webpackerがないとのことなので、作成したymlにwebpackerのインストールと、アセットのプリコンパイルを実行するようにします. また、今回はlinterは走らせないので、jobsからlintを削除します.

デプロイのコードはこちらを参考にします.

以下のようにymlファイルを編集します.

name: "Ruby on Rails CI"
on:
  push:
    branches:
      - main
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:11-alpine
        ports:
          - "5432:5432"
        env:
          POSTGRES_DB: rails_test
          POSTGRES_USER: rails
          POSTGRES_PASSWORD: password
    env:
      RAILS_ENV: test
      DATABASE_URL: "postgres://rails:password@localhost:5432/rails_test"
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Install Ruby and gems
        uses: ruby/setup-ruby@8f312efe1262fb463d906e9bf040319394c18d3e # v1.92
        with:
          bundler-cache: true
      - name: Install webpacker
        run: bin/rails webpacker:install && bin/rails assets:precompile
      - name: Set up database schema
        run: bin/rails db:schema:load
      - name: Run tests
        run: bin/rake

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3.12.12 # This is the action
        with:
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
          heroku_email: ${{secrets.HEROKU_EMAIL}}

mainにpushされたらテストを実行してデプロイ、ですね.

pushする前にリポジトリのSettingsからsecretsを設定します.

設定し終えpushしてみると、無事デプロイできました 🎉

jobs.deployでneedsを設定したことでテストが正常に終わってからdeployされるようになりました.

まとめ

mainブランチにpushされるタイミングでテストを走らせ、デプロイまで自動化できました.

開発効率がぐんと向上しそうです 💪