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

CureApp開発者ブログ

アプリで治療する未来を創造するCureApp, Inc. のエンジニアブログです

functionのbindメソッドのシンタックスシュガーがJSの保守性をより高める

要約

JSで保守性を高めながら複数人で中規模以上の開発をするというのは今までは至難の業だったが、 ES6+の登場でやりやすくなってきている。

さらにそれを推し進めるのが Function Bind Syntax · Babel obj::methodというシンタックスシュガーだ。 これで関数の階層化を防ぐことができるとともに、 無駄なクラスを作る必要もなくなり、見通しがよくなって保守性が高くなる。

introduction

JSで中規模以上の開発はできる

弊社CureAppはJavaScriptの会社。 今まではCoffeeScriptを用いたOOPによって保守性を高めて開発していたが、 ES6+を使うことでも保守性を高めた開発ができるということがわかってきた。

JSで保守性を高めた開発をするためには、変数の状態がどこからでも変わり得るという恐ろしい事態を避けることが大事だ。

JavaScriptで、別のスコープに変数を共有する方法

別のスコープに変数を共有する方法は3つだ。

  1. 引数
  2. this
  3. 外のスコープの変数の参照

1. 引数

function scopeA() {
    let foo = 123
    scopeB(foo)
}

function scopeB(x) {
    console.log(x  + 1)
}
scopeA() // 124

2. this

class Foo {
    constructor() {
        this.foo = 123
    }

    scopeB() {
        console.log(this.foo + 1)
    }
}
new Foo().scopeB() // 124

3. 外のスコープの変数の参照

const foo = 123
function scopeB() {
    console.log(foo + 1)
}
scopeB() //124

ここで以下の問題を上記3つの方法それぞれで解いて比較してみよう。

例題

export function onRequest(req, res, options) {
}

あなたはこういう関数が外部から呼び出されるモジュールを作っている。 reqresは複雑な構造を持ったオブジェクトだ。

例えば、req.headerを見て、適切な値かどうかチェックする処理をいれたい。

1 引数で渡す

export function onRequest(req, res, options) {
    const isValid =  isValidHeader(req.header)
}

function isValidHeader(header) {
    // なんかの処理
}

okこれでいい。 では、optionsの内容によってisValidHeaderの挙動を変えたいということが出てきたら?

export function onRequest(req, res, options) {
    const isValid =  isValidHeader(req.header, options)
}

function isValidHeader(header, options) {
    // なんかの処理
}

これは皆さんお気づきの危険シグナルだ。プロジェクトが進むと引数はどんどん増え、 ほとんどの関数が微妙に違う4つぐらいの引数を持っている状態になる。 引数名もargs, params, config, optsと微妙に違うものが乱立して辛い状態になっていたりするのだ! 恐ろしい。

2 クラスをつくる

class RequestHandler {
    constructor(req, res, options) {
        this.req = req
        this.res = res
        this.options = options
    }

    init() {
        const isValid = this.isValidHeader()
    }

    isValidHeader() {
        this.req.header // これでreqがとれる
        this.options // これでoptionsもとれる
    }
}
export function onRequest(req, res, options) {
    new RequestHandler(req, res, options).init()
}

保守性も高いだろうが、ちょっと大げさだったりする。 そしてプロジェクトが進むと、すべての雑多な処理がこのクラスのメソッドにぶら下げられて、 「なんのクラスなのか分からん」状態になる。

3 外のスコープに置く

let req, res, options;

export function onRequest(_req, _res, _options) {
    req = _req
    res = _res
    options = _options
    const isValid = isValidHeader()
}

function isValidHeader() {
}

ああ、これはやっちゃだめだー!これで地獄をみたプロジェクトはたくさんあるはず。 例えば複数回このモジュールを呼んだとき、プログラマの脳がstack overflowする。

ということで、3つの方法、いずれにも欠点があった。

新しいsyntax obj::method で解決

ここでobj::methodを使ってこの問題を解いてみよう。

export function onRequest(req, res, options) {
    const subject = { req, res, options }
    const isValid = subject::isValidHeader()
}

function isValidHeader() {
    this.req.header // これでとれる
    this.options // これでとれる
}

これすごい!

解説

subject::isValidHeader()は、 「subjectをthisにして、isValidHeaderを実行してね」という意味。

昔は isValidHeader.bind(subject)() などという書き方をする必要があった。 このbindはもちろん便利だったが、トリッキーに見え保守性はあまり高くなかったように思っていて、 大きなプロジェクトでは敬遠される機能だった(と私は思っている)。

しかしsubject::isValidHeader()を見給え。 これは、「subjectをthisにしてるんじゃないかな?」というのが分かりやすいではないか!

  • 引数渡しと違い、渡すものが増えてもコードを書き換える必要がない
  • クラスより簡潔に書ける
  • 外のスコープを汚さない
  • 読みやすい

4つをクリアしたこの記法、チームでの開発ではぜひ実践したい。

株式会社CureAppはチームで簡潔で保守性の高いJavaScriptを書きたいエンジニアを募集しています。