パーフェクトRuby on Rails(増補改訂版)を読みました
特に覚えておきたいこと、読み直したいことについてメモを取りながら読みました。
1章 Ruby on Railsの概要
bin/rails db:console
設定ファイルに定義されている接続情報を使ってDBに接続し、コンソールを起動できる(便利)
さらに、DBコンソールからどのようなCREATE TABLE文が発行されたか確認する(これはRailsあんまり関係ない)
sqlite> .schema tasks CREATE TABLE IF NOT EXISTS "tasks" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "content" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
http://localhost:portId/rails/info/routes にアクセスするとルーティング設定の確認ができる( bin/rails routes
と同じ出力結果)
2章 Ruby on RailsとMVC
ActiveRecord::Relation
ActiveRecordのQuery Interfaceによる操作結果をオブジェクトとして表現したもの。メソッド(メソッドチェインを含む)呼び出しを通じてActiveRecord::Relationは内部でどのようなSQLを発行するか、という情報だけを保持する。実際にそのSQLの実行結果が必要になるまでは、データベースに対するアクセスは発生しない。
明示的に任意の箇所でSQLを発行したい場合は、to_aメソッドを呼び出して即座にSQLを発行するというテクニックがある。
Scopeとクラスメソッドの違い
多くの場合、Scopeはクラスメソッドのショートハンドとして利用できる。
find_byなどの、条件がマッチしなかった場合にnilが返るようなメソッドを使う場合に違いが生じる。
クラスメソッドで条件がマッチしなかった場合はnilが返るが、Scopeで条件がマッチしなかった場合は、その検索条件を除外したクエリを発行し、必ずActiveRecord::Relationを返すという動作をする。
そのため、nilを返す必要がある場合は、クラスメソッドとして定義する方が良い。
バリデーション
アプリケーションレイヤでの制御を通じてエラーを取り扱いやすくするためのもの。
モデルのバリデーションで多くの検証を行うことができるが、レースコンディションですり抜けてしまう問題などもあり得るので、2度手間に感じてもnot null制約などDB上で利用できる制約は活用しておく。
フック
aroundフックは、アクションの前後で実行する都合上、呼び出すメソッドではbefore相当の処理を実行した後でyieldを使ってアクション側に処理を戻してあげる必要がある。
class BooksController < ApplicationController around_action :action_logger, only: [:destroy] def destroy # ... end private def action_logger logger.info "around-before" yield logger.info "around-after" end end
Railsでは通常、GET以外のリクエストではセキュリティトークンが必須となっていて、formヘルパーなどを利用していると暗黙的にformで送信するパラメータとして含まれるようになっている
特殊なルーティング
memberを利用すると、「/publishers/:id/detail」のように個別のリソースに対してアクションを設定します。
collectionを利用すると、「/publishers/search」のように、全体のリソースに対するアクションを設定します。
Rails.application.routes.draw do resources :publishers do member do get 'detail' end collection do get 'serach' end end end
URLヘルパーメソッドの生成するパスをコンソール上で確認する
appオブジェクトを経由することで試せる
irb(main):001:0> app.edit_profile_path => "/profile/edit" irb(main):002:0> app.edit_profile_url => "http://www.example.com/profile/edit"
3章 押さえておきたいRailsの基本機能
RackとRailsの関係
Rackとは、WebアプリケーションサーバとWebアプリケーションフレームワーク間のインターフェイスを共通化した仕様であり実装となっているライブラリです。Rackを仲介できるインターフェイスを持つことでUnicornやPumaといったアプリケーションサーバとRailsをはじめとするフレームワーク間でスムーズなやり取りが行えるようになっています。
Rackアプリケーションとはcallメソッドの引数として受け取ったHTTPリクエストデータなどを元にレスポンスとして返す内容を決定し、callメソッドの戻り値とするという行為に他なりません。(callメソッドはRackに対応するアプリケーションのインターフェイスとして用意しておくことが規約で定められている)
複数DBへの接続
実運用では、負荷を分散するために書き込み専用のprimary dbと読み込み専用のreplica dbを使用したいケースが多い。
それらをRails6標準機能で楽に管理できる。
4章 フロントエンドの開発手法
Rails5.2までは、JSの管理はSprocketsをデフォルトで使用していたが、6.0からはWebpackerを使ったJS管理がデフォルトに変更された。
Webpackerとは、JS管理のデファクトスタンダードであるWebpackを、Rails開発者用に必要な設定をあらかじめ済ませたもの。
Rails6.0の時点では、CSSや画像は引き続きSprocketsを使用して管理している。
assets:precompileで生成した静的ファイルの読み取り
- プリコンパイル
- 開発時は画面にアクセスするタイミングで動的にビルドされているが、本番環境ではあらかじめビルド済みのCSSファイルを配信することでアクセスするユーザへの待ち時間を発生させないようにしている
- assets:precompileを実行するとCSSだけではなくJavaScriptファイルのビルドも行われる
- assets:precompileはdevelopment環境とproduction環境の両方で動作するが、development環境では画面アクセス時に動的にビルドする状態で扱うことが一般的なため、通常assets:precompileで生成した静的ファイルを扱うのはproduction環境でRailsサーバを起動した場合となる
rails-ujs
画面の制御を補助するJSライブラリ。submitボタン押下時にダイアログを表示したり、入力フォームの二重送信をしないようにする制御など。直接JSを書かず、ビューテンプレートに少し記述を追加するだけで実現できる。
5章 Rails標準の機能を活用して素早く機能実装する
ActionCableやActionMailboxなどの説明の章。かなり具体的で本番運用を想定した設定などが乗っていた。
ActionMailbox、ActionCable、ActionTextは使ったことなかったが、この章でそれぞれ少しずつ触れることができた。
6章 Railsアプリケーション開発
awesomeeventsのサンプルレポジトリ
https://github.com/perfect-ruby-on-rails/awesome_events
OAuth&Githubで認証
ログイン確認のような、複数のコントローラで使用する汎用的な機能は一般的にApplicationControllerに記述する
ログイン状態を毎リクエスト確認するメソッドもApplicationControllerにbefore_actionフィルタと一緒に書いておいて、デフォルトでログインしていなければいけない状態にすると漏れ(本当はログインしていないと見れないページが非ログイン状態で見れてしまう)が発生しにくい。(helperとかに書きがちだった気がする)
SJR(Server-generated JavaScript Responses)
サーバサイドでJavaScriptを生成してクライアントサイドはそれをそのまま実行する形式。サーバサイド側でHTMLを生成し、クライアント側では受け取ったHTMLをそのまま使うだけになるので、フロントエンド部分をほとんど書く必要がない。
DHHはAjaxリクエストのバリデーションエラーの実装はSJRで対応するのが良いと考えている。(form_withメソッドはデフォルトでAjaxでフォームの情報を送信する)
https://github.com/rails/rails/issues/25197
ReactやVueと真っ向から対立するので、作りたいアプリやメンバーのスキルに応じて導入するか選択する(フロントをそこまでリッチにしなくて良い&専任のフロントエンドがいないならSJRは選択肢の候補に上がりそう)
ActiveRecordのモデルを絞り込む時は関連が使えるならなるべく関連を辿った方が良い。外部キーからfind_byなどで絞り込むこともできるが、関連を辿った方が何が起こっているのか理解しやすく、事故も起こりにくい。
ヘルパーについて
MVCアーキテクチャの原則としてビューにビジネスロジックは書かない。が、ビューの整形に関するロジックを書きたくなる場合がある。
直接ビューにロジックを書くと見通しが悪くなるので、ヘルパーにメソッドを追加してその中にロジックを書くと良い。
しかし、ヘルパーはグローバルな関数のように振る舞うので、ヘルパーにメソッドが増えてくると、何に使われているメソッドなのか分かりにくなる&そもそもヘルパーメソッドの書き方はオブジェクト志向でない。
上記課題を解決するためにDraperやActiveDecoratorなどのプレゼンター用gemを利用して、モデルに紐づいた形でビュー用のロジックを書く方法を採用する方法がある。多くのロジックをビューで利用する場合に導入を検討すると良い。
コントローラの粒度と名前付け
例えば退会処理を行う場合、それはUser#destroyで表現することができる。実際これでも全く問題ない。
しかし、「退会の意思確認ページ」を表示しようと思った場合、それに対応するアクションは何になるのか?おそらく基本の7アクション(index, new, create, show, edit, update, destroy)以外の新しいアクションを新設することになる。
基本の7アクション以外のアクションをコントローラに追加していくと、プロジェクトが進むにつれ1つのコントローラにアクションが増えてしまい、コントローラの見通しが悪くなってくる。
これを避けるための1つの指針として、「基本の7アクション以外のアクションを作成したくなったら別のコントローラを作る」というものがある。
退会処理の例でいうと、User#destroyの代わりに、Retirementsコントローラを作成しRetirements#createで退会処理を表現することができる。
コントローラは関連するモデルと同じ名前にする、という固定観念から離れて別のリソースとしてコントローラを定義することを考えてみよう。
dependent: :nullify
レコードを削除した時に、関連するレコードの外部キーをnullに更新できる
7章 Railsアプリケーションのテスト
サンプルコードはminitest。普段書かないので勉強になった。
8章 Railsアプリケーションの拡張・運用
activestorageとかkaminariとかelasticsearchとかエラーハンドリングの話題。
elasticsearchの起動忘れるとサーバエラーで先に進めなくなるので注意
9章 コード品質をあげる
rubocop_todo.ymlについて
bundle exec rubocop --auto-gen-config
によってエラーを抑制する設定が rubocop_todo.yml
に書き込まれる。
その後の使い方としては、 rubocop_todo.yml
を読んで、チームのコードスタイル方針に合っているから抑制したままで良い項目については、その設定を .rubocop.yml
に移動し、コードを修正すべき項目についてはそのまま rubocop_todo.yml
に置いておく。
10章 コンテナを利用したRailsアプリケーションの運用
buildkitを利用してイメージビルドを高速化する
マウントキャッシュを利用して、イメージビルド中に書き込んだファイルをホストマシン上にキャッシュして保存しておくことができる
docker-compose
複数のコンテナイメージを組み合わせて一度に起動してくれる(rails, mysql, redis ...etc)
コンテナに馴染む設定ファイルの記述
ファイルベースで各環境用(prod, dev, test)の設定ファイルを配置してから起動するという今までの方式は、完結したファイルシステムの状態でパッケージングされその状態のまま起動できることがメリットであるコンテナに馴染まない。
なので、起動時に可変する設定値を注入するようにする。そのために環境変数を用いる。
秘匿情報についてはcredentials.yml.encを利用する。暗号化されているのでそのままコンテナイメージに含めてしまい、master key を環境変数として実行時に渡すことでコンテナ起動時に秘匿値を復号化し読み込むことができる。
上記のように環境変数を使用することで、環境毎にコンテナイメージを作ってそれぞれのアクセス権限を管理する必要は無くなるが、一方で環境変数に指定する値を誰がどこで管理するのかという問題が生じる。
→ クラウドサービスを活用する
クラウドサービスの活用
上記のような権限管理をクラウドプラットフォームに任せる。
KMSで作成した鍵情報を元にファイルを暗号化して、S3もしくはGCSにファイルとして保存しておく方法がある。コンテナ起動時にラッパースクリプトを実行するようにしておいて、ファイルをストレージからダウンロードしつつ復号化し、それからアプリケーションを起動する。
RailsとHTTPサーバの関係
nginxなどのhttpサーバをRailsアプリケーションの前段におく必要性
- railsのアプリケーションサーバとして多く利用されているunicornの特性。unicornはマルチプロセスを基盤にしたアプリケーションサーバで、外部のインターネットから直接接続を受け取ると、接続先との不安定な通信状況や大きなレイテンシの影響でI/O待ちが発生し、プロセスが長時間占有されてしまう。そういったI/O待ちによる無駄を避けるために、内部ネットワークで高速に通信が可能で大量のコネクションをバッファしておける場所としてnginxなどのHTTPサーバが必要になる
- SSLを直接Railsで受けるよりも、nginx等を終端にした方が扱いが簡単
HTTPサーバは基本的に必要だと考えておく。
11章 複雑なドメインを表現する
アプリケーションが対象とする問題領域のこと
ドメインを分析して構成概念を抽出すること
ドメインモデル
モデリングした結果得られた概念のこと
ドメインモデルは、その概念に関連する属性と振る舞いを持ったオブジェクトとして定義されます。この振る舞いのことをドメインロジック、またはビジネスロジックと呼びます。Railsのモデルは、ドメインモデルとドメインロジックを実装するレイヤーになります。 ドメインモデルは単にドメインの概念を抽出しただけのものなので、ほとんどの場合、これだけではアプリケーションを形にすることはできません。何らかのデータストアを利用して、ドメインモデルの状態をデータとして保存する必要があります。Railsでは、データストアにRDBを用いることを想定しています。そして、アクティブレコードと呼ばれるアーキテクチャパターンを用いて、データベースのレコードとオブジェクトを対応づけています。そのため、モデルの実装で利用するライブラリの名前がActiveRecordなのです。
アクティブレコードとは
アクティブレコードは、データの取得・保存処理とドメインロジックを合わせてカプセル化するアーキテクチャパターンです。Rubyのようなクラスベースのオブジェクト志向言語で実装する場合、データベースのテーブルをクラス、レコードをクラスのインスタンス、カラムをインスタンスの属性に対応させます。アクティブレコードを実装したクラスでは、SQLの実行結果からクラスのインスタンスを構築し、インスタンスメソッドを介して特定のレコードを操作します。アクティブレコードはデータベースのテーブルとクラスが直接対応しているため、単純で理解しやすいという利点があります。一方で、その構造上、単体では複雑なドメインロジックを表現しきれないという欠点があります。
サービスオブジェクトの実装ルールやインタフェース
- サービスオブジェクトを実装するクラスの名前は、ある1つのドメインロジックを指すものにする。そして、このロジックを実行するためのクラスメソッドを1つだけ公開するようにする。
- クラスの外からはインスタンスを生成できないよう(intializeメソッドをprivateにする)にすることで、サービスオブジェクトに状態を持たせないようにする。これは、入力が同じであれば常に同じ結果を返すというサービスオブジェクトの性質を満たす上で重要
https://qiita.com/joker1007/items/25de535cd8bb2857a685
サービスクラス等という単語に踊らされる前に、オブジェクト指向プログラミングに関する知識を貯めて、真剣にクラス同士の関係性を設計しロジックを表現する努力をしようということだ。
12章 複雑なユースケースを実現する
アプリケーション開発におけるユースケースとは
何らかの目的を達成するために行われるユーザとアプリケーションの間の一連のやりとりを表したもの(GitHubアカウントでログインする、など)
RailsはURLで表されるリソースをデータベースのテーブルと1対1に対応させており(慣例的に、リソースと同名のモデルのCRUD操作を行うため)、これらのCRUD操作を通じてユーザとやり取りすると言える。1つまたは複数のCRUD操作によって、1つのユースケースが構成される。
ActiveModelを使ってデータベースと紐づかないモデルを作った
フォームオブジェクト
ユースケースのロジックを実装するオブジェクト(インタラクター)に、form_withとの連携に必要なインタフェースを持たせたもの。ユーザとのやり取りに用いるフォームを簡単に実装できる
ActiveModel::ValidatorやActiveModel::EachValidatorを使用して共通のバリデーションルールを作った。
プレゼンター
app/helpersはグローバルスコープであるため、全てのビューヘルパーでメソッド名が重複しないように注意しなければならない。
プレゼンターというレイヤーを導入することはこの問題を解決する手段の1つ。
Railsにおいては、あるモデルがもつ属性やロジックを利用して、表示に関するロジックを実装するオブジェクトのことを指す。Decoratorと呼ばれるデザインパターンを用いて実装されることが多いため、デコレーターとも呼ばれる。
ActiveDecorator
モジュールとして定義されたプレゼンターを、対応するモデルの各インスタンスに動的にextendする。
13章 複雑なデータ操作を実現する
concernとかコールバックオブジェクトとか。必要になったらまた読み返そう。
プログラミングHaskell第2版 4章メモ書き
4章まできた! パターンマッチとかラムダとか勉強するときに、昔ちょっとだけ勉強したOCamlの知識が役に立ってよかった。 練習問題も少しずつ難しくなってきたが今の所なんとかやっている。
条件式
Haskellの条件式には、常にelse部が必要。(これによって「ぶら下がりelse問題」が回避できる)
signum :: Int -> Int signum n = if n < 0 then -1 else if n == 0 then 0 else 1
ガード付きの等式
条件式の代わりに、ガード付きの等式を使って関数を定義することもできる。
条件式と比べて、条件が多くなった時に読みやすい点が優れている。
もし最初のガードがTrueなら最初の候補、そうではなく2つ目のガードがTrueなら2つ目の候補、という具合に候補が選ばれる。
otherwiseは、プレリュードで単にotherwise = Trueと定義されているため、「他の全ての場合」を処理するのに便利。どのガードもTrueにならない場合はエラーが発生するが、これはotherwiseによって回避できる。
signum n | n < 0 = -1 | n == 0 = 0 | otherwise = 1
パターンマッチ
パターンと呼ばれる式に基づいて、列挙された同じ型の候補の中から結果が選ばれる。
-- 否定演算子notのパターンマッチを使った定義 not :: Bool -> Bool not False = True not True = False -- 論理積を計算する&&演算子のパターンマッチを使った定義 (&&) :: Bool -> Bool -> Bool True && b = b False && _ = False -- `_` はワイルドカード。上の例は、1つ目の引数がTrueであれば結果は2つ目の引数、1つ目の引数がFalseであれば -- 結果はFalseであるという意味。
タプル・パターン
パターンを要素として持つタプル。
「要素数が同じで、それぞれの要素が対応するパターンに全て合致するタプル」に合致する。
-- 組の1つ目の要素を返す関数 fst :: (a, b) -> a fst (x, _) = x -- 組の2つ目の要素を返す関数 snd :: (a, b) -> b snd (_, y) = y
リスト・パターン
パターンを要素として持つリスト。
「長さが同じで、それぞれの要素が対応するパターンに全て合致するリスト」に合致する。
-- リストの長さが3で、かつ先頭の要素が文字'a'であるかを検査する関数 test :: [Char] -> Bool test ['a', _, _] = True test _ = False
リストの合成とcons演算子
Haskellのリストは合成されたデータであり、空リスト[]
に対して演算子:
を使って要素を1つずつ増やしていくことで生成される。
:
は既存のリストの先頭に新しい要素を追加して新しいリストを生成する演算子で、cons演算子と呼ばれる。
-- リスト[1, 2, 3]は次のように分解できる [1, 2, 3] = 1 : [2, 3] = 1 : (2 : [3]) = 1 : (2 : (3 : [])) -- = 1 : 2 : 3 : [], cons演算子は右結合
cons演算子によるパターン
先頭の要素と残りのリストがそれぞれ対応するパターンに合致するような、空でないリストに合致する。
-- 空でないリストの先頭の要素を取り出す head :: [a] -> a head (x:_) = x -- 空でないリストから先頭の要素を取り除く tail :: [a] -> [a] tail (_:xs) = xs -- 関数適用は演算子よりも結合順位が高いので、cons演算子を使ったパターンは括弧で囲まなくてはならない
無名関数のこと。なので関数名は持たない。
引数のパターンと、引数から結果を計算する方法を示した本体とからなる。
-- 数値の引数xを取り、x + xを計算して返す無名関数の作成 -- `\`はギリシア文字の`λ`を表している \x -> x + x -- 関数名はないが、他の関数と同じように利用できる (\x -> x + x) 2 = 4
ラムダ式を使うと、カリー化された関数の型注釈と関数定義を同じ形にすることができる
add :: Int -> (Int -> Int) add = \x -> (\y -> x + y)
「関数を返す関数」を定義するときに、カリー化された結果ではなく本来の関数が結果として返される場面でも便利
-- プレリュード関数constは関数を返し、その関数は引数が何であれあらかじめ定められた値を返す const :: a -> b -> a const x _ = x -- 上は、型宣言に括弧をつけ、定義にラムダ式を用いれば、constが返り値として関数を返すことが明確になる const :: a -> (b -> a) const x = \_ -> x
一度しか参照されない関数に名前を付けずに済む
-- 最初のn個の正の奇数を返す関数 odds :: Int -> [Int] odds n = map f [0..n-1] where f x = x*2 + 1 -- example odds 4 #=> [1, 3, 5, 7] -- 次のように簡略化できる odds :: Int -> [Int] odds n = map (\x -> x*2 + 1) [0..n-1]
セクション
+のように、引数の間に置かれる関数を演算子と呼ぶ。
引数を2つ取る関数は、7 div
2 のように関数名をバッククォートで囲むことで演算子になる。
逆に、任意の演算子を()で囲むことによって引数の前に置いて使うカリー化された関数とすることも可能。
(+) 1 2 #=> 3
また、必要であれば(1+) 2、(+2) 1のように引数の1つを括弧の中に入れることもできる。
'#'を演算子とすると、引数xとyに対し、式(x), (x #), (# y)は一般にセクションと呼ばれる。
セクションの関数としての意味は、ラムダ式を使って次のように形式化できる。(このへん結構好き)
(#) = \x -> (\y -> x # y) (x #) = \y -> x # y (# y) = \x -> x # y
セクションの用途
単純で有益な関数を簡潔に定義する
(+) 加算関数 (1+) 引数に1を加える関数 (*2) 引数を倍にする関数 etc...
演算子の型を宣言する(Haskellでは演算子そのものは正しい式ではないため)
(+) :: Int -> Int -> Int
他の関数に二項演算子を引数として渡す(これ理解できてない)
sum :: [Int] -> Int sum = foldl (+) 0
練習問題の回答はこちら
https://gist.github.com/h-shima/1e6c22c4fa655940009b60b436f0f886
プログラミングHaskell第2版 3章メモ書き
3章まできた。今の所毎週1章ずつ進められているので順調だが、この先どうなる事やら。。。 社内勉強会もスキップ無しで毎週開催できているので、こちらも続けたいところ。
第3章 型と型クラス
型
互いに関連する値の集合
例: Bool型 ... False, Trueという二つの真理値が含まれる。「Bool → Bool」型 ... Bool型をBool型に変換する否定演算子notのような関数が全て含まれる。
-- vの型はTである。vは型Tの値である。 v :: T False :: Bool True :: Bool not :: Bool -> Bool -- 式eを評価すると型Tの値を生成する e :: T not False :: Bool not True :: Bool not (not False) :: Bool
Haskellでは全ての式に必ず型が付き、式の型は型推論という機能によって式を評価する前に決定される。
Haskellでは式を評価する前に型が検査されるので、評価の際には型エラーが起きない。(型安全。ただし評価の際に他のエラーが起きないことを保証しているわけではないので注意)
関数型
関数は、ある型の引数を他の型(もしくは引数と同じ型)の結果に変換する。
-- 型T1の引数を型T2の返り値に変換する関数の型を「T1 -> T2」と書く。 not :: Bool -> Bool -- 関数notの型は「Bool -> Bool」である even :: Int -> Bool -- 関数evenの型は「Int -> Bool」である add :: (Int, Int) -> Int -- 関数addの型は「(Int, Int) -> Int」である。
カリー化された関数
関数で複数の引数を処理する、タプルを引数に渡す以外の方法。
関数が返り値として関数を返せるという性質を活用する方法。
以下の例のように、1度に1つの引数をとる関数をカリー化されていると表現する。
-- タプルを使って複数の引数を処理する例(カリー化されていない) add :: (Int, Int) -> Int add (x, y) = x + y -- カリー化された関数の例 add' :: Int -> (Int -> Int) -- 関数add'の型は「Int -> (Int -> Int)」である。(Intを引数として受け取って、「Int -> Int」型の関数を返す) add' x y = x + y -- xを引数として受け取って、「Intを引数として受け取ってIntを返す関数 ... ①」を返し、 -- ①をyに関数適用してIntを返す。 -- add'は、2つの引数を一度に1つずつ取っている点がタプルを使う例と異なる。
カリー化された関数を扱うときに括弧が過剰になるのを避けるための2つの規則
- 型の中の → は右結合
-- 両者は同等 Int -> Int -> Int -> Int Int -> (Int -> (Int -> Int)) -- Int型の値を引数に受け取って、「Int -> (Int -> Int)」型の関数を返し、 -- 「Int -> (Int -> Int)」型の関数はInt型の値を引数に受け取って「Int -> Int」型の関数を返し、 -- 「Int -> Int」型の関数はInt型の値を受け取りInt型の値を返す
- 空白文字を用いて表す関数適用は、必然的に左結合である
-- 両者は同等
mult x y z
((mult x) y) z
多重定義型
1つ以上の型クラス制約をもつ型のこと。
型クラス制約
ある関数がある型の値に適用可能であるという事実を正確に表現するためのもの。
C a という形式で書く。(Cは型クラス名、aは型変数。)
-- +は数値型の値に適用可能であり、数値型の値を引数にとり、「a -> a」型(aは数値型)の関数を返す (+) :: Num a => a -> a -> a
練習問題の回答はこちら
https://gist.github.com/h-shima/1e6c22c4fa655940009b60b436f0f886
プログラミングHaskell 2章読んだのでメモ
ghcのインストール
haskell platformからインストールした https://qiita.com/takehilo/items/7b65d32e47510cd1fe48#comment-8c965688a5a7db364818 ※使い込みたい場合はstackで管理した方が良いらしいが、まあ最初のうちは大丈夫だろう
ghci(対話的インタプリタ) tips
# ホームディレクトリにprogramming-haskellディレクトリを作ったので、 # 本書で作成したプログラムはここに突っ込んでいく # **自作ファイルを読み込ませつつghciを起動する** $ ghci ~/programming-haskell/test.hs # 上で読み込ませた自作ファイルを再読み込みする > :reload # ghciの終了 > :quit # プログラムnameを読みこむ > :load name # 現在読み込ませているプログラムを編集する > :edit # exprの型を編集する > :type expr
プレリュード Haskellの組み込み関数はプレリュード(あらかじめ読み込まれるモジュール)の中で定義されている
関数適用
-- 数学 f(a, b) + cd -- haskell f a b + c * d -- 関数適用は全ての演算子の中でもっとも優先順位が高い f a * b = (f a) * b
引数の命名規則 引数がリストである場合は、名前の末尾にsをつけて複数の値を含んでいる可能性を示す。 数値のリスト ns 任意の値のリスト xs 文字のリストのリスト css
2.7 練習問題 1. この章の例題の実行(やった) 2. 結合順位を示す括弧をつける
2^3*4 = (2^3)*4 2*3+4*5 = (2*3)+(4*5) 2+3*4^5 = 2+(3*(4^5))
- 3つのエラーを修正してGHCiで正しく動くようにする
-- 修正前 N = a 'div' length xs where a = 10 xs = [1, 2, 3, 4, 5] -- 修正後 -- 1.新しく関数を定義する場合、関数の引数と名前は先頭を小文字にしなければならない -- 2.レイアウト規則より、レベルが同じ定義(今回の例だと`a = 10`と`xs = [1, 2, 3, 4, 5]`)は、 -- プログラム中で先頭を完全に同じ列に揃えなければならない -- 3.引数を2つ取る関数は、関数名をバッククォートで囲むことで引数の間に書けるようになる n = a `div` length xs where a = 10 xs = [1, 2, 3, 4, 5]
メモ: GHCiで複数行を入力する方法
https://tnomura9.exblog.jp/23586727/
- プレリュード関数last(空でないリストの最後の要素を取り出す)をこの章で紹介されたプレリュード関数を使って定義する
-- 解1. リストを逆順にしてから先頭の要素を取り出す -- (reverse xs)のように括弧をつけないと、関数適用は優先順位がもっとも高いのでheadの引数がreverseと -- 解釈してパースエラーを起こしてしまう last xs = head (reverse xs) -- 解2. (リストの要素数 - 1)を先頭から取り除いて残った要素を取り出す last xs = head (drop (length xs - 1) xs) -- 解3. リストの(要素数 - 1)番目の要素を取り出す last xs = xs !! (length xs - 1)
- プレリュード関数init(空でないリストから最後の要素を取り除いたリストを返す)の定義を2通り示す
-- 解1. 要素を逆順にしてから先頭の要素を取り除いたあと、要素を最後逆順にして元の順番に戻す init xs = reverse(tail (reverse xs)) -- 解2. リストの先頭から(要素数 - 1)個の要素を取り出す init xs = take (length xs - 1) xs
プログラミングHaskellを読み始めた
会社の勉強会でプログラミングHaskellを読むことになった。(来週から始めるので楽しみ) とりあえず1章を読んで練習問題まで解いたのでメモをまとめておく。
第1章 導入
Haskellにおける関数 ... 1つ以上の引数を取って1つの結果を返す変換器
関数プログラミング ... 一般的に、「計算の基本は関数を引数に適用すること」というプログラミング手法のこと
関数型言語 ... 関数プログラミングの手法を「提供」し「奨励」しているプログラミング言語。
Haskellの明瞭簡潔さを示す例としてのクイックソート(任意の数値型のリストに対して要素を昇順に並べ替えたリストを生成する)
-- xはqsortの引数として与えられるリストの最初の要素、xsは残りのリスト qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, a <= x] larger = [b | b <- xs, b > x] --------------------------------------------------- -- `++` は2つのリストを連結する二項演算子 qsort [3, 5, 1, 4, 2] = qsort [1, 2] ++ [3] ++ qsort [5, 4] = (qsort [] ++ [1] ++ qsort [2] ) ++ [3] ++ (qsort [4] ++ [5] ++ qsort []) = ([] ++ [1] ++ [2]) ++ [3] ++ ([4] ++ [5] ++ []) = [1, 2] ++ [3] ++ [4, 5] = [1, 2, 3, 4, 5]
クイックソートの型
-- 任意の順序(Ord)型aに対し、qsortはその型を要素として持つリストを同種のリストに変換する -- 順序型とは、数値や文字列などの順序を持つ型全般のこと qsort :: Ord a => [a] -> [a]
モナドの説明もちょっと出てきたけど難しかった。後の章読みながら理解しよう
1.7 練習問題
- double (double 2)の結果を算出する別の計算方法を考える
double (double 2) = double (2 + 2) = (2 + 2) + (2 + 2) = 4 + (2 + 2) = 4 + 4 = 8
- xの値によらず sum [x] = x であることを示す
-- 関数sumの定義 sum [] = 0 sum (n:ns) = n + sum ns -- 問2の解 sum [x] = x + sum [] = x + 0 = x
- 数値のリストに対し積を計算する関数productを定義し、product [2, 3, 4] = 24 となることを示す
-- 関数productの定義 product [] = 1 product (n:ns) = n * product ns -- 一応型も定義(sum関数を真似してみた) product :: Num a => [a] -> a product [2, 3, 4] = 2 * product [3, 4] = 2 * (3 * product [4]) = 2 * (3 * (4 * product [])) = 2 * (3 * (4 * 1)) = 24
- リストを降順に整列するに関数qsortの定義を変えるにはどうすれば良いか
-- リストを降順に整列するqsort qsort [] = [] qsort(x:xs) = qsort larger ++ [x] ++ qsort smaller where smaller = [a | a <- xs, a <= x] larger = [b | b <- xs, b > x] -- 具体的な値で降順ソートしてみる qsort [3, 5, 1, 4, 2] = qsort [5, 4] ++ [3] ++ qsort [1, 2] = (qsort [] ++ [5] ++ qsort [4]) ++ [3] ++ (qsort [2] ++ [1] ++ qsort []) = [5, 4] ++ [3] ++ [2, 1] = [5, 4, 3, 2, 1]
- qsortの定義で、≤を<に置き換えるとどのような影響があるか
-- 本書で与えられているqsortの定義(数値リストを昇順に並べ変える) qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, a <= x] larger = [b | b <- xs, b > x] -- <=を<に置き換えたqsortの定義 qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, a < x] larger = [b | b <- xs, b > x] -- <=を<に置き換えたqsortで[2, 2, 3, 1, 1]をソートしてみる qsort [2, 2, 3, 1, 1] = qsort [1, 1] ++ [2] ++ qsort [3] -- 残ったリストの中の要素2が、larger, smallerのどちらにも該当しないため消えてしまう = (qsort [] ++ [1] ++ qsort []) ++ [2] ++ qsort [3] -- 同上の理由で1が消える = [1] ++ [2] ++ [3] = [1, 2, 3] -- 解 -- qsortの定義で<=を<に置き換えると、与えられたリストの要素として同一の数値が複数存在する場合、 -- 重複した値が解のリストから消えてしまうという影響がある。
Haskellは純粋関数型言語なので関数が副作用を持つことは無いと思っていたが、どうもそうではない?ぽい。 型によって純粋な関数と副作用を引き起こす関数を明確に区別することができ、それこそHaskellの中心となる機能であるとのこと。まだ全然理解できていないけれど、この本を読み終わる頃には理解できているといいな。
リファクタリングRubyを読んでいる
社のもくもく会でリファクタリングRubyを読んでいる。 4章まで読み進めていて、以降の章はリファクタリングの実際的な手法の話になるため、プロダクションのコードを見ながら本書で紹介されている手法が適用できないかどうか考えながら読み進めるつもりだ。
ここには、1章から4章まで読んだメモをまとめておく。
優れたコードは書き換えやすい ソフトウェア開発は有機的で持続的な活動である
リファクタリングとは
コードの外から見た振る舞いを変えずに、内部構造を改良することによってソフトウェアシステムを変えていくプロセス
本質的には、コードが書かれた後でコードの設計を改良しているということ
インデックス
1章 リファクタリングの本質を理解したければ読め
2章 リファクタリングの一般原則、定義、リファクタリングする理由(なぜリファクタリングが必要なのか?)
3章 Kent Beckの力を借りてコードの「臭い」を見つける
4章 コードにテストを組み込む方法
5〜12章 リファクタリングのカタログ(リファレンスなので一度に全部読むのは辛そう。実際にリファクタリングが必要になったら読む。※グロ放題とかグロプラのリファクタリングに役立ちそうな章を選んで読んでから、実際にPRを作ってみるという夢)
1章
- 基本的に、メソッドは(そのメソッドが使用している)データを持つオブジェクトに割り当てるべき
- 新メソッドに処理を委ねるという形で古いメソッドを残すことがある(古いメソッドが公開メソッドで、他のクラスのインターフェイスを書き換えたく無い時に役立つ)
- 一時変数を使用してメソッド呼び出しの戻り値を代入しているが、その後何も変更を加えていない → 「一時変数から問い合わせメソッドへ」(一時変数を取り除く)
- 一時変数を取り除くことでメソッド呼び出しが増え、パフォーマンスが下がるのでは無いかという懸念があるかもしれないが、リファクタリングの時にこのような考え方をするべきでは無く、リファクタリングをしている時にはわかりやすくすることに集中するべきである
- 一時変数は無用に多くの引数をやり取りする原因になるという点で、無い方が良い
- 一時変数が役に立つのは、それが含まれているルーチンの中だけなので、一時変数は長くて複雑なルーチンが作られることを助長する
- 一時変数を使う代わりに新しいメソッドを作ってそれを呼び出す(これも、「一時変数から問い合わせメソッドへ」)
- まずコードをわかりやすくしてから、プロファイラを使ってパフォーマンス問題に取り組む(コードをわかりやすくするのが先)
- メソッド切り出しのリファクタリングでは、新メソッドの冪等性(何度実行しても同じ結果になる性質)に注意を払うべき
- 「ループからコレクションクロージャメソッドへ」を使ってコードを簡潔にする
# リファクタ前(ループ) def total_charge result = 0 @rentals.each do |rental| result += rental.charge end result end
# リファクタ後(コレクションクロージャ) def total_charge @rentals.inject(0) { |sum, rental| sum + rental.charge } end
- 他のオブジェクトの属性に基づいてcase文を書くのは間違っている。case文を使わなければならない場合には、他のオブジェクトではなく自分自身のデータに基づくべき
2章
リファクタリング(名詞)... 外から見えるふるまいを変えずに、ソフトウェアをわかりやすくし、安いコストで変更できるようにするために、ソフトウェアの内部構造に加えられる変更
リファクタリングする(動詞)... 外から見えるふるまいを変えずに、一連のリファクタリングを適用してソフトウェアの構造を変えること
Kent Beckの2つの帽子
リファクタリングを活用してソフトウェアを開発するときは、機能の追加とリファクタリングという2つの別々の活動のために時間をはっきりと区別すべきであるという考え方を説明する比喩。
機能の追加をする時には既存のコードを書き換えず、新しい機能を追加することに専念する。テストを追加し、テストが成功するようになるということによって作業の進度がわかる。
リファクタリングをするときは、機能を追加しないように努力し、コードの改造だけに力を注ぐ。以前は見落としていた条件に気づいた場合、インターフェイスの変更に対処するためにどうしても必要な場合以外ではテストの追加、変更を行ってはならない。
プログラムが扱いにくくなる4つの要因(Kent Beck)
- 読みにくいプログラムは書き換えにくい
- ロジックの重複があるプログラムは書き換えにくい
- 機能の追加によって、動いているコードを書き換えなければならなくなるようなプログラムは書き換えにくい
- 条件分岐が複雑なプログラムは書き換えにくい
リファクタリングと設計
柔軟な解は、単純な解よりも複雑になるのだ。このような設計から作られたソフトウェアは、最初に考えた方向では対応しやすいが、一般にメンテナンスがかえって難しくなってしまう。
対応できる場合でも、設計にどのように手を入れたら良いかを理解しなければならない。... 柔軟な解をすぐに実装するのではなく、「リファクタリングで単純な解を柔軟な解に帰るのはどの程度難しいだろうか」ということを考える。答えが「かなり簡単」であれば(ほとんどの場合はそうなるが)、単純な解を実装すれば良い。(p92)
3章 リファクタリングによって解決できる問題点の兆候
問題点の兆候を簡単にまとめたけど、本読むと解決策も書いてあるので本を見る
- コードの重複
- 長いメソッド
- 古い言語ではサブルーチン呼び出しにかかるオーバーヘッドが大きかったため、メソッドを小さく分割することがなかなかできなかった
- 小さく分割したメソッドを人間が理解しやすくするために、メソッド名の付け方に気をつける。名前がよければ中身を見る必要がない
- メソッド名はコードの仕組みではなく、目的に基づいて付けられる
- メソッド名がコードの目的を説明しているなら、置き換えられるコードよりもメソッド呼び出しの方が長くても、メソッド呼び出しに変えて良い
- 大きなクラス
- 長い引数リスト
- 変更系統の分岐
- あんまりピンとこなかった
- ショットガン創の手術
- 1つの種類の変更をしようとするたびに、様々なクラスに無数の小さな変更を加えなければならないとき
- すべての変更点が1つのクラスにまとまるようにメソッドの移動やフィールドの移動をする
- メソッドの浮気
- メソッドが自分の所属クラス以外のクラスにやたらと関心を持っているように見える場合
- メソッドの移動をして、気になっているクラスに移動する
- メソッドが複数のクラスのメンバを使っている場合は、メソッドの抽出をして、それぞれ別のクラスに移動できるようにする
- 群れたがるデータ
- 連れ立って行動する一連のデータは、それら専用のクラスを持つようにした方が良い
- プリミティブ強迫症
- プリミティブ型(Stringとか)をちょっとだけ組み合わせた小さいクラス(金額クラスとか)を作っていいんやで(データ値からオブジェクトへ)
- case文
- パラレルな継承階層
- あるクラスのサブクラスを作ると、別のクラスのサブクラスも作らなければならなくなること
- 仕事をしないクラス
- 名誉の死を
- 空論的一般化
- 人間「いずれこういったこともできるようにする必要があると思うよ」 → 使われていないのなら、残しておく必要がない。からくりは邪魔なだけであり、取り除くべきだ
- 一時フィールド
- メッセージの連鎖
- 横流しブローカー
- オブジェクトのカプセル化が行きすぎて、クラスの半分以上のメソッドが他のクラスに処理を委譲しているような場合
- 何が行われるか実際に知っているオブジェクトとやり取りをする
- 親密すぎるクラス
- 2つのクラスが親しくなりすぎて、非公開なAPI部分に長々と入り込んでしまう場合
- インターフェイスの異なるクラス群
- ?
- 不完全なライブラリクラス
- データクラス(属性だけを持っているクラス)
- 継承した遺産の拒絶
- コメント
- コメントを書かなければならないという感じがした時には、まずコメントが不要になるまでコードをリファクタリングする
- コメントが効果的に使えるのは、何をしたら良いかはっきりしていない時。よくわからないことが何かを示すことができる
- メタプログラミング狂
- method_missing...
- 柔軟すぎるAPI
- ?
- 紋切り型コードの繰り返し
- クラスアノテーションの導入なコードの意図を明確にする
Rubyの書き方的な話 https://techracho.bpsinc.jp/hachi8833/2017_05_15/38869
4章 テストの構築
自分で開発するときには、その過程でテストを書いていくが、他のプログラマとリファクタリングの仕事をするときには、テストのないコードを相手にしなければならない。そこで、リファクタリングする前に、コードを自己検証コード(自分自身のテストを行う)にしなければならない。
(そのAPIを)テストをするかどうかはリスクによって決めるべきだ。現在または将来に現れそうなバグを見つけるためにテストをしているのだということを忘れてはならない。
まとめ
「リファクタリング」が意味するところをはっきりと言葉にしてくれていたのがよかった。(コードの外から見た振る舞いを変えずに、内部構造を改良することによってソフトウェアシステムを変えていくプロセス)
ガンガンリファクタリングしていくぞい!
正規化ざっくりメモ
第1正規形への変形
繰り返し列の排除。 全ての行の中の全ての列には値が1つずつ入っているべきであり、1つの行の中に値が複数入る列があってはならない
第2正規形への変形
複合主キーの一部への関数従属(部分関数従属)の排除。
関数従属とは
ある列の値Aが決まれば、自ずと列の値Bも決まるという関係のこと。 列Bは列Aに関数従属している、という。
複合主キーを持つテーブルの場合、非キー列は複合主キー全体によって値が決まるべきである。 複合主キーの一部だけで非キーの値が決まるべきではない(部分関数従属であるべきではない)
※複合主キーが存在しないテーブルの場合はそもそも第2正規形の条件を満たしている(主キーが1つしかないので、部分関数従属が起こりえないため)
第3正規形への変形
間接的な関数従属(推移関数従属)の排除。 テーブルの非キー列は、主キーに直接関数従属するべきである。