電池冷蔵庫

除雪のない世界に行きたい

自宅でIT受託やるなら開発環境どうする的な

 ただのメモ。

絶対に必要なもの

 基本中の基本。開発マシンを手で持ちながら働くのはありえない。床でやるのも厳しい。

 最近は棒立ちエンジニアも流行っているらしいんですが、個人的には椅子でダラダラやりたいので普通の椅子と机を買いました。机5000円、椅子4万円なり。

パソコン

 支給品がある場合はそれを使う(しかない)けれども、まあ大抵は自腹で調達。

 デザイン職でないとはいえ、まともな色が出ないような環境では困るし、iOSエミュレーターのこともあるしで、ほぼ自動的にiMacが無難という話に落ち着きます。5K 27インチのやつを買ってきて、いざという時に「今サポートに問い合わせ中で〜」と釈明するためだけにアップルケアをつけて、16GBメモリ x2を自前で突っ込んで… 合わせて25万円くらい? もうちょっとしたかも。

ツール類

 意外と高い道具代。プログラミングが無料でも、プログラミング業にはお金がかかるのであった。

  • JetBrains: 2.5万円/年
  • GitKraken: 6000円/年

 電気代も結構イっちゃってると思いますが、数えていないのでセーフ。

あるといいもの

外部ディスプレイ

 広さは強さだ! ディスプレイは多けりゃ多いほどよし。

 我が家の場合、21インチのクッソ安いモニターを2つ、それぞれディスプレイアームでiMacの横に設置しています。接続するにはアップル純正のコネクタがいるんですが、これがまた高くて… 怪しい品物も試しましたが、接続品質に難ありで無駄な出費と相成りました*1。ディスプレイ1.5万、アームとケーブルで1.5万、それが2セットなので6万ほど。

UPS

 ブレーカーが落ちても雷が落ちても大丈夫なようにUPSを買いましょう。

 家庭用の極めてしょぼいやつであれば、まあ2万円くらいでしょう。それをバッテリー耐用年数ごとに買い換えるので1万円弱/年といったところ。

感想

 結構お金使ってるなあと思いました。

*1:ただし純正品でも現macOSの外部ディスプレイ処理は安定感を欠き、ストレスが大きい。バグ対応もっと頑張って?

ドワンゴ・ニコニコの広報方針について

要約

 スケジュールを告知しましょう。

あらまし

 本日3月1日、ドワンゴはニコニコの推奨投稿設定を変更しました。

2018年3月1日(木)より動画投稿時に生成する画質のうち、540p / 480p / 360pのビットレート上限が引き上げられました。

【ニコニコ動画】540p/480p/360p生成時のビットレート上限を引き上げました

 発表時点において、仕様変更はすでに実行済みであります。

 公表時刻は記載がありませんが、ツイートを検索する限り17時付近と推定されます。

 ニコニコ動画の投稿機能は、少なくとも現時点においてドワンゴ1社で完結しうるサービスではなく、投稿者の動画作成から各種支援ツールの設定まで、一連の結びつきにより成り立っている(脆弱な)生態系です。特に画質に関しては、支援ツールが対応しなければ引き上げもクソもありませんドワンゴ関係者は、当然それを知っておるわけです。さにありながら周知期間を設けない時点でちょっと意味が分からないわけですが、そればかりか「6Mbps以上のビットレートエンコードしてから投稿すること」を自ら「お勧め」しているのはさらに滑稽であります。

 一体、現在のニコニコ投稿者のうち何割が当該「お勧め」に従いうるのでありましょうか。そりゃ一体、誰にお勧めしているのですか。なんでそれを半月前に発表できないんでしょうか。

 そこで我々が疑問を抱えてニコニコインフォを拝見しますと、前日2月28日に下記のような不具合が発生しております。

現在、動画の投稿に失敗する不具合が発生しており原因を調査しております。
■ 発生期間 2018年2月28日(水)22時15分頃 〜 発生中

【復旧】動画の投稿に失敗する不具合

 これはもう下種の勘繰りで、まさかとは思いますが、この不具合とはビットレート上限引き上げに伴うシステム改変が引き起こしたバグなのでしょうか?

「先に仕様変更を告知して、そのスケジュールが遅れたら恥をかいてしまう。そこでこっそりと2月28日にシステム変更したうえ、何もかも解決してから新実装の事実を発表した」などという経緯なのでしょうか。もちろん、まさかそんなことは有り得ないと信じたいのですが、残念ながら前日の不具合には原因の記載がありません。この事実を並べてみると、客観的に見てそのような不信感を持たれてもやむを得ない状況を自ら作出してしまっていると言えましょう。

先に言え

 一般論として、ユーザーとの意思疎通を欠き、約束をせず、奇襲的にシステム仕様を変更することは歓迎されません。緊急やむを得ない変更ならまだしも「スケジュール告知して遅れたら恥ずかしいから」などという理由は言語道断です。

 思い起こしてみますと、去年までのニコニコ動画はスケジュールの告知をしていました。告知されたスケジュールがこれっぽっちも守られなかったとしても、少なくとも「約束をして、それを破る」という手順自体は踏んでいたわけであります。

 どうかドワンゴ社におかれましては、仕様変更前に実施スケジュールを告知する体制を採用していただきたいと思います*1

*1:まだ殿様気分が抜けてないようだな?と思いますが記事に書くのはやめておきます

Rails -> GitLab CI -> Docker -> GKE -> happy.

…という話を頑張って書いたんですが、原稿が消えたのでリンクだけ貼ります。

 処理本体はシェルスクリプトとして格納してあります。まあ要するに、RailsをDockerイメージに閉じ込めて*1GCPのサービスアカウントでgcloudログインしてからぽちぽちコマンド叩いてデプロイしているだけです。簡単ですね。

 ということで、GitLabなら無料で「productionブランチ更新したら自動デプロイ」な環境が作れますよ。非公開のプライベートリポジトリも無料ですよ。

 GitHubを捨ててGitLab CIライフを始めるなら今!

*1:Railsのコンテナにはnginxもいます。そいつがpumaとソケット越しに文通するシンプルな構造。ただ現状だとロギング未対応なので、そのうち直したい

Vue.js E2E test (GitLab CI + Webpack + Jest + Puppeteer)

TL;DR

 snippet & zip file for YOU!

gitlab.com

 vue-cliで自動生成されたE2Eテストの設定が意味不明だったので、自前で置き換えたものです。一応動きました。やったぜフラン!

ご注意

 PuppeteerとChromiumのリリース頻度を考えると、この記事に書いてあることは3時間くらいで陳腐化します。動かなかったら自分でなんとかしてね。

 [追記] 参考にした公式ドキュメントがあることを忘れてました。 Using with puppeteer by jest

GitLab CIとE2Eで幸せになろう

 GitLabとはgitリポジトリとdockerレジストリとcron内蔵型docker-based CIが一体化した無料で使える世界最高のサービスですが、そのことは常識なので説明しません。まさかGitLabを単なるGitHubのパチモンだなんて思っている人はいませんよね?

 E2Eテストというのは要するに「実際ちゃんと動くか」というテストですが、これまた常識なので説明は差し控えさせていただきます。決して私がよく分かってないわけではありません。

 最近では、vue-cilのwebpackテンプレートを利用するとE2Eテストが追加されるなど、E2Eは「あって当たり前」のテストになって来ています。まだ製造コストが高すぎるため「全ての画面に書いて当たり前」とまでは行きません(そうなる時代は当面来ないと思います)が、壊れたときに致命的な部分のE2Eテストを書いておくと、即死ダメージを瀕死レベルまで軽減できる可能性があります。

 クレームを入れるページとか、不具合を報告するページなど、「そこが壊れてたらもう終わり」という部分ですね。こういったセーフティネットがぶっ壊れていると救いがありませんので、是非テストしましょう。

 ただ、既存のE2Eテストはツールの使い方そのものが難しく、設定ファイルはおよそ理解不能であり、導入したが最後、誰もメンテナンスできる気がしないようなコードで構成されていました。これではやはり、プロジェクトの基盤としてはなかなか受け入れられません。

puppeteer

 ここで注目したいのが、最近にわかに流行りつつあるヘッドレスChromium操作ライブラリー puppeteer です。

 puppeteer はあくまで Chromium を操作するだけなので、複数のブラウザー (ぶっちゃけIE) を操作して、厳密に動作チェックするようなテストには使えません。しかし、「極めてクリティカルな機能の、本当に最低限の動作」を確かめるくらいなら、たとえブラウザーごとにデザインの崩れなどがあったとしても、Chromiumで動けばだいたい問題ないはずです*1

 また puppeteer は機能がシンプルである分、使い方もシンプルです。これと別にアクセス先のサーバーさえ用意すれば、E2Eテストに着手できるわけです。それならメンテできそう!というエンジニアも多いと思います。

具体的

 コード類は冒頭のスニペットからzipを取得してください。以下は一部ファイルとポイントの解説。

pacage.json

{
  "scripts": {
    "e2e": "jest --config test/e2e/jest.conf.js --forceExit"
  }, ...
}

 asyncを複数利用すると、現在のjestは壊れます。仕様なのかバグなのか知りませんが、永久にプロセスが終了しません。そこで --forceExit をつけます。

runner.js

let server
let host
let port

function path (str = '/') {
  const h = host || 'localhost'
  const p = port || process.env.PORT
  return `http://${h}:${p}/#${str}`
}

const run = async () => {
  const webpack = require('webpack')
  const DevServer = require('webpack-dev-server')
  const webpackConfig = require('../../../build/webpack.prod.conf')
  const devConfigPromise = require('../../../build/webpack.dev.conf')
  const fetch = require('node-fetch')

  const devConfig = await devConfigPromise
  const devServerOptions = devConfig.devServer
  const compiler = webpack(webpackConfig)

  server = new DevServer(compiler, devServerOptions)
  host = devServerOptions.host
  port = devServerOptions.port

  await server.listen(port, host)

  const url = path()
  console.log('server url', url)

  await fetch(url)
  console.log('dev server running!')
}

module.exports = {run, path}

 ここで行われていることを平易に言い換えると「力ずくで Webpack dev サーバーを起動し、そこにアクセスしてE2Eテストを実施する。そのとき初回ロードだけめちゃくちゃ長いので、初期設定において1回ロードできるまで待つ。こうすると次からは普通に動く」となります。クッソ力技ですね!

 ここの処理は完全に間違っているような気がしますが、とにかくこれで動く(ほかの方法も見つからない)ので、気にしない。後でちゃんと調べて直すかもしれない(願望)。

setup.js

const puppeteer = require('puppeteer')
const mkdirp = require('mkdirp')
const fs = require('fs')
const {dir, endpoint} = require('./endpoint')

module.exports = async function () {
  await require('./runner').run()

  const browser = await puppeteer.launch({args: ['--no-sandbox']})

  // store the browser instance so we can teardown it later
  global.__BROWSER__ = browser

  // file the wsEndpoint for TestEnvironments
  mkdirp.sync(dir)
  fs.writeFileSync(endpoint, browser.wsEndpoint())
}

 argsが既存の例と異なりますが、Linux SUID Sandboxのページを見ると

The Linux SUID sandbox is almost but not completely removed. See https://bugs.chromium.org/p/chromium/issues/detail?id=598454 This page is mostly out-of-date.

 とあり、なんとなく「別になくても動きそうだな?」と思ったので取りました。今のところ動いています。ただ、いまだに問題の所在が謎なので、ダメなら戻すかも…

 なお --no-sandbox も削除してみましたが、これはissueを踏み抜いてエラーになりました。したがって、これは完全にバグのワークアラウンドです。珍妙な動作なので修正してほしいですね。  

404.spec.js

const {path, browser} = require('../puppeteer').get()

describe('/404', () => {
  let page

  beforeAll(async done => {
    page = await browser.newPage()
    await page.goto(path('/404'))
    done()
  })

  afterAll(async done => {
    await page.close()
    done()
  })

  it('loads without error', async done => {
    const text = await page.evaluate(() => document.body.textContent)
    expect(text).toContain('404')
    done()
  })

  it('contains correct email link', async done => {
    const button = await page.$('[data-prj-role="email-button"]')
    expect(await (await button.getProperty('href')).jsonValue()).toEqual('mailto:tottokotkd@me.com')
    done()
  })
})

 これが具体的なE2Eテストのサンプルです。今回はクリックなどをせず、単純にHTMLの内容を見ています。

 謎の呪文もありませんし、意味不明なコードもないと思います。await page.$('[data-prj-role="email-button"]') という部分は、プロジェクトのルールとして導入された独自のアトリビュートに基づく処理です。このような属性があるとテストが非常に楽になるので導入されていますが、もちろん必須ではありませんから、適宜IDなどで引いても問題ありません。

 どうですか? これならE2Eテストを作れそうじゃありませんか?

.gitlab-ci.yml

variables:
  PUPPETEER_DEPS: gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
stages:
- test
build:
  stage: test
  image: node:latest
  script:
  - apt-get update
  - apt-get install -y $PUPPETEER_DEPS
  - npm install
  - npm run test

 GitLab CIでは node:latest を使ってテストを実行していきます。ただ、素の状態ではChromiumがインストールできません。

 もちろんPuppeteer実行用にイメージを作ってもいいと思うのですが、なんかそこのメンテをするのも面倒だよなということで、とりあえず毎回インストールします。node:latestDebianベースなので、必要なパッケージを apt-get しましょう。

 依存については公式ドキュメントに言及があります。

プッシュするたびE2E!

 ここまでの設定で、GitLabにプッシュするたびE2Eテストが実行され、マージリクエストを出すたびにテストが成功するか表示され、「テストが成功したら自動マージ」機能が使えるようになりました。やったぜフラン!

*1:デザインが崩れてボタンが隠れたりすると悲惨ですね。ただそもそも、そんなクリティカルなページに、ピクセル単位で正確に描写しないと操作不能になるデザインを導入するのはやめておきましょう

プログラミング言語と定番書: Scalaコップ本

 ちょっと必要に迫られて書評。

 一言で言うと、まあこんなもんでしょ、という本です。翻訳の品質を問題視するレビューもありますし、実際のところ、英単語を日本語単語に置き換えただけのような部分があったりするかもしれませんが、「英語の文章を英単語で読むより、英語の文章を日本語単語で読むほうが多分はやい」と考えれば特に問題ないですね ワハハ

 初学者が必要とする情報は揃っていたと思いますし、実装に突っ込んでいく解説スタイルは(たとえ分からなくても、分からない事柄があるという発見として)読む甲斐があります。

 ただひとつ致命的問題があるとすれば、この本を何回読んでもPlay Framework (あるいはSlick) のコードは全然読める気がしないというところでしょうかね。 implicit というキーワードが分かっても、実際のPlayにおける使用方法が宇宙的すぎて、自動生成コードを読んでもまったく意味が分からんと思います。それはもちろんコップ本のせいじゃないんですが、現実としてはやっぱり困りますね。今あえてKotlinじゃなくScalaやる理由なんてPlayとAkkaしか思いつかないし…

 いい本です。ただ高いし厚いし、カロリーがすごい。

プログラミング初心者がこの先生きのこるためには…

 タイトルはどうでもいいです。

わしのおしごと

 現在、以下のテクノロジーでご飯を食べてます。

  • Ruby (Rails: 好きじゃない)
  • ObjC / Swift (iOSアプリ: Swiftだけにして)
  • CoffeeScript (Backbone.js: 限界)
  • Python (データ処理: むり)
  • SQL (DB: 言わずもがな)
  • BashとかNginxとかDockerとかChef (所謂インフラ: たーのしー!)

 これ全部、1つのプロジェクトです。とはいえもちろん、常に全部を並行してやっているわけでも、全コードを一人で書いたわけでもありません。都度都度、チーム内でアサインされた作業をやっていくって感じです。クライアントはバグ調査でちょいちょい覗くくらいで、製造作業はやっていません。

 あからさまに「薄く広く」というやつなので、よりプロい人材が確保できるタイミングならば、その人に仕事が飛んでいきます。そういう人的体制が完備された暁には、もっと責任範囲が限定的になり、少なくともインフラ周りは手放せるはずです(そんな日がいつ来るかは知りません)。

わしの学習歴

 まず、プログラミング自体はわりとダラダラ触っていて、C# Java あたりは書いたことある(しかし仕組みは全く謎だし成果物もゼロ)という状態がスタートです。そこから1年半くらい、ガッツリ勉強した期間があります。

 やった言語を思い出せる順番 (勉強した順番ではない) で書いてみると、まあこんな感じ。

 HTMLはプログラミング言語じゃねーぞみたいな注意書きはまあいいですよね。みんな分かってるはずなんで。

 これら言語ごとに、まずは解説書1冊を読んで、モノによってはフレームワーク1つ弄ったくらい。JavaScript以外はサラーっと構文だけ知った程度。

 例えばScalaだと、通称コップ本を読みつつ Play と Slickをやりました。多分もっとも深入りした部類だと思います。他は、定評のある本を1冊読んで「ふーん全部Scalaのパクリだな」で終わりです。クソ初心者!

 JavaScriptは、今考えるとメチャクチャ変化の激しい時期だったので辛かったわけですが、Reactあたりを主として弄っていました。あとReduxとか、lodashを触って「ふーん全部Scalaのパクリだな」という具合です。ほんまクソ!

いいこと悪いこと

 乱読のメリットは明らかにあります。直ちにやれる仕事が増えるのもそうなんですが、それはどちらかというとオマケです。より重要なこととして「新しいもの」への抵抗が非常に薄くなりましたし、新しいものに対して「あれと似ている」という勘が働くようになりました。IT技術はお互いに影響を与えあっているので、一通りあっちこっち触っておくと「ああ、アレだな」という場面が非常によくあります。予習みたいなもので、将来への投資というやつです。詐欺師の決めゼリフですね!

 悪いことは、まあ明らかだと思いますが、1年以上かかっているわけです。その間ずっと引きこもりニートやって、書籍を買いまくってるんですから、頭がおかしくなりそうですね。私は諸般の事情があって精神的に楽でしたが、普通はやめといた方がいい気がします。アルバイトしながら… とかなら、まだマシかな?

笑っちゃうくらい腹が立った同僚のプログラミング

 ここに書いてあることは全部フィクションですぞ!

なんかコードが減ってる

 ある日の業務中、ふとした瞬間に「あったはずの処理がない」と気づきました。

before

# わしの記憶の片隅にあるコード
result = sugoi_results.first

 元々はこういう感じで、複数の値が帰ってくる処理 sugoi_results があり、その1つ目(あるいはnil)を取り出すという処理でした。ごく平凡なRubyのコードですね。

after

# 最新のコード
result = sugoi_results

 おかしいですね。 result = results という時点で3回くらい発狂しそうです。とにかくおかしいです。

 というのは、このプロジェクトはRSpecで管理しているので、コードが欠損すれば自動的に検出できるはずなんです。それが壊れていないということは、何かおかしなことが起こっています。そこで暗い気持ちでGit履歴を見ます。すると…

def sugoi_results

  results = []

  # ...
  # すごい処理
  # ...

  # ↓ここから増えてたコード

  if results.size == 2
    results.first
  else
    results
  end

  # ↑ここまで増えてたコード
end

 わかりますか? これ。この衝撃。

 実は業務上、冒頭の利用箇所では results が2個の配列になるだろうなーという暗黙の了解があったんです。だから、どうせfirstつけるし、2個のときはあらかじめsugoi_results内部でfirstつけたろ!ということらしいです。

# sugoi_resultsの出力結果は… だいたい配列!

[]
[1]
1  # <- だけどここだけ要素1つ!
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]

何がダメなのか

 すごくつらいですが、一応書きます。

名前から想像できない動作がダメ

 results と複数形なのに、2個の時だけ1個になる(?)なんて絶対わからないです。皆さんのプログラミング言語にそんな哲学的な関数ありますか? 同僚をだまし討ちにするのはよくありませんね。

汎用性ないのがダメ

「オレ、2個の要素が2個とも欲しいなあ」という人はどうすればいいんでしょうか。わざわざ results という関数として独立して公開されているからには、他にも利用者がいるはずです。同僚の顔色を伺いましょう。

意味わかんないのがダメ

 致命的だなあと思うのは「意味わかんない」ところですね。なんでこんな改変したんだ?という思考過程が全く見えない。仮に見えても全力却下ですが、こういうことをやると全般的な信用の問題になってきます。

つまり

 ちゃんとプログラムを英語として読んでから書きましょってこと!

追記

 原題「笑っちゃうくらい腹が立った初心者のプログラミング」ですが、この想像上の同僚が初心者かどうかは定かでないので「笑っちゃうくらい腹が立った同僚のプログラミング」に改題しました。