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自体は結構シンプルでわかりやすく、メリットも感じられた。 state周りが、fluxの予備知識が少しあったので、reduxとどのように組み合わせるのかなというのはモヤっとしたけど、一旦放置。

【翻訳】あなたが求めていたリアクティブプログラミング入門 - 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を連携

わからないところは、

Basics | Redux

を見ながら実装。

サーバー連携

サーバー連携のAsync処理はいくつか方法が紹介されていたので少し混乱。

まず単純にaction周りにjQueryAjax処理を書いて機能させてみた。

次にredux-thunkでaction creatorの慣習に従ってAjax処理を呼び出せるように調整

GitHub - gaearon/redux-thunk: Thunk middleware for Redux

次に jQuery Ajaxをfetchに置き換えて完了。

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