”あれの元ネタを探りに行く"、準備はできている。旅は始まっていない。

この記事は積読 Advent Calendar 2023、2日目の記事です。
昨日はげんえいさんまだ読んでないけどおすすめしたい本 2023年版 | gennei's blogでした。
随分と面白そうな書籍が紹介されていて・・・というかこの中から実際に”積んで<購入して>"しまったのですが、これが25回続くんですか。恐ろしい…

こちらが、「積読は体に良い」というコンセプトを表現したイラストです。温かく居心地の良い雰囲気の中、多くの本に囲まれてリラックスしている人物が描かれています。

ChatGPTさんに挿絵も書いてもらいました。
本に飲み物を置かないでおくれ。

さて、私からは、「この言葉を浴びたい!!!!」と思って買ったbut積んでいる本たちの紹介です。
思想や知識と出会うのは「旅」のようにも感じます。
そんな可能性を予感する本が「もう自分の家に積んである!」というのは、すなわち、「旅に出る準備」は出来ているという事です。
積んでしまえば安心、いつでも出発できますので。

・・・・これを「安心」と呼ぶのか「慢心」と呼ぶのかは、人次第でしょう。

という訳で、5冊ピックアップしてみました。
順不同で紹介します。

積読の紹介

Project Myopia

Project Myopia

Project Myopia

Amazon

翻訳の発売当初から一気に話題になっていた『Team Toplogies』で、こんな言葉が引用されています。
自分以外にも、強く印象に残っている人が多いのではないでしょうか?

ハイパフォーマンスなチームを解散するのは、単なる破壊行為では済まない。企業レベルのサイコパスと呼ぶべきものだ

この出典が、こちらの『Project Myopia』になります。

プロジェクト単位での「コツ」「ノウハウ」は、日本語でもアクセスできるリソースが充実していると感じます。
しかしながら、プロジェクトを超えた先の、「機能する安定したチーム」を実現し、育むには・・?という情報がもっと欲しいな、とも思います。自分も組織周りの仕事をしながら、良い文献がないかなぁと探していました。

そんな時に、チームを解散させる事に対して強い言葉で批判している本がある!!!となれば、気になっちゃいますよね。

サーバントリーダーシップ

サーバントリーダー」、十分に普及していてよく耳にする言葉だと思います。
何となくは分かる。が、しかし「理解がふんわりしていないか?」とも感じます。

ロバート・K・グリーンリーフ博士は、サーバントリーダーシップの提唱者です。
その人の著述が書籍化されている、よし当たってみよう!という次第です。

自分の周りでも、「自分はサーバントリーダーである(を目指している)」と発言している人を目にすることがあります。
しかし、その実、「自身の考えを述べない」「しっかりと周りを導いている感じがしない」といった振る舞いが見られて、これは機能不全的な中立と何が違うのか?とも感じました。
ただの「執事のような親切なお世話係」であれば、それで良いのだと思うのですけど。「リーダーシップ」を持っているとは言えないのでは・・・?という違和感です。

多分、本来はそうじゃないんでしょうね。
この言葉を嫌いになる前に自分の目でちゃんと見ておこう、と考えて積んだ1冊。

Quality is Still Free

ワインバーグのシステム思考法 ソフトウェア文化を創る〈1〉は興味深く刺激に溢れる本なのですが、その中で次のような一節があります。

クロスビーの『品質はタダである』という本を読んだ方々は, ソフトウェア品質についてのわたしの見方がクロスビーの見方とおおむね一致していることに気がつくだろう。

G.M.ワインバーグ・大野 徇郎訳 (1994)『ワインバーグのシステム思考法 ソフトウェア文化を創る』共立出版, p.17

「品質はタダ」という言葉自体もキャッチーですが、ワインバーグ氏の品質に対する考え方の軸みたいなものを深く眺められるのであれば、それは触れておきたい・・・と思ったのです。

加えて、この言葉を聞いた時に、このブログ記事を想起しました。

「品質“実質”無料キャンペーン」を開始しました - pixiv inside

当時も「すごいインパクトのある呼び方だ〜」と思っていたのですが、元ネタがあるのか!!!と。 今、改めて読んでみると、本文でも触れてありました。

実際のテーマにする際には、クロスビーの「Quality is Free」にあやかる面もありました

当然、「質とスピード」のt-wadaさんも言及があり。

品質を犠牲にすることでソフトウェア開発のスピードは上がるのか? 和田卓人氏による 「質とスピード」(後編)。デブサミ2020 - Publickey

ここまで来たら、もう原典にも興味津々で、買うぞ!!!と意気込んでいたのですが・・・
日本語訳のクオリティ・マネジメント―よい品質をタダで手に入れる法も、原典のQuality Is Free: The Art of Making Quality Certainも手に入りにくそうな雰囲気でした。

なんとか、「Quality is Still Free」は中古で入手できたので、読むぞ!!!と思っているところです。

企業文化

業務上、あるいはアジャイルソフトウェア開発やリーダーシップについての学びを進める中で、組織や企業における「文化」は自分の中で最重要なトピックの1つになりました。
「良い文化を作ろう」「文化を強く浸透させよう」と、こんな調子ですね。

しかしながら、「じゃあ企業における文化とはどんな存在なのか・・・説明できるのか?」と思ったタイミングがありまして。
気になってしまったものは仕方ないので「元ネタと呼んで差し支え無さそうな存在に当たってみよう」です。

エドガー・H・シャイン博士の書籍は、一気に家にやってきては増殖を続けているような印象がありますね・・・・

「組織文化とリーダーシップ」も気になっているのですが、こちらはまだ手に入れておらずにいます。

エンタープライズアプリケーションアーキテクチャパターン

これは「読む」という感じなのか・・・?という気もしつつ。
それこそ「UnitOfWork」とか「ActiveRecord」とか、色々な名称・パターンの元ネタといって差し支えないはず。

なぜ読みたいのか?も、なぜ読めていないのか?も、同業の皆さんだと思い浮かぶ話はありそうで、多分それはその通りなのではないかとも思います・・・😇
まぁでも、本当にどこかで目を通しておくべきだとは思っています。がんばろう

まとめ

「元ネタ(原典)に当たってみよう」というのは、割と大事にしている気持ちです。

『チームが機能するとはどういうことか?』を呼んだ時には「心理的安全性って言葉(を口にする人)、何か嫌いだな〜」って思っていたのが覆りましたし、「顧客が本当に必要だったもの」も『オレゴン大学の実験』を読んだりアレグザンダーのパターン・ランゲージについて知って行くことで、より面白く感じられるものになりました。
割と最近になってから読んだ『人月の神話』も、非常に刺さると同時に発見もありましたし、XPなんかも『エクストリームプログラミング』を読んでみると「単なるプラクティス集ではなく、とても豊かな物語が込められているかもな」とすら思えたりもします。

これは、決して「古典」に限った話ではなくて、「みんなが知っている言葉」は「ひとまず本を読んでみておく」というのは大事だなぁと感じます。
例えば最近で言えば「4keys」「チームトポロジー」も(ややバズワード的に?)よく出回っている印象もあり、表面的に切り取って「何となく分かったつもりになって真似をする」なんて勿体ないですよね。
労を惜しんだり高を括って損をするのは悔しいので、謙虚に色々な知識・情報に触れていきたいものです。

つまり、これまでもこれからも本が増え続ける・・・・!
お疲れ様でした。

PHPStanを利用しているPJにおけるbaselineの進化を追う

会社などでPHPStanを導入したり荒らしたりする事があるのですが、「まずは入れてみた」状態においては、baselineの記述量をなくしていくぞ!!!!という取り組みがあります。
最終的にはbaselineがなくなるのが健全な筈ですので、その解消作業の進捗やトレンドを分かり易くしておきたいのです。

そんな時に、ふと見つけたのが staabm/phpstan-baseline-analysis でした。

コレは、「baselineファイルをソースとして、分析することで、見えてくるものがあるのではないか?」という面白ツールです。
とっても素敵なアイディア!

Software Architecture Metricsを読んだ時に、「GItのデータを用いて、各種活動の発生状況や対象について分析する」といった観点が紹介されていて、なるほど〜と思った*1のですが。
「普段の活動の中で何かしらの動機をもって作成されたり修正されたデータや、あるいは”品質だ!!!管理だ!!!"という意図がなくとも生み落とされる記録」みたいなものであれば、自身の成果物や活動についての状況を示唆するはずだな〜確かに〜〜〜!という。

で、実際にphpstan-baseline-analysisは使えそう・・というか「(ignore)エラー総数だけでも追いたい、俺が欲しい!」と思ったので、セットアップしてみました。

作るものの概要

  • 集計した結果、コレまでのトレンドを示すグラフや最新状況について可視化する
    • 一目でわかるようにする
    • 情報を集約して、1箇所でわかるようにする
  • 自動で更新して、新鮮なデータが溜まるようにする

↑をこなすための制約だったり前提条件

  • 運用上、baselineファイルは単一のものではなく複数に分割されている
    • 複数のbaselineについて個別に & 全体を統合した集計結果の出力に対応させる
  • 分析対象ファイルとは別のレポジトリに置きたい
    • 自動更新を考える上で、J-SOXが〜〜みたいなことを気にしたくない
    • 「対象レポからコードを引っ張ってきて集計にかける」をやる

データは加工していますが、出力イメージはこんな感じになります↓

トップページ = 全体の(ignore)エラー数の推移と

詳細ページ = baselineファイル別のエラー数の推移

作った

集計を実行するレポジトリを github-org/phpstan-baseline-watch 、集計対象のレポジトリを github-org/nanika-no-pj という名前だとします。

github-org/phpstan-baseline-watch には、

  • 集計を実行する処理の実装
  • GitHub Actionsで、↑と絡めつつ、 github-org/nanika-no-pj をcloneしてくる && 集計を実行する
  • GItHub Pagesに可視化結果を出力する

という機能をもたせます。

github-org/nanika-no-pj は、 .phpstan-baselines に(複数の)baselineファイルを持てる構成になっています。

全体像

こんな構成になります。

.
├── .github
├── .gitignore
├── composer.json    # phpstan-baseline-analysisを取り入れる
├── composer.lock
├── nanika-no-pj-src    # 分析対象レポジトリ
├── docs    # gh-pagesの出力対象
├── main.sh    # 集計実行のロジック
├── tmp    # 集計時一時ディレクトリ
└── vendor 
$ cat .gitignore
/vendor/
/nanika-no-pj-src/
/tmp/
!/tmp/.gitkeep

集計実行のアクション

実際に集計をキックしたり、その前段階のデータ集めをする実態は、GitHub Actionsのワークフローに委ねることになります。

処理の流れは

  1. 分析対象のレポジトリを、ignore対象のパスにcloneする
  2. (github-org/phpstan-baseline-watchの方に)Composer Installを実行する
  3. 集計実行のロジックをキックする
  4. 集計結果をcommit&pushする

というものになります。

actions/checkoutは他レポのcloneは可能ですし、fetch-depthを指定することで最新のコミット以外も取得可能です。
今回は「サブディレクトリを掘って、そこにcloneする」という方法を取りましたが、submoduleなんかでも自然だと思います。
ローカルで作業する際に、submoduleではない「自由にいじり放題なディレクトリがあると楽だった〜」くらいのフワッとした理由で、このような形にしています。

集計実行のロジック

workflowファイルに直接書くにはやや煩雑になるので、シェルスクリプトを別出しします。
ざっくりした流れを掻い摘むと、

  1. 相対日時として、今日〜60日前の範囲で1日毎にイテレーションして
    1. 初日(=HEAD)の場合だけ、最新スナップショット保持用に集計結果を出力(コミット対象となる)
  2. スナップショットを取りたい日付(に1番近い)コミットをチェックアウトして
  3. 個別に分かれているbaselineファイルから、ignoreErrorsを取得してマージするPHPスクリプトの実行し、他のbaselineファイルと並列に配置する
  4. baselineが設置されているディレクトリの.phpファイル*2ごとに、集計の実行を行い、一時ディレクトリに結果を出力する
  5. グラフの生成を行い、トップページと詳細ページにmdファイルとして埋め込む

となります。

ちなみに、このスクリプトの要素要素については、殆どChatGPTさんが書いてくれました。ありがてぇ
いくつかポイントを説明していきます

  • ポイント①
    • git checkoutとかcleanを掛けまくるので、ワーキングディレクトリを対象PJのrootに変更しちゃっています
    • その代わり、集計スクリプトの場所などで混乱しにくいように、PJ(github-org/phpstan-baseline-watch)のパスを一時変数に格納して、nanika-no-pj-src 下でゴニョゴニョしている間は各種パスを絶対パスで指定するようにする
  • ポイント②
    • unstagedファイルをnanika-no-pj-srcの下で一時的に作成しているので、イテレーションの冒頭でお掃除しています
  • ポイント③
    • 過去の情報に遡る時 = 「1日」以上前の時だけ、commitを遡ってcheckoutする・・・という分岐なのですが、今思ったらコレ要らないかもですね。
      • git rev-list -n 1 --before="${day_before} days ago" HEAD ってHEADにならない?
  • ポイント④
    • 最終的に出力されるグラフにおいて、「データファイルが読み込まれた順に、グラフの原点から配置される(=左に来る)」という挙動が見受けられたので、若い日時のデータが最初に食われるように命名しています
    • glob() の結果に従った順番通りにファイルを読み込み、iteratorにappendしている感じ
  • ポイント⑤
    • phpstan-baseline-analysisのRADMEを見ると、いい感じにスナップショット作成対象の日時が反映されている・・・?と喜んだのですが、実際に動かしてみると、集計日時には集計実行時点の現在日時が入るっぽい挙動がありました
    • そのため、出力されたファイルから日時のフィールドを直接変更しています。jqが最初から入っているの有り難い

GItHub Pagesの更新

・・・については、ほぼデフォルトのまま(強いて言えばディレクトリを /docs になるように変更したり、スケジュール実行を入れたり)なので、特に言うこともないです。 GitHub上で、PJの settings > pages に行って Build and deploymentGitHub Actions にしてあげれば、雛形を出してくれます。

やった!できたねぇ〜

君も鬼になって、baselineをゴリゴリに削っていこう!

*1:変更頻度が高いファイル(コミットが集中しているファイル)は優先的にテストを書くべき・変更容易性を高めるためのリファクタをすべきだ〜そもそも責務を持ち過ぎかもなので分割するべきだ〜〜とか、そういった類のものですね。

*2:baselineファイルをphpで作成しています

やったこと・書いたもの{2023,08-09}

8月分の内容を残し忘れていたのでまとめて・・

OSS

ペチコンの資料を作っていて、PHPマニュアルのちょっとした修正漏れに気付いたのでパッチを投げました。
@phpに貢献してみたいなぁ〜って思っていたので、嬉しいですね github.com

勉強会・LT

他社との合同クローズド勉強会

speakerdeck.com

その他

会社のブログ。

zenn.dev

zenn.dev

あのアカセさんにお誘いを受けて、またお邪魔してきましたの回 🙌

tsunagi.me

streamWrapperが「何文字読み込めるか」みたいなのを少し掘る

streamWrapperが〜みたいな記事をzennに書いたんですけども。 zenn.dev

記事中でも「多分こんな感じで動いてるけど、実装を見てないからわからないよ」と書いているのが、stream_readとファイル読み込みサイズの関係。

動かす

準備

例えば、「いつも決まった文字列(PHPスクリプトとして解釈可能)を返す」というstreamWrapperを用意する。
返すのは "<?php echo time() . PHP_EOL; ?>\n"; とし、これは読み取り文字数*1を無視して、いつも返すようにする。

<?php

class InvalidStreamWrapper
{
    private $content = "<?php echo time() . PHP_EOL; ?>\n";

    public function stream_read($count)
    {
        return $this->content;;
    }
}

毎回固定文字列を返すと、ファイル終端のハンドリングに失敗して無限ループが発生するので、「3回stream_read()を読んだら空データを返す」ようにする

<?php
class InvalidStreamWrapper
{
    private $counter = 0;

    public function stream_read($count)
    {
        if ($this->counter > 2) {
            return '';
        }
        $this->counter++;

        return $this->content;;
    }

その他、動作に最低限必要な stream_open()stream_eof()stream_set_option() をダミーで定義して、 return true; させておく。
また、stream_stat() も一旦 return true;で済ませる。

<?php
class InvalidStreamWrapper
{

    public function stream_stat()
    {
        return true;
    }

    public function stream_open($path, $mode, $options, &$opened_path): bool
    {
        return true;
    }

  public function stream_eof(): bool
    {
        return true;
    }

    public function stream_set_option($option, $arg1, $arg2)
    {
        return true;
    }
}

これを利用するための実行部分は以下

<?php

stream_wrapper_unregister('file');
stream_wrapper_register('file', InvalidStreamWrapper::class);

echo '========file_get_contents' . PHP_EOL;
echo file_get_contents('non-exists-file');
echo '========require' . PHP_EOL;
require 'non-exists-file';

3v4l.org

sizeの指定なしで動かす

で、実行するとこうなる

========file_get_contents
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
========require
1695457515
1695457515
1695457515

sizeの指定がない限り、file_get_content()requireも同様に「終端が来るまでファイルを読み込む」ように見える。
また、stream_eof()の結果も変わらない。

sizeの指定をして動かす

stream_stat() がsize情報を返すように改変する。
挙動をわかりやすくするために、ついでに実行部分もいじる。

<?php

echo 'fstat.size = ' . (fstat(fopen('non-exists-file', 'r'))['size']) . PHP_EOL;

class InvalidStreamWrapper
{

    private $size = 32;

    public function stream_stat()
    {
        return ['size' => $this->size];
    }
}

まずはsize=32で。これは、 strlen(IngalidStreamWrapper::$content)と一致する。

実行結果

fstat.size = 32
========file_get_contents
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
========require
1695458370

3v4l.org

「requireだと1回しか$contentが出力されていない」という形に。

sizeを増やしてみる

<?php
    private $size = 32 * 2;

すると、次の結果に

fstat.size = 64
========file_get_contents
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
========require
1695458351
1695458351

sizeが0の場合は、予想していた挙動と変わった。これは無指定時と同じになる

<?php
    private $size = 32 * 0;
fstat.size = 0
========file_get_contents
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
========require
1695458321
1695458321
1695458321

では、contenの長さと一致しないsizeにしてみるとどうなるか。
例えばsize=4の場合、PHPスクリプトファイルとして読み取られて評価されたが、開始タグがない(壊れている)ので、テキストファイルを読み込まれたのと同じ状態。

<?php
    private $size = 4;
fstat.size = 4
========file_get_contents
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
========require
<?ph

PHPスクリプトとして中途半端な文字数にすると、構文エラーとなる

fstat.size = 25
========file_get_contents
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
========require

Parse error: syntax error, unexpected end of file, expecting "," or ";" in non-exists-file on line 1

Process exited with code 255.

受信できるサイズより大きい場合はどうなるだろうか?
これは問題ないっぽい。ストリームのブロックサイズやstream_eof()の内容とも関係してくるのかな、というのも気になる。

fstat.size = 320
========file_get_contents
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
<?php echo time() . PHP_EOL; ?>
========require
1695458524
1695458524
1695458524

ちょっとだけphp-srcを読んで見る・・・

php_stream_read_to_str とか _php_stream_read の辺りを読んでいけば良いのかなーって思った。

お腹が空いたのでここまで、また気が向いたらやろうかなー。未定。

*1:stream_read()に渡されるデータサイズ。$count。

「PHPのファイルに差分があるかを(astを使って)調べる君」を書いた

つくった

gist.github.com

なんで

ってことで、試し書き程度にやってみたのでした。
(ちゃんと動くのかな・・・?そんなにしっかり確かめてない。なんとなく行けるのかな、って所まで作ったので晒す)
(ちなみに、関数のPHPDocは全部AI Assistantさんがやってくれました)


人に説明や証明する事が面倒くさくてぇ、受け取った側も億劫でぇ・・・みたいなものが世の中にあると、
「説明や証明をしなくて済めばいいのに!あるいは、どっかの誰かが代わりにやってくれれば良いのに!」って思いますよね。
いちいち、そういう”くだらなさ”で仕事の場面やらでストレス溜めたくないなぁ〜って思いつつ、
でも「ガガッと気になったところをぶち潰して行きたい衝動に駆られることはある」のも自分にとっては真なので、
サボるための道具を作って遊んでみるか?というアレです。

モノタロウさんの記事、初めて見た時にメチャクチャ衝撃を受けて。「いいなぁ!あれ、俺も欲しい!!」という感じで。
「コメントとかインデントとか改行とかを変えましたよ!!」みたいな、PHP-CS-Fixerなどでガガーっとやるような変更について、どうでもいいからLGTMくれよーーーって交渉や説得・説明が面倒くさすぎます。

で、「何かASTとかそういうの触れてみたい、遊んでみたい!」とも思っていたので再発明でございます。

どんな感じの

といっても、ツイートで触れている記事で紹介されている actionsを見ながら、写経したようなもんです。 github.com

変えたところとしては

とかとかやりました。
比較したいブランチ名やコミットハッシュを2つ渡してあげる〜みたいな使い方をします。
2つ目は、省略したらHEAD を利用します。

 $ php prototype.php main tmp
# Check diff between main...tmp
| main | tmp |
| ---- | ---- |
| 48261f6f838b529b003fa3a26eb770ea2b7bf06c wip | 6c0c3b15f07051277993e24b7ce876e0a5baa3c4 Create README |
## Diff
### non-PHP Files
| filename | status |
| ---- | ---- |
| README | A |
### PHP Files
| filename | status | BASE | HEAD | ast-changed |
| ---- | ---- | ---- | ---- | ---- |
| hello.php | M | b9b45a8d7d4608bce4541443e0db1ec7 | b9b45a8d7d4608bce4541443e0db1ec7 | NO CHANGE |
| prototype.php | M | 39bfc6a9f6632f1723bc13d5b234c396 | 3278e5a8d39c10c4cb30b3232af507d7 |  |
| src/Command/EchoHashCommand.php | D | f5612c756ba27d3be6a479387ee97dcd |  |  |
| src/Parser/Parser.php | D | db232594d101bc1a7e4f40e67e6dc54c |  |  |
| src/Parser/Validation.php | D | 69850a9556020ed7d4234cc13f1d1c57 |  |  |

(気が向いたら)

  • symfony/consoleとかを使って、なんかそれっぽい感じに書き直したら楽しそう
    • pharで固めるか、Dockerで動くようにするかかなぁ。使いたい環境でサクッと使えるようにしたい
  • GItHub上で使えるようにして、レビュアー大歓喜!号泣!!みたいなものにしたら面白そう
    • PRのコメントに「えいっ!!」って書き込んだら結果を貼り付けてくれる〜、みたいなの出来たら使えそう?

やったこと・書いたもの{2023,06}

OSS

勉強会・LT

PHPカンファレンス福岡2023に参加しました & 登壇しました #phpconfuk / @自分の登壇まわり - 大好き!にちようび

その他

会社でテックブログを(仮)始動させたので、Zennにいくつか記事を出しました。

会社のアカウントからも出しています