Firefox 57以降で、ブックマークを新しいタブで開く方法

about:configbrowser.tabs.loadBookmarksInTabstrue に。

dynabook V82/Dを買って、Windows 10 1709をクリーンインストール

dynabook V82/Dを買いました。

元々はLet'snote RZ-5を使っていたんですが、10インチサイズの筐体が仇になり、キーボードが小さくてキー入力ミス率が上がっていたり、長時間キー打つのは疲れたり、打ち合わせ相手にディスプレイを見せるときに小さすぎて見えづらいと言われたり、割と散々な感じだったので、乗り換え先を探していて、候補に残った感じです。

  • いちおう開発もできるスペック
  • クラムシェル型
    • タイプカバーというかキックスタンドなタイプだと、新幹線で使いづらい
  • タブレットモードになる
    • 打ち合わせ相手に見せるときに便利
  • ペン入力ができる、しかもワコム
    • デザイナーと打ち合わせする際に、PDFに書き込みとかを割とやる
    • ペンは標準付属、某Surfaceみたいな別売りとは違う
    • 実際に書いてみたけど、確かにこだわっているだけあって、書きやすい
  • 本体1100g + ACアダプター260g + USB-Cアダプター100gぐらいで、ぎりぎり持ち運びができる許容範囲内
    • 買ってから気が付いたけど、筐体にHDMI/VGA出力がなくて、本体充電と共用のUSB-C/Thunderboltポート1つしか付いてない(MacBookと同じ)ので、プロジェクターに接続するような場合は、USB-C アダプターが必要
    • 専用のUSB-Cアダプターは標準で付属してたのと、実測で98gだったので、まぁ許容範囲内…
    • ACアダプタは260gぐらいあるけど、コンセントとつなぐ側のメガネケーブルが50g強あったので、L字プラグとかに変えると軽くなる
      • ACアダプタはUSB-C PD 45Wなので、純正ではない軽いやつに変更もできるはず
    • バッテリー公称17時間あるので、ケースが合えば本体のみの持ち運びも十分考えられる
  • 割と安い
    • キャンペーン中だったっぽいけど、税込17万円強
    • 同じぐらいのスペックで某Surfaceとかと比べると…

富士通のLIFEBOOK WU2/B1 or WU2/B3ともかなり迷ったんですが、軽さよりもペン入力を取ってdynabook V82/Dにしました。

で、この辺りはマイナスポイントですが、特に国内メーカーPCだと余計なソフトウェアが色々入っているので、買って早々にWindowsクリーンインストールしました。東芝PCのクセを把握せずに作業していたので、1回インストールに失敗して、リカバリーしたりしています…。

  1. TOSHIBA Recovery Media CreatorでUSBリカバリーメディアを作成する
  2. オリジナルドライバー、ソフトウェアをバックアップする
    • C:\TOSAPINS フォルダ全て
    • C:\Program Files\TOSHIBA\TOSAPINS フォルダ全て
  3. Windowsクリーンインストール
  4. バックアップしておいたオリジナルドライバー群を、元と同じパスに配置
  5. C:\TOSAPINS\HTML\SETUP\index_top.html を起動して、必要最低限のドライバーおよびソフトのみをインストール
    • 「ドライバ」タブに表示されているドライバー類全て
    • 「ユーティリティ」タブの以下
      • Intel Chipset SW Installation Utility(チップセットドライバー)
      • TOSHIBA System Settings
      • TOSHIBA eco Utility(いわゆるいたわり充電の設定に必須)
      • 任意:DTS Studio Sound(サウンド設定)
      • 任意:TOSHIBA Service Station(アップデート通知、ハードウェアチェック)
    • その他は必要ならインストール
  6. 4.で展開したオリジナルドライバー群のファイルを削除(当然残しておいても構わない)

これで必要最低限の環境になったはずですが、しばらく様子見を見します。

Haxeのマクロで外部プロセスに処理を移譲する場合の知見

HaxeでVue.jsの開発をするために、.vueファイル(Single File Component)コンパイラをNode.jsで実装して、マクロから呼び出してコード生成するということをやっていた。このエントリーを書いている時点では、コンパイラ自体はまだ「とりあえずコードが生成できている」というレベルなのだが、マクロから外部プロセスを起動する部分については知見が溜まってきたのでメモを残す。

なお、この記事で使用しているHaxeはv3.4.3である。Windows 10でしか動作確認していないが、おそらく他のOSでも挙動は同じになると思われる。

プロセスの起動方法

次のコードが基本形となる。

gist.github.com

ポイントとしては、

  • sys.io.Processを使う。
  • プロセスの起動確認にstdoutを読み込んでいる。これはstdoutが読めるまでブロッキングされる。
  • 外部プロセス側で「起動が完了したことが分かる何らかの値」をstdoutに出力しなければならない。
  • プロセスの起動が失敗するなどの要因で、プロセス側からstdoutがクローズされ、終端を読み込むとEofがthrowされてくる。

この方法だと、外部プロセス側で処理が完了したら、successfulでもなんでもいいので、なんらかの起動完了を表す文字列をstdoutに出力しなければならない。これがないと、readLine()で処理がブロッキングされてしまう。

上記以外に起動確認をする方法としては、リターンコードの確認(process.exitCode())をする方法もある。これもリターンコードが返されるまでブロッキングされる。ただ、後述する外部プロセスを常駐化することを考えると、この方法は利用できない。

また、補足として、process.stdoutの読み込みも当然ながらブロッキングされるため、こちらを監視して正常起動を確認するという方式はとることができない。

その他注意点

  • プロセス起動は比較的オーバーヘッドが大きいので、マクロ内で何度もプロセス起動をすべきではない。
    • 厳密な計測ではないが、手元の環境(Xeon E3-1241 v3)で試した際は、プロセス起動では300msec強かかるのに対して、localhostに対してのHTTPであれば20msec未満でリクエスト処理が完了する。
  • stdoutに改行なしで大きなデータ出力される場合、応答がなくなる?
    • 最初は都度プロセスを起動して、処理結果をstdoutに出力してデータのやり取りをする素朴な実装をしていたのだが、process.stdout.readAll().toString() という実装ではマクロの応答がなくなってしまう問題が発生した。readLine()の場合はデータサイズが大きくても問題は発生していない。
    • 原因未調査だが、プロセスが停止されている(=stdoutが閉じられている)状況でもデータのサイズが大きいと応答がなくなってしまうため、Haxeの標準ライブラリ側に問題がある可能性もあり。
  • stdoutでテキストデータのやり取りをする場合、Windows環境では文字エンコーディングの問題が生じる。

上記のような問題に遭遇し、色々試したのだが、マクロ起動時にコンパイラをサービスプロセスとして起動して、HTTP RPCで都度の処理を移譲する返す形に落ち着いた。

マクロ起動時に処理を実行するには、Haxeコンパイラパラメータで--macro https://haxe.org/manual/macro-initialization.html を指定する。また、Haxelib化している場合は、extraParams https://haxe.org/manual/haxelib-extraParams.html と併用すれば、ライブラリを参照するだけで自動で --macro が付与されるようにできる。

また、sys.io.Processは明示的にclose()もしくはkill()しなくても、マクロ終了時にプロセスを停止してくれるのだが、Haxeコンパイラの入力補完機能を利用している場合は事情が異なってくる。

この場合は、Haxeコンパイラプロセスが常駐状態になるので、入力補完が走るたびにプロセス起動処理が呼び出され、明示的に外部プロセスをクローズしない限りは起動しっぱなしになる。そのため、マクロ側かプロセス側のどちらかで(できれば両側で)多重起動を防止するコードが必要となる。マクロ側で対応する場合は、単純にsingletonとして処理すれば十分である。

追記:Haxeコンパイラの入力補完サービスの場合、Haxeコンパイラプロセスが停止しても起動した外部プロセスは停止されないようだ。これは明示的にハンドリングする方法がないようなので、ある程度お行儀をよくするには、外部プロセス側で一定期間利用されていなければ自動終了させるような仕組みを入れるしかないと思われる。

思い付きでHaxe/JavaScript extern用のabstract型を作ったら幸福度が上がった

HaxeJavaScript用のexternを書く際、EtherType(Union Typeを表現する型)とRest(可変長引数を表現する型)が存在する。

ただ、「EtherTypeは型を2つしか指定できないからなー」という感じの思考から、思い付きでちょっとabstract型を定義してみたら思いのほかexternを書くのが楽になるものができた。見たらわかるレベルのものなので、解説省略。

Kotlin/JavaScriptを試してみた

Kotolinとは

ググって

KotlinでJavaScriptにトランスコンパイル

スタンドアロンコンパイラを使う方法とGradle等のJVMビルドツールを使う方法がある。実際に開発すると仮定すると、インクリメンタルビルドが可能なGradleを使うことになると思われる。

手順としては、

  1. JREをインストール(JVMAndroid向けの開発も行う場合はJDKが必要だが、JavaScript開発の場合はJREで良い)
  2. Gradleをインストール(当然、Gradle Wrapperを使ってもよい)
  3. プロジェクトのスケルトンを作る(build.gradle)

という感じになる。

Hello World

ブラウザでHello Worldが動くところまでを試してみた。IntelliJ IDEA等のIDEを使えばプロジェクトテンプレートがあるかもしれないが、IDEを使わずにテキストエディタのみでやってみた。

ここに記載する手順は、次の環境で確認した。

  • Windows 10 Creators Update
  • JDK 8
  • Kotlin 1.1.4
  • Gradle 4.1

まずビルドファイルを作る。任意の空ディレクトリ内で次のように build.gradle ファイルを作成する。 ext.kotlin_version の部分は、使いたいKotlinのバージョンを指定する。

group 'org.example'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.1.4'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin2js'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}

task assembleWeb(type: Sync) {
    configurations.compile.each { File file ->
        from(zipTree(file.absolutePath), {
            includeEmptyDirs = false
            include { fileTreeElement ->
                def path = fileTreeElement.path
                path.endsWith(".js") && (path.startsWith("META-INF/resources/") || 
                    !path.startsWith("META-INF/"))
            }
        })
    }
    from compileKotlin2Js.destinationDir
    into "${projectDir}/web"

    dependsOn classes
}

assemble.dependsOn assembleWeb

次にMainとなるコードを記述する。

fun main(args: Array<String>) {
    println("Hello World")
}

ビルドは gradle build コマンドを実行する。ビルドに成功すると、 web ディレクトリ以下にJavaScriptが生成される。

ブラウザで実行するために、HTMLファイルを作成する。ここでは作業ディレクトリ直下にHTMLファイルを作成する(webディレクトリ配下にHTMLファイルを配置してしまうと、ビルド時に消えてしまう)。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
<script type="text/javascript" src="web/kotlin.js"></script>
<script type="text/javascript" src="web/kotlin_main.js"></script>
</body>
</html>

作成したHTMLファイルをブラウザで読み込み、開発者ツールのコンソールを確認すると、 Hello World と表示されているはずである。

試してみた感想

実用レベルだが、自分で積極的に使うことはないかなぁというのが正直な感想。

実用となる根拠としては、

  • DOM操作が可能
  • js()関数で生JavaScriptコードの埋め込みが可能
  • Dynamic型でJavaScript側のオブジェクトが操作可能
  • CommonJS, AMD形式のモジュールとしてビルド可能
  • SourceMapが出力可能
  • dead code elimination (DCE) が使える

というあたり。

一方で積極的に使うことはないなぁという根拠としては、

  • 実行時にKotlin Runtime (kotlin.js) に依存しなければならず、非圧縮状態で1.3MB程度ある

というところが個人的に最大のネックになる。kotlin.jsはminifyすれば数百KB程度になると思われるが、どうせRuntimeに依存しないといけないのであれば、私ならScala.jsを選択する。

Scala.jsの場合、

というメリットがあるため、Kotlinの現状では、チームメンバーがKotlinに精通しまくっているということでもない限りは選ぶことはない。

(蛇足として、そもそもHaxeおじさんなのでScala.jsも積極的には選択しないのだけど、自社の人間はScala.js経験値を積み上げているという…

エンジニアは業務時間外に勉強すべきかの話

他社社長が盛り上がってるみたいなんですが、そこの言説だけが広がっていってもアレだなぁと思ったので、単に自分がやってきた経験値とかを書いてみた。銀の弾丸欲しい。

お前誰よ

  • 零細ITシステム会社経営
    • 従業員5人、エンジニア数だと6人(私自身が含まれる)
    • 会社は設立して4年弱
    • 自社サービスを作っているが、今のところの収益構造は受託・SESが100%
  • 10年ぐらい名古屋でコミュニティ活動に関わってきている(ただし、ここ2年ぐらいは忙しすぎて、ほぼ勉強会に行けてない)

色々やってきて至った基本的な考え方

会社という組織を前提とするのであれば「雇用契約」による利害関係で考えるのがシンプル。

  • 会社は利益を上げたい
  • 利益を上げる手段としては、良いエンジニアが必要(それだけではないが、この話題の本筋ではないので割愛)
  • 良いエンジニアを育むには学習が必要

目的は利益であって、エンジニアの勉強は手段。エンジニアが勉強すべきかみたいな話をしたら、そりゃあ、「エンジニアである以上は生涯学習すべき。ただし、プライベートに勉強を強制させるの(無償労働)はあかん」という一般論にしか終着しない。

「かくあるべき」という話にせず、人事評価(賃金)の話にするのが分かりやすくなる。

  • 業務を遂行する上で必要最低限の教育を、賃金を支払ったうえで行わなくてはならない
    • 賃金が発生する以上、学習成果が上がっているかを人事評価に反映させることは正当となる
  • エンジニア個人の学習成果が収益に直結するのであれば賃金に反映し、新たな事業展開に繋がるような場合はプロジェクトメンバーに抜擢するとか、人事的な面で評価する

これを前提に、会社としてはこういう技術が欲しいとか、こういう人材が不足しているということを伝えて、賃金に反映させる。賃金に繋がるとなれば、学習意欲に繋がって資本主義ヤッターとなる。

当然ならない場合もあるが、賃金に反映されない理由としてはわかりやすい。賃金を払うための収益が改善されないだから、割増する理由がない(これも単純化しすぎたらあかんところではあるけど、割愛)。

強制される勉強の成果は上がらない

サラリーマンプログラマーの時代には、こういうことをやっていたがうまくいかなかった。

terurou.hateblo.jp

会社を作ってから、ある程度の選別をして採用を行ってきているが、すべてのケースにおいて合格点となることはない。そのため、社員教育はやるしかないという意識で取り組んできている。ただ、正直なところ、期待する成果は上がっていない。

振り返ってみて、

  • 本人に興味・素質がある領域や教育を行いやすい領域は伸びるが、そうではないところはほぼ伸びない
  • 期待値が高すぎた

という、当たり前だよねという思いに至っている。

「強制される勉強」というのは「本人に興味がない」とか「学習成果が上がってるように感じない」というところから来るものかと思う。

「本人に興味がない」ケースで、人事評価で釣れないのであれば、これ以上は会社からインセンティブを与える手段はない。ただ、本人に聞いても「やる気はあります!」みたいな答えしか当然出てこないので、外部から「やる気」を評価することはできないように思う。

「学習成果が上がってるように感じない」については、学習カリキュラムやゲーミフィケーションなど、改善余地はあると思う。自社に対してだけ考えると、正直私の力不足(時間不足)感が強いので、やり方を見直さないといけないなぁとは考えている領域にはなっている。 ただ、学習成果の面において、「マニュアル化」「自動化」については、成果が見えやすいと思っている。マニュアルには即効性のある内容が書かれている。自動化は厳密には学習ではないのだけど、そもそも覚えなくてもよい状況にすれば、成果はあがる。

まとめる

割と殺伐としたまとめになるが、雇用契約に基づいて、システマティック・資本主義的に考えるのが素直だと思っている。

端的に「エンジニアは業務時間外でも勉強すべきか」という問いに対して、経営者としての立場の回答は「強制は逆効果になるからNG、インセンティブを明示して社の要望を伝えることは必要」となる。

  • 期待しすぎない
    • ある程度の誘導はできるにしても他人が自分の期待通りに動くわけがないし、銀の弾丸もない
    • 高すぎる期待値は精神衛生上良くない結果になりやすい
  • マニュアル化、自動化等の属人性が排除できる部分を対処していくことで底上げを図る
  • プライベートでの勉強の有無に関係なく、会社の利益につながる人間(端的に業務遂行能力の高い人間)を人事的に評価する

社員のやる気を上げる手段は色々提供することは可能だけど、最終的には、やる気とかいうのは運要素だと考えた方が良い。そういう人が採用できるとも限らないし、興味分野はコロコロ変わるし、ゲームやりたくなったりするし、結婚したりとかで状況は常に変わる、人間だもの。

追記: 「期待しすぎない」は「地に足をつけろ」「着実にやれることをやる」みたいなニュアンス。

おまけ

うちは就業規則GitHubに公開しているので、エンジニアの勉強に関連する制度の現時点の評価を書く。

EmployeeHandbook/005_福利厚生規程.md at master · DenkiYagi/EmployeeHandbook · GitHub

  • 書籍購入補助
    • ほぼ使われていない。私がTwitterや社内チャットで面白そうというワードを見ると、だいたい即購入して社の図書として積読している。社員はそれを借りていくのが普通になっているので、形骸化してしまっている。
  • セミナー補助(勉強会参加補助)
    • 年に数回程度しか使われていない。これは社員が近場の勉強会に行く頻度が高いので、単に費用が発生しない等の理由と思われる。
    • 有償セミナー・トレーニング等については今のところ実績がない。行きたい・行かせたいという具体的なところまで決まりきってないだけ。

その他、社員から機械学習ぶん回したいからサーバーと空調をどうにかしてくれみたいな要望が上がってきたが、マニー・設置スペースの両面で都合がつかなかったので、直近は見送りになっている。

Spring Bootで実装した認証付きWeb APIで異なるAPIのレスポンスが返される問題

発生していた問題

  1. 未ログイン状態で、認証が必要なWebAPI-1にリクエス
    • HTTP 401が返される
  2. ログイン
    • HTTP 200 ログイン成功
    • レスポンスボディなし
  3. WebAPI-2(1とは異なるAPI)を呼び出す
    • WebAPI-1のレスポンスが返される
    • クライアントサイドのリクエストパラメータは、WebAPI-2を指定
    • サーバーログを見る限り、1.でリクエストした際のリクエストパラメータを元に処理が行われ、レスポンスが返されている

Spring Bootのバージョン

  • Spring Boot 1.4.2
  • Spring Security 4.1.3

原因

問題が発生するパターンのSpring Securityの設定は概ねこんな感じ。

public class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String URL_PATTERN = "xxx";

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.regexMatcher(URL_PATTERN)
                .formLogin()
                .successHandler((req, res, auth) -> res.setStatus(HttpServletResponse.SC_OK))
                .failureHandler((req, res, auth) -> res.setStatus(HttpServletResponse.SC_UNAUTHORIZED))
                .permitAll();

        http.regexMatcher(URL_PATTERN)
                .exceptionHandling()
                .authenticationEntryPoint(new Http401AuthenticationEntryPoint("Bearer error=\"invalid_request\""));

        http.regexMatcher(URL_PATTERN).csrf().disable();
    }
}

また、APIは下記のような実装を行っていた。

  • 単一URLにPOST
  • HTTP Headerによって処理をルーティング

medium.com

この環境下で、前述の再現手順を行うと、以下のようなことが発生していた。

対応策

Spring Securityの設定でリクエストキャッシュを無効にする。

public class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String URL_PATTERN = "xxx";

    @Override
    protected void configure(final HttpSecurity http) throws Exception {

        http.regexMatcher(URL_PATTERN)
                .requestCache().requestCache(new NullRequestCache());

    }
}

参考URL

http://www.sedooe.com/2016/04/rest-authentication-using-spring-security-and-spring-session/