カニ本読書メモ 1章:テストでコードを駆動する


Rubyベストプラクティス -プロフェッショナルによるコードとテクニック

Rubyベストプラクティス -プロフェッショナルによるコードとテクニック

でかいテストを書いてからとかやるより、小さいステップからイテレーティブにやってくとハッピーだよ

コードの正しさは書いたテストの正しさと同じ程度にしかならない

テスト中にプライベートメソッドをsendで頻繁に呼び出す必要があったらリファクタリングのサイン

と、言うことらしい

TDDの最終成果物→仕様通りにメソッドが動作することを検証可能な、自動化されたセーフティーネット

  • 問題発見、リファクタリング、イテレーティブな設計といったプロセスがTDDの真価
  • 書かれたテストは副作用に過ぎず、TDDの本当の力は最初にテストを書くことで得られる洞察

1個のテストケースに複数のテストを書くより分けた方がよいよー

  • どんな問題が起きたかすぐ分かる
  • 個々のテストをクリーンな環境で実行できる

例外のテストを書いたら、例外じゃない場合のテストも書こう

XMLとか複雑な出力をテストする時はパーサーライブラリ使った方が、テストがはっきりするよ

  • ライブラリがないなら、簡単なフォーマットだったらパーサー書いた方がよいよ
  • パーサー書くのに時間かかりすぎるなら、期待する出力をファイルにしといてdiffした結果を出すと良いよ

まとめ

  • 解決策をテストするのが難しいと感じたら、設計に柔軟性が足りず、リファクタリングや外部からの利用が難しいというサインかも
  • 小さな機能毎にテストを書いてコードをリファクタリングすることで、複雑すぎるコードにありがちな落とし穴を回避できる
  • どうしてもいろんな事情からテスト書きづらい場合もあるけど、それでも無いよりは少しでもあったほうがハッピーだよ!

感想

テストってこれらを保証する為に書いているイメージが強いけど

それだけじゃなくもう一歩進んで、テストを軸する実装を行うことで、より洗練された設計のコードになるよという話と解釈

node.js を軽くいじったメモ

hello world

$ node
> console.log("hello world");
hello world
undefined

サーバ起動

  • createServer にレスポンスを返すfunctionを渡す
  • res.writeHead でレスポンスヘッダを渡す
  • res.write でレスポンスボディを渡す
  • res.end で終了
var http = require('http');
var server = http.createServer(
  function(req, res){
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('Hello World\n');
    res.end();
  }
);
server.listen(3000);

> node server.js


http://localhost:3000 にアクセスすると、
hello world が表示される

Socket.IO を使ってみる

Socket.IOって何?

いろんなブラウザのwebsokectの実装の違いを吸収してくれるライブラリ

ローカルディレクトリにインストール
npm install socket.io
サンプルコード書いてみる
クライアント
<html>
<head>
<meta charset="utf-8">
<!-- クライアント側のsocket.io.js が違う所にあった -->
<script src="./node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost:3000');
  // socket.on が送信 この場合、newsイベントを受信している
  socket.on('news', function (data) {
    console.log(data);
    // socket.emitが送信 この場合、my other event に、オブジェクトを送信している
    socket.emit('my other event', { my: 'data' }); 
  });
</script>
</head>
</html>
サーバ
var io = require('socket.io').listen(3000);

io.sockets.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) { console.log(data); });
});
起動
$ node socket.js


クライアントを開くと console に object が届いた

Object { hello="world"}


その後サーバ側でデータを受け取っている

   debug - websocket writing 5:::{"name":"news","args":[{"hello":"world"}]}
{ my: 'data' }


これだけじゃつまらないので、1個のブラウザが投げたイベントを他のクライアントが出すのを作る

soket.broadcast.emit を使う


soket.broadcast.emit は、onイベントの中で使うと、
イベントを投げたクライアント以外の全クライアントにemitするメソッド

クライアント
<script>
  $(function(){
    var socket = io.connect('http://localhost:3000');
    socket.on('message', function(data){
      console.log(data);
      message(data);
    });
    socket.emit('login', window.navigator.userAgent);


    function message(msg){
      $("#message").html($("#message").html() + '<br>' + msg);
    }
  });
</script>
サーバ
var io = require('socket.io').listen(3000);

io.sockets.on('connection', function (socket) {
  socket.on('login', function (data) {
    console.log(data);
    // イベントを送ったクライアントに返信
    socket.emit("message", "welcome " + data);
    // イベントを送った以外の全クライアントに送信
    socket.broadcast.emit("message", data +" が来ました");
  });
});


2個ブラウザを立ち上げると…



わかりにくいけど、上がFireFoxで、下がChrome


まずはそんなところで

カニ本読書メモ 2章:美しいAPIを実装する


コードの共通化する時のコツの学べる本はないか? と、相談した時にお勧めされたので読んでみました

Rubyベストプラクティス -プロフェッショナルによるコードとテクニック

Rubyベストプラクティス -プロフェッショナルによるコードとテクニック

本の目的

熟練したRubistたちが当たり前と考えている、プラクティスやイディオムを明らかにすることで
Rubyという言語の根本的な理解を高めること

2章 美しいAPIを設計する

  • メソッドにおける順序付き引数の数は最小限にする
  • メソッドにデフォルト値を持つパラメータが複数あるなら、optionsハッシュによる擬似キーワード引数の利用を検討する
  • 同時に複数の引数処理を扱う必要がある場合、splat(*) 演算子を使う
  • 必須のパラメータがあるなら、それはoptionsハッシュに入れない

2.3 コードブロック

ブロックの使い道

  • each メソッドを実装して、Enumerable を include すれば、組み込みコレクションで使える機能が使えるようになる
  • 後処理/前処理の抽象化
  • 動的コールバック
    • &block を配列とかに保存しておいてコールバックが必要な時に実行したりする
  • instance_evalでインターフェースをシンプルにする
Server.run do
  handle(….)
  handle(….)
  …
end

class Server
  def self.run(&block)
    server = Server.new
    server.instance_eval(&block)
  end
end


yeild と、block.call の戻り値は、与えられたブロックの戻り値とおんなじにしよう!!

2.4 驚かせないこと

  • atter_reader, atter_writer, atter_accesser を使う
    • get_something, set_something より、something, something= を使う
  • メソッドに疑問符をつけた場合、ブール値を返す
  • 感嘆符は破壊的であるというより、注意すべきメソッドという意味で使う
  • カスタム演算子
    • append, add は、<<, + にする
スペースシップ演算子 <=>
  • 現在のオブジェクトと、比較対象が
    • 小さい : -1
    • 同じ : 0
    • 大きい : 1
  • スペースシップ演算子を実装して、Comparableをincludeすると、組み込みの比較メソッドが使えるようになる

まとめ

  • コードをRubyらしくする最善の方法は、できるかぎり基本的なRubyオブジェクトと同じようにすること

感想

  • カスタム演算子とか使ったこと無かった。
    • C++で書いてた時は積極的に使ってたので、使い道がある時があったら使用を検討したい
  • get_something やりがちなので気をつける
  • ブロックはいろんな使い道がある

WebSocketってなあに?

ざっくりと

  • クライアント、サーバ間で双方向通信を行うためのプロトコル
    • connectionをはりっぱなしにして、双方向の通信ができる
  • 1つのクライアントが何度もリクエストを投げないので、webサーバの負荷が減る
    • 擬似的にAjaxでやろうとすると、クライアントが何度もリクエストを投げるので、通信やサーバの負荷が上がる
  • HTTPとくらべて軽量のプロトコルなので、通信量が少ない
  • 元々HTML5の一部だったけど、現在は独自のモジュール扱いらしい

ブラウザの対応状況はどんな感じなの?


実は何回かプロトコルが細々と変わってて、色々ややこしい事になってるので一覧表を見てくださいな


node.js ならば、socket.ioっていうライブラリを使えば、その辺の問題をなんとかしてくれる

cometって昔あったよね?

  • あれは、擬似的に双方向通信を可能にしてた
  • リクエストを受けてもすぐに情報を返さないで、本当に情報を返したい時に返す
  • 接続方法は、ストリーミングと、通信リソースの軽減を目指した、ロングポーリングがメジャー
ストリーミング
  • 接続後、データの送信を完了させないで、必要に応じてデータを送信する方法
ロングポーリング
  • 接続後、イベントが発生してデータが来ると接続を遮断して、クライアントはpushが欲しい時に再度接続を行なって待つ方法
    • ストリーミングよりもコネクションを節約できる
  • どちらにせよ無理やり従来のブラウザで双方向通信を可能にしたので、ブラウザ側やサーバ側で色々問題があったらしい
    • HTTPコネクションを維持し続けるので、通信リソースを食う
    • 同時接続数やタイムアウトの時間がブラウザ毎にまちまちなので、その辺を意識したクライアント、サーバ側の実装が大変


その辺の問題点を解決するため、

  • 無理やりHTTPプロトコルを使わないで、軽量な独自プロトコルを作ろうぜ!
  • そんでブラウザでも標準的に使えるようにしようぜ!

っていうのが、websocket

SPDYとはどう違うの?


そもそもの目的が異なる
詳しくは、こっち読んで

websocket = node.jsって印象だけど、サーバ側はnode.jsじゃないとダメなの?


当然ながらプロトコルが実装されたモジュールがあるなら、どんなのつかっても構わない


ざっくりとそんな感じで

WebSocketと、SPDYってどう違うのよ?


どっちもクライアント、サーバ間でコネクションを確立して、双方向通信が可能なので、
SPDYって、WebSocketの上位互換なの? みたいな印象が強かったけど違うらしい

目的がそもそも違う

  • WebSocket → サーバ、クライアント間での双方向通信を可能にしたい
  • SPDY → 従来のHTTP通信って遅いから早くしたい

できることは似てる

  • WebSocket → サーバ、クライアント間でコネクションを1本確立して、その中でメッセージの送受信を行う
  • SPDY → サーバ、クライアント間でコネクションを1本確立して、その中でリソースの受け渡しを行う

どう違うの?

WebSocket
SPDY
  • 1本のコネクションで、
    • リソース(画像とか、cssとか)を優先度をつけて受け渡すことで、従来より高速な通信がしたい
    • クライアントがhtml読んでから、必要なリソースを要求するんじゃなくて、サーバ側がpush通信で先に送りつけることで高速な通信がしたい


なので、

  • 通信の高速化をしたい → SPDYを使う
  • 双方向通信をしたい → WebSocketを使う


という事になるっぽい
やれることは似てるけど、目的が違うと

実装方法の違い
  • WebSocket → 何を送受信するか、クライアント、サーバ側それぞれで実装する必要がある
  • SPDY → 単にリソースを単一コネクションで渡すだけなら、ブラウザ側の実装は不要。サーバ側もSPDYモジュールを追加するだけ


目的がそもそも違うから実装方法も違う

参考

Lionにnode.jsの環境を作ってみる


前に1回つくったけど、なんか色々有耶無耶だったので、もっかい色々理解しながら作ってみる

目標


nvmを使って、node.js や npmの管理をできるようにする

nvmのインストール

nvmって何?


node.jsのバージョンマネージャ。ruby における rvm 的なもの

nvm - Node Version Manager - Simple bash script to manage multiple active node.js versions.
インストール


オフィシャルに書いてあるやり方でインストール

$ curl https://raw.github.com/creationix/nvm/master/install.sh | sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1202  100  1202    0     0    418      0  0:00:02  0:00:02 --:--:--   571
Cloning into '/Users/kasei_san/.nvm'...
remote: Counting objects: 557, done.
remote: Compressing objects: 100% (328/328), done.
remote: Total 557 (delta 290), reused 470 (delta 218)
Receiving objects: 100% (557/557), 76.38 KiB | 77 KiB/s, done.
Resolving deltas: 100% (290/290), done.

=> Appending source string to /Users/kasei_san/.bash_profile
=> Close and reopen your terminal to start using NVM
.bash_profileに書いたよ! とか言われたので、設定を.zshrcに移す
# NVM
# 複数マシンでzshrcを共有しているので、特定マシンでだけ動作するようにした
if sw_vers | grep 10.7 ;then
  [[ -s /Users/kasei_san/.nvm/nvm.sh ]] && . /Users/kasei_san/.nvm/nvm.sh # This loads NVM
fi
シェルを1個起動してみる
$ nvm

Node Version Manager

Usage:
    nvm help                    Show this message
    nvm install [-s] <version>  Download and install a <version>
    nvm uninstall <version>     Uninstall a version
    nvm use <version>           Modify PATH to use <version>
    nvm run <version> [<args>]  Run <version> with <args> as arguments
    nvm ls                      List installed versions
    nvm ls <version>            List versions matching a given description
    nvm ls-remote               List remote versions available for install
    nvm deactivate              Undo effects of NVM on current shell
    nvm alias [<pattern>]       Show all aliases beginning with <pattern>
    nvm alias <name> <version>  Set an alias named <name> pointing to <version>
    nvm unalias <name>          Deletes the alias named <name>
    nvm copy-packages <version> Install global NPM packages contained in <version> to current version

Example:
    nvm install v0.4.12         Install a specific version number
    nvm use 0.2                 Use the latest available 0.2.x release
    nvm run 0.4.12 myApp.js     Run myApp.js using node v0.4.12
    nvm alias default 0.4       Auto use the latest installed v0.4.x version


インストールできた

とりあえずnodeの最新版を入れたい


いっぱいある

$ nvm ls-remote
v0.1.14 v0.1.27 v0.1.96 v0.2.4  v0.4.1  v0.5.1  v0.6.3  v0.6.16 v0.7.7  v0.8.7  v0.8.20
v0.1.15 v0.1.28 v0.1.97 v0.2.5  v0.4.2  v0.5.2  v0.6.4  v0.6.17 v0.7.8  v0.8.8  v0.9.0
v0.1.16 v0.1.29 v0.1.98 v0.2.6  v0.4.3  v0.5.3  v0.6.5  v0.6.18 v0.7.9  v0.8.9  v0.9.1
v0.1.17 v0.1.30 v0.1.99 v0.3.0  v0.4.4  v0.5.4  v0.6.6  v0.6.19 v0.7.10 v0.8.10 v0.9.2
v0.1.18 v0.1.31 v0.1.100        v0.3.1  v0.4.5  v0.5.5  v0.6.7  v0.6.20 v0.7.11 v0.8.11 v0.9.3
v0.1.19 v0.1.32 v0.1.101        v0.3.2  v0.4.6  v0.5.6  v0.6.8  v0.6.21 v0.7.12 v0.8.12 v0.9.4
v0.1.20 v0.1.33 v0.1.102        v0.3.3  v0.4.7  v0.5.7  v0.6.9  v0.7.0  v0.8.0  v0.8.13 v0.9.5
v0.1.21 v0.1.90 v0.1.103        v0.3.4  v0.4.8  v0.5.8  v0.6.10 v0.7.1  v0.8.1  v0.8.14 v0.9.6
v0.1.22 v0.1.91 v0.1.104        v0.3.5  v0.4.9  v0.5.9  v0.6.11 v0.7.2  v0.8.2  v0.8.15 v0.9.7
v0.1.23 v0.1.92 v0.2.0  v0.3.6  v0.4.10 v0.5.10 v0.6.12 v0.7.3  v0.8.3  v0.8.16 v0.9.8
v0.1.24 v0.1.93 v0.2.1  v0.3.7  v0.4.11 v0.6.0  v0.6.13 v0.7.4  v0.8.4  v0.8.17 v0.9.9
v0.1.25 v0.1.94 v0.2.2  v0.3.8  v0.4.12 v0.6.1  v0.6.14 v0.7.5  v0.8.5  v0.8.18
v0.1.26 v0.1.95 v0.2.3  v0.4.0  v0.5.0  v0.6.2  v0.6.15 v0.7.6  v0.8.6  v0.8.19
安定版はいくつなんだろ?


本家を当たってみる

Current Version: v0.8.20

無い!!

とりあえず、0.8.19入れてみる
nvm install v0.8.19
######################################################################## 100.0%
Now using node v0.8.19
インストール済一覧をチェック
$ nvm ls
current:        v0.8.19

$ node -v
v0.8.19

$ which node
/Users/kasei_san/.nvm/v0.8.19/bin/node


よさそう

デフォルトのnodeを決めたい


オフィシャルに、以下の設定をすれば良いと書いてある

To set a default Node version to be used in any new shell, use the alias 'default':
nvm alias default 0.6


やってみる

$ nvm alias default 0.8.19
default -> 0.8.19 (-> v0.8.19/)


別シェル開いて、バージョン確認

$ node -v
v0.8.19


よさそう

npmについて

npmって何?


node.jsのパッケージマネージャ。ruby における gem 的なもの

Node Packaged Modules
npmのインストール先


npmにはグローバルインストールとローカルインストールがある
グローバルインストールは環境全体に影響して
ローカルインストールはカレントディレクトリにのみ影響するらしい

インストール先の確認
$ npm root -g
/Users/kasei_san/.nvm/v0.8.19/lib/node_modules
$ npm root
/Users/kasei_san/work/node_modules


なるほど
基本的にはローカルインストールですすめることにする

知らなかったコマンド色々


忘れるのでメモ

特定のユーザでコマンドを実行

$ sudo /sbin/runuser -l {ユーザ名} -c "コマンド"

日時を指定してtouch

$ touch -t201301090000 01.txt
$ touch -t201301080000 02.txt
$ touch -t201301070000 03.txt
$ touch -t198001010000 04.txt

$ ll
total 0
-rw-r--r-- 1 kobayashi wheel 0 1 9 00:00 01.txt
-rw-r--r-- 1 kobayashi wheel 0 1 8 00:00 02.txt
-rw-r--r-- 1 kobayashi wheel 0 1 7 00:00 03.txt
-rw-r--r-- 1 kobayashi wheel 0 1 1 1980 04.txt

タイムスタンプを指定して検索(今日が2013/02/08として)

$ find . -name "*.txt" -mtime +30 # 過去31日以上
./02.txt
./03.txt
./04.txt

$ find . -name "*.txt" -mtime 31 # 31日前のみ
./02.txt

$ find . -name "*.txt" -mtime -32 # 過去31日以降
./01.txt
./02.txt

xargsでmvする

GNU Linux

$ find . -name "*.txt" -mtime +30 | xargs mv --target-directory=./test/


Macだと、

$ find . -name "*.txt" -mtime +30 | xargs -J % mv % ./test/

$ ll *
-rw-r--r-- 1 kobayashi wheel 0 1 9 00:00 01.txt

test:
total 0
-rw-r--r-- 1 kobayashi wheel 0 1 8 00:00 02.txt
-rw-r--r-- 1 kobayashi wheel 0 1 7 00:00 03.txt
-rw-r--r-- 1 kobayashi wheel 0 1 1 1980 04.txt