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

作業手順書を書くときに気をつけたいこと

最近社内で手順書をレビューしてて気になったことがあったので、書いておく。

作業の目的を明確にする

まず、なんのための作業なのか明確にしておく必要がある。例えば、

  • 管理者を追加したい
  • データをクリーニングしたい
  • リリース作業したい

など作業の目的は様々であるが、それがはっきりしないことには、そもそも手順が正しいかどうか判断できない。

「今度削除するデータをお客様の要望により念のためバックアップしたいです。」
「はい、でも対象になるデータは、n週間前のデータなので週次バッチでバックアップが残っていると思います。そもそもこの作業は不要で、削除だけの手順で十分だと思いますがどうでしょうか?」
「確かにそうですね」

みたいな不毛なことがないようにしたい。

日本語の表現を曖昧にしない

例えば履歴のデータをクリーニングするとする。

#先月までのデータを削除する
DELETE FROM hoge_table WHERE created_at < ‘2017-02-01’;

「ちょっと待って、先月までって、1月までってことで1月は含まないんじゃないでしょうか???いや私が勘違いしているかもしれないし、「まで」ってのは含むの含まないの?」

日本語は難しい。口語でサラッと書いてしまうと境界条件が曖昧になってしまう。 SQL見れば自明だろ!って思うかもしれないけど、日本語とSQLとどっちが正しいか確信が持てない。

スコープを揃える

スコープと言う表現が適切ではないかもしれないが。

先の例でいうと、履歴データは年月日のデータを持っているので、「先月まで」「半年」みたいな月や年単位の表現ではなく、をベースに範囲とかを記述すべきだと思う。

#2017-02-01未満のデータを削除する
DELETE FROM hoge_table WHERE created_at < ‘2017-02-01’;

と書いてもらえると、SQLと日本語で違いがない。日本語の表現としては少し変な感じがするかもしれない。でも、境界条件はハッキリしたい。

時刻は固定された表現にする

ついでに書くと「先月」という表現は作業をする時点で変わってしまう。2月だと1月だし、6月だと5月になってしまう。

作業をする時点での、明確な時刻を指定した方が揺るぎがないので、極力時刻を固定した方が良いと思う。

作業が延期されたりすることもあるが、その際にはその時の日時で手順書を更新すべきだと思う。

後から振り返ったときに、いつやった作業なのか明確になっている方が良い。 「2017-02-01データクリーニング手順書.txt」となっていても、実際には3月に作業したみたいなこともあって、そういう時にファイル名も更新すべきだし、手順書の内容もちゃんと日時を合わせるべき。

根拠を明確にする

手順書に落とし込む必要はないかもしれないが、作業の根拠は明確でないといけない。

「データクリーニングの対象レコードが1億件あるので、メモリが足りない可能性があります。」
「メモリが足りないという根拠はなんですか?」
「前回2千万件のレコード削除の際にはメモリが枯渇していました。」
「(トレンドのグラフを見つつ)前回の作業の時には明確なメモリ使用量の変化はないようですが…」
「COPYを使ったので…(モニョモニョ…)」
「逆に同じレコード件数で、別の作業の時にpg_dumpを使用していますが、コレはメモリには影響なかったんでしょうか?」
「それは…」

例えばレコード数が同じだった時にCOPYはダメでpg_dumpは大丈夫という根拠が明確でないといけない。

同じ操作ではないのでもちろんリソースの使い方も違うと思うし、一方が良くて一方が良くないという理由はちゃんと説明できないといけない。

そもそもどちらの作業も考慮が足りてなかったんじゃないか?と思ってしまう。たまたま影響がなかっただけで。

実際、何が影響して障害になったりするかわからない部分も正直あるんだけど、少なくとも自分がやる作業については、どういう作業でどういう影響があって、どういうリスクを伴うかというのは根拠を持ってないといけない。

根拠が間違っているかもしれないんだけど、そこが揺らぐと何もできないし、自分で説明できるレベルで根拠はしっかりしておいてほしい。

他にもあった気がするけど、とりあえず終わり。

phaserを使ってクソゲー作った

この前、社内のLT交流会があったので、その発表用にちょっとしたゲームを作ってみた。

iPhoneとパソコンで連携するウェブゲーム

プレーヤーはiPhoneライトセーバーのようなものとして扱う。 iPhoneを左右に振ると、パソコン上のライトセーバーのようなものが同期して左右に揺れる。 パソコン上では次々と敵と思わしきものが上から落ちてくるので、ライトセーバーのようなものを接触させて、消滅させる。

そんなゲーム。面白さはあまりない。

動機

最初はforceというsalesforceのデータを扱えるCLIのツールについて紹介しようと思っていた。
GitHub - heroku/force: A command-line interface to force.com

でも途中でたまにはいつもと違うことをやってみようと思い、ゲームを作ってみることにした。

phaserとは

Phaser - A fast, fun and free open source HTML5 game framework

ゲームエンジンであってるかな。ブラウザでゲームを作るためのライブラリで、色々とできるっぽい。

  • ゲーム内のオブジェクト管理
  • 音声
  • 画像
  • 当たり判定
  • イベント管理

本格的なゲームを作った経験はないが、flashとかで簡単なものは作ったことがあって、思いの外苦労はしなかった。

それに、オブジェクトが当たった時の跳ね返りとか勝手にやってくれるので、かなりラクができた。

Typescriptの定義ファイルもあったので、コーディングもあまり苦労しなかった。

iPhoneとパソコンの同期

websocketを使った。サーバはGoで構築した。

  • iPhoneからの接続とパソコンからの接続を管理
  • iPhoneからのデータをパソコンに転送する

というやつを作った。

iPhoneでは、

  • 端末の角度を取得、計算して送信
  • 加速度を取得して、音量調整

を行なった。ライトセーバーっぽさを演出するために、強く振るとブーンの音が大きくなるようにした(がLTの時には聞こえなかったっぽい)。

パソコンでは、送信されてきた角度に合わせて、画面上のライトセーバーを傾けるようにした。

ゲームとしてのクオリティ

もともとすごいゲームを作ろうってわけでもなかったので、完成度の低さは気にしていないが、

素材が大事

ということが勉強になった。

発表の3週間くらい前からやり始めたが、websocketで通信し合うとか、ゲーム内の当たり判定とか、そういうのはライブラリなどのおかげでほとんど苦労はしなかった。

画像や音声の素材探しと調整のための加工が大変だった。正解が良く分からないし、無限に時間が吸われていく感じがした。

何でも作ると面白い

専門で仕事にしようとはあまり思わないが、自分でゲームを作るのも面白かった。

iPhoneとパソコンの画面が繋がった時にはやっぱり「おぉーー!すごいすごい!」ってなるし、こういう風に動くかな〜と思って実装したものが期待通りに動く楽しさや嬉しさがある。

良い経験になった。

goaのVSCode用のスニペット書いてる

goaで設計するとして、ResourceやMediaTypeとかを地道に書くのは正直めんどくさい。

なので、少しでもラクするためにコードスニペットを書いている。まだ書き始め。

vscode goa snippets · GitHub

ざっくりとしたテンプレート的な部分と、細かく調整が必要そうなParamとか、必要に応じて書き足して行こうと思ってる。

ある程度まとまったら、MarketPlaceに公開したい。

Angular入門 6日目

社内ツールをバックエンドをGo(goa)で、フロントエンドをAngularで作ることにした。

6日目。 今回も少しだけ作業した。

やったこと

通知周りを表示

bulmaの通知用のCSSがあるので、それを使った部分をComponentとして独立させてみた。

通知用と本文のComponent間でのやりとりが必要になったので、その辺を調べていたが、どうやらServiceを共有するのが良いらしい。

何でもかんでもServiceだなぁと思った。ModelをラップしただけのServiceとか…。 Fat Controler → Fat Model → Fat Service という時代の流れなのかな?

Angular入門 5日目

社内ツールをバックエンドをGo(goa)で、フロントエンドをAngularで作ることにした。

5日目。 ちょっと忙しくてあまり時間が取れなかった。

金曜日にやったこと

バリデーション

inputタグにpatternという属性を指定すると良いみたい。ngModelでバインディングしておく必要があるかもしれない。

<input type="text" id="code"
    name="code" [(ngModel)]="project.code"
    #code="ngModel" pattern="PJ-[0-9]{6}">

こんな感じで正規表現が使える。

バリデーションのメッセージ

invalidな時にエラーメッセージを出すには以下のようにする。

<div [hidden]="code.valid">
    <span class="help is-danger">
        codeはPJ-で始まる6桁の数字です
    </span>
</div>

hiddenはCSSdisplay: none を付けるようなので、他のCSSのスタイルと競合しないように注意する必要がある。

もしかしたら ngIf を使った方が良いかもしれない。

フォームの見た目

inputタグなどを赤くしたいような時には、規定のCSSクラスを定義する。

例えはinvalidな時には、 ng-invalid というクラスが付与されるので、form.cssなどを作って置いてそこに必要なスタイルを書いてHTMLから参照する。

.ng-invalid {
    border-color: #F00;
}

app.component.css に書けば良いのでは?と思ってたけど、違った。cssはcomponentごとに独立しているらしい。

Angular入門 4日目

社内ツールをバックエンドをGo(goa)で、フロントエンドをAngularで作ることにした。

4日目。

今日やったこと

ルーティング

そもそもルーティングの設計がおかしかったのと、謎の思い込みがあって、関係ないところで時間をつぶしていた。

// 抜粋
const appRoutes: Routes = [
  {
    path: 'projects',
    component: ProjectsComponent
  },
  {
    path: 'projects/new',
    component: ProjectDetailComponent
  },
  {
    path: 'projects/:id',
    component: ProjectDetailComponent
  },
];

この状態で /projects/new にアクセスすると、route parameter(と言うらしい)のidはnewになる。当たり前なんだけど、整数値の時だけ /projects/:id に振り分けられるという思い込みをしていた。

それに、Serviceでデータを取得する箇所でエラーが出ていたのに、ルーティングでのエラーだと勘違いしていた。

新規用ページの作成

編集ページを流用して、idが取れないときには空のオブジェクトを作ってComponentで使うようにした。

ようやく、一覧、新規、編集、削除とできた。

rxjs

Observable について深く気にしてなかったけど、rxjsと関係があるらしい。

使いこなせると、多分このコードも上手く直せるんだろうな。

  getProject() {
    let id = +this.route.snapshot.params['id'];

    if (id > 0) {
      this.projectService.getProject(id)
      .subscribe(project => this.project = project as Project);
    } else {
      this.project = new Project();
    }
  }

雑感

細かいところで色々わかってないところはあるものの、CRUDを実装したので、ダミーデータで一応動くようになった。

あとは、バリデーションとそれのメッセージ通知辺りかな。

Angular入門 3日目

社内ツールをバックエンドをGo(goa)で、フロントエンドをAngularで作ることにした。

3日目。

今日やったこと

Observerableの対応

PromiseからObserverableに変えたら上手くいってなかった件、単純にデータの取得が間違っていただけだった。

  getProjects(): Observable<Project[]> {
    return this.http.get(this.projectURL)
      .map(res => res.json().data as Project[]);
  }

res.json().datadata が抜けていた。

チュートリアルの後半にsearch関係のがあって、関係ないと思って読んでなかったけど、ちゃんと書いてあった。 チュートリアルよく読もう…

Typescriptのキャストについて調べた

Typescript: cast an object to other type. How? And instanceof or typeof? - acdcjunior’s

as で良いっぽい。

編集、削除の機能追加

チュートリアルを参考に、update, deleteを実装した。

気になったのが、リストから個別に削除するのはまぁ簡単なんだけど、そのあとリストを更新しなくて良いんだろうか?

まぁでもブラウザ上は存在している(と思われる)データが実はなかったというのを知るタイミングの問題かな。

ルーティングがよくわからん

Rails のルーティングを参考に設定したいんだけど、

// 抜粋
const appRoutes: Routes = [
  {
    path: 'projects',
    component: ProjectsComponent
  },
  {
    path: 'projects/new',
    component: ProjectDetailComponent
  },
  {
    path: 'projects/:id',
    component: ProjectDetailComponent
  },
];

サブディレクトリのところが上手くいかない。:idの方が優先されるのかなぁ。

Angular2のRouting, Formsの簡単なサンプル。ついでにFlux。(2.0) - Qiita

この辺参考に明日やってみよう。

雑感

コードを書くときはTypescriptなので、型が間違ってるとかはちゃんとチェックしてくれるんだけど、as とかのキャストを使ったりするところでは、実際動かすと上手くいかない。
キャストする時はその辺注意した方が良さそう。