プロ野球の個人成績をグラフで表示できるサイトを作った

↓作りました。

npb-career-stats-visualizer.firebaseapp.com

たとえば、どうしても強打者の通算ホームラン数を年齢ベースで今すぐ比較したい...という人に便利。

f:id:ymr-39:20181117080248p:plain

通算じゃなくて各年ごとの表示にも切り替えられるし、もちろんホームラン以外の成績についても見られます。

使った技術

久々にサイトを作ったので、色々と勉強になった。

とくに、algoriaという全文検索エンジン(Elasticsearchみたいなやつ)のPaaSは、初めて使ったのだがすごく便利で驚いた。firebaseのデータベースであるfirestoreはLIKEクエリが効かないので、実質的に検索を実装できないのだが、そこをalgoriaで補うことができてよかった。普通のRDBを使っているサービスでも、全文検索だけはalgoriaを使うみたいな用途も十分考えられる気がする。

「詳解システムパフォーマンス」を読んだ

詳解 システム・パフォーマンス

詳解 システム・パフォーマンス

少し前に、仕事でパフォーマンスの問題が発生したので、「詳解システムパフォーマンス」という分厚すぎる本を読んでいた。読みやすい本ではないが、面白い考え方が色々書いてあったので、記憶にあるいくつかを紹介する。

Known-Unknowns

ものごとは以下の3つに分類することができる。計算機のパフォーマンスの問題の例を一緒に挙げる。

  • Known-Knowns:知っていること
    • 例:CPUの使用率をチェックすべきということを知っていて、現在のCPU使用率がわかっているとき
  • Known-Unknowns:知らないことを知っていること
    • 例:スワッピングが発生しているかどうかを調べるべきということを知っているがまだ調べていないとき
  • Unknown-Unknown:知らないことを知らないこと
    • 例:カーネルのslab cacheが動的に増加していくということを知らないのでメモリ圧迫の原因がわからないとき

「詳解システムパフォーマンス」では、パフォーマンスは多く知らないほど知らないことが増える分野であり、これはUnknown-UnknownがKnown-Unknwonsになるということであると述べられていた。Known-Knownsは対処できている状態で、Known-Unknwonsは対処できていないがこれから対処できる状態であるが、Unknown-Unknownには対処のしようがない状態なので、パフォーマンスに限らず、自分が詳しくなりたいと思った領域においては、Unknown-Unknownを減るように勉強していきたい。

ちなみに、上の3つの分類が見るとUnknown-Knownsというものを考えたくなるが...これは「知っていることを知らないこと」なので無意識のうちに前提としている当たり前の知識みたいな感じか?

負荷とアーキテクチャどちらの問題か

パフォーマンスの問題が発生したとき、原因は負荷かアーキテクチャのどちらかにある。

  • アプリがシングルスレッドで動作していることが原因で、一つのCPUの使用率が100%で他のCPUがidle状態だとすると、これはアーキテクチャの問題
    • アプリを書き直すことで改善が可能
  • 全てのCPU使用率が100%の場合は、アプリの改善ではどうしようもなく、単に負荷が大きすぎるという問題
    • CPUの増設や処理の分散で解決できる

パフォーマンスを客観的な問題にするには

パフォーマンスは主観的な問題である。何らかのシステムのパフォーマンスが良いか悪いかは、以下によって左右される。

  • システムの用途
  • ユーザの期待
  • 開発者の期待

パフォーマンスを客観的な問題にするためには、例えば目標の平均応答時間を設定するなど、明確な基準を設ければ良い。

CPUバウンドな処理とIOバウンドな処理

  • CPUバウンド

    • 科学計算などCPUに負担がかかる処理
    • ほとんどの時間ユーザ空間で動く
    • スループットが大事
  • IOバウンド

    • Webサーバなど主にIOを行う処理
    • カーネルの時間を使う
    • レイテンシが大事
  • スケジューラはCPUバウンドの処理を見分けて優先度を下げ、IOバウンドの処理(レイテンシが小さいことが重要)を早く終わらせるようにする

    • プロセスがCPUバウンドかどうかは直近のCPU時間と経過時間を比較すればわかる

たのしいJavaScriptの仕様

最近久しぶりにReact.jsを書いているんですが、細かい javascript の文法とかを忘れてしまって、↓の本を読んでいる。

最近のjavascriptは色々と良く

Rediscovering JavaScript: Master ES6, ES7, and ES8

Rediscovering JavaScript: Master ES6, ES7, and ES8

なったので良いところとその使い方を詳しく紹介する、という趣旨の本だと思うが、最初の1/4くらいでは、前提として昔のjsのどこがだめだったかというのが述べられている。プログラミング言語のだめな仕様、最初はいい加減にしろと思って終わるのだが、詳しくなってくるとだめな仕様に詳しくなるとき専用の気持ちよさみたいなものがある気がする。

かなり昔、たしか↓の本

JavaScript 第6版

JavaScript 第6版

javascriptを勉強した時に知ったBooleanのラッパー型の悲しい仕様。

const FALSE = new Boolean(false)
console.log(new Boolean(FALSE)) // [Boolean: true]

↑のような状況に実際になることはまずないとは思うが、悲しすぎる...。

次にこの本にも載っていたvarの話。最近jsを勉強している人ならvarは使わない方が良いというのは常識になっていると思うが、varの何がだめかというと、以下の2点にまとめられるらしい。

  • 変数の再宣言が許されている
  • ブロックスコープではなく関数スコープ+hoistingという仕様のため、varで変数宣言した変数のスコープがやっかいになる

スコープの話はめんどくさいので置いておくとして、変数の再宣言の話は以下のようなコードが許されるということである。

var a = 1
var a = 2 // エラーにならない!

人類は以下の2通りに分類できる

  1. ↑のコードの2行目で、1行目とは違う変数に2を代入したかった人
  2. ↑のコードの2行目で、1行目と同じ変数に2を代入したかった人

1の人は、例えばvar aa = 1と書きたかったはずなので、↑のコードは絶対にエラーになってほしいはずである。2の人は、a = 2と書きたかったはずなので、↑のコードはエラーになってくれた方がありがたいはずである。つまり、全人類が↑のコードの2行目でエラーが出てほしいと願っているのにエラーが出ないということになる。全人類...。

このvarの仕様は、最近のjsではconstletによって解決されているし、昔のjsの他のだめな点も色々と改善されている。jsは最低でもあと5年くらいは重要なプログラミング言語であり続けると思うので、詳しくなっておきたい。

React.jsに再入門した

プログラミングのリハビリに、昔に少し書いたことがあるreact.jsを再び勉強している。人間はプログラミングを覚えるととりあえずtodoアプリを書いてしまう習性があるので、todoアプリを作った。

APIサーバをGoで書いたやつ

reactでtodoアプリを書くのは手癖でなんとかなったので、サーバ側で新しいことをやろうと思ってgoでAPIを書いてみた。gormというORマッパーを使ってみたが、railsみたいなことができて非常に便利。

firebase/firestoreを使ったやつ

firebaseも昔ちょっとだけ勉強したことがあったので再入門したくてやってみた。reactのコードは上のやつとだいたい同じ。firebase、認証が本当に便利なので、簡単なアプリならいちいちサーバを書かなくてもfirebaseで一瞬でAPI+DB+認証を出来るようになっておきたい。

firebase hostingを使うと労力0でデプロイが出来るのもありがたすぎる。

firestoreを使うと、データベースで更新が走った際にその変更をローカルに自動的に反映させることが簡単にできて便利なんだけど、当然コードから更新のタイミングが制御できないのでステートの管理を明示的にやりたいreduxとの相性がいまいちだと感じた。これは絶対に解決してる人がいるはずと思ったらredux-firestoreというライブラリがあるらしい。redux-firestoreを使ったtutorialも発見したので、次はこれをやってみたい。

ピカールの思い出

中学校の頃、自分で0からスプーンを手作りしよう、みたいな授業を受けた。一枚の金属の板を渡されて、まずは糸鋸か何かでしゃもじ型に切り出し、金槌で叩いてスプーンのすくう部分の凹みを作る。これでスプーンの形状はできるのだが、表面が汚いのでその後にやすりで磨くという工程があった。このやすりがけがめちゃくちゃ大変で、最初は金属のやすりで角をとる程度に磨き、そこから粗めの紙やすり→中くらいの粗さの紙やすり→細かい紙やすり...みたいな感じで永遠にやすり続けて表面を綺麗にしていく。一生分とも思えるやすりがけの結果、みんなのスプーンの表面の綺麗さにはそれなりの差が出ていたと思う。やはり、やすりがけの丁寧さとか回数で綺麗さが変わってくるんだな...と思い、俺のスプーンは平均よりは綺麗だったので満足した記憶がある。

ここで突然、「ピカール」という謎の薬品が登場した。先生が、やすりがけしたスプーンをピカールをつけて磨こうみたいなことを言い出す。そうですか...と思いながらピカールでスプーンを磨くと、表面がめちゃくちゃに綺麗になる。ピカール最高か?みたいなことを周りの人と話しながら磨いていると、どんどんみんなのスプーンがぴかぴかになっていく。そして...驚いたことに、ピカール前は差があったみんなのスプーンの綺麗さが、ピカールによって完全に一様になっているのである。まじで、ピカールがあればそれまでのやすりの努力とか関係ないやないか...と思って悲しみにくれた。人生にもピカールがあってほしい。

ピカール 金属磨き 300g

ピカール 金属磨き 300g

ANTLR4 を使ってスクリプト言語を作った

以前の記事で、javascript を使って非常に簡単なスクリプト言語を作った。スクリプト言語を解釈するインタプリタは、大ざっぱに言うと、コードの文字列をトークン列に変換する lexer、トークン列を抽象構文木に変換する parser、抽象構文木から値を評価する evaluator という3つのコンポーネントに分けられる。前回の記事では、勉強のために lexer/parser/evaluator を自分で1から書いたのだが、これはなかなか手間なので、例えば何か簡単に自作DSLを作りたいと思った時のベストの方法ではないと思う。もっと簡単な方法に、なんらかの方法で作りたいスクリプト言語の文法を定義しておき、そこから lexer/parser を自動的に生成してくれるパーサジェネレータというソフトウェアを使うというのがあるらしい。今回は、パーサジェネレータの一つである ANTLR4 を使って、前回よりも複雑なスクリプト言語を作った。具体的には、

  • 変数割り当てができる
  • 関数宣言と呼び出しができる
  • if文とwhile文が使える
  • ブロックスコープ

という普通の言語で、例えば以下のようなコードが動く。

fn add = (a, b) => {
    let c = a + b;
    c;
}

let x = 1;
let y = 2;
let z = 3;

if (add(x, y) == z) {
    print "ok!!!";
}

print "count" + "down" + "...";
while (z >= 0) {
    print z;
    z = z - 1;
}

let s = "global";
print "in global scope: " + s;
if (true) {
    let s = "block";
    print "in block scope: " + s;
}
print "in global scope again: " + s;

あえて言語を自作する割にはまったく面白みがない仕様だが...。言語の名前は、エラー処理などをほとんどしておらず、プログラムが正しく動作するかがすべて使用者の良心にかかっているため、conscience とした。コードは全て github に置いてある。

今回重要な役割を果たす ANTLR4 の使い方については以下の本を読んで勉強した。

The Definitive ANTLR 4 Reference

The Definitive ANTLR 4 Reference

The Definitive ANTLR 4 Reference という一ミリも面白くなさそうなタイトルからの、DSL やトランスパイラの作り方や考え方の基本が例とともにわかる非常にいい本です。以下の本文では ANTLR4 の使い方については特に説明しないので、興味がある人はこの本を読むと良いと思う。ただ、一般的なスクリプト言語の作り方の例は載っていなかったので、今回は以下のコードも参考にした。

github.com

ANTLR4 の準備と文法定義

まずは、公式のページに載っている手順で ANTLR4 をインストールする。ダウンロードしてCLASSPATHを通せば OK。

続いて、作りたい言語の文法の定義ファイルを作る。以下のようになった。

grammar Conscience;

file : block ;

block : stat+ ;

stat : assignStat
     | mutateStat
     | printStat
     | ifStat
     | whileStat
     | fnDefStat
     | exprStat
     ;
assignStat : 'let' ID '=' expr ';' ;
mutateStat : ID '=' expr ';' ;
printStat : 'print' expr ';' ;
ifStat : 'if' expr '{' block '}' ;
whileStat : 'while' expr '{' block '}' ;
fnDefStat : 'fn' ID '=' '(' ids? ')' '=>' '{' block '}' ;
exprStat : expr ';' ;

ids : ID (',' ID)* ;
params : expr (',' expr)* ;
expr : expr op=('*'|'/'|'%') expr             # MulDivModExpr
     | expr op=('+'|'-') expr                 # AddSubExpr
     | expr op=('>'|'<'|'<='|'>='|'==') expr  # CompareExpr
     | ID '(' params? ')'                     # FnCallExpr
     | ID                                     # IdExpr
     | INTEGER                                # IntegerExpr
     | STRING                                 # StringExpr
     | BOOLEAN                                # BooleanExpr
     | '(' expr ')'                           # ParenExpr
     ;


BOOLEAN : ('true'|'false') ;
INTEGER : [0-9]+ ;
STRING : '"' .*? '"' ;
ID : [a-zA-Z]+ ;
WS : [ \t\r\n] -> skip ;

大変なので内容の説明はしないが、なんとなく雰囲気を感じ取っていただきたい。そこそこ直感的に文法が定義できてありがたい。

評価器の実装

上記の文法定義ファイルを ANTLR に渡すと、自動的にスクリプト言語ソースコードから抽象構文木を生成してくれる。人間がやらなければいけないのは、抽象構文木の上を渡り歩いて値を評価したり変数や関数を定義したりする部分のみである。具体的に言うと、インタプリタmain関数は以下のようになるのだが(雰囲気を感じ取っていただ期待2)、

public static void main(String[] args) throws IOException {
    String fileName = args[0];

    CharStream input = CharStreams.fromFileName(fileName);
    ConscienceLexer lexer = new ConscienceLexer(input);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    ConscienceParser parser = new ConscienceParser(tokens);

    ParseTree tree = parser.file();
    EvalVisitor visitor = new EvalVisitor(new Scope());
    visitor.visit(tree);
}

このうち、やらなければいけないのは ANTLR が作ってくれたParseTreeの上を歩くEvalVisitorの実装だけである。以下に、EvalVisitorのコードのうちの一部のみを載せる。

    // EvalVisitor.java

    @Override
    public ConValue visitAssignStat(ConscienceParser.AssignStatContext ctx) {
        String var = ctx.ID().getText();
        ConValue value = visit(ctx.expr());
        scope.assign(var, value);

        return ConValue.VOID;
    }

    @Override
    public ConValue visitPrintStat(ConscienceParser.PrintStatContext ctx) {
        ConValue value = visit(ctx.expr());
        System.out.println(value);

        return ConValue.VOID;
    }

    @Override
    public ConValue visitMulDivModExpr(ConscienceParser.MulDivModExprContext ctx) {
        Integer left = visit(ctx.expr(0)).asInteger();
        Integer right = visit(ctx.expr(1)).asInteger();

        switch (ctx.op.getText()) {
            case "*":
                return new ConValue(left * right, Type.INTEGER);
            case "/":
                return new ConValue(left / right, Type.INTEGER);
            case "%":
                return new ConValue(left % right, Type.INTEGER);
        }

        throw new RuntimeException("undefined operator: " + ctx.op.getText());
    }

なんとなく雰囲気を感じ取ってもらえればありがたいが(3)、とりあえず抽象構文木のあるノードを訪れたとき、

  • どういう処理をして
  • 次にどのノードを訪れるか

を指定すれば OK。これだけで、if文や関数宣言/呼び出しができるある程度まともなスクリプト言語が書ける。

まとめ

ANTLR4 を使うと非常に簡単にスクリプト言語が作れることがわかった。ALNTR4の詳しい使い方については、The Definitive ANTLR4 Reference が非常にわかりやすいので読んでほしい。今回のような一般的かつ趣味的なスクリプト言語を作ることには(面白いということ以外に)あまり意義はないと思うが、ふとしたときに DSL とか言語変換器を作れると役立つこともなくはなさそうな気がする。とりあえず、インタプリタの基本的な仕組みがわかって楽しかったので勉強してよかった。

「Team Geek」を読んだ

読みました。

Team Geek ―Googleのギークたちはいかにしてチームを作るのか

Team Geek ―Googleのギークたちはいかにしてチームを作るのか

チームで仕事をするときにはHRT(謙虚・尊敬・信頼)の文化を大事にしようという本。特にパフォーマンスの低い人やチームにとって有害な振る舞いな人に対処するときほどHRTが大切らしい。具体的に有害な振る舞いへの対処の例が書いてあってよかった。その他とくに面白かった点は、

  • 早い段階で失敗をすることが大事
  • 同期コミュニケーションの人数を減らし、非同期コミュニケーションの人数を増やすことがコミュニケーションの原則
  • 技術的負債の解消など防御的な仕事はどれだけ大切でも全体の仕事量の1/2以下にする

以下読書メモ。

1章 天才プログラマの神話

  • ソフトウェア開発は、天才一人による仕事ではなく、チームスポーツ
    • 私たちは天才ではないし、仮に天才であってもミスをする
    • Linusの功績はLinuxのコンセプトを実証したことではなく、大勢のエンジニアをうまくリードして開発を進めたこと
  • HRT(謙虚・尊敬・信頼)を大事にしよう
    • あらゆる人間関係の衝突はHRTの欠如によるもの
    • エゴをなくし謙虚に振る舞う
    • 攻撃的な非難でなく、尊敬を含んだ建設的な批判や謙虚な質問をする
    • 早い段階で失敗をする
    • 影響を受け、影響を与える

2章 素晴しいチーム文化を作る

  • チームの文化とは、チームが共有する経験・価値・目標のこと
  • チームの文化の基礎を作るのは初期のメンバー
  • 強固な文化を作ると「自己選択的」になる
    • 良いチームは良く、悪いチームは悪くなっていく
  • コミュニケーションの原則は、同期コミュニケーションの人数を減らし、非同期コミュニケーションの人数を増やすこと
    • メーリスやチャットを活用して検索できる形でログを残す
    • ミーティングの人数を減らす。ミーティングでメールを読んでいる人は、ミーティングに参加しなくても良い人
  • チームにとってミッションステートメントが重要。エンジニアチームのミッションステートメントであれば、「チームの方向性を定義して、プロダクトのスコープを制限する」だけで良い
    • ex) GWTのミッションは、開発者が既存のjavaツールを使ってajaxを構築できるようにすることで、ユーザーのウェブ体験を劇的に改善することである

3章 船にはキャプテンが必要

  • マネージャーになると自分の仕事を定量化するのが難しくなる。しかし、チームの成果を自分の手柄にする必要はない。チームの幸せと生産性がマネジメントの仕事
  • パフォーマンスが低い人は無視せずに、成長させるかチームから退席させる。何れにしても、まずはHRTを前提とした一時的なマイクロマネジメントが必要になる。期限を設定して、達成して欲しい目標を決める。うまくいかなければ、場合によっては中止する
  • マイクロマネジメントをやめる。リーダーはチームの合意形成や方向性の決定を支援することで、目標の達成方法はエンジニアが決定する
  • エンジニアがリーダーに相談するのは、リーダーが問題を解決を求めているのではなく、エンジニア自身が問題解決するのを手伝って欲しいから。そのために問題の調査/整理を行う
  • リーダーは障害を取り除くための答えを知る必要はなく、取り除ける人を知るだけで良い

4章 有害な人に対処する

  • 有害な人ではなく、以下のような有害な振る舞いを排除する
    • 無駄な質問や間違った発言でチームの時間/エネルギーを消費する
    • 合意の決定を受け入れない、異なる視点の意見に耳を傾けない
    • 完璧主義
  • 悪意を持って有害な振る舞いをする人間は無視する
  • 悪意を持たずに有害な振る舞いをしてしまっている人間にはHRTを持って対応し、場合によっては退場してもらう
  • 技術的に貢献できる人は交換可能だが、HRTの文化はかけがえのないもの

5章 組織的操作の技法

  • 悪い習慣をやめることはできない。良い習慣と置き換えなければいけない
  • マネージャーとの約束は小さくし、届けるものは大きくする。できないことを約束しない
  • 技術的負債の解消などの防御的な仕事は評価されないので、どれだけ必要でも時間をかけすぎない
  • 忙しい人にメールでお願いをするには「3つの箇条書きと行動要請」を利用する
    • 問題について説明する最大3つの箇条書きと1つだけの行動要請