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

The future starts today

Webとか英語とか育児とかに関する雑記

顔と名前を覚えるのが得意すぎてツラい

f:id:shibe97:20170417223901j:plain

先日こんなツイートをしたところ、思ったより反響があったのでこれについて書いてみる。

自分は、1回でも面と向かって話したことがある人であれば、顔と名前はだいたい覚えている。

少なくとも顔はほぼ100%覚えている。

1度しか話したことがない場合、久々に会うと相手には覚えられていないことの方が多いのだが、たまに覚えてくれている人もいる。

俺調べでは、ビジネス寄り(営業とか企画)の方が多い。

逆にエンジニア、デザイナーは高確率で覚えられていない。笑

「覚えられないくらい印象が薄いのでは?」と言われたらそれまでだが、それにしてもだいぶ偏りがあると思う。

一度会社内でこの話をしたときに同調してくれた方々は皆、所謂「コミュ力が高い」部類の方々だった。

ちなみに皆、既婚。 周りをよく見ていて、ささいなことに気を遣えるような方々だった。

先日の飲み会の話

社内の若手デザイナーを集めて飲もうという会があった。

自分は5年目で、例年新卒研修で教える立場だったため、全員に対して面識があるつもりだった。

その場にいたとある後輩に

「久しぶりだね〇〇さん、最近は何やってるの〜?」

とさらっと聞いてみたところ、「え?」と驚きの表情と共に相手に焦りが見えた。

「誰だっけ・・・」と必死に頭のデータベース内を探っていた。

「あ、覚えてないか〜」とすかさずフォローを入れたが、なんとも言い難い悲しい気持ちになった。

先日の廊下での出来事

自分はデザイナーだが、とある案件で営業の方々(全員初見)+自分で飲みに行ったことがあった。

その中にデザイナーの業務に興味を持ってくれた先輩がいた。

ばっちりスーツを着こなしていて、「この人はモテそうだ。」という印象だった。

それ以来、3ヶ月に1度くらい社内でばったり会うが、その度に向こうから会釈してくれる。

先日は会社の廊下で20 ~ 30メートル先から会釈してくれて驚いた。

自分より相手の方が先に気づくということが自分にとっては結構珍しく、素直に「この人はすごい」と思った。 信頼できる人だと思った。

顔と名前を覚えることのメリット

自分の場合、1度しか話したことがない相手が自分のことを覚えていてくれた場合、「この人はできる!」とかなり好感度が上がる。 特に相手が目上の方だったらなおさらだ。

偉い人ほど顔と名前を覚えるべきだと思っている。

一般的に、偉くなればなるほど名前が知れ渡って、一方的に知られる可能性が高い。 そんな時こそチャンスだ。

「え、こんな人が自分のことを知ってくれているの?」となると、途端にその人に対する好感度は上がるものだ。

自分とは遠い世界の人だと認識されると好感度は下がっていく。

まとめ

顔と名前を覚えるだけで、人生がちょっと良い方向に変わるかもしれない。

一方的に覚えているとツラいことも多いけれど、それも上手く利用できるとコミュニケーションを円滑に進めることができる。

【Three.js】perlin-noiseを用いてなだらかな地形を生成する

最近スマホ版のマインクラフトにどハマりしていて、ある時「これthree.jsで作れるのでは?」と思い立ち、作り始めた。

three.jsの記事を色々と探していると、だいたいHTMLにscriptタグでthree.jsを読み込んで、立方体や球を表示させて、回転させてみて、終わり〜という記事ばかりであまり参考にならなかった。

とりあえずNode環境で実装したかったので、npmパッケージを探すところからスタート。

three.jsを扱えるnpmパッケージを使う

www.npmjs.com

以下のような形で使うことができる。

import THREE from 'three.js';
import keyEvents from './keyEvents';
import { createMap } from './map';

const main = function() {
  const scene  = new THREE.Scene(),
    width = window.innerWidth,
    height = window.innerHeight,
    fov = 60,
    aspect = width / height,
    near = 1,
    far = 1000,
    camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(0, 20, 0);

  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(width, height);
  document.body.appendChild(renderer.domElement);

  const directionalLight = new THREE.DirectionalLight(0xffffff);
  directionalLight.position.set(0, 0.7, 0.7);
  scene.add(directionalLight);

  // set map
  createMap(scene);

  renderer.render(scene, camera);

  // set key events
  document.addEventListener('keydown', (e) => keyEvents(e, renderer, scene, camera));
}

window.addEventListener('DOMContentLoaded', main, false);

なだらかな地形を生成する

最初は立方体をひたすら敷き詰めて平面を作った。
しかし、実際マインクラフトはいい感じに地形が形成されている。
それっぽくなだらかな凹凸を出すためにはどうすれば良いのか悩んだ結果、perlin-noiseというパッケージを見つけた。

www.npmjs.com

中身を見るととても短い実装だが、とりあえず今は動けば良い。

perlin-noiseの使い方

generatePerlinNoise(width, height)を実行するとwidth * height分のlengthを持った配列が返ってくる。 各要素には0~1の値が格納されており、縦横にループで回すと良い感じのy座標が得られるという仕組みだ。

以下のように実装できる。

import THREE from 'three.js';
import perlin from 'perlin-noise';
import config from './config/blocks';

const X_LENGTH = 100;
const Y_LENGTH = 100;

class Block {
  constructor(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  setMesh() {
    const material = new THREE.MeshPhongMaterial({ color: config.color, side: THREE.DoubleSide });
    this.mesh = new THREE.Mesh(geometry, material);
    this.mesh.position.set(this.x, this.y, this.z);
  }
  getMesh() {
    return this.mesh;
  }
}

export const createMap = (scene) => {
  const yArray = perlin.generatePerlinNoise(X_LENGTH, Y_LENGTH);
  for (let i = 0; i < X_LENGTH; i++) {
    for (let j = 0; j < Y_LENGTH; j++) {
        const y = Math.round(yArray[i * Y_LENGTH + j] * 3) * 10 - 25;
        const block = new Block(-X_LENGTH*10/2 + i * 10, y, -Y_LENGTH*10/2 + j * 10);
        block.setMesh();
        scene.add(block.getMesh());
    }
  }
};

すると、こんな感じの地形が作れる。

f:id:shibe97:20170328231322p:plain

まとめ

スター数を見た感じ、もっとメジャーなライブラリがありそう。
一旦やりたいことは実現できたので良しとする。

Jestのスナップショット機能を使ってみた

f:id:shibe97:20170320232906j:plain

Jestとは

facebook.github.io

  • Facebook製のテストフレームワーク
  • Reactアプリケーションのテストに向いてる
  • スナップショットテストがある

スナップショットテストとは

  • スナップショット != スクリーンショット
  • 画像の差分を取るのではない
  • 出力されるHTMLの構造を比較する

Worcに導入してみた

shibe97.hatenablog.com

(別件ですが、先日Windowsにも対応しました!)

Worcはかなり細かくコンポーネントを分けているので、試しやすかった。
完全に自分にとっての実験環境になりつつある・・・

対象のコンポーネント

とりあえず今回は、ツイート部分のテキストエリアコンポーネントを対象としてテストを行なった。
以下のようなコンポーネント

  1 import React from 'react';
  2 import styles from './textarea.css';
  3
  4 export default ({ value = '', inputUpdate }) => (
  5   <textarea
  6     className={styles.textarea}
  7     placeholder="What's happening?"
  8     value={value}
  9     onChange={e => inputUpdate(e.target.value)}
10   />
11 );

スナップショット機能の使い方

スナップショット機能を使うにはreact-test-rendererというパッケージが必要。

$ npm i -D jest react-test-renderer

今回は対象のコンポーネントと同階層にテストファイルも置いた。
テストファイルの中身は以下のような感じ。

  1 import React from 'react';
  2 import renderer from 'react-test-renderer';
  3
  4 import Textarea from './Textarea';
  5
  6 describe('components/atoms/textarea', () => {
  7   it('renders correctly', () => {
  8     const tree = renderer.create(
  9       <Textarea value="test" inputUpdate={() => {}} />
10     ).toJSON();
11     expect(tree).toMatchSnapshot();
12   });
13 });

スナップショットが生成される場所

テストを実行すると、テストファイルが置いてある階層に__snapshots__ディレクトリが生成される。
このディレクトリ内にスナップショットファイルが入る。

.
├── Textarea.js
├── Textarea.test.js
├── __snapshots__
│   └── Textarea.test.js.snap
└── textarea.css

生成されたスナップショット

次のようなスナップショットファイルが生成される。

exports[`components/atoms/textarea renders correctly 1`] = `
<textarea
  className="textarea"
  onChange={[Function]}
  placeholder="What\'s happening?"
  value="test" />
`;

テスト時にこのファイルと常に比較され、差分があるとこける。

そのままでは修正するたびにこけてしまうし、意図的な変更をする場合でもテストがこけてしまう。

そこでスナップショットを更新するオプションが用意されている。

$ npm test -- -u

ちなみに--はエスケープの役割だそう。 npm testjestが呼ばれるようにpackage.jsonscriptsに設定してある。

試しに少し変更を入れてテストを走らせてみる

valueの部分にtestという文字列を付け加える。

  1 import React from 'react';
  2 import styles from './textarea.css';
  3
  4 export default ({ value = '', inputUpdate }) => (
  5   <textarea
  6     className={styles.textarea}
  7     placeholder="What's happening?"
  8     value={`${value}test`}
  9     onChange={e => inputUpdate(e.target.value)}
10   />
11 );

この状態でテストを走らせると次のようになる。

f:id:shibe97:20170321215237p:plain

差分がとても分かりやすく表示されている。
オプションを付けてスナップショットを更新するとエラーはなくなる。

Worcでの運用方法

とりあえず以下のようにしてみた。けどもっと良い運用方法あるはず。
皆どうしているんだろうか。

  • 元々push時にCIでnpm testを回していた
  • しかし、CI上でnpm test -- -uしてしまうとスナップショットテスト全て通過してしまうので意味がなくなってしまう
  • そこで、push直前にnpm testして、うまくいった場合にCI上でnpm test -- -uを行う
  • push直前の処理はGit hooksを利用

Git hooksとは

Gitコマンドにフックして何らかの処理を行わせる仕組み。

.git/hooks以下のシェルファイルをいじると動く。

  • pre-commit
  • post-commit
  • pre-push
  • post-push
  • pre-receive
  • post-receive
  • etc…

今回自分はpre-pushを利用。

.git以下はGitHubで共有できないので、postinstall時などに書き換えるようなスクリプトを用意する必要がある

まとめ

スナップショットという単語に興味は持っていたけどずっと触れていなくて、ついに試すことができて良かった。 非常に簡単にテスト実行まで試すことができたので、誰でもさくっと導入できると思う。。

難しいと思うのは運用面で、複数個所がエラーで落ちた際に、本当は正解なのに前回のスナップショットを正としているためエラーとなってしまっているパターンが混ざっていると、その切り分けをしなくてはならないのが大変だと思った。

このあたりはよく考えれば良い解決方法がありそうなので、色々と試していきたい。

関数の魅力を引き出すmap, filter, reduce

f:id:shibe97:20170302175949j:plain

JavaScriptの関数は第一級オブジェクトである。

変数に関数を代入できるし、関数の引数に関数を与えられるし、関数の返り値として関数を返せる。

ES2015で追加されたmap, filter, reduceを使うと今まで何だか綺麗に書けなかったものがスッキリいい感じに書けるようになる。

よくあるJSON配列をごにょごにょと変換する例を挙げていこう。

対象のJSON配列:

const list = [
    {
        id : "1",
        name : "hoge",
        status : "active",
        num : 13,
        children : ["a", "c", "e"]
    },
    {
        id : "2",
        name : "fuga",
        status : "stop",
        num : 22,
        children : ["a", "g"]
    },
    {
        id : "3",
        name : "piyo",
        status : "active",
        num : 57,
        children : ["e", "h"]
    }
];

map

mapを使うと、ある配列を新しい配列に変換できる。 要素数を変えずに他の配列書き換えたい場合に便利。

idだけを抜き出した配列を作りたい

従来の書き方

var ids = [];
for (var i = 0; i < list.length; i++) {
    ids.push(list[i].id);
}

console.log(ids);   // [ '1', '2', '3' ]

mapを使った書き方

const ids = list.map((item) => item.id);

console.log(ids);   // [ '1', '2', '3' ]

idとnameだけを抜き出した配列を作りたい

従来の書き方

var ids = [];
for (var i = 0; i < list.length; i++) {
    ids.push({
        id : list[i].id,
        name : list[i].name
    });
}

console.log(ids);
// [ { id: '1', name: 'hoge' },
//  { id: '2', name: 'fuga' },
//  { id: '3', name: 'piyo' } ]

mapを使った書き方

const ids = list.map(({id, name}) => ({id, name}));

console.log(ids);
// [ { id: '1', name: 'hoge' },
//  { id: '2', name: 'fuga' },
//  { id: '3', name: 'piyo' } ]

filter

filterを使うと、ある配列から必要なものだけを選んだ新しい配列を作ることができる。

statusがactiveのものに絞り込んでidを取得したい

従来の書き方

var activeList = [];
for (var i = 0; i < list.length; i++) {
    if (list[i].status === 'active') {
        activeList.push(list[i].id);
    }
}

console.log(activeList);   // [ '1', '3' ]

filterを使った書き方(mapも用いる)

const activeList = list.filter((item) => item.status === 'active')
                       .map((item) => item.id);

console.log(activeList);   // [ '1', '3' ]

statusがactiveのものがあるかどうかをBool値で取得したい

従来の書き方

var flag = false;
for (var i = 0; i < list.length; i++) {
    if (list[i].status === 'active') {
        flag = true;
    }
}

console.log(flag);   // true

filterを使った書き方(mapも用いる)

const flag = 
    list.filter((item) => item.status === 'active')
    .length > 0;

console.log(flag);   // true

reduce

reduceは配列内の全要素を用いて値やオブジェクトを生成する。 使い方が難しいが、使いこなせると強い。

各要素が保持しているnumの合計値を取得したい

従来の書き方

var sum = 0;
for (var i = 0; i < list.length; i++) {
    sum += list[i].num;
}

console.log(sum);   // 92

reduceを使った書き方(mapも用いる)

const sum = list.map((item) => item.num)
                     .reduce((x, y) => x + y);

console.log(sum);   // 92

各要素が保持しているchildren配列を連結したい

従来の書き方

var children = [];
for (var i = 0; i < list.length; i++) {
    children = children.concat(list[i].children);
}

console.log(children);
// [ 'a', 'c', 'e', 'a', 'g', 'e', 'h' ]

reduceを使った書き方(mapも用いる)

const children = list.map((item) => item.children)
                     .reduce((x, y) => [...x, ...y]);

console.log(children);
// [ 'a', 'c', 'e', 'a', 'g', 'e', 'h' ]

まとめ

for文やforEach文はループ処理であり、returnが存在しないため、ループ処理後は一度式を途切らせなくてはならない。
しかし、mapfilterreduceを用いると次の処理にそのままつなげることができる。
関数型の入り口として知っておいて損はない。

また、ほとんどの処理を1行で書くことができるため、コードが簡潔で済む。
慣れていないと解読する側は大変だが、mapfilterreduceさえ抑えておけば、後々活きてくるだろう。

electron-builderを使ってdmgファイルを生成する

f:id:shibe97:20170216225044p:plain

shibe97.github.io

このサイト上からdmgファイルをダウンロードし、ユーザーの端末上でインストールできるようにした。

Electronアプリからdmgファイルを生成する方法

electron-builderを用いる。

github.com

electron-builderを使うための設定

まず、package.json内に以下のプロパティを設定している必要がある。

  • name
  • description
  • version
  • author

また、electron-builder自身の設定もpackage.json内に記述する。
dependenciesscriptsと並列にbuildというプロパティを設定する。

"build": {
    "appId": "com.electron.worc",
    "mac": {
        "target": "dmg"
    },
    "directories": {
        "output": "docs"
    }
}

appId

アプリの識別子。
MacではCFBundleIdentifier、WindowsではApplication User Model IDとして用いられる。
デフォルトはcom.electron.${name}という形式。

Apple Developer Programに登録していないと警告が出るが、そのままパッケージングは進められる。
Code Signingに関してはこのあたり参照。

github.com

mac

Macにおける設定。
パッケージングファイル形式をdmgにしたい場合はtargetプロパティにそう指定する。

directories

パス関連の設定。
dmgファイルのアウトプット先はoutputプロパティに指定する。
今回はGitHub Pagesにホスティングさせるためにdocsディレクトリを指定した。

その他の設定

github.com

electron-builderを起動する

package.jsonに以下のようにscriptsを登録した。

"scripts": {
    "pack:osx": "build --mac --x64"
},

また、次の作業も必要。

  • electronをdevDependenciesに置く必要がある
  • package.jsonmainプロパティに指定しているファイルをもとにパッケージング処理が走る
  • アプリのアイコンをbuildディレクトリ配下に用意する(ファイル名はicon.icns)

Worcのディレクトリ構成は以下のような感じ(一階層のみ表示)

.
├── README.md
├── app
├── assets
├── build   <= アプリのアイコンを置く
├── dist
├── docs
├── node_modules
├── package.json
└── webpack.config.js

全てが設定済みの状態で先ほどpackage.jsonscriptsに登録したコマンドを叩けば起動する。

$ npm run pack:osx

dmgファイルをGitHub Pagesにホストする

Worcでは、GitHub Pagesを利用してランディングページをホストしている。 ここにdmgファイルも一緒において、ダウンロードしてもらおうと考えた。

しかし、dmgファイルをGitHubにpushしようとすると100MBを超えていて、pushできない現象が陥った。 そこで、Git Large File Storage(LFS)を利用した。

LFSの使い方

下記の手順で行う。(hoge.dmgは適宜、正しいパスに置き換えてください)

$ git lfs install
$ git lfs track hoge.dmg
$ git add .gitattributes
$ git add hoge.dmg
$ git commit -m "add dmg file"
$ git push origin master

git lfs trackを行うと、.gitattributesが生成され、そこに対象のファイルが記述される仕組みとなっている。

これらの作業により、100MB以上のファイルもpushできる。(ただし月に1GBまで、それ以降は有料)
たぶん1ヶ月にそんなに多くリリースしないと思うので、無料でいけるはず・・・。

まとめ

今までelectron-packagerを用いてパッケージングを行なった後、zip形式にし、GitHubのreleaseページにデプロイしていた。

なぜかzipにした後に解凍してみるとアプリケーションが動かなかったり、よくわからない現象が起こって困っていた。
また、GitHubのreleaseページからのダウンロードはあまり良いユースケースとは思えなかったため、ランディングページから直接ダウンロードできるようになって良かった。

ElectronアプリにGoogle Analyticsを導入する

f:id:shibe97:20170116230507p:plain

先日、Electron製のTwitterクライアントであるworcを公開した。

shibe97.hatenablog.com

せっかくなのでアクセス解析をしようと思い、Google Analyticsを導入したのだが、色々とハマりどころが多かったのでメモ。

Google Analyticsに登録

f:id:shibe97:20170116221551p:plain

新規にアカウントを作ろうとすると上記のような画面が出る。

その際、ウェブサイトのURLを入力する欄があるが、Electron製のためURLがない。 ここは、example.comなど適当に入力しておく。

ラッキングコードを設定する

アカウントの作成が終わるとトラッキングコードが生成される。
しかし、このコードをそのままElectronアプリのHTMLに挿入してもうまく計測がされない。

Google Analyticsではcookieを用いてユニークユーザーの判定をしているが、Electronアプリはドメインがないためcookieの登録ができない。

そこで、electron-cookiesを用いる。

github.com

その名の通り、Electron上でcookieを擬似的に持たせることができる。

npm installを行なった後、これを用いて以下のようにトラッキングコードを設定する。

$ npm install @exponent/electron-cookies --save
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  (() => {
    'use strict';
    const ElectronCookies = require('@exponent/electron-cookies');
    ElectronCookies.enable({ origin: 'https://example.com' });
    ga('create', 'GOOGLE_ANALYTICS_ID', 'auto');
    ga('set', 'location', 'https://example.com/');
    ga('set', 'checkProtocolTask', null);
    ga('send', 'pageview');
  })();
</script>

node_modulesに注意

Electronアプリは容量が多いためWebpackなどでバンドル後、ビルド時にnode_modulesを除外しているパターンもあるだろう。
一見、Webpackでの依存解決でelectron-cookiesbundle.jsに含まれそうだが、このトラッキングコードはindex.htmlに記述されているため、Webpackのバンドルの対象外だ。

よって、node_modules/electron-cookiesを含めた状態でアプリをパッケージングする必要がある。

これに気付くのにすごい時間かかった。。

2017年の目標

ブログで宣言。

Happy New Year

ブログ

月2回以上書く。
去年は途中全然書いていなかったので、今年は飽きずに頑張る。

英語

とりあえずTOEIC800点超える。
海外出張1回以上行く。
海外ドラマや映画に手を出す。

社外活動

登壇4回以上する。
WorcのDAUを1000人以上にする。
定常的な副収入源を作る。

社内活動

熟練JSerを増やす。
給料上げる。
残業時間を月10時間以内に抑える。

総じて

今年で30歳になるので、色々とチャレンジしていきます〜!