読者です 読者をやめる 読者になる 読者になる

MERYにおける商品人気順の話

こんにちは。開発部の sian-izm です。

1年程前からペロリでエンジニアを始め、現在はECサービスの開発をメインに行っています。

今回はMERYのEC商品人気順のアイディアについて紹介します。 実際にはこのアイディアをベースにいくつかのチューニングを入れています。

人気順の切り口

人気順と言っても、販売数やPV数、商品毎にスコアのようなものを持っていれば、それを利用したりといくつか切り口があると思います。

MERYでは、LOVE という形で、ユーザーがお気に入り商品を保存しておくことができます。 そのアクションのタイミング(時刻)と数はデータベースに保存しており、商品検索にはElasticsearchを利用しています。 そのため、比較的容易に商品スコアを算出できそうであったこと、加えてある程度のサンプル数があることから、LOVEを人気順の切り口として使ってみようと考えました。

商品のスコア定義

全商品データを累積LOVE数でソートしたものでは、古くからの人気商品が常に上位に出続けてしまいます。 また、MERYの特徴としてEC商品は、商品特集や記事での紹介、またはトレンドワード等による外部検索からユーザーの目に届くことが多く、LOVEされるタイミングが必ずしも公開直後に多いとは限りません。

この点を考えて、まず思いついたのが直近何日かのLOVEを窓関数で畳み込む方法です。 ただし、これを全商品分計算するとなるとかなりのコストがかかってしまいますね。 そこで次に考えたのが指数関数なのですが、これだと再計算がとても簡単になりました。

式で表すと以下のような形です。

{
\displaystyle
S_t = \lambda S_{t-1} + s_t
}

S: 累積スコア, s: ある時間にされたLOVE数, λ: 減衰定数

今回は、バッチによる日次更新とすることにしました。 λによって、例えばある一日についたLOVE数(1000件の場合)は、時間経過により以下形で減衰します。 (日次更新だと実際は非連続となります)

f:id:sian-izm:20160621132941p:plain:w500

イメージ的には、LOVEを1週間後や1ヶ月後にどの程度残しておきたいかという感じですね。

更新処理流れ

例えばRailsで実装すると以下のような感じになります。

ProductScore.update_all("value = value * #{DECAY_CONSTANT}")  # (1)

ProductLoveAction.created_product_ids_and_counts(1.day).each do |product_id, count|  # (2)

  if product_score = ProductScore.find_by_product_id(product_id)
    product_score.value += count
    product_score.save!
  else
    ProductScore.create!(product_id: product_id, value: count)
  end
end

Product.import refresh: true  # (3)

(1) DB内の全商品のスコアを定数で減衰させる
(2) 特定期間で作成された、LoveAction(LOVE数)を商品単位で取得し加算する
(3) スコア情報をもたせている全商品のElasticsearchのindexを更新する
importelasticsearch-rails のメソッドで、テーブルのindexが更新されます

これを1日一回バッチで実行します。

最後に

以上が、人気順のベースアイデアとなります。 これによって、低い更新コストで、新しさだけでなく急上昇キーワードや特集等によるユーザーからの注目にも、比較的効果の期待できるスコア付けすることができました。

一方、一ユーザーが同じ商品にLOVEをすることはできないため、シーズン問わない根強い商品や複数回の特集などにより訪れるブーム等をこれだけでは適切に表現することができません。 加えて、もっとそもそもの話、単純にこのスコア順が本当に人気順(〜ユーザーにオススメするべき順)なのか?という課題も考えるべきだと思います。 前者は、当初設計的に難しかったPVベースでのランキングにもしすることができればよりよくできるかもしれませんし、後者に関しては例えば在庫がない商品も同等に出していてよいのか等あります。

また調べた範囲では、よりシンプルなロジックで累計スコアをアイテムの作成時間に応じて減衰させるHacker News*1Reddit Ranking*2アルゴリズム(それぞれの参考記事を下部注釈に記載)等OSSで提供されているものもありました。 これらは、ニュース等の新しさがポイントとなるランキングには適していると思いますが、MERYのEC商品一覧では、特性上本記事で紹介したアイデアの方がよかったと考えています。

今後も様々なタイプのランキング等を作ることはあると思いますので、こういったアイデアと合わせて検討しながら、開発を行っていきたいと思っています。

今回は以上になります。

ペロリではMERYの開発 & 改善をしてくださる方を大いにお待ちしております。 www.wantedly.com

AngularJS で爆速で開発するベストプラクティス

こんにちは、MERY で主に Android の開発を行っている栗野です。 今回は Android のことではなく、以前広告プラットフォームを作っていた時に使っていた AngularJS に関して話したいと思います。

現状のフロントエンド界隈の流れ

Facebook が出してきた React + Flux が一気にシェアを広め、webpack などのビルドツールも広まってきて、基本的には React + Flux + webpack みたいな構成で開発するのがモダンなのではないでしょうか。個人的にもよくこの構成は触っており、とても便利に感じています。

なぜ今AngularJS?

そんな中でなぜ AngularJS にフォーカスするかというと、やはりある特定のケースにおいては、圧倒的に AngularJS での開発を推奨するからです。そのケースとは SEO などが一切関係しないシステムの管理画面系アプリケーション・CRUD系アプリケーションです。

以下では AngularJS の基本概念、特徴から Rails との組み合わせで爆速なフロント開発ができるようになるまでを解説したいと思います。

AngularJSの特徴と概念

https://angularjs.org/angularjs.org

フロントエンドのMVWなフルスタックフレームワークです。 基本的に API との通信を司る $http$resource をラッピングした Service モジュール、 そこから得られたデータを View に仲介する Controller モジュール、 React でいう View コンポーネントに該当する Directive モジュール、 アプリケーションのルーティングを司る $route などを組み合わせてアプリケーションを構築していきます。他にも公式サイトを見て分かる通りたくさんのモジュール(約束事)が存在し、それらを用いながら開発を進めていきます。基本的に開発に必要なことは全て AngularJS が準備しているので、他のライブラリの導入も必要ないですし、 UI ライブラリなども AngularUIAngular Modules などがありますので Bootstrap 的な開発ができます。 モジュールごとに変数が閉じられているので、 webpack などの導入も必要ありません。

(もちろんタスクランナーは適宜導入したほうがよいです。)

AngularJS で有名なのがデータバインディングだと思います。データバインディングのおかげでサンプルコードレベルであればとても簡単に開発できるのですが、大規模なシステムを構築しようとすると上記のたくさんの約束事を組み合わせていく必要があり、それが大きなハードルになるのではないかと思います。

しかし、逆にこのそれぞれのモジュールの使い方を知ってしまえば、大規模なシステムでも効率的に AngularJS の潤沢なリソースを利用して開発を行うことができます。

qiita.com

こちらの記事が AngularJS の仕組みを理解する上で参考になると思います。

AngularJSを用いた開発の流れ

別に Rails である必要はないのですが、弊社の広告プラットフォームを Rails で開発していたため、こちらを題材にして実際に大規模アプリケーションを構築する流れを紹介したいと思います。

ここでキーマンとなるのが JSON SchemangMockE2E です。

JSON Schema とは、名前の通り JSON の型を定義するものになります。 Rails の場合、JSON Schema のバリデーターを Rack ミドルウェアとして実装されており、フロントとバックエンドの話し方をここで制約付けることができます。

tech.degica.com

こちらが参考になると思います。

ngMockE2E とは、AngularJS に用意されているモジュールの一つで API 通信をインターセプトし、自分の定義したモックデータを返すためのものになります。ステータスコードなどもいじることが可能です。

この2つの仕組みを用いて、バックエンド開発者と円滑に開発を進める手順をまとめました。

1, JSON Schema を定義しましょう

バックエンド開発者とフロントエンド開発者で APIスキーマを確認し合い、決まったスキーマJSON Schema として定義します

例) schema.yml

---
"$schema": http://json-schema.org/draft-04/hyper-schema
definitions:
  sample: !include sample.yml
properties:
  sample:
   "$ref": "#/definitions/sample"
type:
  - object
description: A schema for Sample API.
links:
  - href: http://sample.api.jp
    rel: self
title: Sample API

例) sample.yml

description: Sample API のスキーマ定義です。
id: schemata/sample
title: Sample(Sample API)
type:
  - object
links:
  - description: Sample API の GET
    href: "/api/sample"
    method: GET
    schema:
      properties:
        count:
          type: string
          example: "1"
    targetSchema:
      properties:
        data:
          type: object
          properties:
            id:
              type: integer
              example: 1
            name:
              type: string
              example: "サンプルAPI"  

詳細な説明は省きますが、

/api/sample というエンドポイントに対して count をいうクエリストリングをつけて GET でリクエストしてください。そうすると

{
  "data": [
    {
      "id": 1,
      "name": "string",
    }
  ]
}

このようなレスポンスを返します。」という意味になります。 このスキーマが決まれば、フロントエンドの開発者は ngMockE2E でこれに即したモックを作成します。上記のスキーマに合わせて定義すると、

例)mock-e2e.js

angular.module('SampleApp.MockE2E', ['ngMockE2E'])
  .run(function ($httpBackend, mockData) {
    $httpBackend.whenGET(/^\/?views\//).passThrough();
    $httpBackend.whenGET(/^\/?i18n\//).passThrough();

    var urlParameterToJson = function (urlParamter) {
      return JSON.parse(
        '{"' +
          decodeURI(urlParamter)
            .replace(/\?/g, '').replace(/"/g, '\\"')
            .replace(/&/g, '","').replace(/=/g,'":"') +
        '"}'
      );
    }

    $httpBackend.whenGET(/^\/api\/sample(\?[^?]+)?$/)
      .respond(function (method, url, data, headers) {
      var urlMatch = url.match(/\/api\/sample(\?[^?]+)?/);

      var params;
      if (urlMatch.length > 1 && typeof urlMatch[1] !== 'undefined') {
        params = urlParameterToJson(urlMatch[1]);
      }

      return [200, {data: mockData.sample}, {}];
    });
  });

例)mock-data.js

angular.module('SampleApp')
  .factory('mockData', function (sampleData) {
    return {
      sample: sampleData.generate()
    }
  });

例)sample-data.js

angular.module('SampleApp')
  .factory('sampleData', function () {
    return {
      generate: function () {
        var results = [];

        for (var i = 1; i <= 100; i++) {
          results.push({
            id: i,
            name: 'Sample Name ' + i,
          });
        }

        return results;
      }
    }
  });

このようになると思います。

2, 定義した JSON Schema 、 ngMockE2E をもとに並行して開発をしましょう

API の開発者は、JSON Schema の通りに開発を進めます。ここは今回の主題ではないため、以上に止めます。

フロントエンドの開発者は作成したモックをつかってフロントの開発を進めます。(API 通信をインターセプトしてモックを返す仕組みなので、モック用の仮のエンドポイントに向ける必要はありません。)

例)app.js

angular
  .module('SampleApp', [
    'SampleApp.MockE2E'
  ]);

ここで重要になってくるのが、先ほども説明したモジュール(Controller, Service, Directive など)を作っていく中で、各モジュールごとにテストを用意する点です。

AngularJS 開発では基本的にテスト用のタスクランナーとしては karma を使うのが良いと思います。テストフレームワークはなんでもよいのですが jasmine が簡単でオススメです。

上記で作った ngMockE2E がテストにも生きてきます。作成したモックを使って高速なスペックテストが可能になるのです。 ngMockE2E をインジェクションさせてテストする雛形は

例)Controller のテストの場合

describe('Controller: SampleCtrl', function () {
  beforeEach(module('SampleApp'));
  beforeEach(module('SampleApp.MockE2E'));

  var SampleCtrl, scope;
  var httpBackend, mockData;

  beforeEach(inject(function ($controller, $rootScope, $httpBackend, _mockData_) {
    scope = $rootScope.$new();

    SampleCtrl = $controller('SampleCtrl', {
      $scope: scope
    });

    httpBackend = $httpBackend;
    mockData    = _mockData_;
  }));

  afterEach(function () {
    httpBackend.verifyNoOutstandingExpectation();
    httpBackend.verifyNoOutstandingRequest();
  });

  describe('describe test here...', function () {

  });
});

になります。このようなものを、 Service や Directive などで用意し各モジュール単位でスペックテストを通過させておきます。

3, APIサーバーとの接続チェックをしましょう

ここまできたらあとは バックエンド開発者が作成した API とフロントエンド開発者が作成した AngularJS アプリケーションの接続をするだけです。ここでは ngMockE2E を抜くだけで完了です。 ngMockE2E が JSON Schema に即して作成されている限りはここで巻き戻しがあることはありません。なので、開発前に JSON Schema を定義しておくことが大事になります。

4, E2Eテストを行いましょう

スペックテストだけでは不十分であり、モックサーバーとの E2E テスト(主にフロントの UI・画面遷移確認)と、API につないだ状態での E2E テスト(主に通信・接続確認)も行います。ここでは protractor を利用します。 Selenium をベースにしたテストフレームワークSelenium にプラスアルファで AngularJS 独特のライフサイクル(今回は説明しません)を考慮したものになっています。

テストシナリオを JS で記述し、そこに詳細な各ユースケースでの操作を定義していきます。あとはそのシナリオを protractor が読み込んで E2E テストを行います。こちらもテストフレームワークはなんでも大丈夫です。

モックサーバーとの E2E テスト(主にフロントの UI・画面遷移確認)と、API につないだ状態での E2E テストは AngularJS が勝手にはやってくれないので、タスクランナーにそれようの記述をしないといけないです。なぜ2回するの?という話ですが、当たり前ですがモックサーバーとの E2E テストはとても早いので、頻繁に行えます。各コミットごとに行ってもいいかもしれません。対して API サーバーとの E2E テストですが少し時間がかかります。なので、こちらは頻繁には行わず、develop ブランチなどにマージされるタイミングで行えばよいかなと思います。

ここまでやって全てのテストが通過していれば自信を持ってリリースさせることができます。(もちろんテストの質にもよります)

さらに BrowserStack などを用いて作成した E2E のシナリオを各ブラウザで行うとさらに安心です。

まとめ

ここまで説明してきたように AngularJS はフルスタックフレームワークとしてフロントエンド開発に必要なほとんどのことを内包しています。なので、一個まるごと AngularJS アプリケーションのような開発をするときに威力を発揮すると思います。逆に既存のプロジェクトに部分的に AngularJS を入れるなどの選択肢はあまりおすすめできません。 その場合は AngularJS ではなく Vue.js や Riot.js などを用いたり、 http://tech.pero.li/entry/2016/05/27/110000 の記事で紹介しているように 導入済みのライブラリにアーキテクチャだけ導入するのがよいと思います。

今回 AngularJS の1系について説明をしており2系に関しての説明ではありません。これから2系が出てきてそちらでは既存の仕組みが大きく変わると思われます。なので、これから AngularJS をやられる方は2系から始めたほうがいいかもしれません。

ペロリ開発部のチーム構成の特徴と、年長エンジニアの果たす役割

組織 キャリアマネジメント

こんにちは。開発部の平山( @orangevtr) です。現在はMERY ECチームで、商品管理・仕入れ管理・在庫管理・帳票出力などのバックエンドシステムの開発を担当しています。

私は 41歳にしてペロリ開発部内でも最年長のエンジニアです。MERYはそのサービスの対象年齢同様に、開発エンジニアの年齢構成も比較的若いのですが、その中で年長のエンジニアが果たすべき役割について考えてみたいと思います。

ペロリ開発部のチーム構成

ペロリ開発部のチーム構成は、先のエントリでも触れたとおり、現在以下のようになっています。

その時々の事業戦略や優先度に応じて柔軟にチームは編成していて、時には少人数の新規立ち上げチームを作ったりしますが、今は、

  • MERY Server Team(Media)
  • MERY Server Team(EC)
  • MERY Server Team(Common)
  • MERY App Team
  • MERY PASS Team

で、構成しています。

tech.pero.li

チーム構成の特徴

小規模チームが多いこと

ペロリ開発部のチーム構成の特徴としては、まず各チームが非常に小規模であることが挙げられます。少ない場合は1人もしくは2人、多い場合でも5人程度です。

また、これまでのベンチャー特有の組織の成り立ちにより、上意下達によるアクションよりも、各個々人がチームの問題として自律的に解決にあたる、というカルチャーが浸透しています。

そのため、メンバー間の関係がフラットで、ほとんどの場合で特に明確にチームリーダーというものが設定されていません。

年齢構成が若いこと

また、メンバーの年齢が全体的に若いことも特徴です。20代前半のメンバーから、私の41歳まで年齢層は幅広いですが、平均年齢は約30歳、中央値に至っては30歳を切っています。

最近は徐々に中途採用者も加わってきたり、親会社のDeNAからの出向者が加わってきたこともあり、年齢を重ねたエンジニアも増えてきましたが、一般的な年齢構成に比べるとまだまだ年齢層が若いです。*1

年長エンジニアの役割

このような非常に若い年齢構成のチームの中で、現役で開発に参加している最年長エンジニアとして、果たすべき役割や、心がけていることについて書いてみます*2

エンジニアと年齢については、「プログラマー35歳定年説」という非常に有名な言葉があるくらい、長年に渡り熱いトピックになっています。また、中年以降のエンジニアのキャリアプランについては、現在でもロールモデルが少ないこともあり、世間でもまだまだ悩んでいる人も多いというのが同年代としての実感です。

以下を通して、ペロリ開発部の開発現場をイメージしていただいたり、年長エンジニアの働き方の一例として、キャリアプランニングの参考になれば幸いです。

若手と年長エンジニアの比較

40代ともなると、他の業種では「ベテラン」と呼ばれることが多い年齢です。合わせて製造業などの昔ながらの分野における技術職は、「職人」という名前がついたりなど、経験という価値が大きい場合が多いです。

ところが我々のようなエンジニアの場合、以下のように知識の多様化・陳腐化を促進する要素が多いです。

  • 新言語やフレームワークの流行
  • インフラ環境の変化(IaaSやPaaSの流行、DevOpsの浸透)
  • 市場の変化(WebからApp中心へ)

そのため、古い「開発経験」自体の価値は比較的高くありません。

加えて、体力・集中力・記憶力・理解力などのファンダメンタルな能力が低下します。それに加えて結婚したり、家庭を持ったりしてライフステージが変わると、可処分時間がより少なくなっていきます。

そのため、若手の方が新技術をキャッチアップしやすい環境と言えます。また、サービスローンチ直前に一時的にハードワークでアクセルを踏み込んだり、障害時に迅速に対応するなども動きやすいことが多いです。

一方で、(なかなか定量的に比較するのは難しいのですが)以下のような経験に基づくアクションは、年長エンジニアの方が有利なことが多いと感じています。

  • リスク予測能力
  • 全体的な俯瞰による判断力
  • 多様な相手とのコミュニケーション能力(コンサルティング/メンタリング/ビジネス企画担当)

また、これはコミュニケーション能力とも一部重複する部分もありますが、チームのリーダーシップをとったり、メンタリングなどを通じて人材を育成する、なども年長エンジニアが向いていることが多いです。

チーム内で気をつけていること

ただし前述のとおり開発部全体は、開発部長を除きフラットな組織体制になっています。そのようなチーム内で対等かつ円滑に業務を推進していくにあたり、私が個人的に気をつけていることについて以下に触れてみたいと思います。

現場力の維持

前述のとおり技術のキャッチアップは若手の方しやすく、また新しいトレンドへの感度についても若手の方が高い傾向が強いですが、年長エンジニアも時間や能力の許す限りキャッチアップに努めることは必要です。幸い新しい技術であっても従来の技術に立脚したものが多いため、その点ではキャッチアップは有利に行えるはずです。

現在はECシステムのバックエンド開発を行っていますが、以前はGolangでの広告配信エンジンの開発を1人で行っていました。私は当時でも既に39歳を迎えていましたが、C/C++言語での開発経験や、他言語での広告配信エンジンの開発経験を活かすことで、新しい言語やミドルウェア、モダンなインフラでの開発のキャッチアップを迅速に行うことができています。

年長エンジニアでもこのような新しいチャレンジを積極的にしていくことで、現場力の維持に努めることができると考えています。

コミュニケーションの質

前述の通り、年長エンジニアの方がコミュニケーション能力が高い傾向にあると考えていますが、それを活かして日々のコミュニケーションの質を高めることが重要です。

例えば、若手エンジニアの方がよく知っていて、自分が知らない、分からないことは素直に若手に師事を仰ぐことにしています。これは文面にすると簡単なのですが、実は地味に苦しいことがあります。人間の自尊心を甘く見てはいけません。

また、「若手に花を持たせる」点も私は重要であると考えています。多様な業務がある中で、「花形」である仕事はあります。例えば、ユーザーに直接相対するサービスフロントエンドの部分や、スマートフォンアプリの分野、厳しいチューニングが要求されるサーバーサイドなどは、エンジニアとしては非常にやりがいのある部分です。

これらの分野はある程度経験を重ねた若手エンジニアに任せ、チャレンジを促すことが重要だと私は考えています。その際は任せっきりにするのではなく、適宜「見守り」、「導く」ことが重要です。

このようなコミュニケーションは、年長者の方に優位性があることが多い、と私は考えています。

今後の課題

前述のとおり現在のところはまだ各メンバーが自律的に問題解決していくカルチャーが残っています。しかし今後ますます開発の領域が拡大し、開発部自身や個々のチームの規模が必然的に大きくなっていくと、より多くの課題が山積していきます。いずれ組織にマネージメント機能を備えて、全体最適の観点から優先度を適切に設定したり、効率的に行うしくみを作っていくことで、パフォーマンスを十分発揮できる強い組織にしていく必要があるでしょう。

また、事業が拡大するということは、開発以外とのステークホルダーとのコミュニケーションも増えていきます。そういう点でも、ミドルマネージャを適切に採用したり育成していくことが必要になってきます。

エンジニアのキャリアを考えるのに必携の「Being Geek」にも、以下の様な一文があります。

マネージャの仕事は、情報を集め、整理して、適切な人に伝達することである。これはエンジニアであるか否かに関係なく、ありとあらゆる種類の人たちと早く、効率的にコミュニケーションをしなくてはならないということである。

(中略)

だが、マネージャの仕事では、何が真実かすら明確ではない。利害の対立、政治的な駆け引きによって、どうするのが正しいのか判断することはとても難しくなる。

Being Geek ―ギークであり続けるためのキャリア戦略

Being Geek ―ギークであり続けるためのキャリア戦略

現在のカルチャーの良い所をひき続き維持しつつ、より複雑な問題を解決できるような個人や組織に成長していくにあたって、年長エンジニアが培った経験や、コミュニケーション能力を活かしてくことが、非常に重要な役割であろうと私は考えています。

まとめ

ペロリ開発部での現在のチーム構成の特徴についてご紹介させていただきました。全体的に年齢層が若く、またフラットな小規模チームが多数集まっています。

その中で年長エンジニアの役割や私が心がけていることについても書いてみました。エンジニアのキャリアプランニングのひとつの例として、参考になれば幸いです。

まだまだ若くて課題も多い中、ますます開発分野が広がっていきますが、個々のメンバーはチャレンジを楽しんで開発しています。

*1:以下のサイトが参考になると思います rplay.me

*2:DeNA Techcon 2016のカジュアルトークでも同様のテーマ登壇させていただいています。engineer.dena.jp

Swiftzつかってみた

MERY のサーバーサイドエンジニアの藤原です。

今回は、MERYのiOSアプリには使用していないのですが、一部で話題のSwiftzについて調べてみました。

Swiftzとは

他の関数型言語によくある機能を、Swiftでも使えるようにするライブラリです。

Swiftにも関数型言語っぽいmapやreduce、filter、flatMap等がありますが、例えばEitherはまだありません。

そういった、「他の言語だったらこう書けるのに」を解消してくれるライブラリです。

Swiftzの利点と欠点

利点としては、

  • 上手くやりたいことと噛み合うとコードを短くスッキリと書ける
  • コードの再利用性を上げやすくなる(部品を書きやすくなる)
  • Swift3.0で追加されそうな機能を先取りして使える
  • 他の関数型言語を覚えやすくなる

等があると思います

欠点としては、

  • SwiftのupdateにSwiftzが追従するのにタイムラグが起きやすい (他のライブラリと比べて、言語仕様の影響を受けやすい)
  • 学習コストが高い (演算子が直感的にわかりにくい人が多そう)

等がありそうです。

使ってみる

Swiftzで拡張される機能は、ざっくり分けると型と、演算子です。

全ては紹介しきれないですが、それぞれいくつか例を上げて紹介したいと思います。

Either

まずは利用機会の多そうな、Eitherを使ってみます。

ObjectiveCでは

NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

のように、成功時につかう値(この場合はdata)と失敗時に使う値(この場合はerror)を同時に受け渡したいために、 NSErrorの参照を渡すパターンが多く出てきます。

そこを綺麗に書けるようにSwiftではtry-catch等の構文が追加されたのですが、"通信エラーの場合はキャッシュに入れる処理をスキップして、エラーメッセージをHUDで表示する"のような用途だとtry-catchは扱いづらいと思います。 そういうった時に活躍するのがEitherです。

Eitherは、「2つの型のうち、どちらかの値が入っている」ことを表現するための型です。 Either<Int,String>であれば、IntかStringの値が入っています。 多くのコーディング規約では、左側に失敗時に使う値を、右側に成功時に使う値を入れることになっています。 *1

例として、通信に成功した場合は受信した文字列を、失敗した場合はhttpステータスをintで入れる想定のEitherを作ってみます。

let ok :Either<Int,String> = Either.Right("connected")
let ng :Either<Int,String> = Either.Left(404)

使うときは、onLeftとonRightで取り出します

func ToString(httpResult:Either<Int,String>) -> String {
    return httpResult.either(
        onLeft: { return "[http error] \($0)"},
        onRight: { return "[recive message] " + $0}
    )
}

成功時も失敗時も同じ型の変数で受け渡しできるので便利ですね。

getOrElse

既存の型に対しても、使いやすくするExtensionが入っています。 例として、getOrElseを紹介します。 SwiftのOptional型のExtensionで、中身がnilの場合は引数で与えた初期値を返すメソッドです。

var someString:String? = "hello"
var noneString:String? = nil
print(someString.getOrElse("not found")) // hello
print(noneString.getOrElse("not found")) // not found

if-let else で書くより簡潔に書ける場面がありそうですね。

演算子

Swiftzで用意されている演算子

https://github.com/typelift/Operadics#operators

にまとめられています。 *2

いくつか使ってみましょう。

compose / apply / thrush

composeの演算子は•です。複数の関数を1つの関数にまとめるための演算子です。

let addHeader: String -> String = { "Your input " + $0 }
let addFooter: String -> String = { $0 + " in Int"}
let toString :    Int -> String = { "\($0)" }

という3つの関数があるとして、compose演算子を使わずにまとめると

let compose1 = {x in addHeader(addFooter(toString(x)))}

となります。

compose演算子を使った場合は

let compose2 = addHeader • addFooter • toString

という風に書き直せます。

compose1とcompose2はどちらも同じ動きをして、

compose1(3),compose2(3)は"Your input 3 in Int"を返します。

動きは同じなのですが、compose演算子を使った場合は関数が複数あっても

let compose = func1 • func2 • func3 • func4 • func5

のようにネストが深くならないので、見た目がすっきりします。

composeと似た使い方ができるものに apply や thrush があります。それぞれの演算子は、<|と|> です。

composeは複数の関数をまとめて1つの関数を返すのですが、 applyやthrushは複数の関数をまとめて適用する事ができます。

let result1 = addHeader <| addFooter <| toString <| 3
let result2 = 3 |> toString |> addFooter |> addHeader

apply(result1)とthrush(result2)は、書く順番が逆ですがどちらも同じ動きをして、やはり"Your input 3 in Int"が入っています。

iOS開発ではアニメーションのネストが深くなりやすいので、autoclosure等と組み合わせて

//compose
let animationNotify = animationApply • animationFadeFadeOut • animationShake • animationFadeIn • animationBuild
animationNotify(notifyView)

//apply
animationApply <| animationFadeFadeOut <| animationShake <| animationFadeIn <| animationBuild <| notifyView

//thrush
notifyView |> animationBuild |> animationFadeFadeIn |> animationShake |> animationFadeOut |> animationApply

みたいに使うと便利かもしれません。

まとめ

Swiftzは以下の様な場面で便利です。

  • 他の関数型言語を使ったことがあり、Swiftで同様な型/機能を使いたい
  • Swiftをきっかけに関数型言語を覚えていきたい

Reactive系のライブラリと組み合わせて使うのも相性が良いと思います。

*1:右(Right)が正しい(Right)というのが、きっかけのようです。

*2:extract演算子は、Operators.swiftを見る限り用意されていない気がします

jQuery + Flux という選択肢

JavaScript Front-End

こんにちは、SKAhack です。普段はMERYのWebフロントエンドを主に書いています。

今回はMERYのフロントエンドで採用している jQuery + Flux という構成を紹介してみたいと思います。

なぜReactではなくjQuery

普通はReact + Fluxで語られることが多いですが、MERYではJavaScriptソースコードの大半がjQueryに依存しており、簡単にはjQueryを捨てられない状態です。 また、Viewの変更をする2つのライブラリを共存させるのも良くないですし、MERYのサービス特性上、現時点で1画面を頻繁に書き換えるような処理は少ないこと、ReactがサポートしていないIE8など古いブラウザもサポートしているという理由でReactを採用するには至っていません。

Fluxの導入についてはReactと違いほとんど考えることなく導入していました。 Fluxは概念のようなもので、導入についてはアーキテクチャとして適切かという点のみ考えればよかったことと、MERYではBackbone.jsなどのフレームワークを使っていなかったので、新しいアーキテクチャの導入も容易にできました。

Fluxとは

FluxはFacebookが2014年に提唱したアーキテクチャの名前で、Dispatcher(Action)、Store、Viewから構成されます。

f:id:SKAhack:20160526145133p:plain

データが一方向のみに流れるので、依存関係が明確になり、状態を管理せずに済みます。 詳細は Fluxのドキュメント をみるとわかりますが、なぜ今まで思いつかなかったのかと落ち込むくらい上手く機能します。 これは個人的な感想ですが、Fluxの登場前後には “JavaScriptでもFunctional Reactive Programmingを” という流れがあったのですが、Fluxはオブジェクト指向プログラミング的にそれを解決したと思っています。

前置きが長くなってしまいましたが、今回の記事ではFluxのViewをjQueryで扱うことを紹介したいと思います。

jQueryをViewとして使うために

jQueryを使う場合でもReactのような機構を作ります。具体的には setState で状態が変わるか、親のViewが render を呼び出したタイミングで render を呼ぶようにしてViewをレンダリングします。 また、1つのViewは1つの ElementjQueryオブジェクトとして持ち、このオブジェクトに対して書き換えをします。 実際はあまりjQueryオブジェクトであることに意味はないですが、ブラウザ依存の処理などを吸収してくれるので便利です。

レンダリングするHTMLは mustache, handlebars などテンプレートエンジンを使います。差分更新はできませんが、レンダリング前の状態で一意のViewを作りたいので要件は満たしています。

コードの雰囲気はこんな感じになります。

const $ = require('jquery');
const Handlebars = require('handlebars');

const FooStore = require('../stores/foo');
const BarView = require('./bar');
const _template = require('../templates/foo.html');
const template = Handlebars.compile(_template);

class FooView {
  constructor() {
    this.$elm = $('<div/>');
    this.bar;
    FooStore.on('change', this.onChange.bind(this));
  }
  
  render(props) {
    if (!this.bar) {
      this.bar = new BarView();
    }
    this.$elm.html(template({ data }));
    // this.barに渡すdataは書き換え不可な状態にしておくと安全
    this.setBarView(this.bar.render({ data }));
    this.handleEvents();
  }
  
  ...
}

このようにViewをReactのコンポーネントに近い構造にしているので、将来的にReact、もしくは軽量なReactに近い何かに移行しやすくなるかもしれないと考えています。

この構成で気をつけないといけないのは、簡単に子を辿っていけたり、親まで遡ることが出来るところなので、気をつけます。一つのViewの中で閉じた状態をつくれば変なことは起こらないと思います。

また、パフォーマンスについて考える必要が出てきた時は、自力で差分更新をすることになります。なるべくViewを小さく作って差分を考える範囲を狭くすると多少マシになります。これについて考えることが多くなるようならReactに変えるのがいいと思います。

まとめ

React使えるならReact使いましょう

まじめに書くと、Reactが選択できなくてもFluxのようなアーキテクチャの選択はできるので、同じような環境で悩んでいる方の参考になればと思います。

Rails アプリケーションにおけるリファクタリングの実践

Ruby on Rails

こんにちは、MERY のサーバサイド開発をしている末並 @a_suenami です。 TDD、アジャイル、DB 界隈等によく出没しますが、最近では糖質警察としてのほうが広く知られている気がする今日この頃です。糖質制限に興味ある方はぜひウィスキーを片手にケトン体の話でもしながら飲みましょう。

さて、現在、MERY は Ruby on Rails で開発されていますが、最初にリリースされたのはもう 3 年近く前であり、その頃とはサービスを取り巻く状況が大きく変わってきています。これまで多くのユーザの「かわいい」を支え、よい体験を提供し続けてきた現在の MERY とそのコードベースを否定することは決してできませんが、日々変わるユーザの「かわいい」ニーズと我々のビジネス状況の変化にスピード感を持って追随していくためにはいわゆる「技術的負債」を返済していく必要があることも否定できない事実です。

そこで今回は実際の Rails アプリケーションにありそうなサンプルコードとともにそのリファクタリングについて紹介しようかと思います。普段、私が MERY のコードをリファクタリングする際にも意識しているテクニックになります。

技術的負債とは?

ソフトウェアエンジニアの人で「技術的負債」という言葉を聞いたことがないという人はあまりいないと思いますが、巷では単なるクソコードを揶揄するためだけにその言葉が使われている印象を受けることも少なくありません。もちろんクソコードを書かないに越したことはありませんが、それは技術的負債以前の話であり採用や教育によって解決するべき課題でしょう。 本来の金融的意味での負債がそうであるように技術的負債もそれ自体は悪いことではありません。資金がないと事業に推進力が生まれないのと同様、今この瞬間にこの機能をリリースしないと貴重なビジネスチャンスを失うということはありえます。重要なのは通常の負債と同様、きちんと返済計画を立てることであり、そのためにはリファクタリングのパターンを知り使いこなせるようになる必要があります。

リファクタリングはいつやるべきか

リファクタリングは「時間があればやる」という優先度になりがちな作業ですが、私の知る限り、その感覚でうまくいく事例はほとんどありません。 外部からの振る舞いが変わらない以上、非エンジニアからの価値はゼロに等しく、そのためのまとまった時間を確保することは実際にはかなり厳しいでしょう。(実際には開発スピードの改善やチームの士気向上という効果はあるのですが、それはエンジニア同士でしか計測ができません。)

したがってリファクタリングを継続的におこなえるチームを作るためにはそれを「特別なタスク」としてとらえるのではなく「定常業務のひとつ」ととらえなければなりません。朝出社したらタイムカードを押すように、数時間おきにメールチェックをするように、帰る前には日報を書くように、リファクタリングも日々の業務の中に紛れ込ませて進めていくべき業務なのです。 リファクタリングといえばそれがズバリそのまま書籍名になっているマーチン・ファウラー氏の名著がありますが、そこでは「いつリファクタリングをすべきか?」という問いに対していくつかの回答があります。

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

  • 同じコードを 3 回書いてしまったとき
  • 既存機能に変更をするために該当のコードを読むついでに
  • 新機能を実装してバージョン管理システムにコミットする前

同じコードを書いてしまったときというのはわかりやすいでしょう。いわゆる DRY 原則にしたがって重複を排除しようということです。ここで重要だと思うのは「3 回」と回数を指定しているところです。2 回目だとたまたまコードが同じになった可能性を否定できず、早すぎる最適化になりうるることを示唆しています。文脈を無視してコードの見た目が似てるからまとめたという過度な抽象化は DRY であるどころか害悪ですので気をつけましょう。

2 つ目のシチュエーションは私が最も多く遭遇するケースです。既存機能に変更をおこなうケースではまずその部分のコードを読み、どこをどのように変更すれば要件を満たせるかを検討しますが、コードを読む時点で読みにくいと感じたり意味がわからなかったりするケースがあります。この場合、後述する仕様化テストを書き、自分が理解しやすく、かつ、これから自分がやろうとしている変更をしやすいだろうと思うように書きなおしてみます。これが機能変更前のリファクタリングになります。

3 つ目はテスト駆動開発( TDD )をちゃんと実践できている人にとっては当たり前だと思いますが、機能を実装したあとに自分以外の誰かに見てもらう前にリファクタリングをおこないます。TDD に関してここで言及すると紙面が足りなくなるので割愛しますが、テストファーストを意識していれば今書かれているプロダクトコードに対しては確実にテストコードが存在することになりリファクタリング可能ですし、このシチュエーションは主に新機能の開発時に遭遇すると思うので既存のコードベースは関係なく、もっともリファクタリングを実践しやすいケースかもしれません。

レガシーコードのリファクタリングを行うための重要なテクニック

リファクタリングはソフトウェアそのものの振る舞いを変えずに内部の実装方法を変えることであり、通常は振る舞いを変えてないことを保証するためにテストコードが必要です。しかし、世の中にはテストコードが存在していないコードベースも存在しており、そういったソフトウェアのリファクタリングをおこなうためのテクニックとして「レガシーコード改善ガイド」という書籍ではいくつかの手法を紹介しています。

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)

これらのテクニックは非常に多くの現場で活用可能なのでぜひ積極的に活用していきましょう。

シグネチャの維持

テストコードがない状態でも可能なリファクタリング手法として「レガシーコード改善ガイド」では 2 つの手法が紹介されています。

1 つは「コンパイラまかせ」で、テストが本来担う役割をコンパイラに任せる方法です。コンパイラの静的型検査を利用することによって修正必要箇所を教えてもらうという方法で、その言語の型システムや静的検査時の厳密さ次第ではかなり有効な方法ですが、残念ながら動的型付き言語である Ruby でこの手法は適用できません。

もう 1 つの方法が「シグネチャの維持」です。これはある場所に定義されたメソッドを"そのまま"別のもっとテストしやすいクラスやモジュールに移動させることです。当たり前だと思う人もいるかもしれませんが、人間の欲望というのはおもしろいものでリファクタリングをするつもりになったらつい"ついでに"他のこともやりたくなってしまうものです。しかし、テストコードのないプロダクトコードに対して、それがどんなに簡単に見えて自分自身は修正することに自信があったとしても、シグネチャを維持しない状態で変更をするのはリスクをともないます。

リファクタリングの第一歩は何よりもまずテスト可能な状態にすることであり、それ以上のことを最初にやるべきではないのです。

メソッドの抽出

ある場所にあるメソッドをそのまま別のクラスやモジュールに委譲する場合は「シグネチャの維持」が使えますが、巨大なモンスターメソッドのごく一部が非常に複雑になっていてその部分だけ別に切り出したいケースもあるでしょう。むしろ実際の現場ではそのケースのほうが多いと思います。

この場合には「メソッドの抽出」という手法が使えます。その名の通り、あるメソッド内のある処理を別のクラスやモジュールのメソッドとする手法で、本来はもともとのメソッドにテストコードがある場合か IDE に自動リファクタリング機能ががある場合に利用するのが好ましいですが、そうでない場合にも以下の手順で慎重に行えばリスクを最小化して変更を行うことが可能です。

  1. 抽出したいコードを特定してコメントアウトする。
  2. 新しいメソッドを作成し、抽出したいコードをコピーする。
  3. 新たに作成したメソッドをただ実行するだけのテストコードを書き、引数・戻り値を調整する。
  4. コメントアウトした箇所を新たに作成したメソッドの呼び出しに変更する。
  5. コメントアウトした部分を削除する。

「レガシーコード改善ガイド」の中では 3 の手順が「コンパイラまかせ」を使うように紹介されていますが先述したように Ruby ではこの手法は使えないため、テストコードで代用します。この手順を経ることでグローバル変数やもともとのクラスのインスタンス変数へ依存していたことがわかることがあり、それらも引数で渡してあげるようにしましょう。「シグネチャの維持」でも述べたようにこの状態におけるリファクタリングではコードそのものはコピー以外のことをしてはいけません。

仕様化テスト

TDD では何を実装するべきかをテストコードで表現し、それをパスするようにプロダクトコードを実装します。しかし、すでに存在しているコードにテストコードがない場合はこの方法は使えません。そこで用いる有効な手段が「仕様化テスト」です。

仕様化テストとは現在の実装が正しく仕様を満たしていると仮定して書かれるテストコードのことです。仕様に基づいて実装をするという本来のソフトウェア開発の真逆の考え方で、現在の実装を仕様として再定義するということになります。

シグネチャの維持やメソッドの抽出もそうですが、リファクタリングの最中というのはついついいろいろなところに手を出したくなるものです。リファクタリングの仮定で今まで気づかなかったバグや好ましくない挙動に気づくことも少なくなく、そのときに"ついでに"なおしてしまおうとすることはよくあります。しかし、テストコードがない状況においてはこれは危険な発想で、自分にとって不具合だと思ったことがある状況において重要な振る舞いであることもあるので、まずは現在の振る舞いを正しく表現するテストコード、つまり仕様化テストを準備することが大切なのです。

Rails アプリケーションにおけるリファクタリングの実践

ここからは実際の Rails アプリケーションでどのようなリファクタリングを行っていくかをサンプルコードを交えながらご紹介していこうと思います。MERY にもまだファットコントローラーやファットモデルとなっている部分があるため、今回はその改善について 2 つのアンチパターンとその対処法という形で紹介します。

アンチパターン: とりあえずインスタンス変数

私はこれは明確にアンチパターンだと思っているのですが、ビューテンプレートで利用する変数をすべてインスタンス変数としてアクションメソッド内で宣言してしまうケースがよくあります。MERY 内でも最近はあまりありませんが以前に実装された部分だとまだ残っていることがあります。これは以下のような点で扱いが難しいです。

  • アクションメソッドが長くなってしまい可読性が下がる。
  • 一般にコントローラーはテストしづらく、やろうと思うと実際にもしくは擬似的に HTTP リクエストを該当のエンドポイントに送る必要がある。
  • コントローラーのインスタンス変数とはいっても実際はそのリクエスト内においてはグローバル変数のようなものであり、どこでどういう風に参照・更新されているかを把握するのが難しい。

特に重要なのは 3 つ目で、MERY だと PC とモバイルで異なるテンプレートを使っていたり、パーシャルテンプレートを多用していたりするため、影響範囲が広く小さな修正でも確認箇所が増えてしまうということになりがちです。

対処法

不幸中の幸いはこれらがメタプログラミングによって動的に生成されたものでなく、ソースコード内の検索で宣言されている箇所・参照されている箇所をほぼ見つけられることです。Ruby のよいところでもあり悪いところとしてメタプログラミングのやりやすさがあり、例えば define_methodclass_evalinstance_eval などで動的にメソッド定義可能ですし、逆に send で動的に呼び出し可能です。今回問題にしているインスタンス変数も instance_variable_setinstance_variable_get で同様の問題は起こり、実際にそのメソッドや変数が定義されている箇所や参照されている箇所を特定しにくいことがしばしば問題になります。

アクションメソッド内で宣言・代入されビューテンプレートで参照されるインスタンス変数において、現状の MERY ではほぼこういった問題はないため、きちんとしたテクニックさえ知っていればリファクタリングをおこなうことが可能です。

  1. コントローラーのインスタンス変数に代入されている部分を"そのまま"モデルやヘルパーに移動する。(メソッドの抽出・シグネチャの維持)
  2. views/ 以下の全ファイルを対象にコード内検索をかけ、そのインスタンス変数を使っているところを新たに作成したメソッドを呼ぶようにする。
  3. 作成メソッドの仕様化テストを書く。
  4. ブラウザで手動動作確認をする。

もちろん 4 も理想的には feature spec や Cucumber ライクなフレームワークによる自動シナリオテストで行われるのが理想ですが、まずはファットになっているコントローラーから他のコンポーネントへ処理を移譲していき、モデルやヘルパーのテストで品質を安定させていくことが目的であり、シナリオテスト自動化はこれらの積み重ねによってようやく達成可能になるので、これが第一歩と言えます。

以下のように条件分岐やループ処理を含むコードがアクションメソッド内にあるとします。

class UserController < ApplicationController
  def mypage
    @user = User.find(params[:id])
    # 制御構造を含む複雑な処理
    @some_array = []
    if @user.some_condition?
      @user.some_collection.each do |element|
        @some_array << do_something_to(element)
      end
    else
      @user.another_collection.each do |element|
        @some_array << do_otherthing_to(element)
      end
    end
  end
end

これを次のようにほぼそのまま User クラスのメソッドとして抽出します。

class User
  def some_array
    some_array = []
    if self.some_condition?
      self.some_collection.each do |element|
        @some_array << do_something_to(element)
      end
    else
      self.another_collection.each do |element|
        @some_array << do_otherthing_to(element)
      end
    end
    some_array
  end
end

これによりビュー側では

<%= @some_array.each do |element| %>

としていたところが

<%= @user.some_array.each do |element| %>

のようになります。

ここのポイントは some_array の処理は @user から self への置換こそあるもののそれ以外に一切手が加えられてないことです。これ以上手を加えるのはこのメソッドのテストコードが十分に整備されるまで決してやってはいけません。

アンチパターン: 賢すぎるビュー

ドメイン駆動設計(DDD)をご存知の方はご存知の人も多いかと思いますが、本来ドメイン層(ビジネスロジックと言い換えてもよい)にあるべきはずの振る舞いが画面に漏れ出してしまい、画面の変更がしにくくなるという状態を「スマートUI(利口なUI)」と呼びます。

これは Rails のアプリケーションではモデルやヘルパーではなくビューテンプレートに複雑な処理が出現するという形で現れます。UI というのはニーズの変化によって変更されやすい箇所であり、それがテストしにくいビューテンプレートに直接書かれていることは大規模なサービスになると非常に問題になってくるので改善が必要になります。

対処法

このアンチパターンも「とりあえずインスタンス変数」アンチパターンと同様、モデルやヘルパーに振る舞いを移動するということで対処するのが基本ですが、ERB や Haml 等のテンプレートエンジンから通常のクラスやモジュールへコードを移動することになるので本来の意味での「メソッドの抽出」は適用できず単なるコピー以上のコード変更をすることになります。そのため、先に紹介したリファクタリングよりさらに慎重に行う必要があります。

  1. ビューテンプレートに直接記述されている複雑な処理を"制御構造はそのままの状態で"モデルやヘルパーに移動する。(「メソッドの抽出」の応用)
  2. 対象のビューテンプレートでは新たに作成したメソッドを呼ぶようにする。
  3. 作成メソッドの仕様化テストを書く。
  4. ブラウザで手動動作確認をする。

ポイントはメソッドの制御構造を変えないことです。

以下はパンくずリストの表示をビューテンプレートで実装している例になります。

<ul class="breadcrumb">
  <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb">
    <a href="<%= root_url %>" itemprop="url">
      <span itemprop="title">MERYトップ</span>
    </a>
  </li>

  <% if @user.group.present? %>
    <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb">
      <a href="<%= group_url(@user.group) %>" itemprop="url">
        <span itemprop="title"><%= @user.group.name %></span>
      </a>
    </li>
  <% end %>

  <%# 複雑な処理 %>

  <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb">
    <a href="<%= user_url(@user) %>" itemprop="url">
      <span itemprop="title"><%= @user.name %></span>
    </a>
  </li>
</ul>

このコードは制御構造は複雑ですがレンダリング処理そのものは単純なループ処理で実現でき、簡単なバリューオブジェクトの導入で以下のように整理できます。

class BreadcrumbItem
  attr_reader :title
  attr_reader :url

  def initialize(title, url)
    @title, @url = title, url
  end
end
<ul class="breadcrumb">
  <% user_breadcrumb_items(@user).each do |item| %>
    <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb">
      <a href="<%= item.url %>" itemprop="url">
        <span itemprop="title"><%= item.title %></span>
      </a>
    </li>
  <% end %>
</ul>

この変更によって複雑なコードはすべてヘルパーへ移動することができます。

module UsersHelper
  def user_breadcrump_items(user)
    items = []
    items << BreadcrumbItem.new('MERYトップ', root_url)

    if user.group.present?
      items << BreadcrumbItem.new(user.group.name, group_url(user.group))
    end

    # 複雑な処理

    items << BreadcrumbItem.new(user.name, user_url(user))
  end
end

この変更はコードのコピー以上のことをやっているため厳密には「メソッドの抽出」とは言えませんし、それをテストコードなしの状態で行っているため「リファクタリング」と呼んでいい変更とも言えませんが、十分に小さい範囲を慎重に行う限りにおいては通常の「メソッドの抽出」や「シグネチャの維持」と同様にリスクを最小化して実施することができます。「レガシーコード改善ガイド」で紹介されている手法のある種の応用と言えるでしょう。

これ以降は抽出したヘルパーメソッドに仕様化テストを記述し、通常のリファクタリングを行っていくことになります。

まとめ

リファクタリングには多くのテクニックがあり、正しい手順でリスクを最小限にしながらおこなうためにはそれなりの知識と習熟が必要になります。しかし、リファクタリングの結果を保証するためのテストケースが十分に存在している限り、それを必要以上に恐れる必要はありません。

それよりも難しいのは何よりもまず「リファクタリング可能な状態にする」ことであり、世の中に多く存在しているであろうテストコードがないソフトウェアにとってはむしろこちらのほうが重要なスキルであり、単にリファクタリングをおこなうことよりもさらに多くの知識と高い技術が必要になります。

今回は実際の Rails アプリケーションで陥りがちな 2 つのアンチパターンを例にとってその具体的な改善方法を紹介しましたが、技術的負債の返済方法は他にも多くのテクニックやアプローチがあります。これを読んだみなさんがこれをきっかけにそれらを学び、技術的負債を適切にマネジメントしてサービス価値の最大化につながれば幸いです。

推薦書籍

テストコードがないコードをリファクタリング可能にするテクニックについては先に紹介した「レガシーコード改善ガイド」に数多く紹介されていますし、その後のリファクタリングについてはマーチン・ファウラー氏の「リファクタリング」を参考にすれば多くのコードを改善していくことができるでしょう。

ここではさらに役に立つであるであろう 2 冊の書籍をご紹介します。

リファクタリング: Ruby エディション

その名の通り、「リファクタリング」の Ruby 版になります。もともとの書籍は多くのサンプルコードが JavaC++ で記述されており、コンパイラによる静的検査を前提とするテクニックも多いため、Ruby ではそのまま適用できない手法も存在します。この書籍ではそういった部分を Ruby ではどうするかという観点から紹介されているため、Ruby を利用しているエンジニアは必見の書籍になるかと思います。

リファクタリング:Rubyエディション

リファクタリング:Rubyエディション

ソフトウェアテスト技法ドリル

リファクタリングが可能になればその後はテストケースの追加とプロダクトコードの修正を繰り返しながら設計を改善していけばよいのですが、それを続けていくと、どの程度・どういう観点でテストを書けばいいかという問題に直面することがあります。 もちろんこれは TDD 的な開発者視点でのテスト、ユーザ視点での QA テスト等によっても違いますし、テストレベル(ユニットテスト結合テストシステムテスト etc. )によっても違うのですが、そういったテスト観点の洗い出しや分析、適切なテスト設計を学ぶためにはこの書籍が最良かと思います。

ソフトウェアテスト技法ドリル―テスト設計の考え方と実際

ソフトウェアテスト技法ドリル―テスト設計の考え方と実際

MERY PASS における MVP の実践

開発プロセス

MERY PASS における MVP の実践

ペロリ MERY PASS チームの libitte です。 今回は我々のチームで実践している MVP について簡単にご紹介します。

目次

  • MERY PASS とは
  • 新サービスで使うと便利な MVP
  • MERY PASS での MVP の実践例
  • おわりに

MERY PASS とは

MERY PASS はネイル、マツエク、リラクなどのビューティー系サービスがどれでも毎回 3000 円で受けられるサブスクリプションサービスです。

f:id:libitte:20160429121824p:plain

月額 980 円で会員になることができ、その特典として、MERY PASS 内に掲載されているネイル、マツエク、リラクなどのビューティーサロンでのサービスがどれでも会員限定価格 3000 円で受けられるものになっています。

美容サロンに対しては、空席とブランディングの支援を行い、更に費用は一切無料で掲載させていただいています。

現在、渋谷エリアを中心としてサービスを展開中で、今後も順次拡大していく予定です。

新サービスで使うと便利な MVP

新サービスを作るとき、まず最初に大きな課題を定めたうえで、「oo によってその課題を解消できるのではないか」 という仮説を立てます。

そして、その仮説を検証するためにプロダクトを開発し、提供してみて、ユーザーからフィードバックをもらいます。 このようなサイクルを何度も回すことで、ユーザーが満足するプロダクトが形作られます。

MERY PASS の場合、最初の仮説は例えば以下のようなものでした。

  • 一定以上のサロンは空席を抱えており、一定のコストを払ってでもその空席を埋めたい、と考えるのではないか
  • ビューティー系サービスのユーザーは、サービスの利用料に課題を感じており、利用料を抑えられるのであればもっと利用したい、と考えているのではないか

サービス立ち上げ時、これらの仮説はより早く検証されるべきものであり、その時に用いた有力な開発手法が MVP でした。

MVP (minimum viable product) とは、仮説を検証するために、最小限の労力と時間で開発できるものを指します。

MVP を活用することで、仮説−構築−計測のサイクルを回す時間を最小限に抑えることができ、その結果より速いサイクルで学習し、プロダクトの軌道修正を行うことができます。

このような特長をもつため、MVP は高い品質でつくり上げることは重要視されません。

この考えはもしかしたら、大きなプロダクトを開発されている方であると違和感をお持ちになるかもしれません。 顧客が価値を認めていて特性が既にわかっているプロダクトの開発において、品質は非常に重要だからです。プロダクトの先に多くの顧客がいて、彼らに最も直接的な影響をあたえるものが品質であることを考えると、これは当然のように思われます。

しかし、新サービスにおいては「誰が顧客なのかわからなければ、何が品質なのかもわからない」という前提に基づいています。

まだ顧客にとって必要な機能を探し当てられていない状態なので、顧客にとって有益かどうかわからないものの開発に時間をかける価値はないという立場に立っているのです。

したがって、MVP では検証までのサイクルを最小にし、そのためには高品質であることは重視しない、ということになります。

MERY PASS でのMVPの実践例

ここで、MERY PASS が取り組んでいた MVP を一つご紹介しようと思います。

サロンの空席情報掲載

MERY PASS では

「掲載メニューのページにサロンの空席情報を掲載すれば、予約件数は今まで以上に増えるのではないか」

という仮説を検証するために、空席情報掲載を行いました。

f:id:libitte:20160208112812p:plain

今でこそサロン向け管理画面を提供して入力してもらっているこの空席情報ですが、 MVP 実践当時は、サロンの方に空席情報をチャットツール経由で送ってもらい、それをチーム内の入力部隊が社内向け管理画面から全サロン分ひたすら登録を行う、という運用を行っていました。 (このようなMVPの手法はオズの魔法使いと呼ばれています)

f:id:libitte:20160428122056p:plain

振り返り

このような形式でユーザーに提供することで、提供までの時間を非常に短くすることができました。 この結果、この施策の効果検証を素早く行うことができました(予約件数は今までの数倍に増えました)。

この施策では、効果検証の結果予約件数は今までの数倍に増加することがわかったので、より高品質にこの価値を提供するための仕様決めに時間を割くことができたのでした。

おわりに

本記事では、MVP を活用し、品質よりもユーザーに提供するまでのスピードを重視することで、一定の効果を上げることができました、という事例をご紹介させていただきました。

ちょっと視点を変えるだけで簡単に実践できるかと思いますので、もし新サービスの開発や、効果があるか未知数な機能開発を行う場合など、試してみてはいかがでしょうか。

© peroli, Inc.