Advent Calendar 0217

12月ぜんっぜん書いてないな!!って思ったので、

f:id:o0h:20171224192312p:plain

その反証と言うか言い訳と言うか・・・で、諸所に書いたものをまとめとして残しておく!!というもの。
今月はAdvent Calendarにも参加していたので、このブログで書くような雑な話ってより「他人が読むかもしれない」フォーカスで書いたのが4本。疲れた・・・w
もう12月25日を過ぎたので、それらを連ねてみる

qiita.com

qiita.com

BigQueryのやつは、書いていて本当に疲れた・・・しかしながら自分としても割と直近で使っていたHotな知識だったし、そこら辺を改めて丁寧に理解できたなっていう点では甲斐はあった

composer installは早くならないけど意外とそこまで困らないのかもなって思った話

続き的なもの。 この前、「うまくhackしたらインストール早くなるの?」という話を書いて見た。

魅力あるし、単純に勉強としてcomposerの挙動を知るぞ!!は面白そうなのだけど、「現実問題としてほしいかい?」がある。
あー、まぁ、ほしいけど、「何も知らないところからcomposerのプラグインを書く(大変そう)」事のコストと、「今直面している問題の所在と大きさは?」のバランス。

で、結論として、「意外と困ってないかもな」となった。

この辺りは、職場での開発・デプロイフローがどうなっているか?という話次第だった。*1

  • dockerで、vendor含めてprojectのソースコードをホストから共有する形で食わせながら、処理はコンテナ側でやる
  • デプロイは、travis-ci上でビルドして配布

という現状。

で、「遅くて困る」かどうかは

  • composerはなぜ遅いのか、何をクリアすれば遅さが気にならなくなるのか
  • composerの遅さが気にならなくなるのはどんな場面か
  • 開発時、デプロイ時に問題となるか(回避できるか)

という複合だと思う

なんで遅いの

については、色々ありそうだ・・ ただ今回の話は、「各パッケージを並列DLできたら解消し得る問題」に絞りたい。
そうなると、「DLの遅さ」だ。*2

で、これに関しては標準のキャッシュを活かせればほぼ解決する。・・全てではないが、まぁかなり嬉しいレベルで軽減する。

いつ問題になるの

  1. 新規にinstallするとき
  2. .lockを利用してinstallするとき
  3. requireするとき
  4. updateするとき

が、「何かパッケージを落としてくるとき」なので、影響を受ける。
余談だが、ただし2に関しては「依存状態の解消を行う」ような処理は完了している!という状況といって差し支えないと思う。そのため、コンフリクトする可能性を前提として排除できるからパラで落として来て問題なくない?というのが前に書いた内容だった。

開発時やデプロイ時に問題となるか

「遅さやばい」となのはキャッシュがきかない時で、「日常的に行う」のは2になると思う。
もっというと、ローカルでの開発において(ブランチの切り替えやdocker imageのビルドに比べたら)composer installなんて「やらないで済むならやらないで済ませたい(必然性が低い)」ものではないか。

なので、「問題となるか否か」は「キャッシュがきけば問題ない」から「キャッシュが効く場面を最大化できれば実質解決する」はず。

デプロイについて

travis-ci上でdocker imageをビルドしているという話をしました。
で、ci上にキャッシュを残す機能がある。.travis.ymlに caches/directoriesを指定してあげると、そのディレクトリがピルドプロセスをまたいで共有されるようになるので、$COMPOSER_HOME/cache を対象にしてあげれば良い。そうすることで、「いちいちpackagis→githubからダウンロードしてこないで済む」ことになる。もちろんvendorディレクトリごとキャッシュしたりもできるし。

これを利用して、CMD等でコンテナに作用するのではなく、ビルド時にcomposer installをするよな形にして、「installは1回だけに、しかもその1回を高速で完了させる」という要望は叶えられる

localでの開発時

これも「毎回クリーンインストールはしないよね・・・」という話は大きい。
基本的な戦略は同じで、「キャッシュを使いまわせるようにする」なのだが、imageに含めるよりかはマウントする形でホストからコンテナに渡す方が自然な感じがする。まぁ共有すゆのは「キャッシュじゃなくてvendor自体」になると思うので、composer.lockからのinstallをかけところで所有済みパッケージとのdiffがない!となり、そもそもキャッシュを参照しにいかないで済む場面が大半な気はする。

ということで

自分の今触れている状況においては、composer install自体はそんなに問題になっていなさそうだなーというかんそ

*1:個人では今は複雑なアプリケーション書いていないので!

*2:そういう意味だと、「ルートパッケージの並行DLは許容しない」「各パッケージごとに、依存パッケージはパラレルで落としてくる」みたいな制御が出来たら効果高いのでは・・

composer installをめっちゃ早くできるのかな?

という話を、社内でしておりました。
「そういえばprestissimoって本体に取り込まれる?とかって話なかったっけ」てな話題となり、PR見てみたら「難がありそ」と。

それが I think that it’s difficult to support conflict, suggest, provide and replace dependencies.かな?と思った。。

なるほどな〜たしかにな〜難しそう〜〜と思いつつ、それにしたって「composer isntallがめっちゃ早くなるよ」は魅力。

逆に言うと、「インストールの時だけならパラレルでDLしても大丈夫じゃない?」って言う気もしたのでした。
composerでパッケージの取り込みを行うのは、主に

  1. install
  2. require
  3. update

であって、このうち「1」については「依存パッケージの衝突が起きていないか」というチェックは、すでに終わっているフェーズにあるのでは? と。 (雑な見方の気もする・・・

あまりcomposerの内部処理を追ったことがないのだけど、「installのときだけこのプラグインを有効にする!」ということができれば、もしかしたら大丈夫だったりするのかなー。

どっかで実験してみよ

Configure::readOrFail()っていうのがあって便利だからみんなで使おう

CakePHPorFailシリーズ

Configure に、定数ぽい値を持たせていたりしている。

※ イミュータブルじゃなくなるし、constとしたほうが良いのでない?という観点はあります。
※ 「いや、stageによって異なる値を上書きしたりしたいじゃん!」という声もありますが、それはdotenvや環境変数でどうにかなりそうな問題
※ まぁ色々あって、Configure::write() に寄せているよ!というお話をスタート地点とさせていただきます。

直接 ::write()メソッドを用いて書き込む〜というよりかは、 config/app.php のような「設定ファイル」を介在させて食わせて、という風な。

「絶対に設定しないと行けない」という制約

例えば、cakephp/appを見てみると、 App.encodingenv('APP_ENCODING', 'UTF-8') と書かれていたりするじゃないですか。 みたいな感じで、

  1. 環境変数として値を用意して
  2. Configureに食わせる

という利用シーンはあると思います*1
その中には、「どの環境においても、絶対に設定されていなければ困る」というものもあると思うのです。

Configure::read() とすると、「キーが存在しなかった場合はnull」を返します。
場合によっては、「nullのまま進んでは困る」と。
このときに役立つのがConfigure::readOrFail() です。 Class Cake\Core\Configure | CakePHP 3.5

keyが未設定、もしくはされているがnullいなっている 場合に、RuntimeExceptionを投げるようになります。
read()になっている箇所を置き換えるだけで、「ちょっと事故った、しかし事故が大きくなりすぎる前に気付く」ことがしやすくなります。

まだまだあるぞ! orFail()シリーズで堅牢なプログラミング

良きようにfail fastになるな、という利点です。 Configure::read() 以外にも、 orFail()なメソッドはちょこちょこあります。

f:id:o0h:20171124221702p:plain

  • SELECTを試みたレコードが1件もなかった場合に例外を投げる Query::firstOrFail()
  • saveを試みて(validation error/ domain rule violationで)保存ができなかった場合に例外を投げる Table::saveOrFail()
  • deleteを試みて、(entityにPKがない / entityが「isNew()」である / domain rule violationで)削除ができなかった場合に例外を投げる Table::deleteOrFail()

こういった顔ぶれです。 便利ですね😁

*1:最も、「読み出せなかったときのために初期値を用意して扱う」というのが良いのかもしれませんが・・

我々はcomposer scriptをもっとカジュアルに使っていってもいいかもねの話②

ふと気付いて実行してみたら便利っぽい使い方ができたので、 前回の続きみたいなエントリー。 daisuki.nichiyoubi.land

自分で使うプロジェクトに関しては、composer でどこからでも叩ける用にDockerfileなんかでパスを通している。

RUN wget http://getcomposer.org/composer.phar && \
    mv composer.phar /usr/bin/composer && \
    chmod 0755 /usr/bin/composer

みたいな。

で、composer.jsonスクリプトで例えば

    "scripts": {
        "test": "phpunit"
    },

なんて書いておいて。

こうすることで、ホスト上から

docker-compose run my_service composer test

みたいに、気軽にコマンドを叩くことができるようになる。 CodeSnifferや静的解析だったりとか、そこら辺をすぐに使えるようになるかも? あと内容によってはgit pre-push hookとかと合わせてもいいのかもしれないなぁ。

我々はcomposer scriptをもっとカジュアルに使っていってもいいかもねの話

composer script

というものがある。 Scripts - Composer

あまり「自分らが開発しているPJ」において拡張しまくっていたりはしないものの、 これを「当たり前に使う」ようなレベルまで持っていくと、まぁ便利ではあるんだろうな〜とは思う。 もちろん、各種イベントにフックして実行する手続きをドン!!というのはもちろんとして、 「そうじゃなくても!」だ。

例えば、CakePHPでは以下のようになっている。

    "scripts": {
        "check": [
            "@cs-check",
            "@test"
        ],
        "cs-check": "phpcs --colors -p ./src ./tests",
        "cs-fix": "phpcbf --colors ./src ./tests",
        "test": "phpunit",
        "test-coverage": "phpunit --coverage-clover=clover.xml"
    }

これがあるので、composer check などとするだけでCodeSnifferとtestが走り出すのだ。 個人でやっている内容については、例えるなら.bashrcなどに書くような「便利エイリアス」くらいの働きはしてくれそう。 チームでやっているのであれば「このくらいは、普段から(なるべく)意識しておこうな!」というスタンダードの表明的な役割はあるのではないか。

例えば「○○ツールで静的解析をやるときは、このphp.iniを食わせてからやるぜ!」とかいうのは、 いちいち覚えておくのが面倒くさそう。それが composer check くらいで済むのなら、嬉しくないですか? もしくは、よくありそうなのはCIに任せたい一連の処理をPHPで記述しておいて、composer経由で叩かせる・・とかか。

なんか使いみちあるかなー

今更ながらPHPStormの「Diagrams」に感動したのでメモ

コードリーディングの補助ツールとしてのIDE

「この機能がいいね」と君が言ったら・・・ 記念日である。

GuzzleHttpを用いてアプリケーションを書いていて、例外処理を行おうとしていて少し困った。 「ClientExceptionServerExceptionと、あとBadResponseExceptionが来る可能性があるなー」と。 サーバー例外!と変な応答例外!はなんとなく近い気がする(1つの共通クラスで丸っととってこれそうなイメージ)、ただクライアントが変!!のときは、何か違うかな・・・?と。名前から受ける印象。

で、「そういえば」と思ってクラス名を右クリックした結果。

f:id:o0h:20171114215522p:plain f:id:o0h:20171114215541p:plain な〜んだ、どちらも RequestException を取っているのね!であれば、それで拾ってしまって良さそうだな。 getRequest() getResponse()どちらも通る前提での実装ができる。

未知のコード・未知の知識として、「正直、細部まで丁寧に読んだり追ったりしている余裕はないぞ・・・」というときに、すごく助けられるな〜と思った次第。

Viewing Diagram - Help | PhpStorm