コンテンツにスキップ

2017

Node.jsとnpmのインストール

手元の Mac に Node.js と npm がインストールされていなかったのでインストールした手順

homebrew を使って nodebrew をインストール

$ brew install nodebrew
==> Downloading https://github.com/hokaccha/nodebrew/archive/v0.9.2.tar.gz
==> Downloading from https://codeload.github.com/hokaccha/nodebrew/tar.gz/v0.9.2
######################################################################## 100.0%
==> /usr/local/Cellar/nodebrew/0.9.2/bin/nodebrew setup_dirs
==> Caveats
Add path:
  export PATH=$HOME/.nodebrew/current/bin:$PATH

To use Homebrew's directories rather than ~/.nodebrew add to your profile:
  export NODEBREW_ROOT=/usr/local/var/nodebrew

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completion has been installed to:
  /usr/local/share/zsh/site-functions
==> Summary
🍺  /usr/local/Cellar/nodebrew/0.9.2: 7 files, 34.4K, built in 2 seconds

nodebrew がインストールされたことを確認

$ nodebrew -v
nodebrew 0.9.2

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install a <version> (compile from source)
    nodebrew install-binary <version>     Download and install a <version> (binary file)
    nodebrew uninstall <version>          Uninstall a version
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias <key> <version>        Set alias to version
    nodebrew unalias <key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in <version> to current version
    nodebrew exec <version> -- <command>  Execute <command> specified <version>

Example:
    # install from binary
    nodebrew install-binary v0.10.22

    # use a specific version number
    nodebrew use v0.10.22

    # io.js
    nodebrew install-binary io@v1.0.0
    nodebrew use io@v1.0.0

インストールできる Node.js のバージョンを確認

$ nodebrew ls-remote
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE release}/ at /usr/local/bin/nodebrew line 731.
v0.0.1    v0.0.2    v0.0.3    v0.0.4    v0.0.5    v0.0.6    

...

io@v3.3.0 io@v3.3.1 

バージョンを指定してインストール (binary を指定したほうがインストールにかかる時間が短いようだ)

$ nodebrew install-binary v6.11.3
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE platform}/ at /usr/local/bin/nodebrew line 731.
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE release}/ at /usr/local/bin/nodebrew line 731.
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE arch}/ at /usr/local/bin/nodebrew line 731.
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE version}/ at /usr/local/bin/nodebrew line 731.
fetch: http://nodejs.org/dist/v6.11.3/node-v6.11.3-darwin-x64.tar.gz
Warning: Failed to create the file 
Warning: /Users/sojiro/.nodebrew/src/v6.11.3/node-v6.11.3-darwin-x64.tar.gz: 
Warning: No such file or directory

curl: (23) Failed writing body (0 != 792)
download faild: http://nodejs.org/dist/v6.11.3/node-v6.11.3-darwin-x64.tar.gz

自分でインストール先のディレクトリを作らないとダメらしい

$ mkdir -p ~/.nodebrew/src

再度インストール

$ nodebrew install-binary v6.11.3
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE platform}/ at /usr/local/bin/nodebrew line 731.
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE release}/ at /usr/local/bin/nodebrew line 731.
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE version}/ at /usr/local/bin/nodebrew line 731.
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/#{ <-- HERE arch}/ at /usr/local/bin/nodebrew line 731.
fetch: http://nodejs.org/dist/v6.11.3/node-v6.11.3-darwin-x64.tar.gz
######################################################################## 100.0%
Install successful

インストールされたバージョンを確認

$ nodebrew list
v6.11.3

current: none

使うバージョンを設定

$ nodebrew use v6.11.3
use v6.11.3

コマンドにパスを通す

$ echo 'export PATH=$PATH:/Users/sojiro/.nodebrew/current/bin' >> ~/.bashrc
$ source ~/.bashrc 

node 及び npm がインストールされたことを確認

$ node -v
v6.11.3

$ npm -v
3.10.10

値オブジェクトとコレクションオブジェクト

某先輩の書評を読んで気になったので 『現場で役立つシステム設計の原則』 という本を読んだ。

とあるプロジェクトでその先輩に自分の書くコードについて色々ご指摘をいただいていたタイミングであり、自分としてはかなり参考になった点があるので備忘も兼ねて書いてみる。

今回は特に「値オブジェクト」と「コレクションオブジェクト(ファーストクラスコレクション)」について。

値オブジェクト

値オブジェクトはアプリケーションに登場する様々な値に対してその値を扱うための専用クラスを作るという考え方。

値ごとにクラスを定義する

値オブジェクトの考え方をルール化すると、「プリミティブ型や String 型は使わない」という方針となる。

例えば、先述のプロジェクトのコードでは登場する様々な値を String 型で表現していた。

  • userId
  • nickname
  • address
  • etc...

これらはサーバーリクエストの口でバリデーションを通るものの、その後の値がアプリケーションコード内で保証されないため非常に不安定だった。

また、例えば nicknameaddress を引数として受け付けるメソッドがあったとすると

public User hoge(String nickname, String address) {
    ...
}

となるわけだが、この時このメソッドを使う側で間違えて address, nickname の順で引数を指定したとしてもこのメソッドはその引数を受け付けてしまう。

そこで Nickname クラスと Address クラスを定義するというのが値オブジェクトの考え方だ。

class Nickname {
    static final int MIN_LENGTH = 1;
    static final int MAX_LENGTH = 20;

    String nickname;

    Nickname(String nickname) {
        if (nickname.length() < MIN_LENGTH) {
            throw new IllegalArgumentException("nickname length must be at least" + MIN_LENGTH);
        }
        if (nickname.length() > MAX_LENGTH) {
          throw new IllegalArgumentException("nickname length must be under" + MAX_LENGTH);
        }
        this.nickname = nickname;
    }
}

address に対しても同様にクラスを定義して先ほどのメソッドを

public User hoge(Nickname nickname, Address address) {
    ...
}

とすればより堅い実装となる。

値オブジェクトを不変とする

値オブジェクトの値は上書きしないというのも値オブジェクトを扱う上で重要な考え方で、これをルール化すると、「値を変更する度にオブジェクトそのものを新しく(別のオブジェクトに)する」ということになる。

Nickname nickname = new Nickname("sojiro");
nickname.setNickname("shin-sojiro");

ではなく

Nickname nickname = new Nickname("sojiro");
Nickname updatedNickname = nickname.update("shin-sojiro");

とすることで、各オブジェクトがどの値をもっているのかが明確となり、知らないうちに値が変わっている、ということを防ぐことができる。

コレクションオブジェクト(ファーストクラスコレクション)

値オブジェクトの考え方を発展させ、コレクション型のデータとロジックを独自のクラスに切り出す考え方をコレクションオブジェクト、もしくはファーストクラスコレクションという。

List<User> users = new ArrayList<>();
users.add(user);

のように扱っていた User のコレクションを、 Users というクラスを定義し、そのクラスで操作が完結するようにする。

class Users {
    List<User> users;

    Users(List<User> users) {
        this.users = users;
    }

    Users add(User user) {
        List<User> userList = new ArrayList<>(users);
        return new Users(userList.add(user));
    }

    List<User> asList() {
        return Collections.unmodifiableList(users);
    }
}

ここでのポイントは、

  • 値オブジェクトで値に変更がある場合は常に新しいオブジェクトを返したように、コレクションオブジェクトでもコレクションに変更がある場合は新たなオブジェクトを返すようにすること
  • コレクションへの参照を返す場合はなるべく変更できない状態にして返すこと

コレクションへの操作を独自クラスに閉じ込め、同一オブジェクトが持つコレクションの内容が変化することのないようにすることでより堅い実装とすることができる。

感想

Perl からプログラミングを始めたこともあってか(言い訳)、オブジェクト指向と言っても型への意識は低く、如何にコンパイル時点でバグを見つけられる堅い実装とするか、その変数に今どのような値が入っているかをコードの書き方で明確にするか、というような意識も低かった。

現場で役立つシステム設計の原則 からは他にも学んだ点が多いので追って記事にできたらと思う。

参照

現場で役立つシステム設計の原則

WordPress on GAE

GCP の事前準備

以下の準備が事前に必要。ここでは割愛。

  • GCP アカウントの作成
  • GCP プロジェクトの作成
  • 課金の有効化
  • GCS(Google Cloud Storage)に default bucket を作成
    • default bucket: YOUR_PROJECT_NAME.appspot.com
  • Google Cloud SDK のインストール
    • https://cloud.google.com/sdk/

Composer のインストール

Composer は PHP のパッケージ管理ツール

Composer is strongly inspired by node's npm and ruby's bundler.

node の npm、ruby の bundler に強く影響を受けているようだ。

手順

PHP がインストールされていることを確認

$ php -v
PHP 5.5.38 (cli) (built: Aug 21 2016 21:48:49) 

こちら の手順通り Composer をインストール

$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php -r "if (hash_file('SHA384', 'composer-setup.php') === '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
$ php composer-setup.php
$ php -r "unlink('composer-setup.php');"

グローバルに呼び出せるようにしておく

$ mv composer.phar /usr/local/bin/composer

GCS へのアクセス権限変更

作成したプロジェクトでログイン

$ gcloud auth login
$ gcloud config set project YOUR_PROJECT_NAME

GCS のアクセス権更新

$ gsutil defacl ch -u AllUsers:R gs://YOUR_PROJECT_NAME.appspot.com

DB の setup

Cloud SQL を使うので GCP の SQL メニューへ

  • wp という名前で MySQL のインスタンスを立てる
    • このとき第2世代を選択可
    • 日本向けがメインであればリージョンは asia-northeast1
    • マシンタイプは安さ重視なら db-f1-micro とする (このタイプで 30円/日 という印象)
    • 他の設定はお好みで...
  • 立てたインスタンスに wp という名前のデータベースを作る
  • wp というユーザーを作る
    • アクセス制御 → ユーザー → ユーザーアカウントを作成、とたどる

WordPress の setup

ローカルに WordPress 構築に必要な素材を用意する

$ git clone https://github.com/GoogleCloudPlatform/php-docs-samples.git
$ cd php-docs-samples/appengine/wordpress/
$ composer install

setup の開始

$ php wordpress-helper.php setup
# ここでいろいろインタラクティブに聞かれるので答える

※ リージョンを聞かれるが、 asia-northeast1 がない場合は適当に答えておく

DB_HOST 設定を変更

上記手順で WordPress setup 時に適切なリージョンが指定できなかった場合には自分で一部設定を変更する

cd YOUR_PROJECT_NAME/
vim wordpress/wp-config.php

/** Production login info */ 以下の DB_HOST を適切なリージョンを用いて変更する

Deploy

GAE インスタンスに WordPress を deploy する

$ gcloud app deploy --promote --stop-previous-version app.yaml cron.yaml

最初はかなり多くのファイルを GCS にあげるのでそこそこ時間がかかる

参照

https://github.com/GoogleCloudPlatform/php-docs-samples/tree/master/appengine/wordpress

Unix timeの扱い

Unix time を日付及び時刻に変換したいこと、日付及び時刻を Unix time に変換したいことがある。

Unix time からの変換

$ date -r 616388399
1989年 7月14日 金曜日 11時59分59秒 JST

日付からの変換

$ date -jf '%Y-%m-%d %H:%M:%S' '1989-07-14 11:59:59' +%s
616388399

Could not open the requested socket エラーが出たら

Android Studio で GAE に乗せるアプリを開発しているとき、ローカルで立ち上げようとすると以下のようなエラーが出た

Could not open the requested socket: Address already in use
Try overriding --address and/or --port.

どうやら以前立ち上げた際のプロセスが生き残っていて邪魔しているらしい

以下のように対応した

Android Studio のメニューから RunEdit Configurations... とたどり、対象 module が使っている port を確認

ポートを指定してプロセスを確認、そして kill

$ lsof -i:8080
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
java    74792  sojiro   48u  IPv6 0xXXXXXXXXXXXXXXXX      0t0  TCP *:http-alt (LISTEN)
$ kill -9 74792