JavaScript の Promise について

しばらく触れていないとすぐ忘れてしまうので Promise についてのメモを残しておく。

基本的には JavaScript Primer を読み返すと理解が早い。

Promise の状態

Promiseの状態
Promiseの状態

new Promise(executer) すると Pending 状態の Promise インスタンスが生成される。 executer は同期的に(つまり、new Promise を呼び出した瞬間に)実行される。 executer はお馴染みの 以下のような関数。

(resolve, reject) => {
    // 処理が成功したときはresolveを呼ぶ
    // 処理が失敗したときはrejectを呼ぶ
}

この関数内で同期的な関数しか呼んでいなければ、すぐさま Fulfilled or Rejected の状態に遷移するし、 非同期の関数を呼んでいれば非同期処理の結果で Fulfilled or Rejected の状態に遷移する。

Promise.resolve と Promise.reject

Promise.resolve は Fulfilled 状態の Promise インスタンスを返す。 実態は new Promiseの糖衣構文。

// const fulfilledPromise = Promise.resolve(); と同じ意味
const fulfilledPromise = new Promise((resolve) => {
    resolve();
});

Promise.reject も Rejected 状態の Promise インスタンスを返すだけで同様。

例外

new Promise(executer) のexecuter 内で発生した例外は、 reject と同じ扱いになる。 executer 内の

reject(new Error(message));

throw new Error(message);

はほぼ同じ。

then と catch

Promise インスタンスに対して then(onFulfilled, onRejected) を呼び出すと、Promise インスタンスの状態が Fulfilled or Rejected となった時に onFulfilled or onRejected が呼び出される。 catch は then(undefined, onRejected) と同じ処理を catch(onRejected) として書ける。

thenの返り値

then は Promise インスタンスを返す。 これによって、Promiseチェーンが実現されている。

then(onFulfilled, onRejected) において、

  • onFulfilled or onRejected が Promiseインスタンスを返す場合
    → この場合特別扱いされ、onFulfilled or onRejected が返した Promise インスタンスをthen もそのまま返す。
  • onFulfilled or onRejected が Promiseインスタンス以外の値を返す場合
    → onFulfilled or onRejected が返したvalueに対して then は Promise.resolve(value)を返す。
  • onFulfilled or onRejected 内で例外が発生した場合
    → 発生したerror に対して then は Promise.reject(error) を返す。

Go の Must のついたメソッド

今日は Go について書きます。今回はさらっと。

github.com/jmoiron/sqlx を使っていて MustExec と Exec の違いなんだっけ、となったのでどんなメソッドに Must が付くか調べ直しました。

結論

先に結論です。

Must のついたメソッド: エラーが発生した場合に panic を発生させる関数

Must のないメソッド: エラーが発生した場合、返り値で error を返す関数

よって、絶対にエラーが発生しない(正規表現コンパイルとか、html の template とかコンパイル時には既に確定している内容)処理の場合のみ Must のついたメソッドを使用してよいです。

実行時に panic を起こす可能性がある場合は Must のないメソッドを使用し、エラー処理をしっかり行う必要があります。

sqlx の例

まずは冒頭で触れた sqlx の godoc を見てみます。

func (*NamedStmt) Exec

func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error)

Exec executes a named statement using the struct passed. Any named placeholder parameters are replaced with fields from arg.

func (*NamedStmt) MustExec

func (n *NamedStmt) MustExec(arg interface{}) sql.Result

MustExec execs a NamedStmt, panicing on error Any named placeholder parameters are replaced with fields from arg.

Exec は 返り値として sql.Result と error を返します。 MustExec は返り値として sql.Result のみを返し、

panicing on error

します。 その他は基本同じですね。

プログラミング言語 Go での記述

プログラミング言語 Go を読んだときに何回か出てきたなーという印象だったため、読み返して見ました。

4.6 節で html/template パッケージの例を出している場所で以下のように述べられています。

テンプレートはたいていコンパイル時に確定しているので、テンプレートのパースに失敗するというのはプログラムの致命的なバグを示しています。template.Must ヘルパー関数はエラー処理をより便利にしています。その関数はテンプレートとエラーを受け付けて、エラーが nil であるかを検査し(nil でなければパニックになります)、そしてそのテンプレートを返ます。

事前に用意した正しいテンプレートを用いる場合、パースに失敗するはずがない(あれば致命的なバグ)であるということですね。 逆に、(あまりないでしょうが)プログラム中で動的にテンプレートを生成する場合は、パースに失敗しうるのでエラーチェックを正しく実装すべきです。

regexp

Must が出てくる他の例は標準パッケージの regexp です。

正規表現も固定の文字列を用いる場合がほとんどで、コンパイル時には確定しているというわけですね。

godoc には以下のようにあります。

func MustCompile

func MustCompile(str string) *Regexp

MustCompile is like Compile but panics if the expression cannot be parsed. It simplifies safe initialization of global variables holding compiled regular expressions.

終わりに

他の言語ではあまり見ないような気がするので、Go 特有の命名でしょうか。 適切に使い分けていきたいと思います。

Javaのアクセスレベル (public, protected, パッケージプライベート, private)の使い分け

前回Javaの4つのアクセスレベルについて書きました。

今回はその4つのアクセスレベルをどう使い分ければ良いか書きたいと思います。

結論

要件を満たす中で、最小のアクセスレベルを選択する。

トップレベルのクラス

他のパッケージに公開する必要がある
→ public

他のパッケージに公開する必要がない

→ パッケージプライベート

クラスメンバ

他のパッケージに公開する必要がある

→ public, protected

他のパッケージに公開する必要がない

→ パッケージプライベート, private

基本方針

Effective Java には以下のように書かれています。

大まかなやり方は単純です。すなわち、各クラスやメンバーをできる限りアクセスできないようにすべきです。

基本的にはこの方針に従っていけば問題ありません。

各アクセスレベルを理解しておき、今実装しているクラスやメンバをどこまで公開する必要があるかを考えていきます。

public, protected vs パッケージプライベート, private

「public, protected」と「パッケージプライベート, private」の間にまず大きな違いがあります。

public, protected は異なるパッケージからアクセス可能、つまりAPIを公開することになります。

なので、トップレベルのクラス・インターフェースに関しては他のパッケージに公開する必要がある場合のみ public にし、必要がなければパッケージプライベートにすべきです。

メンバーに関しても、他のパッケージに公開する必要がある場合のみ public にします。

public vs protected

ここでも できる限りアクセスできないようにすべき という方針に従うのみです。

protected を使うと、別パッケージではそのクラスを継承したクラス内でしか見れなくなります。

そのため、継承せずに別パッケージから見る必要があれば public 、そうでなければprotected とします。

しかし、 protected を使う場合はパッケージプライベートや private ではダメなのか再考の余地があります。

先に述べた通り public や protected は API を公開することになるため、安易に実装を変えられなくなりますので、 protected を使わずともパッケージプライベートで要件を満たせるのであれば大きなリターンがあります。

Effective Java でも以下のように書かれています。

protected のメンバーの必要性は、比較的にまれであるべきです。

継承させて使うことを意図したクラスを公開するというのは決して多くないと思います。

パッケージプライベートvs private

この2つは基本的にクラスの公開APIに影響しません。

同一パッケージからそのクラスやクラスのメンバにアクセスする必要があればパッケージプライベートを使います。

一番分かりやすいのは、テストコードを書く時でしょう。

パッケージプライベートであれば、同一パッケージのtestからメソッドを呼び出すことができます。

その必要がなければprivateを使いましょう。

クラスの public の API を注意深く設計した後に、無意識に行うべきことは、他のすべてのメンバーを private にすることです。

まとめ

何も考えずに public にしてしまうのは楽ですが、その分大きな責任が伴います。

基本的には public なクラスのメンバは全て private にしてみて必要なら広げてみるというやり方を取れば、都度アクセスレベルを意識できそうですね。

Javaのアクセスレベル (public, protected, パッケージプライベート, private)

ブログを解説してから1ヶ月以上放置してしまいましたが、ようやく初記事です。 ちゃんと定期的に技術ブログできる人、尊敬です。

今回は Effective Javaを片手にJavaのアクセスレベルについて書こうと思います。

Effective Java は1年ほど前に会社の研修で読んだもので、研修中は訳者の柴田芳樹さんに講師として直々に解説をいただきました。

アクセスレベルについては 第4章 クラスとインターフェースの項目15, 16で触れられています。

4つのアクセスレベル

まずは、トップレベルの(ネストしていない)クラスとインターフェースに関するアクセスレベルと、メンバー(フィールド、メソッド、ネストしたクラス、ネストしたインターフェース)に関するアクセスレベルを見ていきます。

トップレベルのクラス、インターフェース

public パッケージプライベート
同一パッケージ内
全ての場所 -

メンバー

public protected パッケージプライベート private
宣言されたトップレベルのクラス内
同一パッケージ内 -
宣言されたクラスのサブクラス内 - -
全ての場所 - - -

public, protected, privateの修飾子をつけるとそれぞれのアクセスレベルになります。

何も修飾子をつけないとパッケージプライベートになります。(ただし、インターフェースのメンバーはデフォルトでpublicです。)

具体例

実際に試してみましょう。

トップレベルのクラス

トップレベルのクラスからです。

package topclass.access1;

// publicなクラス
public class PublicClass {
    public String str = "Hello, world!";
}
package topclass.access1;

// パッケージプライベートなクラス
class PackagePrivateClass {
    public String str = "Hello, world!";
}

publicなクラスと、パッケージプライベートなクラスを作成しました。

続いて、別のパッケージからこれらのクラスをインスタンス化します。

package topclass.access2;

import topclass.access1.PublicClass;

public class AccessPublicClass {
    public static void main(String[] args) {
        PublicClass c = new PublicClass();
        System.out.println(c.str);
        // Hello, world!
    }
}

publicなクラスはアクセスできました。

package topclass.access2;

import topclass.access1.PackagePrivateClass;

public class AccessPackagePrivateClass {
    public static void main(String[] args) {
        PackagePrivateClass c = new PackagePrivateClass();
        // java: topclass.access1のtopclass.access1.PackagePrivateClassはpublicではありません。パッケージ外からはアクセスできません
    }
}

パッケージプライベートなクラスはコンパイルエラーになります。

アクセスするために、同じパッケージでインスタンス化してみましょう。

package topclass.access1;

public class AccessPackagePrivateClass {
    public static void main(String[] args) {
        PackagePrivateClass c = new PackagePrivateClass();
        System.out.println(c.str);
        // Hello, world!
    }
}

アクセスできました。

クラスメンバー

続いて、クラスのメンバーの場合です。

package member.access1;

public class PublicClass {
    public int publicValue = 1;
    protected int protectedValue = 2;
    int packagePrivateValue = 3;
    private int privateValue = 4;
}

それぞれのアクセスレベルをもつメンバ変数を用意しました。

まずは、同一のパッケージからアクセスします。

package member.access1;

public class AccessPublicClass {
    public static void main(String[] args) {
        PublicClass c = new PublicClass();
        System.out.println(c.publicValue); // 1
        System.out.println(c.protectedValue); // 2
        System.out.println(c.packagePrivateValue); // 3
        // System.out.println(c.privateValue);  コンパイルエラー
        // java: privateValueはmember.access1.PublicClassでprivateアクセスされます
    }
}

別のクラスからは、private変数にはアクセスできません。

次に、別のパッケージからアクセスしてみます。

package member.access2;

import member.access1.PublicClass;

public class AccessPublicClass extends PublicClass {
    public static void main(String[] args) {
        PublicClass c = new PublicClass();
        System.out.println(c.publicValue); // 1
        // System.out.println(c.protectedValue);   コンパイルエラー
        // java: protectedValueはmember.access1.PublicClassでprotectedアクセスされます
        // System.out.println(c.packagePrivateValue); コンパイルエラー
        // java: member.access1.PublicClassのpackagePrivateValueはpublicではありません。パッケージ外からはアクセスできません
    }
}

こうすると、public変数のみアクセス可能になります。

では、別パッケージからprotected変数にアクセスしてみます。

package member.access2;

import member.access1.PublicClass;

public class AccessPublicClass2 extends PublicClass {
    public static void main(String[] args) {
        AccessPublicClass2 c = new AccessPublicClass2();
        System.out.println(c.publicValue); // 1
        System.out.println(c.protectedValue); // 2

        PublicClass c2 = new AccessPublicClass2();
        // System.out.println(c2.protectedValue); コンパイルエラー
        // java: protectedValueはmember.access1.PublicClassでprotectedアクセスされます
    }
}

PublicClassを継承したサブクラスを作成し、そのインスタンスを通じてアクセスできます。

ただし、インスタンス変数の型を親クラスとしてしまうと、アクセスできません。

長くなってしまったので今回はここまでとし、次回にこれらのアクセスレベルの使い分けについて書きたいと思います。

ブログ開設

技術のアウトプットのためにブログを書くことにしました。

サーバーサイドエンジニアとして調べた知識をまとめていくつもりです。 主にJavaやGo言語に関する内容がメインとなる予定です。

どこまで続くか分かりませんが、頑張ります。