Chapter3 - Practical Object Orient Design in Ruby
Managing Dependencies
抜粋
デザインのやるべきことは、それぞれのクラスが仕事をこなすために必要な最低限のことだけを知っているように、依存性を管理すること
dependency injection
クラスが知っていることを減らすという視点で、クラスの名前と初期化方法、メソッド名を知っているより、メソッド名だけを知っている状態の方が依存性が少ない
- isolate vulnerable external messages
外部参照は慎重に扱うべきというのはわかる気がするけど、なんかやりすぎな気がする。 Gear#diameter では意味が変わってしまうのでは?
- remove argument-order dependencies
ハッシュによる呼び出しが優れている。 呼び出し順の依存性がなくなり、引数の追加、削除をリスクなしに行える。
ハッシュ名を記入するのは冗長になってしまっているが(そのまま順に指定していった方がコードの量は減る)、この冗長性は現在の要求とと不確かな未来の要求の媒体として存在している。
また、ハッシュでの呼び出しはドキュメントとしても価値がある。
- 依存性の向き
変更が多いと想像できるクラスはなるべく影響範囲が広がらないようにする。 変更が多いクラスから変更が少ないクラスへ依存するように努めることで、影響の広がりを抑える。
Abstractなものはconcreteなものより細かい変更が起こりにくいので、Abstractなものに依存する方が変更の影響を受けづらい。
感想
クラス間の依存性など、なんとなくこうした方がいいかな〜という感じで実装してきていたけど、判断基準などを言語化して明確にしていくことで、クラス間の関係性を考える基準を明確に持つことにより、考えるコストを減らせて、習慣化していければ、ミスジャッジを減らしていくことができそう?
依存性とは具体的にはどのようなものか、どのようにシステムに影響を及ぼすか、どのような管理、評価方法があるか、というようなものをしっかり定着させることができればコードの質が上がっていきそう。
英語
- These dependencies turn minor tweaks into major undertakings where small changes cascade through the application, forcing many changes.
それらの依存性は細かい調整を、その調整の影響のあるたくさんの箇所の変更を強いられるような大きな仕事に転じさせてしまう。
- On the face of it 一見したところでは
Chapter 2 - Practical Object Oriented Design in Ruby
Designing Classes with a Single Responsibility
抜粋
- クラスは一つの責任しか持つべきではない。 そのクラスを一文で簡潔に説明できるかどうかが一つのバロメーター。 andやorが入る場合は、2つ以上の責任を持ってしまっている可能性が高い。
- designの決定はしかるべき情報が揃った時にするのが一番いい。 現状のクラスがイマイチすっきりしないように感じても、情報が揃うまで手をつけづにおくのは、コストを抑える一つの考え方。
- data[0],data[1]のような曖昧なデータ表現は避けたほうがいい。 こういった曖昧なデータを明示的にどういったデータを表すかをはっきりさせるのも一つの責任で、コードの中に散りばめてしまったら変更が大変になるので、 曖昧なデータを明示的にするコードは一つの場所にまとめるべき(DRY).
rubyではStructを使うことが一つの手段。
- 責任の分離に関して、別のクラスを設けるほどではないけど、今のままでは気持ち悪いなというケースは大いに考えらる。
こういった場合、別のクラスに分けてしまってもいいが、Rubyの場合は、クラス内のStruct等にコードを分離しておくのも一つの手段。
こうしておくと、後から来た要望で、やっぱり別クラスにしたほうがいいなとなった時に、スムーズにコードを移植することができる。
また、そのままの状態が続いても、コードの意図が解りやすくなる。
感想
DRY(Don't Repeat Yourself)やSRP(Single Responsible Principle)について実際のコードの用いて説明されていてわかりやすかった。
曖昧なデータを使うべきではないところや、別クラスに分けるほどでもないな〜というケースは、よく遭遇するケースで、ぼんやりとケースごとに対処していたけど、解りやすく言語化されているので参考になる。
決定の保留に関してかなり頻繁に言及されていたりと、単純に原則を説明しているのではなく、実際のコストなどを考慮されていて、だいぶ実践的なのでいい感じ。
英単語
- fraught 悲惨な
Every decision seems both permanent and fraught with peril. perilは危険
- overwhelming 圧倒的な
These questions can be overwhelming.
foresee 予見する
cohesion 凝集
OO designer use the word cohesion to describe this concept.
Chapter 1 - Practical Object Oriented Design in Ruby
いい本だと紹介されていたのと英語の勉強など。
第1章はオブジェクト指向デザインの基本的な紹介。
概要
ソフトウェア開発に変更は付き物。なぜなら顧客は自分が求めているものが最初の段階で解っていない。
なので、変更を容易にするのがOOD(Object Oriented Design)の目的。
OODはオブジェクト間の依存性を管理する技術。
1つのオブジェクトに対して多くの依存関係があると、そのオブジェクトの変更がある場合の影響範囲が大きくなってしまう。
Designとはソフトウェアの未来を予測することではない。 単に、何がまだ未確定か、どう変化していくか、現時点で解っていないかを許容する。
良いデザインはiterativeに発見されるもので、Agile的な開発と相性が良い。 Agileの基本的な概要についても説明されている。
感想
今の所それほど発見的な内容はないけど、Agile周りに関してわかりやすく説明されている印象。
英単語
immersion
the action of immerse to someone or something in liquid. immerseは浸す。
the first learning how to do object-oriented design is to immerse yourself in objects.
inevitable 回避できない
(Software) Change is unavoidable. It is ubiquitous, omnipresent, and inevitable.
wreak もたらす
In the absence of design, unmanaged dependencies wreak havoc because objects know too much about one another.
amenable 従順な
you must also create code that is amenable to being changed later.
excess 過剰
zeal 熱心
However the popularity of patterns led to a kind of abuse by novice programmers, who, in an excess of well-meaning zeal, ...
premise 前提 if this premise it true, ...
React,Reduxのキャッチアップ
フロントエンド周りの技術が少し落ち着いてきたという風潮があるので、キャッチアップ。
やったこと
- React Tutorial https://facebook.github.io/react/docs/tutorial.html
React自体は結構シンプルでわかりやすく、メリットも感じられた。 state周りが、fluxの予備知識が少しあったので、reduxとどのように組み合わせるのかなというのはモヤっとしたけど、一旦放置。
- FRP?
【翻訳】あなたが求めていたリアクティブプログラミング入門 - ninjinkun's diary
いい記事だと聞いていたので見てみた。 ぼんやりと理解。
- Redux video course
Getting Started with Redux - Course by @dan_abramov @eggheadio
公式ページの上部に紹介されていたので、英語のリスニング練習も兼ねて見てみた。
TodoListの完成まで(1/3ぐらい?)見てなんとなく雰囲気がわかってきたので一旦停止して、自分で書いてみることに。
Todoアプリを書いてみる
- 準備
npm init and install react, react-dom, babel-*
- webpack 設定 appsとvendorを分けるように設定した。
var path = require('path'); var webpack = require('webpack'); module.exports = { entry: { main: './main.jsx', vendor: ['react', 'react-dom', 'react-addons'] }, output: { path: __dirname, filename: 'bundle.js' }, module: { loaders: [ { test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['es2015', 'react'] } } ] }, plugins: [ new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js") ], devServer: { } }
サーバー通信なしで機能させる
- reducerにstateを定義
- actionを定義
- component作成
- storeを作成
- react-reduxのProviderでstoreとcomponentを連携
わからないところは、
を見ながら実装。
サーバー連携
サーバー連携のAsync処理はいくつか方法が紹介されていたので少し混乱。
まず単純にaction周りにjQueryでAjax処理を書いて機能させてみた。
次にredux-thunkでaction creatorの慣習に従ってAjax処理を呼び出せるように調整
GitHub - gaearon/redux-thunk: Thunk middleware for Redux
GitHub - matthew-andrews/isomorphic-fetch: Isomorphic WHATWG Fetch API, for Node & Browserify
サーバーサイドはrailsで作成した。
感想
reactコンポーネントは考え方もシンプルで、APIも少なく、分かりやすい。 stateの移行や、data flowが絡むと複雑になる。
ReactComponent.stateやreduxのconnectなど、また手動でもできるということで、すっと理解するのが難しい。
react-reduxのconnect, Providerを使うとこの辺りの関係がすっきりする。
この程度の規模のコードならスッキリ書けるけど、プロダクトレベルになった時にどうなるのか、どのように管理していくかが解らない。
componentsは整理できそうだけど、actions, reducersあたりがだいぶ複雑になりそう。
ということで次の課題は、 Recipes | Redux を読んでみるのと、もう少し複雑なものを自分で作ってみる?
メモ
react-redux connect
componentのpropsにstoreと連携するためのオブジェクトを設定できる。
引数なし: React.component.props.dispatchにstore.dispatchが入る。
mapStateToProps: stateからpropsへマージされるデータを生成する。pure function.
mapDispatchToProps: store.dispatchを受け取り、propsへマージされる、dispatch処理を含んだ関数を生成する。
Providerはstoreとchild compnentをconnectを通じて連携させる。
react-thunk
store.dispatch() にfunctionを指定できるようにするmiddleware
returnするfunctionにはdispatch, getStateが引数として取れるので、asyncプロスセのcallback等でdispatchを呼び出すことができる。
asyncの処理を含むaction creatorを他のaction creatorと同じように呼び出せることがメリット?
fetchでpostする際の注意点
fetch https://github.com/github/fetch
form data でPOSTする際。 http://stackoverflow.com/questions/29775797/fetch-post-json-data
json形式でpostする際。 http://jxck.hatenablog.com/entry/whatwg-fetch
railsでscaffoldされたController#createメソッドはform形式で送信される想定で実装されているので、jsonで受け取る場合は修正する必要がある。 http://masawada.hatenablog.jp/entry/2015/12/29/195317
Reactコンポーネントでのformの値の扱い方
linked state mixin https://facebook.github.io/react/docs/two-way-binding-helpers.html deprecated
https://facebook.github.io/react/blog/2016/04/07/react-v15.html linkedStateMixinはdeprecatedなので、stateのchangeをhandleする関数を自作するのが推奨?
ActionDispatch::Http::UploadedFileのコードリーデイング
概要
ActionDispatch::Http moduleに class UploadedFile が定義されている。
サーバーにアップロードされたtime fileを扱うクラス。
pickup
initialize以外の処理は@tempfileに対するショートカット。
initializeでは、ファイルの有無チェックとファイル名のエンコードをUTF8に変換する処理を行っている。
ファイル名エンコード処理。
force_encodingは実際にエンコードの変換処理を行なうのではなく、エンコーディングの情報だけ設定する。
if @original_filename begin @original_filename.encode!(Encoding::UTF_8) rescue EncodingError @original_filename.force_encoding(Encoding::UTF_8) end end
encode!で発生するエラーは
- 変換後のエンコーディングに対応するコードがない場合 Encoding::UndefinedConversionError
- 変換前の文字列に不正なコードがある場合 Encoding::InvalidByteSequenceError
がある。 どちらの場合もエンコーディング情報だけUTF8に設定している。
エンコーディングに関する参考記事
Ruby の invalid byte sequence in UTF-8 例外を encode("UTF-8", "UTF-8") で回避するのはおかしいよ、という話 - sonots:blog
Ruby 2.1.0 に追加される不正なバイト列を除去する String#scrub の紹介 - sonots:blog
ActionDispatch::Http::URLのコードリーディング
概要
ActionDispatch::Http::URLモジュールに諸々追加している。
pickup
class << self でクラスメソッドをまとめて定義している。
hostからドメインを取得する。 Array#last は要素数を指定できる。
def extract_domain_from(host, tld_length) host.split('.').last(1 + tld_length).join('.') end
Arrayの添え字部分にlengthを指定することで、部分取得できる
def extract_subdomains_from(host, tld_length) parts = host.split('.') parts[0..-(tld_length + 2)] end
url_for: アプリケーションが参照するURLを生成
http://railsdoc.com/references/url_for
Hash#to_params
ActiveSupportでHash#to_paramsが定義されている。 HashをURLのパラメーター形式に変換する。
Enumerable#collectのendに.で戻り値の配列が参照できるんですね。
class Hash # Returns a string representation of the receiver suitable for use as a URL # query string: # # {name: 'David', nationality: 'Danish'}.to_query # # => "name=David&nationality=Danish" # # An optional namespace can be passed to enclose key names: # # {name: 'David', nationality: 'Danish'}.to_query('user') # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" # # The string pairs "key=value" that conform the query string # are sorted lexicographically in ascending order. # # This method is also aliased as +to_param+. def to_query(namespace = nil) collect do |key, value| unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? value.to_query(namespace ? "#{namespace}[#{key}]" : key) end end.compact.sort! * '&' end alias_method :to_param, :to_query end
path周りのノーマライズ処理は、ActionDispatch::Journey::Route::Utilsに定義されている。
def escape(component, pattern) component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) end
URLに使用可能な文字以外をエスケープする処理 gsubにブロックを渡すことで、置換処理にヒットした部分を使うことができる。
URL#add_anchorのエスケープ処理に使われているので調べてみた。
add_trailing_slash 末尾にスラッシュを追加する処理
queryが入っているかどうかで条件分けしている。
def add_trailing_slash(path) # includes querysting if path.include?('?') path.sub!(/\?/, '/\&') # does not have a .format elsif !path.include?(".") path.sub!(/[^\/]\z|\A\z/, '\&/') end end
normalize_protocol protocolをnormalizeする処理。 case分に正規表現をして、$1で参照している。
def normalize_protocol(protocol) case protocol when nil "http://" when false, "//" "//" when PROTOCOL_REGEXP "#{$1}://" else raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}" end end
url method. コメントの書き方がすごくわかりやすい。
# Returns the complete URL used for this request. # # class Request < Rack::Request # include ActionDispatch::Http::URL # end # # req = Request.new 'HTTP_HOST' => 'example.com' # req.url # => "http://example.com" def url protocol + host_with_port + fullpath end
x_forward_hostの扱いについて
http://qiita.com/mechamogera/items/32db29aa0db91df704ba
port @portが定義されていなければ、ホストデータ等からポート情報を取得するという処理だけど、このbeginの使い方の情報が検索しても見つからなかった。
def port @port ||= begin if raw_host_with_port =~ /:(\d+)$/ $1.to_i else standard_port end end end
ActionDispatch::Http::Headers のコードリーディング
なぜ?
CakePHPを長く使っていて、Railsを趣味でちょいちょい触ってみている状態で、
その差として思いつくのが、
この標準機能の知識と言語の慣れを埋めるために、railsのコードを読んでみるのは面白いんじゃないか?ということで。
プログラムの開始点ということで、ディスパッチ周りから適当にピックアップ。
概要
HTTPヘッダー情報を取得する処理
ActionDispatch::HttpモジュールにHeaderクラスを定義している
pick up
Set#include Setはinclude?をArrayより高速に使えるので使ってる?
http://docs.ruby-lang.org/ja/2.2.0/library/set.html
%W記法でスペース区切りで配列を表現出来る http://qiita.com/mogulla3/items/46bb876391be07921743#2-4
Object#freezeで変更不可に出来る 定数的に表現出来る
文字列の置換 string#tr http://ref.xaio.jp/ruby/classes/string/tr
Headers constructorの引数のrequestが@reqに入れられてるけど、実態が解らない。
@reqの正体は、Rack::Request http://www.rubydoc.info/gems/rack/Rack/Request
Rackとは http://route477.net/d/?date=20080716
今回は偶々わかったけど、実体を見つけるのは結構面倒そう?
alias method の定義 http://qiita.com/akishin/items/41f03a513c7ffd51a014
headerのマージ処理。 dupで複製してから、!付きでマージ。
# Returns a new Http::Headers instance containing the contents of # <tt>headers_or_env</tt> and the original instance. def merge(headers_or_env) headers = @req.dup.headers headers.merge!(headers_or_env) headers end
正規表現について
String#=~ でパターンマッチ http://ref.xaio.jp/ruby/classes/string/match_operator
\A, \z は開始文字と終了文字を表す http://qiita.com/jnchito/items/ea7832df6f64a9034872