2015 年ふりかえり

今年は家族が増えて、 予想通りというか、予想以上にプライベートで使える時間が激減した。 家でネットやテレビはほとんど見ていない。 情報収集はもっぱら隙間時間にスマホFeedly

技術書は持ち運びを優先して、電子書籍のみ買うようになった。 Gihyo Digital Publishing で WEB+DB PRESS は発売日に買えるし、 WEB+DB PRESS Plus や Software Design Plus の本も買える。 オライリーオーム社にも電子書籍ストアがあるし、 達人出版会Kindle だってある。 良い時代になってきた。でもまだまだ。 他の出版社も追随してほしい。

インプットはなんとか以前と同じ量をキープしているけど、 問題はアウトプット。 ブログを更新するのが精一杯。 プロダクトは1つもリリースできなかった。 こうもコードを書く時間が無くなるとはね。 新しい MacBook Pro 欲しいけど、 「買ったところで使う時間無いだろうな」って、 あきらめる程度には時間無い。 むしろ、空き時間でコード書けるように、 持ち運びを優先して MacBookSurface とかにするべきだろうか。 なにか手を打ちたいところ。

今年は採点不能だな。

天神炒飯

赤坂駅すぐ側、以前ボン田中があった店舗が天神炒飯ていう店に変わってた。文字通り炒飯専門店。

炒飯は塩味の和風、醤油味の中華風、あと高菜があったかな。炒飯に麻婆豆腐あんを組み合わせるのが人気みたいだけど、ご飯にあんかけは好みじゃないので、中華風炒飯の大盛りにしてみた。

醤油味の濃さがちょうどいい。大盛りでも容易く完食した。具に刻んだチャーシューは入ってたんかな。ちょっと具のボリュームは足りなかった。ボリュームが欲しい人はトッピングしろってことなのかもね。

関連ランキング:中華料理 | 赤坂駅天神駅西鉄福岡駅(天神)

博多担々麺とり田

中州に本店がある水炊きの人気店『とり田』が担々麺専門店を出していたので行ってみた。 場所は美野島。博多駅から1kmくらい歩いた。結構遠い。

f:id:griefworker:20151226181309j:plain

担々麺は辛さが選べるので、スタンダードの3辛にしてみた。 真っ赤で凄く辛そうだけど、見た目ほどではなかったかな。 麺はツルツルのちぢれ麺で、スープの絡みがよかった。 スープはさすがとり田という旨さで、全部飲み干してしまった。

f:id:griefworker:20151226175803j:plain

せっかくなので唐揚げも注文。 鶏モモと鶏むねが1つずつ。 揚げたてジューシーで美味かった。

f:id:griefworker:20151226175444j:plain

関連ランキング:担々麺 | 博多駅

S.B.C

用事で七隈に行ったので、 ついでに七隈周辺のケーキ屋でケーキを買って帰ることにした。 寄ったのは、七隈駅から徒歩 10 分ほどにある『S.B.C』。 ケーキ屋っぽくない名前。 『Sweet Baker's Crew』の略らしい。

生クリームが美味しいとか、チョコレートケーキが美味しいとか、プリンが美味しいとか、 事前に情報を仕入れていたので、3種類買ってみた。

ショートケーキとチョコレートケーキは、どちらもクリームが軽い口当たりで上品な甘さだった。 プリンはカラメル含め3層になっていて、非常になめらか。 ケーキはもちろん美味かったけど、それ以上にプリンが美味かったな。

関連ランキング:ケーキ | 七隈駅金山駅梅林駅

『実践ドメイン駆動設計』読んだ

ようやく読了。 長かった。 少しずつ読み進めて、読み終わるまで3週間かかった。

読み終わって思ったのは、DDD をまったく実践できていなかったな、 という反省。 DDD がどんなものかは分かっていたつもりだったけど、 エンティティやリポジトリ、レイヤー化アーキテクチャといった、 実装よりのものにばかり目がいってしまっていた。 境界づけられたコンテキストと、 その中で定義するユビキタス言語の力を甘く見ていた。 それに集約についての理解もまだまだだった

その結果出来上がっているのは、 ほとんど DAO といっていいリポジトリや、 欠乏症のエンティティや、 あって無いような集約。 目も当てられない。

本書は、架空の企業 SaaSOvation が DDD を実践して製品を開発する、 という形で DDD を解説している。 製品開発の過程で、間違いをおかしたり、問題に直面したりするけど、 それを DDD の手法で解決していく、という分かりやすい流れ。 DDD が何なのかは知っていても、どう実践すればいいのかが分かっていない、 自分のような開発者の手本になる。 事実、自分のプロジェクトの集約やリポジトリのマズさにも気付いたし。

文章が読みやすかったおかげで、ボリュームのわりにスラスラ読めた。 DDD 始めるなら必読といっていい本だと思う。

実践ドメイン駆動設計

実践ドメイン駆動設計

TypeScript でも React Meets Redux

React を使って TODO サンプルを TypeScript で書き直したので、 今度はさらに Redux も使ってみる。

まずは Redux の型定義ファイルをインストール。

tsd query redux --action install --resolve
tsd query react-redux --action install --resolve

特にひっかかることなく、スンナリと実装できた。

/// <reference path="typings/tsd.d.ts" />
import * as React from "react"
import * as ReactDOM from "react-dom"
import { createStore, combineReducers, Dispatch } from "redux"
import { Provider, connect } from "react-redux"
import * as _ from "lodash"

// タスクのインタフェース
interface ITask {
    name?: string;
    completed?: boolean;
    editing?: boolean;
}

// タスクを追加するときに呼び出すコールバックのインタフェース
interface ITaskAddCallback {
    (task: ITask): void;
}

// タスクを保存するときに呼び出すコールバックのインタフェース
interface ITaskSaveCallback {
    (task: ITask, newValue: ITask): void;
}

// タスクを削除するときに呼び出すコールバックのインタフェース
interface ITaskDeleteCallback {
    (task: ITask): void;
}

// アクションの種類を定義
enum TaskActionType {
    Add = 1,
    Save,
    Delete
}

// タスクのアクションを表すインタフェース
interface ITaskAction {
    type: TaskActionType;
    task?: ITask;
    values?: ITask;
}

// タスクを追加するアクションを作成する
function addTask(task: ITask): ITaskAction {
    return {
        type: TaskActionType.Add,
        task: task
    };
}

// タスクを保存するアクションを作成する
function saveTask(task: ITask, newValues: ITask): ITaskAction {
    return {
        type: TaskActionType.Save,
        task: task,
        values: newValues
    };
}

// タスクを削除するアクションを作成する
function deleteTask(task: ITask): ITaskAction {
    return {
        type: TaskActionType.Delete,
        task: task
    };
}

// タスクを操作する Reducer
function tasksReducers(state: ITask[], action: ITaskAction): ITask[] {
    state = state || [
        { name: "foo", completed: false, editing: false },
        { name: "bar", completed: false, editing: false },
        { name: "hoge", completed: false, editing: false },
        { name: "fuga", completed: false, editing: false }
    ];
    switch (action.type) {
        case TaskActionType.Add:
            return state.concat(action.task);
        case TaskActionType.Save:
            return state.map(t => {
                if (t == action.task) {
                    return _.assign({}, t, action.values);
                } else {
                    return t;
                }
            });
        case TaskActionType.Delete:
            return state.filter(t => t != action.task);
        default:
            return state;
    }
}

// タスクを表示するコンポーネントの props のインタフェース
interface ITaskProps {
    task: ITask;
    onTaskSave: ITaskSaveCallback;
    onTaskDelete: ITaskDeleteCallback;
}

// タスクを表示するコンポーネント
class Task extends React.Component<ITaskProps, any> {
    private handleEdit = (e) => {
        this.props.onTaskSave(this.props.task, {
            editing: true
        });
    }

    private handleDelete = (e) => {
        this.props.onTaskDelete(this.props.task);
    }

    private handleChangeCheckbox = (e) => {
        this.props.onTaskSave(this.props.task, {
            completed: !this.props.task.completed
        });
    }

    render(): JSX.Element {
        return (
            <div>
        <input type="checkbox"
            checked={this.props.task.completed}
            onChange={this.handleChangeCheckbox} />
        <span>{this.props.task.name}</span>
        <a href="#" onClick={this.handleEdit}>[edit]</a>
        <a href="#" onClick={this.handleDelete}>[del]</a>
                </div>
        );
    }
}

// タスクを編集するコンポーネントの props のインタフェース
interface IEditTaskFormProps {
    task: ITask;
    onTaskSave: ITaskSaveCallback;
}

// タスクを編集するコンポーネントの state のインタフェース
interface IEditTaskFormState {
    name: string;
}

// タスクをその場編集するためのコンポーネント
class EditTaskForm extends React.Component<IEditTaskFormProps, IEditTaskFormState> {
    constructor(props: IEditTaskFormProps) {
        super(props);
        this.state = { name: "" };
    }

    private handleCancel = (e) => {
        this.props.onTaskSave(this.props.task, {
            editing: false
        });
    }

    private handleSave = (e) => {
        e.preventDefault();

        if (!this.state.name) {
            return;
        }

        this.props.onTaskSave(this.props.task, {
            name: this.state.name,
            editing: false
        });

        this.setState({ name: "" });
    }

    private handleChangeText = (e) => {
        this.setState({ name: e.target.value });
    }

    render(): JSX.Element {
        return (
            <div>
        <input type="text"
            value={this.state.name || this.props.task.name}
            onChange={this.handleChangeText}
            ref="name"/>
        <button onClick={this.handleSave}>save</button>
        <button onClick={this.handleCancel}>cancel</button>
                </div>
        );
    }
}

// タスク一覧を表示するコンポーネントの props のインタフェース
interface ITaskListProps {
    tasks: ITask[];
    onTaskSave: ITaskSaveCallback;
    onTaskDelete: ITaskDeleteCallback;
}

// タスク一覧を表示するためのコンポーネント
class TaskList extends React.Component<ITaskListProps, any> {
    render(): JSX.Element {
        var rows = this.props.tasks.map(t => {
            if (t.editing) {
                return (
                    <EditTaskForm task={t}
                        onTaskSave={this.props.onTaskSave} />
                );
            } else {
                return (
                    <Task task={t}
                        onTaskSave={this.props.onTaskSave}
                        onTaskDelete={this.props.onTaskDelete} />
                );
            }
        });

        return (
            <div>
        {rows}
                </div>
        );
    }
}

// タスク追加フォームの props のインタフェース
interface IAddTaskFormProps {
    onTaskAdd: ITaskAddCallback;
}

// タスク登録フォームを表示するためのコンポーネント
class AddTaskForm extends React.Component<IAddTaskFormProps, any> {
    private handleAdd = (e) => {
        e.preventDefault();

        var newTask = ReactDOM.findDOMNode<HTMLInputElement>(this.refs["name"]).value.trim();
        if (!newTask) {
            return;
        }

        this.props.onTaskAdd({
            name: newTask,
            completed: false,
            editing: false
        });

        ReactDOM.findDOMNode<HTMLInputElement>(this.refs["name"]).value = "";
    }

    render() {
        return (
            <div>
        <input type="text" placeholder="newTask" ref="name"/>
        <button onClick={this.handleAdd}>add</button>
                </div>
        );
    }
}

// ルートコンポーネントの state のインタフェース
interface ITaskAppProps {
    tasks?: ITask[];
    dispatch?: Dispatch;
}

// ルートコンポーネント
// 状態をまとめて管理する
class _TaskApp extends React.Component<ITaskAppProps, any> {
    private handleTaskAdd = (newTask: ITask): void => {
        this.props.dispatch(addTask(newTask));
    }

    private handleTaskDelete = (task: ITask): void => {
        this.props.dispatch(deleteTask(task));
    }

    private handleTaskSave = (task: ITask, newValues: ITask): void => {
        this.props.dispatch(saveTask(task, newValues));
    }

    render(): JSX.Element {
        return (
            <div>
        <AddTaskForm onTaskAdd={this.handleTaskAdd} />
        <TaskList tasks={this.props.tasks}
            onTaskSave={this.handleTaskSave}
            onTaskDelete={this.handleTaskDelete} />
                </div>
        );
    }
}
const TaskApp = connect(state => {
    return {
        tasks: state.tasks
    };
})(_TaskApp);

// ストアに渡す Reducer を作成
const rootReducer = combineReducers({
    tasks: tasksReducers
});

// 唯一のストアを作成
const store = createStore(rootReducer);

ReactDOM.render(
    <Provider store={store}>
        <TaskApp />
    </Provider>,
    document.getElementById("content")
);

コンテナコンポーネントが持っていた状態を Redux のストアに持つようにした。 それにともない、コンテナコンポーネントは状態を操作するのではなく、アクションを呼び出すだけに変更。 Redux の枠組みにのっとって、状態は Reducer が操作している。

あと、アクションの種類に enum を使ってみた。 もし複数の enum で定義する場合は、明示的に開始値を指定して値の範囲を分けておかないと、 複数の Reducer で処理されてしまいそう。 素直に文字列で定義するか、1 つの enum にアクションの種類をすべて詰め込んでいいかもな。

TypeScript で React

今まで書いたサンプル程度の規模でも、 実行時に踏むまでバグに気付かないことが結構あって、 実際踏むと萎える。 React で規模の大きい SPA 作っていたらと思うとゾッとする。 コンパイル時に分かれば良いのに。

そうだ。 TypeScript で書いてみよう。 JavaScript のスーパーセットというアプローチが、 AltJS の中でも筋が良いと思っていたんだよな。

github.com

TypeScript と、型定義ファイルを管理するための tsd をインストール。

npm install -g typescript
npm install -g tsd

React と React-DOM、 その他使っているライブラリの型定義ファイルをインストールする。

cd src
tsd query react --action install --resolve
tsd query react-dom --action install --resolve
tsd query lodash --action install --resolve

TypeScript コードを JavaScriptコンパイルするには、tsc を使う。 毎回オプションを指定するのは面倒なので、 tsconfig.json を書いておく。

{
  "compilerOptions": {
    "module": "commonjs",
    "jsx": "react",
    "rootDir": "src",
    "outDir": "built"
  },
  "exclude": [
    "node_modules"
  ]
}

これで tsc を実行するだけでコンパイルできる。 ただ、tscJavaScriptコンパイルできるけど、 これをブラウザで実行できるようにしないといけない。 TypeScript になっても Browserify のお世話になるみたいだ。 package.json の scripts に書いている build コマンドを修正。

"scripts": {
  "build": "tsc && browserify built/app.js --outfile dist/bundle.js"
},

これでようやく準備が終わった。 試しに、TODO アプリのサンプルを TypeScript で書き換えてみた。

/// <reference path="typings/tsd.d.ts" />
import * as React from "react"
import * as ReactDOM from "react-dom"
import * as _ from "lodash"

// タスクのインタフェース
interface ITask {
  name?: string;
  completed?: boolean;
  editing?: boolean;
}

// タスクを保存するときに呼び出すコールバックのインタフェース
interface ITaskSaveCallback {
  (task: ITask, newValue: ITask) : void;
}

// タスクを削除するときに呼び出すコールバックのインタフェース
interface ITaskDeleteCallback {
  (task: ITask) : void;
}

// タスクを表示するコンポーネントの props のインタフェース
interface ITaskProps {
  task: ITask;
  onTaskSave: ITaskSaveCallback;
  onTaskDelete: ITaskDeleteCallback;
}

// タスクを表示するコンポーネント
class Task extends React.Component<ITaskProps, any> {
  private handleEdit = (e) => {
    this.props.onTaskSave(this.props.task, {
      editing: true
    });
  }

  private handleDelete = (e) => {
    this.props.onTaskDelete(this.props.task);
  }

  private handleChangeCheckbox = (e) => {
    this.props.onTaskSave(this.props.task, {
      completed: !this.props.task.completed
    });
  }

  render(): JSX.Element {
    return (
      <div>
        <input type="checkbox"
               checked={this.props.task.completed}
               onChange={this.handleChangeCheckbox} />
        <span>{this.props.task.name}</span>
        <a href="#" onClick={this.handleEdit}>[edit]</a>
        <a href="#" onClick={this.handleDelete}>[del]</a>
      </div>
    );
  }
}

// タスクを編集するコンポーネントの props のインタフェース
interface IEditTaskFormProps {
  task: ITask;
  onTaskSave: ITaskSaveCallback;
}

// タスクを編集するコンポーネントの state のインタフェース
interface IEditTaskFormState {
  name: string;
}

// タスクをその場編集するためのコンポーネント
class EditTaskForm extends React.Component<IEditTaskFormProps, IEditTaskFormState> {
  constructor(props: IEditTaskFormProps) {
    super(props);
    this.state = { name: "" };
  }

  private handleCancel = (e) => {
    this.props.onTaskSave(this.props.task, {
      editing: false
    });
  }

  private handleSave = (e) => {
    e.preventDefault();

    if (!this.state.name) {
      return;
    }

    this.props.onTaskSave(this.props.task, {
      name: this.state.name,
      editing: false
    });

    this.setState({ name: "" });
  }

  private handleChangeText = (e) => {
    this.setState({ name: e.target.value });
  }

  render(): JSX.Element {
    return (
      <div>
        <input type="text"
               value={this.state.name || this.props.task.name}
               onChange={this.handleChangeText}
               ref="name"/>
        <button onClick={this.handleSave}>save</button>
        <button onClick={this.handleCancel}>cancel</button>
      </div>
    );
  }
}

// タスク一覧を表示するコンポーネントの props のインタフェース
interface ITaskListProps {
  tasks: ITask[];
  onTaskSave: ITaskSaveCallback;
  onTaskDelete: ITaskDeleteCallback;
}

// タスク一覧を表示するためのコンポーネント
class TaskList extends React.Component<ITaskListProps, any> {
  render(): JSX.Element {
    var rows = this.props.tasks.map(t => {
      if (t.editing) {
        return (
          <EditTaskForm task={t}
                        onTaskSave={this.props.onTaskSave} />
        );
      } else {
        return (
          <Task task={t}
                onTaskSave={this.props.onTaskSave}
                onTaskDelete={this.props.onTaskDelete} />
        );
      }
    });

    return (
      <div>
        {rows}
      </div>
    );
  }
}

// タスクを追加するときに呼び出すコールバックのインタフェース
interface ITaskAddCallback {
  (task: ITask): void;
}

// タスク追加フォームの props のインタフェース
interface IAddTaskFormProps {
  onTaskAdd: ITaskAddCallback;
}

// タスク登録フォームを表示するためのコンポーネント
class AddTaskForm extends React.Component<IAddTaskFormProps, any> {
  private handleAdd = (e) => {
    e.preventDefault();

    var newTask = ReactDOM.findDOMNode<HTMLInputElement>(this.refs["name"]).value.trim();
    if (!newTask) {
      return;
    }

    this.props.onTaskAdd({ name: newTask });

    ReactDOM.findDOMNode<HTMLInputElement>(this.refs["name"]).value = "";
  }

  render() {
    return (
      <div>
        <input type="text" placeholder="newTask" ref="name"/>
        <button onClick={this.handleAdd}>add</button>
      </div>
    );
  }
}

// ルートコンポーネントの state のインタフェース
interface ITaskAppState {
  tasks: ITask[];
}

// ルートコンポーネント
// 状態をまとめて管理する
class TaskApp extends React.Component<any, ITaskAppState> {
  constructor(props: any) {
    super(props);

    this.state = {
      tasks: [
        { name: "foo", completed: false, editing: false },
        { name: "bar", completed: false, editing: false },
        { name: "hoge", completed: false, editing: false },
        { name: "fuga", completed: false, editing: false }
      ]
    };
  }

  private handleTaskAdd = (newTask: ITask): void => {
    var newTasks = this.state.tasks.concat(newTask);
    this.setState({ tasks: newTasks });
  }

  private handleTaskDelete = (task: ITask): void => {
    var newTasks = this.state.tasks.filter(t => {
      return t !== task;
    });
    this.setState({ tasks: newTasks });
  }

  private handleTaskSave = (task: ITask, newValues: ITask): void => {
    var newTasks = this.state.tasks.map(t => {
      if (t === task) {
        return _.extend(task, newValues);
      } else {
        return t;
      }
    });
    this.setState({ tasks: newTasks });
  }

  render(): JSX.Element {
    return (
      <div>
        <AddTaskForm onTaskAdd={this.handleTaskAdd} />
        <TaskList tasks={this.state.tasks}
                  onTaskSave={this.handleTaskSave}
                  onTaskDelete={this.handleTaskDelete} />
      </div>
    );
  }
}

ReactDOM.render(
  <TaskApp />,
  document.getElementById("content")
);

イベントハンドラにメソッドを渡すと、 this が window になってしまっていてハマった。 this を束縛するためにアローファンクション使って定義して回避。

refs は型定義ファイルのせいで、 refs.name みたいにアクセスできない。 refs["name"] じゃないといけなかった。 こればかりは仕方ない。 props や state みたいにインタフェースを指定できれば良いんだけど。

それ以外はすんなり。 コンパイルnpm run build で一発。 実行してエラー踏んで気付いてたようなミス(主に typo だけど)が、 コンパイルの時点で気付けて精神衛生上良かった。

ちなみに Visual Studio 2015 で書いたんだけど、間違ったコードは即時にエラーが表示されたり、 インテリセンスが効いたりして、 すこぶる良かった。