Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

elm-upgradeに従ってElmのバージョンを0.18から0.19へ上げる

ForSchool事業部の石上です。ウェブのフロントエンドを中心にStudyplus for Schoolの開発に携わっています。

あるアプリケーションのElmのバージョンを0.18から0.19に上げる対応をしました。今回はこのことについて書きます。

背景

Studyplus for School で新たにちょっとしたサブシステムが必要になり、その小さなSPAのためのウェブフロントエンドの言語として今回、Elmを採用しました。

弊社のプロダクトのほとんどは、サーバーサイドはRuby on Railsで作られており、ウェブフロントエンドはJavaScriptかTypeScriptです。Elmでウェブのフロントエンドを書くことはけっこう挑戦的でしたが、会社としても他の言語に手を伸ばしていきたいという話もあり、CTOとチームリーダーから許しをいただきElmでの実装に至りました。

ある程度実装が終わった段階でElm 0.19のリリースが発表され、これまでのElmの仕様から大きく変更がありました。主要パッケージの移動、関数の仕様変更の数々...。しかしこれはある程度覚悟していたことです。今回実装したものがまだリリース前かつ小規模なアプリケーションだったので、1日くらいガッとやれば対応できるだろうということで、対応しました。

対応手順

  • Elmのバージョンを上げる
  • elm-upgradeを実行
  • elm-upgradeで自動修正されない部分を手で書き換え

Elmのバージョンを上げる

yarn upgrade elm@0.19-bugfix2

elm-upgradeを実行

elm-upgradeというツールがあるので、それを使います。

npx elm-upgrade

elm-upgradeを実行すると、修正の方針について何点か質問されるのでそれに答えながら修正を実行していきます。

INFO: Found elm at node_modules/.bin/elm
INFO: Found elm 0.19.0
INFO: Found elm-format at node_modules/.bin/elm-format
INFO: Found elm-format 0.8.1
INFO: Cleaning ./elm-stuff before upgrading
INFO: Converting elm-package.json -> elm.json
INFO: Detected an application project (this project has no exposed modules)
INFO: Installing latest version of elm-community/list-extra
Here is my plan:

  Add:
    elm-community/list-extra    8.1.0
Would you like me to update your elm.json accordingly? [Y/n]:
  • elm-stuffを消す
  • elm-package.jsonからelm.jsonへ移行する
  • 最新のelm-community/list-extraをインストールする

と言われています。

Elm 0.19からはパッケージの依存関係を記録するファイルが elm-package.json から elm.json になっています。 インストールしたモジュールのコードはelm-stuff から ~/.elm になりました。

elm-upgrade がこの移行をやってくれます。パッケージを入れ直そうとしているので、そのための質問が続きます。全部Yesと答えました。

完了すると、以下のメッセージが表示されます。

SUCCESS! Your project's dependencies and code have been upgraded.
However, your project may not yet compile due to API changes in your
dependencies.

See <https://github.com/elm/compiler/blob/master/upgrade-docs/0.19.md>
and the documentation for your dependencies for more information.

Here are some common upgrade steps that you will need to do manually:

- elm/core
  - [ ] Replace uses of toString with String.fromInt, String.fromFloat, or Debug.toString as appropriate
- undefined
  - [ ] Read the new documentation here: https://package.elm-lang.org/packages/elm/time/latest/
  - [ ] Replace uses of Date and Time with Time.Posix
- elm/html
  - [ ] If you used Html.program*, install elm/browser and switch to Browser.element or Browser.document
  - [ ] If you used Html.beginnerProgram, install elm/browser and switch Browser.sandbox
- elm/browser
  - [ ] Change code using Navigation.program* to use Browser.application
  - [ ] Use the Browser.Key passed to your init function in any calls to Browser.Navigation.pushUrl/replaceUrl/back/forward
- elm/url
  - [ ] Changes uses of Navigation.Location to Url.Url
  - [ ] Change code using UrlParser.* to use Url.Parser.*

elm-upgradeで自動修正されない部分を手で書き換え

上記のメッセージの通り、以降はソースを手で直していきます。親切にリストになっているので、これを上から潰していけばいいでしょう。

- elm/core
  - [ ] Replace uses of toString with String.fromInt, String.fromFloat, or Debug.toString as appropriate
- undefined
  - [ ] Read the new documentation here: https://package.elm-lang.org/packages/elm/time/latest/
  - [ ] Replace uses of Date and Time with Time.Posix
- elm/html
  - [ ] If you used Html.program*, install elm/browser and switch to Browser.element or Browser.document
  - [ ] If you used Html.beginnerProgram, install elm/browser and switch Browser.sandbox
- elm/browser
  - [ ] Change code using Navigation.program* to use Browser.application
  - [ ] Use the Browser.Key passed to your init function in any calls to Browser.Navigation.pushUrl/replaceUrl/back/forward
- elm/url
  - [ ] Changes uses of Navigation.Location to Url.Url
  - [ ] Change code using UrlParser.* to use Url.Parser.*

Replace uses of toString with String.fromInt, String.fromFloat, or Debug.toString as appropriate

2件ありました。以下のように修正しました。

- toString 1000
+ String.fromInt 1000

Read the new documentation here: https://package.elm-lang.org/packages/elm/time/latest/

https://package.elm-lang.org/packages/elm/time/latest/ を読むと、Elm 0.19においての時間の取扱について書かれています。Daylight Saving Timeの話を出したりしつつ、人間用の時刻をモデルやデータベースに持たせるんじゃない! ということが書かれています。データとしてはPOSIXタイムとタイムゾーンとして扱い、人間用の表現はモデルではなくビューの関数の中でやるんだぞというようなことが書かれています。なるほど。

Replace uses of Date and Time with Time.Posix

0.19ではそのような考えがモジュールに反映されています。これまでの0.18ソースで使っていたDateとTimeをTime.Posixに置き換える必要があります。

Time.everyしか使っていなかったので特に変更は必要ありませんでした。

If you used Html.program*, install elm/browser and switch to Browser.element or Browser.document

使っていないので関係ありませんでした。

If you used Html.beginnerProgram, install elm/browser and switch Browser.sandbox

使っていないので関係ありませんでした。

Change code using Navigation.program* to use Browser.application

今回このアプリケーションはSPAとして実装、つまりページを読み込み直さずに状態とURLを書き換えてページ遷移を行いたいと考えていました。このフロントエンド側でのルーティングの機能は、Elm以外の言語・ライブラリでもたいてい何かしらの形で提供されています(ReactならReact Routerなど)。

Elm 0.18では「クリック時のデフォルト挙動を無効化しつつ、引数でメッセージを渡して副作用を起こす」みたいなヘルパーを書いてこれを実現していました。 こういう感じです

Elm 0.19ではこんなことをしないでもこの機能を実装することができるようになったようです。

Browser.applicationを見ると、関数シグネチャはこうなっています。

application :
    { init : flags -> Url -> Key -> ( model, Cmd msg )
    , view : model -> Document msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    , onUrlRequest : UrlRequest -> msg
    , onUrlChange : Url -> msg
    }
    -> Program flags model msg

applicationに渡すレコードの中に、onUrlRequestonUrlChangeがあります。名前からしてこれらを使えば良さそうです。それぞれ説明を読んでみます。

onUrlRequest

When someone clicks a link, like <a href="/home">Home</a>, it always goes through onUrlRequest. The resulting message goes to your update function, giving you a chance to save scroll position or persist data before changing the URL yourself with pushUrl or load. More info on this in the UrlRequest docs!

リンクのクリック時にデフォルトでonUrlRequestメッセージが発行されるとのこと。ありがたい!

onUrlChange

When the URL changes, the new Url goes through onUrlChange. The resulting message goes to update where you can decide what to show next.

↑URLが変更されるとonUrlChangeになるとのこと。

UrlRequest Browser.UrlRequestUrlChange Url.Urlというメッセージを用意してあるとすると、こういう感じになります。

    Browser.application
        { view = view
        , init = init
        , update = update
        , subscriptions = subscriptions
        , onUrlRequest = UrlRequest
        , onUrlChange = UrlChange
        }
update msg model =
        UrlRequest urlRequest ->
            case urlRequest of
                Browser.Internal url ->
                    ( model, Navigation.pushUrl model.nav.key (Url.toString url) )

                Browser.External url ->
                    ( model, Navigation.load url )

        UrlChange url ->
            ({ model | url = url }, Cmd.none)

Use the Browser.Key passed to your init function in any calls to Browser.Navigation.pushUrl/replaceUrl/back/forward

Elm 0.18でページ遷移したいときは、Navigation.newUrl "/hoge" みたいな形でできました。0.19のBrowser.NavigationにはnewUrlは無く、見てみるとpushUrlを使えば良いようです。

しかし遷移先の文字列をただ渡すのではなく、Browser.Keyなるものを渡す必要があるみたいです。

pushUrl : Key -> String -> Cmd msg

このKeyは外から書き換えたり出来ない値で、initで入ってくるものをModelに持っておいて、pushUrl などURL変更の関数に渡して使います。

You only get access to a Key when you create your program with Browser.application, guaranteeing that your program is equipped to detect these URL changes. If Key values were available in other kinds of programs, unsuspecting programmers would be sure to run into some annoying bugs and learn a bunch of techniques the hard way!

Changes uses of Navigation.Location to Url.Url

言われている通り修正しました。

Change code using UrlParser. to use Url.Parser.

言われている通り修正しました。

その他

elm installできない?

Elmのモジュールの依存関係はnpmなどと同様、1つのJSONファイルに記録されます。しかし npm install で全部ダウンロードというようなことはできません。elm 0.19で elm install を実行すると以下のメッセージが表示されます。

~/project (feature/update-elm-to-0.19 *)$ elm install
-- INSTALL WHAT? ---------------------------------------------------------------

I am expecting commands like:

    elm install elm/http
    elm install elm/json
    elm install elm/random

Hint: In JavaScript folks run `npm install` to start projects. "Gotta download
everything!" But why download packages again and again? Instead, Elm caches
packages in /Users/ishigami/.elm so each one is downloaded and built ONCE on
your machine. Elm projects check that cache before trying the internet. This
reduces build times, reduces server costs, and makes it easier to work offline.
As a result elm install is only for adding dependencies to elm.json, whereas
elm make is in charge of gathering dependencies and building everything. So
maybe try elm make instead?

パッケージは./elm-stuff ではなく、 ~/.elm にキャッシュされます。

Variable Shadowing

こんなエラーも出てきました。

./src/Main.elm
[=========================                         ] - 1 / 2-- SHADOWING --------------------------------------------- src/Views/Hoge.elm

The name `hoge` is first defined here:

23| viewHogeName hoge =
                    ^^^^^^^
But then it is defined AGAIN over here:

25|         Just hoge ->
                 ^^^^^^^
Think of a more helpful name for one of them and you should be all set!

Note: Linters advise against shadowing, so Elm makes “best practices” the
default. Read <https://elm-lang.org/0.19.0/shadowing> for more details on this
choice.
Detected errors in 1 module.

怒られている通り、こう直します。

viewHogeName maybeHoge =
    case maybeHoge of
        Just hoge ->
            hoge.name

        Nothing ->
            ""

エラーメッセージの通り、なんでこう直さないと怒られるんだというのは以下に書かれています。

Regex

I cannot find a `Regex.regex` variable:

330|             Regex.regex "hogehoge"
                 ^^^^^^^^^^^
The `Regex` module does not expose a `regex` variable. These names seem close
though:

    Regex.never
    Regex.find
    Regex.replace
    Regex.split

Hint: Read <https://elm-lang.org/0.19.0/imports> to see how `import`
declarations work in Elm.

Regexも変わっていました。今回エラーが出た Regex.regexRegex.fromString になったようです。以下のような変更が必要でした。

removeHogehoge hogehoge =
    let
         regex =
-            Regex.regex "hogehoge"
+            Regex.fromString "hogehoge"
        in
-            Regex.replace Regex.All regex (\{ match } -> "") hogehoge
+            Regex.replace regex (\{ match } -> "") hogehoge

さらにその他

非標準ライブラリにもElm 0.19で変わったものがあり、その対応も必要でした。

所感

変更範囲はかなり大きいので、大きめのアプリケーションを0.19対応するのは相当大変そうです。

しかし、elm-upgradeが最初にやることリストを出してくれたりコンパイルエラーにここ読めリンクが適切に貼られていたりと、とても親切に感じました。

変更点についても、Browser.applicationで簡単にSPAを作れるようになっていたりして、SPAのフレームワークとして改善されていると感じました。

参考記事

UIPickerViewをUIControlを使用してキーボードの様に表示する

こんにちは。 入社して一ヶ月が経過したiOSエンジニアの弘田です。
今回はUIPickerViewをキーボードの様に表示する方法を解説します。

なぜそんなことをするの?

昔のiPhoneでしたら画面の中心などにUIPickerViewを表示しても画面サイズが小さかったので片手で操作できていましたが、最近は大画面化が進み片手での操作が難しくなってきました。
操作性を損なわずにUIPickerViewを使用してもらう為にも今回の方法が役にたつと思います。

Human Interface GuidelinesでもPickerついて触れているページがありこの様な記載があります。

Avoid switching screens to show a picker. A picker works well when displayed in context, below or in close proximity to the field being edited.

翻訳
ピッカーを表示するように画面を切り替えることは避けてください。ピッカーは、編集中のフィールドの下、または近くにコンテキストで表示されたときにうまく機能します。

今回目指すもの

※シミューレーターなのでキーボードを閉じるアニメーションが少しおかしいです

実装

1. UIControlを継承したclassを作る

class pickerKeyboard: UIControl {

}

2.イニシャライザを作成し、自身がタップされた時にinputViewを出す処理を作る

class pickerKeyboard: UIControl {
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        addTarget(self, action: #selector(tappedPickerKeyboard(_:)), for: .touchDown)
    }
    
    @objc private func tappedPickerKeyboard(_ sender: PickerKeyboard) {
        self.becomeFirstResponder()
    }
}

3.canBecomeFirstResponderをtrueで返して自身をFirstResponderにする

canBecomeFirstResponderのデフォルトはfalseになっていて、
trueを返さないと後述のinputViewで指定したViewが表示されません。

class pickerKeyboard: UIControl {
    
    //~~~省略~~~
    
    override var canBecomeFirstResponder: Bool {
        return true
    }
    
}

4.FirstResponderになった上でinputViewをoverrideする

ここでは表示したいViewを返します。
今回はUIPickerViewをaddSubviewしたUIViewを返します。
UIViewを返す理由はSafeAreaに対応するためです。
inputViewについて(Apple公式)

class pickerKeyboard: UIControl {

    //~~~省略~~~

    override var inputView: UIView? {
        let pickerView: UIPickerView = UIPickerView()
        pickerView.delegate = self
        pickerView.dataSource = self
        pickerView.backgroundColor = UIColor.white
        pickerView.autoresizingMask = [.flexibleHeight]
        
        // SafeArea対応をする為にUIViewを挟む
        let view = UIView()
        view.backgroundColor = .white
        view.autoresizingMask = [.flexibleHeight]
        view.addSubview(pickerView)
        
        pickerView.translatesAutoresizingMaskIntoConstraints = false
        pickerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        pickerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        pickerView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
        
        return view
    }
}

5.inputAccessoryViewをoverrideしてUIPickerViewを閉じるボタンを作る

class pickerKeyboard: UIControl {

    //~~~省略~~~

    override var inputAccessoryView: UIView? {
        
        let view = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight))
        view.frame = CGRect(x: 0, y: 0, width: frame.width, height: 44)

        let closeButton = UIButton(type: .custom)
        closeButton.setTitle("閉じる", for: .normal)
        closeButton.sizeToFit()
        closeButton.addTarget(self, action: #selector(tappedCloseButton(_:)), for: .touchUpInside)
        closeButton.setTitleColor(UIColor(red: 0, green: 122/255, blue: 1, alpha: 1.0), for: .normal)

        view.contentView.addSubview(closeButton)
        
        closeButton.translatesAutoresizingMaskIntoConstraints = false
        closeButton.widthAnchor.constraint(equalToConstant: closeButton.frame.size.width).isActive = true
        closeButton.heightAnchor.constraint(equalToConstant: closeButton.frame.size.height).isActive = true
        closeButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 5).isActive = true
        closeButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true

        return view
    }

    @objc private func tappedCloseButton(_ sender: UIButton) {
        resignFirstResponder()
    }
}

6.通常のUIPickerViewと同様にUIPickerViewDelegateとUIPickerViewDataSourceを継承してデータを表示

class pickerKeyboard: UIControl {
    let array:[String] = ["A","B","C","D","E"]
        
    //~~~省略~~~
}

extension PickerKeyboard: UIPickerViewDelegate, UIPickerViewDataSource {
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return array.count
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return array[row]
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        // delegateなどでViewControllerに選択された情報を渡す
    }
}

7.StoryboradやXibでUIViewのCustomClassとして設定する

まとめ

手順4のUIPickerViewを作った時にpickerView.backgroundColor = UIColor.whiteとしているのでわかり難いですが、 別の色に変更するとSafeArea対応できていることが確認できます。

記事で紹介したコードはGithubで公開しています。
https://github.com/srknra/PickerKeyboard

今回はUIPickerViewをキーボードの様に表示してUXを低下させない様な工夫でしたが、
Studyplusのアプリでは他にもユーザーの事を考えて様々な工夫をしてるので今後もブログで紹介していこうと思います。

Visual Studio Codeの拡張「GitHub Pull Requests」を使ってみた

こんにちは。7月に入社したStudyplus開発部の田口です。
先日Microsoft社が発表したVisual Studio Codeの拡張のGitHub Pull Requestsを試してみたのでその記事を書きます。

まずは弊社のエンジニア陣が普段なんのエディタを使っているのか、アプリチーム以外でアンケートを取ってみました。

  • RubyMine … 2人
  • IntelliJ IDEA … 1人
  • Atom … 1人
  • Visual Studio Code … 1人
  • Vim … 3人
  • Emacs … 1人

Vim強しですね。Visual Studio Codeは自分しか使っていないようでした。
また、アプリチームの方々もXcodeやAndroid Studio以外で、普段よく使うエディタを聞いてみたところ

  • Sublime Text … 1人
  • Atom … 1人
  • Visual Studio Code … 2人

でした。こちらはVisual Studio Codeを使ってるエンジニアが2人いました。嬉しいですね。

RubyやRailsを書くエディタとなるとまず思いつくのがJetBrains社のIDEであるRubyMineですが、前にReact/Reduxを書く際にVisual Studio Codeを使って以来すっかりお気に入りのエディタになったのでずっと使っています。
GitHub社がMicrosoft社に買収されてから、マイクロソフト製のプロダクトにGitHub関連の機能が追加されていくのかなと予想していたのですが、まさにそういったパッケージだと思います。
今回は、GitHub Pull Requestsを実際に使ってみた様子を記載していきます。

インストール

基本的には、Visual Studio Codeの拡張機能が検索できるVisual Studio Marketplaceで「GitHub Pull Requests」と検索すれば出てきます。
Image from Gyazo

使い方

サインイン

拡張を追加すると、画面右下にGitHubへのサインインを求めるダイアログが出るので、クリックしてGitHubにサインインします。

追加項目

GitHub Pull RequestsをインストールしてからVisual Studio Codeのソース管理タブを開くと、「GITHUB PULL REQUESTS」という項目が追加されています。デフォルトは閉じていて最下にあるのでちょっと見落としがちです。
GitHubで管理されているプロジェクトをルートとして開くと、自動的に「GITHUB PULL REQUESTS」に以下の項目が追加されます。
Image from Gyazo
ここではGitHub上にあるプルリクエストを項目別で見ることができます。自分が作成したプルリクエストのみ、自分がアサインされたプルリクエストのみといった項目があります。
例として、RailsのGitHubリポジトリに上がっているプルリクエストを見てみます。
Allを選択すると、今見ているGitHubリポジトリの全プルリクエストが表示されます。
Image from Gyazo
Railsには2018/09/25現在で700以上のプルリクエストが上がっていました。基本的には最新の20件までがデフォルトで表示されるみたいです。

Conversationを見る

確認したいプルリクエストをクリックして開き、さらにその下の「Description」をクリックすることで、GitHub上の「Conversation」タブで見れるページがエディタ部分に表示されます。
こちらも、Railsのリポジトリを例に見ていきます。
Image from Gyazo
ブラウザでGitHubを確認することなくここでプルリクエストの概要やレビューのやり取りを確認できます。Visual Studio Codeで設定したカラースキームでプルリクエストが確認できるのが個人的に嬉しいポイントです。
コミットハッシュのリンクをクリックするとブラウザでGitHubの当該コミットのページを開きます。
右上のCheckoutボタンで、当該ブランチをワンクリックでチェックアウトできたりもします。
また、コメントやレビュー、プルリクエストを閉じたりもVisual Studio Code上でできます。便利ですね。
Image from Gyazo
現状ではプルリクエストのマージはできないみたいですね。

差分を確認する

GitHub上の「File Changed」も簡単に見れます。「Description」以下がファイルごとの差分になっており、それをクリックするとエディタ部分に表示されます。
Image from Gyazo
アイコンは

  • A … Add
  • D … Delete
  • M … Modify

だと思われます。直感的ですね。
行番号の右にある+ボタンをクリックすることで、コメントをつけることもできます。
Image from Gyazo

今後改善してほしい点

非常に便利なこの拡張ですが、個人的に今後のバージョンアップで改善してほしい点としては

  • マルチルートワークスペースに対応してほしい
  • マージできるようにしてほしい
  • レビュアの設定などができるようになってほしい

という感じでしょうか。
GitHub上でできることがすべてVisual Studio Code上でできるようになってくれれば最高なんですが、さすがに高望みな気もしています。
個人的に特に気になる点はマルチルートワークスペース非対応なところです。複数のリポジトリを横断的に見るためにマルチルートワークスペースを利用しているのですが、現状はマルチルートワークスペースだとGITHUB PULL REQUESTSの項目が表示されないようになっています。
ここが改善されて対応してくれるようになるとさらに虜になると思うので、期待したいところです。

まとめ

GitHub Pull Requestsを触ってみて、自分はかなり便利だと感じました。
まだバージョンも0.1.6(2018/09/25現在)なので、今後のアップデートに期待したいと思います。

buildersconに行ってきた

スタディプラスCTOの島田です。 今回は9/6~8に開催されたbuilderscon tokyo 2018へ行ってきた感想を書きます。

f:id:yo-shimada:20180912200947j:plain

最初に

buildersconとは

buildersconは、「知らなかった、を聞く」をテーマとした技術を愛する全てのギーク達のお祭りです

という趣旨のもと、インフラ・IoT・サーバー・デザイン・etcと本当に幅広いジャンルのセッションが聞けます。

builderscon.io

スタディプラスは今回スポンサーとして協賛をさせて頂きました。 ノベルティグッズとしてペンと付箋を提供しました。

f:id:yo-shimada:20180912201804j:plain

当日は私を含め3名(島田、石上、田口)でbuildersconに行ってきました。 どのセッションも大変興味深かったのですが、その中でもこの3名のがそれぞれ特に印象に残ったセッションの感想を書かせてもらいます。

島田

IoT開発の闇

SNS等でのシェア禁止の内容という事で詳細は触れられないのですが、闇(笑)を物凄く堪能できました。これだけで前夜祭行って良かったかも。

パスワードレスなユーザー認証時代を迎えるためにサービス開発者がしなければならないこと

speakerdeck.com

パスワード認証の現状は、色々と漏洩リクスがあり課題・問題ありという整理に共感。 ではパスワードレスを実現するための最新動向はどうなっているかという点での、FIDO 2.0、WebAuthn APIの紹介にまだまだ普及への時間がかかりそうだが、未来を感じた。

「Web とは何か?」 - あるいは「Web を Web たらしめるものは何か?」

Webの黎明期の動向から丁寧に説明があり、Webに関わる人の視点から、どうWebが変化したが整理されていた。 そして、今後のWebがOSに近づいていくのでは、という流れが秀逸。

石上

ForSchool事業部の石上です。 私は普段ウェブアプリケーションのエンジニアなので、それに関連ありそうな発表を中心に聞いてきました。

個人的に1日目、2日目でそれぞれ特に面白かったのは以下の発表です。

Electronによるアプリケーション開発事情2018

ElectronベースのMastodonクライアントWhalebirdを個人で開発されているh3potetoさんの発表。 趣味でここまでできるのすごい...。

リリース直後はElectron製ということで評判が悪かったそうですが、原因を調べてパフォーマンスの問題を解決したりして、今では多くの人に快適に使ってもらえているそうです。

こちらの発表では、実装面でのSwiftでiOSアプリを開発していたときとの比較があり、勉強になりました。

私はiOSのことを全然知らないので、なんとなくSwiftのほうがGUI設計の考え方は進んでるんだろうなくらいに思っていました。 そのため、Fluxを使えることがElectron選択のメリットとなることに少し驚きました。

一方で、TootのStreamingによる描画パフォーマンスの劣化の話では、iOSのUITableViewの描画の最適化みたいなところは便利にできてるんだなと感心しました。

その他もMac App Storeへの配布周りのつらみなど、お試しデモアプリだけ作っていては出てこないような問題について聴けて貴重でした。

デザイナーとうまく協働する方法

buildersconの中では異色な、デザインをつくる際のコミュニケーションについての発表でした。 偉い人の意見を優先してしまう、あるいはデザイナーの作ったものを無条件で正しいとしてしまったりせず、論理的にデザインを作っていく方法について話されていました。

「よい」というニュアンスを自分の中ではなく組織内で共有するためのプロセスが必要で、それに基づいてデザインの評価は行われるべき。そうでないと、デザインがセンスや好みだったり偉い人が決めるみたいな状況になってしまう。そうならないように、徹底した言語化と文書化が必要という話でした。

普段開発ばかりしていると、ドキュメントをあくまでも補助的な、「あったらいいよね」くらいのものとして考えてしまいがちです(少なくとも私はそうでした)。しかし、こちらの発表を聞いて、ドキュメントは議論のベースに使ったり手戻りを防ぐための重要なツールであると認識することができました。

田口

1日目、2日目それぞれで自分が良いと思ったセッションの感想を書きます。

1日目: ブロックチェーン(DApp)で作る世界を変える分散型ゲームの世界

speakerdeck.com

この発表は、発表者の緒方さんご自身がブロックチェーンを用いて作っているゲームの説明を元に、分散型ゲームの世界を解説していくというものでした。 発表が丁寧でわかりやすい説明だったので、ブロックチェーンをほとんど知らない自分はとても助かりました。

ブロックチェーンは様々な分野で活用される可能性がありますが、まだまだ実験段階のようで、現在は主に「投資」の面で利用されていることが多いそうです。 そんな中、「投資」ではなく「利用」に重きを置いたブロックチェーンの活用という点で、ブロックチェーンを利用した様々なゲームが紹介されていました。 発表内では、ブロックチェーンゲームの特徴として ・ゲーム内で購入や投資したものが資産になる可能性がある ・セキュアで公平な取引 ・「トークンエコノミー」と呼ばれるトークンの互換性 の三つが挙げらています。 個人的には特に三つ目のトークンエコノミーが気になります。価値をトークンに落とし込むことで、様々なものに応用できそうです。 シームレスな接続のために規格が統一されてきているそうなので、今後が楽しみです。

2日目: RDB THE Right Way ~壮大なるRDBリファクタリング物語~

speakerdeck.com

普段自分たちが利用しているRDBの設計・リファクタリングをどのようにやっていくかという発表でした。 アンケートシステムの回答を保存するという具体的な例を元に、どのような罠があり、どう対応していくかが明確でわかりやすかったです。

データベースで扱うのは「データ」、アプリケーションで扱うのは「情報」であり、どのようにデータを保存するかを設計する「データ設計」と、データをどのように加工・利用するかを設計する「情報設計」を、まずそもそも認識できていなかったなと反省しました。 情報を優先してデータ設計をするとデータに矛盾が生まれるので、まずはモデリングをしっかり行うことも常に念頭に置いていこうと思います。 Entityの定義やそれの関連付けなど、とても勉強になることばかりでした。 そーだいさんが紹介されていたSQLアンチパターンの本をしっかり読もうと思います。

最後に

3名とも初めて参加したのですが、どのセッション大変面白く是非とも来年も参加をしたいと思います!

記念撮影ブースで石上、田口が撮影してもらった素敵な写真をあげておきます。

f:id:yo-shimada:20180912201421j:plain

iOSDC2018に参加しました

こんにちは。スタディプラスに9月からiOSエンジニアとして入社した弘田です。

Kotlin festの記事でも紹介しましたが、 弊社には 勉強会・カンファレンス参加補助があるので、iOSDC2018に参加させていただきました。ありがとうございます。 弊社のiOSエンジニアも登壇したので弊社からは2名参加しました。 内一名は「iOSでグラフを描くために必要な知識について」というタイトルで登壇しています。

ちなみにスタディプラスは今回シルバースポンサーとして協賛して、ノベルティ(付箋とボールペン)を提供しております。

iOSDC 2018とは

iOSDC JapanはiOS関連技術をコアのテーマとした技術者のためのカンファレンスです。 去年の開催を大盛況のもと終え、みなさまのBlogやSNSでのシェアのおかげで無事、帰って参りました! そして、今年は3日+前夜祭の3.5日開催です!祭りです!予定を空けておいてください!

iOSDC公式ページから引用

参加したセッション

セッションの数が多いのでいくつか抜粋してご紹介させていただきます。 ※登壇者の敬称略

「標準アプリから学ぶ、HIGが教えてくれないiOSデザインのこと」

Ryo Usami : https://speakerdeck.com/usa619/biao-zhun-apurikaraxue-bu-higgajiao-etekurenaiiosdezainfalsekoto

最近よく見るSemi-ModalViewの解説があり、ふわっとした認識が固まったので聞けて良かった。


MicroViewControllerで無限にスケールするiOS開発

tarunon : https://www.icloud.com/keynote/0vgTYDXyHQTd0l1FKTiF1jT7g#MicroViewController-en

ベストトーク賞1位のセッション まだちゃんと理解しきれてないので、スライドの読み直しやビデオを見たいと思います。


5000行のUITableViewを差分更新する

ばんじゅん🍓 : https://speakerdeck.com/banjun/difference-update-uitableview-with-5000-rows

最近流行っている差分更新 5000行もあれば遅くなるので、差分更新ライブラリなどを比較して早くするのが非常に面白かった。


UIViewとUITextInputで作る縦書きのTextView

六々 : https://speakerdeck.com/cc4966/vertical-textview-based-on-uiview-with-uitextinput

標準では用意されていない縦書きのTextViewを自作するセッション 文字の描画エンジンも自作だそうで開発に数年かかっている大作らしい。 基本的に縦書きで表現したいことは少ないが需要はあると感じた。


iOSでグラフを描くために必要な知識について

須藤将史 : https://speakerdeck.com/masashi_sutou/iostekurahuwomiao-kutamenibi-yao-nazhi-shi iOSDC2018で「iOSでグラフを描くために必要な知識について」というタイトルで発表しました

弊社iOSエンジニアのセッションです。 Studyplusではグラフが多く使用されており、その全てをライブラリを使用せず自前で書いています。 グラフを書く際に必要な知識などがセッション内で解説されています。


その他のセッション

Qiitaにまとめてくださっている方がいました。 iOSDC 2018 セッション資料まとめ

雑感

  • 今回が3回目の開催 回を重ねるごとに参加者が増えている 参加者が増えているのにスタッフの対応の質などが毎年上がっている 会場のネットワークチームが快適なWi-Fiを提供してくれていた!

  • ランチは毎日違う物が提供されていた

  • スポンサーブースの力の入れ方がすごかった

まとめ

聞きたいセッションが被っていることが多かったので、「まだiOSDCは終わっていない!」というのが個人的な感想。 いつになるのかは不明だが、全セッションの動画がYouTubeに上がるはずなので早く見たいです。

懇親会にも参加し美味しい料理やお酒を楽しみながら、交流をできたのも良かったです。

来年も是非開催してくれることを願っています。

スタディプラスでは現在、Swift化を進めています。是非、興味ある方はご連絡お待ちしています

GraphQLを導入しようとしている話

こんにちは。Studyplusでサーバーサイドを担当している金澤です。 弊社ではいまapiの一部にGraphQLを導入するべく取り組んでいます。

GraphQLってなんだという話や導入手順などはweb上にすでに沢山あると思います。 なのでそのへんはあっさりめで、検証にあたってどのような実装をしているかという話をします。

で、GraphQLってなんだ

公式ページから言葉を借りれば、

A query language for your API

です。

apiに対する問い合わせをクライアントで組み立てて柔軟にできます。

上記ページのデモがとても分かりやすいのでピンと来ない方は是非ご覧ください。

GraphQLでできる3つのこと

query

  • データの問い合わせ
  • 今回はこの話だけします

mutation

  • データの変更

subscription

  • いわゆるpub/sub

なぜGraphQLなのか

動機としては

  • パフォーマンスの改善のため、各種apiを一つにまとめようという話が以前からあった
    • GraphQLによってパフォーマンスがよくなるという話ではなく、apiが細分化しすぎていてインデックスも複雑(あるいは効いてない)というケースを整理しましょうという流れ
  • 画面をトライアンドエラーするとき、いちいち微調整にコミュニケーションコストがかかるのは勿体無い
    • ある程度好きにできるapiを用意するので、開発フェーズによってはそれを柔軟に使って完結してほしい
    • クライアントとサーバを同じ人が作るような体制の場合はこういう凝った仕組み必要ないかもしれない
  • ナウい
    • たまには新しいものを取り入れないと澱む

といったところです。

導入方法

まっさらなrailsプロジェクトにGraphQLを導入する手順です。

railsプロジェクトを作る

$ rails new graphql_test

graphql-rubyのインストール

https://github.com/rmosolgo/graphql-ruby

gem 'graphql'
$ bundle install
$ rails generate graphql:install
  • 上記コマンドでGemfileにgem 'graphiql-rails', group: :development というのが追加されるので、使う場合はもう一度bundle install

    • graphiqlは開発用のインタラクティブな画面です
    • 多分graphicとgraphqlの言葉遊びなのかな?
  • app/graphql以下にいろいろ関連ファイルができます

    • application.rbconfig.eager_load_paths << "#{config.root}/app/graphql" を足すと便利

graphiql

rails s して http://localhost:3000/graphiql から見れます

Hello, World!

インストールが終わったら、graphiql画面に{ testField }と入力して実行しましょう。 いつものが返ってきます。

Queryで頻出しそうなパターンをどう実装したかの話

ここからが本題です。 データの問い合わせapiを作るにあたって、よく登場するパターンをどう解決したかという例を三つほどご紹介します。

ユーザー権限をチェックしたいパターン

正しくインストールされていると、app/controllers/graphql_controller.rb というコントローラができてます。 下記のような感じです。

class GraphqlController < ApplicationController
  def execute
    variables = ensure_hash(params[:variables])
    query = params[:query]
    operation_name = params[:operationName]

    context = {
      # Query context goes here, for example:
      # current_user: current_user,
    }
    result = GraphqlTestSchema.execute(
      query, variables: variables, context: context, operation_name: operation_name
    )
    render json: result
  rescue => e
    raise e unless Rails.env.development?
    handle_error_in_development e
  end
  # 以下略

コメントにあるように、コンテキストを意識したい場合はcontext に追加し、後から取り出せます。 権限チェックのためにログインユーザーが欲しい、というような場合は例えば下記のように実現できます(実際のコードではないです)。

GraphiQL::Rails.config.headers["Authorization"] = -> (context) { "Bearer hogehoge" }
    # 前略
    auth_header = request.headers["Authorization"]
    session = find_session_by_header(auth_header)
    context = {
        session: session
    }
    # 以下略

こうすると、query_typeやobject_type内でresolveする際にctxからセッションを取得できるようになります。

field :test, types.String do
    resolve ->(obj, args, ctx) {
        ctx[:session] # コントローラでセットしたオブジェクト
    }
}

ページングするパターン

データの問い合わせにページを指定したり、検索文字列を指定したりなどよくあると思います。

以下のようにクエリにはargumentを設定することができます。

Types::QueryType = GraphQL::ObjectType.define do
  name "Query"

  field :page, !types[types.Int] do
    description "ページングテスト"
    argument :per_page, types.Int, default_value: 5, prepare: ->(limit, _ctx) { [limit, 5].min }
    argument :page, types.Int, default_value: 1
    resolve ->(_obj, args, ctx) {
      page = args[:page]
      limit = args[:per_page]

      arr = %w(1 2 3 4 5 6 7 8 9 10)
      offset = [(page - 1), 0].max * limit
      last = offset + limit
      arr[offset...last]
    }
  end
end

上記query_type.rbを上記の内容にし、下記クエリをqraphiql画面から入力することで、ページングが確認できます。

{ page(per_page: 3, page: 2) }
{
  "data": {
    "page": [
      4,
      5,
      6
    ]
  }
}

lazyに呼ぶパターン

GraphQLはapi呼び出し側でクエリを自由に書けるので、呼び出される側が無駄な計算をしないように気をつける必要がありました。

例えば、下記のようなTypeがあったとします。

Types::Test::LazyType = GraphQL::ObjectType.define do
  name 'Lazy'

  field :karui, !types.String
  field :omoi, !types.String
  field :sinu, !types.String
end

このtypeを使った返事を以下のように実装した場合、

Types::QueryType = GraphQL::ObjectType.define do
  name "Query"

  field :lazy, !Types::Test::LazyType do
    resolve ->(_obj, _args, _ctx) {
      karui = "軽い!"
      omoi = -> () {
        sleep(3)
        "重い..."
      }.call # ここが重い
      sinu = ->() {
        raise "死ぬ"
      }.call # ここで死ぬ

     OpenStruct.new(karui: karui, omoi: omoi, sinu: sinu)
    }
  end
end
  • クエリで必要なものだけ取得できる意味がない
  • というかこの場合は必ず死ぬ
  • しかも順番に実行されていくので重い作業を待った末に死ぬ

という本末転倒なことになります。

そこで下記のように、必要な分だけ計算するようにしました。

Types::QueryType = GraphQL::ObjectType.define do
  name "Query"

  field :lazy, !Types::Test::LazyType do
    resolve ->(_obj, _args, _ctx) {
      res = OpenStruct.new
      def res.karui
        "軽い!"
      end

      def res.omoi # ここでは重くない
        sleep(3)
        "重い..."
      end

      def res.sinu # ここでは死なない
        raise "死ぬ"
      end

      res
    }
  end
end

こうすることで、

  • { lazy { karui } } 実行時は軽い
  • { lazy { karui omoi } } 実行時は重いものを取得したいのでしょうがない
  • { lazy { sinu karui omoi } } とすることで一瞬で死ぬため計算資源を無駄にしない
    • ただし、{ lazy { karui omoi sinu} } とすると順番に実行されてやはりomoi分が無駄になる

当たり前の話ですが、個別に実行できるようにし、必要なものを必要なだけ取るようにできると無駄がありませんでした。

終わりに

まだ手探り状態なので、実運用に乗せたら何が起きるかなどはわかっていませんし、テスト運用して採用を取りやめる可能性もゼロではないと思っています。

起きそうな問題としてパッと思いつくのは、

  • cartesian product問題やN+1問題に代表されるようなデータアクセス上の典型的諸問題が、レイヤーを一段またいで脳みそが二つになることによってより厄介になったりしないだろうか
    • たくさん取得してクライアントで選別しよう、みたいなコードを書こうと思えば書けてしまう
    • もちろんapiの作りっぷりで制限することは可能で、そういう意味では従来の作りとあまり変わっていないかもしれない
  • endpointが一つになるので、NewRelicなどでパフォーマンス計測する際よくわからないことにならないか?
    • クエリ別に集計する必要がありそうだが対応しているのか?
      • { hoge fuga piyo}fuga piyo hoge は同じものとして集計されてほしいのか違うのかなど場合によって違いそう
    • 関係ないけどNewRelicの無料プランがだいぶ制限されるようなのでどなたか良い代替プロダクトを教えてください

といったところでしょうか。

とはいえ、うまく使えば開発効率が上がりそうな手応えも感じています。 色々な導入例や運用例から、ベストプラクティスやアンチパターンが出来上がっていくと思いますし、追随していきたいと思います。

Kotlin festに行ってきました

弊社には 勉強会・カンファレンス参加補助 があります。 その制度を使って会社のお金でKotlin Festに参加させていただきました。補助といいつつ基本全額サポートしてくれました。 ありがとうございます。

image.png (42.8 kB)

Kotlin Festとは

「Kotlinを愛でる」をビジョンに、Kotlinに関する知見の共有と、Kotlinファンの交流の場を提供する技術カンファレンスです。(connpassから引用)

愛でる と言われる言語なんて今まであっただろうか。 Kotlinかわいいとも言われている。Kotlinかわいい。

そんな素敵な言語のカンファレンスです。

ざっくりとKotlinをご紹介

色々な方がKotlinについて説明してくれているので、詳しいことはそちらにお願いするとして。 オープニンセッションでお聞きしたKotlinの哲学をそのまま流用してます。

  • 実用主義: Javaの考えがそのまま使える、学習が容易!
  • 簡潔: 読みやすさ重視でボイラープレートを減らす
  • 安全: 静的型付け、Null安全
  • 相互運用性: Javaと相互に行き来、Javaライブラリの豊富な資産が使える!

より良いJavaなイメージです!

セッション

※登壇者の敬称略

image.png (11.4 MB)

オープニングセッション

長澤太郎 藤原聖

ここで発表いただいた Kotlinの哲学 は上記で引用させていただきました

参加したセッション

「Kotlin で改善する Android アプリの品質」

あんざいゆき https://speakerdeck.com/yanzm/kotlinfest

「How to Test Server-side Kotlin」

鈴木 健太 ・ 前原 秀徳 http://htn.to/kwU92M

「start from Convert to Kotlin」

望月美帆 https://speakerdeck.com/mochico/start-from-convert-to-kotlin

「Kotlin コルーチンを理解しよう」

八木俊広 https://speakerdeck.com/sys1yagi/kotlin-korutinwo-li-jie-siyou

その他のセッションなど

こちらにまとめられています。Kotlin Fest 2018 - 資料一覧 - connpass

雑感

  • 記念すべき一回目
    今回の盛り上がりを見ると第二回も開催しそう!

  • 参加者 367名

  • チケット完売!
  • 人が多すぎてwifiつながらないときもアリ
    ほんとに人がたくさんいました。会場の椅子が足りなくて床に座ってセッション聞いてたこともありました。登壇者の発表に拍手が頻繁に起きたりいい雰囲気でした。

  • ランチは会場のレストランで特別に500円ランチが。カツカレー
    ちなみに会場はコチラ

  • mixiさんのブースでやってたKotlin Quiz 5問正解でお菓子もらえた。

  • Androidエンジニア以外にもサーバーサイドでkotlin使ってますという人が意外と多かった。
  • トートバッグもらった
    友人たちに好評。 ことりかわいい→KotlinかわいいでひとつKotlinの普及

IMG_4865.JPG (81.7 kB)

まとめ

セッションの内容や途中少しお話させていただいた方、企業ブースなどを見てKotlinの盛り上がりを改めて認識しました。Kotlin正式リリース前に初めて触ってから考えると、日本語の記事も導入事例も爆発的に増えてきました。 今年GoogleがAndroid公式言語としてKotlinが選ばれたためKotlin == Android と思われがちですが、サーバーサイドでの導入事例も増えてきています。実際私が参加したセッションではサーバーサイドで使ったことある方が4割ほどいました。

AndroidのみならずサーバーサイドやJs / Kotlin nativeなどもあり、これから益々Kotlinを活用するプロジェクトが増えてくると思います。むしろKotlin好きとしては増えてくれることを願っています。

私は参加してないのですが、Kotlin Festの懇親会はとても美味しい料理が出たという噂が。 心残りはそれだけです。第二回は必ず懇親会に参加したい。

スタディプラスでは現在、Kotlin化を進めています。是非、興味ある方はご連絡お待ちしています