TECH PLAY

株式会社モバイルファクトリー

株式会社モバイルファクトリー の技術ブログ

223

この記事は Perl Advent Calendar 2020 と モバイルファクトリー Advent Calendar 2020 2日目の記事です。 こんにちは。 id:kfly8 です。 今年、会社の勉強会の時間を使ってちょこちょこと、 perl-users.jp を静的サイト化しました。 せっかくなので、2008年から2010年の記事を読み返したのですが、勉強になりました。 温故知新というと大仰ですが、昔から今にかけての変化も触れながら、 個人的に面白かった記事をいくつか紹介してみたいと思います。 1. 元々の文字列を破壊しないように置換する方法 最も素朴な方法は、別の文字列に退避する方法だと思いますが、2行になって面倒です。 my $old = "dog" ; my $new = $old ; $new =~ s/ dog / cat / ; 2008/12/12 で紹介されていたのが、 List::MoreUtils#apply を使う方法です。 my $new = apply { s/ dog / cat / } $old ; 確かに、ワンステートメントで意図がはっきりして読みやすいです。 さらに、 2008/12/12 で、代入文をカッコにくくる方法が紹介されています。シンプル。 ( my $new = $old ) =~ s/ dog / cat / ; 今だと、perlのバージョンが5.14 以降であれば、replace optionを使って、よりスッキリ書けますね。 my $new = $old =~ s/ dog / cat /r ; 2. SQLiteでdbをオンメモリにする SQLiteを利用してテストを書く時、dbのファイルを管理するのが面倒、オンメモリで処理してしまえば、ファイルが意図せず消えなかった時のことを考えなくて便利という話です。 2008/12/10 時点では、 DBI->connect('dbi:SQLite:','',''); のように、dbnameを指定しない方法が紹介されていましたが、 今だと、 DBI->connect('dbi:SQLite:dbname=:memory:','','') といった具合に :memory: を指定して明示的に書けます。 3. Errnoでエラー処理する 「ディレクトリが存在しなければ作成」という処理を素朴に書けば、ディレクトリの有無確認( -d )して作成する( mkpath )すると思います。 ただ、この書き方だと、 -d のあと mkpath を呼び出すまでの束の間に、別の処理がディレクトリを作成してしまう可能性はあります。 if (! -d $dir ) { mkdir $dir or die "failed to create dir: $dir : $! " ; } この可能性を考慮するなら、まずディレクトリの作成を試みて、返されたエラー変数( $! )とErrnoの定数と数値比較するという話が、 2009/12/05 の記事で紹介されています。 unless ( mkdir $dir or $! == Errno::EEXIST) { die "failed to create dir: $dir : $! " ; } こういった低レベルな内容だと、10年経っても原理は変わらないですね。 4. DBIx::TransactionManagerとSQL::Maker 2010/12/10 にDBIx::TransactionManager、 2010/12/13 にSQL::Makerの紹介がされています。この2つのモジュールは、今でも使われているORMの Aniki や DBIx::Sunny の内部で利用されています。 この2つのモジュールに関して、DBIx::Skinnyからパーツを分解して出来ていることが、 2010/12/05 の記事でわかります。歴史的経緯がわかって面白いです。 5. 記号プログラミングアドベントカレンダー Acme::EyeDrops便利 記号プログラミングのネタだけで、25日走りきっている アドベントカレンダー がありました。「どこへ向かっているの?」というはてぶコメントがありましたが、10年経っても色褪せないコメントだと思いました。 記事は、1日目BrainF*ck、2日目PHP、3日目JavaScript、4日目Ruby、5日目Perl・・・と続きます。 おわりに 紹介できた内容はほんの一部ですが、先人の記事は面白かったです。 過去の記事を読み返してみるのもたまには良いかもしれないと思いました。 明日の記事は Perl Advent Calendarは、hkobaさん、モバイルファクトリーAdvent Calendarは、 id:momoyagi さんです!
アバター
この記事は モバイルファクトリー Advent Calendar 2020 1日目 の記事です。 こんにちは、ブロックチェーンチームでソフトウェアエンジニアをしている id:odan3240 です。最近会社で使っていた椅子と同じモデルの椅子を購入して QoL が上がっています。 認証に Cookie を使用している API を叩いてサーバサイドレンダリング (以下 SSR) する場合、SSR 時にも認証を通しておく必要があります。 Nuxt.js において、API リクエストを行うライブラリとして nuxt/axios を使用していると SSR 時に HTTP ヘッダーを proxy してくれるため、これを達成できます。この記事ではこれについて解説します。 Cookie を伝搬させる必要性 セッション情報を Cookie で管理している API サーバにリクエストを投げるときには HTTP ヘッダーに Cookie を設定する必要があります。ブラウザからこのリクエストを投げる場合は自動的にヘッダーに Cookie が付与されます。 一方で、Nuxt.js で SSR している場合、ブラウザと API サーバの間に SSR 用の Node.js のサーバが置かれることになります。以下の画像のようにブラウザから SSR 用の Node.js サーバの間だけではなく、SSR 用の Node.js のサーバと API サーバの間でも HTTP リクエストに Cookie を付与する必要があります。 nuxt/axios nuxt/axios は、axios をセキュアかつ簡単に扱うための Nuxt.js のモジュールです。Nuxt.js のコアチームのメンバーが開発しています。 nuxt/axios には proxyHeaders というオプションがあります。このオプションはデフォルトでは true で、 SSR 時にブラウザからの HTTP ヘッダーを axios を使ったリクエストのヘッダーとして proxy してくれます。 実装は、 process.server が truly かつ HTTP リクエストのヘッダーが存在する場合に、 req.headers を axios の headers.common に設定しています。 実装箇所: https://github.com/nuxt-community/axios-module/blob/v5.12.2/lib/plugin.js#L205-L214 サンプルコード 最後にサンプルコードを紹介しておきます。 github.com 明日の記事は id:kfly8 さんです!
アバター
要約 git内部のデータ格納に関するサブコマンド、 git cat-file と git hash-object を自分でPerlで実装しgit内のデータの保存方法について知る 目次 イントロダクション git内部のデータの確認 perl実装の紹介 実装 github.com イントロダクション 自己紹介 駅メモにて主にバックエンドを担当している id:toricor です。 仕事ではPerl実装のサーバ周りを触ることが多いです。 仕事以外では、以前Perlで 簡単なJVMを書きました 。 毎日使うソフトウェアといえば gitは欠かせないですよね。 でも毎日の仕事で生成されるあの膨大なファイルやコミットを、内部でどのように記録し管理しているか気になりませんか。私は気になりました。 本家の説明によると Git は内容アドレスファイルシステムです。 素晴らしい。 …で、それはどういう意味なのでしょう? それは、Gitのコアの部分はシンプルなキー・バリュー型データストアである、という意味です。 Git - Gitオブジェクト 少しわかりにくいですね Gitを自分で書いたら理解できるかも!? "Write yourself a Git!" https://wyag.thb.lt/ gitの自力実装の方法を紹介してくれています。 数百行程度のPython実装でgitのいろいろなサブコマンドが実装できます。 以前Python実装のままチーム内で紹介したのですが、弊社はPerlをよく使う会社なので今回Perlで書き直しました(ただし一部のみ)。 以降はデータの格納という点に注目してgitを見ていきたいと思います。 内部のデータの確認 .git のどこにデータが入るのだろうか gitを使う場合に必要なデータ(ブランチ、コミット、など)は .git に全て格納されています。 適当なGitプロジェクトをつくり、コミットをした場合に .git の中身がどのように変わっていくかをまずは確認します。 git init 直後 % tree .git .git ├── HEAD ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │ (省略) │   └── update.sample ├── info │   └── exclude ├── objects │   ├── info │   └── pack └── refs ├── heads └── tags 8 directories, 16 files ファイル作成 & git add % echo 'print "Hello Git!\n";' > hello.pl % git add . % tree .git .git ├── HEAD ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │ (省略) │   └── update.sample ├── index ├── info │   └── exclude ├── objects │   ├── 36 │   │   └── 9b01f4c6a00c39f0362e3f6c9648c2dc178b47 <-- ファイルが増えたぞ?? │   ├── info │   └── pack └── refs ├── heads └── tags 9 directories, 18 files git add . を実行後、 .git/objects/ 以下に1つファイルが増えました。 git commit % git commit -m 'add an example file' [master (root-commit) dc8cc4a] add an example file 1 file changed, 2 insertions(+) create mode 100644 hello.pl % tree .git .git ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │ (省略) │   └── update.sample ├── index ├── info │   └── exclude ├── logs │   ├── HEAD │   └── refs │   └── heads │   └── master ├── objects │   ├── 36 │   │   └── 9b01f4c6a00c39f0362e3f6c9648c2dc178b47 │   ├── dc │   │   └── 8cc4a05ebbb7771a46e61bfe6dcfbefb905eaa <-- これと │   ├── dd │   │   └── b362c0207a67dc4d63684794a50fcdaf69a155 <-- このファイルがcommitしたら増えた! │   ├── info │   └── pack └── refs ├── heads │   └── master └── tags 14 directories, 24 files コミット操作により、更に2つのファイルが生成されました。 これらは Gitオブジェクト と呼ばれます。 生成されたファイル(Gitオブジェクト)の中身を見てみる % less .git/objects/36/9b01f4c6a00c39f0362e3f6c9648c2dc178b47 ".git/objects/36/9b01f4c6a00c39f0362e3f6c9648c2dc178b47" may be a binary file. See it anyway? バイナリファイルでした % xxd .git/objects/36/9b01f4c6a00c39f0362e3f6c9648c2dc178b47 00000000: 7801 4bca c94f 5230 3264 2828 cacc 2b51 x.K..OR02d((..+Q 00000010: 50f2 48cd c9c9 5770 cf2c 51e4 52b2 e602 P.H...Wp.,Q.R... 00000020: 0087 f508 5c ちょっとこれでは内容がよくわからないですね。 Gitオブジェクトのフォーマットについて 実はコミットや特定の時点でのファイルを表すGitオブジェクトは、ハッシュ値に基づいたファイルとなります。 それぞれオブジェクト作成時に40文字からなるSHA-1ハッシュが計算され最初の2文字をディレクトリ、残り38文字がファイル名のファイルとなります。 例: .git/objects/36/9b01f4c6a00c39f0362e3f6c9648c2dc178b47 ハッシュ値が 369b01f4c6a00c39f0362e3f6c9648c2dc178b47 のとき 36 というディレクトリに 9b01f4c6a00c39f0362e3f6c9648c2dc178b47 というファイルが作られます。 Q. Gitオブジェクトの中身はどういう変換を受けている? (普通のファイル: blob(後述)の場合) 普通のファイルの内容にヘッダーをつけて、zlibを使い圧縮されています ヘッダー: オブジェクトタイプ + 空白 + データの長さ + ヌルバイト Gitオブジェクトを読み取るコマンド: git cat-file Gitオブジェクトを扱うためのgitのサブコマンドを紹介しておきます。 読み取り専用のサブコマンドがあります。 git-cat-file - Provide content or type and size information for repository objects では実際に今回生成された3つのGitオブジェクトの内容を見てみます。 -t オプションでGitオブジェクトのタイプを表示します。 % git cat-file -t 369b01f4c6a00c39f0362e3f6c9648c2dc178b47 blob % git cat-file -t dc8cc4a05ebbb7771a46e61bfe6dcfbefb905eaa commit % git cat-file -t ddb362c0207a67dc4d63684794a50fcdaf69a155 tree -p オプションでもう少し詳しく見てみましょう # blob % git cat-file -p 369b01f4c6a00c39f0362e3f6c9648c2dc178b47 print "Hello Git! "; # commit % git cat-file -p dc8cc4a05ebbb7771a46e61bfe6dcfbefb905eaa tree ddb362c0207a67dc4d63684794a50fcdaf69a155 author toricor <toriyabe@mfac.jp> 1597738778 +0900 committer toricor <toriyabe@mfac.jp> 1597738778 +0900 add an example file # tree % git cat-file -p ddb362c0207a67dc4d63684794a50fcdaf69a155 100644 blob 369b01f4c6a00c39f0362e3f6c9648c2dc178b47 hello.pl このようにGitオブジェクトは上に示したblob, commit, treeタイプと他にtagタイプの4つから成り立っています。 Gitオブジェクトを書き込むコマンド: git hash-object git-hash-object - Compute object ID and optionally creates a blob from a file git cat-file と対になるコマンドになります。Gitオブジェクトにしたいファイルのハッシュ値を計算して( -w 付きなら) .git/objects/ 以下にGitオブジェクトファイルを作成します。 Gitを実装しよう git cat-file と git hash-object を理解できればgit内部のデータの格納について基本は理解したといえそうです。 参考にしたサイト はPython実装ですが、 Perlでこれらのコマンドを実装してみました。 実装のポイント いくつかポイントを絞って実装を紹介します。 例1: SHA-1のハッシュ値を求める Digest::SHA1 モジュールの sha1_hex が使えます。 use Digest::SHA1 qw/sha1_hex/ ; my $data = ...; # 適当なデータ my $len = length ( $data ); # データの長さ # build a header my $fmt = 'blob' ; # commit/blob/tree/tag my $result = " $fmt $len \x00 $data " ; # e.g. 369b01f4c6a00c39f0362e3f6c9648c2dc178b47 my $digest = sha1_hex( $result ); 例2: Gitオブジェクトを解凍してデータを確認する バイナリファイルを読むのでopenの第2引数に"<:raw"を指定します binmode($fh) でもいいです 圧縮・解凍には Compress::Zlibモジュール が使えます # simple_uncompress.pl # 適当なPerlプロジェクトを作成し簡単なスクリプトを用意しました use Compress::Zlib qw// ; sub dump_commit { my ( $path ) = @_ ; open ( my $fh , "<:raw" , $path ) or die $! ; my $bufs ; while ( read $fh , my $buf , 1024 ) { $bufs .= $buf ; } close $fh ; my $uncompressed = Compress::Zlib::uncompress( $bufs ); print $uncompressed ; } # git logした結果から適当なものを選んでcommitタイプのGitオブジェクトを見てみる my $path = '.git/objects/8f/083b8b230e833e1ea7e679fac265ca6a422c02' ; dump_commit( $path ); % carton exec -- perl simple_uncompress.pl commit 220tree 6e7c0d7b065c5bd3708c8aeb7e85c81ccffbb01d parent 97a587d82878afd2d81d56b30d3dd9a910316b8a author toricor <toriyabe@mfac.jp> 1596040311 +0900 committer toricor <toriyabe@mfac.jp> 1596040311 +0900 refactor HashObject ヘッダ+コミットからなる表示が見えました! Gitを動かそう では先の実装ポイントを踏まえて実装したperlのスクリプトを実行してみましょう ハッシュ値 => 内容表示 ( cat-file ) コミットを調べます % carton exec -- perl main.pl cat-file -t 8f083b8b230e833e1ea7e679fac265ca6a422c02 commit % carton exec -- perl main.pl cat-file -p 8f083b8b230e833e1ea7e679fac265ca6a422c02 tree 6e7c0d7b065c5bd3708c8aeb7e85c81ccffbb01d parent 97a587d82878afd2d81d56b30d3dd9a910316b8a author toricor <toriyabe@mfac.jp> 1596040311 +0900 committer toricor <toriyabe@mfac.jp> 1596040311 +0900 refactor HashObject コミットの中身が表示できました ファイル => Gitオブジェクト 適当なファイルのハッシュ値を求めます % echo 'print 123;' > 123.pl % carton exec -- perl main.pl hash-object -t blob 123.pl 4a8a8bc47b318a7ca83c5f739440e969fef59325 ハッシュ値を求めるだけではなくGitオブジェクトも作ってみます(ファイルは blob タイプです) % carton exec -- perl main.pl hash-object -w 123.pl 4a8a8bc47b318a7ca83c5f739440e969fef59325 % ls .git/objects/4a/8a8bc47b318a7ca83c5f739440e969fef59325 8a8bc47b318a7ca83c5f739440e969fef59325 Gitオブジェクトが.git/objects以下に作られました % git cat-file -t 4a8a8bc47b318a7ca83c5f739440e969fef59325 blob % git cat-file -p 4a8a8bc47b318a7ca83c5f739440e969fef59325 print 123; 本物の git cat-file コマンドからもきちんとGitオブジェクトの内容が認識されているようですね! まとめ 意外とgitは自分で作れます! 参考文献 Write yourself a Git! Git 第10章 Gitの内側
アバター
こんにちは。 id:kfly8 です。普段はヒューマンリレーションズ部でエンジニア組織開発をしています。 先日、ISUCON *1 でPerlの参考実装をやらせてもらったのですが、とても楽しかったです!貴重な機会をありがとうございました。また、"あのISUCON"の運営裏側を見れて、苦労、凄さなど身近な所で感じることができました。 微力ながら協力できて嬉しかったです。 この記事では、Goの参考実装からPerlへの移植をして考えたことを書きたいと思います。今後、移植をされる方の何かの参考になれば幸いです。注意として、ここでの考えは公式の見解ではなく、あくまで個人的な見解です。 できるだけGo実装に寄せる 移植は、できるだけオリジナル実装のGoに寄せるよう心がけました。 実装の乖離が大きいと競技としてフェアでない、移植ミスの際に気づきやすくなりそう、そんなことが理由です。 具体的には、次の2つを行いました。 Cpanel::JSON::XS::Typeで、JSONレスポンスを明示 返り値でエラーも返してみる 1. Cpanel::JSON::XS::Typeで、JSONレスポンスを明示 これまでのISUCONのPerl実装では、ベンチマーカーの期待通りのJSONレスポンスをエンコードするために、JSON::Typesを利用していました。 ですが、今回は Cpanel::JSON::XS::Type を利用し、JSONレスポンスの構造を明示的にしました。 # JSONレスポンスを明示しておく use constant Chair => { id => JSON_TYPE_INT, name => JSON_TYPE_STRING, description => JSON_TYPE_STRING, thumbnail => JSON_TYPE_STRING, price => JSON_TYPE_INT, height => JSON_TYPE_INT, width => JSON_TYPE_INT, depth => JSON_TYPE_INT, color => JSON_TYPE_STRING, features => JSON_TYPE_STRING, kind => JSON_TYPE_STRING, popularity => undef , stock => undef , }; use constant ChairSearchResponse => { count => JSON_TYPE_INT, chairs => json_type_arrayof(Chair), }; Goのstructを使って明示する書きっぷりに、雰囲気は似ている(?)と思います。JSONレスポンスの構造が把握しやすいメリットだけでなく、パフォーマンス面でもJSON::Typesより優位になります。通常のJSONエンコードは、値が文字列か数値かといった内部の状態を確認してエンコードしますが、この場合、値が何であれ型宣言の通りエンコードを試みます。JSON::Typesの実行コスト分、エンコード速度は優位になります。JSON::Typesは、簡単、簡潔に利用できるメリットがありましたが、今回は変更してみました。 2. 返り値でエラーも返してみる 返り値でエラーも返すようにしてみたのですが、効果は特に得られなかった趣味の話です。 例えば、普段であれば、 system(@cmd) or die '..' と異常時はorで繋ぐところ、次のように書いていました。 my $err = system ( @cmd ); if ( $err ) { ... } range_idからrangeを取り出すget_range関数も、エラーも返すようにしました。 my ( $chair_price , $err ) = get_range( $CHAIR_SEARCH_CONDITION->{ price } , $price_range_id ); if ( $err ) { ... } 次のようにget_rangeを剥がすこともできましたが、参考実装のGoに寄せました。 my $chair_price = $CHAIR_SEARCH_CONDITION->{ price }{ ranges }{$price_range_id} ; if (! $chair_price ) { ... } ただPerlの場合、例外で処理するケースがどうしても混ざるので、一貫性が出せず、中途半端でした。趣味でした。 余談 移植は、予定スケジュールよりも早く参考実装を運営チームが用意してくれたので、かなり前持って作業を始めることができました。ただ、悩んだ所、躓いた所もあったので、早めに始められて助かりました。特に次のような所で悩みました。 perlのビルドにuselongdoubleオプションを利用 perlのビルドオプションにuselongdoubleを利用しました。uselongdoubleは、拡張倍精度浮動小数点を扱うためのビルドオプションで、 perl -V:nvsize が8でなく16になります。変更した理由は、緯度経度で桁が足りなくなる為です。例えば、緯度:34.560727610897644 が、緯度:34.5607276108976 となってしまっていました。他の選択肢として、任意桁を扱うモジュール(Math::BigFloatなど)を考えましたが、Go実装と乖離が大きくなりそうな為、不採用にしました。オチとして、PHPでも同様の問題を抱え、結局ベンチマーカーのチェックは緩くなりました。今回の移植で一番悩み、不安だった所です。 isa operator せっかく、perl5.32を採用したので、本筋でないところにしれっと利用しました。 eval { if ( $err ) { die $self->res_no_content ( $c , HTTP_NOT_FOUND); } } if ( $@ ) { # $@が blessされているかどうかとかいちいち確認しなくて済んで便利 return $@ if $@ isa Plack::Response; } isa operatorはさらっと書けていいですね。ただ、そもそも、evalでなく、try/catchを使う方が直感的で実務の場面に近いと感じるので、evalを無くすかどうか迷いました。Syntax::Keyword::Tryを採用すれば、次のように簡潔に書けるのですが、Try::Tinyを普段利用している人にとっては逆にハマりどころになると思い、今回は見送りました。Syntax::Keyword::Tryの魅力については、 papix氏の記事 を参照ください。 use Syntax::Keyword::Try; try { if ( $err ) { # ここでreturnしても、直感通り動く。(普通のことなんだけど・・!) return $self->res_no_content ( $c , HTTP_NOT_FOUND); } } catch { ... } cpmを採用 CPANインストーラーに、cpmを利用しました。速いですね。 Perlの公式系でもcpmを利用されている例 があり、採用しても受け入れてもらえると思い、入れさせてもらいました! 秘密のアレ ISUCONの恒例(?)でしょうか。気づいてもらえたみたいで、よかったです。 ISUCON予選のperl実装。今回はセッションがなかったわけだけど、恒例のtagomorisは残っていたのでウケた — 達人が教えるつぶあん🇺🇦 (@kazeburo) 2020年9月13日 おわりに 初めての移植でしたが、本番一発勝負で受け入れてもらえるか、きちんと動作するか緊張感がありましたが、その分充実感も大きかったです。楽しかったです!ありがとうございました! *1 : 「 ISUCON 」は、LINE株式会社の商標または登録商標です。
アバター
ブロックチェーンチームのソフトウェアエンジニアの id:odan3240 です。 Vue.js で、input タグの ::placeholder 疑似要素に動的なスタイルを当てたい場面がありました。 この記事では、その際の問題と解決方法について紹介します。 この記事のゴール Vue.js では HTML クラスのバインディング や インラインスタイルのバインディング を用いて、動的にクラスやインラインスタイルを割り当てることで CSS を動的に変更ができます。 例えば、 <input :style="{ paddingLeft: myPadding }"> で、input タグの padding-left は myPadding を更新することで動的に変更されます。 しかし、これらの構文では ::placeholder の疑似要素 だけ を指定できず、結果スタイルを変更できません。 CSS カスタムプロパティによる解決 CSS カスタムプロパティ とは、JS などのプログラミング言語の変数と同じように、CSS で変数が使える機能です 1 。使用例は以下の通りです。 .class { /* 変数の宣言。`--` が先頭に付く */ -- main -bg- color : brown ; /* `var` を使って変数を使用する */ background-color : var(--main-bg-color); } SCSS などの CSS プリプロセッサの変数は、プリプロセス時に変数が置換されて定数として CSS に埋め込まれます。それに対して CSS カスタムプロパティは CSS の変数としての機能を持つので、JS から --main-bg-color を書き換えれば、動的に背景色を変更することが可能です。 このCSS カスタムプロパティを用いて、疑似要素の動的にスタイル変更できます。次の codepen は input タグの ::placeholder 疑似要素を動的に右にずらすサンプルです。 See the Pen abNpQmx by odanado ( @odanado ) on CodePen . インラインスタイルのバインディングを使って CSS カスタムプロパティを定義しています。このカスタムプロパティを padding-left に渡しています。 これにより、Vue.js 側で変更された値が CSS カスタムプロパティとして CSS に伝わり、動的に padding を変更することができます。 まとめ CSS カスタムプロパティをインラインスタイルのバインディングを用いて設定することで、疑似要素に動的なスタイルを適用する方法を紹介しました。 IE11 では使えない。 Can I use... Support tables for HTML5, CSS3, etc ↩
アバター
こんにちは、エンジニアの id:tenmihi です。 この度弊社の社内勉強会の時間を活用して、TechKaigiを開催したので紹介したいと思います。 TechKaigiとは TechKaigiはモバイルファクトリーのエンジニアがどんな問題を抱え、どう解決してきたかを共有をする勉強会です。 弊社のエンジニアであればチームを問わず誰でも参加できます。 開催ごとに発表テーマを決める予定で、第1回目のテーマは 隣のエンジニアが知らなそうなこと でした。 開催の理由 リモート勤務だと場所を選ばず仕事ができたり通勤時間に縛られなくなる一方で、コミュニケーションなどで辛いところもありますよね。 私は2年ほど前から東京を離れて長崎の自宅からフルリモートで働いていますが、オフィス側との会議や突発的な障害対応などでコミュニケーションの問題を経験しています。 猛威を奮っているコロナウィルスによる影響で弊社も今年の2月頃から原則リモート勤務となり、それから約半年が経つ中でチーム内外でコミュニケーションの問題が露出してきました。 そのひとつに「周りのエンジニアや隣のチームがどういうことをやっているのかわからない」という声がありました。 そこで各チーム・個人の知見共有やコミュニケーションの問題解決のためのエンジニア横串勉強会としてTechKaigiを開催しました。 TechKaigi イントロダクション 当日の様子 今回は4名の方にシェル、Android APIやgitなど幅広い技術内容で隣のエンジニアが知らなそうなことを発表していただきました。 また、今年入った新卒にとっては名前を聞いたことしか無いエンジニアもいるのではないかと思い、自己紹介もしていただきました。 シェルの1つである Fish についての発表 id:dorapon2000 さんのfishについての発表はこちらにブログ記事としても上げていただいております tech.mobilefactory.jp ちょうど開催のタイミングで一部チームが忙しい時期であったものの社内から27名の方に参加していただくことができ、多くの人が知見の共有をできたのではないでしょうか。 終わりに チームの垣根を超えて社内のエンジニアが各々の知見について発表しあうことができました。 これからもリモート勤務でのエンジニア達の情報の輪を保つための取り組みとしてTechKaigiを続けていければと思っています。
アバター
こんにちは!新卒1年目エンジニアの id:dorapon2000 です。最近暑いですね。 さっそくですが、シェルを便利にカスタマイズしたい気持ちはあるけれど面倒だなぁとか、そろそろbash以外のシェルにも手を伸ばしたいという方はいるのではないでしょうか?今回紹介するfishはデフォルトですでに便利なので、入門者であっても導入しやすいですし、設定ファイルをいじりたくない人にもうってつけです。私自身もoh-my-zshを長らく使っていましたが、fishでいいじゃんとなった一人です。一方で、bash構文が使えないことやfish独自の書き方に抵抗がある人は向いていないので注意です。 本記事ではfishの他のシェルにはない魅力的な特徴と使い方をgif付きで紹介していきます。また、コマンドヒストリーの検索が便利になるplugin-pecoも一緒に紹介します。 fish公式: https://fishshell.com/ 目次 向いている人 fishインストール手順 fishの特徴 各特徴説明 bashとの違い 使ってみての感想 plugin-peco plugin-pecoインストール手順 まとめ インストール手順 # fishのインストール (ubuntu) sudo apt-add-repository ppa:fish-shell/release-3 # fish 3を入れるために必要 sudo apt update sudo apt install fish fish --version # fishのインストール(mac) brew install fish fish --version # デフォルトシェルにする場合 echo $( which fish ) | sudo tee -a /etc/shells chsh -s $( which fish ) fish fishのインストールはここまでです。以下は私が使っているテーマのインストール。 # パッケージマネージャfisherのインストール curl -Lo ~/.config/fish/ functions /fisher.fish --create-dirs git.io/fisherman fisher git_util fisher rafaelrinaldi/pure fishの特徴 fishの公式では6つの特徴をあげています。いくつかピックアップしてみていきましょう。 オートサジェスト 今風のスクリプト構文 manを使った補完 24ビットカラー ウェブからの設定変更 直感的な操作性 オートサジェスト 過去のコマンド履歴を利用して、入力した文字と一致する最後のコマンドをうっすらと表示してくれます。 実行できないコマンドを入力したときに赤くなるのも嬉しいですね。 manを使った補完・直感的な操作性 fishではmanを読み込んでオプションの補完ができるようになります。gitのコマンドはもちろん、manがあるコマンドであればどれでも補完可能です。 ウェブからの設定 fishではウェブからテーマやプロンプトの設定ができ、環境変数なども見られます。ただし、ブラウザがない環境だとエラーがでます fish_config # ブラウザが立ち上がる テーマの設定 プロンプトの設定 環境変数も見られる fishスクリプト fiやesacといったシェルスクリプト独特な書き方から今風の書き方になっています。正直なところ、Unixにデフォルトで入っていないfishでスクリプトを書く気持ちにはなれていません。 function detect_os switch (uname) case Linux echo Hi Tux! case Darwin echo Hi Hexley! case FreeBSD NetBSD DragonFly echo Hi Beastie! case '*' echo Hi, stranger! end end bashとの違い bashとは似ているようで、違う部分も多々あります。 同じ パイプ | 条件式 &&, ||, ! [] 入出力 >, >>, < 違う 設定ファイルの場所は、 ~/.bashrc ではなく ~/config/fish/config.fish 環境変数の設定はexportではなく set set -x PATH /path/to/bin $PATH 数値計算はletや(())ではなく math math 1 + 1 mathはfishの組み込み関数 コマンド置換は$()から () へ echo (math 1 + 1) <()も不可、代わりに psub 以下の記事はbashとの違いについて詳しく書かれていて参考になります。 シェル芸人のためのfish入門 - Qiita 使ってみての感想 普段使っている中では、bashとの違いを意識することは環境変数の設定(set)くらいです。既存のシェルスクリプト自体はbashに渡したり、シェバングを書けば良いので気になりません。たまにパイプでつなげまくるワンライナーを書いてfishで動かないことがありますが、そのときだけbashに切り替えています。fishを使い始の頃は FAQ が助け舟になるでしょう。 fishを使っていて困ることは、ググって入力するコマンドが動かないですね。例えば、ssh-agentなんかはつまづきポイントです。 # bash eval `ssh-agent` # fish eval ( ssh-agent -c ) plugin-peco 最後に1つだけ、plugin-pecoというプラグインだけおすすめしておきます。plugin-pecoを使うにはpecoコマンドも必要です。 Ctrl-Rを押すとコマンドヒストリーが開き、1タイプごとに絞り込める さらにCtrl-Rを押すことで絞り込みの種類を変更できる fishでも↑↓キーでヒストリーを順に表示できますが、一覧表示されると検索効率がぐんとあがります。 pecoとplugin-pecoのインストール # pecoのインストール(ubuntu) sudo apt install peco # pecoのインストール(mac) brew install peco # plugin-pecoをインストール fisher install oh-my-fish/plugin-peco vim ~/.config/fish/config.fish # 以下を書き加える function fish_user_key_bindings bind \cr peco_select_history end # シェル再起動 まとめ 強力な補完 導入コストも学習コストも低い pecoと組み合わせてらくちんヒストリー検索 ここまで読んでくださりありがとうございました。 この記事をきっかけにfishの魅力に気づいて、fishを使う人が増えることを期待します!
アバター
こんにちは!4月に入社したエンジニアのDozi0116です。 5月に駅メモ!開発チームに配属されてから毎日コードと戦っています。 今回は自分が入社して1ヶ月の新人研修・技術研修 + 2ヶ月の現場配属で試したことや学んだことを紹介します。 意思疎通が難しい この3ヶ月で1番の問題は意思疎通が難しいことでした。 元から物事を伝えるのが苦手な上、リモートで 会社には一度も出社しておらず同僚の誰にも一度も会っていない ため、質問はもちろん、自分の進捗を報告することすらできなかったので、コードレビューのタイミングで指摘されて大幅に書き直すハメになったり、認識のすり合わせが十分にできておらず完成後にお互いの認識の違いに気が付いたりと、意思疎通ができていないことが原因の問題が多々発生しました…。 また、リモートということもあり、相手の空気感や調子が感じ取れない状態で意思疎通をするのはすごくハードルが高いと感じました。 3ヶ月で試したこと そんな中、少しでも円滑に意思疎通をしようといろいろなことを試してみました。次に紹介するのは、その中で学びが大きかった3つです。 進捗を定期的に書き出す まずは自分の考えをまとめられるようになろうと思い、毎日自分の作業スレッドを立てて、そこに定期的に自分が今ある状態を書き出すことにしました。 とある日に正規表現で悩んでいた時の作業スレッド 何かを見て得られた知見、実行して得られた結果に対する感想、単なるメモなどをとにかく書きまくる。 これをやったことで、長時間同じことを書いている = 自分が詰まっていることに気が付いたり、いざ質問しようと思ったら、いろいろ書き出しているおかげですでに質問の下地が出来上がっていたり、チームの全員が見られる位置でこのスレッドを作っているため、本当に困った時はこの流れを見てもらうことで質問の意図が伝わりやすかったりと複数の場面で役立ちました。 雑談を毎日10分する メンターの方に意思疎通のハードルが高く感じると話したら、毎朝好きな話をしていい雑談の時間を10分設けてもらえました。 雑談で親睦を深めることで話しにくさを減らせました。 また、リモートだと相手のタイミングを掴むことが難しいため、業務中に「今は相談できるタイミングなのかな…?」と戸惑うこともあります。ですが、 この時間は自分のために確保してくれる時間 だと考えたら、様々なことが聞きやすくなって質問のハードルを大きく下げられました。(なんでも答えてくれるメンターさんには本当に感謝です!) 細かいことでもすぐにビデオ通話をする 先述した失敗を踏まえて、最近は少しでもわからないところがあったらなるべく通話をつないで、話し合いながら認識や方針のすり合わせを行うようにしています。 先輩の時間を奪ってしまう後ろめたさはあるけれど、とても親切に聞いてくれ、親身に考えてくれるのでありがたいです。 さらに、 認識の違いが減りレビューにかける時間が大幅に減った ので、事前に相談をする能力はコードを書く能力より大事かも?と思うようになりました。 まとめ 簡単に3ヶ月で試したことや学んだことを紹介しました。 この3つを実施することで、意思疎通がしやすくなりました。 進捗を定期的に書き出す 雑談の時間を取り入れる 細かいことでもすぐにビデオ通話をする これからどんどん先輩たちと意思疎通していって、コーディング知識やそれ以外の事もたくさん蓄えていき、いち早く一人前のエンジニアとして貢献できるように今後も頑張っていきたいです!
アバター
4月入社の新人エンジニアのxztaityozxです。趣味はdotfilesいじりです 皆さんは日々の開発でふと、調査やデータの加工が必要となったことはありませんか?私はたくさんあります。 ではそういった時、どうやって解決していますか?私はいくつかのCLIツールを組み合わせることで解決しています。 例えば、 /etc/services からtcpなサービスの名前を取り出すときは以下のようにします $ cat /etc/services | grep -P " \d +/tcp " | awk ' {print $1} ' こういったテクニックを「ワンライナー」と呼ぶことにし、日常のちょっとした調査やデータ加工をサッと出来るようにコマンドラインと触れ合っていくことを目的とした勉強会「ワンライナー勉強会」を開催しました。 この記事では、「ワンライナー勉強会」の進め方や問題、解答例を紹介します。 進め方 社内勉強会は開催者が自由にその形式を決めて良いとのことなので、プライベートでよく参加している シェル芸勉強会 を参考に、以下のような進め方にしました。 参加者に問題を提示する 参加者は10分間でその問題にチャレンジする このとき使って良いのはコマンドラインのみ 解答ができたから、Slackへ投稿する このとき私が解説できそうなものは解説します 解答例を示す 1~4を繰り返す 作問はUbuntu 20.04のbash v5.0.16 で行いました。 問題 今回の勉強会のためにいくつか作問しましたが、この記事ではその中から2問紹介したいと思います。この記事を読んでいるみなさんも是非挑戦してください! Q1 30までのFizzBuzzを出力してください FizzBuzzとは、3の倍数のときは Fizz 、5の倍数では Buzz 、15の倍数では FizzBuzz と発言するパーティーゲームです。よくプログラミングの入門としても出題されますね。 参加者の解答として投稿されたものをいくつか紹介します。 $ seq 30 | awk ' { if ($1 % 15 == 0) { print "FizzBuzz" } else if ($1 % 3 == 0) { print "Fizz" } else if ($1 % 5 == 0) { print "Buzz" } else { print $1 } } ' スクリプト言語であるawkを使った解答です。FizzBuzzの要件をそのまま落とし込んだ!という感じの解答でとても読みやすいです。 $ python3 -c " for i in range(1,31):print('Fizz'*(i%3==0)+'Buzz'*(i%5==0) or str(i)) " $ python -c " [print(('Fizz' if i % 3 == 0 else'') + ('Buzz' if i % 5 == 0 else '') or i) for i in range(1, 31)] " こちらはPythonを使った解答です。両者とも「文字列を組み立てて出力する」タイプで、1つ目の解答は文字列の繰り返しを上手く使った解答でした。 この他にも、Perlやnodeを使った解答もありました。同じ問題を解くにしても、いろんなアプローチが見られるのがワンライナーの面白いところです。 ちなみに私が用意してきた解答例は $ seq 30 | awk ' NR%3==0{printf "Fizz"}NR%5==0{printf "Buzz"}{print " "$1} ' | awk ' {print $1} ' というものです。1回目のawkではデータの加工、2回目のawkではデータの抽出を行っています。 Q2 nums というファイルには、100,000個の数値が改行区切りで書いてあります。これの総和を求めてください。 10万行の数値データの総和を求める問題です。皆さんはどういう解答をされたのでしょうか。以下に示します。 $ cat nums | awk ' BEGIN{s = 0} {s += $1} END{print s} ' $ cat nums | python -c ' import sys; print(sum(map(int, sys.stdin.readlines()))) ' $ ghc -e ' do f <- fmap (\x -> map read (lines x)) (readFile "nums"); print $ sum f ' この問題もawk,python,haskellなど、たくさんの方法での解答が投稿されました。特に気になったのは以下の解答です。 $ echo $( tr ' \n ' ' + ' < ./nums ) 0 | bc tr で数式を組み立て、 bc コマンドで数式を評価するというものです。少し詳しく解説してみます。かんたんのために nums ファイルの中身は以下のとおりとします $ cat nums 12726 24406 17542 30717 14595 9099 10860 9689 29765 122 これに対して解答にある tr コマンドを実行してみます。 $ tr ' \n ' ' + ' < ./nums 12726 + 24406 + 17542 + 30717 + 14595 + 9099 + 10860 + 9689 + 29765 + 122 + 数式が出来ました。ただし、最後の改行も + に置換されているので、式が成り立っていません。このままでは bc に渡したところでエラーが返ってきてしまいます。 $ tr ' \n ' ' + ' < ./nums | bc ( standard_in ) 1: parse error なので、末尾に 0 を付けて式を成立させます。 $ echo $( tr ' \n ' ' + ' < ./nums ) 0 12726 + 24406 + 17542 + 30717 + 14595 + 9099 + 10860 + 9689 + 29765 + 122 + 0 $() はコマンド置換と呼ばれるものです。 $() でくくったコマンドの結果を別のコマンドに埋め込むことが出来ます。この場合では、 tr コマンドの結果を echo に埋め込んでいます。これにより無事式が成立し、 bc で評価することが出来るようになりました。 $ echo $( tr ' \n ' ' + ' < ./nums ) 0 | bc 159521 とても素敵な解答でした。 ちなみに100,000個の数値データは以下のようなワンライナーで生成しました。この問題に挑戦するときにお使いください。 $ seq 100000 | while read _;do echo $RANDOM ; done まとめ 初めての開催でしたが、たくさんの参加者が来てくれ、積極的に解答してもらえて嬉しかったです。参加者からも「楽しかった」「面白かった」と言ってもらえたので次回開催のモチベーションになりました。問題の難易度や時間配分を調整してまた開催したいです。 モバイルファクトリーは、技術好きなエンジニアを募集しています。
アバター
要約 JVMは任意の言語で作ることができるので、Perlで書いてみました github.com このようにクラスファイルを読み取り、それを実行することができます 今回は読者がJVMを書き始められるようにクラスファイルの読み取り方に焦点をおいて解説します(あまりPerlの話はしません) 目次 自己紹介 JVMの基本 クラスファイルの解説 オペコードの実行 まとめ 自己紹介 駅メモにて主にバックエンドを担当している id:toricor です。 仕事ではサーバがPerl実装なので、Perlでいろいろな機能を実装したりパフォーマンスチューニングをしたりしています。 JVMをつくろう 残念ながら今のところ仕事ではほぼJVMと縁がないので、まずは基本を確認します JVMとは Java Virtual Machine(Java仮想マシン)の略です Java仮想マシン - Wikipedia JVMはJavaプログラムのどこを担うか MyProgram.java というjavaのソースコードがあった場合、これを実行するためには以下のような操作になります % javac -encoding UTF-8 MyProgram.java # コンパイルしてクラスファイルを生成 % java MyProgram # JVMがクラスファイルを読み取り実行 コンパイラが生成したクラスファイルを読み取り実行するのがJVMです なぜJVMを実装する? いわゆる スタックマシン が現実でどのように動くのか知りたかった 「堅い仕様書」を元に実装してみる経験がしたかった 年末年始にTwitterを見ていたらJVMを実装するのが流行っていた(?) HelloWorldするための詳細な解説スライドが流れてきたので実装できそうな気がした https://speakerdeck.com/memory1994/phperkaigi-2019 とか 「JVMを実装する」とは To implement the Java Virtual Machine correctly, you need only be able to read the class file format and correctly perform the operations specified therein. The Java Virtual Machine Specification 第2章冒頭 より引用 「JVMを正しく実装するにはクラスファイルを読んでそこの指示を正しく実行できればよい」 詳細な仕様書 があるのでそれにそって実装すればよいです。 仕様 を満たせばどの言語で書いてもそれはJVMです。 JVM 実装した 主に年末年始に実装しました https://github.com/toricor/p5-jvmtiny HelloWorld の他、 FizzBuzz くらいなら出力できます Java実行環境 手元のMacで動かします % javac -version javac 1.8.0_144 // 昔インストールしたままのjava 今回はJava 8を使います。以下に示すクラスファイルの形式はJava8でコンパイルした場合のものになります(そういえばJavaは14まで出ていますがJava界のシェアは 8が依然トップ らしいですね)。 簡単なプログラムを実行しよう よくある例はHelloWorldですが、HelloWorldのような標準出力に何かを出すようなプログラムには実は結構難しい命令が含まれるので、今回は簡単のため次のような足すだけのプログラムを見ます class JustAddInt { public static void main(String[] args) { int a = 1 ; int b = 2 ; int c = a + b; } } これをコンパイルしたJustAddInt.classを読み取り、実行しましょう クラスファイルの中身をみる % javac -encoding UTF-8 JustAddInt.java // コンパイルしてクラスファイルをつくる バイナリを読む xxdが便利です。 xxd - make a hexdump or do the reverse. % xxd JustAddInt.class 00000000: cafe babe 0000 0034 000f 0a00 0300 0c07 .......4........ 00000010: 000d 0700 0e01 0006 3c69 6e69 743e 0100 ........<init>.. 00000020: 0328 2956 0100 0443 6f64 6501 000f 4c69 .()V...Code...Li 00000030: 6e65 4e75 6d62 6572 5461 626c 6501 0004 neNumberTable... 00000040: 6d61 696e 0100 1628 5b4c 6a61 7661 2f6c main...([Ljava/l 00000050: 616e 672f 5374 7269 6e67 3b29 5601 000a ang/String;)V... 00000060: 536f 7572 6365 4669 6c65 0100 0f4a 7573 SourceFile...Jus 00000070: 7441 6464 496e 742e 6a61 7661 0c00 0400 tAddInt.java.... 00000080: 0501 000a 4a75 7374 4164 6449 6e74 0100 ....JustAddInt.. 00000090: 106a 6176 612f 6c61 6e67 2f4f 626a 6563 .java/lang/Objec 000000a0: 7400 2000 0200 0300 0000 0000 0200 0000 t. ............. 000000b0: 0400 0500 0100 0600 0000 1d00 0100 0100 ................ 000000c0: 0000 052a b700 01b1 0000 0001 0007 0000 ...*............ 000000d0: 0006 0001 0000 0001 0009 0008 0009 0001 ................ 000000e0: 0006 0000 002d 0002 0004 0000 0009 043c .....-.........< 000000f0: 053d 1b1c 603e b100 0000 0100 0700 0000 .=..`>.......... 00000100: 1200 0400 0000 0300 0200 0400 0400 0600 ................ 00000110: 0800 0700 0100 0a00 0000 0200 0b ............. おぼろげながらクラス名などがいくつかあるのはわかります フォーマットの仕様に基づき解読しよう 以下の仕様書を見ながら解読します docs.oracle.com ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } クラスファイルはこのような並びで記述されています u1, u2, u4はそれぞれ符号なし(unsigned)の1バイト、2バイト、4バイトのことです 適宜参照しやすいようにxxdの結果の一部と比較しながら順番にみていきます magic: u4 00000000: cafe babe 0000 0034 000f 0a00 0300 0c07 .......4........ 0xCAFEBABE この冒頭4byteはクラスファイルであることを宣言しています バイナリファイルの冒頭にこれが見えたらJavaのクラスファイルだとわかりますね minor_version, major_version : u2, u2 00000000: cafe babe 0000 0034 000f 0a00 0300 0c07 .......4........ 0x0000 : minor_version 0x0034 : major_version 2byteと2byteのこの組み合わせで、今回はJava 8のクラスファイルだとわかります Javaのバージョンとの対応表はこちらをみてください ( 表があるのはJava 11のドキュメントですが) constant_pool_count: u2 00000000: cafe babe 0000 0034 000f 0a00 0300 0c07 .......4........ constant_pool(後述)の要素数+1 (=15) constant_pool[constant_pool_count-1]: cp_info クラス名やメソッド名、メソッドの型情報などをまとめたものです。 以降の処理で必要な情報は適宜constant_poolの配列へindexでアクセスして取得します。 位置 16進表示 ascii表示 00000000: cafe babe 0000 0034 000f 0a00 0300 0c07 .......4........ 00000010: 000d 0700 0e01 0006 3c69 6e69 743e 0100 ........ .. 00000020: 0328 2956 0100 0443 6f64 6501 000f 4c69 .()V...Code...Li 00000030: 6e65 4e75 6d62 6572 5461 626c 6501 0004 neNumberTable... 00000040: 6d61 696e 0100 1628 5b4c 6a61 7661 2f6c main...([Ljava/l 00000050: 616e 672f 5374 7269 6e67 3b29 5601 000a ang/String;)V... 00000060: 536f 7572 6365 4669 6c65 0100 0f4a 7573 SourceFile...Jus 00000070: 7441 6464 496e 742e 6a61 7661 0c00 0400 tAddInt.java.... 00000080: 0501 000a 4a75 7374 4164 6449 6e74 0100 ....JustAddInt.. 00000090: 106a 6176 612f 6c61 6e67 2f4f 626a 6563 .java/lang/Objec 000000a0: 74 00 2000 0200 0300 0000 0000 0200 0000 t. ............. 表: xxd結果を整形したもの。緑色部分がconstant_poolの情報をもつ部分。 このままでは見にくいので javap -v JustAddInt の結果の一部をはります(javap: classファイルの逆アセンブルコマンド) 以下のように14個の要素が並んでおり、たしかにconstant_pool_count-1個あることがわかります(JVMをつくるときはjavapの結果を見ながら作業するとスムーズです)。 Constant pool: #1 = Methodref #3.#12 // java/lang/Object."<init>":()V #2 = Class #13 // JustAddInt #3 = Class #14 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 main #9 = Utf8 ([Ljava/lang/String;)V #10 = Utf8 SourceFile #11 = Utf8 JustAddInt.java #12 = NameAndType #4:#5 // "<init>":()V #13 = Utf8 JustAddInt #14 = Utf8 java/lang/Object constant poolの各要素はtag(上記のMethodRefやUtf8などに対応する)とその詳細の組として表されます。下に示す u1 info[]; は tagによって示すものが変わります 。 cp_info { u1 tag; u1 info[]; } ここでConstant poolの要素についてどう読み取っていくか見てみましょう。 たとえば先頭の#1はjavap結果だとMethodref_infoということですが確認します。 00000000: cafe babe 0000 0034 000f 0a 00 0300 0c07 .......4........ 1byteのtagが 0x0a (=10)なので 定義 から、たしかにMethodref_infoです。 Methodref_infoの 定義 は以下のような形式になります。 CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } 00000000: cafe babe 0000 0034 000f 0a00 0300 0c 07 .......4........ class_indexが 0x0003 、続くname_and_type_indexが 0x000c (=12)となりjavap結果と一致していそうです(Constant pool内の要素のindexを示しています)。 access_flags: u2 000000a0: 74 00 20 00 0200 0300 0000 0000 0200 0000 t. ............. このクラスやインターフェースのpublic等のアクセス修飾子です(和で表されます)。 今回は 0x20 です。 this_class: u2 000000a0: 7400 20 00 02 00 0300 0000 0000 0200 0000 t. ............. このクラスの情報です。constant_poolの2番目を指していて JustAddIntです。 #2 = Class #13 // JustAddInt super_class: u2 000000a0: 7400 2000 02 00 03 00 0000 0000 0200 0000 t. ............. 親クラスはconstant_poolの3番目で、java/lang/Object です。 #3 = Class #14 // java/lang/Object interfaces_count: u2 000000a0: 7400 2000 0200 03 00 00 00 0000 0200 0000 t. ............. インターフェース数は0です interfaces[interfaces_count]: u2 今回はインターフェースがないのでデータなしです。 fields_count: u2 000000a0: 7400 2000 0200 0300 00 00 00 00 0200 0000 t. ............. フィールド数は0です。 field_info: fields[fields_count] フィールドがないのでデータなしです。 methods_count: u2 000000a0: 7400 2000 0200 0300 0000 00 00 02 00 0000 t. ............. メソッド数は2です。mainだけでなくコンパイラが <init> もつくるので2つです。 At the level of the Java Virtual Machine, every constructor written in the Java programming language (JLS §8.8) appears as an instance initialization method that has the special name . This name is supplied by a compiler. https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9 method_info: methods[methods_count] ようやくメソッドの具体的内容です。 定義 は以下のようになります。 method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } しかし、 書くのが大変になってきたのでPerlのプログラム上でmethod_infoをダンプした結果で代用します。 2つ目の要素がmainメソッドについての情報です。 [ [ 0 ] { access_flags 0 , attribute_info [ [ 0 ] { attribute_length 29 , attributes [ [ 0 ] { attribute_length 6 , line_number_table_length 1 , line_number_tables [ [ 0 ] { line_number 1 , start_pc 0 } ], name "LineNumberTable" } ], attributes_count 1 , code "*� \0 �" , code_length 5 , exception_table_length 0 , exception_tables [], max_locals 1 , max_stack 1 , name "Code" } ], descriptor_index "()V" , name_index "<init>" }, [ 1 ] { # ここ以下がmainメソッドの情報 access_flags 9 , # ACC_PUBLIC(0x0001), ACC_STATIC(0x0008) attribute_info [ [ 0 ] { attribute_length 45 , attributes [ [ 0 ] { attribute_length 18 , line_number_table_length 4 , line_number_tables [ [ 0 ] { line_number 3 , start_pc 0 }, [ 1 ] { line_number 4 , start_pc 2 }, [ 2 ] { line_number 6 , start_pc 4 }, [ 3 ] { line_number 7 , start_pc 8 } ], name "LineNumberTable" } ], attributes_count 1 , code "<=>�" , # これが計算処理 code_length 9 , # コードの長さは9 exception_table_length 0 , exception_tables [], max_locals 4 , max_stack 2 , name "Code" } ], descriptor_index "([Ljava/lang/String;)V" , name_index "main" } ] mainメソッドのもつ Codeアトリビュート がコードの長さ9からなるコードを持っています (後でこのコードを実行します) attributes_count: u2 (省略) attribute_info: attributes[attributes_count] 定義 です。attribute_infoはmethod_infoでも使われていました。 attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; } これでクラスファイルの読み取りができました。 コードを実行しよう クラスファイルの読み取り結果に基づき、どのような計算指示が格納されていたかをまず確認しましょう code "<=>�" , # これをいい感じに実行したい code_length 9 , このままではよくわからないので、mainメソッドがもつコード情報を16進表示します。すると以下のような配列であることがわかりました。 \ [ [0] "04", [1] "3c", [2] "05", [3] "3d", [4] "1b", [5] "1c", [6] 60, [7] "3e", [8] "b1" ] これは オペコード と オペランド があつまったものになります(今回はオペランドないですが)。 あとはオペコードの仕様を確認しながら、オペコードの指示に従ってスタックに入れたり出したり(+ローカル変数というものもあってそれに入れたり出したり)すれば計算ができます。 # JustAddIntプログラムは 1 + 2 = 3 を計算する [ 0 ] "04" , # iconst_1: スタックに1を積む [ 1 ] "3c" , # istore_1: ローカル変数1番にスタックからpopした値を入れる [ 2 ] "05" , # iconst_2: スタックに2を積む [ 3 ] "3d" , # istore_2: ローカル変数2番にスタックからpopした値を入れる [ 4 ] "1b" , # iload_1: ローカル変数1番の値をスタックに積む [ 5 ] "1c" , # iload_2: ローカル変数2番の値をスタックに積む [ 6 ] 60 , # iadd: スタックから2つpopして足した値をスタックに積む [ 7 ] "3e" , # istore_3: スタックからpopした値をローカル変数3番に入れる = 3 が入る [ 8 ] "b1" # return: voidを返す (一応)計算できました! ちなみに HelloWorld ではこのようなオペコードになります。 # テストから抜粋 qw/ b2 00 02 / , # getstatic qw/ 12 03 / , # ldc qw/ b6 00 04 / , # invokevirtual qw/ b1 / , # return 最後は駆け足でしたが、ごく簡単なJVMをつくるために必要なことを紹介しました。 最初はクラスファイルを読み取る部分をつくるまでが大変なので、乗り越えやすいようにできるだけ詳しく紹介しました。 まとめ JVMはPerlでもつくれます(簡単なものなら) 皆さんも JVM + YOU, LET'S WRITE IT! 参考資料 https://speakerdeck.com/memory1994/phperkaigi-2019 など 仕様書 https://docs.oracle.com/javase/specs/jvms/se8/html/index.html 参考実装 いろいろとお手本実装が見つかったのでそのうち書き直したいですね PHP: https://github.com/php-java/php-java Java: https://github.com/k0kubun/jjvm Rust: https://github.com/maekawatoshiki/ferrugo Node.js: https://github.com/YaroslavGaponov/node-jvm Python: https://github.com/gkbrk/python-jvm-interpreter など もっと本格実装する話 セルフホストで学ぶJVM入門 - k0kubun's blog
アバター
こんにちは、ブロックチェーンチームでソフトウェアエンジニアをしている id:odan3240 です。 モバファクには毎日1時間社内勉強会の制度があります。 tech.mobilefactory.jp 様々な目的の社内勉強会が開催されていますが、その中に一つ OSS への貢献が目的の勉強会があります。この貢献先の一つとして、perl-users.jp を GitHub Pages へ移管しているプロジェクトがあります。 github.com 今回はこのプロジェクトでプルリクが作られるたびにレビュー用の環境がデプロイされる仕組みを作ったので紹介します。 モチベーション Netlify の Deploy Preview 機能 はプルリクが作られるたびに、プルリクの内容のウェブサイト(以下プレビューサイト)を構築する機能のことです。この機能により、動作確認のためにローカルでサーバを立ち上げる必要がなくなり、レビューの効率化に繋がります。 perl-users-jp.github.io では mixed contents 対応の確認のために https でホスティングされた動作確認用の環境が必要になりました 1 。このプレビューサイトは https でホスティングされているため、Deploy Preview 機能を使えば目的を達成できそうです。 課題 perl-users-jp.github.io の静的サイトジェネレータは Perl によって実装されています 2 。しかし、Netlify が CI/CD を行うマシンに 事前にインストールされているソフトウェア には Perl がありません。 Perl はディストリビューションに含まれている可能性はありますが、静的サイトジェネレータは carton というパッケージマネージャによって管理されており、周辺のツールチェインは別途インストールする必要があります。 解決策 perl-users-jp.github.io へのデプロイについては既に GitHub Actions を用いた CI/CD パイプラインが構築されていました。これを流用して、GitHub Actions 上で生成された静的ファイルを Netlify のプレビューサイトにデプロイする方法があれば、上記の課題をクリアできるのではないかと考えました。 今回は GitHub Actions から Netlify にデプロイする Action が公開されていたので、これを利用しました 3 。 github.com 以下が GitHub Actions の設定ファイルです。 name : Build and Deploy to Netlify on : pull_request : types : [ opened, synchronize ] jobs : build : runs-on : ubuntu-18.04 steps : - uses : actions/checkout@v2 - name : Setup Perl uses : shogo82148/actions-setup-perl@v1 with : perl-version : '5.30' - name : Build run : make build - name : Deploy to Netlify uses : nwtgck/actions-netlify@v1.0 with : publish-dir : './public' github-token : ${{ secrets.GITHUB_TOKEN }} deploy-message : "Deploy from GitHub Actions" env : NETLIFY_AUTH_TOKEN : ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID : ${{ secrets.NETLIFY_SITE_ID }} make build で静的ファイルを ./public ディレクトリに生成されます。このディレクトリの内容を Netlify にデプロイしています。 動作の様子 動作確認したプルリク の様子です。 コメントにプレビューサイトの URL が投稿され、シュッと動作確認ができます。 まとめ Netlify の Deploy Preview は https で動作確認用のサイトが作成されるので便利です。しかし Netlify は Perl に対応しておらず、Perl 製の静的サイトジェネレータを用いたサイトでは利用できないという問題がありました。 そこで、GitHub Actions 上で静的ファイルを生成し、これを Netlify のプレビューサイトにデプロイすることで、この問題を解決する方法を紹介しました。 https://github.com/perl-users-jp/perl-users-jp.github.io/issues/24 ↩ Perl に関する記事が集まっているサイトなので納得感がある ↩ 実は同時期に全く同じ Action を開発していたが完成が間に合わなかったのでクローズしこちらを利用した ↩
アバター
こんにちは、ブロックチェーンチームでソフトウェアエンジニアをしている id:odan3240 です。 モバファクには毎日1時間社内勉強会の制度があります。 tech.mobilefactory.jp 様々な目的の社内勉強会が開催されていますが、その中に一つOSSへの貢献が目的の勉強会があります。この勉強会の紹介は別の機会として、この記事では、この勉強会を通じてDefinitelyTyped に新しいパッケージの型定義を追加したので、その紹介をします。 DefinitelyTyped とは DefinitelyTyped とは TypeScript の型定義を集めたリポジトリです。 TypeScript を書いているときに、 npm パッケージと一緒に @types/xxx というパッケージをインストールしたことがある方もいるかも知れません。この @types から始まるパッケージを集約しているリポジトリが DefinitelyTyped です。 DefinitelyTyped には型定義のない JavaScript 製のパッケージに対する型定義 1 が大量にあります。 今回は、 keccak - npm に対する型定義を追加しました。 手順 コントリビュートの手順については README の How can I contribute? に丁寧に紹介されています。今回は新しいパッケージの追加だったため、 Create a new package にある手順に沿って作業をしました。 ボイラープレートの作成 README にある通り、 dts-gen を用いてボイラープレートを生成しました。今回の対象のモジュールは keccak なので、以下のコマンドを実行しました。 $ npx dts-gen --dt --name keccak --template module このコマンドにより、 index.d.ts , keccak-test.ts , tsconfig.json , tslint.json の4つのファイルが生成されます。 index.d.ts に型定義を、 keccak-test.ts に型定義に対するテストを書くのが今回のゴールです。 テスト .d.ts ファイルの lint やテストには dtslint が使用されます。 テストでは以下のように、コメントで期待される型を書く形式になっています。 const keccak = create ( 'keccak224' ); // $ExpectType Keccak 実際のプルリク 実際に出したプルリクはこれです。 feat: add types of keccak by odanado · Pull Request #44016 · DefinitelyTyped/DefinitelyTyped typescript-bot という bot が住んでいます。この bot はレビューが遅れていると、「もう少し待ってね」というコメントを付けるようです。個人的に放置されている感じが少なくて良いと思いました。 感想 keccak はブロックチェーンの Ethereum で使用されているハッシュ関数です。Ethereum に関するコードを書く時によく使用していましたが、型定義が存在しないのがいつも気になっていました。そんな中、 OSS への貢献を目的とした社内勉強会が開催されると聞いて良い機会と感じて keccak の型定義を追加するプルリクを作成しました。 DefinitelyTyped には世界中のエンジニアが協力して型定義のない npm パッケージに型定義を追加しています。仕事でも趣味でも TypeScript を書いている人間としては DefinitelyTyped には普段からお世話になっています。今回は、これに自分も協力できたという実感があり、感慨深いです。 元の npm パッケージに型定義が含まれている場合もある ↩
アバター
こんにちは、ブロックチェーンチームでソフトウェアエンジニアをしている id:odan3240 です。 ページをまたぐエラーを制御したい場合、グローバルな状態管理を行えるVuex が選択肢に上がるかと思います。しかし、Vue.js 2系に対応する 3系の Vuex は公式の TypeScript サポートがなく TypeScript と相性が悪いことが知られています。エラー状態管理のような規模の小さいものなら Vue.observable を利用すれば良いのでは?と考え実装してみたところ、いい感じになったので紹介します。 前提知識 Vue.observable とは Vue.observable は Vue.js 2.6 から追加された API です。この API はオブジェクトをリアクティブにすることができます。コンポーネントの data 関数の返り値がリアクティブになるのと同じ仕組みです。 jp.vuejs.org Vue.js におけるエラーハンドリング Vue.js では処理されていない例外が検知された場合、 Vue.config.errorHandler に登録されている関数を呼び出します。つまり、ここに関数を登録すれば独自のエラー処理を実装することができます。 jp.vuejs.org Vue.js のプラグイン Vue.js のプラグインを使用することで、Vue のインスタンス、つまり this にプロパティを生やすことが可能です。プラグインはアプリケーションの初期化時に呼び出されるようにする必要があります。 jp.vuejs.org 実装例 今回紹介する実装例は以下のリポジトリに置いています。 github.com src/plugins/error.ts を実装する src/plugins/error.ts を簡略化しています。 import Vue , { PluginObject } from "vue" ; export type Options = {} ; // 状態の型を定義 export type State = { hasError: boolean ; message: string ; } ; // this.$error でアクセスできるように型定義を拡張 declare module "vue/types/vue" { interface Vue { $error: State ; } } // エラーの状態を Vue.observable を使って定義 const state = Vue.observable < State >( { hasError: false , message: "" } ); // エラーが発生するとこの関数が呼ばれる Vue.config.errorHandler = err => { // エラーが存在することと、その内容を格納 state.hasError = true ; state.message = err.message ; // 3秒後にエラーの状態をクリア setTimeout (() => { state.hasError = false ; state.message = "" ; } , 3000 ); } ; export const errorPlugin: PluginObject < Options > = { install ( Vue ) { // this.$error に state を注入 Vue. prototype .$error = state ; } } ; コンポーネント側 コンポーネント側の使用例を示します。 src/App.vue を簡略化しています。 < template > < div id = "app" > < div > < button @click= "click" > click me! </ button > </ div > < div v-if= "hasError" > {{ errorMessage }} </ div > </ div > </ template > < script lang = "ts" > import Vue from "vue" ; export default Vue.extend ( { name: "App" , methods: { click () { throw new Error ( "Error! yabai!" ) ; } } , computed: { // this.$error.hasError と this.$error.message はエラーが発生すると自動的に値が変わる(リアクティブ) hasError () { return this .$error.hasError; } , errorMessage () { return this .$error.message; } } } ) ; </ script > このような実装をしておくと、アプリケーションで発生した例外を自動的にキャッチして、リアクティブな変数 state の状態が変更され、自動的に errorMessage が表示されます。 まとめ Vue.observable を使ってエラー管理用のリアクティブな変数を用意して、 Vue.config.errorHandler で更新し、その変数に対して this 経由でアクセスする方法を紹介しました。 Vue.observable を使ったサンプルは日本語ではあまりヒットしなかったので、今回記事にしてみました。
アバター
こんにちは。エンジニア組織開発責任者の id:kfly8 です。 はじめに 最近、COVID-19の影響で、在宅で勤務している方が増えているかと思います。モバイルファクトリーでは2月中旬から原則在宅勤務になり、家の開発環境をこの機に整えている声などを聞きます。 そこで、今回は弊社のメンバーに在宅の開発環境のこだわりなどを聞いてみました *1 尚、この記事はGunosyさんのこの記事にインスパイアされた記事です。 tech.gunosy.io 1人目 1人目は、デザイナーの @momoyagi です。 普段はMBP本体+ディスプレイ2枚で開発していますが、自宅環境はMBP+ディスプレイ+気分転換用にipadになっています。デザイナー職のため、会社支給のwacom ペンタブレットを持ち帰ってます。デバイスが少し多いので配線管理がつらい。 在宅開発環境の良い点 周りがうるさいと集中できないので、一人の空間が快適。幸い趣味開発用にデスクが整備されていたので開発自体は通常と変わらず行えてます。あと宅配便が受け取りやすい。 在宅開発環境の悪い点 室温管理が難しい。オフィスの空調とドリンクサービスは良い福利厚生 *2 。あと会議通話中に宗教勧誘やセールスがくること。 今後、在宅開発環境をどうしたいですか? 会議通話の環境が微妙で安いイヤホンしかないので、ミキサーにつないだりプッシュ・トゥ・トークできるようになるといいな(それ整えるまでリモート勤務続くのか…?) フリーコメント 在宅勤務のコツは着替える事と、朝のルーティン決めておくこと。 2人目 2人目は、エンジニアの @nanamachi です。 ディスプレイ: LGの42.5インチ4Kモニタ(43UD79T-B) 先代のモニタが壊れたのを機に新しいものにする際に21型を3枚か思い切って4K1枚にするかで悩みましたが、4Kにして正解。ウィンドウ配置の自由度が高く、作業効率が上がっています。机をほぼ端から端まで専有するのは少し難点…… macを開いているとモニターが隠れてしまうため、カメラを使うとき以外は閉じています。 キーボード: FILCO Majestouch NINJA 茶軸/フルサイズ/US ASCII (FKBN104M/EFB2) 大学時代からの戦友。会社では音を気にして黒軸を使っていますがやはり茶軸の打ち心地が好きです。印字をキーの前面に持ってくることでデザイン性と機能性を両立した一品です。リモートが始まったのを機にUSB切替器(私用デスクトップと社用macへの配線をボタン一つで切り替えられる)も導入したのですが、退勤してすぐ私用PCに切り替えられるため思った以上に効果が高いです。 机: ニトリのデスク(横120x奥70くらい) 大学時代から使っているデスクです。中央が引き出し式になっていてキーボードを置けるのが気に入っています。 椅子: ニトリのワークチェア 背もたれが大きめで長時間のデスクワークでもしっかり支えてくれます。 音響設備: ディスプレイから流すことが多い。 ヘッドフォン系統はaudio-technica ATH-M50x(ヘッドフォン) + CREATIVE Sound BlasterX G6(外付けサウンドカード) 通話時などはヘッドフォンに切り替えています。マイクは外付けのもの。 在宅開発環境の良い点 やはり大きなモニター。中央にエディタを大きく開いても左右にブラウザ・チャットツール・音楽プレイヤーなどを広げられるのでいちいち画面を切り替える手間が省けて良いです。コーディング中に好きな音楽をモニターから流せるのも気分転換になって作業に打ち込めます。 在宅開発環境の悪い点 (開発環境ではないですが)飲み物の消費が速いこと…… オフィスだとフリービタミン制度で飲み放題な各種飲み物を自分で調達しなければならないのは困ることが多いです。 逆にそれ以外の開発環境ではあまり困っていないです! 今後、在宅開発環境をどうしたいですか? 現状でもかなり整備していると思うので、掃除をしっかりして使い勝手を維持することが課題…… 3人目 3人目は、事業部長のSさんです。 ディスプレイはEIZOの4Kディスプレイ、キーボードはHHKB HYBRID TYPE-S 無刻印、椅子はアーロンチェア、オーディオインターフェイスはRME Babyface Proを使っています。HHKBは前作のBTから乗り換えましたが、新機能のBluetoothのマルチペアリングが使いやすくてとても気に入ってます。 在宅開発環境の良い点 会社では持ち運んで業務を行うことも多いので、どこでも同じ作業効率が得られるよう、基本的にノートPC一台で完結するようにしていますが、自宅ではディスプレイやキーボードは外付けのものを使っています。やはり大きい画面や打ちやすいキーボードは使い勝手が良いです。会社の椅子もハーマンミラーのセイルチェアですが、自宅のアーロンチェアのほうが細かな調整が出来て自分にはフィットします。部屋がとても静かなので集中できます。 在宅開発環境の悪い点 ビデオ会議が毎日有るのですが、部屋の照明が暗いせいかMacの内蔵カメラだと暗く映っているように感じます。 今後、在宅開発環境をどうしたいですか? スタンディングデスクを検討しています。 4人目 4人目は、エンジニアの @PikkamanV です。 私はプライベートでは主にWindowsのデスクトップマシンで開発していますが、一方でThunderbolt 3ドックに私物のMacをつないで開発することもあります。そこで、リモートワークにおいては業務で使っているMacBook ProをThunderbolt 3ドックにつなぐことで、スムーズにプライベート開発と同等の快適な環境を構築することができました。 【ディスプレイ】 メイン: EIZO FlexScan EV3285 (31.5型 4K) サブ1: EIZO FlexScan EV2450 (23.8型 FHD) サブ2: EIZO FlexScan EV2450 (23.8型 FHD) エディタとターミナルは4Kメインディスプレイ上のVScodeに集約し、サブ1はGitHubなど開発に必要なブラウジングに、サブ2はSlack、はてなブックマーク、Twitterなどコミュニケーションにあてています。3枚のマルチディスプレイ環境によってウィンドウを何枚も重ねたり、仮想デスクトップを行ったり来たりする手間から解放されました。 【キーボード】 東プレ REALFORCE 108U-A XE3100 荷重All 30gのフルキーボードです。残念ながら廃盤になってしまい、後継のREALFORCE R2はスペースキーが長くなってしまったので手に馴染みませんでした。HHKBに30gモデルがあれば使ってみたいのですが、なかなか登場しませんね。 このキーボードはプライベートと共用ですが、会社では同じく荷重30gのREALFORCEでテンキーレスの91UG-Sを使っています。こちらも廃盤なので将来キーボード難民になってしまいそうです。 【マウス】 Microsoft Sculpt Ergonomic Mouse 手首に負担がかからない形状のマウスで、何回か買い換えながら愛用しています。 クリック感もちょうどいいです。 【音響】 スピーカーにFOSTEXのPM0.3Hを使っており、USB DACのPC100USB-HR2でマシンにつないでいます。リモートワークで音楽を聴きながら開発するとき、イヤホンより良い音で聴けるので良いですね。会議の際はLogicoolのヘッドセットH111Rを利用しています。 【ペンタブレット】 XP-PEN Deco 02 自分はデザイナーではないので業務ではあまり出番がないのですが、会議中に画面共有する際に文字などを書き込むとき便利です。 【Thunderbolt 3ドック】 KENSINGTON SD5200T VPN接続を安定して持続させるにはWi-Fiより有線が必要だと考えているためドックを使用しています。また、ELECOMのUSB切替器U3SW-T4と組み合わせることでスイッチひとつでプライベートのWindowsマシン とドックの間でキーボード、マウス、スピーカー、ペンタブレットを共用できる仕組みになっています。 在宅開発環境の良い点 自分に合う機器を選んで開発できるのが最大の利点です。会社でもキーボードとマウスは好きなものを使えるのですが、ディスプレイも自分にとって最高のものを組み合わせられるのは在宅ならではです。 また、まわりの人の気配や視線がないので集中して開発に取り組めるのも良い点です。 在宅開発環境の悪い点 椅子だけは会社のものが素晴らしいので持って帰ろうか迷うレベルでした。 今後、在宅開発環境をどうしたいですか? ほとんど不満はないので椅子を買い換えるくらいですかね? あとはマルチディスプレイを複数の環境で使っているため裏の配線がひどいことになっているので整理したいです。 フリーコメント EIZOのFlexScanシリーズといえば、正確な発色こそColorEdgeシリーズに譲るものの、目を疲れさせない高品質な画面と制御ソフトScreen InStyleで有名ですね。マルチディスプレイ環境でカラーモードや輝度を同期させることができるScreen InStyleですが、残念ながらMac版が存在しません。しかしScreen InStyleがインストールされているWindowsを別のマシンで起動しておくことで、擬似的にMacでも使えるようにすることができます。たとえMacの映像が表示されていても、表示されていない他のInputにWindowsから映像が入力されていれば、ディスプレイ下部のスイッチによる設定変更が検知され他のディスプレイにも反映されるのです。 5人目 5人目はエンジニアのyunagi_nです。 【ディスプレイ】:LG 24UD58-B と Philips 246E7QDSB、上にもう1枚あるのはプライベートのデスクトップ PC 用のディスプレイです。 基本的には MacBook Pro のディスプレイでコードを書いて、 Philips のディスプレイに Slack と予定表・カレンダー、もう1枚は雑にいろいろ映しています。 【椅子】GTRACING GTBEE-RED 比較的安価なゲーミングチェアを使っています。会社のほど良くはないですが、仕事する分には腰が痛くなったりはしていないので、良いと思います。 【デスク】サンワダイレクト 100-DESK039 広くて物が一杯おけるので良いです (そのため散らかってますが)。 【キーボード】 MacBook Pro にくっついているキーボードが好きなのでそれを使っています。後ろにある光っているのはデスクトップ PC 用のキーボードです。 在宅開発環境の良い点 好きな音楽をスピーカーから流せること 設備を好きに自分好みに変えることが出来ること 突然立ち上がって軽い運動したり叫んでも気にする人がいないこと 昼休みに入力切り替えで PC ゲームを立ち上げることが出来ること 在宅開発環境の悪い点 散らかっていることが多いこと 今後、在宅開発環境をどうしたいですか? 棚でも買ってものを整理したいです。 6人目 6人目は、私( @kfly8 )です。 夫婦で並んでゆったり作業できる机が欲しく、200cmの机をDIYして使ってます。 ホームセンターで買ったパイン集成材にオスモカラーのウォールナットとノーマルクリアの塗料を裏捨て塗りして、鉄脚をくっつけてます。当初はウォールナット材を素直に買おうと思ったのですが予算オーバーしそうだったので、手間はかかりますが安い方法にしました。結果的には正解で、質感も十分満足できかつ、天板裏面にPCのマウント取り付けや配線のためにネジやステップルを打ち込みやすい加工のしやすさが気に入っています。鉄脚には有孔ボードを強力磁石で取り付け、電源の配線隠しとキーボード置き場にしています。 作業がしやすいのはもちろんですが、子供も自然に隣に座ってくるのもお気に入りなところです。 机の裏です 鉄脚に有孔ボードを強力磁石で取り付け 在宅開発環境の良い点 ディスプレイなど開発環境はほぼ会社と同じなので、在宅開発環境だからこそ良いことは家族と一緒にいられる時間が長くなったことです! 在宅開発環境の悪い点 間違いなく運動不足 今後、在宅開発環境をどうしたいですか? 会社で使っているハーマンミラーの椅子が腰に疲れを感じないので2脚欲しいのですが、自宅に「あのごついのが並ぶとオフィス感が半端ない(by妻)」ため、シンプルでファブリックな椅子を探し求めてアマゾンの奥地へ向かいたいと思います。 フリーコメント コップはYAPC::Asia 2015の時のタンブラーをずっと使ってます。麦茶が美味しいです。 7人目 7人目は、エンジニアの @wgg_sh です。 机: ニトリのデスク (120cm x 55cm) 部屋の大きさと相談して,扱いやすそうなサイズの机を用意しました モニタ: I-O Data のGigaCrysta (EX-LDGCQ271DB) 2枚 27型,WQHDのモニタです 机の奥行55cmでは,モニタをそのまま置くと近すぎて目を悪くしそうだったのでアームを使って少し後ろに配置しています キーボード: Vortexgear Core (茶軸) カスタマイズ性の高い40%サイズのキーボードです キー配列やコンビネーションキーの機能を書き換えられる特徴から,キーキャップを無刻印のものに変えています パームレスト: ARCHISS Massive Wrist Rest Short 手の高さがキーボードに合うので,手首を痛めずらくなります 在宅開発環境の良い点 人が居なくて快適 PCを持ち運ぶ必要がない 在宅開発環境の悪い点 家の横を救急車が頻繁に通るのでちょっと耳障り... (多いときは10分に1回ぐらい来る) 会社のフリービタミン制度を使えない点 今後、在宅開発環境をどうしたいですか? マウス,もしくはトラックボールとかを検討したいです 8人目 8人目は、ソフトウェアエンジニアの @odan3240 です。 会社支給の MacBook Pro 1台で作業しています。家にディスプレイはありますが、ちゃんとした椅子や机がないため利用していません。これでも困らずにやっていけています。 キーボードは HHKB Professional2 Type-S 白を利用しています。これは物理出社していた時と同じく尊師スタイルで作業をしています。 作業時の姿勢は、クッションの上にうつ伏せか座椅子に座っています。基本的はうつ伏せの姿勢で、気分を変えたいときに座椅子に座るようにしています。 うつ伏せだと腰を痛めることがないので、良いです(他に健康上の問題があるかもしれませんが...)。 在宅開発環境の良い点 家でコードを書くのはいつもうつ伏せなので、この体勢コードが書ける点が嬉しいです。 在宅開発環境の悪い点 うつ伏せだとディスプレイを増やすことが難しいので、常に Slack や VSCode を表示させておくということが難しい点が厳しいです。 今後、在宅開発環境をどうしたいですか? ディスプレイの枚数は増やしたいので、iPad Pro の Sidecar に興味があります。 フリーコメント 他の人と比べると一般人です 9人目 9人目は、エンジニアの @pon_dev です。 会社ではMBP本体とディスプレイ2枚で開発していますが、自宅環境はMBPとディスプレイ1枚と自宅PC(音楽等用)になっています。 【モニター】メインは Pixio の PX5 HAYABUSA2 で、サブは EIZO の FORIS FG2421 【キーボード】REALFORCE R2 「PFU Limited Edition」 【ヘッドホン】ACRO L1000 -> SRH1840 【マイク】ニンテンドーUSBマイク 【机】サンワサプライの昇降デスク 【椅子】ニトリのソファにもなる折りたたみベッド 在宅開発環境の良い点 自宅なのでリラックスして仕事に臨むことができます。 会社と変わらずにスタンディングに切り替えられるデスクのため、ちょっと体勢を変えたい時などに変えられるので、変に体が凝ることも無いです。 また、音楽を聴きたい時も、無音がいい時も、好きにコントロールができるのが良いです。(会社だと無音はなかなか難しいので) 在宅開発環境の悪い点 椅子がソファベッドなのがあまり良くない点です。 今後、在宅開発環境をどうしたいですか? オフィスチェアを何か買いたいです。 また、勤務終了時に自宅の環境に切り替えるのがなかなか面倒臭いので、USB切替器などを導入したいです。 10人目 10人目は、エンジニアの @nonsako です。 リモートワークを始める以前は、オフィスではMacbook Pro + 24型ディスプレイの2画面構成、プライベートではWindowsデスクトップ + ディスプレイ1台で活動していました。リモートワークを始めるタイミングで自宅のデスク周りを改善しました。 【机】 引っ越し当初から使っていたIKEAのデスクです。天板は120x60cmで、スタンド付きのディスプレイとキーボードとマウスを置くには十分な広さです。ここに業務に使うホワイトボードなどを置きたかったのですが、趣味の音楽制作に関する機材を置くと若干窮屈です。そこで、Bauhütteのキーボードスライダーを思い切って購入し、マウスとキーボードを移しました。思った以上に机に余裕ができました。 【トラックボール・キーボード】 オフィスの環境と近づけるため、現在親指タイプのトラックボールと、青軸のフルサイズキーボード(オフィスでは茶軸テンキーレス)を使っています。余裕のない机にはトラックボールが欠かせません。リモート以前は自宅マシンのみの操作に用いていましたが、業務用Macbookでも使いたかったため、USBのスイッチャを買いました。トラックボールの左に置いたボタンを押すことで、両方の環境を行ったり来たり出来るようになりました。 【ディスプレイ】 Macbookをスタンドに置き、EIZOの24型ディスプレイ1台を机に置いて2画面で作業していました。しかし、それぞれのスタンドが空間を圧迫していたので、ディスプレイアームを2台購入し、それぞれ設置しています。これによって、スタンドなしでディスプレイを前に置くことが可能になりました。 【オーディオ】 楽曲制作用のインターフェイスであるTASCAMのUS-2x2をそのまま使っています。ツマミで2つの出力の音量を切り替えられるため、通話中はヘッドホン、そうでないときはスピーカー、といったように音を出し分けるようにしました。ヘッドホンはSHUREのSRH-440です。低価格でありながらフラットに鳴ってくれるので便利です。スピーカーはFOSTEXのPM0.1を使っており、一人暮らしのアパートで迷惑にならない程度でしっかり音が鳴ってくれます。 在宅開発環境の良い点 オフィスの環境とほぼ同一でありながら、オフィスより静かで、スピーカーで音が出せるところがいいです。 また、自炊が可能なため、ごはんが足りなければおかわり出来るのも嬉しいところです。 在宅開発環境の悪い点 若干人恋しくなるのが困ります。会議でもwebだと相手が遠く感じます。 あとは飲食についての福利厚生がなかったり、周囲に店がないのもつらいです。 また、椅子についてはオフィスのと比べて座りづらいです。 今後、在宅開発環境をどうしたいですか? ディスプレイをもう1台増やしたいかなぁと考えています。業務では2枚しか使っていませんし、自宅マシンでの作業も困らないのですが、作業効率改善と趣味の環境改善の一石二鳥って考えると手に入れたくなっちゃいます。あぁボーナスが溶けちゃう…… 11人目 最後は、ディレクタの @NortonSam1 です。 PCは会社から支給されたMacBook Pro(13インチ)を使用しています。 机はないので無印のスツール(中古)を台にしています。アイロン台を机がわりにすることもあります。椅子は会社の先輩に譲って頂いたアウトドアチェアを使っています。長座布団に座ることも多いですね。全ては気分次第です。ディスプレイはありません。キーボード? ごめんなさい何もわかりません...。 在宅開発環境の良い点 写真にもありますが、その日の気分でいろんな場所、いろんな姿勢で作業できるのが嬉しいですね。あとは昼食後にお昼寝ができるのも嬉しいです。リラックスしてまた午後から頑張れます。あとはパートナーのオオサンショウウオくんの存在も大きいです。 在宅開発環境の悪い点 明らかに出すゴミの量が増えましたね。米の消費量も半端ないです。会社の福利厚生のありがたみを感じています。あと人に会わないので風呂に入り忘れます。 今後、在宅開発環境をどうしたいですか? 現状の業務を行うにあたって特に問題は感じていません。今後ディスプレイやその他の機器が必要になれば揃えます。強いて挙げるなら、運動不足になりそうなので定期的に武田真治をデプロイしたいくらいですね。 フリーコメント 退勤後、家にたくさんあるボードゲームを毎日一人で遊んでいます。少し寂しくなってきました。 おわりに 今回の記事ではモバイルファクトリーの社員の在宅開発環境について紹介しました。 それぞれ個性が出て面白いですね。弊社では在宅開発環境を追求する人もそうでない人も募集しております。気軽にお問い合わせください。 recruit.mobilefactory.jp *1 : 実際にお宅に突撃はしませんでした。 *2 : 福利厚生で、ドリンク(ミネラルウォーター、お茶、ジュース等)を無料で提供しています。
アバター
はじめに こんにちは、エンジニアの @ringoh72 です。 弊社のブロックチェーンチームでは、ブロックチェーン用のアプリケーションを簡単に扱えるようにする Quragé Link というサービスを開発しています。 フロント向けライブラリである @uniqys/qurage-link-lib をnpmで公開しており、バージョニングにセマンティックバージョニングを採用しました。 開発を進める上でバージョンの付け方に疑問があったのですが、公式ドキュメントやリポジトリのissueを参照して無事解消したため、当時の背景とともに解説していきます。 TL; DR 特定の規格を扱う場合、1.0.0のリリースは機能の完成だけでなく規格にちゃんと則ってから行う リファクタはセマンティックバージョニングの対象外で、メジャーバージョンを上げる破壊的変更とはみなされない 1.0.0はどう定義すべきか 最初のリリースバージョンどうする? 僕は完成直後のQuragé Linkを引き継ぎ、npmへリリースするのが最初のタスクでした。 当時ぼんやりと2つの選択肢を考えていました。 機能は完成しているのでひとまず 1.0.0 でリリース まだまだ安心できない、 0.1.0 でリリース 「やっぱバーンとやりたい」 これが悲劇を巻き起こすことになりますが、当時は知るよしもありませんでした。 1.0.0ってなんだっけ? 1.0.0のリリース時には知らなかったことなのですが、標準規格に沿っていないインターフェースがありました。 最終的にちゃんと対応したのは3.0.0です。 セマンティックバージョニングには、FAQに 「1.0.0のリリースはいつすべきでしょうか?」 という項目があります。 もし既にプロダクション用途であなたのソフトウェアが利用されているのなら、それは1.0.0であるべきでしょう。またもし安定したAPIを持ち、それに依存しているユーザーが複数いるのなら、それは1.0.0であるべきでしょう。もし後方互換性について多大な心配をしているのなら、それは1.0.0であるべきでしょう。 今回は一番最後の「後方互換性についての多大な心配」について考えます。 最初のリリースをした当時は、機能が完成していたので後方互換性について多大な心配がありました。 しかし、当時「インターフェースが規格に沿っていない」と知っていたら、僕は1.0.0にしたでしょうか? 答えはノーです。 規格に沿うまではいかなる破壊的変更も許容すべきであり、後方互換性について考えなくてもよいと思うためです。 知っていたのであれば0.1.0あたりでリリースして、規格に沿った時点でのリリースを1.0.0としていたように思います。 リファクタリングをしたらパブリックバージョンを上げるべきなのか 内向けリリースノートの作成 だいたい3,4回目くらいのリリースを迎えた頃、内部的な変更や日本語でのリリースノートを書き残すために内部向けリリースノートを作成しました。 publishの際に書くリリースノートは外向けです。 先述したリファクタリングがあった際など、内部的には大きな変更でも外部から見たら小さいことは十分考えられます。 そういった場合に「まあマイナーバージョンだから大丈夫でしょ」と安心できるのは利用者であって、開発者はそうとは限りません。 というかパッチバージョンでも安心できません。 そこで、安心するために「ここのバージョンまでは破壊的変更がなかった」ことを記録する必要性を感じていました。 内部の破壊的変更とsemver その後何度目かのアップデートで、バージョンを決める際に1つの疑問が出てきました。 リファクタリング(内部の破壊的変更)はメジャーバージョンを上げる理由になるのか? です。 結論から言うとこれはノーですが、結局他の破壊的変更があったためメジャーバージョンを上げることになりました。 悩んだ意味そんなになかった… こちらのissue によると、どうやら以下のような方針のようです。 1. セマンティックバージョニングはパブリックAPIについてのもので、そもそも対象ではない 1. パブリックAPIに変化(バグや新規機能、後方互換性の破壊)がない場合リリースすべきでない まとめ セマンティックバージョニングの運用にあたり 1.0.0の基準とは何か? リファクタによる内部の破壊的変更はメジャーバージョンを上げなければならないのか? という疑問を抱えていましたが、 特定の規格を扱う場合、1.0.0のリリースは機能の完成だけでなく規格にちゃんと則ってから行う リファクタはセマンティックバージョニングの対象外で、メジャーバージョンを上げる破壊的変更とはみなされない ということがわかりました。 採用することが決まった時点で一通り公式ドキュメントに目を通していたのですが、1度読んだだけで完璧に運用することは流石に難しかったです。 とはいえよくある疑問は既に解消されていることが多く、今回も公式ドキュメントおよびissueを探すだけで済みました。 この記事が、セマンティックバージョニングを運用する一助となれれば幸いです。
アバター
概要 こんにちは、エンジニアの id:mp0liiu です。 自分は Perl でチーム開発をしているのですが、最近ある外部モジュールの使い方に関するチーム独自のコーディング規約が追加されました。 このコーディング規約に沿ってコードが書けているかどうかは人間の目でチェックする必要があるので、手間がかかりますし、開発時やコードレビュー時に見落としてしまう可能性があります。 そこで Perl コードの構文チェックをするツール Perl::Critic の Policy(ルールのような概念)を作り、チーム独自のコーディング規約を機械的にチェックできるようにしました。 この記事ではその際したことをまとめ、チーム独自のコーディング規約のチェックのために Perl::Critic の Policy が作れるよう、 Perl::Critic の Policy の作り方を解説します。 今回つくった Policy の完全版は こちら のレポジトリにあります。 経緯 自分が担当しているプロダクトでは Data::Validator を利用して引数の型チェックを行っています。 Data::Validator では 通常 validate メソッドで渡した値を名前付き引数としてチェックします。 use Data::Validator; sub add { # 引数の型の指定 state $v = Data::Validator->new( x => 'Int' , y => 'Int' , ); # 引数の型をチェック my $args = $v->validate ( @_ ); return $args->{ x } + $args->{ y } ; } add( x => 2 , y => 3 ); しかし、引数が1つなのに名前付き引数を渡すのは大抵の場合冗長です。 なので最近チームで引数が1つの場合は Data::Validator の拡張機能 StrictSequenced を使って普通の引数として渡すようにする、というコーディング規約が追加されました。 use Data::Validator; sub named { state $v = Data::Validator->new( num => 'Int' ); my $args = $v->validate ( @_ ); return $args->{ num } ; } # 通常はこのように引数が1つでも名前付き引数として渡す必要がある named( num => 1 ); sub sequenced { state $v = Data::Validator ->new( num => 'Int' ) # Data::Validator の拡張機能 StrictSequenced を使う ->with( 'StrictSequenced' ); my $args = $v->validate ( @_ ); return $args->{ num } ; } # StrictSequenced を使っている場合は引数だけを渡す sequenced( 1 ); この規約が導入されると、コードレビューなどの際にコードの書き方をチェックする必要がでてくるわけですが、コードの書き方のチェックを人間がやるのは大変です。 なので構文チェックツールなどで機械的に書き方のチェックをしたくなり、 Perl::Critic の Policy を作ることにしました。 前提知識 Perl::Critic の Policy を実装するにあたって、次のドキュメントを読みました。 PPI のドキュメント Perl::Critic::Util のドキュメント Perl::Critic::Policy のドキュメント Perl::Critic では PPI で静的解析をして構文チェックを行っているため、自分で Policy を実装する場合も PPI のドキュメント構造を走査するようなコードを書くことになります。 なので、PPIの使い方や PDOM(Perl Document Object Model)について理解しているとスムーズに実装が行えるでしょう。 Perl::Critic::Util では Policy を実装するにあたって役に立つような PDOM をパースする関数が提供されています。 簡潔に処理を書けたり、自前で実装すると大変な処理が利用できることもありますので、どんな関数があるか目を通しておくといいと思います。 Perl::Critic::Policy は、全ての Policy の親クラスとなるクラスで、 Policy の実装やAPIについて記述されています。 これらのドキュメントに目を通した上で、既に実装されている Policy のコードを読み、実装の参考にしました。 今回、自分は Perl::Critic::Policy::TryTiny::RequireUse と Perl::Critic::Policy::Subroutines::ProhibitManyArgs を参考にしました。 コードと解説 以上のようなドキュメントに目を通した上で Policy を実装したので、どのようなコードを書いたのかを解説します。 package Perl::Critic::Policy::DataValidator::RequireStrictSequenced ; use strict ; use warnings ; use utf8 ; our $VERSION = '0.01' ; use List::Util qw( any ) ; use Perl::Critic::Utils qw( $SEVERITY_LOWEST parse_arg_list is_class_name is_method_call ) ; # (1) Perl::Critic::Policy を継承して実装 use parent 'Perl::Critic::Policy' ; # (2) サポートする Policy の設定値 sub supported_parameters { return () } # (3) デフォルトのチェックの厳しさ sub default_severity { return $SEVERITY_LOWEST } # (4) デフォルトのtheme sub default_themes { return qw( cosmetic ) } # (5) この Policy が対象とする PPIのクラス名 sub applies_to { return 'PPI::Statement::Variable' } # (6) Policy の構文チェック処理本体 sub violates { my ( $self , $stmt , $doc ) = @_ ; # (7) 'Data::Validator->' とマッチするか my $data_validator = $stmt->find_first ( sub { my ( undef , $child ) = @_ ; return is_class_name( $child ) && $child->content eq 'Data::Validator' ; }); return unless !! $data_validator ; # (8) '->new' とマッチするか my $guess_new = $data_validator->snext_sibling->snext_sibling ; return unless is_method_call( $guess_new ) && $guess_new->content eq 'new' ; # (9) Data::Validator->new には名前付き引数としてハッシュでパラメータをが渡されているはず my @args_passed_to_new = parse_arg_list( $guess_new ); return if @args_passed_to_new % 2 == 1 ; # (10) 引数がない場合はチェックしない my $params_num = int @args_passed_to_new / 2 ; return if $params_num == 0 ; # (11) new の後で '->' とマッチするか my $guess_method_call_operator = $guess_new->snext_sibling->snext_sibling ; return unless defined $guess_method_call_operator ; # (12) '->with' とマッチするか my $guess_with = $guess_method_call_operator->snext_sibling ; return unless is_method_call( $guess_with ) && $guess_with->content eq 'with' ; # (13) with で渡されている引数を見て StrictSequenced が使われているか調べる my @args_passed_to_with = parse_arg_list( $guess_with ); my @extensions_name = map { $_->literal } map { @$_ } @args_passed_to_with ; my $has_strict_sequenced = any { $_ eq 'StrictSequenced' } @extensions_name ; # (14) 引数が1つでかつ StrictSequenced が使われていなければ Policy 違反を報告 if ( $params_num == 1 && ! $has_strict_sequenced ) { return $self->violation ( q{You should use 'StrictSequenced', an extensions of Data::Validator.} , q{Passing named arguments when a subroutine has only one argument is redundant.} , $stmt ); } return ; } 1 ; Policy を実装するには、まず (1) のように Perl::Critic::Policy を継承します。 (2) の supported_parameters メソッドは、 Policy でサポートする設定値のリストを返すメソッドです。今回は特に設定値使うことを考えていなかったので空のリストを返しています。 (3) の default_severity メソッドは、 Policy デフォルトの厳しさ 1 を返すメソッドです。 (4) の default_themes メソッドは Policy がデフォルトで属する Perl::Critic のテーマ 2 のリストを返すメソッドです。適切な設定かどうかの自信はあまりないのですが、今回はコードの健全さに影響するテーマとして maintenance を指定しています。(独自の theme を設定することもできるのかもしれませんが、未検証です。) (5) の applies_to メソッドはこの Policy が対象とするPPIのクラス名を返すメソッドです。 Policy が構文チェックをするときは、 構文チェック中のコードからこのメソッドが返したクラスの PDOM を全部取得して、 取得したPDOMごとに (6) のチェック処理本体を呼び出すという挙動をします。 何もオーバーライドしない場合のデフォルトは PPI::Element なので、すべてのPDOMを対象に Policy のチェック処理が走ることになりますが、 より詳細な対象クラスを指定するとチェック対象のPDOMが絞り込まれるのでパフォーマンスが向上します。 今回は Data::Validator で引数の型チェックをする場合一度変数に Data::Validator のインスタンスを格納することが多いかと思い、 変数宣言文のクラスである PPI::Statement::Variable を指定しています。 (6) の violates メソッドに Policy のチェック処理本体を記述します。 第1引数には applies_to で指定されたチェック対象のPPIクラス(今回は変数宣言文のクラスである PPI::Statement::Variable )のPDOMが、第2引数にはチェック中のコード全体のPPIドキュメントが渡されます。 (7) では変数宣言文のPDOM $stmt の子要素に Data::Validator-> という文字列にマッチするトークンがあるかどうかを、 find_first という引数に渡したコールバックが最初に真を返した要素を取得するメソッドで調べています。 子トークンが Data::Validator-> とマッチしているかは、クラス名であるかどうかを判定する is_class_name 関数と子トークンの中身が Data::Validator であるかを調べて判定しています。 (8) では Data::Validator とマッチした部分のトークンの隣でコンストラクタが呼ばれているかどうかを調べています。 まずコンストラクタとしてのメソッド名 new があると期待される Data::Validator の次の次のトークンを、 snext_sibling という動作に影響しない空白のようなトークンを除いた、次のトークンを参照するメソッドで取得しています。 そしてそれがメソッド呼び出しかどうかを is_method_call 関数で判定し、トークンの中身が new であるかを調ることで、 Data::Validator からコンストラクタが呼ばれるコードであるかを調べています。 (9)、(10) では、 parse_arg_list 関数でコンストラクタにどのような引数が渡されたかを解析し、 Data::Validator のコンストラクタに渡されている引数の数、すなわちそのメソッドの仮引数の数を調べています。 (11)、(12) では Data::Validator の拡張機能を使う with メソッドが呼ばれているかを調べ、 (13) で with メソッドに渡されている引数を解析し、 StrictSequenced が使われているかを調べています。 そして (14) でいよいよ仮引数の数が1つの場合 StrictSequenced が使われているかどうかを調べ、使っていなければ violation メソッドで Perl::Critic::Violation のインスタンスを作って Policy の違反を報告しています。 Policy のテスト テストがあった方が開発もメンテナンスもしやすいので、 Policy のテストを書きながら開発を進めたいところです。 Perl::Critic のインスタンス生成時、引数 --single-policy にテストしたい Policy を指定しその Policy だけで構文チェックをさせるようにした上で、 構文チェックするメソッド critcue にスカラリファレンスで文字列を渡しその文字列をコードとして扱わせ構文チェックすると、テストがしやすいのでおすすめです。 use strict ; use warnings ; use utf8 ; use Test2::V0; use Perl::Critic; my $critic = Perl::Critic->new( '-single-policy' => 'DataValidator::RequireStrictSequenced' , ); my @violations = $critic->critique (\ q{ sub one_args { my $v = Data::Validator->new( "num" => 'Int', )->with(qw/ Method StrictSequenced /); } } ); is @violations , 0 ; done_testing; 実装した Policy を使う 実装した Policy のモジュールが Perl::Critic::Policy 名前空間に属していて、かつ Perl::Critic を動かしている perl から実装した Policy がロードできる状態になっていれば、 Perl::Critic は Policy のモジュールを自動的にロードしてくれます。 なので、後は設定ファイル(通常は .perlcriticrc )で次のように Policy名(Policyのパッケージ名から Perl::Critic::Policy:: を除いたもの ) を追加する設定を記述するなどすれば実装したPolicyが有効になります。 include = DataValidator::RequireStrictSequenced 設定を追加したらCIを回す時にコーディング規約にそったコードになっているかテストしたり、エディタでチェックしたりと活用することができるようになります。 おわりに このように、 PPI や Perl::Critic についての知識があれば、チーム独自のコーディング規約をチェックする Policy を実装できます。 Perlのプロジェクトで人間の目でチーム独自のコーディング規約をチェックしていて、チェックに要する手間や見落としが問題になっているのであれば、 Perl::Critic の Policy を実装して機械的にチェックさせてみてはいかがでしょうか。 どれくらい重大な違反なのかを示すパラメータです。詳細は こちら 。 ↩ カテゴリのような概念です。詳細は こちら 。 ↩
アバター
はじめに こんにちは! モバイルファクトリー Advent Calendar 2019 24日目担当の @PikkamanV です。 今回は運用中のプロダクトのCIをJenkinsからCircleCIへ移行するにあたり特にハードルが高かった点の解決方法を紹介します。 オンプレのJenkinsサーバでフルテストを回すのが前提となっていたリポジトリをCircleCIで扱うにあたり、shallow cloneとsparse checkoutを活用することでテストの前準備の高速化を図りました。 背景 今回扱うリポジトリは物理サーバ上に開発環境を用意することが前提になっており、テストも同様に単一の物理サーバ上で実行されていました。長期間の運用により総コミット数は90,000回を超え、リポジトリのサイズは30 GB弱となっていましたが、社内の強力なサーバ上でJenkinsを利用することで高速にテストを実行できていました。サーバ上に常にローカルリポジトリが存在するので、最新のコミットとの差分だけをリモートリポジトリから取得すればよかったためです。 しかし、CircleCIは通常の設定のままcheckoutするとまっさらな状態からリポジトリをコピーするので、歴史ある巨大なリポジトリではテストの前処理にサイズに比例して時間がかかる問題がありました。そこで、CircleCIにおいてgitのshallow cloneとsparse checkoutを活用することで高速にリポジトリをコピーし、テストの実行開始を早めることを目指しました。 shallow clone 今回のリポジトリは約5年間の運用を経て .git のサイズは約18GBにまで大きくなっていました。リポジトリ全体のサイズが約30GBなので、その占める割合が分かります。そのため、通常の git clone を行うと大変時間がかかりました。しかし、想定しているCI環境においては対象のブランチの最新コミットに対してのみテストが実行できれば十分です。そこで、shallow cloneを使うことよりcheckoutの時間を短縮しました。 command : | git init git remote add origin "$CIRCLE_REPOSITORY_URL" git fetch --depth=2 origin "$CIRCLE_BRANCH" git fetch --depth=1 origin HEAD:refs/remotes/origin/HEAD git checkout "$CIRCLE_BRANCH" git reset --hard "$CIRCLE_SHA1" sparse checkout また歴史的経緯からリポジトリには画像などのサイズの大きいアセットが含まれており、約8.5GBを占めていました。これらをリモートリポジトリからコピーするには時間がかかります。よってsparse checkoutによって、サイズの大きいアセットはclone時に除外することを考えました。sparse checkoutは以下のようにcheckoutするディレクトリを .git/info/sparse-checkout に指定することで設定可能です。あるディレクトリAに含まれる特定のディレクトリBだけを除きたい場合は、 ディレクトリA と !ディレクトリA/ディレクトリB を両方指定することで実現できます。 command : | git config core.sparsecheckout true echo 'ディレクトリ1 ディレクトリ2 ... !除外するディレクトリ1 !除外するディレクトリ2 ... ' >> .git/info/sparse-checkout しかし、除外したアセットに対してもそのファイルの存在を確認するテストなどが書かれていました。ですがサイズの大きなアセットこそが前処理の高速化においてボトルネックになっていたため、sparse checkoutはぜひ導入したい機能でした。ここでテストの中身に注目すると、テストされていた機能はめったに変更が入らず、また他の機能への影響も少ないものであることが分かりました。そこで、日中の開発中は対象のテストを除外して高速化を行う一方、1日1回すべてのテストを実行し、テスト実行時間と網羅性のバランスを取ることにしました。 まずは通常トリガされる build workflowの他に、1日1回実行される nightly workflowを用意します。 nightly workflowでは、sparse checkoutによるアセットの除外を行わない checkout_code_full を設定しています。(上の例で !除外するディレクトリ1 などを削除するイメージです) workflows : version : 2 build : jobs : - checkout_code - frontend : requires : - checkout_code - backend : requires : - checkout_code nightly : triggers : - schedule : # UTC表記 平日の午前7時20分から実行 cron : "20 22 * * 0-4" filters : branches : only : - develop - /base-.*/ jobs : - checkout_code_full - frontend : requires : - checkout_code_full - backend_full : requires : - checkout_code_full さらにアセット関連のテストも実行する backend_full ジョブにおいて環境変数 BUILD_TIME を設定します。 backend_full : docker : - image : ... environment : ... BUILD_TIME : nightly ... そして対象のテストの始めに環境変数を見てテストをスキップするかどうかの処理を追加しました。 use Test::More; ... if ( $ENV{ BUILD_TIME } ne 'nightly' ) { plan skip_all => 'No images' ; } subtest 'subtest_name' => sub { ... } done_testing; こうして1日1回だけ実行されるnightlyジョブでアセット関連のテストを行うことができるようになりました。 まとめ 以上のように、shallow cloneとsparse checkoutを組み合わせた上にCircleCIのworkflowを使い分けることで 日中の開発中はサイズの大きいアセットを除いてテストすることで実行時間を短縮し、開発のストレスを減少させる 1日1回すべてのアセットを含んだテストを実行することでテストの網羅性を確保し、重大な開発の手戻りを防止する の両方を実現することができました。巨大なリポジトリをCircleCIへ移行するにあたってヒントになれば幸いです。
アバター
モバイルファクトリー Advent Calendar 2019 の23日目の記事です。 
ヒューマンリレーションズ部の @overallfactory です。 普段は新卒の総合職採用を中心に活動しています! はじめに 12/11(水)に開催された「 Vue.js for 2020 」にスポンサー企業としてブースを展示させていただきました! 私も非エンジニアながら会社の宣伝をするべく、ブース展示に協力をすることになっていたのですが、 1つ問題が…それは、私がエンジニア向けのイベントに参加したことが今までなかったことです… 参加を依頼された時は、「技術的な話ができない私では、参加しても価値がないかも」 「自分が技術的なことを話せないあまり会社が悪く見えたらどうしよう」など考え過ぎてしまい、イベントに参加するまで緊張と不安でいっぱいでした。 この記事では、そんなエンジニア向けイベント初体験で不安だらけだった私が、イベントに参加して感じたことを書いていきます。 初めて技術イベントに参加される方に、少しでも参考になればと思います! 自分が持っている技術の知識では、全く話の理解ができない 控えめに言って、登壇されてる方のプレゼン内容は全く理解できなかったです。 ちょっとだけ、私もプログラミングをかじったことがあったので、多少はわかるかなと考えていたのですが、甘かったです。 自分が学生時代にかじった程度の知識など全く通用しなかったです。 また、モバイルファクトリーのブースにてエンジニアの方とお話しをしたのですが、 繰り出される質問が全く理解できず、懇親会の序盤はテンパりまくってしまいました。 会社を良く見せようと、「会社のことについての質問はどんなものでも、明快に答えよう」と準備をしていましたが、 凄い勢いで、知らないワードが投げかけられるため、全然うまく質問に答えられず、冷汗が止まらなかったです。 開始数分で、技術的な質問はわからないと正直に言おうと心に決めました。 ちょっとのプログラミング経験で、知ったかぶりをしたところで誰の価値にもならないと思います。 自分には知識がないことを認めて、わからないと言うことも時には大事です。 「わからない」ことを「わからない」と言うことは重要 プレゼン中、懇親会中と、あまりにも自分には理解のできないワードが飛び交っていたので、 途中で自分の恥を捨てて、「技術のことわからないんです!」って言うことにしました。 周りから「な〜んだ、お前技術のことわからないのかよ!」って思われてしまい、 ブースから人が離れていってしまうんじゃないかと不安だったのですが、そんことはなかったです。 むしろ、「技術はわからないんです!」と伝えたら、 「じゃあ、どんな企業なんですか?」「なんでブロックチェーン事業をやっているんですか?」と、 企業についての質問をたくさんしてくれました。 企業説明となれば、技術がわからない私でも話せます。 「面白そうな取り組みですね!」「こんな会社があったんだ!」と驚いてくれる人がいたのを見ると、 少しは私も会社の宣伝に貢献できた気がします。 また、逆にこちらから「どんな技術に興味あるんですか?」「どんなことがその技術でできるんですか?」と聞くと 私にもわかるように、わかりやすい言葉で皆さんが説明してくれました。 エンジニアの皆さんが優しすぎて感動しました。技術的に難しいことを優しく話せる方って本当にすごい! 自分も会社のことや事業のことを、もっとわかりやすく話せるようにならねばと強く感じました。 参加者の知的探究心が高い モバイルファクトリーのブースでは、「Vue.jsの好きなAPI」のアンケートとサンプルコードを展示していたのですが、 ブースの前を通る方のほとんどの方が足を止めて、サンプルコードをかじりつくようにみていました。 サンプルを見て気になることがあれば質問していただいたり、近くの人と議論したりと、本当に皆さん知的好奇心が高いなぁと 終始感心させられっぱなしでした。 師走の忙しさで最近インプットをサボり気味だったのですが、参加者の方を見て、自分も採用や広報のことをもっと深く学んでいかないとと刺激をもらいました。 ちなみに、「好きなAPI」についてのアンケートにはブースに来てくれたほとんどの方が回答してくれました! 約来場者の30人の方が回答してくれて15票でComposition APIが一番人気であるという結果になりました。 ご協力してくださった皆さんありがとうございました! speakerdeck.com お酒は偉大だ。。。 懇親会でお酒が入ってくると、緊張が抜けてきて、 エンジニア、非エンジニア関係なくざっくばらんにお話をすることができました。 モバイルファクトリーでは、冷凍みかんをスポンサーとして120個ほど提供したのですが、 参加者の方や他のスポンサーの方から「なんで冷凍みかんを選んだのか?」と質問攻めにされました。 まさか冷凍みかんの話で盛り上がると思っていなかったですが、たくさんのエンジニアの方とお話しできるきっかけになって よかったです。冷凍みかんありがとう笑 まとめ 以上が私がイベントに参加して感じたことです。 参加してみるまで、「エンジニアの方は技術的な話を参加者は求めているだろう」と決めつけて、 「エンジニアの方と何を話したらいいんだろう?」「相手にメリットのある話ができるかな?」って不安になっていました。 でも、それは自分の勝手な思い込みでした。 実際に「技術のことは話せない」と伝えた上でも、「会社の事業やミッション」「会社の風土」について聞いてくれる方はたくさんいましたし、 話を聞いた上で「面白い」「こんな会社があったなんて知れてよかった!」と、言ってくれる方もいました。 変にカッコつけてなんでも答えようとせず、「自分は技術のことは話せないが、これなら話せる!」と相手に伝えてあげたからこそ、 コミュニケーションが円滑に進み、自分が一番伝えたかった会社のことを伝えることができたと思います。
アバター
この記事は モバイルファクトリー Advent Calendar 2019 の22日目の記事です。 こんにちは、ヒューマンリレーションズ部の @chan_center です。 約4年前にモバイルファクトリーに転職をしてきて初めてエンジニア採用を経験し、大変さを知りました。 現在は中途・新卒エンジニアの採用をメインで担当しています。 エンジニアの採用は、有効求人倍率の通り(詳細割愛)なかなかの売り手市場となっており、現場のエンジニアのみなさんにも採用担当から採用活動をお願いすることがどんどん増えていくと思います。 そんな時、「面倒だな」と思わずに「やってみたい!」と思えるように、今回は私がこれまで採用を7, 8年やってきて思う採用の楽しさについて書きたいと思います! 採用の楽しさ いろんな人の素敵な人生の話が聞ける 正解はない、自分で探し出すこと ターゲットを探すことに必死なうちに強固なメンタルが出来上がる 地方にも海外にもいけるぞ! 何より働いた後のお酒が美味しい それではひとつずつ簡単に説明させていただきます。 1. いろんな人の素敵な人生の話が聞ける 初めて会う人の人生に触れ、深掘りしていくことができます。こんなこと普段だとあんまりないですよね。 更には、研究やプログラミング、ゲームなど、学生時代(割と早い年頃)から、何かを突き詰めて来た人が多く、またなぜそれを突き詰めたのかと理由もさまざまで、自分になかった考え方や価値観に触れることができます。 例えば、中学時代に某オンラインボードゲームの世界ランク1位をとった時のPDCAの回し方にはとても驚きました(現モバファク社員)。 ただ、続けているとこれまでしてきたことや選んだ選択肢に対して、なぜ?とどんどん知りたくなってしまうため、合コンなどでのウケは悪くなります。ご注意ください。 2. 正解はない、自分で探し出すこと 相手は人間なので、同じアプローチをしても、感じ方も返ってくる反応も人によって全く違います。 そんな中で、相手を知り、相手のことを考え、何をどういう言葉で誰からいつ伝えるか、、組み立てていくこと、そして結果が返ってくる。 正解はない、というおもしろさと難しさ、良いですよね。 ただ、採用と恋愛は同じ、とよく言われなんとなく分かるような気もしますが、私は違うと信じています。 なぜなら、私はモバイルファクトリーに入ってから、採用はどうにかやってきましたが、恋愛はどうにもなっていないので。 3. ターゲットを探すことに必死なうちに強固なメンタルが出来上がる ターゲットとなる人に出会えないとそもそも何も始まりません。 たまたま紹介されることもありますが、どこにいるか分からないターゲットを自ら探し出し、引っ張ってくることが大切であり、大変です。 少しの可能性があるのであれば、声をかけます。当たって砕けろの精神ですね。砕けることを繰り返していくうちメンタルがどんどん強固になっていきます。 ただ、メンタルが強くなりすぎて、何も怖くない自分が怖くなるときが時々ありますので強固になりすぎにはご注意ください。 4. 地方にも海外にもいけるぞ! 昨今の厳しい採用環境であるため、自ら出向く、ということが重要です。 日本各地、もはや海外まで、そこにターゲットとなる人がたくさんいるのであれば行きます。 出張は移動で体力的に疲れることも多いですが、どうにか夜に現地の美味しい食事とお酒を嗜んだり、空いた時間で温泉に入ってみたり、空いた時間で観光地に寄ってみたり、何より位置ゲームが捗る、など楽しいことも多いです! (ちなみに、モバファクの位置ゲームは こちら ) ただ、大雪でマイナス15度を経験したり、傘なしでスコールの中マーライオンを見に行ったり、海外で現地の方に1日に3回道を聞かれたり、海外のホテルでオートロックの鍵を持たず締め出されたり、大変なこともありますが、家で動画を観ているよりは話のネタは増えていきます。 出張✈️✈️ 美味しいし、すごく寒い。 pic.twitter.com/XXCRPhEEJh — ちゅん🀄️@モバファク (@chan_center) 2019年11月12日 帰りにイタンキ浜でコナンごっこを楽しんだ。 pic.twitter.com/3ucWGoC8Nk — ちゅん🀄️@モバファク (@chan_center) 2019年11月13日 5. 何より働いた後のお酒が美味しい これはもう、採用とは関係ないですが、働いた後のお酒は、何より一番美味しいです!!! ただ、休日起きてからすぐに飲むお酒は未だにやめられないですが、やはり仕事の後ですね! 最後に 採用の楽しさが少しでも伝わったでしょうか。 もし、今後採用担当から採用活動をお願いされた時は、上記のこと思い出して、わくわくしながら採用に携わってくれると嬉しいです! ということで、モバイルファクトリーでは絶賛エンジニアを募集しております。 お気軽にご連絡ください!
アバター
はじめに この記事は モバイルファクトリー Advent Calendar 2019 の21日目の記事です。 こんにちは、新卒4年目のエンジニアの id:tsukumaru です。 4年目となり、後輩がかなり増えました。そして、今まで以上に後輩の手本となり、周りをリードしていくことを求められるようになりました。 あるとき、とある後輩から「振り返りのファシリテーションのコツを教えて欲しい」という質問が来ました。ファシリテーション自体は何度か経験がありましたが、自分が気をつけていることをしっかりまとめたことがなく、その場ですぐに答えることができませんでした。 そこで、今回はファシリテーションをするときに自分が気をつけていること ベスト5 を紹介してみようと思います。 いま振り返りのファシリテーションの仕方に悩んでいる方が、この記事を読んでなんとなく「自分でもできそう」と思ってもらえれば幸いです。 ※ 今回振り返りの手法は特定していません。振り返り全般に対して気をつけていることとしてお読みください。 気をつけていること 自分がファシリテーションをするときは、基本的に曖昧な部分を無くし、参加メンバー全員が 同じものを見ている 状況を目指しています。 これを具体化して5つに分解すると、以下のようになります。 1. つまりどういうことか? 複数の解釈ができそうな意見を具体化します。 例えば、「ペアプロが勉強になった」という意見がでたとします。 これだけだと「(ペアプロのやり方を学ぶことができて)勉強になった」のか、「(ペアプロをすることで分からないところを効率的に教えてもらえて)勉強になった」のかがわかりません。 いろんな解釈ができるので、その場にいる参加メンバーは自由にカッコの中身を想像してしまいます。 本当に言いたいこと、知見になりそうな部分を知るために、「ペアプロのどういう面が勉強になりましたか?」などと質問し具体化していきます。 そして、相手の話を一通り聞いた後、「なるほど、ということは〇〇ということですね」とまとめます。するとそれを聞いている周りのメンバーは「〇〇したことがよかった」と 認識を一致させる ことができます。 2. 他の立場ならどうか? 出された意見をいろんな方向から見ることを促します。 例として、「ディレクターが企画を決めるのが遅くなってしまった」という意見がでたとします。 この場合、ネクストアクションとしては「ディレクターがPMと事前によく話し合う」のような意見が出がちです。 このネクストアクションで本当にいいのでしょうか。 振り返りは、成功/失敗を共有して チームとして 次の改善案をだしていくためのものだと思います。 そこで、ディレクター以外の立場としての意見を求めてみます。 「他の人(や職種)としても決めるのが遅かったと思ったか?」「遅くなってしまった原因はなにか?」「エンジニア、デザイナーとしてできたことはなかったか?」など深掘りしてみると、チームとしての振り返りができるようになります。 プラスな意見が出た場合も同じで、「エンジニア同士でホワイトボードを使って認識を共有できたのがよかった」という意見が出た場合、「他のエンジニアメンバーもそう思うか?」「エンジニアとデザイナーでは使えたか?」など違う目線を提供することを意識します。 本当に エンジニア同士で ホワイトボードを使って認識を共有できたのが知見なのか を探っていきます。 3. 目的に沿っているか? 出された意見が振り返りの目的に沿うように調整します。 振り返りではまず目的(何のためにふりかえるのか)を決め、みんなで意識を合わせた後に振り返りを行なうと思います。 しかし、振り返りをしていると、目的に沿った意見もあれば沿わない意見もあるなど様々な意見がでてきます。どの意見を深掘りしたらいいのか、どういう方向にまとめていけばいいのか迷うこともでてくるでしょう。 そんなときは 最初に決めた目的を振り返りましょう 。当たり前ですが、とても大事なことです。 「目的に対して有意義な意見か?」「このまま話を進めて目的に沿ったネクストアクションがだせそうか?」と心の中で考えます。 ここで気をつけたいのは、 声に出さない ということです。「この意見は目的に沿っていません!」とファシリテーターが言ってしまうと、参加メンバーが意見を出すのを萎縮してしまいます。 あくまで出された意見は尊重しつつ、深掘りの度合いで進行する際に調整します。 4. みんなが話しているか? 全員が公平に発言できるように配慮を行います。 振り返りは多くの場合で関わっているチームメンバー全員が参加するので、話す人、話さない人がどうしても出てきてしまいます。 話す人の意見ばかりを取り入れ振り返りを進めていってしまうと、それはチームとしての振り返りではなく、 有志な人たちだけの振り返り となってしまい無意味なものになってしまいます。 そこで、ファシリテーターは 参加メンバー全員が公平に発言できるように 配慮を行います。 注意したいのは、 発言しない人が悪いわけではない ということです。思ったことをそのまま話せる人もいれば、よく考えてから発言したい人もいます。発言のタイミングは人によって違うことを理解した上で、相手の負担にならない程度に話を振っていきます。 逆に、ベテラン社員やリーダーなどの発言力のあるメンバーの意見に誘導されがちな場合は、「こういう意見もあるみたいですが、他の方はどうですか?」や、「〇〇さんはどう思いますか?」などで別の意見を求めてみると良いです。 このとき、全く同意見でも問題ないです。 みんなで振り返りを進めているという雰囲気をつくることが大事 です。 5. 時間は大丈夫か? 限られた時間で振り返りを終えられるように意識します。 時間を守ることは大事なことですが、地味に難しいことでもありますよね。 自分がファシリテーターをやる時はタイムキーパーも兼ねました。他にタイムキーパーをつけておいてもいいでしょう。 振り返りに熱中しているとすぐに時間が経ってしまうので、全体の意見の数を見つつ、前述の [3. 目的に沿っているか?] も考えながら時間内に終わるように調整します。 一人で全部調整しようとすると大変なので、「あと30分なのでこれは飛ばしてもいいですか?」だったり「こういう進め方で行こうと思うのですが、どうでしょう?」など都度都度 参加メンバーと進行の認識を合わせていきましょう 。 自分では思いつかない方法がメンバーから出てくることもあります。 以上が、自分が振り返りのファシリテーターをする際に気をつけていることベスト5です。 まとめると、 つまりどういうことか? 意見を具体化する 立場を変えたらどうか? 意見を様々な視点で見る 目的に沿っているか? 目的に合わせて意見に優先度をつける みんなが話しているか? 声の大小に関わらず、公平に意見を言えるようにする 時間は大丈夫か? 参加メンバーと協力しながら時間内に終わらせる 頭文字をとると「 つたもみじ 」ですね。振り返りといえば つたもみじ 。振り返りといえば つたもみじ 。 紅葉がきれいな季節は終わってしまいましたが、これを意識してより良いファシリテーションができるようになれば幸いです。 自分もまだまだ勉強中なので、 つたもみじ を完璧にできるようになるために経験を積んでいこうと思います。 🍁
アバター