くらしのマーケット開発ブログ

くらしのマーケットを開発する、みんなのマーケットによる技術ブログ

新卒でエンジニアとし入社して1ヶ月働いてみて感じたこと

新卒でエンジニアとし入社して1ヶ月働いてみて感じたこと

テックチームの都築です。
この記事では、私がみんなのマーケットに新卒のエンジニアとして入社して見て考えたことや感じたことを書いていきたいと思います。

以下が今回のブログの目次となっています。

目次

  • この会社に新卒でエンジニアとして入社してみて良かったこと悪かったこと
  • 私と同期の人たちとの関係について
  • 私の両隣にいる人たちについて
  • 私の担当している仕事について
  • 1ヶ月働いてみて感じたこと
  • これからの目標

この会社に新卒エンジニアとして入社してみて良かったこと悪かったこと

 まずは、この会社に入社してみて良かったことについて書いていきたいと思います。
 1つ目は、最初に仕事用のMacを貸してもらえる点です。私は今、MacBook Pro 2017 13インチモデルを使用して仕事をしています。仕事でプログラムを作成するときと個人的にプログラムを作成するときの環境を物理的に変更できるおかげで、心置き無く開発が進められます。
 2つ目は、新卒で右も左も分からない私に、Webページやアプリのテストを任せてもらっている点です。会社のシステムを理解するためにはテストをするのが一番とのことらしいのですが、とても重要な最終防衛ライン的部分を任せてもらえていることはとても誇りに思っています。
 3つ目は、駄菓子をいつでも食べられる点です。ちょっと方向性に行き詰まった時や、ちょっとした休憩のお供には最高ですが、食べ過ぎには注意しないといけないと思っています。
 4つ目は、新鮮な水がいつでも飲める点です。人間が生きるために必須な水がいつでも冷たい状態で飲むことができることはとてもありがたいことだと思っています。以下の写真が私の生命を支えている水です。
 新鮮な水

 次に、この会社に入社してみて悪かったことについて書いていきたいと思います。
 1つ目は、Webページやアプリのテストを任せてもらっている点です。嬉しいことでもありますが、普通に大変です。(楽しいから別にいいかなと思ってます)
 2つ目は、新卒入社のエンジニアが私だけという点です。寂しいです。(周りの同期が優しいから最近はあまり寂しさを感じないようになりました)
 3つ目は、悪い点をあげていると特に思い浮かばずに他の人に「え、それ本当に思ってる」と言われる点です。(とりあえず今のところは本心です)  

私と同期の人たちとの関係について

 同期の人たちの優しさが身にしみます。おかげで今週も頑張れます。
 しかし、具体的に優しさってなんだいと言われそうなんで細かく書きます。
 1つ目は、エンジニアとしての仕事をできるだけ理解しようとしてくれている点です。エンジニアの仕事と言えば「何やってるか分からない」とか言われるという話を大学時代の友人から聞きます。しかし、同期の人たちはそんな私のわけわからない話を理解しようとしてくれる優しさがとても嬉しいです。
 2つ目は、部署関係なくみんなの仲がいい点です。(私はそう思っています)よく飲みにいきながらどういう仕事をしているなどの情報交換ができるのでとてもありがたいですし、とてもいい刺激になります。  

私の両隣にいる人たちについて

 私が仕事をしている席の両隣にはベトナム出身のズイさんトゥエンさんがいます。お二人は普通に日本語が喋れます。たまに、これはどういう意味などの日本語に関することについて聞いてきてくれるのが嬉しいです。日本人に生まれてよかったーと思います(大げさ)最近は、少しだけベトナム語も教えてもらっています。
 少し話が脱線しましたが、お二人は私にとってとてもいい刺激をくれる存在です。日本語を頑張って覚えようとするお二人を見ていると「頑張って仕事します!」と思えます。さらに、お二人にプログラムのことなどについて質問すると、とてもわかりやすく教えてくれます。技術力の高さに救われてます。cảm ơn bạn(ベトナム語でありがとうと言う意味だそうです)  

私が担当している仕事について

 私は、WebのUIなどが正常に動くか試すためのテストを行うプログラムを書いています。利用している言語としてPythonテストフレームワークとしてrobotframeworkを利用しています。また、シミュレータの制御にはAppium、WebテストのライブラリとしてSeleniumを利用しています。
 では、突然ですが、少しrobotframeworkの書き方について説明したいと思います。
 
 まずは、プログラムで何かしら表示する操作から説明していきます。
 

log to console  hello word

 hello wordと表示するということです。  ここで注目するべき点は、log to consolehello wordの間の空白は2つですが、logtoconsoleの間の空白は1つという点です。
 
 続いて、変数に値を入れるという操作から説明していきます。
 
${minma}=  set variable  tech

 わかる方にはわかると思いますが、${minma}という箱のなかにtechという文字を入れておくということです。
 ここで注目すべき点は、変数を定義するときは、${変数名}という形を取らなくてはいけないということです。
 
 続いては、条件式です。
 
run keyword if  ${minma} > 0  log to console  くらしのマーケット

 ${minma}が0より大きければくらしのマーケットと表示するということです。
 ここで注目するべき点は、if文が長い点です。他にも色々な種類のif文があるので調べてみると面白いかもしれません。
 
 と言った感じでプログラムを記述しています。
 もっとrobotframeworkについて知りたいと思った方はこちらを参考にして見てください。  

1ヶ月働いてみて感じたこと

 結論から言うと、ここの会社で働けてとても嬉しいです。
 一緒に働いている方たちもとても優しくミスをしても次頑張ろうと言ってくださる優しい方達です。(たまに本気で怒られる時もありますが。。。)
 仕事もやっていて楽しいです。
 職場の環境にも満足しています。(職場環境が良すぎて一部、自制心を持たないといけない場合があるので注意しています)  

これからの目標

 ここからは1ヶ月働いて見て感じたこと思ったことから目標を設定していきたいと思います。
 1. テスト以外にも様々なプログラムを作れるようになりたいと思っています。
 2. techだけでなく他の部署のことも知りたいと思うようになりました。なので、色々な部署の仕事について学んでいきたいと思っています。
 3. 新卒だけでもいいので部署間交流を活発にやっていきたいと思っています。(新卒だけで色々やっているよねと言う内側からの声は聞こえないことにします)

いかがでしたか?私個人の独断と偏見によりこの記事を書いているので他の方々がどう思っているかはわかりません。しかし、これだけは自信を持って言えます。みんなのマーケット株式会社に入社できてよかった。

一緒に働いてみたいといった方もぜひお待ちしてます!

凸最適化問題の紹介

はじめに

テックチームのトゥエンです。

この記事では、機械学習や他の多くの分野に適用される興味深い部分を紹介します。 それは凸最適化問題です。

猫

問題

めぐみさんは、ワインが大好きです。ワインが大好きすぎて自分でワインを作ることにしました。 めぐみさんは、ワインを作るためのフルーツとして葡萄と苺を育て、ワインにすることにしました。 しかし、安く早くワインが飲みたいめぐみさんは予算は1万8千円、期間は1ヶ月(30日)でワインを作りたいと思っています。また、 めぐみさんは畑を所有しており、その畑の面積は800平方メートルあります。葡萄栽培のコストは3千円、苺は千円です。葡萄の栽培時間は、100平方メートルあたり2日間、苺の栽培時間は100平方メートルあたり5日間です。100平方メートルあたり葡萄は50リットルのワイン、100平方メートルあたり苺は30リットルのワインを作ることができます。 最も多くワインを栽培する方法を教えてください。

これを最適化問題と呼んでいます。今回は、最適化問題の中でも凸最適化問題という手法を使って問題を解いていきます。

上の問題を解決するのは簡単ですが、実際に起こる問題はもっと複雑です。

問題を解決する方法をより深く理解するために、数学的基礎について少し紹介したいと思います。

少し数学について

最適化問題

最適化問題(optimization problem)とは、特定の集合上で定義された実数値関数または整数値関数についてその値が最小(もしくは最大)となる状態を解析する問題です。

最適化問題の公式は、

最適化問題

f(x) :目的関数 g_{i}(x), h_{j}(x) :制約条件

最適化問題では、大域的最適解 (globally optimal point) ではなく局所的最適解 (locally optimal point) だけが見つかる場合があります。それは、最高の解決策ではないかもしれません。

局所的最適解と大域的最適解

したがって、次のセクションでは、凸集合と凸最適化問題について議論し、その特殊な性質から、上記の問題を回避することができます。

凸集合

ユークリッド空間における物体が凸(convex)であるとは、その物体に含まれる任意の2点に対し、それら2点を結ぶ線分上の任意の点がまたその物体に含まれることを言います。

凸集合と非凸集合

集合 C が凸であるとき、任意の x, y ∈ C および任意の λ ∈ [0, 1] に対し、点 λx + (1 − λ)y もまた C に属することをいう。

凸最適化問題

凸最適化問題の公式は、

最適化問題

ここで、 - 関数 f, g_{i} は凸である。 - 関数 h_{j}アフィン変換、すなわち線形関数。

凸最適化問題では、局所的最適解(local optimal solution)は大域的最適解(global optimal solution)です。この性質は結果を確実にすることができる最善の選択肢です。

大域的最適解

問題の例

このセクションでは、凸最適化問題について簡単な例を見ましょう!

デモのために、Pythonと CVXOPT ライブラリ を使います。

線型計画法

線型計画法の公式は、

Linear Programming

この記事の冒頭の例は、線形計画法を利用して問題を解きましょう。

数学の実装のために、 x は葡萄の面積、 y は苺の面積です。

  • ワインの量は equation

  • 畑の面積は800平方メートルです。 equation

  • 葡萄栽培のコストは3千円、苺は千円、最大コストは1万8千円です。 equation

  • 葡萄の栽培時間は、100平方メートルあたり2日間、苺の栽培時間は100平方メートルあたり5日間です、最大で30日かかります。 equation

問題は次のように表されます。

equation

デモ

上記の式は次のように書き直すことができます。

equation

また、行列を使って上記の式を表すと以下のようになります

equation

from cvxopt import matrix, solvers

c = matrix([-50., -30.])
G = matrix([[1., 3., 2., -1., 0.], 
            [1., 1., 5., 0., -1.]])
h = matrix([8., 18., 30., 0., 0.])

solvers.options['show_progress'] = False
sol = solvers.lp(c, G, h)

print(sol['x'])

結果

[ 5.00e+00]
[ 3.00e+00]

最適な解決策は、500平方メートルの葡萄と300平方メートルの苺を植えます。なので、めぐみさんは340リットルのワインを作ることができます。

二次計画法

次は、二次計画法問題という線形計画法問題の拡大を見てみましょう。この問題の公式は、

Quadratic Programming

P = 0 なら線形計画問題になります。

次の問題を解きしましょう。

equation

デモ

上記の式は次のように書き直すことができます。

equation

また、行列を使って上記の式を表すと以下のようになります。

equation

from cvxopt import matrix, solvers

P = matrix([[2., 0.], [0., 2.]])
q = matrix([-50., -80.])
G = matrix([[1., 3., 2., -1., 0.], 
            [1., 1., 5., 0., -1.]])
h = matrix([8., 18., 30., 0., 0.])

solvers.options['show_progress'] = False
sol = solvers.qp(P, q, G, h)

print(sol['x'])

結果

[ 3.33e+00]
[ 4.67e+00]

よって、 equation のとき、 equation の値は最小です。

まとめ

  • 凸最適問題は非常に実用的な問題です。

  • このブログでは、凸最適化問題の2つの基本的な数学的モデル、線型計画法と二次計画法を紹介しました。

  • CVXOPTライブラリは、凸最適化問題を素早くきれいに解決するのに役立ちます。

あなたは多くの条件で問題を抱えたことがありますか?この記事がお役に立てば幸いです。

一緒に働いてみたいといった方もぜひお待ちしてます! 次回は都築さんが書く予定です。

お読みになっていただきありがとうございます!

参考文献

[1] ウィキペディア

[2] Convex Optimization – Boyd and Vandenberghe, Cambridge University Press, 2004

[3] Machine Learning co ban - Vu Huu Tiep, 2018

[4] CVXOPT

ReSwiftライブラリの紹介

はじめに、

こんにちは、エンジニアのDuyです。

現在、アプリを開発し続けるにつれて、MVCというアーキテクチャには弱点があらわれつつあります。システムが複雑になるにつれて、アプリの状態を管理しにくかったり、データの同期の問題もあります。その問題を解決したいという理由で、ReSwiftが作られました。今回アプリの開発に関して、ReSwiftを簡単に紹介します。

ReSwiftとは?

最近、JavaScriptのReduxというライブラリをよく耳にしますが、Swiftに関しても、Reduxのようなライブラリは存在するのか?答えは、存在します。

I have Redux. I have Swift. Ughhh! ReSwift!

f:id:curama-tech:20180410115849p:plain

そうです。ReSwiftとは、

ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift.

詳細的には下の図を一緒に見ましょう。

f:id:curama-tech:20180409100121p:plain

図を見ながら、ReSwiftの主要なコンポネントを単に説明したいと思います。

The Store: アプリに対して、Storeはユニークです。アプリの現在のState(状態)を保存することがThe Storeの役割です。その状態を変更したいとき、必ず、Actionsをディスパッチしないといけない。 そして、Stateの値が変化したら、The StoreがすべてのSubscribersに通知を送信します。アプリの状態を全てStoreに保存するため、アプリの状態を管理しやすいです。

Actions: Actionはプレーンオブジェクトです。ActionがStateの変化を記述します。

Reducers: ReducersがActionとStateを受け取り、新しいStateを返します。

Subscribers: Storeに保存されるStateを変化したら、SubscribersがそのStateを画面に表示します。

イメージとしては、下記のようなフローになります。

  1. SubscribersがStoreにActionを発送します。
  2. StoreがそのActionを消費し、同時に、アプリの現在StateをReducer宛にフォワードします。
  3. ReducerがActionによって、新しくStateを作成して、StoreにそのStateを保存します。
  4. StoreのStateが変化したので、Subscribersが新しいStateをもらって、表示します。

現在、うちの会社ではたくさんのStateを管理します。例えば、カレンダーとか、メッセージとか、店舗の情報とかサービス等、それぞれ項目のStateを作って管理します。 実際の使った結果によって、アドバンテージがあります。

  • コンポネントの任務が分割できます。
  • アプリの状態はStoreしか管理しないので、データの同期の問題を解決でき、変化したら、すぐにSubscribers(Views)に反映される。
  • コンポネントを拡張しやすい。
  • Unidirectional data flowなので、ログして、テストしやすい。

シンプルプロジェクトを開発してみよう

実際に、どういうふうに実装しますかって言う質問がある方がいると思うので、Demoで説明したいと思います。

まずは、こちらのプログラムをダウンロードしましょう。

Note: Xcode 9 + swift3を使うDemoです。

ダウンロードが完了したら、Cocoapodで、ReSwiftをインストールします。 Cocoapodについて、こちらに参考できます。

ダウンロードしたリポジトリを開いて、Terminalでpod コマンドを実行します。

$ pod install

とりあえず、BlogContactDemo.xcworkspaceを開いて、ビルドして見ましょう。 結果は:

f:id:curama-tech:20180410122658p:plain:w240 f:id:curama-tech:20180410122716p:plain:w240

詳細画面で何か変更して、更新ボタンを押して、バックボタンを押してみたら、リスト内容は前の状態のままです。変更したところは反映されていなかったです。

変更した内容を反映したい時、NotificationCenterを観察して、内容が変わったら、表示するのも一つのやり方ですが、今回はReSwiftで処理します。

まずは、List画面のActionsを定義します。

// Actions/ContactListAction.swift

import ReSwift

struct RequestGetContactListAction: Action {}

struct ResponseGetContactListAction: Action {
    let model: [Contact]
}

extension ContactListState {
    static func getContactList() -> Store<AppState>.AsyncActionCreator {
        return { (state, store, callback) in
            store.dispatch(RequestGetContactListAction())
            callback { _,_ in ResponseGetContactListAction(model: ContactFixtures.currentData) }
        }
    }
}

ここは、2つActionを定義します。

  • RequestGetContactListActionには、Contactの一覧を習得したいというActionです。今回は簡単に説明したいので、APIを使わずに、Fixtureデータを使います。
  • ResponseGetContactListActionには、想定的にはAPIから結果をもらう時、結果を返しましたよというActionです。

次、List画面のReducerを定義します。

 // Reducers/ContactListReducer.swift

    import ReSwift

    struct ContactListReducer {}

    extension ContactListReducer {
        func handleAction(action: Action, state: ContactListState?) -> ContactListState {
            let prevState = state ?? ContactListState()
            var nextState = prevState
            
            switch action {
            case is RequestGetContactListAction:
                nextState.model = nil
            case let action as ResponseGetContactListAction:
                nextState.model = action.model
            default:
                break
            }
            
            return nextState
        }
    }

ContactListReducerにはそれぞれのContactList画面のActionをハンドリングして、新しいStateを返します。

詳細画面にはActionが2つあると思います。

    // Actions/ContactDetailAction.swift

    import ReSwift

    struct RequestUpdateContactDetailAction: Action {}

    struct ResponseUpdateContactDetailAction: Action {
        let model: Contact
    }

    extension ContactDetailState {
        static func updateContact(with id: Int, name: String, phone: String) -> Store<AppState>.AsyncActionCreator {
            return { (state, store, callback) in
                store.dispatch(RequestUpdateContactDetailAction())
                let contact = ContactFixtures.updateData(with: id, name: name, phone: phone)
                callback { _,_ in ResponseUpdateContactDetailAction(model: contact) }
            }
        }
    }

また、ContactDetailActionをハンドリングするため、ContactDetailReducerを作ります。

    // Reducers/ContactDetailReducer.swift

    import ReSwift

    struct ContactDetailReducer {}

    extension ContactDetailReducer {
        func handleAction(action: Action, state: ContactDetailState?) -> ContactDetailState {
            let prevState = state ?? ContactDetailState()
            var nextState = prevState
            
            switch action {
            case is RequestUpdateContactDetailAction:
                nextState.model = nil
            case let action as ResponseUpdateContactDetailAction:
                nextState.model = action.model
            default:
                break
            }
            
            return nextState
        }
    }

ContactListStateとContactDetaiStateにはAppStateの属性ですので、StoreにActionが発送される時AppStateを変化します。そのため、アプリのReducerを作りましょう。

    // Reducers/AppReducer.swift
    import ReSwift

    func appReducer(action: Action, state: AppState?) -> AppState {
        return AppState(
            contactListState: ContactListReducer().handleAction(action: action, state: state?.contactListState),
            contactDetailState: ContactDetailReducer().handleAction(action: action, state: state?.contactDetailState)
        )
    }

上にメンションしましたが、The Storeまだ見ていないですよね。では、Storeを作りましょう。 リマインダー:The Storeは唯一です。

ReSwift公式ページに対して、The Storeの定義はAppDelegateに書きます。

import UIKit
import ReSwift

var store = Store<AppState>(reducer: appReducer, state: nil) //追加したコード

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    ...

StateとかActionとかReducer等、全て定義しました。これから、使いかたの説明をします。

ControllersのContactListViewController.swiftにReswiftをimportして、下記のコードをファイルの最後に入れましょう。

    // ファイルの最後に書きます。
    extension ContactListViewController: StoreSubscriber {
        func newState(state: AppState) {
            if let model = state.contactListState.model {
                dataSources = model
            }
        }
    }

次はContactListViewControllerのclasの中にfetchDataとviewWillAppearとviewDidDisappearという関数を入れます。

    private func fetchData() {
        store.dispatch(ContactListState.getContactList())
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        store.subscribe(self)
        if store.state.contactListState.model == nil {
            fetchData()
        }
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        store.unsubscribe(self)
    }

dataSourcesの定義、少し変更します。

    fileprivate var dataSources: [Contact] = [] {
        didSet {
            tableView.reloadData()
        }
    }

ここで、アプリをビルドしてみます。一覧画面からセルを選択して、詳細画面に遷移します。名前と電話番号を変更して、更新ボタンをクリックして、バックボタンを押したら、 何も変わらないことがわかります。

Animated GIF - Find & Share on GIPHYgph.is

原因は、詳細画面にはまだStoreにsubscribeしていなかったので、StoreにActionをディスパッチされていない。

一覧画面みたいな書き方に応じて、ContactDetailViewController.swiftに下記のコードを入れます。

import:

import UIKit
import ReSwift //追加したコード

class ContactDetailViewController: UIViewController {
...

subscribe:

    // ファイルの最後に書きます
    extension ContactDetailViewController: StoreSubscriber {
        func newState(state: AppState) {
            if let _ = state.contactDetailState.model {
                let _ = navigationController?.popViewController(animated: true) // 更新したあとで、すぐに一覧画面に戻ります。
            }
        }
    }

と、

    ...

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
    }
    
    <b>
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        store.subscribe(self)
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        store.unsubscribe(self)
    }
    </b>
    private func setupViews() {
        view.backgroundColor = .white
    
    ...

ContactDetailViewControllerのupdateContact()関数には下記になります

    @objc private func updateContact() {
        // ボタンをくりっくして、見やすくしたいので、ボタンの色を変更します。
        submitBtn.backgroundColor = .darkGray
        view.endEditing(true)

        let name = detailView.nameTextField.text ?? ""
        let phoneNumber = detailView.phoneTextField.text ?? ""
        
        store.dispatch(ContactDetailState.updateContact(with: contact.id, name: name, phone: phoneNumber))

        // 前のボタンの色を戻します。
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in  
            self?.submitBtn.backgroundColor = .purple
        }
    }

最後のステップ、ContactListReducerにResponseUpdateContactDetailActionをハンドリングします。

    ...
    case is ResponseUpdateContactDetailAction:
        nextState.model = nil

    ...

じゃあ、実行してみよう!

詳細画面に変更した内容が反映されましたが、また、詳細画面に開いたら?

そうです。すぐに一覧画面に戻ってしまいます。

Animated GIF - Find & Share on GIPHYgph.is

原因は? ResponseUpdateContactDetailActionに発送したが、state.contactDetailStateのmodelが値を持っているので、すぐさま親の画面に戻ります。下記のコードの原因です。

    if let _ = state.contactDetailState.model {
        let _ = navigationController?.popViewController(animated: true)
    }

解決方法はContactDetailActions.swiftに新しいActionを追加します。 ResponseUpdateContactDetailActionの下にコードを入れます。

    ...

    struct RefreshContactDetailAction: Action {}
    ...

Actionがありました。ハンドリングしよう。ContactDetailReducerにはこういう感じになります。

import ReSwift

struct ContactDetailReducer {}

extension ContactDetailReducer {
    func handleAction(action: Action, state: ContactDetailState?) -> ContactDetailState {
        let prevState = state ?? ContactDetailState()
        var nextState = prevState
        
        switch action {
        case is RefreshContactDetailAction: //追加したコード
            nextState.model = nil //追加したコード
        case is RequestUpdateContactDetailAction:
            nextState.model = nil
        case let action as ResponseUpdateContactDetailAction:
            nextState.model = action.model
        default:
            break
        }
        
        return nextState
    }
}

後、ContactDetailViewControllerのviewDidDisappear関数にはこういう感じになります。

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    store.dispatch(RefreshContactDetailAction()) //追加したコード
    store.unsubscribe(self)
}

修正が完了しました。ビルドしてみたら、うまく動きました。

Animated GIF - Find & Share on GIPHYgph.is

完了したものはこちらです

終わりに

上記にReSwiftについて、初期的に紹介しました。ReSwiftに関して、ReSwift-RouterとかReSwiftRecorder等もあります。 さらにStoreのStateが変化したら、すぐにSubscriberに反映するのはReactive感があります。ReactiveProgramingとReSwiftを結合するのはReactiveReSwiftです。 読者がReSwiftの親戚に興味があれば、公式ページにサンプルコードを参考できます。

また、猫ちゃんの言いたいことがあります。

こちらは私の友達のシーちゃんです。

f:id:curama-tech:20180410123945j:plain

"技術について、興味がある方、ぜひ、しーちゃんのチームに参加しましょう。"

次回は、Tuyenさんによる凸最適化問題の応用の記事です。

ExpressJSフレームワークの紹介

はじめに

こんにちは、みんなのマーケットのテックチームのクイです。
今回、ExpressJSフレームワークと弊社の利用の仕方を紹介します。 みんなのマーケット

ExpressJSとは

サーバーサイドJavaScriptのNode.jsのWebアプリケーションフレームワークである --wikipedia

ExpressJSは軽いウェブフレームワークですが、弊社のシステムの要求に応じてカスタムしやすいです。なので選択して利用しました。

特徴

  • 高速、柔軟、最小限のウェブフレームワーク。
  • 様々なnpmパケージに提供されています。

単純なフレームワークですから、ExpressJSを利用する時色々な処理を自分で実装しなければなりません。それはExpressJSの欠点だと思います。

NodeJSのHTTPモジュールからExpressJSのルートまで

  • NodeJSのHTTPサーバーモジュールはHTTP(Hyper Text Transfer Protocol)メソッドでデータを送信し、受信するというモジュールです。HTTPの様々な機能をサポートできるように設計がされます。
  • まずはExpressJSを使わないで、サーバーを書いてみます:サーバーが3000ポートで動いています。
const http = require("http");
const server = http.createServer();
const callback = function(request, response) {
    response.writeHead(200, {
      "Content-Type": "text/plain"
    });
    response.end("Hello world!\n");
};
server.on("request", callback);
server.listen(3000);

http://localhost:3000にアクセスしたらHello world!文字列をもらえます。

  • リクエスト処理の方法:
    上のコードを見たら、毎回サーバーがリクエストを受けると、callback関数が実行されます。なのでcallbackみたいな関数はリクエスト処理が可能になります。
    だが色々な処理ならどうするのか。幸いにもNodeJSのEventEmitクラスがその問題を解決できます。EventEmitEventEmit.emit()を呼び出してEventEmit.on()の処理をアタッチするクラスです。
    ExpressJSのコードも同じです。以下はExpressJSのコードからの抜粋です。
function createApplication() {
    var app = function(req, res, next) {
        app.handle(req, res, next);
    };

    mixin(app, EventEmitter.prototype, false); // merge-descriptorsパケージでEventEmitterとappの記述をマージします。
    mixin(app, proto, false);

    // expose the prototype that will get set on requests
    app.request = Object.create(req, {
      app: { configurable: true, enumerable: true, writable: true, value: app }
    })

    // expose the prototype that will get set on responses
    app.response = Object.create(res, {
      app: { configurable: true, enumerable: true, writable: true, value: app }
    })

    app.init();  // protoのinit()メソッドがあるから、マージした後appも用いられるようになりました。
    return app;
}

app変数はEventEmitterの継承です。サーバーがリクエストを受ける時

function(req, res, next) {
    // 処理
};

みたいなハンドルで処理します。
ExpressJSに上みたいな関数はMiddlewareと呼ばれます。

  • ExpressJSMiddleware:
    Middleware関数は要求オブジェクト、応答オブジェクト、次のミドルウェアの関数に対する権限を持つ関数です。
    Middleware関数のできること:
    • ロジックを更新可能。
    • 要求オブジェクト、応答オブジェクトを変更可能。
    • 要求応答サイクルをストップ可能。
    • 次のミドルウェアを呼び出す可能。 要求オブジェクトreqはhttp.IncomingMessageが継承され、色々な便利なメソッドが追加されています。
      例えば:
header(name: string): string;
accepts(...type: string[]): string | boolean;
acceptsCharsets(...charset: string[]): string | boolean;
acceptsEncodings(...encoding: string[]): string | boolean;
param(name: string, defaultValue?: any): string;
...

      応答オブジェクトnextは応答ステータスを設定できるとか、
      テキストあるいはファイルなど応答もできます。

status(code: number): Response;
sendFile(path: string, options: any, fn: Errback): void;
render(view: string, callback?: (err: Error, html: string) => void): void;
redirect(url: string, status: number): void;

ExpressJSにおけるスタックで一連のミドルウェアを保存します。next()関数を実行する時スタック内の次のミドルウェアを呼び出します。
次の例はミドルウェアをバインドするケースです。

  • アプリケーションレベルのミドルウェア:
const express = require("express");
const app = express();
app.use(function(req, res, next) {
    // 処理
    next();
});

アプリケーションレベルのAPI

  • エラー処理ミドルウェア:
app.use(function(err, req, res, next) {
    // エラー処理
});
  • ルートレベルのミドルウェア:
const app = express();
const router = express.Router();

router.get("/index/", function(req, res, next) {
    res.render("index");
});
app.use(router);

ルートのAPI

  • サードパーティのミドルウェア:
const csurf = require('csurf');
const express = require("express");
const app = express();
app.use(cookieParser());

ExpressJS + Typescript

以下は弊社の利用方法の一つです。
TypescriptDecoratorでルートとかミドルウェアなどをバインドします。
まずDecoratorを作成します。

export class Router {
    public static get(path: string): MethodDecorator {
        return (target: Function, key: string, descriptor: TypedPropertyDescriptor<any>) => {
            this.defineMethod(Method.GET, path, descriptor.value);
            return descriptor;
        };
    }

    private static defineMethod(method: Method, path: string, target: Object): void {
        let metadata: IMetadata = <IMetadata>Reflect.getMetadata(METHOD_METADATA, target);
        if (!metadata) {
            metadata = {
                urls: [],
                before: [],
                after: []
            };
        }
        metadata.urls.push({
            path: path,
            method: method
        });
        Reflect.defineMetadata(METHOD_METADATA, metadata, target); // TypescriptのReflectでメソッドの`metadata`を保存する
    }
}

Decoratorを利用して実装する。

import { Router } from "../../vendor/router";

export class HomeController {
    constructor() {
    }
    @Router.get("/")
    @Router.get("/index")
    public async index(req: express.Request, res: express.Response, next: express.NextFunction): Promise<any> {
        return res.json({
            message: "Hello world"
        });
    }
}

ルートのミドルウェアをバインドします。

export class Route {
    public static resolve(dir: string, router: express.Router): void {
        klawAsync(dir, { nodir: true }) // コントローラディレクトリのファイル一覧を探す。
        .map(file => file.path)
        .filter(file => file.endsWith(".js")) // javascriptファイルをフィルタリングする。
        .forEach(file => {
            const module: Object = require(file); // javascriptファイルのモジュールをインポートする。
            const controllerContainer: string[] = [];

            Object.keys(module) // 各モジュール名を取得する
            .filter(m => m.endsWith("Controller")) // コントローラをフィルタリングする。
            .forEach(m => {
                if (controllerContainer.indexOf(m) !== -1) return; // もしコントローラが登録されたら次のコントローラを処理する。
                controllerContainer.push(m);
                const instance = new module[m](); // コントローラインスタンスを作成する。

                Object.getOwnPropertyNames(Object.getPrototypeOf(instance)) // コントローラのメソッドを取得する。
                .filter(method => method != 'constructor') //constructorメソッドが無視される。
                .forEach(method => {
                    const metadata = <IMetadata>Reflect.getMetadata(METHOD_METADATA, instance[method]); // メソッドの定義されたmetadataを取得する
                    const middleware = async (req, res, next) => {  // ミドルウェアを定義する。
                        const result = instance[method](req, res, next);
                        if (result["then"]) {
                            await result;
                        }
                    }

                    metadata.urls.forEach(item => {
                        const args = [item.path, ...metadata.before, middleware, ...metadata.after]; // 一連のミドルウェアを作成する
                        router[item.method].apply(router, args); // 一連のミドルウェアを登録する。
                    });
                });
            });
        });
    }
}

コードの参考はこちらです

最後に

今後、カスタムしたExpressJSの構成をもっと独立性が守られるように他のテクニックを当てはまる予定です。ExpressJSに興味があるとか素敵な技術を持っている方はぜひお待ちしてます!
次回は、DuyさんによるReSwiftについての記事です。

Flutterモバイルフレームワークの紹介

はじめに

みんなのマーケットでテックチームに所属しています、楊です。

今回は、Googleが最近発表したFlutterモバイルフレームワークを紹介します。
簡単に言ったら、一応Google版のReact Nativeという理解でいいと思います。

最初にFlutterのドキュメントを褒めてあげたいです。順調にインストールできました。
普段だとGoogleのドキュメント通り1ステップずつ実施して、最後にうまくいかず、stackoverflowで答えを探すのが個人的な日常です。

普段のGoogleドキュメントを参照しながら、開発する私:

f:id:curama-tech:20180330140440g:plain

試す狙い

今回くらしのマーケットで新規開発する「出店登録〜審査」の機能に対して、Tech Stack を選びたい。
独立の機能なので、独立で開発して、後はlibraryの形で主Appにintegrateしたらいいんじゃないかと思う(主AppはLibraryの入り口しか知らない、High aggregation and low coupling)。

A機能を開発する際に、Module A を作って、該当 Module を独立で実行できるようにすれば、Application A で爆速 compile & run できる(UIテストも簡単になる)。
最後リリースする際に、Main Application で各 Module を組み立てて、packageする。

Appアーキテクチャ:

f:id:curama-tech:20180330140530p:plain

要件

  • CrossPlatformの開発能力
  • うちのサポートしているデバイスやOSバージョンに合わせる
  • 既存のNative Appに集約できる
  • Material Designに優しいFrameworkなら、更によい。

結論

React Nativeより完成度や便利性がもっと高いFrameworkで、私の理想のようだけど、まだ本番アプリには使えない段階だ

理由1 - サポートしているデバイスやOSバージョン:

  • まだ 32 bit の iOS デバイスに対応していない(iPhone 4 や iPhone 5など)
  • Android sdk 16以上のみに対応している (うちは 14 から)

理由2 - 大事なWebViewはまだ実装していない:

  • ひとつのアプリの中にたまにnativeで実装したくない機能があると思う

理由3 - JSX よりもっとやばい書き方:

  • 目が痛い
    @override
    Widget build(BuildContext context) {
        return new Scaffold(
           appBar: new AppBar(
               backgroundColor: Theme.of(context).canvasColor,
               elevation: 0.0
           ),
           body: new Column(
               crossAxisAlignment: CrossAxisAlignment.stretch,
               children: <Widget>[
                   // Give the key-pad 3/5 of the vertical space and the display 2/5.
                   new Expanded(
                       flex: 2,
                       child: new CalcDisplay(
                           content: _expression.toString()
                       )
                   ),
                   const Divider(height: 1.0),
                   new Expanded(
                       flex: 3,
                       child: new KeyPad(calcState: this)
                   )
               ]
           )
       );
   }

理由4 - Dart 言語少し不安定(まだ進化中):

  • 今使っている Dart 1 はまるで no-JVM java Lite な感じがする(同じように、null safety がない、why!!!)
  • Kotlinの勉強を始めたばかり! !!

すばらしさ

  1. SDK レベルの Redux
  2. No more controllers / views / layouts / viewGroup / xml / xib / activity / fragment / delegate / resource
  3. Only ウィジェット / State / Reducer ひとつの ウィジェット を一ページとしてもいいし、複数のウィジェットを組み立てもいいし、すごくアジャイルである。
    つまり開発過程はレゴ玩具のように作れる。No More MVC, MVVM, MVP, VIPER(一部分のNative開発者はすごく嫌かも)。Reducer はまったくUIと関係がないし、Pure Fuction なので、ユニットテストは書きやすい。

  4. SDK レベルの Router 画面遷移はさらに簡単になる。WebView(もしあれば^_^||)やプッシュ通知からも簡単に Native ページを起動できる(urlと同じフォーマット)

  5. 既存のNative Appに集約できる

  6. ひとつのページに半分 Native 半分 Flutter も簡単に実現できて、お互いに通信できる(MessageChannelを通して)

  7. 豊富な UI フレームワーク 標準のUIフレームワークに Android の Material Design ウィジェット と iOS の Cupertino Style ウィジェットを両方提供している。 これで Android に Cupertino 風アプリ、 iOS に MD 風アプリを簡単に作れる。

Material Design:

f:id:curama-tech:20180330140628p:plain

Cupertino:

f:id:curama-tech:20180330140642p:plain

  1. iOS UIKit や Android View Package に依存していない React Native は JS を iOS と Android それぞれの Component に変換していく。
    これで大きな問題ふたつがある:システムの Component が修正したり、バグあったりのとき、React Native は待つことしかできない;また、両方統一していない Component は if else の分岐でわけて開発しないといけない。
    Flutterの方は全部の ウィジェット をゼロから実装したので(Skia の力)、Platform や OS とかかわらず、統一している。問題が発生しても、Google 側ですぐに解決ができる。
    もし React Native は Learn Once, Write Everywhere なら、 Flutter は Write Once, Deploy Everywhere だなぁ。

現実世界のReact Native(表から考えると小さな開発だけど、着手したら大きな罠が待っている):

f:id:curama-tech:20180330140657g:plain

  1. 仮想 DOM React Native と同じように、仮想 DOM を実装した。
    これによって State から行われる画面の更新が結構早くなるし、Hot Loading 機能も合わさってさらに開発の効率が上がる。

  2. 便利な auto layout iOS の Constraint Layout と Android の Layout 両方サポートしている(React Native は flexbox のみ)。

  3. Dart 言語に非同期処理がサポートされている Dart 1 には Future を使っていて、 Dart 2 には async await も追加していきました。
    (はい、Swiftさん、君のことに不満がある)

非同期処理:

f:id:curama-tech:20180330140858g:plain

最後の一言

実際数年前、同じような技術はすでに存在している。 Adobe の AIR っていうことだ。

みんなのマーケットでは、一緒に働いてみたいといった方をお待ちしてます。

次回は、Webエンジニアの記事です。