こちらは、PLAID Advent Calendar 2019 22日目の記事です。
11月にプレイドにエンジニアとして入社しました。
これまでは2年ほど機械学習関連プロダクトのプロトタイプ開発を行っており、モデリングや前処理の部分はPython, 軽くフロントを作るために時々Vue.jsを書いているという感じだったのですが、プレイドではフロントエンドにVue.js, サーバーサイドにNode.js を採用しており、JavaScript一色の開発環境の中で日々奮闘しています。(しかも、実際に稼働しているプロダクションの開発は初という)
そんな環境の中で1ヶ月間程開発をしてきたにあたって、詰まったこと・学んだことを共有していきたいと思います。ポエムっぽくなる気がしていますが、お付き合いいただけると嬉しいです。
対象読者
- JavaScriptの超基本文法はわかるけど、JavaScriptでのプロダクト開発経験が乏しい人
- 転職して間もない、かつ経験が少ないエンジニアがどのようなところで苦労しているか気になる人
そもそも非同期処理分からない
まずぶち当たった壁がこれでした。Node.jsというものが初めてだったということもあり、割と困惑しました
言葉で説明するよりコードで見てもらった方が良いと思うのですが、
function increment(a) {
setTimeout(function() {
return a + 1;
}, 1000); //何かしら時間のかかる処理(今回は擬似的にsetTimeout)
}
const result = increment(2);
console.log(result); //undefined
??? ちゃんと変数に代入してるのに? Pythonなら以下のように問題なく動くのに、、
import time
def increment(a):
time.sleep(1)
return a + 1
result = increment(2)
print(hoge) #3 1秒後に表示される
さらに、実際にはundefinedでない時もあり、非同期処理という概念が自分の中にない状態だと余計に戸惑いました。
解決方法は数多の先人たちが残してくれているので、詳細には説明しませんが、以下のような例が考えられると思います。
function increment(a) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve(a + 1);
}, 1000);
});
}
increment(2).then(result => {
console.log(result); // 3
});
人によって書き方がバラバラ
「ふむふむ、"非同期処理完全に理解した"」となった瞬間にぶち当たったのがこれです。
//aとcallbackを受け取って、何かしらの処理をしてcallbackを返す関数
function hoge_callback(a, cb) {
//何かしらの処理
cb(err, data);
}
//bを受け取って、インクリメントした値をPromiseオブジェクトとして返す関数
function fuga_promise(b) {
return new Promise((resolve, reject) => {
resolve(b + 1);
});
}
//hoge_callbackを呼ぶ時
hoge_callback(a, (err, data) => {
console.log(data);
});
//fuga_promiseを呼ぶ時
fuga_callback(b).then(result => {
console.log(result);
});
コード例の通り、callbackを返す関数とPromiseオブジェクトを返す関数が混在している状態であったため、呼び出し方もそれらに合った方法にしなければいけず、Node.js初心者の自分にとってはかなり困惑してしまいました。他にもasync/await
とかもあるので、非同期処理周り分かり合える気がしないなという気持ちになってました。。もういっそcallback地獄でも良いわという魔が刺しそうになることも。
逆に() => {}
とfunction() {}
って書き方違うだけで中身同じやんと思ってたら、this
の挙動が違うという。。
詳しくは関数とthis · JavaScript Primer #jsprimer を参考にしてもらえればと思います。
const hoge = {
a: "fuga",
b: function() {
return this.a;
},
c: () => {
return this.a;
}
};
console.log(hoge.b()); //fuga
console.log(hoge.c()); //undefined
どこにロジックを依存させるか
JavaScript依存の話ではありませんが、ここも自分の中で大きく学べたことなので、共有したいと思います。
現在開発しているプロダクトのコードはざっくりと以下のような構成になっています。
- Vue Component: 画面表示を担う
- Store: 画面表示のためのデータの状態を管理する
- Router: VueとLogic間の情報の受け渡しを行う
- Logic: DBから取得した情報を処理してVue側に伝える/Vue側で変更した情報を処理してDBに保存する
- MongoDB: 名前そのままにDB
以上の構成に則り実装し、想定通りの動作をすることが確認できたので、レビューに出したところ、レビューワーの方に「Rounter内にロジックは書かないほうが良いよ」と言われました。自分の書いたコードでは、情報の受け渡しを行っているRouterの部分にロジックを書いていたのです。
レビューワー曰く、ここは情報を受け渡すだけのところだからロジックは後ろに移動したほうが良いとのこと。いわゆるMVCモデルでいうコントローラの部分でロジックは書いちゃいけないということかと納得しました。
前職ではプロトタイプ開発ということもあったからか、そこらへんをあまり意識せずに書いていたので、自分の中では大きな学びになりました。
最後に
以上が1ヶ月間JavaScript一色の開発環境に飛び込んで学んだことになります。
初歩的な話ばかりだったかもしれませんが、おそらく初心者が詰まりやすいポイントだと思うので、誰かしらの助けになればと思います。
O'Reilly Japan - Node.jsデザインパターン 第2版もかなり参考になったので、共有しておきます。
他にもVue.jsのStoreパターンって良いな〜とか、Our seeking to stable k8s deployment.で紹介されているようなマイクロサービスって便利だな〜とか、いろいろ感じたことはあるのですが、またどこかで学んだことを共有できればと思います。