20160703 yapc8oji レガシーなsdkをnodeで書き直して一年間メンテした学び

44
レガシーなSDKをNodeで書き直して 一年間メンテした学び 7/3 YAPC::Asia Hachioji 2016 mid in Shinagawa by fuku2015

Upload: kazuya-fukumoto

Post on 14-Apr-2017

812 views

Category:

Engineering


0 download

TRANSCRIPT

レガシーなSDKをNodeで書き直して一年間メンテした学び

7/3 YAPC::Asia Hachioji

2016 mid in Shinagawa

by fuku2015

fuku2015 • Twitter @FukMo2014

• ニフティ株式会社 – クラウドエンジニア?(PaaS開発/SDK開発)

– IoTアーキテクト?(コンサルとかSIっぽいこと)

– 新人研修のメンターとか後輩のトレーナーとか

• Scrum Alliance 認定スクラムマスター (2016/6~)

自己紹介

2

今日のお話

• やったこと – (当時)新人が一人でレガシーなJavaScriptSDKをNode.jsで書き直した

– それをOSS公開して一年間メンテした

• 話すこと – 開発・メンテ中のエピソード・学び

– 育てる側にもなった今、当時のやり方を振り返る

• 話さないこと – サービスの具体的な使い方

書いたもの

• JavaScript SDK for NiftyCloud mobile backend v2

mbaas github

ニフティクラウド mobile backend

• スマホ向けのBaaS(Backend as a Service)

– RESTAPIでバックエンド機能が使える

– プッシュ通知、DB、ユーザ認証etc

– 2016/3にスクリプト機能を追加

• APIからサーバサイドに設置したスクリプトを実行

• AWSのlambdaみたいなやつ

書き直した経緯

• v1がメンテしづらかった – 先行サービスを多分に参考にした複雑なコード

• 謎機能・謎引数

• ドキュメントも不十分

• SDKなのに何故かviewがある

– テストがまともになかった

• サーバサイドで動くSDKが必要になった – スクリプト機能

やったこと

• 元のSDKを捨ててすべてNodeで書き直し – フロントエンド用のファイルはbrowserifyで生成

• CI環境を整備

• GitHubでOSSとして公開

• npm/bowerでpublish – パッケージ名:ncmb

開発中の様子はこちらをご覧ください

• NodeでmBaaSのSDKを書いてみた – 東京Node学園17時限目LT

– http://www.slideshare.net/kazuyafukumoto54/0831-nodelt

• 内容三行 – JS初心者の新人がひとりでSDK書いた

– coverage80未満でテストが落ちる細工されて辛い

– CI環境作って新人放り込めば成長して出てくる

テスト落ちる細工が好評

例のライオンで本人降臨

強制圧倒的成長

どこかに被害者

主な改善点

• Promiseとcallbackが両方使える – フロントエンドでもpromise chainで書ける

• method chain標準装備 – 非同期処理以外は基本的にthisを返すように統一

• Queryがクラスメソッドでシンプルに書ける – 以前は検索対象クラスを指定したインスタンスが必要だった

v2 v1

callback/promise

Query

• クラスメソッド実行→Queryのインスタンスをreturn

– リクエストメソッドまでreturn thisでchainする

– プロパティ情報がクラスに残らない

開発しての振り返り

• SDKは基礎を身に着けるのに最適だった – HTTPの基本要素を一通り使う

• GET/POST…、ヘッダ、非同期通信、認証、バイナリ etc…

– 少しだけ違うメソッドを反復的に書く

• 手で覚える

• レビューの学びを他ですぐ実践できる

• 似てるからこそ理解しないと書き分けできない

振り返って一番良かった点

• 「とことんやる」合意が先輩と早い段階でとれた – お互い気兼ねなく?育てる/られる

– 先輩のレビュー・要求が(こちらの体感で)全力

– 「レビューまだすか?」とか言いやすい

– 育てられてる実感があると応えたくなる

信頼関係と相互コミットメント

育て(られ)戦略

• 若手サイド(やったこと/やってほしいこと)

– 世話焼いてくれる先輩を見つける

– 「踏み込んで大丈夫」感を行動で示す

– 遠慮せずコミュニケーションをとりにいく

• 先輩サイド(やってもらったこと/意識してること)

– 話しやすい空気/環境を作る

– 勝手に踏み込めるレベルを判断せずに対話する

– 忙しくても全力でコミットする

大事なことなので(ry

それからの活動

• 一般的なメンテ業務 – バグ修正

– 細かな機能追加

– issue・p-r対応

• スクリプト機能対応

• テストリファクタ

テストの負債

• Nodeテストは実システムだと動かなかった – ランダム生成のidをスタブでは固定文字列で結果比較

– 重複不可のユーザー名を各所で使用 • スタブファイルを使いまわし

• Monaca用のテストが別ファイルで存在 – WebUIなのでローカルのスタブが使えない

– テストコードはbrowserifyできなかった

– 事前の準備が面倒(データ入れておく等)

– ドキュメントが一切ない

テストリファクタ

• 実システムで動かない要素を極力排除

• スタブテストからフロントテストを生成 – npmコマンド一発

– browserifiable化できなかったので力技

• isStubフラグを追加 – 環境を判断して処理を分離

• ユーザ名を切り替え

• 実システムでは手作業必須の部分をスキップ 等

• ドキュメント作成

やってみた結果

• 同一ファイルで両対応の自動テストが完成 – 実システム上でも事前準備なし

– テスト工数大幅削減

– 属人性排除

• ユニットテストかと言われると微妙 – 作成のテストで作ったインスタンスを削除のテストで利用したり

– ただ、ユニット毎に一々データ作成するとテストコード量が倍以上になるので考えどころ

一年メンテした学び

• ログインIFの設計に失敗した話

• superagentの闇が深かった話

ログインIFの設計に 失敗した話

ユーザ管理機能

• ユーザの登録/認証ができる – ログインAPIで取得したトークンをリクエストヘッダに付けて識別

– ACLに使う

• 以下のログイン方法がある – ユーザ名/PW

– メールアドレス/PW

– SNSアカウント利用 • Facebook, twitter, Google

– 匿名ユーザ • 本登録せず、一度ログアウトすると消滅する

v2での新しい要望

• 複数ユーザのログイン状態を保持したい – 複数のユーザを使うときに毎回ログインリクエストをしたくない

• 取得したトークンはインスタンスに持たせたい

• ヘッダのトークンを簡単に切り替えたい

– 取得したトークンをヘッダにセットせず、インスタンスにもたせるだけの処理を可能にしたい

こんな感じになった

結果どうなったか

• メンテナンスコストが増大 – 引数のパターンが増えてバリデーションが大変

• コードが複雑化・可読性低下

• 異常系を弾くパターンが漏れる

– 引数の数に対してテスト項目数が指数的に膨れる

• ユーザビリティが低下 – 使い方が複雑

• メソッドの使い分けがわかりにくい

• インスタンスのログインでヘッダがつかないのは非直感的

– ドキュメント拡充・Qiita執筆でフォロー

ncmb.User(user) ncmb.User(user, callback())

↓ ncmb.User( user.username, user.password) ncmb.User( user.username, user.password, callback())

ncmb.User(userName, callback())

↓ ncmb.User( userName, null, callback())

userNameとpasswordのバリデーションはprototype.loginの中でされる →上の例だとpasswordがなくて弾かれる

何が悪かったのか

• 一つのIFで欲張りすぎた – クラスメソッドに変数とインスタンスを両方入れられるようにしたことで複雑さが爆発

• 要求に対する検討不足 – トークンだけ取得してすぐリクエストで使わないケースはかなりイレギュラー

• というか今のところ見たことない

学び

• 書いてて辛いコード=技術的負債 – 書いてて辛いコードは後で読むと五倍辛い

– 書いてて辛いテストはもはや読まれない

– 書いてる段階で一定以上の辛みを感じたら何かが間違っている

• ユースケースと頻度を考えて設計すべき – シンプルな機能がシンプルに使えるのが第一

superagentの 闇が深かった話

superagent

• Http通信のモジュール • SuperAgent is a small progressive client-side HTTP request

library, and Node.js module with the same API, sporting many high-level HTTP client features.

全然同じじゃなかった

browserify前後の変化

• レスポンスのオブジェクト構造が違う – しかもオブジェクトが大きいのでわかりにくい

• Httpレスポンスの格納先が違う – res.textだったりres.bodyだったり

• エラーの格納先が違う – errだったりerr.response.errだったりres.errorだったり

片っ端からバリデーション

バイナリファイル編

• Node module – 通信はhttpモジュール

– bodyのMIMEを判別してBufferクラスに入れてくれる • あとはfsモジュールがBufferクラスをよしなにしてくれる

• Browserified – 通信はXMLHttpRequest

– リクエストにresponseTypeを設定する必要がある • そのためのIFはNodeのコードには一切存在しない

• ドキュメントにもない – client.js(browserify時の参照先)を追いかけて自力で発見

• 設定しないとresponseTextでテキストとして受け取る

マジでハマった

• 一見取得できてるので問題切り分けミス

表示で変換されたとかじゃなくて

最初からテキストファイルだった

学び

• Browserifyは魔法の杖だが黒魔術 – Same APIかどうかはモジュールによる

• superagentの場合は完全に別ファイル

• OSSに勝手に品質を期待してはいけない – あくまで善意の提供物

– スターの数は賞賛であって品質保証ではない

– そもそもオープンなんだからソース読もう

– 何か気づいたらむしろp-rチャンス

感想

• 運用してみることでの気づき・学びがたくさんあった – 特にメンテナンスコストはやってみて初めて辛みが分かる

• 時間をおいてコードを読むと色々見える – この資料作ってて無駄コード大量発掘

– 自分が一回忘れた時にそのコードの質が分かる

そろそろ次のアクション

宣言

個人でも OSS開発やります!