おやまのエンジニアリングブログ

某ゲーム開発会社のフルスタックエンジニアしてます

サーバーとクライアントの時刻をシンクロさせる方法

本来ならNTP使ってろって話なんでしょうが、クライアントがNTP使ってる保証無いですし、絶対に合わせる必要があったので今回実装せざるを得なかったので実装しました。

まずサーバー側の時刻を正とした場合、一番単純な実装方法として、サーバーの時刻をクライアントに送信して、サーバーの時刻からクライアントの時刻を引いた数をクライアントの時刻に足すという実装が有ります。Javascript 辺りで書くと以下の様な感じです。

$.get("/echo/json", function(data, status, jqXHR) {
    var clientTime = new Date();
    var serverTime = new Date(Date.parse(jqXHR.getResponseHeader("Date")));
    var diff = clientTime - serverTime;
    var accurateTime = serverTime + diff;
    console.log(new Date(accurateTime));
});

ただ↑の実装だと、サーバーが時刻を生成してからクライアントに届くまでの時間が計算されていないので、クライアントの時刻が数ミリ秒~数秒遅れます。

これが結構難しい話で、そもそもお互いの時間がズレているので、サーバーからクライアントに届くまでに掛かった時間を計算することができません。

ただ、クライアントが通信を開始してから通信が完了する時間と、サーバーが通信を受け取ってから、応答を返すまでの時間は図ることができます。その2つを組み合わせることで通信全体に掛かった時間は割り出せます。それを2で割ると、サーバーとクライアント間の通信に掛かった時間がとれます。実装すると以下の様なコードになります。

var clientStart = new Date();
$.get("/echo/json", function(data, status, res) {
    var clientEnd = new Date();
    // ↓ サーバーの処理開始時刻を何かしらの方法で取得
    var serverStart = new Date(Date.parse(data.startTime));
    var serverEnd = new Date(Date.parse(res.getResponseHeader("Date")));
    var diff = clientEnd - serverEnd;
    var delay = ((clientEnd - clientStart) - (serverEnd - serverStart)) / 2;
    var accurateTime = new Date(new Date().getTime() + diff - delay);
    console.log(clientEnd);
    console.log(serverEnd);
    console.log(accurateTime);
});

実はこの実装も完璧ではありません。↑の計算は、送信した時に掛かった時間と応答した時に掛かった時間が同じであることを前提にした計算だからです。

更に正確に時刻を合わせる場合、NTPの実装のように、複数台のサーバーから時刻を取得して↑の計算を帝王するみたいにするともう少し正確な時刻が取れるようです。(自分はそこまでは実装しませんでしたが・・・)

全ての計算をラッピングしたクラスを実装するといい感じに抽象化できるのではないでしょうか。 自分はこれをC#(Unity)で実装したので、そのソースコードは後ほどGithubに載せる予定です。

リソース
Network Time Protocol - Wikipedia, the free encyclopedia

UPDATE: Gistのコード追加 (コンバーター付き)

Gist - taka-oyama / ServerTime.cs

Rails のおすすめ Gem 一覧 2014

自分にとって、Railsで開発する時の喜びの一つは、rubygemsを使ってビジネスロジックの実装が安易にできることです。過去にいくつものプロジェクトを立ち上げてきましたが、必ずお世話になるgemがいくつかあります。今日はそのgemたちを紹介します。

better_errors
デフォルトのエラー画面より読みやすく、変数なども安易に確認できるようになる。
また、binding_of_callerというgemを入れると、エラー画面内で変数のインスペクションもできるようになる。

meta_request
Chromeプラグインをインストールするだけで、サーバー側の出力内容をブラウザのデバッグ画面でみることができるようになる。

annotate
このgemを入れた後で、db:migrateを行うと、自動的にDBのテーブルのカラム情報をモデルファイルの中に出力してくれる。

quiet_assets
アセットパイプラインに対するログの出力を停止させることができる。アセットのログを読み通す手間がなくなるのでデバッグが捗る。

default_value_for
Active Modelに対してデフォルト値を設けることができる。マイグレーションファイルのほうで設定してもいいのだが、ロジックはすべてモデルに集約しておいた方が可読性が上がるのでそういう意味でもおすすめ。

whenever
cron jobsをRails側で書くことができる。cronjobの設定はデプロイ後によく忘れたりするのでRails側で設定。いろんなところに情報が拡散し保全性が下がってしまうのを防いでくれる。

paranoia
物理削除を論理削除にすることができる。論理的に削除された情報もスコープでフィルターできるように設計されているので使い勝手がよい。

active_admin
管理画面といえばまずこれ。簡単に管理画面を作成が可能。ドキュメント不足なのが難点。

finite_machine
有限状態機械(Finite State Machine)を実装するときに使う。state_machineよりシンプルで読みやすく実装も安易にできる。

capistrano-rails
サーバーにデプロイするときに使う。優秀で Jenkinsさんと組み合わせると最強。エンジニアなしでデプロイが比較的簡単に実現できる。

rspec-rails
テストといえばこれ。factory_girl_railsと組み合わせると、とても読みやすいテストが書ける。

devise
Railsで認証を安易に実装できるようにしてくれる。多くの方式をサポートしている。
※コードが非常に丁寧に書かれているので、Plugin作ったりするときに参考になる。

Rails PluginでRspecを使う環境の構築方法

Railsプラグインのテストを書くのに、rspecを使いたかったので実装方法をまとめました。

1. まず、railsのジェネレーターを使ってファイルを一式作成。
とりあえず、クラス名はMyGemにします。

rails plugin new my_gem --skip-test-unit --dummy-path=spec/dummy

2. my_gem.gemspec のTODOと書いてある部分を埋めて以下の行を追加する

s.add_development_dependency "rspec-rails"

3. 以下のコマンドを実行

bundle install
cd ./spec/dummy
rails g rspec:install
ln -s ../../spec

4. 自動生成されたspec/spec_helper.rbの以下の部分を

require File.expand_path("../../config/environment", __FILE__)

以下に変更

require File.expand_path("../dummy/config/environment", __FILE__)

5. rakeコマンドでテストが実行されるようにRakeFileに以下を追加する

require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)
task :default => :spec

6. rakeコマンドを叩いて以下の情報が出れば成功

No examples found.

Finished in 0.00005 seconds
0 examples, 0 failures

7. あとは./rspec/配下もしくはサブディレクトリに *_rspec.rb という名前でテストファイルを配置すれば、それが実行されるはず。

Rails Engineを使う場合は、generatorをrspecに変更した方がいいかも

module MyGem
  class Engine < ::Rails::Engine
    isolate_namespace MyGem

    config.generators do |g|
      g.test_framework :rspec
    end
  end
end

コードレビューの時に使う略語集

最近会社で大量のコードレビューをしているのですが、似たような内容を一行一行タイプしている事がアホらしくなってきています。もっと良いやり方はないかなーと考えていたら大学生の時に最初に受けたプログラミングの授業を思い出しました。その授業では、教授がコードを採点するときに、減点箇所の指摘内容を略語を使って表現していました。当時は、あまりなんとも思ってなかったのですが、略語にすればキーを打つ回数が減るので書く側は楽ですし、伝えたい内容の結論が最初に書かれているので読む方も一度略を覚えてしまえば、楽になるのではないだろうか。
仮説なのであまり説得力がありませんが、自分は近日チームと相談して実践してみようと思います。以下に使う予定の略語一覧を開示しておくので興味のある方は参考にしてみてください。

略語 意味(英語) 意味(日本語)
ANC Avoid Nested Conditions ネストした条件は省く
BCN Bad Class Name クラス名がいけてない
BMN Bad Method Name メソッド名がいけてない
BVN Bad Variable Name 変数名がいけてない
IND Indentation Inconsistent インデントが一貫じゃない
MM Mismatch 書いてあることとやっていることが違う
NETC Not Enough Test Cases テストケースが足りない
NG Not General 一般的ではない
TC Too Complicated 複雑すぎ
TE Throws Exception エラーが発生する
TMRC Too Much Repeated Code 類似コード多すぎ(コピペ)
UIV Unnecessary Instance Variable 不要なインスタンス変数
VS Violates Specifications 規約・仕様違反

多言語対応ウェブアプリを作る前に知っておきたい事

はじめに

最近仕事で、他言語対応(主に英語)のアプリを初めて作った訳ですが、事前準備を色々したにもかかわらず、翻訳、コーディング、サーバー構築などあらゆる分野で大変苦労したので気をつけたい点を共有します。ちなみに自分が担当した部分は、日→英→独仏のみで RTLなどの言語は含まれません。

押さえておきたい単語
  1. 他言語対応のことは一般的に i18n(internationalization) という。
  2. 地域化のことを l10n(localization) という。
  3. サマータイムは、デイライトセービングタイム(DST)ともいう。
プログラミング
  1. 日付のフォーマットは、国毎に表現が違う。Railsの場合、I18n.lで自動変換可能。
    • 【日】2014年3月10日
    • 【米】03/10/2014
    • 【独】10.3.2014
  2. 国によって小数点や千単位の区切り方が違う。バリエーションが多すぎるので、ライブラリ使っての対応が必須。Railsの場合、number_with_delimiterで実現可能。気になる人に→バリエーションリスト
  3. 英語は単数形(Singular)、複数形(Plural)、所有格(Possessive)などで単語が変わる。これらはとにかくルールが多い上に例外も沢山あるのでライブラリ必須。Rails の場合、rails-i18n で対応可能。(Possessive対応には possessive gem)
  4. 米国だけメートル法ではなくヤード・ポンド法、摂氏ではなく華氏。Railsの場合、rails-unitsで変換可能。
  5. 英語では例えば、「リンゴ100個」を翻訳した場合、「100 Apples」となり、単位が主語の先頭にくる+助数詞が無い。単位と主語を1つの翻訳データとして扱わないと英語で表現できなくなる。
    • 悪い例)コード: t(".apple") + count
    • 良い例)コード: t(".x_items", item: t(".apple"), count: count)
          翻訳の定義(英): x_items: "%{count} %{item}"
          翻訳の定義(日): x_items: "%{item}%{count}個" 
  6. DBデータは翻訳部分だけ切り出して別のテーブルを作る。国別にサーバーを持つなら、国毎に翻訳データを編集することもでるが、更新作業にかかるコストが高い上に間違えやすいのでお勧めしない。Railsの場合、Globalizeがお勧。
  7. Javascriptを使う場合、サーバー側の文字列を直接持たせないようにする。handlebarsunderscoreの_.templateを使って、なるべくロジックをHTML側に書く。
  8. どうしてもJS側で出力する場合、クオテーション(’)やダブルクオテーション(”)は、エスケープして出力させる必要がある。よく英語圏ユーザーの入力データーで使われているので、要注意。Railsの場合、<%=j %>で簡単にエスケープ可能。
  9. 英語は基本日本語より文字が長くなる。ドイツ語はもっと長くなる。事前にこうなる事を想定してボタンなどの横幅に余裕を持ったデザインにしておく事が理想的。だが、完全に事前把握するのは困難なので、崩れたデザイン部分は後追いで、CSSでルールを上書いて対処する。例えば、.titleというルールがあるとする。そして英語版でルールを上書きしたいとする。その場合、body などの要素のヒエラルキーのトップに、.lang-[言語] (例えば.lang-en)などのクラス名を追加して、.lang-en .titleというルールを定義することで、英語版専用のプロパティを適応する事ができる。
  10. あまりに表現が苦しい時は、文字ではなく画像で対応するのもあり。画像にしたら、配置やフォントを自由に行えるので便利。これを実現する場合、言語専用パスを画像にも設ける必要有り。
  11. ユーザー入力に対するNGワードフィルタなどは、言語毎に特徴が異なるので、慎重に導入する必要がある。下手するとフィルタしたくない言葉まで大量にフィルタしてしまう事になる。
システム設定
  1. アセット(画像、CSS、JS)はなるべくAkamaiなどのCDNサービスを使う。理由は、日本から米国までの通信は~200msほどかかる。欧州までは、~300msほどかかるため。
  2. 時間の保存には必ずUTCを必ず使う。間違っても米国とかDST(サマータイム)を使っている国に設定してはいけない。例えば、サーバーの設定をカルフォルニアに設定していると、3月9日から11月2日までは、時差が-7時間だが、それ以外の時は時差が-8時間になるので、時間の計算をする過程で計算がズレる可能性がある。
  3. サーバーのシステム設定でもUTCを使う。最悪でもDSTを使わない地域に設定しておく。これがDST有りの地域だとcrontabがDSTの切り替わりのタイミングでおかしな動作をする。crontabの仕様としては、1時間以上の感覚で実行されるジョブはDSTで時間が進んだ時には即実行され、時間が遅れた時には2度実行されないようになっていて、1時間以内の感覚で実行されるジョブは普通にスケジュールされる。詳しくは crontab の man page
  4. サーバー、ミドルウェア、アプリケーション全てのタイムゾーンを一致させる。一つでも違う値を使っていると運営がカオスになる。後で発見しても移行後の動作確認が地獄に。
  5. MySQL限定】コラムの utf8 ではなく utf8mb4に設定する。MySQLのutf8は 3バイトまでしか対応していないので、一部の絵文字などに対応できない。ちなみになぜ絵文字対応?と思うかも知しれないが、最近は海外のアンドロイド端末で絵文字がデフォルトで使えるらしい。ちなみにutf8mb4は、MySQL 5.5.3以降のみ対応。
その他豆知識
  1. 国際化の略である i18n は、iとnの間に18文字あるからi18nと書くらしい。
  2. 同じ英語圏でも米国・英国・豪州等で表現や書き方が違う。スペイン語も同じ。
  3. アメリカ、カナダは西海岸と東海岸で3時間の時差がある。
  4. カナダは英語とフランス語、両方とも公用語
  5. 米国、カナダ、メキシコだけ印刷用紙のサイズが違う。
  6. 西洋言語の明朝体は嫌われる。
最後に

如何でしょうか。
自分は上記の内容を把握できずに見積もりを立てたのでプロジェクトがデスマーチとなってしまいました。
皆さんも見積もりを立てる時はお気をつけて。

プログラマーにおすすめする Sublime Text 3プラグイン ベスト5

もうずいぶん前から使っているSublime Textですが、最近プラグインを使って作業を効率化することにハマっているので、現在使っている便利なプラグインを紹介します。

1. Package Control

絶対外せないのがこれ。これは他のプラグインのインストール・アンインストールを安易に行えるようにしてくれるプラグインです。インストール後、⌘(Ctrl)+Shift+Pを押して"package control"と入力すると色々オプションが出てきます。オプションの中から"Install Packages"を選択するとSublime Text用のプラグインの 検索→インストール が簡単にできます。
f:id:toyama4649:20140309005518p:plain

2. Git

git を使ってソースコード管理している事前提ですが、⌘(Ctrl)+shift+p を使って git の操作ができるようになります。
f:id:toyama4649:20140309020638p:plain
また、ステータスバーでブランチが確認できるのもうれしいです。
f:id:toyama4649:20140309020644p:plain

3. Git Gutter

gitで最後にコミットしたものからファイルのどの行が変更されたかを左の端のガターエリアにアイコンとして表示してくれるプラグインです。変更内容を把握したり、変更内容を元にもどしたりするときにとても便利です。一度慣れると手放せません。
f:id:toyama4649:20140309005308p:plain

4. TrailingSpaces

行末にうっかり入れてしまったスペースや改行した際にエディタの補完機能などで勝手に入ってしまったスペースやタブをピンク色にハイライトして知らせてくれます。行末のスペースの削除だけされているコミットなどがなくなり、無駄なコミットがなくなってスッキリします。デフォではオフになっていますが、セーブした時に自動的に削除してくれるオプションなども設定できたりします。
f:id:toyama4649:20140309003743p:plain

5. SublimeFileOpTabContextMenu

タブに対して、操作系のメニューを追加してくれるプラグインです。Goto Anything で開いたファイルは開いたときに左パネルでそのファイルを選択してくれませんが、このプラグインで追加される Find In Sidebar を使えばそれができるようになるので便利です。
f:id:toyama4649:20140309003539p:plain

他にも言語別の便利なプラグインは色々あるので、Package Control の検索でお好みのプラグインを探してみる事をお勧めします。

Turbolinksを実装するにあたっての注意点

はじめに

Rails 4になってTurbolinkspjaxなどの非同期通信を使ってページを更新する方法が広く知られるようになりました。自分も積極的にプロジェクトに取り込んだのですが、結構癖があり、実装するにあたって色々考慮が必要だったのでその実装方法をまとめておきます。

ページ遷移後に$.readyが実行されない

jQueryの.readyが実行されないというのがありますが、実はjquery-turbolinksというgemをGemfileに加えて、追加されたファイルをJavascriptマニフェストファイルに加えるだけで解決するので特に問題ではありません。

グローバルスコープがクリアされない

技術の特性として、コンテンツ部分だけを置き換えているので、Javascriptのスコープはページ遷移をしてもクリアされません。なので、グローバルスコープに関数や変数を定義し続けると、どんどんメモリーが消費されますし、変数の上書き等が原因で想定しない動作をする危険性がでてきます。また、bodyなどにイベントをバインドするコードがページ内にある場合、ページ遷移をする度に実行されるイベントの回数が増えてしまいます。以上のような問題を回避するためには、即時関数等を使ってページ内に埋め込まれているJavascript処理のスコープを閉じれることが重要です。そうすることで、ページから離れた時にGC(Garbage Collection)の回収対象にすることができ、GCが実行されたタイミングでメモリーが解放されます。

setTimeoutとsetIntervalが破棄されない

通常の画面繊維を行う場合、画面が切り替わった時点でsetTimeoutとsetIntervalは勝手にクリアされますが、turbolinksを使った場合は、これがクリアされません。なので、非同期でページが切り替わるときにタイマーをクリアする仕組みが必要です。これを実現する方法は色々あるともいますが、とりあえず実装方法を2つ紹介しておきます。まず1つ目は、自分でタイマーidを管理し、ページが切り替わる時のイベントハンドラーが呼ばれた時にクリアするコードを仕込む方法です。実装がシンプルという利点がありますが、各ページのすべてのタイマーを個別管理するという欠点があります。実装は以下のような感じになります。

var intervalId = setInterval(function() {
  console.log(1);
}, 100);

$(document).on('page:change', function() {
  clearInterval(intervalId);
});

もう一つの方法はsetTimeout/setInterval/clearTimeout/clearIntervalを書き換えてしまう方法です。各ページ固有のJavascriptに対して、turbolinksの存在を意識しなくてよいという利点があります。また、turbolinksを切ってもちゃんと動作する事が保証されるという利点もあります。しかしデフォルトでブラウザに入っている関数を上書いてしまうので、実装するときには細心の注意が必要です。実装例は以下の通りです。

var intervals = {};
var _setInterval = window.setInterval;

window.setInterval = function(fn, interval) {
  fn.intervalId = _setInterval(fn, interval);
  intervals[fn.intervalId] = fn;
  return fn.intervalId;
};

var _clearInterval = window.clearInterval;
window.clearInterval = function(intervalId) {
  _clearInterval(intervalId);
  delete intervals[intervalId];
};

$(document).on("page:change", function() {
  // clear all timers in queue
  for(var intervalId in intervals) {
    _clearInterval(intervals[intervalId]);
  }
  intervals = {};
});

※ 上の例はintervalの方だけカバーしています。timeoutも同様の実装が必要です。

こうすることで普通のページ繊維とほぼ同じ挙動になります。ですが、上書きされる前の状態で定義されたタイマーはページ遷移でクリアされなかったり、ライブラリなどで自動でスタートされるタイマーなどがこの中に含まれてしうとページ遷移時に消えてしまうので注意が必要です。利点が大きい分、欠点も大きいのがこの実装の特徴です。

通信中の非同期通信が止まらない

この問題は、ちゃんとハンドリングしていないと非常に厄介です。なぜならこれはタイミングの問題なで毎回発生する訳ではないからです。本来ページ遷移が行われた場合、通信中の非同期通信は、すべてキャンセルされます。ということは、当然コールバックは実行されません。しかし、Turbolinksの場合、ページ遷移も非同期通信であるため、ページが変わっても他の非同期通信が切断されませんし、コールバックも実行されてしまい、想定外の挙動をしたり、エラーが出たりします。この問題を回避するために、ページ取得後に必ず、通信中の非同期通信をすべてキャンセルする仕組みが必要となります。
以下が実際に実装してみたコードをシンプルにまとめたものです。

var ajaxCalls = {};
var uniqId = (function() {
  var id = 0;
  return function() { return ++id; };
}());

$(document).ajaxSend(function(e, xhr) {
  xhr.ajaxId = uniqId();
  ajaxCalls[xhr.ajaxId] = xhr;
});

$(document).ajaxComplete(function(e, xhr) {
  delete ajaxCalls[xhr.ajaxId];
});

$(document).on("page:change", function() {
  for(var id in ajaxCalls) {
    ajaxCalls[id].abort();
  }
  ajaxCalls = {};
});
最後に

如何でしょうか。これでとりあえずTurbolinksを使っていても安定動作を実現できると思います。めんどくさい実装ですが、リワードは結構大きいと思いますので、新規プロジェクトなどで試してみる事をお勧めします。