SQLアンチパターンを読んだので、少しずつまとめてみる。
こんばんは!
最近、我が家の豆苗のテンションが低めです
▼ 『寒いんじゃ…』
私の食料用に育成しているこの人ですが、日陰+常に気温は10℃前後という過酷な環境でも育ってくれています。
もともと温室育ちなはずなのに… 私もどんな辛いプロジェクトに放り込まれても、根を張って耐え抜く強さを持ち続けていたいです笑
さて!寒さにめげずにアンチパターンの続きをまとめていきます!
前回はSQLアンチパターンを読んで、論理設計編をまとめるという内容で展開しました。
残りが消化できていないので、こちらも見ていきましょう🐕
1. ジェイウォーク(信号無視)
2. ナイーブツリー(素朴な木)
3. IDリクワイアド
IDリクワイアド
前置き長いので、内容は本旨からどうぞ。
私は最近までRuby on Railsというフレームワークを使う現場で仕事をしていました。
RailsはORマッパーを採用していて、テーブルのデータをひとつのモデルとみなし、必要なデータの設計を高速に実施できます。
SQLで抽出したデータはActiveRecord::Relationというクラスでサポートされており、データの整形にキャスト(型変換)を意識せず素早く使うことができます。
経験や学びが浅く、エンジニアとして自信を無くしかけていた私にとって、このフレームワークの存在は本当にありがたかったです。昔では怖くて触れることができなかったロジックで色んな機能を実装することが出来、人の役に立つことが出来たので…。
その前はお客さん先でJavaとOracleを使ってDB周りのことをしていました。
A5M2というツールで眺めた証券系システムを成すテーブル群はゆうに300を超えていて、当時1:nもよくわからなかった私は衝撃を受けました。
研修で作ったアプリについても、「テーブルなんて実行する回数少なければひとつだけあればいいじゃない」みたいなぬるい感想を持った素人にとっては、あの光景はなかなかに破壊力があるものです。マジで過呼吸になるかと思った。
あ、A5M2はSQLパーな私にとって強力な調査の相棒となったので、WindowsOS使っている人に広めたい気持ちがあります。
マーケティングの人がSQLと仲良くなるきっかけとして。開発者と仲良くなるきっかけとして。
こちらもまた使ってみてまとめ記事あげたいなあ。
本旨
こちらがIDリクワイアドの内容の本旨になります。
私も研修の際に覚えたことですが、
各テーブルにおわすidはprimary key(主キー)として、そのデータが一意であることを示し、かつ他のテーブルのforeign key(外部キー)から参照され、テーブルの関連性を持つことが出来ます。
しかし、一意である=ダブらないといっても、本当にダブらないのかはちょっと疑問なところではあります。
下記は記事の詳細データを扱うテーブルです。
id | reference_url | article_id | tag_id |
---|---|---|---|
1 | 423429 | 2 | 3 |
2 | 423429 | 2 | 3 |
3 | 524520 | 3 | 6 |
…
一度article_id=2の記事を消したものの、関連テーブルのデータは消えていませんでした。
このテーブルから記事に紐つけられている引用ページの数を知りたくて、以下のSQLを発行します。
select * article_image_mapping_id, count(*) as uniq_image_id from article_detail where tag_id = 3
すると、idが1と2のreference_urlが取れました。
idは一意であるものの、挿入されるデータは別物とは限りません。
この場合はid以外のカラムにユニーク制約(一意制約)をかけるべきだったのですが、ここで各テーブルのidは必要なのか?という疑問が浮かび上がります。
id列があって困るパターン
①単純に紛らわしい
articleテーブルに同名のid列があったらどうでしょう。
同じidのユニークな列が出来た際に、上のような出来事は避けられるものの、idでもarticle_idでもレコードは取れてしまいます。
②重複行を許可してしまう
上の例ではレコードを挿入する際に、テーブルのidはserial primary keyを許可されているものの、
他の2カラムについては重複を許可してしまっています。
新たに同じtag_id, article_idでレコードが更新されてしまうのです。
③キーの意味がプログラムにとってわかりづらい
select b.id, a.id
from tags b
inner join articles a on b.assign_to = a.id
where b.name = 'coffee';
上のクエリは記事とタグのテーブルを結合し、コーヒーのタグがついた記事を抽出するものです。
データ名で内容を識別するとき、article_id, tag_idのように識別されない為に、列で内容が上書きされてしまうケースもあるようです。
Railsを使用していれば起こりにくい問題ですが、一から設計した時には起こりうる問題です。
④using句の使いどころが減ってしまう
もしもidカラムにテーブル名を表す情報が付加されていた場合、以下のクエリを簡単に書き直すことが出来ます。
select * from tags as a
inner join articles as b
on a.id = b.tag_id;
select * from tags as a
inner join articles as using (tag_id);
using句便利!idカラムを実装するよりも、tablename_idの命名規則に沿うと楽そうですね♪
参考
idリクワイアドを適用してもいい場合
行で重複しても良い
クエリで個別の行を参照しない
外部キー参照をサポートしない
擬似キーをサポートしているデータベース
DB種類 | 機能名 | 参考url |
---|---|---|
mysql | AUTO_INCREMENT | MySQL :: MySQL 5.6 リファレンスマニュアル :: 3.6.9 AUTO_INCREMENT の使用 |
mysql, postgres | SERIAL | https://www.postgresql.jp/document/7.3/user/datatype.html |
sqlite | ROWID | Rowid Tables |
Oracle | SEQUENCE | CREATE SEQUENCE |
mysql_insert_id()は最後に生成されたAUTO_INCREMENTの値を取得してくれるそう。
SQLにも関数がある、これ応用できたら便利やろな…。
キーレスエントリ(外部キー嫌い)
冒頭のケースで語られるのは、外部キー参照のないDBで設計をした為に、データ重複や孤児(親データが削除された後も置き去りとなった関連データ)の扱いについてです。
不整合なデータを定期的に検出し、結果をメールで通知させる設計を追加したものの、テーブルの数とデータそのものが増えてきてクエリの実行に時間がかかるようになってしまったのでした。
本来であれば、ユーザーが無効なデータを入力した時にエラーを返せる設計にすればよかったとありますが、
では無効なデータとはどんなものを指すのでしょうか?
外部キーを参照したくないパターン
- テーブルの設計がしょっちゅう更新される
- データの更新が外部参照と衝突する
- 本テーブルのデータを更新する為に外部テーブルも更新しなければならない-
- クエリを調べるのに時間を割きたくない(笑)
- 外部キー参照を実装していないDBを使用している(本文ではMyIsam)
完璧なクエリを発行してデータを保持していかなければならない
1:nのarticle, tagがあった場合に、tagを更新する際は親であるarticleが存在するかselectで確認する必要がある。
1:nのarticle, commentがあった場合に、articleを削除する際は子のcommentが存在していないか、selectで確認する必要がある。
(さらに、削除作業を行う際には新たなcommentが作成されているかもしれないことを念頭に置くこと。)
孤児データの検索が必要になる
select a.article_id, t.tag_name
from articles left outer join tags t
on a.tag_id = t.tag_id
where t.tag_id is null;
これを毎度発行して、定期的にデータの管理をする事を考えただけでも、気が遠くなりそうですよね…。
削除、更新の際は親のカラムを更新してから子のカラムを更新していかなければならないのです。。
ソリューション
外部キー制約を宣言しよう
複数テーブルの更新をサポートするカスケード更新
create table Bugs (
reported_by BIGINT UNSIGNED NOT NULL,
-- 正の数のみ, NULL許容しないstatus VARCHAR(20) NOT NULL DEFAULT 'NEW',
-- デフォルト値はNEWで20文字までの制約あり, NULL許容しないFOREIGN KEY (reported_by) REFERENCES Accounts(account_id)
ON UPDATE CASCADE
ON DELETE RESTIRCT,
-- カスケード処理を定義する, Bugsテーブルを参照しているaccount_idは削除時にエラーを返すFOREIGN KEY (status) REFERENCES BugStatus(status)
ON UPDATE CASCADE
ON DELETE RESTIRCT
-- statusをNULLにした場合には、set defaultが作用してNEWが設定されるようになる 孤児の心配はない
);
テーブル作成時にカスケード更新を設定すれば、参照している子の行も更新が反映される…おったまげたぜ…
これで削除、更新前にselectを投げる必要がない
複数のテーブルの更新を気にして排他的ロックをかける必要がない
孤児を検索するスクリプトを書く必要がない
👏👏👏
ご参考
寒い日には美味しいラーメンが食べたい!
という事で、ぼっちラーメン散策して参りました👟
地元にあるこのラーメン屋さんですが、皿の上に乗っているものが全て美味しかったです。
特製煮干しラーメンの塩を頼んできました。
スープつやっつやですよね!?一度口をつけたら飲み干すまで止まらないですよ…😈
体が欲しがる栄養スープって感じです!
体に優しいお味で、チャーシューの代わりに半ナマのローストビーフとチキンが乗っていました🐃🐓
優しい脂が、噛み締めるたびに口の中を満たします🤤
味玉はとろりとした半熟、海苔も噛み締めると濃いめの味が広がって、それでもスープを邪魔しない程度の主張。
麺は自家製みたいです。もくもくと噛み締めて、私は好きです。
特製とあってお値段少し張りますが、通常メニューのラーメンは880~からいただけるので、今度はこっちも食べにいきます!
家系よりもあっさりした方が好きな方は是非!
関連ランキング:ラーメン | 高円寺駅、東高円寺駅、新高円寺駅
http://tabelog.com/btb/0bbbdcd7680f03ca3988b4c586a62e53ea73e731111cc24a272de3e1bad7eba3/
DBの設計してきました
バックエンド(サーバサイド)エンジニアです!と名乗りだして半年が経ちました。
成長が亀の歩みなのですが、なかなか体験できなかったDB設計について実際にやってみよう!という
イベントがあったので、参加をしてきました🐶
参加背景
Railsでロジックを書き始めた頃、よく思うことがありました。
「このロジックはモデルに書くべきなの?
コントローラに書くべき?」
「スコープ(モデルからデータを引っこ抜くORマッパーが書いてあるメソッド)が欲しかったら、モデルに書くのかな
…あれ?似た処理がコントローラにある気がする(幻覚)」
といった具合に。
私は歴が浅いこともあり、言われるがままの要件を満たす実装をしがちでした。
cssを書いている時も感じていましたが、
「これ、同じ部品あるんじゃ?」「このバリデーション、viewにいちいち書くのか…」
と感じつつも、要件が投げられるからという理由でバックエンドのリファクタを実施できなかった後悔があります。
メモリの使用率などを計算に入れてコーディングしたためしはないですが、
false, nil以外は真というRubyの性質上、判定文で使用されなかった変数もメモリを取ったまま、処理が実行されるという事に気付きました。
データ抽出もORマッパーに頼りっぱなし、いまだに疎い部分はたくさんありますが、自分の書いたスコープがテーブルの中を何往復しているかという想像も出来ていませんでした。
気がつけばmackaelがアラートを出し、CPU負荷率の調査の為にログを眺めていてもSQLの意味がわからない…
上記に後悔ばかりが連ねてありますが、そもそもどこから手をつけるべきなのかが全く見えていませんでした。
そんなもやもやを抱えている時にSQLアンチパターンと出会い、Rubyの問題にしろ、Railsの問題にしろ、ちょっとずつ知ることから始める事にしました。
いらないものは実装しない
YAGNIの原則というものがあります。
私はプライマリキーはどのテーブルにもある!という謎の宗教観の人間だったのですが、
実はそうでなくて、これはアンチパターンの一種であるIDリクワイアドに見事にハマってしまっている状態だという事に気づかされました。
無意識にガーッと書いたER図の中にid, user_id...などが軒を連ね…
参照される必要のないカラムは'もしかしたら'プロダクトが成長した暁に、DBの容量を逼迫するかもしれません。
ついつい入れてしまった『参加者をカウントする数字を格納するカラム』もきっとアプリ側で解決する問題でしょう…
他の参加者の方の設計を見て考えさせられたのは、上手にテーブルを分割して、要件を実現するだけでなく、後々の運用までをうっすらとでも考えている見通しの意図でした。
DB設計はシンプルさが重要で、頼まれた要件を渡すように作りこむのかがDB・アプリ技術者の腕にかかっていて、それはシームレスに行われるものだと実感しました…。
それからどうする?
最近はサーバレスというものが流行っていますが、私はそれにしばらく着手せずに、上にあげた後悔と向き合うだろうと思います…
後本当はプリンシパルオブプログラミングもちゃんと読み込みたいです。
速読術が欲しいなあ…
SQLアンチパターンを読んだので、論理設計編を個人的なアンチパターンと共にまとめてみる。
論理設計の章ではデータの構成、関連付けなどをまとめています。
具体的にはDBのテーブル、列、関連性の設計があります。
物理設計の章は格納データタイプの決定後、テーブルやインデックスの定義、データ型の決定となります。具体的なDBの中身の決定ですね。
今回は論理設計の章についてまとめていきます。
個人的な所感が多く含まれますので、リアクションいただけると嬉しいです。
1. ジェイウォーク(信号無視)
2. ナイーブツリー(素朴な木)
3. IDリクワイアド
4. キーレスエントリ
5. EAV(エンティティアトリビュートバリュー)
6. ポリモーフィック関連
1. ジェイウォーク(信号無視)
これ、まさしくこのアンチパターンを頭の中で考えたことがありました。
(適当な絵面が浮かばなかった…)
文字面で説明しておくと、Product(製品)とUser(ユーザー)が1: nの関係です。製品は複数のユーザーが使用しているので、連絡先を参照できるようにしたいという要件が投げられたとします。
今回のアンチパターンはカンマ区切りでaccount_idを列挙するカラムをProduct側に定義するというものです。
この場合だとカラムを1個定義するだけで終了なのですが、
如何せん運用中にどんなSQLを発行すればいいか想像つかないですよね…
後該当データがダブりそう。
事例ではユーザーから製品を参照したいときに正規表現が必要だと言っています。
account_id = 3のユーザーのidが登録されている製品と、その製品詳細が欲しい… となるとテーブルを結合してViewを作りたい気持ちなのですが、
ユーザーidで製品抜いて!該当の製品idでデータ抜いて!その作業だけでもややこしいSQLが発行されるのが想像に容易くないですよね…?(さらに最近アクティブな製品だけ抜きたい…Productのupdated_atでソートをかけたいなど、要件追加されたらちょっと面倒臭いなあ…)
それが100行近くに肥えた暗黒クエリとして勉強会のスライドショーに晒されるのも遠くない未来でしょう。
* ソリューション
中間テーブルを作って相互参照が容易なものにする。
外部キーをカラムに持つテーブルを作るんですね。
これだったらNULLの心配もないですし、更新・削除がお手軽になりそうです。
中間テーブルはRailsを現場で使い始めてから初めて知りました。
何でもかんでも適用していいという訳ではないと思いますが、命名が重要ですよね。
連絡先だけじゃなくて、関連性だけに注視するならProductUserMappingって名前でもいいのかなあと思います。
2. ナイーブツリー(素朴な木)
これもJavaのアプリケーション作成時に苦しみました。
記事に結びつくコメントのテーブル設計ですが、投稿された後にどんな運用が考えられるかというパターンを出しきれなかったのです。
id = 1のコメントに対する返信のコメントを書いて、返信文だけ消したいのに
親記事まで消してしまったり…
発行するクエリが悪い場合もあるんですけどね…💧
今回のお題では親記事に連なるコメントを取得したい場合。
親のidが動的に変わるので、親記事内でparentとして扱われているコメントを取得しなければいけないので、左外部結合をひたすら行わなければいけないようです。
<<アンチパターン>>
LEFT OUTER JOIN …左軸に検索元のテーブル1の外部キーを提示し、抜きたい関連テーブルを右側に置くことでNULLを混入せずにデータが取れる。
* ソリューション1 - 経路列挙
該当データまでのパスをcomment_idで表現し、格納します。
上の図でいう『いってみたい』のコメントを抽出したい場合に、親記事のcomment_id = 1, 該当のcomment_id = 2となる為、1/2/というパスをしまっておきます。
隣にもう一列できた場合も1/5/のように表現できるので、元記事に連なるデータは、ワイルドカードを使って1/%のように取得できます。
* ソリューション2 - 入れ子集合
親記事を起点にして、今回は便宜上その距離をrightとleftで表します。
こんな深さの記事があったとして
親記事は1番上の高さにいるのでleftには1
データの起点から数えて12番目の距離にいるので、rightは12を代入します。
親記事から1階層下であれば左側からleftの値が1ずつ増えるのです。
この起点となるデータ(親記事)からのパスを辿ることで、目的のデータが抜けるという手続きを想定しています。
画像の『こんな姉妹店が…』以下のデータを抜きたい場合は、該当階層のleftの最小値とrightの最大値を検索して抜く事ができます。
元記事に連なるデータはleftの最小値が2、rightの最大値は11までのデータを指定する事で取得が可能です。
* ソリューション3 - 閉包テーブル
各データまでのパスを格納するテーブルを作ります。
親記事は子孫のデータ分、自身を含めたパスデータを持ちます。
各子孫も、自分よりも下にデータを持っている場合にそのパスを格納します。
パスは記事の持っているcomment_idで表現します。
コメント元記事に連なるデータは、記事とパスのテーブルを結合し、先祖と子孫の最大距離を指定してクエリで抜く事ができます。
少し長くなったので、3~6は次記事以降にまとめます🐕
Rubyのクラス、特異クラスの話。
本日も寒いですね。
突然甘くてほかほかなたい焼きが恋しくなって、四ツ谷のわかばさんに並んでしまいました。
甘さ控えめ、あずき本来の味と塩気を生かした粒あん、尾までぎっしりあんこ + 注文を受けてから一丁焼きで生誕するたい焼きたち。
Ruby Goldの対策をしながら並んでいたら、なんとかあり着くまでに並び続ける事が出来ました。笑(平日の14時ごろ並んで、20分強でした。参考までに。)
個人的にはくりこ庵のカスタードも大好きです🐟
今日はクラスとモジュールと特異クラスの話をします。
だいたいオブジェクト指向の話を知っていて、Javaなんかを触っている人は聞くまでもない話かもしれません。ふんわりオブジェクト指向についてご存知の方が今回の記事の読者の対象です。
Rubyのクラスは定数であり、再代入が可能です。
従って、クラス自体の定義を再オープンしてメソッドの追加をする事が可能です。
他の言語とは違って単一継承する仕様ですが、moduleという部品を輸入したり、クラス内に展開する事でいろんな事ができるようになります。
クラス内にはinitializeというメソッドが存在し、クラスが評価される(初期化される)ときに呼ばれます。私はJavaでいうコンストラクタのように捉えています。
コンストラクタはattr_accessorというメソッドで定義する事ができ、インスタンス変数を使って表現することもできます。
Class Taiyaki
PRICE = 150
def nakami @nakami end def nakami=(str) @nakami = str end
end
このコードはTaiyakiクラスのnakamiを設定しています。
上からnakamiを保存しておくメソッド(ゲッターと呼びます)
nakamiを設定しているメソッド(この方はセッター)です。
また名前空間という特性があり、クラスのネスト状態を定義する事が出来ます。
Class Taiyaki::ShiroiTaiyaki
PRICE = 200
end
こいつはたい焼きの亜種の白いたい焼きです。
ぱっと見Taiyakiを継承しているかのように見えますが、違います。別物です。
こいつは便宜上Taiyaki内でネストして宣言され、定数PRICEが200で初期化されている他人です。親子関係にはありません。
Class Taiyaki
PRICE = 150
def nakami @nakami end def nakami=(str) @nakami = str end
end
# あっ、中身詰めるの忘れてた。
Class Taiyaki
PRICE = 150
def nakami=(str)
@nakami = "tsubuann"
end
end
これをクラスの再オープンと言います。
これからTaiyakiのnakamiはこのプログラムが動作している上では初期値が全てtsubuannです。
何が言いたいかというと、クラスを再オープンして定義を変更した場合には、基本的にはその設定が全体に適用されます。
また、Nikumann < Manjyuのようにスーパークラスを継承している場合は、きちんと同じお名前で開いて中身を詰めてあげないとTypeErrorを起こしてしまうので気をつけましょう。
標準の中身の設定はあんこでいいけれど、クラスを操作せずにカスタード味も作ってみたいなあ…なんて思った人は、下で説明する特異クラスを使ってみてもいいかもしれませんね。
特異クラスのお話
インスタンスについてですが、これはクラスを分身させ、汎化(出生所よりもちょっとキャラを薄くした感じ)したものたちのことです。
Rubyのインスタンスは限られたスコープの中でしか生存できません。インスタンス変数しかり。宣言された箇所から~endまでが生きれる範囲です。儚い。
さて、特異クラスですが、これは指定したインスタンスに対して適用されるクラスです。
クラスメソッド、クラス変数は一切噛みません。
def object_name.method_name
end
これでこっそりと邪道の白いたい焼きカスタード味を作成するんです。 へっへっへ。
Class Taiyaki
PRICE = 150
def nakami=(str)
@nakami = "tsubuann"
end
def self.change_shape_to_white
@nakami = "custard"
p "白い#{self}、#{@nakami}味。"
end
end
taiyaki = Taiyaki.new
taiyaki.change_shape_to_white #=> 白いTaiyaki、custard味。"
無理矢理感はあるんですが、白いたい焼きができましたよっと。
あ、家族にお土産買っていきたいなあ。お母さんはあんこが苦手なので、
家族別に注文を分けておきましょー。
my_taiyaki = Taiyaki.new("zunda")
mother_taiyaki = Taiyaki.new
def mother_taiyaki.change_shape_to_white
@nakami = "custard"
p "白い#{self}、#{@nakami}味。"
end
father_taiyaki = Taiyaki.new
father_taiyaki.change_shape_to_white #=> NoMethodError
おぅ、間違ってお父さんのたい焼きを白いやつにしてしまいました。
この注文はお母さん用なので、お母さん以外を指定すると "白いたい焼き欲しがっとるお母さん存在せんやないけ" という苦情を申し立てられます。
ちょっとプログラムよりな話をすると、一時的にmother_taiyakiというインスタンス自体が特異クラス(親クラス)となっているので、change_shape_to_whiteはレシーバから本来の実行対象であるmother_taiyakiを探しに行くのですね。
しかし、father_taiyakiのsuper classはTaiyakiであるものの、Taiyakiの中にはmother_taiyakiは存在しないんです。なので、NoMethodErrorが返ってきます。
上の例で白いたい焼きの注文が成功しているのは、レシーバがselfだからであり、
クラス内で定義されているselfはクラス自身を指します。
でも…ベースのあんこにカスタードを投入したいなあ…なんていう邪道も邪道なたい焼きも下のように実現させることが可能です。
<<~Taiyakiクラスの中~>>
class << self
def half_anko_with_custard
@nakami << " half_custard"
end
end
end
<<~Taiyakiクラスの外側~>>
my_taiyaki = Taiyaki.new
p my_taiyaki.half_anko_with_custard #=> "tsubuann half_custard"
はい。これであんことカスタードの魔融合を楽しみたい他の人も注文できるようになりました。
これはさっきのように特定のインスタンスではなくて、Taiyaki自体に注文を追加しています。Taiyakiクラス内のselfはTaiyaki自身なので、これからもインスタンスとして新メニューの注文は可能です!
しかし、Taiyakiクラスの本来の中身はankoのままなので、うっかりTaiyaki.half_anko_with_custardなんてクラスからの呼出をすると、職人に怒られてしまいます。
half_anko_with_custardはTaiyaki本来の味を乱してしまわぬよう、特異クラスに追加してあるからです。
このように、クラスの外側でインスタンスに対してメソッドを作ることができてしまうのはちょっと恐ろしいですね。
Taiyaki職人のあずかり知らぬところで、ピザ味のたい焼きや、あまつさえ米の詰まったたい焼きも登場してしまうかもしれない…
それを防ぐためにもたい焼きのカテゴリー毎にレシピブックを作っておいたり(クラス設計)、instance_methodsなどのメソッドたちを活用することは重要ですね。
Taiyakiのゲシュタルト崩壊
たい焼きもいいのですが、あったかくてしょっぱいものも食べたくなってきますね〜。
おでんなんてどうでしょうか?しっかり赤だしが染み込んだ大きな大根…
お酒が進むと尚いいですよね!
渋谷の居心地もコスパも良い居酒屋さんをご紹介しまーす🍢
土日来店時予約は必須です!半個室・カウンターがあって適度に薄暗く、賑やかですよ🐶
私は居酒屋に行くとポテサラをすぐに頼むポテサラ廃人なのですが、
こちらのポテサラはフライドオニオンやサラミが細かく投入されていて、歯ごたえが楽しいやつでした!
刺し盛りが2000円弱で5点くらいいけたはずですが、遅いと売り切れてしまうので行くなら早い方がいいですよー!私はハマチが食べたかった…
RubyのブロックとProc
こんばんは。ろんです。最近は冷え込みますね…
暖房器具はオイルヒーター派です。
今日はRubyの強力なお友達、ブロックについて紹介します。
並列してるProcとはブロックをオブジェクト化したもので、まずはブロックの理解から初めてこうと思います。
Rubyのブロックとは?何に使えるものなのか
私の解釈では、参照する事ができて、メンバの更新が可能なメソッドの事。
Javaでいう参照渡しが可能な関数の事を指すようです。
引用先のページではモジュールパターンのような応用が可能とありますが、私はこれを
observer patternと解釈しました。
かろてんさんのデザインパターンの説明がわかりやすいので、ぜひ読んでみてください。エモいという言葉は好きではないですが、マジでエモかった…(褒め言葉)
モジュールパターンについての説明は結城浩さんのデザパタ本が個人的におすすめです。ちょっとヒく厚さなんですが、1週間に1章とかでもいいので読んでおくと設計系の話を見かけた時に抵抗なく入れるし、何より説明が具体的でわかりやすいですよ。
物に例えるところがイメージしやすくていいですよね…
それでRubyのブロックとはなんなのか
だいぶ話が私の設計思想に傾いてしまいましたが、文脈を戻してブロックの説明です。
そう、ブロックはcontext(文脈)が大事なのですよ!!
def block_sample arg
arg + yield
end
p block_sample 3 { 2 }
block_sampleの返り値は何になると思いますか?
正解は5です。
argについての説明は割愛しますが、yieldはブロックの処理結果を呼び出しているメソッドです。
あっけない感じがしますね?しかし、このブロックにはちょっとしたクセがあります。
- ブロックで宣言した変数は外部から参照することができない( 束縛 )
- 他のメンバの代入に影響を及ぼす
ブロック評価の順番とローカル変数への影響
def return_calculate_add val1, val2
val1 + yield(5 + val2)
end
p return_calculate_add(3, 4){|x + y| x + y }
これで下記のうちどの値が返ってくるでしょう?
1. 9 2. 12 3. 15
正解は2の12です。
まずval1, val2それぞれに代入されて
def return_calculate_add val1, val2
val1 + yield(5 + val2) # val1=3, val2=4
end
次にブロックの評価がされます。
return_calculate_add(3, 4){|x + y| x + y } # x=5, y=4
最後にメソッドの中に戻ってval1と足し算してあげれば完了です。
こちらはどうでしょうか?
def return_calculate_add val1, val2
val1 + yield(5 + val2)
end
x = 10
p return_calculate_add(3, 4){|x + y| x + y }
惑わされないでくださいね。スコープの外に定義されている変数なのでブロックでは参照されません。答えは同じです。
def return_calculate_add val1, val2
val1 + yield(5 + val2)
end
x = 10
p return_calculate_add(x, 4){|x + y| x+= x + y }
答えは19です。
def return_calculate_add(val1, val2)
val1 + yield(val2, 5)
end
z = 10
p return_calculate_add(z, 4){|x, y| z+= x + y }
p z
問題はこれで最後です。*1に答えを記載しましたのでチャレンジしてみてください。
これが正解出来たら今日の晩ご飯にデザートをつけてもいいと思います。
応用できそうなこと
共有スコープを使いたい時に便利そうですよ。
よくインスタンス変数にお世話になっていますが、こんなメソッドの書き方ができそうです。
# ケーキの数を計算してくれるメソッド
def cakes_calc cakes, &const
p plain_text = "カップケーキが#{cakes + const.call}個"
end
sample_constructor = Proc.new {
result, pledged_cakes = 0, [3, 8, 1]
bought_cakes, made_cakes = 0
bought_cakes = 1000 / 120
made_cakes = 1
pledged_cakes.each do |c|
result += c
if c == pledged_cakes.last
result += bought_cakes + made_cakes
end
end
result
}
cakes_calc(12, &sample_constructor) # => "カップケーキが33個"
ブロック内ではスコープ外に参照を置いていませんが、もちろん参照は可能です。
これならメンバが再代入される危機も薄れて一石二鳥!
うーん。命名がビミョー。
本日は作り置きのハンバーグを食べ損ねたので、美味しい肉バルの紹介です。
虎ノ門界隈にしてはランチのコスパが~1500円程度とお手頃です。
赤身は臭みがなくて程よく柔らかく、ジューシーなのでわさび醤油ソースでいただけるランチメニューがおすすめ。
同僚の方が食べていたグラタンにonされているハンバーグも私の拳くらいあるのでした。また行きたい。
引用・参考 :
( ありがとうございます )
答えは29, 19
*1:こちら
Rubyの変数とスコープについて
寒いですね。ろんです。
最近ヒットした自炊メニューはハンバーグです。
今晩はいい加減写経も飽きてきてしまったので、Rubyの言語仕様について触れてみようと思います。今回は変数とスコープについて、Javaをなんとなく触れていた時期と比べてメモっておきます。
Rubyのメンバとは
Rubyの変数にはJavaのようにデータタイプの修飾子はなく、暗黙的に代入時のデータを解釈してくれるようです。
また、Rubyの定数は再代入が可能です。ここもJavaとは違うところですね。
そもそもClassが定数として扱われるという性質があり、
メイン宣言(メソッド、メンバを呼び出すフィールドのようなもの)が関数として扱われるJavaとはこちらも違ったところです。同じMVCで設計されているといっても、呼び出し方も違いますね。
その為か、クラスをimportやinterface, overrideなどで多重継承するというよりも、クラスの再オープンや継承が単一クラスの継承で実行されます。
これでは不便なので、mix-inという機能でいろんな機能追加などが可能になるのですが、今回は割愛します。
Rubyの変数たち
勉強を始めた序盤でかなり惑わされ、曖昧になってしまっている部分ですが、
先ほど述べたようにRubyの変数はメンバ宣言が必要なく、修飾子も定義されていません。
主に下記の5つで成り立っています。
ローカル変数 … 代入された位置からブロックもしくはメソッドの終端まで
$グローバル変数 … どこからでも参照ができる
@@クラス変数 … クラスインスタンスから参照が可能
定数 … クラス・モジュール内(内側での宣言も含む)、宣言元を継承もしくはincludeしているモジュール内、外部からはモジュール演算子と宣言されているクラスを表記することでアクセスが可能
ローカル変数はスクリプト言語を学び始めた私にとっては珍妙で、『プログラムは上から下へ降りる挙動をする』という感覚があった為、同一ファイル内で定義されている変数を呼び出そうとした時にName Errorなんて返ってくるので、勝手に理不尽さを感じていました。
こんなにメンバ宣言してしまって…、GCしなくて大丈夫なのかしら…とJavaのコンパイル待ちをしていた時期と比べて開発速度が段違いだった衝撃を思い出します。
話が脱線しましたが、Rubyでのローカル変数はメソッド内での宣言が必要になります。(attr_accesorなどのコンストラクタ類はのぞいて)
グローバル変数は使った事がないのですが、どこからでも参照・更新が出来てしまうという危うさは変わらないようです。freezeを使って再代入出来なくするという手も使えるようですが、各所で話を聞く限り、特異メソッドで定義してあげる方が精神衛生上良さそうな気がします。
インスタンス変数は属しているフィールドのインスタンス内でのみ生存ができるイメージです。正直、メソッドと同名のviewにこやつらが生存している原理に違和感を持ったままですが…
宣言していないのに参照したらどうなるの?
これはクラス変数・定数、一部ローカル変数について例外が発生します。
条件分岐で代入が実行されなかった場合は宣言のみ適用され、後続で参照する事が可能です。
過去に条件の考慮漏れで代入されていないインスタンス変数を参照してしまい、データを作るミスを犯してしまった事があります。
何を集計してどんな出力を行うかにもよるのですが、集計or抽出されたデータにnilが予想される場合、どのような設計を行うのかなあと考えるところです。ログに焼き付けるようにしておくのがベターなのですかね…やった事ないですが。
Javaであれば即刻 IndexOutOfBoundsExceptionやらNullPointerExceptionを投げてくれるので、良い意味で常に危機感と隣り合わせでコーディング出来ますよね。
所感
配列やらを決め打ちでやらずに柔軟に対応してくれるのが助かる!(Vectorもいるけど…)
でもたまにnilに泣かされてます
技術の話ばかりではつまらないので、推しの飲食店をメモることにします。
今回は代々木のトスカーナさん。
コスパ良しでもちもちボロネーゼが評判です。個人的には同じくらいもちもちで焼きたてさっくりなピザ推しなのですが。
ドルチェのオレンジとチョコレートのアイス(料理名失念)も口当たりが良くてサイコーでした。
マサカリお待ちしてまーす🐕
WebCampでRailsの勉強をしたので紹介する
はじめまして。ろんです。
もともと福祉系の仕事をしていて、縁あって今はエンジニアの仕事をしています。
(正確には、次の会社を探しているところですが)
今回は自分がエンジニアという肩書きでお仕事をしていく導入でお世話になったプログラミングスクールと、そこで学ぶ経緯について書いていこうと思います。
もともとSEとして都内のSIerに入社、1ヶ月ほどJavaを書く機会がありました。
ひとりで送り込まれた現場では、テスター・詳細設計から降りてくるコードの実装などを担当していましたが、速度感のある開発がしたいという気持ちが抑えきれず、1年程でこの会社を退職しました。
無職期間は手当を貰いながらPHPやPython, Ruby, Swiftなどを学習系のサイトで学んだり、
ローカルで動くアプリを作ったりしていました。
運よくpaizaのやっている転職支援サービスで未経験可の求人を発見し、面接、入職に到るまではslim, SaSS, jQuery、基本的なRailsの取り扱い方をRails tutorialなどで勉強していました。
しかし、外注として一緒に働いていた先輩に「このままのスキル感じゃヤバイ」
と脅された事、個人的にも「このままでは死ぬ(将来継続して収入を手に入れるスキルが身につく気配がない)」と感じていた為に、バックエンドのお仕事もしていきたいと考えました。そこで、働きながら学べるプログラミングスクールです。
とりあえずやりたいことはないけどエンジニアという肩書きでお金は稼いでいきたい
その足がかりとして
・アプリケーションを作成する上で基本的な概念MVCを人に説明できるようになる
・フロントエンドの最低限(SaSS / CSS, html)の自分のコードを根拠を持って人に説明できるようになる
・この先のことはわからないけどとりあえずRailsでアプリを作れるようになりたい
みたいな目標をもやっと掲げてスクール巡りをしました。
結論から言うと、最初の会社と比べて給料は7万円ほどあげてもらいましたし、
フロントエンドで採用されたものの、要件に挙げられた機能を丸ごと担当するレベルになりました。つまりは未経験・仕様も全くわからん状態からフロントとバックエンドを兼任しました。
WebCampの良かったところ
・メンターが優しい、無駄に絡んでこない、質問には教室が閉まるギリギリまで答えてくれる(私のようなコミュ障に適した距離感)
・とにかくメンターが寛大、業務的な悩み・技術的な興味の問題にも、カリキュラム外であるに関わらず、時間を割いて話を聞いてくださっていました
・自習スペースが綺麗、眺めが良いので精神衛生上良い(私が居たのは渋谷の神南の方でした)
・カリキュラムにめちゃくちゃ難題が含まれない
・コードはGithubで公開してもいいらしい
・受講生もギラギラしていなくて平和そうな雰囲気
私はアプリケーションの制作コース(Ruby on Rails)をTwitterの社長割で利用しました。3ヶ月コースで300k…だったかな。社長割の説明については割愛します。
身につくスキルはhtml, css, RailsのMVC設計と仕組みについて。
コスパ的にはトントン、手に職の先行投資なのでお安いくらいかと思います。
他のスクールだとプレゼンも込みなガチガチのハードモードなカリキュラムもありますが、WebCampは心理的な負荷が少なく、教室も綺麗でとても通いやすかったです。
WebCampで習得した技術など
・DBの基本的なセットアップ( mysql )、 環境構築系
・モデルとコントローラの違い、バリデーションの設定、複数モデルの絡んだフォームの作成
・インデントが綺麗になった
・なんとなくで使っていたcssプロパティの説明ができる( floatは要素自体が浮くので後続の要素を巻き込まない為にはclearfixが必要、みたいな )
・スマートデバイス対応する根気
業務でその後伸ばしていった技術・実装( 一部 )など
・ erbを使ってcontrollerたたいたり、API叩いたりして動的なフォーム追加や勘定計算するフォームのバグフィックス( 非同期処理系の実装 )
・DragDropというgemを拡張してインプットからpdf, csv, xlsxファイルなどをアップロードできるようにする( gemの公式ドキュメントを読み込む根気 )
・スコープをモデルに作って抜きたいデータの設定、ransackというgemを拡張してオリジナルの検索スコープを作る( 多対多の子モデルの条件で親モデルを検索かける感じ、SQL周りの勉強 )
この他に新規モデルの追加とか、決済周りのステータス変更とか重いとこもやってました。ちなみにこの現場にはロジックレビューできる人はいませんでした。
一緒に提携して仕事している外注さんに聞きながら頑張ってました。
頑張ってくれるメンターさんと1つの作品の完成までやり抜くことで、そんな辛い状況でも生き抜くガッツが育まれます。
他にも言いたかったこと
・エンジニアは基本的に質問しない生き物( 自分の意見や不明点が明瞭でなければ誰も話を聞いてくれない世界 )
・メンターに憧れて講師になりたいなら少なくとも2年は現場で仕事してみて欲しい
・インフラトップの社長はイケメン