valid,invalid

関心を持てる事柄について

Generative Art を始めて7日間、作品を毎日公開した記録

pandora_black

ゴールデンウィーク(以下、GW)の途中で以下の記事を読んで Generative Art (ジェネレーティブアート - Wikipedia)*1 なるものを知り、試しに触ってみたら面白かった。

dev.to にも My first step in learning Generative Art という記事を書いてみたら反応がそこそこあって嬉しかったので GW が終わるまでの7日間で毎日絵を書いて投稿してみることにした。

開発環境

Processing という言語と開発環境があるのだが慣れている JavaScript で書きたかったので p5.js を利用した。

p5.js Web Editor または OpenProcessing - Algorithmic Designs Created with Processing の web 上のインタラクティブエディタを使って描いた。Auto-refresh を有効にすると見た目の確認がすぐできて便利…だがコードでミスって無限ループや OOM に出くわすとブラウザが固まってしまい、作業内容が失われるという"痛み"を何度か味わった。

感想

HTML Canvas を触ったことあるので最初の描き始めるハードルはそれほど高くなく、line で線が引ける、rect で四角形が描ける、なるほど完全に理解した〜〜〜と思ったものの作品例を見た瞬間に「は???」ってなった。

とはいえとにかく手を動かさないと何も身につかないと思ってとにかく描いてみたらこれが楽しかった。

業務で書くコードと異なり、 Generative Art には正解もなければ自分が良いと認めるまで終わりもない。出来上がった幾つかのパターンを見比べてどちらがより良いかでずっと悩んだりする。無限に時間が吸われていきながらコーディングの楽しみを再び思い出した気がした。

流石に毎日の投稿を続けるのは厳しいが Generative Art は今後も続けていきたい。

作ったもの

全て ohbarye - OpenProcessing で公開している。

day 1

何も考えずに reference を読みながら出てきたものをコピペしていた。書けた時は「すげー!!!楽しー!!!」という感じでテンションがアップライジングしていたのだが、今になって見返すとなんだコレ…という気持ち。

day 2

現実に存在する何らかのモチーフを再現できるようになりたい、と思って作った。あまりこだわらなかった色合いがやはり良くない。

day 3

day 2 とは対称的に抽象的なイメージを絵に落とし込みたいと考えた。テーマは「糸」なので糸車的でありながらよくわからない現実には存在しない物体を描いてみた。清涼感ある色合いが気に入っている。

day 4

day 2 と同様に現実に存在する何かをもっと綺麗に描いてみたかった。curveVertex を使った曲線がちょうど花びらのようになったのと、noStroke にしたときの儚い感じは良く出来たと思う。

day 5

これは day 1 の次に気に入らないやつ…。「知らない言語を聞いたときの気持ち」を表現するという斜め上のところに飛び込んだが、何が描きたいか途中でよくわからなくなってしまった。

day 6

この日は明確に 2D noise を使った波で絵を描こう!と決めていた。ただの波だとチュートリアルのコピペで終わってしまうので大地を伝うような波、大地の断面図であるかのような波に見えるような動きを付けてみた。

このあたりからコードが複雑になってきており、あとで読み返すのが辛くなりそうだ。

day 7

実は http://evanyou.me/ の稲妻みたいなデザインにインスパイアされ、コードもかなり参考にした。

パンドラの箱」というモチーフが若干中二臭いが、躍動感・残像の雰囲気・ビビッドなカラーリングなどを総合的に見て、今のところ一番気に入っているかもしれない。

余談だが、パンドラの箱はもともとは水瓶だったのが箱として誤って伝わったらしいと描いている途中に調べて知った。

*1:カタカナだとジェネラティブ・ジェネレーティブ・ジェネレイティブなどで表記揺れするので英語に統一したい。Creative Coding

GitHub GraphQL API v4 を JavaScript から利用する

TL;DR

  • 開発している Slack bot で発生する N+1 問題を解消するために GitHub GraphQL API v4 を利用した
  • クライアントサイド(今回は JavaScript)側から使ってみただけだが、かなり開発体験が良かった

背景: review-waiting-list-bot について

review-waiting-list-bot という Slack bot を開発し、今もメンテナンスしている。

ざっくり言うと以下のように動いていた

  1. GitHub REST API v3 の Search API を叩く
  2. ユーザーの入力した条件に応じてフィルタリングする
  3. 結果を整形する
  4. Slack に通知する

詳細は以下の記事を参照

ohbarye.hatenablog.jp

今回はこのプロセスのうちの 1 を、 GitHub GraphQL API v4 を使ってややスマートにできたという話をする。

requested reviewer でフィルタしたい

GitHub issue に requested reviewer でフィルタしたいという要望*1が来た。requested reviewer は pull request page の以下の UI から設定できる情報のことだ。

f:id:ohbarye:20180504120941p:plain

フィルタの追加は過去にもやった*2ので最初は簡単だと思ったのだが、Search API の返すフィールドに requested reviewer の情報が含まれておらずどうしたものかと少し悩んだ。この情報を取得するための Review Requests API もあるのだがこのエンドポイントを pull request ごとに呼び出すと bot の処理がたいへん重くなってしまう(クライアントサイドの N+1 問題)*3

これが GraphQL だと1リクエストでバシッと必要な情報だけ取得できたりするのだろうかと思い至り、まずは https://developer.github.com/v4/explorer/:title) で試しにクエリを書き始めてみた。するとあっさり必要な情報だけを expose するクエリが書けてしまった。

f:id:ohbarye:20180504121849p:plain

赤い四角で囲んだ箇所が欲しかった情報だ。

JavaScript から GraphQL API を利用する

クエリが書けた、つまりやりたいことが GraphQL を使って実装できるとわかった。あとはこのクエリを JavaScript、正確に言うと Node.js のコードとして埋め込むだけだ。

GraphQL API の呼び出し方は 4 simple ways to call a GraphQL API – Apollo GraphQL を参考にした。HTTP クライアントは promisify したかった & 使ったことあるというだけで axios にしたので実質何でも良い。

骨子は以下のようになった。

const axios = require("axios")

const client = axios.create({
  baseURL: 'https://api.github.com/',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'Authorization': `Bearer ${process.env.GITHUB_AUTH_TOKEN}`,
  },
})

const query = `
  query {
    search(first:100, query:"type:pr author:ohbarye state:open", type: ISSUE) {
      nodes {
        ... on PullRequest {
          title,
          url,
          author {
            login,
          },
          labels(first:100) {
            nodes {
              name,
            },
          },
          reviewRequests(first:100) {
            nodes {
              requestedReviewer {
                ... on User {
                  login
                }
                ... on Team {
                  name
                }
              }
            }
          }
        }
      }
    }
  }`

const response = await this.client.post('graphql', { query })
console.log(response.data) // This results below

/*
{
  "data": {
    "search": {
      "nodes": [
        {
          "title": "Enable to fetch pull requests by specifying assignee",
          "url": "https://github.com/ohbarye/review-waiting-list-bot/pull/26",
          "author": {
            "login": "ohbarye"
          },
          "labels": {
            "nodes": [
              {
                "name": "enhancement"
              }
            ]
          }
        }
      ]
    }
  }
}
*/

response の中身も graphiql 上で確認できているので統合もスムーズにできた。

(実際には GraphQL の導入requested reviewer によるフィルタ機能の追加は別PRにした)

残課題

  • ページング
    • pageInfo.hasNextPagepageInfo.(start|end)Cursor を使って実装できることはわかるが、配列要素がネストしている場合が謎
    • クライアント側の実装がハチャメチャに複雑にならないか?
  • エラー処理
  • フィールドを区切るカンマは不要?
  • クエリ内のダブルクォーテーションの中にダブルクォーテーションをさらに埋め込むことはできない?

感想

  • 今回の実装で受けられた恩恵は一般的に語られているものの再確認であり、"真髄"に迫るような感じではなかった
    • N+1 を解消できた
    • 必要なフィールドのみにアクセスすることでレスポンスの content size を減らせた
  • とはいえ API が返却するリソースのモデル定義を容易に、かつインタラクティブに確認できるのはクライアントを実装するうえでかなり良い開発体験だった
  • 雰囲気でやっているのでベストプラクティス的なものを知りたい
  • サーバサイドの実装はまったくわからない

*1:https://github.com/ohbarye/review-waiting-list-bot/issues/32

*2:正確にはやってもらったtomoima525/support label by tomoima525 · Pull Request #23 · ohbarye/review-waiting-list-bot · GitHub

*3:worker 処理なのでタイムアウトなどの心配はないのだが Slack で bot を呼び出してから応答が遅くなるのは決して良くない

babel-eslint を使って非標準仕様でも lint できるようにする

Parsing error on ESLint

review-waiting-list-bot の実装中に以下のようなオブジェクトの分割代入するコードを書いたら eslint で parse error が起きるようになってしまった。

const { authors, ...conditions } = { authors: [], owner: '', repo: '' }
$ eslint .

/Users/ohbarye/.ghq/github.com/ohbarye/review-waiting-list-bot/src/App.js
  19:21  error  Parsing error: Unexpected token ..

✖ 1 problem (1 error, 0 warnings)

error Command failed with exit code 1.

Object Rest/Spread Properties はまだ stage 3 (2018-04-30 時点)。

また、標準化プロセスの中途にある仕様はこうなるのが正しいとのこと。

github.com

babel-eslint

stage n の仕様を利用したい場合は babel-eslint を使う模様。

devDependency として追加して

yarn add -D babel-eslint

.eslintrc.json に以下の行を追加する。

# .eslintrc.json
{
  "parser": "babel-eslint",
  ...
}

これで eslint コマンドが通るようになった。

環境

  • yarn v1.6.0
  • Node v8.3.0
  • eslint v4.4.1
  • babel-eslint v8.2.3

技術的負債を抱えながら突っ走る -日々15分の改善活動-

開発しているシステムが大規模かつ老朽化しかけていることもあり、最近は負債の返却を念頭に置いて普段の開発をしている。

前提として、個人的には負債を返す頻度は多いほど良い*1と考えている。

この立場に立つときに生まれる問い、「では、日々の開発のスピードを落とさずに頻度を上げるためにどうすれば良いか?」の1つの答えとして「まずは15分向き合ってみる」というのを実践している*2。その実践に関するメモが本記事である。

TL;DR

  • たった15分でも日々負債に向き合ってみると良い
    • 全体像も見えない、どれだけ時間をとられるかわからないものならなお良い
    • ここでいう負債には技術的負債だけでなく使われていないが残存している機能など(便宜的にプロダクト負債と呼んでみる)も含む
  • 全体像が不明瞭で手も付けづらい何かが頭の片隅にあること自体が精神衛生上よくない
  • 一方、わずかづつでも改善に向かっているという実感を個人ないしチームで得られるというのは精神衛生上とても良い

「技術的負債」は少しでも解決に近づける

実感として15分で解消できるものは意外と多い

技術的負債への取り組みは完全解決に至らなくても少しでも解決に近づけるだけで価値がある

  • 複雑な問題を複数の小さい問題に分解する
  • なぜ難しいかを言語化する
  • 解決への道筋を立てる

手に入った情報に対して他の人が議論を重ねたり代案を出したりしてさらに前進することもしばしばある。

「プロダクト負債」はステークホルダーに先手を打つ

技術的負債は基本的には非開発者からは見えないのに対してプロダクト負債は"見える"もの。つまりステークホルダーへの確認・コミュニケーションが高確率で発生する。そのため15分で何もかも片付くようなケースはあまりない。許可より謝罪*3という美しい言葉で攻めていきたいが信頼貯金を失うのは長期的に見て得策ではない

なのでイシュートラッカーやチャットで非同期にディスカッションするなど、手を動かす前に15分で合意を得るための先回りをしておくと後で物事がスムーズに進む。

多少強引なやり方としては「この機能の使用率n%なので消します。反対意見があれば何日までに返事ください。反対なければ進めます」というのもある。これはチームの文化・意思決定プロセスによっては良くないかもしれない。

なぜ15分?

正直15分でも1ポモドーロ25分でも何分でも良い。大事なのは「1日の間に捻出できる」「息抜きとして使える」「何も成果が出なくても許せる」程度の時間ということ。

Fail fast, fail cheap, and fail smart損失の限定らへんの考え方から影響を受けている。

これが1時間2時間ともなると同日のメインタスクの計画・見積もりに影響が出てくる。影響が出てくると周囲に「今日ちょっとこの負債見ますね、なぜなら…」みたいに都度ことわっていく必要が出てくる。マイクロマネジメントされているわけではないので裁量の範囲で片付けてもよいが、チームメンバーが何に時間を使っているかわからないというのは嫌なものだ(そのメンバーのタスクが遅れるならなおさら)。

どれから手をつければいいか?

コスパが良い・NPVの高い投資をするのが理想的だが15分の取り組みであればあまり意識しなくても良いと思う。「負債返却プロジェクト」みたいな大掛かりなものであれば慎重に吟味する必要はある。

負債を辛いと感じたら普段からメモしておく。何度もメモしたくなるタイミングがあるならその問題に取り組むことはきっと費用対効果が高い。また、普段から課題発見を意識しておくと質の良い課題を発見するスキルを向上するトレーニングになると思う。


技術的負債との戦い方は幾度となくどこでも語られたことと思うが、結局のところ少なからず個人やチーム、ないしは会社の状況なりのやり方を模索することになる。自分も今まさに模索フェーズなのでその現状を経験知としてダンプすることに価値があると考える。

また、今のチームではこの15分とは別に "Quality Budget" という、メインタスクとは別の改善活動に充てる予算(=時間)を設けている。2週間に一度、チームメンバー全員が丸1日を改善に使って良いというこの試みはこの4月にスタートしたばかりなので、いずれ振り返りを書く。

*1:技術的負債とどうやって戦うか - Qiita

*2:クックパッド社の朝Lint活動などで知られるように、考え方としては目新しいものではない

*3:許可より謝罪 : けんすう日記

package.json の dependencies を JavaScript のコード内で参照する

package.json に書かれた、依存しているライブラリの名前 (dependencies) を JavaScript のコード内で参照するやり方。

import pkg from './package.json';

const dependencies = Object.keys(pkg.dependencies)
const devDependencies = Object.keys(pkg.devDependencies)
const peerDependencies = Object.keys(pkg.peerDependencies)

たとえば rollup.config.js で external を指定するときとか。

{
  external: Object.keys(pkg.dependencies)
  ...
}

brew cleanup でディスクの空き容量を増やす

brew は古くなった formula を自動的に消してくれないので、使い続けるほどにインストールしたパッケージが積もってディスクを圧迫する。

FAQ — Homebrew Documentation に書いてある brew cleanup を試したら 4GB ほどの空き領域を得られた。

How to use brew cleanup

# 特定の formula を消す
$ brew cleanup <formula>

# outdated な formula をまとめて消す
$ brew cleanup

# 削除対象の formula と得られる空き容量を確認する
$ brew cleanup -n

Read help

FAQ 以上の情報は help を見てみる。

$ brew cleanup -h

brew cleanup [--prune=days] [--dry-run] [-s] [formulae]:
    For all installed or specific formulae, remove any older versions from the
    cellar. In addition, old downloads from the Homebrew download-cache are deleted.

    If --prune=days is specified, remove all cache files older than days.

    If --dry-run or -n is passed, show what would be removed, but do not
    actually remove anything.

    If -s is passed, scrub the cache, removing downloads for even the latest
    versions of formulae. Note downloads for any installed formulae will still not be
    deleted. If you want to delete those too: rm -rf $(brew --cache)

新たにわかったこと

  • --prune=days で n 日以前のキャッシュを破棄できる。指定しない場合の記述がないが code を見るとデフォルトは14日のようだ。
  • -n--dry-run の 短縮形だった
  • -s で 最新の formula のダウンロードファイルも削除してキャッシュを掃除する。ただしインストールされたものについては消えない。これらも消したい場合は rm-rf $(brew --cache) を使う。

より詳細な挙動を追うならエントリーポイント実際の処理あたりを読むと良さそう。

環境

スプレッド構文と Object.assign の違い

JavaScript の Object の spread operator (スプレッド構文) と Object.assign の違いを調べてわかった気になるが何度も忘れるので整理

結論

実質ほぼ同じことが実現できるが互換性の問題があるので Babel や TypeScript でトランスパイルするなら spread operator を使っておきたい。

前提

実質ほぼ同じことが実現できる。

const object1 = {
  a: 1,
  b: 2,
  c: {
    d: 3
  }
};

const object2 = {
  b: 4,
  c: {
    d: 5
  }
};

const spreaded = {...object1, ...object2};
console.log(spreaded);
// > Object { a: 1, b: 4, c: Object { d: 5 } }

const assigned = Object.assign({}, object1, object2);
console.log(assigned);
// > Object { a: 1, b: 4, c: Object { d: 5 } }

また、どちらも shallow copy である。

Spread operator

メリット

  • サポートしていない環境で動かす場合には、Babel などでトランスパイルするだけでOK
  • 簡潔

デメリット

Object.assign

メリット

  • 標準化されている
  • 引数を動的に取ることができる
const sources = [{ a: 1 }, { b: 2 }];

const assigned = Object.assign.apply(Object, [{}].concat(sources));
console.log(assigned);
// > Object { a: 1, b: 2 }

デメリット


感想

「標準化されている」ことはメリットとして挙げられるはずなのに、標準化されているからこそトランスパイル対象外になってしまい、避けたくなってしまう??

何か間違っている気が…。

参考

Object.assign() - JavaScript | MDN

スプレッド構文 - JavaScript | MDN

javascript - Object spread vs. Object.assign - Stack Overflow