« 2010年11月 | メイン | 2011年1月 »

2010年12月28日

Photoshopのスクリプトを気軽に複数ファイルに適用する
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

みなさんこんにちは。11月に入社したデザイナーの935です。

デザインの仕事をしていると「バッチ処理ではできない複雑な作業を複数のファイルに一度に適応したい」という気持ちになることがありませんか?そんなとき私はjsflなんかと比べると若干マイナーな感のあるphotoshopのスクリプトを愛用しています。

でも、photoshopのスクリプトってメニューからだと「一度に開いたファイル一つ」にしか適応できないですよね?そうすると、せっかくスクリプト書いたのに「それってなんか片手おちじゃない?どうせなら一度にやってよ」という気持ちになるわけです。

そこで今回はドロップレットを使って複数のファイルにスクリプトを適用する方法をご紹介したいと思います。

まあ、特定のフォルダのファイルリストを取得してforで回すようなスクリプト書いてもいいんですが、今回はドロップレットを使って気軽にやってみましょう。

1.スクリプトの準備

まず簡単なスクリプトを用意してみましょう。

fName = activeDocument.name.slice(0,1);
colObj = new SolidColor();

if (fName == "a") {
	colObj.rgb.red = 255;
	colObj.rgb.green = 0;
	colObj.rgb.blue = 0;
}else if (fName == "b") {
	colObj.rgb.red = 0;
	colObj.rgb.green = 0;
	colObj.rgb.blue = 255;
}

activeDocument.activeLayer = activeDocument.artLayers[activeDocument.artLayers.length-1];
activeDocument.selection.selectAll();
activeDocument.selection.fill(colObj,ColorBlendMode.NORMAL, 100, false);

ファイル名がaから始まれば背景を赤に、bから始まれば青に塗りつぶすだけのスクリプトです。これを「サンプルスクリプト.jsx」という名前でPhotoshop.exeのあるフォルダの\Presets\Scripts以下に保存します。

Photoshopを起動し直すとスクリプトメニューに「サンプルスクリプト」が追加されます。

1_1.jpg

ついでに、このスクリプトを適応するファイルも用意しました。

yamane1_2.jpg

2.スクリプトをアクションに登録

そしてこれをアクションに登録します。新規のアクションを作ってスクリプトを実行しましょう。

yamane1_6.jpg

これでスクリプトを実行するアクションができました。

3.ドロップレットを作る

先ほど作ったアクションからドロップレットを作成([ファイル]-[自動処理]-[ドロップレットを作成])します。

yamane1_3.jpg

ここでは試しに「サンプルドロップレット.exe」を作成しました。

4.バッチファイルを作る

フォルダを作って処理したいファイルを入れ、さらに3で作ったドロップレットにフォルダを渡すバッチファイルを作ります。

サンプルドロップレット.exe C:\sample\psd

↓サンプルで作ったフォルダの中身はこんな感じです。

yamane1_4.jpg
C:\sample
    \psd
      処理したいファイル群
    サンプルドロップレット.exe
    サンプルバッチ.bat

これで、バッチファイルをダブルクリックするだけでフォルダの中身全部にスクリプトが適用されるようになりました!あとはゆっくりコーヒーでも飲んでいれば、Photoshopがかいがいしくファイル処理をしてくれますよ~。

yamane1_5.jpg

補足

よく考えてみればフォルダをドロップレットにドラッグ&ドロップするだけでもできるんですよね。...ドロップレットなんだから...。

それではみなさんよいお年を!

2010年12月27日

Adobe Illustrator CS5自動化1
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

12月に入社したものです。
得意なアプリケーションはIllustrator
そこで、現在使っているJavaScriptを共有させていただきます(`・ω・´)
基本OSはWindowsですがMacでも起動します。


注意点:ソースコードで使用する"\"マークは"/"に変更しないと起動しません。


それでは、現在使っている自動化スクリプトです↓

 

【他のアプリケーションを起動する】

■プログラム説明
 起動したいアプリケーションのパスをファイルオブジェクトとして作成します。
   

■ソースコード

 fileObj = new File("実行へのアプリケーションパス");
 fileObj.execute();

 ▼使用例(Photoshop)

  fileObj = new File("C:/Program Files/Adobe/Adobe Photoshop CS5/Photoshop.exe");
  fileObj.execute();


■使い方
 1:ファイルメニューからスクリプト→その他スクリプトを実行します。
  (もしくは、Program FilesのAdobe Illustrator CS5内プリセットの中から、
   スクリプトフォルダの中に格納しちゃえばもっと簡単に起動します。)

 2:アドレスブックが起動します。


■ポイント
 Windowsでは.exeファイルを実行することができます。

 

■大きな変更点としては...
 CS4までは座標系の原点が左下でしたが、
 Illustrator CS5から座標系が変更され原点が左上になっています。
 ↑個人的には、ちょっと厄介な変更です(´Д`)


■作ったきっかけ
 ノートPCで作業する際、Illustratorで作業→Photoshop起動がとっても面倒くさかった
 一発でPhotoshopを立ち上げたい!ついでにFlashも!
 ただそれだけ

レディの嗜みについて ~ PHP と Membase と MessagePack と私
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは mayutan です。

ラボブログを書かないとクビだと脅されたのでクリスマスに泣きながら書いておりますが 皆様いかがお過ごしでしょう。
↑(すみませんネタですが、本気にされる方がいらっしゃいそうとの指摘を受けましたので訂正)


今回のレディの嗜みですが、
レディたるもの、キーバリューストアくらい扱えないとなりません!!!!!!111
という事で membase についてご紹介しようと思います。

最近猫も杓子もソーシャルソーシャルわっしょいわっしょい!といった感じで、
これまでのシステムとはまた違った独特の工夫を皆様されていると思うのですが、
memcached を使っている方はかなり多いのではないでしょうか。
ここにプラットフォームから取得した情報や、あまり更新されない情報などを保存して、
DB やプラットフォームへのアクセスを極力減らす、という使い方がメジャーだと思います。
そして、同時に「これ永続化できたらもっと便利なんじゃないの?」って皆思ってるでしょ!
そんなエンジニアの気持ちにこたえてくれるのが、membase!
membase とは、ざっくり説明すると「memcache を永続化したもの」
でも、実際使ってみたらどうなの?

そんなわけで準備編と、実施編の二つに分けてお送りいたしますので、お前らちゃんと読めよ!

まず下準備

  • Windows 7 Professional
  • VMware Player3.1.3
  • Ubuntu 8.04

Apache, PHP, MySQL をインストールします

sudo apt-get install apache2 php5 php-pear mysql-server php5-mysql

続いて membase
ここから拡張子が「.deb」ダウンロードします。
membase グループを作成してから

sudo dpkg -i membase-server-enterprise_x86_1.6.0.1.deb


これでインストール完了!ずいぶん簡単すぎますね。
http://localhost:8091/index.html から確認してみましょう。
かっこいいめむべーすの画面が表示されましたか?
membase の管理画面の真ん中にある start ボタンを押してみます。
なにやら色々設定項目がありますね。
デフォルトのまま次へボタンをがんがん押して完了します。

さて、membase の特徴として memcached protocol を採用していることが有名ですが、
どういう事かと言うと アプリケーションからは memcached と同じように使えます。
memcached が必要ですね。

sudo pecl install memcache
sudo vi /etc/php5/apache2/php.ini
↓追加する
extension=memcache.so

ざっと環境が整いましたね。
こんな感じで次回は今最高になうい!と評判の MessagePack と一緒に実際に使ってみましょう!

Zynga Japanでは積極的にエンジニアを採用しています。
採用ページをご覧になり、興味のある方はぜひご応募ください。

2010年12月24日

EclipseでPHP開発
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

メリークリスマス!11月に入社した関山です。

みなさんはPHP開発に何を使っていますか?
僕はMac用のエディタTextMatemi、コンソールではvimを併用していますが、入社を機にEclipseでのPHP開発にトライしました。

Eclipse for PHP Developersをインストール

Eclipse DownloadsよりPHP開発ツール (PDT) がセットアップ済みのパッケージEclipse for PHP Developersが入手できますので、これを使いました。バージョンは最新のEclipse Helios (3.6.1 SR1)です。


プラグインをインストール

ローカルで開発するだけならそのままでも大丈夫ですが、僕は以下のプラグインをインストールしました。

Babel
インストール直後はUIが英語なので、プラグインで日本語化します。
「ヘルプ」>「新規ソフトウェアのインストール...」を開き、Work with:の欄にEclipse Babel Project DownloadsBabel Language Pack Update Site for HeliosのURLを入力後、ガイドに従ってプラグインをインストール・再起動すればEclipseのUIが日本語になります。
Subversive & EGit
EclipseのGUI上でSubversionやGitのレポジトリ操作ができるプラグインです。
Heliosでは更新サイトが登録済みですが、デフォルトでは無効だったので環境設定の「有効なソフトウェア・サイト」でsubversive/egitの更新サイトをアクティベートした後、「新規ソフトウェアのインストール...」でWork with: の欄からsubversive/egitを選んでインストールします。
Remote System Explorer (RSE)
SSHでリモートサーバーに接続し、リモートサーバー上のファイルを直接編集したりコマンドを実行したりといったことが可能になるプラグインです。Helios PDTではデフォルトで使えるので、追加インストールする必要はありません。
PHP開発ならリモート・システム・エクスプローラーのパースペクティブを使うより、PHPパースペクティブをカスタマイズしてRSEの「リモート・システム」ビューを追加すると、「PHP Explorer」ビューにリモートプロジェクトをインポートできたりして便利です。
Vrapper
Eclipseでvimライクなテキスト編集ができるようになるプラグインです。
Eclipseのキーバインドをvim風にできるVrapperが素晴らしすぎる件について - ゆろよろ日記で知りました。
IDEとしては強力なEclipseですが、テキスト編集機能は単体のエディタに劣る面も少なからずあります。プラグインでそういった欠点をカバーできるのは素晴らしいですね。
MakeGood
PDTでTDDを可能にするプラグインです。テスティングフレームワークはPHPUnitのほか、SimpleTest、CakePHP等をサポートしています。風の噂ではSymfony2にも対応予定とか...
プラグイン自体の他にもPHP側でいくつか動作に必要なものがありますので、詳細は公式サイトをご覧ください。

あとがき

今回はプラグインの紹介のみでしたが、次回は実際にこんな風に使っているよ、ということを書きたいと思います。
また、僕もEclipseを使い始めて日が浅いので、便利なプラグインやTipsを教えていただけると嬉しいです。

余談ですが本日12月24日は弊社で外部からもエンジニアもお招きしてハッカソンを開催します。みなさんも楽しいクリスマスを!

Zynga Japanでは積極的にエンジニアを採用しています。
採用ページをご覧になり、興味のある方はぜひご応募ください。

2010年12月20日

<?php を閉じてみる
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、7月からphpを始めた細川です。

phpの作法で<?phpではじめたファイルを?>で閉じない、というものがあります。これまで、あまり考えずに従ってきたのですが、よく考えてみると「タグを閉じない」というのは直感的には不作法です。

検索して調べてみると、以下のような記事が見つかりました。

http://zendframework.com より引用


PHP コードのみからなるファイルでは、終了タグ ("?>") は決して含めてはいけません。これは必須なものではなく、 終了タグを省略することで、ファイルの最後にある空白文字が出力に影響することを防ぎます。

「決して」含めてはいけない。ということで、わりと大事のようです。
そこで、実際にタグを閉じると何がおこりうるのか調べてみることにしました。

まず、以下のようなphpファイルtest.phpを用意しました。

<html><body><?php
echo 'おはようございます';
?></body></html>

出力結果は以下のようになります。

<html><body>おはようございます</body></html>

次に、別のphpファイルfunctions.phpを用意し、それをrequireで読み込んでみます。

<?php

function hoge()
{
}

?>

functions.phpファイルは?>で閉じていますが、ファイル末尾にはなにもありません。

test.phpを以下のように変更します。

<html><body><?php
require('functions.php');
echo 'おはようございます';
?></body></html>


この場合、出力結果は、以下のようになります。

<html><body>おはようございます</body></html>

この場合には何も余分なものはつかないようです。

次に、閉じタグの後ろに改行をいれてみました。
その場合の出力結果が以下です。

<html><body>おはようございます</body></html>

改行だけでは問題ないようです。

改行を2つ入れてみました。

<html><body>
おはようございます</body></html>

改行が1つ挿入されました。

閉じタグの後ろに4つスペースをいれます。

<html><body>    おはようございます</body></html>

スペースが挿入されました。

最後に閉じタグ無しで末尾に4つスペースをつけた場合です。

<html><body>おはようございます</body></html>

当然といえば当然ですが、大丈夫ですね。

閉じタグ以降に改行だと2つ以上から、スペースだと1つ以上あると、出力に影響するようです。

必ずしも閉じタグをつけたからといって出力に影響が出るとは限らないようですが、事故防止のためには閉じない方が良さそうですね!

Zynga Japanでは積極的にエンジニアを採用しています。
採用ページをご覧になり、興味のある方はぜひご応募ください。

Symfony2のFormsを使ってみる
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは。目下Symfony2を勉強中のtakaです。

Symfony1.4ではsfFormを使った事があるのですが、いくらか苦戦したというか、sfWidgetFormが面倒というか、これは使えば楽なのか?何なのか?良く判らなかったので、今回Symfony2でどのように実装していくのかFormsを試してみました。

まず、Symfony2についてですが、12/01時点ではSymfony2 PR4が公式サイトにて公開されていました。今回はこれを使ってやってみます。環境は適当にCentOS5.5にPHP5.3などなどを入れてcheck.phpが動く事を確認しました。ダウンロードして展開するとsandboxとありHelloアプリケーションが既に用意されています。

Controller名はhello/signupとします。

src/Application/HelloBundle/Resources/config/routing.yml
  1 signup:
  2     pattern:  /hello/signup
  3     defaults: { _controller: HelloBundle:Hello:signup }
  4 
  5 hello:
  6     pattern:  /hello/:name
  7     defaults: { _controller: HelloBundle:Hello:index }

Formの内容Entityを定義するところからはじめます。

src/Application/HelloBundle/Entity/Customer.php
  1 <?php
  2 namespace Application\HelloBundle\Entity;
  3 
  4 class Customer
  5 {
  6     public $name;
  7     private $age = 20;
  8 
  9     public $address;
 10 
 11     public $email;
 12 
 13     public $emails = array('1' => 'a', '2' => 'b', '3' => 'c');
 14 
 15     public function getAge()
 16     {
 17         return $this->age;
 18     }
 19 
 20     public function setAge($age)
 21     {
 22         $this->age = $age;
 23     }
 24 }

顧客情報として名前や年齢などを定義します。publicで定義した項目については自動でアクセッサが用意されるようですが、privateで定義した項目については(ここでは年齢)アクセッサを自前で用意してあげる必要があるようです。

src/Application/HelloBundle/Entity/Address.php
  1 <?php
  2 namespace Application\HelloBundle\Entity;
  3 
  4 class Address
  5 {
  6     public $street;
  7     public $zipCode;
  8 }

住所を定義する場合はこのように分けて、目的毎にしておくと良いようです。また、Formの定義はすべてEntity以下に配置します。

Formの定義が終わったら次はViewです。

src/Application/HelloBundle/Resources/views/Hello/signup.php
  1 <?php $view->extend('HelloBundle::layout.php') ?>
  2 
  3 <form action="#" method="post">
  4     <?php echo $view['form']->render($form) ?>
  5 
  6     <input type="submit" value="Send!" />
  7 </form>

共通部品のlayout.phpとformタグのみと至ってシンプルです。Formオブジェクトをrenderに渡すだけです。Formオブジェクトの中身はControllerの方で実装します。

FormといえばValidationです。Formの定義同様にEntityに配置します。

src/Application/HelloBundle/Entity/Registration.php
  1 <?php
  2 namespace Application\HelloBundle\Entity;
  3 
  4 class Registration
  5 {
  6     /** @validation:Vaild */
  7     public $customer;
  8 
  9     /** @validation:AssertTrue(message="Please accept the terms and conditions.") */
 10     public $termsAccepted = false;
 11 
 12     public function process()
 13     {
 14         // 特にエラーもなければ保存やリダイレクト等の処理
 15     }
 16 }

アノテーションを使って定義します。顧客情報の入力と規約に同意することを定義しています。もし$termsAcceptedがTrueで無い場合はmesseage以下の文字列が表示されエラーとなります。process()メソッドは成功時にPOSTデータの登録なり確認なりの処理を実装します。

Controllerです。ここではsignupActionを実装しています。

src/Application/HelloBundle/Controller/HelloController.php
  1 <?php
  2 
  3 namespace Application\HelloBundle\Controller;
  4 
  5 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
  6 
  7 use Application\HelloBundle\Entity\Customer;
  8 use Application\HelloBundle\Entity\Registration;
  9 use Symfony\Component\Form\Form;
 10 use Symfony\Component\Form\TextField;
 11 use Symfony\Component\Form\IntegerField;
 12 use Symfony\Component\Form\FieldGroup;
 13 use Symfony\Component\Form\RepeatedField;
 14 use Symfony\Component\Form\CollectionField;
 15 use Symfony\Component\Form\CheckboxField;
 16 
 17 class HelloController extends Controller
 18 {
 19     public function indexAction($name)
 20     {
 21         return $this->render('HelloBundle:Hello:index.twig', array('name' => $name));
 22 
 23         // render a PHP template instead
 24         // return $this->render('HelloBundle:Hello:index.php', array('name' => $name));
 25     }
 26 
 27     public function signupAction()
 28     {
 29         $registration = new Registration();
 30         $registration->customer = new Customer();
 31 
 32         $form = new Form('registration', $registration, $this->get('validator'));
 33         $form->add(new CheckboxField('termsAccepted'));
 34 
 35         $group = new FieldGroup('customer');
 36 
 37         $group->add(new TextField('name'));
 38         $group->add(new IntegerField('age'));
 39         $form->add($group);
 40 
 41         /* グループ化したり様々なフィールドを追加できる
 42         $group = new FieldGroup('address');
 43         $group->add(new TextField('street'));
 44         $group->add(new TextField('zipCode'));
 45         $form->add($group);
 46 
 47         $form->add(new RepeatedField(new TextField('email')));
 48 
 49         $form->add(new CollectionField(new TextField('emails')));
 50         */
 51 
 52         if ('POST' === $this->get('request')->getMethod()) {
 53             $form->bind($this->get('request')->request->get('registration'));
 54 
 55             if ($form->isValid()) {
 56                 // Validation True
 57                 $registration->process();
 58             }
 59         }
 60 
 61         return $this->render('HelloBundle:Hello:signup.php', array(
 62             'form' => $form
 63         ));
 64     }
 65 }

基本的なテキストボックスやチェックボックス、数値や日付といったFormコンポーネントが用意されており、適宜useで読み込みます。Formオブジェクトのaddメソッドで簡単に追加できます。FieldGroupは項目をグループ化するもので、Formタグ上ではregistration[customer][name]のようにアクセスできます。

55行目の$form->isValid()はValidationが成功時にTrueとなり処理が続行され、失敗時にはFalseとなりエラーメッセージと共にViewに表示されます。

Formオブジェクトにはエラーであれば$form['name']->getErrors()、Formデータであれば$form['name']->getData()のようにしてアクセスする事が可能です。また、registration[_token]という形でCSRFプロテクトが埋め込まれるようになっていました。

目的毎に切り分けておくことができ、組み合わせて使いやすいのではないかと思います。公式サイトのドキュメントにはこれ以外にも沢山のコンポーネントが用意されている事が書かれてありますので、今後色々と組み合わせて試してみたいと思います。


第1回Symfony2勉強会に引き続き勉強会を行うそうです。興味のある方は参加してみてはいかがでしょうか。

ATND 第2回 Symfony2勉強会 (1/15)


Zynga Japanでは積極的にエンジニアを採用しています。
採用ページをご覧になり、興味のある方はぜひご応募ください。

2010年12月16日

フィードバックサイクルを素早く回すために
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

この記事は、Symfony アドベントカレンダー 2010に参加しています。

こんにちは。murahashi sanemat kenichi です。開発で重要なのはフィードバックのサイクルを素早く回すことですよね。

こうなってくれるとうれしい。
落ちるテストを保存するとred
for-quick-feedback-cycle-red.png
通る実装を保存するとgreen
for-quick-feedback-cycle-green.png

フィードバックサイクル

簡単に達成するために必要なのは次の4つです。
テストコードのグルーピング、ファイル更新検知、ファイル対応関係、screenのstatus line
Symfonyの場合の話を順にみていきます。
一発でグループ分けされたテストコードが走る仕組み
Symfonyは簡単
$ php symfony test:unit #=> 全ユニットテストが走る
$ php symfony test:all #=> 全テストが走る
$ php symfony test:functional NAME #=> NAMEの機能テストが走る
ファイル更新検知をして、それをトリガに別のスクリプトを呼び出す仕組み
Symfonyのアプリルートを watchr で監視して、レシピは symfony-watchr 書きました。
あるファイルを更新したときに、対応するテストがわかりやすい仕組み そしてその逆も
Symfonyは設定より規約 convention over configuration のルールに従っているので、対応するファイルがわかりやすいです。
テスト結果をparseして、GNU Screenのstatus lineに流し込む仕組み
autotest_screen から、screenのstatus lineに表示する部分をはがして screenout に切り出しました。

これで楽しいdeveloper testing lifeがおくれるようになりました。

$ watchr ~/work/symfony_watchr/bin/symfony_watchr.rb


screenoutとwatchrとsymfony_watchr組み合わせた時の欠点

欠点1
ファイルの対応関係を雑に書いたので、粒度が大きすぎて素早いフィードバックが回らなかった。
  • modelかunit以下のファイルを更新したら全unit testが走る
  • test/functional 以下のファイルを更新したら、そのfunctional testが走る
欠点2
コードが悪くて、会社環境だと100MB近くメモリ食ってた
そしてどこがわるいかよくわからない

車輪の再発明感満載なので、どなたか、もっとまともな組み合わせがあれば教えてください。
cpan探したのですがどこみていいかよくわからなくて、みんなどんなの使ってるんでしょう。

というわけで、 rspecautotestautotest_screen の説明をお送りしました。



参考URL



Symfony Advent 2010

Symfony Advent 2010 では12月1日から12月24日までを使って日替わりで symfony でイイなと思った小さな tips から内部構造まで迫った解説などを ブログ記事にして公開していくイベントです。

※Syfony Advent 2010はsymfony好きな有志で集まったチームです。

以上デース

Zynga Japanでは積極的にエンジニアを採用しています。
採用ページをご覧になり、興味のある方はぜひご応募ください。

2010年12月14日

puppetのCustom factを使って、puppetをもっと柔軟に使う
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、satoshiです。

Puppetという、システム管理を自動で行うためのツールがあるのですが、 今回は、これを使ってEC2インスタンスにEBSボリュームをAttachするスクリプトを作成してみました。

前提環境

CentOS5.4

Puppet-0.25.4-1

Facter-1.5.7-1

Puppet, Facter?

Puppetとは、システム管理を自動で行うためのツールです。 Facterは、システム情報(OS, FQDN, IPアドレス)などを収集するRubyライブラリです。 Facterによって取得できる項目のことを fact と呼びます。

下記が詳しいです。
オープンソースなシステム自動管理ツール Puppet

Custom factを使用するための事前準備

下記のサイトを参考に事前設定します。
Docs: Plugins in Modules

puppet master、client両方で下記の設定ファイルに、pluginsync = trueを追加します

puppet.conf:

[main]
pluginsync = true

puppet master上でのcustom factの配置パスは下記の通りです。

fact path:

<MODULEPATH>/<module>/lib/facter

例えば、<MODULEPATH>=/etc/puppet/modulesで<module>=customならcustom factを配置するディレクトリは下記のようになります。

example:

/etc/puppet/modules/custom/lib/facter

AmazonEC2インスタンスにEBS VolumeをAttachする

AmazonEC2インスタンスでEBSVolumeをAttachする場合、Instance-id, available-zoneが必要ですが、そのInstance-id等をFacterで取得するCustom factを利用した manifestを作成してみます。

Ec2 Facter Recipeの配置

AmazonEc2のFacterレシピが下記のリンクにあるので、それをpuppet master上のcustom factの配置パスに作成します。 Amazon EC2 Facter Recipe

path:

/etc/puppet/modules/ec2utils/lib/facter/ec2.rb

ec2.rb:

# Copyright 2008 Tim Dysinger
# Distributed under the same license as Facter
# 27.02.09 KurtBe
# Added a can_connect? function so that this fact can safely be distributed to non-ec2 instances
# otherwise the script hangs if the amazon-ip is not reachable
# 13.03.09 Francois Deppierraz
# Fixed the timeout handling code because which was not actually working. A
# file named "169.254.169.254" was created instead.

require 'open-uri'
require 'timeout'

def metadata(id = "")
  open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read.
    split("\n").each do |o|
    key = "#{id}#{o.gsub(/\=.*$/, '/')}"
    if key[-1..-1] != '/'
      value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read.
        split("\n")
      value = value.size>1 ? value : value.first
      symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym
      Facter.add(symbol) { setcode { value } }
    else
      metadata(key)
    end
  end
end

begin
  Timeout::timeout(1) { metadata }
rescue Timeout::Error
  puts "ec2-metadata not loaded"
end

上記のFacter Recipeから下記のcustom factが取得できます。

facts:

ec2_ami_id
ec2_ami_launch_index
ec2_ami_manifest_path
ec2_block_device_mapping_ami
ec2_block_device_mapping_ephemeral0
ec2_block_device_mapping_root
ec2_block_device_mapping_swap
ec2_hostname
ec2_instance_id
ec2_instance_type
ec2_kernel_id
ec2_local_hostname
ec2_local_ipv4
ec2_placement_availability_zone
ec2_public_hostname
ec2_public_ipv4
ec2_public_keys_0_openssh_key
ec2_reservation_id
ec2_security_groups

EBS Volume作成用ファイル

EBS Volumeを作成/Ec2インスタンスにAttachするためのmanifestファイルとEBSVolumeを作成するScriptのテンプレートファイルを用意します。

path::
/etc/puppet/modules/ebsraid/manifests/init.pp
/etc/puppet/modules/ebsraid/templates/ebs_create_with_attach.rb.erb

EBS Volumeを作成/Ec2インスタンスにAttachするScriptファイルをpuppetのテンプレートファイルで用意します。
テンプレートファイル内に、 <%= custom fact名 %> と記述すればfactの値が設定されます。

ebs_create_with_attach.rb.erb:

#!/usr/bin/env ruby

require 'yaml'
require 'pp'
require 'optparse'
require 'rubygems'
require 'right_aws'
[...]
# Load AWS KEYS
begin
  auth = open(S3CONF_FILE) {|f| YAML.load(f)}
rescue => e
  warn e.pretty_inspect
  warn e.backtrace
  exit 0
else
  ENV['AWS_ACCESS_KEY_ID'] = auth["aws_access_key_id"]
  ENV['AWS_SECRET_ACCESS_KEY'] = auth["aws_secret_access_key"]
  @instance_id = <%= ec2_instance_id %>           # fact: ec2_instance_idを取得
  @az = <%= ec2_placement_availability_zone %>    # fact: ec2_placement_availability_zoneを取得
  ENV['EC2_URL'] = "https://ec2.#{@az.chop}.amazonaws.com"

  @ec2 = RightAws::Ec2.new
end
[...]

例えば、Ec2インスタンスの/dev/sdj, /dev/sdkにEBS Volumeをマウントし、Raid0デバイスを構築する場合の puppetのmanifest例は、下記のようになります。

init.pp:

class ebsraid {
  package { "right_aws":
    ensure => installed,
    provider => "gem";
  }

  # EBS Raid
  file { "/mnt/ebsraid":
    ensure => directory,
    owner  => "root",
    group  => "root",
    mode   => "755",
    require => Package['right_aws'],
  }

  file { "/usr/local/bin/ebs_create_with_attach.rb":
    content => template("ebsraid/ebs_create_with_attach.rb.erb"),
    mode => 755,
  }

  exec {
    'create_ebs_raid':
      command => "ruby /usr/local/bin/ebs_create_with_attach.rb -s 200 -D /dev/md1 -L 0 /dev/sdj /dev/sdk",
      require => File["/usr/local/bin/ebs_create_with_attach.rb"],
      subscribe => File['/mnt/ebsraid'],
      refreshonly => true,
  }

  exec {
    'create_mdconf':
      command     => 'echo "DEVICE /dev/sd[bcdejklm]" > /etc/mdadm.conf && /sbin/mdadm --detail --scan >> /etc/mdadm.conf',
      require     => Exec['create_ebs_raid'],
      subscribe   => Exec['create_ebs_raid'],
      refreshonly => true,
  }

  exec {
    'mkfs.ext3':
      command     => 'mkfs.ext3 -F /dev/md1',
      require     => Exec['create_ebs_raid'],
      subscribe   => Exec['create_ebs_raid'],
      refreshonly => true,
  }

  mount { "/mnt/ebsraid":
    atboot  => true,
    ensure  => mounted,
    device  => "/dev/md1",
    fstype  => "ext3",
    options => "defaults",
    dump    => 0,
    pass    => 0,
    require => [ File['/mnt/ebsraid'], Exec['create_ebs_raid'], Exec['mkfs.ext3'] ]
  }
  # END OF EBS RAID
}

上記manifestを適用するには、適用するnodeでincludeします。

sample:

node /db\d+/ { include ebsraid }

そして、対象のクライアントにてpuppetdを実行すると適用されます。

example:

$ sudo /usr/sbin/puppetd --verbose --no-daemonize --server puppet --onetime

Custom factを自分で作ってみる

Capistranoというデプロイツールがありますが、拡張コードを配置するために必要なLibraryパスを取得するCustom factを作成してみます。

環境が下記の場合、factの値は、 /usr/lib64/ruby/gems/1.8/gems/capistrano-2.5.19/lib が取得できます

OS: CentOS5.4 x86_64
Gem: version 1.8
Capistrano: version 2.5.19

capistrano_libpath.rb:

require 'rubygems'

# fact: capistrano_libpathを設定(capistranoのlibraryの最新バージョンのディレクトリを取得)
Facter.add(:capistrano_libpath) do
  begin
    value = File.dirname(Gem::find_files("capistrano.rb").sort.last)
  rescue => e
    value = nil
  end

  setcode do
    value
  end
end

custom factを作ることにより柔軟にpuppetを使うことができます。 puppetのmanifestを書くのは面倒ですが、一度記述しておけば設定漏れとかがなくなります。

変数名について
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

皆様、こんにちは。
トクヤマです。

今回はTipsでも何かをお伝えするものでもなく、逆に皆様のご意見を承りたいと思っています。

変数名というのはプログラマーやエンジニアで個性が強くでる部分ではないかと思います。
所属する会社やグループで決められた命名規則に沿って変数名を決めている方も多いと思いますが
内心はもっとこんな命名をしたい!なんて思っている方も多いのではないでしょうか。

例えば私は業務や私用を含めPHPを主に使用していますが私用的な変数名の付け方は格納する値の型+格納する値の意味(英名で)にしていて、
それをキャメルケースで命名しています。

ゲーム開発でユーザーの体力を格納する変数名は
$intUserHitPoint
というようにです。

しかしこれで困るのが格納する値の意味が意味合いが変、もしくは冗長的になってまうときです。
例えばユーザーが敵に攻撃を受けて減った分の体力の総計値を格納する変数を作るときです。

$intEnemyAttackDamageUserHitPoint

ついついこういう冗長的な一見して意味がわからない変数名をつけてしまいます。
ここで一気に簡素化して

$intEnemyAttackDamage

で、良いように思いますがこれでは敵から与えられるダメージ値だけという意味に捉えられかねません。

こういったように変数名の命名で一瞬躊躇してしまう事はあるのではないでしょうか。

もちろん仕様書に明確に変数名とその詳細を記載することは当然としても、
やはりプログラムを見てすぐに他の方にもわかるような命名をしたいものです。

皆様はどういった命名方法をなされているのでしょうか。
何かわかりやすい命名をしているなどありましたらご意見やご教示を頂ければ幸いです。

2010年12月 6日

FarmVilleの開発 - FacebookのNo.1ゲームを5週間で作りあげスケールさせた手法
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、五十川です。

2010年12月1日に、ウノウがジンガジャパンとなってから初めてのタイトルとなるファームビレッジがリリースされました。ファームビレッジはFacebookで永らくNo.1の座にあるFarmVilleの日本語版ですが、先月そのFarmVille開発のリーダーで、現在はZyngaの共有テクノロジーをリードしているAmitt Mahajanが来日していました。

今回ご紹介するのは、そのAmittが2010年3月のGame Developers Conference(GDC)で行った、Rapidly Developing FarmVille - How we built and scaled a #1 Facebook game in 5 weeksと題したプレゼンテーションです。今回Amittの許可を得てそのときのスライドの日本語版を作成しましたので、それに解説を加えてご紹介したいと思います。

なお、スライドのオリジナルはGDC Vaultからダウンロード可能で、Rapidly Developing FarmVille Presentation - Amitt's Spaceに掲載されているリンクから直接ダウンロードできます。スライドの言葉は簡潔なものが選ばれる傾向にあり、そのまま訳しても意味が伝わらない場合が往々にしてありますので、日本語版の作成にあたって意図的に言葉を変えている点が多々あります。もし翻訳について疑問に思われる箇所がありましたら、オリジナルを参照ください。

スライド5までは特に解説は必要ないと思われますので割愛します。なお、ここに登場する数字はいずれもこのプレゼンテーションの作成時点、つまり2010年3月の時点のものである点にご注意ください。

スライド6-9 開発の効率化

ここではデベロッパーの作業が遅延する要因とその対処が述べられています。

まず「他のデベロッパーとの協業」ですが、一般的にPC向けのソーシャルゲームでは、クライアントはFlash、サーバは、Zyngaの場合はPHPと、異なるスキルが必要になります。もしデベロッパーがいずれか一方の知識しかない場合は、もう一方のデベロッパーとの協業が必要となり、これが往々にして作業を遅延させる要因になり得ますが、FarmVilleのデベロッパーはいずれもこれら両方のスキルを持っており、こうした問題が回避できたということです。実際この両方のスキルは、現在のZyngaのすべてのデベロッパーに求められるものになっています。

また、通常デベロッパー以外が担当するもの、つまりゲームの仕様であったり、画面デザインなどのアートワークや、そこに表示するストーリーやメッセージなどのテキストといったものが、デベロッパーの作業を妨げる要因として挙げられています。これらが決まらないためにデベロッパーの作業が待ちになってしまうという事態は往々にしてあり得ることですが、まずこのうち仕様については、通常はゲームデザイナーのみがこれを担当しますが、FamVilleではデベロッパー自身もこれを担当できる裁量が与えられました。スライド中の「ゲームデザイナーがすべてを支配しない(Design DOESN'T rule all)」というのがこの箇所です。また、データ主導のゲームデザイン(Data Driven Design)という考え方のもと、ゲームデザイナーがデベロッパーの手を借りずに自らゲームの機能をコントロールできる仕組みが採用されています。これは例えば、ゲームデザイナーが容易に編集できるXMLファイル等にゲームのパラメーター等を分離しておくといった、ごく簡単な方法によって実現できるものです。これと同様に、ストーリーやメッセージといった文字列をプログラムから分離してことで、ゲームデザイナーがそれを容易に変更でき、デベロッパーにとってはその決定や変更に作業を妨げられることがなくなります。これが、スライド中で「文字列テーブル(String Table)」と呼ばれているものです。これはもちろん、国際化を前提としたプログラムにおいてはお馴染みの手法ですが、それ以外にも、特にFacebook向けのゲームの場合には大きな意味を持ちます。Facebookではゲームに対する規約が非常に細かく定められており、かつそれが突然変更され、限られた期限での対応を求められるということがしばしば起こるのですが、こうした手法を用いることで、このような対応が容易になるというわけです。

スライド10-12 抽象化されたネットワークレイヤー

ここではクライアントとサーバーの連携について述べられています。Zyngaのゲームアーキテクチャーにはクライアントとサーバーを効率的に連携させる、抽象化されたネットワークレイヤーが用意されており、デベロッパーはこれを利用することで、その実際の連携処理を意識する必要なしに、アプリケーションを容易に実装できるようになっています。

スライド13 ソーシャルネットワークラッパー

FacebookのAPIリクエストはソーシャルネットワークラッパーと呼ばれる仕組みの中に一元化されています。FacebookのAPIはその仕様が頻繁に変更されるため、こうして一元化しておくとその変更への対処が容易になります。また、Facebookに限らず、様々なソーシャルネットワークごとに異なるAPIの実装を、ここで共通のインタフェースでアクセスできるようにしておくことで、クロスプラットフォームへの対応が容易になります。

スライド14-15 QAプロセス

スライド全体の締めで言われているように、ソーシャルゲームはマラソンであり、常に改修を加え新しい機能を提供し続けていくことが重要ですが、これらを早期にかつトラブルがないようにリリースするためにはQAプロセスが重要になります。FarmVilleでは、Facebook上に用意したテスト用アプリに対して、ソースリポジトリーの最新版が自動テストを経てビルドされる仕組みを構築しており、そこでのスモークテスト、そしてステージング環境でのフルテストを経て本番環境にリリースされるというプロセスを採用しています。

スライド16-19 サーバーアーキテクチャー

スライド16にあるように、FarmVilleは20週連続で毎週100万ずつDAU(デイリーアクティブユーザー)が増加するという、驚くべき成長を遂げました。こうした急激なトラフィックの増加に対して、いかにきちんとスケールするサーバーアーキテクチャーを構築するかというのは、ソーシャルゲームの最も重要なポイントのひとつとなっています。スライド18に示されているFarmVilleのアーキテクチャーは、一見ごく常識的なものに見えますが、実際はかなり大胆で野心的なアーキテクチャーとなっています。残念ながらここではその具体的な詳細に触れることができませんので、もしご興味を持たれたかたは是非Zynga Japanの人材採用に応募いただき、その一員になっていただきたいと思います(笑)

スライド19で述べられているのは、Zyngaのゲームアーキテクチャーではアプリケーションレイヤー、つまりゲーム本来の部分の実装を除くすべてのコンポーネントが共通化されており、新しいゲームを開発する際にはアプリケーションレイヤーの実装だけに集中すればよいということです。デベロッパーはスケーラブルなバックエンドやネットワークレイヤー、ソーシャルネットワークラッパーなどがすべて用意された状態から新しいゲームの開発をスタートすることができるのです。

スライド20-23 ユーザーエクスペリエンスの向上

ここでは主にユーザーエクスペリエンスの向上について述べられています。ご存知の通りユーザーはゲームが自分の操作に素早く反応することを求めます。この障害になるもののひとつがFacebookのAPIリクエストです。外部へのリクエストは遅く信頼性にも乏しいため、FarmVilleではクライアントが直接APIリクエストを実行することを避け、サーバー側でリクエストを実行してその結果をキャッシュし、さらにそれをクライアント出力に埋め込むといった手法が採られています。同様にデータベースへのリクエストもキャッシュされ、またページプロファイラーと呼ばれる仕組みを使って、キャッシュされていないリクエストを監視することでそれを根絶するといった手法が採られています。

スライド24-26 障害への対処

それでもサーバーはダウンします。あるいはFacebookのAPIが利用できない場合というのもあり得ます。そのため、サーバーアーキテクチャーのすべてのレベルを冗長化し、それでも冗長化に限度があるデータベースや、そもそもコントロールが不可能なFacebookのAPIが機能しなくなってしまった場合など、あらゆる事態を想定して、最低限でも動き続けることを目指すというのがスライド24です。そのためFarmVilleでは、スライド25にあるように、すべての機能にON/OFFスイッチ(Kill Switch)を用意しています。そしてもちろん、こうした事態を早期に発見するためには、スライド25のようなサーバーの監視体制と、スライド26にあるようなユーザーの挙動についての仔細なトラッキングが重要になります。

スライド28 ソーシャルゲームは短距離走ではなくマラソン

ここで登場する「ソーシャルゲームは短距離走ではなくマラソン(Social games are a marathon not a sprint)」という言葉は、ソーシャルゲームを運用された経験をお持ちのすべてのかたが実感されていることでしょう。長い道のりを走り続けるためには、ローンチの前にはそれがきちんと動作することを確信し、(ちゃんと睡眠をとって!)余裕を持ってローンチすることが重要であるとして、このスライドは結ばれています。

The slide introduced in this article

  • Author: Amitt Mahajan, Zynga Inc.
  • Japanese Translation: ISOGAWA, Tadasu, Zynga Japan K.K.

Thank you Amitt for your kind permission of the translation!

Zynga Japanでは特に最近、積極的にエンジニアを採用しています。
採用ページをご覧になり興味のある方、ぜひご応募ください。

Symfony2勉強会に参加してみた
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、小俣です。Symfony1.4にはだいぶ慣れてきた今日この頃、近々登場するらしいSymfony2が速いとかすごいとか噂されているので、乗り遅れないよう情報を仕入れておかねば!ということで、先日ちょうど弊社オフィスを会場に開催されたSymfony2勉強会に参加してみました。そのとき仕入れた情報をご紹介しようと思います。


はじめに:Symfony2は1.x系と内部構造が異なるので戸惑う

Symfony1.xに慣れている方にとっては、初めのうちはディレクトリ構造が違いすぎて何処に何があるのか混乱しがちです。私も何度も迷子になりました。少しずつ慣れていくしかありませんが。。。下記にSymfony2の特徴をいくつか紹介してみます。

Symfony2はすべてBundleで構成されている

Symfony2では、新しい概念がいくつか導入されていて、まずはBundle(直訳:束)の理解が必要です。Bundleとはそれ自体が1つのfeature(機能)を成していて、HogeBundleディレクトリにはHogeBundle.phpとController(/HogeController.php)とResources(/configと/views)とTestsがあります。configではroutingの設定ができ、viewsにはテンプレートを置きます。このようにBundleごとにソースがまとまっているため、機能を追加したり削除するときにBundleごと追加したり削除すればよいので、どの機能があるのかないのか視覚的に分かりやすく、また友達とBundleをshareするのも簡単!とのことです。

routingやlayoutをBundleごとにカスタマイズできる

app/views/layout.phpをBundle内のlayout.phpで継承しているので、Bundleごとにlayoutに違いを出したりするときに便利のようです。また、routingもBundleごとに設定できるようになっています。

DIコンテナを理解せずしてSymfony2の内部構造は理解できない

Symfony2では、DIコンテナ(Dependency Injection Container)が非常に重要な役割を果たしていて、シンプルなYAMLやXMLで設定できるコンフィグレーションシステムの裏には、すごい内部構造が隠されているようです。典型的なプロジェクトにおいては、DIコンテナには触ることはないでしょうとドキュメントに書いてありますが、知っておいた方がExtensionを扱うときにスムーズのようです。

それ以外のSynfony1.xからの変更点や違いについては、勉強会でLTもされていたアシアル小川さんの記事をご参照ください。


Symfony2を動かしてみよう

まず、Symfony2を動かすには、PHP5.3.2以上が必要です。その準備ができたらhttps://github.com/symfony/symfony-sandboxからSymfony2のsandboxをゲットし、cacheとlogsの権限を777に変えて、ブラウザからアクセスできるようにします(Apacheで公開するなど)。そこまでできたらsandbox/web/check.phpで動作環境のチェックもしておきましょう。

※ちなみに以下のレッスンの内容は、ワークショップで行われた内容を元に、自分でいろいろ内容を変えてしまっていますので参考程度にしてください。

レッスン1:HelloBundleのHelloControllerにtestActionを追加してみよう

まず、あらかじめ用意されているHelloBundleがどこかというと

sandbox/src/Application/HelloBundle
その中に
HelloBundle/
└ Controller/
 └ HelloController.php
└ HelloBundle.php
└ Resources/
 └ config/
  └ routing.yml(設定により.xml .phpも可)
 └ views/
  └ layout.php(設定により.twigも可)
  └ Hello/
   └ index.php(設定により.twigも可)
└ Tests/
 └ Controller/
  └ HelloControllerTest.php

があります。 これをこのまま動かすと、app.php/hello/hogeだと「Hello hoge!」と表示されます。
ではまず、HelloController.phpを下記にように変更します(testAction追加と同時に、indexActionから$nameを取り除いています)。
# HelloController.php
public function indexAction()
{
 return $this->render('HelloBundle:Hello:index.php');
}
public function testAction()
{
 return $this->render('HelloBundle:Hello:test.php', array('time' => date('H:i:s')));
}
続いて下記のようにテンプレートを追加します。
# views/Hello/test.php
ただいまの時刻:<?php echo $time ?>
そして最後にrouting追加。
# config/routing.yml
test:
 pattern:  /hello/test
 defaults: { _controller: HelloBundle:Hello:test }
hello:
 pattern:  /hello ← /:nameを外しました
 defaults: { _controller: HelloBundle:Hello:index }
app_dev.php/Hello/test(dev) app.php/Hello/test(prod) で表示の確認をしてみてください。 「ただいまの時刻:H:i:s」と表示される単純なものです。
devで見てみると、デバッグツールバーが表示されています。その説明はしないのですが、いろいろいじってみてください。
ちなみにドキュメントによると、単純なテンプレートでは$this->render()よりも$this->createResponse();を使うことで数ミリ秒の時間をセーブできるとのこと。

レッスン2:共通メニューを作って、layoutに入れてみよう

ダミーの共通メニューを作って、layoutに入れてみます。

# views/menu.php
メニュー1<br />
メニュー2<br />
メニュー3<br />
を作って、layout.phpで
# views/layout.php
<?php echo $view->render('HelloBundle::menu.php') ?>
を追記します。 これでメニューが表示されるようになります。

レッスン3:テンプレート側から指定アクションの結果を引いて表示してみよう

テンプレートで、別アクションの結果を表示することができるんです!これは便利じゃないでしょうか。

# views/Hello/index.php
<?php $name ?> ← $nameを使ってないので削除
<?php echo $view['actions']->render('HelloBundle:Hello:test') ?> 追記
これで、Hello/testの結果がHello/indexでも表示されます。

レッスン4:Edge Side Includes を試してみよう

ESI(Edge Side Includes)は、簡単に言うとWEBページをパーツごとにキャッシュして、更新が必要な箇所だけキャッシュの更新ができる技術です。これによりWebコンテンツ配信の高速化が可能になるとのことで、Symfony2もそれに対応しています。

これを使うと、レイアウト部分のキャッシュは30分毎に更新、コンテンツ部分のキャッシュは3秒毎に更新、という風にキャッシュの更新頻度を分けられる!とのことで、ワクワクしながら私も試してみたのですが、残念なことに私の環境ではうまくいかなかったので、皆さん頑張ってみてください!
ちなみに、$this->render()の結果をすぐにreturnせずに、キャッシュ制御してから返すようにします。

# HelloController.php
    $response = $this->render('HelloBundle:Hello:index.php');
    $response->setSharedMaxAge(30);    ←ここでキャッシュ制御
    return $response;
また、AppKernelをAppCacheでラップしたり、テンプレートでの書き方も変えたりする必要があるのですが、その辺の細かい 設定方法などの詳細はスライドドキュメントをご参照ください。


最後に

最後に余談となりますが、勉強会に行くのが初めてでドキドキの私でしたが、隣に座っていた@uechocoさんがいろいろ優しく教えてくれたので、安心して参加できました。その節はありがとうございました。
ちなみに、この勉強会に参加されてた@chobi_eさんが今、同じチームの仲間となって働いているんです!これをご覧になっている方で、ソーシャルアプリにご興味ある方は、ぜひ一緒に働きましょ〜。


Zynga Japanでは特に最近、積極的にエンジニアを採用しています。
採用ページをご覧になり興味のある方、ぜひご応募ください。