Roll With IT

tamakiのIT日記

チェリー本輪読会 第4週目まとめ

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

チェリー本輪読会の第4週目のエントリーになります。

輪読会の概要については第1週目にまとめています。

🍒 輪読会 第4週目まとめ

第4章4.6.4〜第4章4.7.14まで

期間:2021年06月14日〜2021年06月18日

リファクタリングすべきポイントは「繰り返し処理」部分

先週取り掛かったコードをリファクタリングしました。

リファクタリングとは?

外から見た振る舞いは保ったまま、理解や修正が簡単になるように内部のコードを改善することです。 出典:チェリー本

DRY(Don't repeat yourself)の原則に従い行います。

DRY原則 | プログラマが知るべき97のこと)

簡単な例。

以下は、引数ひとつに対し一行で記述した例です。

irb(main):210:1* def add_second_name(vo, gu, ba, dr)
irb(main):211:1*   puts vo.to_s + 'Ramone'
irb(main):212:1*   puts gu.to_s + 'Ramone'
irb(main):213:1*   puts ba.to_s + 'Ramone'
irb(main):214:1*   puts dr.to_s + 'Ramone'
irb(main):215:0> end
=> :add_second_name
irb(main):216:0>
irb(main):217:0> add_second_name('Joey', 'Johnny', 'DeeDee', 'Marky')
JoeyRamone
JohnnyRamone
DeeDeeRamone
MarkyRamone
=> nil

以下は、each文を使い配列で処理しています。

繰り返し書いている箇所をまとめることができ、DRYに従ったリファクタリングと言えます。

irb(main):218:1* def add_second_name(vo, gu, ba, dr)
irb(main):219:2*   [vo, gu, ba, dr].each do |n|
irb(main):220:2*     puts n.to_s + 'Ramone'
irb(main):221:1*   end
irb(main):222:0> end
=> :add_second_name
irb(main):223:0> add_second_name('Joey', 'Johnny', 'DeeDee', 'Marky')
JoeyRamone
JohnnyRamone
DeeDeeRamone
MarkyRamone
=> ["Joey", "Johnny", "DeeDee", "Marky"]

添字を使った配列の取得方法

配列[位置、取得する長さ]

下記は、2つ目の要素から3つ分を取り出すコードです。

irb(main):001:0> ramones = ["Joey", "Johnny", "DeeDee", "Marky"]
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):002:0> ramones[1,3]
=> ["Johnny", "DeeDee", "Marky"]

他にもさまざま取り出し方法があります。

正の値を指定することで配列を取り出すことは問題なく理解できていましたが、負の値を指定した際に取り出す方法の理解が曖昧でした。

改めて見直してみます。

irb(main):234:0> ramones = ["Joey", "Johnny", "DeeDee", "Marky"]
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):235:0> ramones[-1]
=> "Marky"
irb(main):236:0> ramones[-3]
=> "Johnny"

2番目の要素を指定してみます。正の値で指定した動きと同じです。

しかし、取り出しをスタートする地点から右に数えて取得しています。

irb(main):013:0> ramones = ["Joey", "Johnny", "DeeDee", "Marky"]
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):014:0> ramones[-3, 2]
=> ["Johnny", "DeeDee"]

他に、lastメソッド配列の最後の要素を取得できます。

引数を指定してあげることで、

最後の要素から順番に指定した分要素を取り出せます。

ちなみに、lastとまったく動きが逆になるfirstメソッドもあります。

irb(main):019:0> ramones = ["Joey", "Johnny", "DeeDee", "Marky"]
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):020:0> ramones.last
=> "Marky"
irb(main):021:0> ramones.last(2)
=> ["DeeDee", "Marky"]

concatメソッド

Array#concat (Ruby 3.0.0 リファレンスマニュアル)

2つの配列を連結したいときに使うメソッドです。

Excelの、CONCATENATE 関数 を思い出しました。 当時、Excel表計算していた際は大変お世話になった関数です。

CONCATENATE 関数 - Office サポート

irb(main):022:0> ramones =  ["Joey", "Johnny", "DeeDee", "Marky"]
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):023:0> kana = ["ジョーイ", "ジョニー", "ディーディー", "マーキー"]
=> ["ジョーイ", "ジョニー", "ディーディー", "マーキー"]
irb(main):024:0> ramones.concat(kana)
=> ["Joey", "Johnny", "DeeDee", "Marky", "ジョーイ", "ジョニー", "ディーディー", "マーキー"]

ramones変数は変更されます。(破壊的)

kana変数は変更されません。

irb(main):025:0> ramones
=> ["Joey", "Johnny", "DeeDee", "Marky", "ジョーイ", "ジョニー", "ディーディー", "マーキー"]
irb(main):026:0> kana
=> ["ジョーイ", "ジョニー", "ディーディー", "マーキー"]

ちなみに、 + を使っても連結できますが、こちらは非破壊的なので変数は変更されません。

irb(main):027:0> ramones =  ["Joey", "Johnny", "DeeDee", "Marky"]
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):028:0> kana = ["ジョーイ", "ジョニー", "ディーディー", "マーキー"]
=> ["ジョーイ", "ジョニー", "ディーディー", "マーキー"]
irb(main):029:0> ramones + kana
=> ["Joey", "Johnny", "DeeDee", "Marky", "ジョーイ", "ジョニー", "ディーディー", "マーキー"]
irb(main):030:0> ramones
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):031:0> kana
=> ["ジョーイ", "ジョニー", "ディーディー", "マーキー"]

チェリー本では、concatメソッドは破壊的なメソッドなため、思わぬ不具合を与えてしまい兼ねないため、+演算子を使うことを推奨しています。

キャット?カット?

輪読会恒例になりつつある?笑 「どう発音するか?」問題で盛り上がりました。 イギリス英語、アメリカ英語で違いがあるようです。

CONCATENATE | Cambridge Dictionary による英語での発音

確か、canもイギリスだと「カン」と言っているのを思い出しました。

でもビートルズはキャンって歌っている…

せっかくなので調べてみました。なるほど、納得!

ビートルズはアメリカ英語で歌っていた(230) - ★ビートルズを誰にでも分かりやすく解説するブログ★

# frozen_string_literal: true

普段、Rubyのコードを書く際におまじないのように書いている下記のコード。

# frozen_string_literal: true

こちらも疑問に思ったため調べてみました。

Object#freeze (Ruby 3.0.0 リファレンスマニュアル)

凍結されるのはオブジェクトであり、変数ではありません。代入などで変数の指すオブジェクトが変化してしまうことは freeze では防げません。 freeze が防ぐのは、 `破壊的な操作' と呼ばれるもの一般です

frozen_string_literal はマジックコメントといい、# frozen_string_literal: true と書くことで、上記のるりまで説明があるように文字列が最初からfreezeされるようです。ただし、文字列リテラルに限られます。

文字列リテラルとは?

リテラル (Ruby 3.0.0 リファレンスマニュアル)

破壊的メソッドと非破壊的メソッドの見分け方

まずはるりまを読む。これが鉄則です。

例えば、先程のconcatメソッド。るりまにはこう書いてあります。

  • 配列 other を自身の末尾に破壊的に連結します。

Array#concat (Ruby 3.0.0 リファレンスマニュアル)

! で終わるメソッド

エクスクラメーションマーク(感嘆符)が付くメソッドは、全て破壊的なメソッドであると思っていましたが、これは勘違いでした。!で終わるメソッドは、慣習的に「使用する際には注意が必要ですよ」という意味を持っているだけで、必ずしも破壊的メソッドになるわけではありません。ここは注意が必要です。

cocatメソッドのように、! が付かなくても破壊的なメソッドは存在します。

他には、deleteメソッド、clearメソッドも、!が付かない破壊的メソッドです。

以下は、Matzさんのツイートになります。

なるほど。納得できました。

配列の和集合、差集合、積集合

irb(main):001:0> ramones_a = ["Joey", "Johnny"]
=> ["Joey", "Johnny"]
irb(main):002:0> ramones_b = ["DeeDee", "Marky"]
=> ["DeeDee", "Marky"]
irb(main):003:0> ramones_a | ramones_b
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):002:0> ramones_a = ["Joey", "Johnny", "DeeDee"]
=> ["Joey", "Johnny", "DeeDee"]
irb(main):003:0> ramones_b = ["DeeDee", "Marky"]
=> ["DeeDee", "Marky"]
irb(main):004:0> ramones_a - ramones_b
=> ["Joey", "Johnny"]
irb(main):001:0> ramones_a = ["Joey", "Johnny", "DeeDee"]
=> ["Joey", "Johnny", "DeeDee"]
irb(main):002:0> ramones_b = ["DeeDee", "Marky"]
=> ["DeeDee", "Marky"]
irb(main):003:0> ramones_a & ramones_b
=> ["DeeDee"]

上記と同じことがしたい場合、RubyにはSetクラスが用意されています。一般的にはSetクラスを使って集合演算を行います。

class Set (Ruby 3.0.0 リファレンスマニュアル)

splat展開

*を使って1つの引数ではなく、「複数の引数」として配列を展開することができます。

*splat展開なしの場合。

二次元配列になります。入れ子のデータ構造のことです。

irb(main):001:0> ramones = ["Joey", "Johnny", "DeeDee", "Marky"]
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):002:0> name = []
=> []
irb(main):003:0> name.push(ramones)
=> [["Joey", "Johnny", "DeeDee", "Marky"]]

*splat展開ありの場合。

一次元配列で格納できます。

irb(main):001:0> ramones = ["Joey", "Johnny", "DeeDee", "Marky"]
=> ["Joey", "Johnny", "DeeDee", "Marky"]
irb(main):002:0> name = []
=> []
irb(main):003:0> name.push(*ramones)
=> ["Joey", "Johnny", "DeeDee", "Marky"]

メソッドの可変長引数(variable arguments)

コンピュータプログラムの一部として定義された関数などに渡される引数のうち、あらかじめ数が固定されておらず、任意の数(あるいは事前に定められた範囲の数)を取ることができるものを可変長引数(可変引数、可変個引数)といいいます。チェリー本では、「個数に制限のない引数」と説明されています。

  • splat展開がない場合
    • joinメソッドを使い、引数に渡した配列の文字列を間に挟み連結しした文字列を返します。
    • 引数が配列じゃない場合は、エラーになります。
irb(main):004:1* def greeting(names)
irb(main):005:1* "#{names.join('')}、ハロー!!"
irb(main):006:0> end
=> :greeting
irb(main):007:0> greeting([ "DeeDee", "Marky"])
=> "DeeDeeとMarky、ハロー!!"
irb(main):008:0> greeting( "DeeDee", "Marky")
(irb):4:in `greeting': wrong number of arguments (given 2, expected 1) (ArgumentError)
  from (irb):8:in `<main>'
  from /Users/shiro/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.6/exe/irb:11:in `<top (required)>'
    from /Users/shiro/.rbenv/versions/3.0.0/bin/irb:23:in `load'
  from /Users/shiro/.rbenv/versions/3.0.0/bin/irb:23:in `<main>'
  • splat展開をした場合
    • *name とすることで、greeting("DeeDee", "Marky") としている引数が配列として展開されて、joinメソッドに渡されています。
irb(main):009:1* def greeting(*names)
irb(main):010:1* "#{names.join('')}、ハロー!!"
irb(main):011:0> end
irb(main):012:0*
=> :greeting
irb(main):013:0> greeting("DeeDee", "Marky")
=> "DeeDeeとMarky、ハロー!!"

splatは、アスタリスクという意味でもあるようです。

splatの意味・使い方・読み方|英辞郎 on the WEB

個人的にはこのゲームのイメージから英語の意味を紐付けたりしました。

スプラトゥーン2 | Nintendo Switch | 任天堂

%記法

配列を[ ] を使わずに作成できます。%w %W を使います。

  • %w の場合
    • スペース区切りで配列に格納されます。
irb(main):023:0> ramones = %w(We are Ramones! Hey\nho\nlet's\ngo!!)
=> ["We", "are", "Ramones!", "Hey\\nho\\nlet's\\ngo!!"]
irb(main):024:0> puts ramones
We
are
Ramones!
Hey\nho\nlet's\ngo!!
  • %W 大文字の場合。
    • 改行文字\n タブ文字\tなどは、irb上の返り値では、そのまま出力されるが、変数などに代入しputsなどで出力すると実際に改行、タブで区切られ出力されます。
irb(main):021:0> ramones = %W(We are Ramones! Hey\nho\nlet's\ngo!!)
=> ["We", "are", "Ramones!", "Hey\nho\nlet's\ngo!!"]
irb(main):022:0> puts ramones
We
are
Ramones!
Hey
ho
let's
go!!
=> nil

\n や\t は文字コードではなくバックスラッシュ記法です。

リテラル (Ruby 3.0.0 リファレンスマニュアル)

charsメソッド、splitメソッド

charsメソッドは、文字列を一文字一文字分解して配列にするメソッドです。

irb(main):025:0> 'RAMONES'.chars
=> ["R", "A", "M", "O", "N", "E", "S"]

splitメソッドは、引数で渡した区切り文字(今回は,で区切っている)で文字列を配列に分解するメソッドです。

irb(main):027:0> 'Joey, Johnny, DeeDee, Marky'.split(',')
=> ["Joey", " Johnny", " DeeDee", " Marky"]

chars の読み方

今週2回目の「どう発音するか?」問題。笑

今回はcharsがターゲットになりました。 元々の単語は、character になります。

チャー派とキャラ派。どちらもいい勝負でしたが、結果は....? ちなみに私はチャー派で、ギタリストのcharに馴染みがあるため、自然とチャーと言ってました。

結論ですが、どっちでもいいらしいです。笑

ですが、こういった小ネタ的な話題で盛り上がれるのも輪読会のひとつの楽しみでもあります。

ちなみに、Paizaが取ったアンケートではチャー派の勝利です!!

Array.new

配列は[ ] で作成することが多いですが、Array.newを使って作成する方法もあります。

ちなみに、[ ] はメソッドでもあります。

Array# (Ruby 3.0.0 リファレンスマニュアル)

String# (Ruby 3.0.0 リファレンスマニュアル)

下記、同じ意味のコードです。

irb(main):029:0> ramones = []
=> []
irb(main):030:0> ramones = Array.new
=> []

第1引数を渡すと、その個数分の要素が追加されます。初期値はnilです。

第2引数を渡すと、要素が置き換わります。

irb(main):032:0> ramones = Array.new(4)
=> [nil, nil, nil, nil]
irb(main):033:0> ramones = Array.new(4, 'Hey')
=> ["Hey", "Hey", "Hey", "Hey"]

ミュータブル、イミュータブル

普段馴染みのない単語なので、意味を忘れがちなミュータブル、イミュータブル。

ミュータブルmutable(変更可能な)と、イミュータブルimmutable(変更できない)が「どっちがどっち?」と、ごっちゃになるが、ミュータント・タートルズで覚えることにしました。突然変異。

あと、illiegal < = > ligalの対義語と同じ法則なので、覚えておきたいと思います。

Rubyにはイミュータブルなクラスや値がいくつかあります。基本のデータ型は下記の4つです。

  • 数値(Integerクラス、Floatクラス)
  • シンボル(Symbolクラス)
  • true / false
  • nil

言い替えると、Integerクラスやシンボルなどは、そもそも破壊的なメソッドは存在しないということです。

参考書籍

🍒 まとめ

今週は、配列や繰り返し処理ついて深堀りしていくことができました。

破壊的メソッドについて、深く考えて使うことがなかったのですが、今週の学びを通してプログラムに不具合を与えかねない危険なメソッドであることが理解できました。ミュータブル、イミュータブルも今回しっかり学べ理解することができました。

「同じ値で同一のオブジェクト」なのか?「同じ値で異なるオブジェクト」なのか?意識してプログラムを書く必要があります。今後、不具合が起きたときに、今回学んだことを活かしデバックしていきたいと思います。

では、また来週!(次回、第5週目)