ハウテレビジョン開発者ブログ

『外資就活ドットコム』を日夜開発している技術陣がプログラミングネタ・業務改善ネタ・よしなしごとについて記していきます。

AWS Summit、Google Cloud Nextに参加しました

弊社サービスも日頃からお世話になっている、AWSとGCP。
この両者が大規模なカンファレンスを東京で行うということで、参加してきました。
AWS Summitに3日間、Google Cloud Nextに2日間参加しましたので、ざっくりとしたレポートを掲載します。

なお、個々の発表には触れず、全体的な感想のみ記載します。

f:id:itamisky:20170616171550p:plain

会議の概要

基調講演があり、導入事例紹介や各サービスの紹介が行われるセッションがパラレルで開催される、という点は同じです。

AWS Summit

AWS Summitでは豊富なセッションがあり、実に様々な機能・事例の紹介がありました。
参加者も多く、広い会場が埋まっており勢いを感じました。
また、SummitとDev Dayに大別されて会場が分かれており、専門や興味に応じた参加が可能でした。
なお、Dev Dayの最終日は「Serverless Evolution Day」として全てサーバーレスに関する発表がなされるという思い切った構成でした。

Google Cloud Next

f:id:itamisky:20170616171602p:plain

Google Cloud Nextでも、基本的にはAWS Summitと同じような構成が取られ、発表内容も親しいものでした。
が、規模は比較すると小さく、セッション数も少ないため、興味に合うものが見当たらないことも発生しがちだったかと思います。
GCPが生まれた経緯を踏まえてか、一部セッションでGoogle社内システムの説明があり興味深かったです。
また、KubernetesやOpen API互換などに代表されるように、オープンであることを押し出しており、ロックインを自ら放棄する姿勢が素敵です。

発表の傾向

参加したセッションによりどうしても偏りがでますが、キーワードとして「マイクロサービス」が挙げられるかと思います。

単純なサーバーはパブリッククラウドへの移行が順調に進んでおり、更に各サービスを利用させ、新サービスをクラウド上で構築させる段階に入っているのかな、と感じました。

発表内容の大別

上記を踏まえ、発表内容は以下の2つに大別できるかと思います。

  1. オンプレからパブリッククラウドへの移行促進
  2. 各種サービスの利用促進

1は、引き続きオンプレで運用しているサービスをクラウド上に移行してもらうためのものです。
2は、AI系サービスの新規利用やマイクロサービス化など、今まで各自が実装していた機能を提供することで、利用量を増やすためのものです。
これはロックインにも繋がります。

この区分で言うと、1より2の発表が多く見え、何となく時代の流れを感じました。

その他

AWS Summitの企業ブースは、配っているグッズを貰うと手持ちの二次元コードをスキャンされ、後ほどメールが届くという効率的なシステムでした。

アンケートに答えると公式グッズがもらえます。
何がもらえるかは行ってのお楽しみ。

まとめ

AWS、GCP共に次々と公式サービス・関連サービスが追加され、開発者にとっては便利な世の中になっています。
低コストで高度な機能が実現できるため、これを使わない手はないでしょう。

どのような機能が実現できるかのイメージを掴むため、このような公式のイベントに参加してみてはいかがでしょうか。

おまけ

ハウテレビジョンでは、AWSやGCPの好きな仲間を募集しています。
Webエンジニア
エンジニアインターンシップ

Web Audio API + firebase + React + material-uiでノイズを組み合わせて評価してもらうサービスを作った

弊社ハウテレビジョンでは、週の1日をR&D dayとして、業務と直接関係しない技術を学んでみたり、今まであまり触れてこなかった領域を調べたりしています。

今回はWeb Audio APIを使ったサービスのプロトタイプを作ってみました。
音声処理が必要なWebサービスは限られますが、作ってみると意外に簡単で楽しいので、空いた時間に何か作ってみてはいかがでしょうか。

背景

弊社エンジニアの中で、noisliというWebサービスがにわかに流行りだしました。 https://www.noisli.com/

これが何かというと、「幾つかの環境音をミキシングし、好きなノイズを作って垂れ流せる」というサービスです。
似たようなサービスは幾つかありますが、作った環境音を簡単に共有出来る点が楽しいです。

さて、各人が好き勝手に作った環境音がSlackで共有されていたのですが、「これは良い」「これは無い」という評価がある程度偏っており、ノイズにも良し悪しがあることが分かりました。

そこで、今回はWeb Audio APIを使って、作ったノイズの評価をリアルタイムで閲覧できるようなサービスを作ってみます。
ここではプロトタイプとして、実運用で考慮すべき諸々は省いています。

開発の手順

  1. 環境構築
  2. Web Audio APIで単一サウンドを再生
  3. 複数サウンドを合成
  4. UIの作成
  5. 再生、評価できるサービスをfirebaseで作る

1はReact、2〜3はWeb Audio API、4はmaterial-ui、5はfirebaseがそれぞれ主なトピックです。

では順番に見てゆきます。

環境構築

f:id:itamisky:20170512133022p:plain

まずはサービスを作成できる環境を整えます。
今回はホビーなプロジェクトですので、以下のコマンドで最小限のReactアプリの土台を作ります。
$ create-react-app noize

Web Audio APIで単一サウンドを再生

Web Audio APIはMDNにドキュメントがありますので、まずそちらを読み進めてゆきます。
https://developer.mozilla.org/ja/docs/Web/API/Web_Audio_API
https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API
https://developer.mozilla.org/ja/docs/Web/API/Web_Audio_API/Using_Web_Audio_API

Audio contextやBufferなどの基本概念に軽く触れた後は、サンプルコードを見つつ動かしてみます。

まずはこちらのコードを利用し、ランダムなノイズを生成し2秒間流してみます。
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createBufferSource

componentDidMount() {
  const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

  const channels = 2;
  const frameCount = audioCtx.sampleRate * 2.0;
  const buffer = audioCtx.createBuffer(channels, frameCount, audioCtx.sampleRate);

  for (let channel = 0; channel < channels; channel++) {
    const nowBuffering = buffer.getChannelData(channel);
    for (let i = 0; i < frameCount; i++) {
      nowBuffering[i] = Math.random() * 2 - 1;
    }
  }

  const source = audioCtx.createBufferSource();
  source.buffer = buffer;
  source.connect(audioCtx.destination);
  source.start();
}

createBufferで2秒間分のバッファを作り、そこにランダムな値を放り込んでゆきます。
作ったバッファは出力にAudioContext.destinationを繋ぎます。

これでページをロード(正確にはDOMをマウント)した際にノイズが流れるようになりました。テロですね。

音量調整

このままでは音が大きいので、全体の音量を調整してみます。 GainNodeをsoruceとdestinationの間に挟み、加工するイメージです。 https://developer.mozilla.org/en-US/docs/Web/API/GainNode

// set volume
const gainNode = audioCtx.createGain();
gainNode.gain.value = 0.05;

const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(gainNode);
gainNode.connect(audioCtx.destination);
source.start();

これで小さな音量で再生することができました。 valueは環境にあわせて設定してください。

ファイルを再生する

ずっとランダムなノイズを聞いていいると精神が疲弊してきますので、環境音を再生させてみます。
AudioContext.decodeAudioData() を利用するとファイルからの非同期読み込みが行えます。
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/decodeAudioData

読み込みはXMLHttpRequestFileReader のいずれかを用いて行えます。
今回はXHRでリソースを取得します。

windy.wav というwavファイルを用意し、public/sound 以下に配置します。
それをXHRで取得してオーディオバッファに渡し、以前と同じ流れで再生をさせます。
再生部分はplayという関数にまとめています。
ちなみに、source.loop = true; を付けるだけでループ再生してくれます。

componentDidMount() {
  const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

  const request = new XMLHttpRequest();
  request.open('GET', 'sound/windy.wav', true);
  request.responseType = 'arraybuffer';
  request.onload = () => {
    const audioData = request.response;
    const source = audioCtx.createBufferSource();
    audioCtx.decodeAudioData(audioData).then((decodedData) => {
      source.buffer = decodedData;
      this.play(audioCtx, source);
      source.loop = true;
    });
  };

  request.send();
}

play(audioCtx, source) {
  // set volume
  const gainNode = audioCtx.createGain();
  gainNode.gain.value = 0.1;

  // connection
  source.connect(gainNode);
  gainNode.connect(audioCtx.destination);

  source.start();
}

再生・停止できるようにする

現在はロード時に強制的に再生するようにしていますが、これをボタンで再生・停止できるようにしてみます。
必要な処理として、以下が挙げられます。

  • 読み込んだオーディオデータを保存して再利用する
  • 再生・停止を切り替える処理を作る
  • ボタンを用意しonClickで切り替える

今回は再生位置は関係ない(再生する度最初から始まる)ので、再生位置の保存などは必要ありませんでした。

まず、オーディオデータを雑にthisに保存するよう変更します。

this.audioData = {
  windy: null,
}
...
request.onload = () => {
  const audioData = request.response;
  this.audioCtx.decodeAudioData(audioData).then((decodedData) => {
    this.audioData.windy = decodedData;
  });
};
...

次に、再生ボタンが押されたら、保存済みデータでsourceを作成する処理を作ります。

start() {
  this.source = this.connect(this.audioData.windy);
  this.source.start();
}


connect(data) {
  const source = this.audioCtx.createBufferSource();

  source.buffer = data;
  source.loop = true;

  // set volume
  const gainNode = this.audioCtx.createGain();
  gainNode.gain.value = 0.1;

  // connection
  source.connect(gainNode);
  gainNode.connect(this.audioCtx.destination);

  return source;
}

また、停止する処理も作成します。

stop() {
  if (this.source) {
    this.source.stop();
  }
}

これらを切り替えて表示にも反映されるよう、toggle関数を作ってonClick時に発火するようにします。

render() {
  const toggle = () => {
    this.state.playing ? this.stop() : this.start();
    this.setState({playing: !this.state.playing});
  };

  return (
    <div className="App">
      <div>{this.state.playing ? 'now playing...' : 'stopped'}</div>
      <button onClick={toggle}>Toggle</button>
    </div>
  );
}

完成です!実際に切り替わるかを試してみてください。

複数サウンドを合成

今までは単一のサウンドファイルを再生してきましたが、複数のサウンドを読み込み、合成して出力してみます。
グラフなので、ノードとエッジを増やすだけで同じように処理出来る気がしますね。

複数ファイルの読み込み

まずはオーディオデータ読み込みを複数ファイルに対応します。
ファイル名を指定してXHRの結果をキャッシュする処理を括りだし、それをファイル名のループで呼び出します。

loadAudioData(fileName, extension) {
  const request = new XMLHttpRequest();
  const soundDir = '/sound/';
  request.open('GET', `${soundDir}${fileName}.${extension}`, true);
  request.responseType = 'arraybuffer';

  request.onload = () => {
    const audioData = request.response;
    this.audioCtx.decodeAudioData(audioData).then((decodedData) => {
      this.audioData[fileName] = decodedData;
    });
  };

  request.send();
}

componentDidMount() {
  this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  ['windy', 'rain'].forEach((name) => this.loadAudioData(name, 'wav'));
}

これで、複数のオーディオファイルデータを簡単に読み込めるようになりました。

サウンドの合成

DTMをやったことがある方には馴染みのある現象かと思いますが、複数の音源から好き勝手に音をだすと、一定のレベルを超えた所で音がビリビリに割れてしまいます。
そんな時はコンプレッサーと呼ばれるものを挟み、大きな音を絞って調整します。

Web Audio APIにもDynamicsCompressorNode というものが用意されており、同じような処理が可能です。
https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode

そのため、作成するグラフは、「複数のsource」→「コンプレッサー」→「全体のゲイン(音量)調整」→「出力」という流れになります。

実装としては、まず汎用的な処理にするため、sourceを作成する以下の処理を関数として括りだします。

makeSource(data) {
  const source = this.audioCtx.createBufferSource();
  source.buffer = data;
  source.loop = true;
  return source;
}

その後、複数のsourceをまとめて作成・connect・startさせる処理を記述します。

start() {
  const compressor = this.audioCtx.createDynamicsCompressor();
  const gainNode = this.audioCtx.createGain();
  gainNode.gain.value = 0.1;
  const outputNode = this.audioCtx.destination;
  compressor.connect(gainNode);
  gainNode.connect(outputNode);

  ['windy', 'rain', 'fireplace', 'kiva', 'lavasteam']
    .map((name) => this.makeSource(this.audioData[name]))
    .map((source) => {
      this.sources.push(source);
      source.connect(compressor);
    });

  this.sources.map((source) => source.start());
}

stop() {
  if (this.sources) {
    this.sources.map((source) => source.stop());
    this.sources = [];
  }
}

個々の音源の音量を調整する

個々の音源のバランスを調整するため、それぞれのsourceにもGainNodeをくっつけます。

['windy', 'rain', 'fireplace', 'kiva', 'lavasteam']
  .map((name) => this.makeSource(this.audioData[name]))
  .map((source) => {
    this.sources.push(source);

    const sourceGainNode = this.audioCtx.createGain();
    sourceGainNode.gain.value = 0.3;
    source.connect(sourceGainNode);
    sourceGainNode.connect(compressor);
  });

これで、0.3という固定値を持ったGainNodeを各ソースに紐付けることができました。
後ほど、ここは調整されたパラメータがやってくる予定です。

この段階で、スライダーで全体のゲインを変えられるようにしました(ソースは省略しますが)。
スライダーは範囲を[0, 1000]などにしておき、Gainに渡す際は1/1000すると細かく調整できて便利です。

f:id:itamisky:20170512133055p:plain

UIを作る

さて、このセクションは一休みとして、主にUIを作ります。

リストなどの表示をするのに、通常のulやliをそのまま使うと少し不格好です。
頑張ってCSSを当てても良いですが、ここではMaterial-UIを入れて表示を整えてみます。

今回はSlider、Table、TextField、Buttonの3つのみで事足りました。

http://www.material-ui.com/#/components/slider

http://www.material-ui.com/#/components/table

http://www.material-ui.com/#/components/text-field

http://www.material-ui.com/#/components/flat-button

初期状態の”Welcome to React”だった文字なども変えておきます。
結果、以下のような表示になりました。

f:id:itamisky:20170512133117p:plain

手抜き感はありますが、最低限使えるものにしています。
投票ボタンがキモですが、この段階ではとりあえず置いているだけで、クリックしても何も起きません。

Firebaseとの連携

投票結果を保存して共有するために、サーバーとDBが必要です。
ここではFirebaseの無料プランを使って、お手軽に開発をしてゆきます。

Firebaseの初期化は、コンソールからプロジェクトを作って初期化コードを埋め込むだけの簡単設計です。
https://console.firebase.google.com/?hl=ja

ただし、今回はReactから使うため、NPMとして使います。
https://www.npmjs.com/package/firebase

yarn add firebase -S

DBへの保存

そのままではDBに書き込むのにログインが必要なため、とりあえず全開放のルールを設定します。
FirebaseコンソールのDatabase以下、ルールタブから設定できます。

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

もちろん、プロトタイプ以外では、しっかりとルールを設定しましょう。
全ての情報を誰でも閲覧・編集できてしまうため極めて危険です。

f:id:itamisky:20170512133354p:plain

パラメータとそれに応じた評価を格納すれば良いので、以下のような形式で保存してみます。

/
  - items
    - $id
      - params
        - windy: 0.2,
        - rain: 0.3,
        ...
      - score: 8

認証が無いためシンプルです。
itemの変更を検知するので、ユーザー数が増えそうならもう少し工夫をする必要がありますが、今回はプロトタイプなので手を抜いています。
実際にデータが入ると以下のようになります。

f:id:itamisky:20170512133138p:plain

DBにノイズのデータを書き込む

ユーザーが好きなパラメーターを投稿できるようにします。
まずはこのように適当なフォームを作り、入力を受け取れるようにします。

f:id:itamisky:20170512133156p:plain

“CREATE”ボタンが押されたら、フォームの値を取得し、合計が1.0の時のみデータベースに送信します。

const sum = Object.keys(this.state.values).reduce((result, key) => result + parseFloat(this.state.values[key]), 0.0);
if(sum !== 1.0) {
  this.setState({ message: `sum must be 1.0  - now: ${sum}` });
}
else {
  this.props.sendToDatabase(this.state.values);
  this.setState({ message: 'submitted!' });
}

送信処理は親から渡されており、子コンポーネントはそれにパラメーターを渡して発火するだけです。
送信処理はこれだけです。簡単ですね。

sendToDatabase = (params) => {
  this.db.ref('/items').push().set({
    params: params,
    score: 0,
  });
};

DBからノイズのパラメータを取得する

上記のような処理でデータが詰められてゆきますので、適切に取得する必要があります。
firebaseではDB上のパスを指定し、継続的に変更を検知することができます。
/items の値を検知するには以下のようにコールバックを指定します。

this.db.ref('/items').orderByChild('score').on('value', (snapshot) => {
  const items = snapshot.val();
  this.setState({
    items: items,
  })
})

orderByXXXで特定の値によりソートをすることができます。
なお、indexやlimitなどを考慮する必要がありますが、今回はプロトタイプなので大量のデータを受け取るのも許容してしまいます。

評価を書き込む

評価を変更する箇所ではトランザクション処理にして、複数人が同時に評価してもデータが破損しないようにします。
https://firebase.google.com/docs/database/server/save-data?hl=ja#section-transactions

this.db.ref(`/item/${this.state.playing}/score`).transaction(function (current_value) {
  return (current_value || 0) + value;
});

デプロイする

さて、一応機能ができた所で、デプロイして使ってもらいます。
Firebaseのホスティング機能、create-react-appのbuild機能により、以下のように簡単にデプロイすることができます。
https://firebase.google.com/docs/hosting/deploying?hl=ja

$ firebase init
(hosting.publicをbuildに書き換え)
$ firebase use --add
$ yarn build
$ firebase deploy

database.rulesができた場合は、以前ルールを書き換えた場合と同様に書き換えておきます。

完成

f:id:itamisky:20170512133216p:plain

f:id:itamisky:20170512133232p:plain

まとめ

今回はWeb Audio APIを使い、ちょっとしたサービスのプロトタイプを作ってみました。
create-react-appなどのテンプレート、material-ui、そしてFirebaseを使うとこのようなサービスが簡単に組み立てられますので、プロトタイピングにはオススメです。

Cloud Dataflow入門〜データ処理の実践

弊社ハウテレビジョンでは、週の1日をR&D dayとして、業務と直接関係しない技術を学んでみたり、今まであまり触れてこなかった領域を調べたりしています。

今回はCloud Dataflowに入門し、簡単なデータの分析コードを組み、動かしてみました。
とても簡単に強力な並列処理が出来るので、ログの分析などで活用できそうです。

f:id:itamisky:20170419112030j:plain

概略

Cloud Dataflowは、Google Cloudで提供されているデータ処理用のプラットフォームです。
2017/04現在、JavaとPythonのクライアントが提供されております(Pythonはβ版)。

「便利だよ!」という話はよく訊くのですが、実際に使ってみたことはなかったため、これを機に入門してみます。

できたこと

基本的なDataflowの使い方、そのコンセプトを把握しました。
また、簡単なデータ処理として、「Trump氏のツイート」を対象に「1週間ごとのつぶやき数」を集計、ただし「RTは除く」、という3つの条件でカウントをしました。

進めかた

利用方法をまとめてくれている有用なブログ記事もありますが、変化が激しいのとまとまった時間があるため、下記のGoogle公式のドキュメントを使って入門してみます。
https://cloud.google.com/dataflow/docs/

また、せっかくですので、Pythonのクライアントライブラリを使って進めます。

やったこと

クイックスタート

https://cloud.google.com/dataflow/docs/quickstarts/quickstart-python

セットアップ
まずは新規プロジェクトを作り、APIを有効にします。

https://d2mxuefqeaa7sj.cloudfront.net/s_8ACB1D17AA95783CAEC7C335CBA2166A66ED243120AE0FF2F7C766C37CADD651_1492145849905_+2017-04-14+12.06.51.png

なお、どのGCPのプロジェクトでも必要なgcloud コマンドのインストール、認証などは事前に済ませているものとします。
次に、Cloud Storageにバケットを作成します。

今回は、プロジェクト名が dataflow-research 、バケット名が st-dataflow-research として進めます。
また、現状Pythonのバージョンは2.7のみ使えますので、pyenvで固定しておきます。

必要なコマンドはpipになっているため、簡単にインストールできます。
pip install google-cloud-dataflow

動作確認
一応確認しておきます。
ローカルでの実行、リモートでの実行のそれぞれが載っているので、両方試しておくと無難です。

さて、公式ドキュメントどおりリモート実行を下記コマンドで行うとエラーが出ます(2017/04現在)。

python -m apache_beam.examples.wordcount \
  --project $PROJECT \
  --job_name $PROJECT-wordcount \
  --runner BlockingDataflowPipelineRunner \
  --staging_location $BUCKET/staging \
  --temp_location $BUCKET/temp \
  --output $BUCKET/output

エラーメッセージはこの通り。
ValueError: Unexpected pipeline runner: BlockingDataflowPipelineRunner. Valid values are DirectRunner, EagerRunner, DataflowRunner, TestDataflowRunner or the fully qualified name of a PipelineRunner subclass.

BlockingDataflowPipelineRunner が無いため、DataflowRunner に書き換えて実行します。
正常に実行されると5分ほどかかり、操作が完了します。
ステップごとに実行している様子はGCP上で確認できます。

https://d2mxuefqeaa7sj.cloudfront.net/s_8ACB1D17AA95783CAEC7C335CBA2166A66ED243120AE0FF2F7C766C37CADD651_1492145861146_+2017-04-14+13.56.03.png

Storageに結果が出力されていることも確認しておくと安心です。

パイプラインについて

サンプルが実行できたので、次にコンセプトを学んでゆきました。

公式ドキュメントが充実していますので、重要な所を拾い読みすると捗ります。
https://cloud.google.com/dataflow/model/pipelines

パイプラインはステップの有向グラフ と書かれており、分岐やループが出来ることが併せて述べられています。

変換の項目を見ると、パイプライン(中身はapache beam)は関数型言語で書くような感覚で記述すれば良いことがわかります。
実際サンプルで挙げられているコードも、以下のようにmapやflatMapなどをメソッドチェーン的に呼び出しており、見慣れた形になっています。

(p
 | beam.io.ReadFromText(my_options.input)
 | beam.FlatMap(lambda x: re.findall(r'[A-Za-z\']+', x))
 | beam.Map(lambda x: (x, 1))
 | beam.combiners.Count.PerKey()
 | beam.io.WriteToText(my_options.output))

サンプルを実行するとまたしてもエラーが出ます。
NameError: name 'argv' is not defined
これは単純にサンプルコードでimportが抜けている問題なので、 以下のように修正します。

@@ -1,3 +1,4 @@
+import sys
 import re

 import apache_beam as beam
@@ -17,7 +18,7 @@ class MyOptions(PipelineOptions):
                         required=True,
                         help='Output file to write results to.')

-pipeline_options = PipelineOptions(argv)
+pipeline_options = PipelineOptions(sys.argv)
 my_options = pipeline_options.view_as(MyOptions)

実行してみると、前回と同様の結果が得られました。

実践

https://cloud.google.com/dataflow/pipelines/constructing-your-pipeline

パイプラインのコンセプトをほんのりと理解したので、その周辺の理解を深めるため、実際にデータ処理を行ってみます。
今回は簡単なデータ処理として、「Trump氏のツイート」を対象に「1週間ごとのつぶやき数」を集計、ただし「RTは除く」、という3つの条件でカウントをします。

ちなみに、以下が対象の「本物」なTrump氏のTwitterです。
https://twitter.com/realDonaldTrump

データとしてツイートの一覧が必要なので、tweepyでさっくりと取得しました。
全ツイートは制限により取れないため、直近の約3000ツイート程度です。

import tweepy
import pandas

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

api = tweepy.API(auth)

data = []

def process_status(status):
    data.append([status.created_at, status.text.replace('\n', ' ')])

page = 1
while True:
    print("fetch page {0}".format(page))
    if len(data) > 0:
        print("  top: {0}".format(data[-1]))
    statuses = api.user_timeline('@realDonaldTrump', count=200, page=page)
    if statuses:
        for status in statuses:
            process_status(status)
    else:
        # All done
        break
    page += 1  # next page

df = pandas.DataFrame(data, columns=['created_at', 'text'])
df.to_csv('trump.tsv', sep='\t', index=False, encoding='utf-8')

ローカルで実行

毎回リモートで実行するのは時間がかかるので、ローカルでパイプライン処理を実行させます。
RunnerにDirectRunner を指定するだけでローカル実行されます。
処理がすぐ返ってくるので「実行されてない?」と思ってしまいますが、バックグラウンドで動いています。
しばらくすると結果が同じように出力されます。

パイプラインの組み立て

目的の処理を実現するため、今回は以下の流れで処理をしてゆきます。

  1. TSVファイルの読み込み
  2. RTのツイートを除外
  3. 各ツイートのタイムスタンプを修正する
  4. ウィンドウで区切る
  5. 集計用にデータを変換する(ウィンドウ番号をkeyとして持つ)
  6. カウント(groupBy + count)
  7. 結果を出力する

では、パイプラインのステップを順番に見てゆきます。

TSVファイルの読み込み
これは前のサンプルでもあった通り、beam.io.ReadFromText で読み込めます。
| beam.io.ReadFromText(my_options.input)

RTのツイートを除外
これはフィルタリングするだけで事足りそうです。
ツイート内容にRT @ が入っていれば除外とします。
beam.Filter というプロセッサがあるので、これをありがたく利用します。
| beam.Filter(``**lambda** x: 'RT @' **not in** x)

各行のタイムスタンプを設定する
BigQueryやStorageなどからではない、テキストから読み込んだファイルなどは、全ての行が共通のタイムスタンプを持っています。
データとして持っているものを自動で認識してくれる訳ではありませんので、自分で設定してあげる必要があります。
https://cloud.google.com/dataflow/model/windowing#TimeStamping

JavaではParDoという変換が用意されていますが、PythonではさらにMapとFlatmapが用意されています。
https://cloud.google.com/dataflow/model/par-do

さて、Pythonのサンプルは提供されていませんが、Githubのサンプルを漁った限りでは以下のようにwindow.TimestampedValue を使って出力をすれば良さそうです。
https://github.com/apache/beam/blob/d2b8b2886ce42f138b634d90208780bdce7e058e/sdks/python/apache_beam/examples/complete/top_wikipedia_sessions.py#L68

まずは、入力データが以下のような\t で区切られたTSV形式になっているので、日時と内容を分離します。
2017-04-13 19:21:07 It was a great honor to welcome Atlanta's heroic first responders to the White House this afternoon! https://t.co/ZtC14AJ0xs
次に、タイムスタンプが日付形式になっているので、Unixタイムスタンプに変換します。
結果をwindow.TimestampedValue で返してあげれば、DoFnの完成です。

class ExtractUserAndTimestampDoFn(beam.DoFn):
    def process(self, context):
        import time
        from datetime import datetime
        [str_timestamp, text] = context.split('\t')
        timestamp = datetime.strptime(str_timestamp, '%Y-%m-%d %H:%M:%S')
        unixtime = int(time.mktime(timestamp.timetuple()))
        yield window.TimestampedValue(text, unixtime)

importを関数内で行っている ことに注意です。
グローバルに読み込むと、各関数が別のマシンで実行された際にimportがされておらずエラーになってしまいます。
https://cloud.google.com/dataflow/faq#nameerror-

作ったクラスはParDoで適用すればOKです。

| beam.ParDo(ExtractUserAndTimestampDoFn())

ウィンドウで区切る
タイムスタンプを一定の区間で区切るものとして、ウィンドウという仕組みがあります。
https://cloud.google.com/dataflow/model/windowing
データ集計ではよく出てくる方法ですので、標準で用意されていて便利です。

ウィンドウで区切るには、WindowInto という関数を利用します。
これも公式ドキュメントには無いので、Githubのサンプルから漁ると見つかります。

from apache_beam.transforms import WindowInto
ONE_WEEK_IN_SECONDS = 60 * 60 * 24 * 7
| WindowInto(FixedWindows(size=ONE_WEEK_IN_SECONDS))

集計用にデータを変換する
ウィンドウで区切ったデータを、(window-number,1) という形に変換し、groupByが出来るようにします。
これは以下のようなクラスを作り、いつも通りにParDoで適用します。

class WithWindow(beam.DoFn):
  def process(self, element, window=beam.DoFn.WindowParam):
      yield (str(window), 1)


| beam.ParDo(WithWindow())

カウントと出力
これは標準で提供されているプロセッサを適用させれば完了です。

| beam.combiners.Count.PerKey()
| beam.io.WriteToText(my_options.output))

処理のまとめ
処理部分のソースは以下のようになりました。

class ExtractUserAndTimestampDoFn(beam.DoFn):
    def process(self, context):
        [str_timestamp, text] = context.split('\t')
        timestamp = datetime.strptime(str_timestamp, '%Y-%m-%d %H:%M:%S')
        unixtime = int(time.mktime(timestamp.timetuple()))
        yield window.TimestampedValue(text, unixtime)


class SessionsToStringsDoFn(beam.DoFn):
  def process(self, element, window=beam.DoFn.WindowParam):
    yield u"{0}: {1}".format(str(element).encode('utf-8'), str(window).encode('utf-8'))


class WithWindow(beam.DoFn):
  def process(self, element, window=beam.DoFn.WindowParam):
      yield (str(window), 1)

(p
 | beam.io.ReadFromText(my_options.input)
 | beam.Filter(lambda x: 'RT @' not in x)
 | beam.ParDo(ExtractUserAndTimestampDoFn())
 | WindowInto(FixedWindows(size=ONE_WEEK_IN_SECONDS))
 | beam.ParDo(WithWindow())
 | beam.combiners.Count.PerKey()
 | beam.io.WriteToText(my_options.output))

各ステップでやることがはっきりしているので、分かりやすいですね。

結果
実行してみると、以下のような出力が得られました。

('[1479945600.0, 1480550400.0)', 39)
('[1461801600.0, 1462406400.0)', 60)
('[1480550400.0, 1481155200.0)', 35)
('[1476316800.0, 1476921600.0)', 160)
('[1466640000.0, 1467244800.0)', 78)
...
('[1484179200.0, 1484784000.0)', 47)
('[1483574400.0, 1484179200.0)', 51)
('[1490832000.0, 1491436800.0)', 38)

もちろん、数の代わりに平均を出したり、ひと月あたりに変換したり、ということがすぐできます。

リモートで実行

ソースとなるツイートのデータはStorage上にある必要がありますので、gs://st-dataflow-research/tweet_trump.tsv というファイル名で保存したとします。

さて、このまま意気揚々とリモートで実行をするとコケてしまいます。
これは、例えばdatetimeなど、グローバルでimportしているパッケージをParDoなどで使用しているためです。
ParDo内で実行された関数は、並列に別のマシンで実行されるため、importしていない事になってしまいます。
https://cloud.google.com/dataflow/faq#nameerror-
これを防ぐため、--save_main_session オプションを利用します。

他は同じように、リモートで実行するよう指示を出します。

PROJECT=dataflow-research
BUCKET=gs://st-dataflow-research

python pipeline-tweet.py \
  --project $PROJECT \
  --job_name $PROJECT-tweet-trump \
  --runner DataflowRunner \
  --staging_location $BUCKET/staging \
  --temp_location $BUCKET/temp \
  --output $BUCKET/output \
  --save_main_session

しばらくすると、ローカルで実行したものと同じ結果が出力されました!

まとめ

Dataflowに入門し、軽いデータ処理が行えるようになりました。
並列性の高い処理が簡潔に記述して実行できるため、特に大規模なデータを扱う際には重宝しそうです。

また、軽い気持ちでPythonクライアントを使うと辛いことになるということがわかりました。
ドキュメントが充実していないのはしょうがないにしても、重要な機能であるストリーミング実行がJavaでしか対応していないなど、まだまだ未対応なので使う際は強い意思を持ってご利用ください。

なお、ドキュメントがおかしい時はフィードバックを送ってみましょう。
上記で問題になっていた箇所には既にフィードバックを送っているので、もしかしたら直っているかもしれません。

Rustに入門した理由、チュートリアルの過程と感想

弊社ハウテレビジョンでは、週の1日をR&D dayとして、業務と直接関係しない技術を学んでみたり、今まであまり触れてこなかった領域を調べたりしています。

今回は複数人で集まってRustのチュートリアルを読み進め、実際に簡単なコードを組み、動かしてみました。
結果として、今から入門するのに向いた言語で、特に今まであまり多くの言語を触ったことのない方にお勧めできる、ということが分かりました。

f:id:itamisky:20170412104405p:plain

なぜRust?

Rustでは、通常のWebアプリケーション開発ではあまり気にすることのない、低レベルな処理を学べます。
また、いわゆる関数型言語の特徴を取り入れているためそのパラダイムを学んだり、静的型付けに慣れることができます。

C++やHaskellといった言語を触ったことのある人にはお馴染みですが、RubyやPythonしか触ったことがないというメンバーもいますので、これを気にまとめて学んでしまおうという寸法です。

利点をまとめると以下になります。

  • メモリの状態を意識できるようになる
  • 静的型付けに親しめる
  • 様々なパラダイムに触れることができる(特に関数型)
  • WebAssemblyで使えるかもしれない
  • 最近の話題についていける

このように多くの利点がある便利な言語ですので、Rustを選択しました。

以下では実際にチュートリアル(+ 一部リファレンス)を読んだ過程と、入門をおすすめする理由を記載してゆきます。

やったこと

Rustの公式チュートリアルを読み進め、実際にサンプルを動かしたり、改変したりしました。
公式ドキュメント https://doc.rust-lang.org/book/ を読んで進めてゆきました。

導入

まず、Rustの言語思想が述べられています。
「安全性」「速度」「並列性」をガベージコレクタ無しで実現するとのことで、肝になるメモリ管理をどうするのか、幾つか推測が話されました。

なお、組み込み環境が利用目的に入っているためか、対応プラットフォーム一覧がとても充実しています。
https://forge.rust-lang.org/platform-support.html

環境構築は、全員Macユーザーなので、brew install rust で簡単に導入ができました。

Hello World!

単純なプログラムですが、なかなか重要な情報が詰まっていました。
println!は関数ではなくマクロだということや、インデント・スペースの規約がさらりと述べられていたりします。

コンパイラの説明も、Rubyなどの動的言語しか触ったことがない人のため懇切丁寧に書かれており、入門に向いています。

続いて、ビルドツールであるCargoの説明が入ります。
cargo runcargo new といった便利なコマンドが紹介されました。
公式で用意されており、rails new の便利さに慣れてしまった我々も安心の親切設計です。

なお、作成されたtarget ディレクトリの中を覗いてみましたが、ディレクトリ名を見ただけでそれがどのようなものか分かる、綺麗な構造になっていました。
「依存関係にあるライブラリはここに入る」
「debugとreleaseビルドがある」
「インクリメンタルビルドをする」
などの後々説明が入る特徴もディレクトリ名を見るだけでおおよそ推測でき、ツールへの安心感が芽生えました。

数あてゲームのチュートリアル

ライブラリの読み込みや変数束縛やimmutabilityなどの説明が入り、いよいよコーディングに入ります。
紹介されているのはおおよそ他の言語でおなじみな機能ですが、懇切丁寧に説明されているので初見でも理解が進みます。

一部、パターンマッチや所有権の話も出てきますので、関数型やC++に触ったことが無いと少し理解が大変でした。

所有権はサンプルコードを書き換えて、内部の仕組みを推測しながら進めました。
「コピー渡しなのか、参照渡しなのか」の判別は少し疑問が残りましたが、後ほどCopy traitや所有権の詳細ページで出てきて氷解しますので、とりあえず先に進むことをおすすめします。

基本的な所を見てゆき、読み終わった後は簡単なプログラム(FizzBuzzなど)を各自作成しました。

面白かった点
色々いじってみた中で、以下のコードが生まれました。
後々ドキュメントを読み進めてゆくと出てくるのかと思いますが、挙動が面白かったです。

// plus_twoとして、plus_oneと同じ型の関数を用意する

let mut f: fn(i32) -> i32 = plus_one;
f = plus_two;  // ok!

let mut f = plus_one;
f = plus_two;  // error!

実際に動かすとコンパイルエラーが出るので、そのメッセージを見ると原因が分かります。
是非試してみてください。

チュートリアルの後

ここからは、各機能の詳細ページです。
「4.1 Variable Bindings」〜「4.8 Ownership」まで読み進めました。
特にOwnershipのページは今までの謎が解ける感覚がありますので、Rustの入門としては外せないページでした。
メモリ管理関連のページは3つに分かれており、残り2つも読むことが強く推奨されています。

是非次回は続きから読み進めたいところです。

言語周辺で良いと思ったところ

チュートリアルを終えて、言語の周辺で良いと思った点をまとめます。

  • ドキュメントが面白い & 懇切丁寧
    • 「その説明、いる?」というレベルの丁寧さ
    • ちょくちょく茶番が入る
  • インストールが楽
  • 便利なビルドツールが用意されている
    • ディレクトリ構造が分かりやすい
    • 新規プロジェクト用のコマンドが用意されている

言語の強力さ」「開始のお手軽さ」「ドキュメントの丁寧さ」を兼ね揃えており、これから学習するのに向いている言語だと感じました。

進め方についての感想

新しい言語を使うのに際し、リファレンス的にドキュメントを参照して学習を進めることもありますが、今回はドキュメントに沿って進めたため以下のような感想が挙がりました。

“新しい言語を学習する際に、チュートリアルを丁寧になぞるという事をあまりしないので、よい経験になった。複数人で読み合わせすることにより、理解があいまいな箇所、解説の意味・意図を勘違いしてしまうことを防げたと思う。” (20代 / 男性)

一方で、全員で同じドキュメントを読み進めたのみでしたので、以下の改善案も挙がっています。

“皆で同じことをやるのも良いが、アウトプットが似たり寄ったりになってしまうので、次はもくもくタイムと簡単な発表の時間を設けても良いかもしれない。” (20代 / 男性)

そのため、ある程度の時間を確保し、自習や好きなものを作ってみる時間を取るのが良さそうです。
時間を見積もるのは難しいですが、お題を先に決めておいて、それをどのように実装したか、発表とレビューを行うのも良いと思いました。

同じようにドキュメントを読まれる際は、是非参考にしていただければと思います。

まとめ

今回はRustのチュートリアルを利用し、入門してみました。

今までRailsやJSだけ書いてきた、という方にもお勧めな入門しやすい言語だと思います。
他の言語を学んでみたい場合のご参考になれば幸いです。

おまけ
ハウテレビジョンでは、プログラミング言語の好きな仲間を募集しています。
Webエンジニア
エンジニアインターンシップ

ICSE 2017 論文リーディング

弊社ハウテレビジョンでは、週の1日をR&D dayとして、業務と直接関係しない技術を学んでみたり、今まであまり触れてこなかった領域を調べたりしています。

f:id:itamisky:20170403103601j:plain

背景

最先端の研究を知るのに、カンファレンスの論文を読むのは有効な手段です。
直接役立つことは多くないですが、理論を応用できたり、今後どんな技術が出てくるのかを知ることができます。
何より楽しいです。

そこで今回は、ソフトウェア工学系のトップカンファレンスであるICSE(International Conference of Software Engineering)の論文をざっくり読んでゆきます。

折角なので、まだ開催されていない(2017/05月開催)ICSE 2017の論文を読みます。
まだなのにどうやって読むの?と思いますが、Preprintsとして事前に論文を公開してくれている場合があるので、それを利用します。
公式Twitterによると、おおよそ66%の論文がpreprintsとして集まっているようです。すごい。
https://twitter.com/ICSEconf/status/841892455809187840

preprintsはこちらに掲載されていますので、原文を読みたい方はこちらから辿ってください。
https://docs.google.com/spreadsheets/d/19rjBeNklsfFdggZ7Xj2h1oNpIXfZuR-hYrSQrk4xCfc/edit#gid=1276835202

注意

  • ICSEの全論文ではありません
    • 9つだけ
    • Technical Research Papersの中で、preprintsが公開されているもの
  • タイトルとアブストだけ読むため、手法の詳細には触れません

論文内容まとめ

Analyzing APIs Documentation and Code to Detect Directive Defects

http://www.zora.uzh.ch/134450/1/YU-ICSE.pdf

著者: Yu Zhou, Ruihang Gu, Taolue Chen, Zhiqiu Huang, Sebastiano Panichella and Harald Gall(南京大学・ロンドン大学・チューリッヒ大学)

概要の大雑把な訳:
APIドキュメントは開発者にとって最も重要なドキュメントだが、よくソースコードとずれが生じており、開発の効率や品質に関わる問題になっている。
本論文では、ソースコード理解と自然言語処理を利用し、自動でこのずれを検出する方法を提案している。
手法としては、引数の制約(NonNull、型など)と吐く可能性のある例外に関するAPIドキュメントの記述を利用する。
この内容を用いて、制約ソルバを使ってずれを検出する。
JDK1.8のAPIに対して実験し、2000 APIの中から1146個のずれを検出した。
プレシジョンは81.6%、リコールは82.0%となり、実際に使えることを示した。

感想: ドキュメントとずれている状態はよく直面する。ぜひとも早く普及して解消されて欲しい。ドキュメント中に「ずれてますよ」というマークがあるだけでも便利そう。

An Unsupervised Approach for Discovering Relevant Tutorial Fragments for APIs

http://oscar-lab.org/paper/icse_17_frapt.pdf

著者: He Jiang, Jingxuan Zhang, Zhilei Ren and Tao Zhang(大連大学、遼寧省の研究所、武漢大学、ハルビン工程大学)

概要の大雑把な訳:
開発者はどんどんAPIチュートリアルに頼るようになっている。
しかし、不慣れなAPIの中からチュートリアルのコード片を探すのは未だに困難である。
既存手法では、大量にコーパスを手動で作る必要があり、悩まされていた。
本論文では、FRAPT(APIをページランクとトピックによりコード片をリコメンドする)というアプローチを取り、自動で行えるようにしている。
FRAPTでは、APIに関連するチュートリアルとなるコード片を決定する上で生じる2つの課題に挑戦している。
1つ目は、「代名詞と変数の解像度問題」として、チュートリアルとなるコード片の中からAPIを特定し、抽象的な代名詞と変数をAPI名や即した文脈に置き換える、という問題。
2つ目は、「説明にならないコード片の検出問題」として、不要なコード片を除去するフィルターを作成する問題。
これらの問題を解決し、トピックモデルとページランクアルゴリズムの両方を適用することで相関係数の算出と関連するコード片の集約ができた。
オープンなデータに対して本手法を適用し、既存手法に比べF値で8.77%→12.32%に精度が向上した。
加えて、本手法の核となるコンポーネントの効率性も検証した。

感想: 既存手法でも同様かもしれないが、「開発者はAPIドキュメントのチュートリアルに頼っている」という切り口が良い。チュートリアルはAPIの習熟に大きく関わる要因なので、こちらも実務に直接関わってきそうな内容。

Efficient Detection of Thread Safety Violations via Coverage-Guided Generation of Concurrent Tests

http://mp.binaervarianz.de/icse2017-covcon.pdf

著者: Ankit Choudhary, Shan Lu and Michael Pradel(ダルムシュタット工科大学、シカゴ大学)

概要の大雑把な訳:
並列プログラムを書くのは難しく、困難な問題を隠してくれているスレッドセーフなクラスに頼ることが多い。
これらのクラスのテストは並列プログラムの正確性を担保する上で極めて重要な問題である。
テストの見逃しを減らすため、自動生成した並列テストを作成することは効果的な手法である。
ランダムに作成する既存手法があるが、これは計算コストが高い解析に依存していたり、特定のバグに依存していたりする(アトミック性など)。
本論文では、CovConというカバレッジでガイドしてテストを作成する手法を提案する。
2つのメソッドをペアにして、それらがどれくらい並列に実行されたかを計測し、その頻度に着目する。
本手法では特定のバグのパターンに依存するものではなく、あらゆる並列実行時のバグを検出できる可能性がある。
また、計算コストが高くなく、短時間で多くのテストを生成することが出来る。
本論文ではCovConを18個のスレッドセーフなJavaのクラスに適用し、17のバグを見つけた。
既存の5つの手法に比べ、CovConは短時間でかつより多くのバグを見つけた。
詳細には、38/47のケースでより早くバグを見つけ、22/47のケースでは4倍近い速度で検出をした。

感想: 手法としてはシンプルに見えるが、検出精度も速度も大きく向上しているらしく意外性がある。スレッドセーフなクラスには多くの人が頼っていると思うので、その裏側で日々研究がされているのを見ると面白い。

RClassify: Classifying Race Conditions in Web Applications via Deterministic Replay

http://www-bcf.usc.edu/~wang626/pubDOC/ZhangW17.pdf

著者: Lu Zhang and Chao Wang(バージニア工科大学、南カリフォルニア大学)

概要の大雑把な訳:
競合状態はWebアプリケーションでよく発生するが、原因を突き止めるのが難しく、修復するのも難しい。
既存の競合状態検出ツールはあるが、「ニセの検出(false positive)」が大量に出る。
ニセの検出とは、bogus(実際にはまず起こらない)、benign(エラーを引き起こさない)の両方を示す。
手動でこれらの検出結果を調べることは大変で間違えやすいため、これらの検出結果を提示することは非生産的である。
本論文ではプラットフォームに囚われない、決定性の実行に基づいた手法により、実際に起こり有害な競合状態を特定する。
手法としては、競合するイベントのペアを2つの異なった順番で実行し、プログラムの状態に及ぼす影響を評価する。
ここでは、「有害な競合」を (1)両方のイベント実行が実行完了すること、(2)それぞれが異なった状態を生み出すこと、という両方の条件を満たすものと定義する。
Fortune500の企業で使われている大規模で実際に使われているWebサイトに対して本手法を適用し、既存の手法を大きく上回る効果を示した。

感想: 概要だけでは詳細は分からないが、実際にかけてみやすい類のツールなので、公開されていたら類似ツールも含めて試してみよう。

Repairing Event Race Errors by Controlling Nondeterminism

http://cs.au.dk/~amoeller/papers/eventracecommander/paper.pdf

著者: Christoffer Quist Adamsen, Anders Møller, Rezwana Karim, Manu Sridharan, Frank Tip and Koushik Sen(オーフス大学、米サムスン研究所、ノースイースタン大学、カリフォルニア大学バークレー校)

概要の大雑把な訳:
モダンなWebアプリはイベントドリブンで書かれており、イベントハンドラが非同期にユーザーやシステムのイベントを処理する。
このスタイルでは、非決定性が致命的なエラーを生じさせうる。
近年の研究ではイベント競合とその分類(有害かそうでないか)にフォーカスをしている。
しかし、ソースコードをこれらの競合が起こらないようにするのは難しく間違えやすいため、よくない実行をさせない方が望ましい。
本論文では、JavaScriptで書かれたWebアプリケーションで発生するイベント競合を自動修正する手法を提案する。
この手法では、イベントを後で実行させたり破棄して競合を回避する、というポリシーを適用するため、イベントコントローラーを用意しブラウザ内のイベントハンドラのスケジューリングを制限する。
本手法はEventRaceCommanderというツールにし、Fortune500の中で大きな20のWebアプリケーションに対して適用し、100以上のイベント競合を検出した。
アプリケーションに依存しない修正ポリシーは、パフォーマンスやユーザー体験を大きく損なうこと無く十分にイベント競合を解消したが、特定の修正ポリシーを定めた方が望ましい場合もある。

感想: 1つ前の論文とは別のアプローチで競合状態に挑んでいる。スケジューリングの方法がかなり複雑になりそう。実行時に発生させないようイベント自体をずらすのは面白いが、逆に分かりにくいバグの原因が増えてしまいそう。

TRAVIOLI: A Dynamic Analysis for Detecting Data-Structure Traversals

https://rohanpadhye.github.io/travioli/paper.pdf

著者: Rohan Padhye and Koushik Sen(カリフォルニア大学)

概要の大雑把な訳:
トラバーサル(機械的に一部・全部のデータを辿る処理)はデータ構造に対する最も基礎的な処理である。
本論文ではTRAVIOLIという動的解析でトラバーサルを検知するツールを提案する。
ここでは、ループや再帰の中にある、配列やリスト・木などのトラバーサルを正確に検出することを可能にする、非環(acyclic)な実行コンテキストを導入する。
本論文では、「どのようにTRAVIOLIの実行結果をデータ構造のトラバーサルの可視化に利用するか」「パフォーマンスの劣化はどの程度か」「無駄なトラバーサルによる性能劣化の検出をどうするか」について説明する。
実際に使われている5つのJavaScriptのプログラムに適用し、誤検出は4%以下だった。
検出したうちの93.75%にパフォーマンステストを行った(訳注: 結果は概要にはない)。
また、D3とexpressに性能劣化を検出した。

感想: 面白いテーマ。概要を読む限り、動的解析だとどうしても誤検出が増えそうだが、どのようにして減らしているのかは読み込まないと分からなさそう。

ProEva: Runtime Proactive Performance Evaluation Based on Continuous-Time Markov Chains

http://www.comp.nus.edu.sg/~david/Publications/icse2017-preprint.pdf

著者: Guoxin Su, Taolue Chen, Yuan Feng and David Rosenblum(ウーロンゴン大学、ロンドン大学、シドニー工科大学、シンガポール国立大学)

概要の大雑把な訳:
ソフトウェアシステム、特にサービスは実行時のパフォーマンスが要求される。
もしパフォーマンスが落ちている場合、再設定によって対策をする。
しかし、その対策が反映されるまでしばらくラグがある。
そのため、現在のシステムを受動的に監視することは勿論大事だが、将来のパフォーマンスを予測することも大事である。
連続時間マルコフ連鎖(CTMC)は時間を区切ったパフォーマンスのメトリクスを算出するのに適している(例えば、ある将来の時間でパフォーマンスの劣化がどれくらい起こりうるかを求める)。
CTMCを活かす上で課題となるのが、遷移率などのモデルパラメーターを実行中に計測すること。
これらのパラメーターはシステムや環境によって頻繁に更新されるため、正確な値を与えるのが困難である。
本論文では、既存のCTMCモデルを、遷移率として不正確で区間の中での予測値を許容するよう拡張したProEvaというフレームワークを作った。
ProEvaは不正確なモデルの出力として、漸近的な式と境界を計算することをコアな手法としている。
精度とオーバーヘッドについても触れている。

感想: 数値予測の理論的な論文なので、読むのは骨が折れそう。サービス監視系のツール開発に特に役立つ内容に見える。

Clone Refactoring with Lambda Expressions

https://users.encs.concordia.ca/~nikolaos/publications/ICSE_2017.pdf

著者: Nikolaos Tsantalis, Davood Mazinanian and Shahriar Rostami Dovom(コンコルディア大学)

概要の大雑把な訳:
ラムダ式はJava8で関数型プログラミングをサポートするため導入され、関数を引数として渡すことで「動作のパラメーター化」を実現した。
既存のコードクローンは異なった動作をするものが大部分(Type2、Type3に分類されるもの)である。
しかし、筆者らの知る限りでは、これらのコードクローンをラムダ式を用いて共通化する研究は無かった。
本論文では、ふるまいの違うコードクローンを、ラムダ式を用いてリファクタリングできるかを調べる手法を提案する。
さらに、大規模なコードクローンのデータセットに対し適用し、適用可能性と、リファクタリングに使われたラムダ式の特徴を示している。
結果、他の手法ではリファクタリングできなかった多くのクローンに対して、ラムダ式を使ってリファクタリングができた。

感想: 普段からよくこの手のリファクタリングはするものの、そういえば研究されてるという話を聞かなかった。いい所を突いた感じがある。リファクタリング提案機能が備わっているIDEが増えているが、この機能が搭載されれば開発の強力なサポートになるのでは。期待。

Characterizing and Detecting Anti-patterns in the Logging Code

http://nemo9cby.github.io/resources/pubs/icse2017_chen.pdf

著者: Boyuan Chen and Zhen Ming Jack Jiang(SCALE Lab)

概要の大雑把な訳:
ログのスニペット(Log.infoやSystem.out.printlnなど)は開発者がソフトウェアに埋め込む。
ログコードが増える度に実行時のコンテキストが増えるが、メンテナンスの負担があり多すぎるログコードは望ましくない(訳注: 例えば、ログの中で出力している変数が無くなったり、NPEになったり)。
加えて、パフォーマンスが落ちたり、ディスクI/Oが増えたりする問題もある。
最近の研究では、効果的なロギング方法がしっかりと定められたコーディングガイドラインは無いとされている。
ロギングに関する先行研究の多くは、「どこに」「何を」ログを取るか、に主に取り組んでおり、「どのように」ログを取るか(ロギングコードの開発・保守性)に取り組んだ研究は非常に少ない。
本論文では、ロギングのアンチパターンを特徴づけ、及び検出を行い、「どのように」ログを取るかの問題に取り組む。
ロギングコードの大部分は対象となるコードの進化に伴い変化してゆき、残りはアンチパターンの解消のために修正される。
本研究では、3つのよくメンテナンスされているOSS(ActiveMQ、Hadoop、Maven)に対し352個のロギングコードに対する変更をチェックした。
その結果、6つのアンチパターンが見つかった。
この発見の有用性を示すため、これらのアンチパターンの検知をするLCAnalyzerというツールに実装した。
LCAnalyzerを使った実験では、リコールが95%、プレシジョンが60%でアンチパターンを発見し、未知のアンチパターンの発見にも使えることを示した。
また、フィードバックを得るため、64の代表的な検出結果を10個のOSSで修正して送った所、46個(72%)が(この時点で)マージされた。

感想: 普段開発する中で、たしかにロギング処理はとっ散らかりがち。実際に論文の先を読むと、「Nullable objects」「Wrong verbosity level」など、よくやってしまうものが多い。たかがログのコードと侮らず、ちゃんとメンテナンスしてゆくことが必要だと考えると、アンチパターンという分かりやすい形でまとめてくれているのはとても有用だと思う。

まとめ

まだまだ面白そうな論文がありますが、ここで一旦終わります。
ソフトウェア工学という分野の特性か、上記9つの中でも実用的な内容が多く、実際に使われるイメージが持てるものが多かったです。

ぜひ、上記概要で気になった論文があれば原文を読んでみてください。 また、残りの論文もタイトルを眺めて、面白そうなものを探すと良いかと思います。

おまけ

ハウテレビジョンでは、論文の好きな仲間を募集しています。
Webエンジニア
エンジニアインターンシップ

静的サイトをサクサク作成・公開するためのサービス・ライブラリ選定

弊社ハウテレビジョンでは、毎週一日R&D dayという名目で、業務と直接関係しない技術を学んでみたり、今まであまり触れてこなかった領域を調べたりしています。

通常業務ではいわゆる「動的な」サイトを作成しているため、意外と静的なサイト作成に触れてきませんでした。
という訳で、今回は静的なサイトをいかに簡単に作れるか、調査・実践しました。

折角なので、前々から使おうと思っていたMaterial Component for the web (MDC) を利用します。

やること

  • 静的サイトの1ページを作ります
    • 固定ツールバー(ヘッダー)付き
  • デザインは1から組み上げることとします(既存のテーマに頼らない)
  • Material Component for the web (MDC)を使います

やったことを以下の流れで記載します。

  1. 静的サイトジェネレーターの選定
  2. デザイン
  3. デプロイ先の選定

手順

静的サイトジェネレーターの選定

お手軽に作る上で、利用者の多さは非常に重要です。
というわけで、StaticGenを参考にして人気のあるリポジトリを探してみます。

見てみると、Jekyllは未だ衰えない人気があり、ドキュメントも整備されています。
今回はこちらを使って組み上げることにします。

デザイン

今回はマテリアルデザインで仕上げてみようと思います。
Material Design Liteの後継であるMaterial Components for the webをライブラリとして用います。

MDCのインストール

package.jsonnpm init などで作成し、MDCをインストールします。

npm install --save material-components-web

node_modules の中に必要なファイルが出来上がりますので、これをHTMLから利用します。
Getting startedでも述べられているように、CDNを使ったり、コンポーネントごとのインストールもできますので、用途にあわせて使い分けることができます。

デザインを考える

MDCではリポジトリの中に各コンポーネントのdemoが用意されています。
また、実際に動いているサンプルがホスティングされています(GAEなあたりGoogleらしさを感じさせます)。
これらや、他のマテリアルデザインで作られているサイトを参考にしてデザインを考えます。

本題とは逸れますが、以下にマテリアルデザイン系のお役立ちリンクを少しだけ記載しておきます。
マテリアルデザインは現在非常に勢いがあり、日々新しい物がでてきているため、サンプルには事欠きません。

簡素なページを作る

大まかにデザインが決まったらコーディングをしてゆきます。
1ファイルだと見づらいため、Directory structure - Jekyllを参考にファイルを分割して配置します。

まずは/layout/_default.html にMDCを利用する骨子を作ります。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>{{ title }}</title>

        <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
        <link rel="stylesheet" href="node_modules/material-components-web/dist/material-components-web.css">
    </head>

    <body class="mdc-typography demo-body">
        {% include header.html %}

        { content }

        <script src="node_modules/material-components-web/dist/material-components-web.js"></script>
        <script>mdc.autoInit();</script>
    </body>
</html>

このレイアウトファイルを使って各ページを構成します(今回は1ファイルだけですが)。
ヘッダーは別ファイルにして切り出し、各ページの内容のみそれぞれのファイルに書いてゆきます。

_includes/header.html は以下のように、固定toolbarを配置します。

<header class="mdc-toolbar mdc-toolbar--fixed">
    <section class="mdc-toolbar__section mdc-toolbar__section--align-start">
        <span class="mdc-toolbar__title">Title</span>
    </section>
    <section class="mdc-toolbar__section mdc-toolbar__section--align-end">
        <span>Action</span>
    </section>
</header>

index.html はとりあえずh2だけ置いておきます。
固定ツールバーなので、mdc-toolbar-fixed-adjust を指定しないと文字がツールバーにめり込んでしまいます。

---
layout: default
---

<main class="mdc-toolbar-fixed-adjust">
    <h2 class="mdc-typography--display2">Hello!</h2>
</main>

ここまで書くと、以下のような表示結果になりました。

f:id:itamisky:20170330162141p:plain

グリッドレイアウト

デモページにあるように、グリッドレイアウトも簡単に行えます。

カードをグリッドで表示してみます。
画像が無いと見栄えがしないので、FlickerでCreative Commonsの画像を拾ってきました。
https://www.flickr.com/photos/kamikura/1437333121

index.html を以下のように、グリッドレイアウトでカードを3つ表示するように変更しました。

---
layout: default
---

<style>
    .card__16-9-media {
        background-image: url("images/bird.jpg");
        background-size: cover;
        background-repeat: no-repeat;
        height: 12.313rem; /* 197sp, for 16:9 ratio with 350sp demo card width */
    }
    .card__big-image {
        height: 21.875rem; /* 350sp */
        background-image: url("images/bird.jpg");
        background-size: cover;
        background-repeat: no-repeat;
    }
    .card__big-image .mdc-card__primary,
    .card__big-image .mdc-card__actions {
        background: rgba(0, 0, 0, .4);
    }
</style>

<main class="mdc-toolbar-fixed-adjust">
    <div class="mdc-layout-grid">
        <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-4">
            <div class="mdc-card demo-card">
                <section class="mdc-card__media card__16-9-media"></section>
                <section class="mdc-card__supporting-text">
                    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor.
                </section>
            </div>
        </div>
        <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-4">
            <div class="mdc-card demo-card">
                <div class="mdc-card__horizontal-block">
                    <section class="mdc-card__primary">
                        <h1 class="mdc-card__title mdc-card__title--large">Title here</h1>
                        <h2 class="mdc-card__subtitle">Subtitle here</h2>
                    </section>
                    <img class="mdc-card__media-item" src="images/bird.jpg" />
                </div>
                <section class="mdc-card__actions">
                    <button class="mdc-button mdc-button--compact mdc-card__action">Action 1</button>
                    <button class="mdc-button mdc-button--compact mdc-card__action">Action 2</button>
                </section>
            </div>
        </div>
        <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-4">
            <div class="mdc-card mdc-card--theme-dark demo-card card__big-image">
                <section class="mdc-card__primary">
                    <h1 class="mdc-card__title mdc-card__title--large">Title goes here</h1>
                    <h2 class="mdc-card__subtitle">Subtitle here</h2>
                </section>
                <section class="mdc-card__actions">
                    <button class="mdc-button mdc-button--compact mdc-button--theme-dark mdc-card__action">Action 1</button>
                    <button class="mdc-button mdc-button--compact mdc-button--theme-dark mdc-card__action">Action 2</button>
                </section>
            </div>
        </div>
    </div>
</main>

表示は以下のようになりました。
ウィンドウサイズにあわせて配置が変わっています。

f:id:itamisky:20170330162231p:plain f:id:itamisky:20170330162236p:plain f:id:itamisky:20170330162240p:plain

グリッドデザインもMDCを使うと簡単に利用できました。

デプロイ先の選定

S3やGithub Pagesなどの選択肢もありますが、ビルドを含めたワークフローも管理して欲しいため、Netlifyを使います。
独自ドメインも付けられるので、ちょっとしたサイトを公開するには十分だと思います。

まず、githubに上記のコードを含めたリポジトリをpushします。
その後、Netlify上で対象リポジトリへ連携設定を行い、jekyll に対する設定がされていることを確認します。

なんとこれだけで、bundle install からjekyll buildnpm install を行ってデプロイがなされます。
xxx.netlify.com のアドレスが発行されるので、これにアクセスするとローカルで見ていた画面と同様に表示がされるかと思います。

更新する

一度設定をしてしまえば、後はgithubの該当ブランチに更新があるたびに自動でデプロイが行われます。

ローカルで文言を変更して、git push やプルリクをマージします。
しばらくしてからNetlifyのデプロイ状況を見ると、自動で更新がなされていることがわかります。
とてもお手軽です。

f:id:itamisky:20170330162347p:plain

まとめ

Jekyll、MDC、Netlify(+github)を使うことで、非常に簡単に「生成・デザイン・デプロイ」の一連の作業が行えました。
今回は一人で開発しましたが、開発者が複数人になっても通常のgitとフローが同じため分かりやすいです。

複数人でページデザインの確認を行ったり、プロトタイプを上げる際に特に有用ですので、ぜひ上記構成を検討してみてください。

おまけ

ハウテレビジョンでは、静的サイトの可能性を探る鳥が好きな仲間を募集しています。
Webエンジニア
エンジニアインターンシップ

SIerからWeb系スタートアップ/ベンチャー企業に転職して感じたこと

だれ?

2015年11月にハウテレビジョンに入社いたしました。
入社する以前は、某緑のナビ会社でスマホ向けのAPI書いたり
中小SIerでWebシステムの開発をやってたプログラマでございます。
なんやかんやでエンジニアは歴10年目に突入しました。

今回はいつもの技術ネタではなく、少し趣を変えて
私がハウテレビジョンという会社に転職して感じたことをレポートします。

ハウテレビジョンって何してる会社?

外資・日系トップ企業を目指す学生のための就職活動サイト
「外資就活ドットコム」を開発・運営しております。 gaishishukatsu.com

入社を決めた理由

なんとなく転職支援サイトに登録していたのですが、そこでスカウトを頂いたのがハウテレビジョンを知ったきっかけです。

私の場合、スカウトを頂いた後にオフィスにご招待いただき、カジュアルな感じの面談と社内の案内をしていただきました。 社内を見学しながら感じたのは「エンジニアにとっての働きやすさ」についてよく考えてる会社だな〜ということです。
開発部門に関していうと、高性能な椅子やハイスペックなマシンはもちろんのこと、希望すれば好きなキーボードを購入してもらえたりコーヒーやジュースが飲み放題だったり、 広く開放的なデスクだったりと、エンジニアにとって働きやすい環境になるように配慮がなされています。

また、開発手法としてスクラム開発を取り入れ、ソース管理はGitHub、サービスのデプロイは自動化されていたりと、 新しい技術やツールを積極的に導入しており、旧態依然としない挑戦的な姿勢であることも入社を決めたきっかけになりました。

快適な開発環境

エンジニアにとってストレスの少ない環境は大切だと思います。
ストレスフルな環境は、それだけで「やる気」や「作業効率」を下げてしまうものです。

ここの環境はどうかというと、先ほど述べたとおりデスクやマシン周りについては十分なものが用意されています。 他にもエンジニアの席には電話が無いので、呼び出し音に悩まされることがありません。 さらに席の近くには空気清浄機が設置されていたりと、環境改善のための努力には並々ならぬものを感じますw

ただ全く不満がないわけではなく、人数の増加にともない会議室や打ち合わせスペースが足りなくなってきています。 これは今後の課題ですね。 f:id:yumasukey:20160203165914j:plain:w300

職場の雰囲気

オフィス内の座席の配置は、それぞれの部門が作業に集中しやすいようにデザインされています。 営業・運用担当者のいるブロックとエンジニアのいるブロックは、少し離れてレイアウトされており エンジニア側からは、他部門の電話や雑音が聞こえにくくなっています。

しかし席が離れているからといって、部門間のコミュニケーションが少ないということもありません。 社員全員がSlackを使って頻繁にやり取りしています。
Slackには目的別に色々なチャネルが作られており、なかには雑談用のチャネルもあります。 夕方になると「飲みにいかない〜?」っていう募集もSlackで流れていたりしますw

f:id:yumasukey:20160203163719p:plain:w300

エンジニア同士の席も近いので、気軽に質問したり議論したりとコミュニケーションは活発! ちょっと集中して打ち合わせしたい時などは、席の近くにある打ち合わせスペース(通称ファミレス席)に移動して行います。 f:id:yumasukey:20160222174231j:plain:w300f:id:yumasukey:20160222173721j:plain:w300

ユニークな取り組み

弊社で取り組んでいるユニークな試みをいくつか紹介したいと思います。

Qiita賞

書くのが面倒な日報や業務報告だって、読み手の反応があれば頑張れるというもの。弊社ではQiitaTeamに日報を投稿しており、面白い記事にはメンバーから「いいね!」がつけられ、その週で多くの「いいね!」を獲得するとQiita賞として豪華な食事会に招待されたりします。

f:id:yumasukey:20160203165300j:plain:w300

LiveOps

日々の業務では、サービスの不具合対応やデータ更新作業など、様々な依頼がユーザーサポートチームやコンテンツチームからSlackを通じてエンジニアに送られてきます。しかしエンジニア全員が都度これに対応していると、自身のタスクを消化できなかったり、集中力が途切れてしまったりと生産性が下がってしまいます。そこで毎日「真のランダム」により公平に担当エンジニアが任命され、このような依頼に対応します。担当者が決まっているので、他のエンジニアは自分の作業に集中することができます。

狩猟祭

モンハンXで一狩り行こうぜ!
・・・ではなく、日々あがってくる改善要望には優先度が低いがために対応が後回しとなり、なかなか処理されない案件が溜まってきます。 それら溜まった案件を定期的に一気に片付けてしまおう!しかも楽しんで!(`・ω・)b 

というわけで編み出されたのが「狩猟祭」です。 月に一度くらいの頻度で開催され、対象案件にはポイントが付与されます。エンジニアは案件を処理することで、そのポイントを競います。 そして一番多くポイントを稼いだエンジニアには名誉とご褒美が与えられるというものです。

職場が渋谷

弊社のオフィスがある場所は渋谷! 複数路線が乗り入れているため通勤が楽ですし、お買い物にも便利です! しかもオフィスは2014年に引っ越したばかりなのでとっても綺麗。 今年の夏にはさらに広く快適なオフィスになるという噂も。
渋谷はIT企業が集まっている場所でもあるので、近所のオフィスで開催される勉強会に仕事帰りに参加なんてこともできます。
また渋谷は飲食店の激戦区!オフィスの周りにはおいしいお店がいっぱいです。 (`・ω・)ランチが捗ります!そして太ります!

f:id:yumasukey:20160203173838j:plain:w300

まとめ

以上、私がハウテレビジョンに入社して感じたことでした。 我ながら、この会社の良いところをいっぱいご紹介できたと思います。( ー`дー´)キリッ

しかし実はまだお伝えしたいことが1つだけございます。

現在弊社では


エンジニアが足りておりません!


サービスの拡大にともない、さらなる需要増が見込まれる中でエンジニアの数が圧倒的に不足しております!\(^o^)/

そんなわけで

ハウテレビジョンでは一緒に働いていただける
エンジニアを募集しております。

http://howtelevision.co.jp/recruit


ぜひご検討のほど、よろしくお願いいたします m(_ _)m