がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

「コードの書き方」の一例

元ネタ。
https://twitter.com/komi_edtr_1230/status/1562230567101693953

自称シニアのフリーランスさん(笑)からのPRで

if (foo == “bar”) {
  return true
} else {
  return false
}

というひどいコードがあって朝からゲンナリしてる....
駆け出しエンジニアじゃないんだからこんな指摘させないで欲しい.....

「変数hogeの値が 'foo' かどうか?*1」のコード、多分これくらいのバリエーションがあるんだと思うんですねぇ by PHP
あと、比較演算子が == と === とあるよねぇ、というのは、面倒なんでオミットします。
(ひねたパターンも削除してます……多分)。

if ($hoge === "foo") {
  return true
} else {
  return false
}
if ($hoge === "foo") {
  return true
}
return false
if ($hoge === "foo") {
  $r = true
} else {
  $r = false
}
return $r;
return ($hoge === "foo") ? true : false;
$r = ($hoge === "foo") ? true : false;
return $r;
return $hoge === "foo";
$r = ($hoge === "foo");
return $r;
return isHoge($hoge);
$r = isHoge($hoge);
return $r;

先に書いておくと、「特になんの制約もない」状態でおいちゃんが書くんなら「return $hoge === "foo";」。
いやだって一番シンプルやん。「比較演算子の戻り値はbool」だし。

分析軸は……この辺かなぁ。
・"if" か "三項演算子" か "比較演算子戻り値直" か "関数に切り出す" か
 → ifの時、elseを書くか書かないか
・"値を直return" か "一度変数で受ける" か

先に後者から話をすると。
個人的には「わざわざreturnする値を変数に入れるのは面倒だしメモリ勿体ないし*2」とは思うんだけど「デバッガとかブレークポイントとか考えると変数で受け止めるのはデバッグしやすい」とも思うので、まぁお好みかなぁ、と。
PHPになってから「どちらかというと変数で受けておく(あとでデバッグしやすい)」に寄せてはいるかもしれない……とはいえ染みついた手癖はなかなか直らないんだけど(笑

前者のほうはより一層色々あって。
個人的には
・"比較演算子戻り値直"は、まぁ普通に理解できると信じているし楽だから、基本はこれ
・if文は、色々と「まぁわからんでもない」。初心者向きだしね*3
三項演算子が一番「中途半端」なので、ここを最大肯定する理由があんまり見つからない
・関数に切り出す、は、この比較だと「無い」かなぁ(もっと複雑ならやる)
って感じかなぁ。

この辺は、多分「どれで書いても、そこまでクリティカルに問題が起きる可能性は低い」ので、ただまぁだからこそ「致命的なレベルで"自分の感覚と違う"のが受け入れにくい」のかなぁ、とも思ったりはする。
制限としてあり得るのはどちらかというと「直returnすんなデバッグしにくいだろ」で、これは現場によってはルール化されうるんだろうなぁ、とは思う。

で、多分議論の本質であろう「ifか三項演算子か比較演算子か関数切り出しか」については……まぁ本当に「好きにすりゃええがね」としか思わないかなぁ、個人的には。
おいちゃんの好みとしては、前述した通りではあるんだけど
・この程度なら、比較演算子
・もっとややこい条件なら、関数切り出し
って感じかなぁ。

まぁ、こーゆーの、限界事例とかまで考え出すと色々と「今まで関わってきた天国とか恩恵とか地雷とか地獄とか煉獄とか殺意とか」がうかがい知れるので。
「相手の背景と意見を尊重しつつ」、「自分は、こ~ゆ~経験があったから、こっちが好みなんだよねぇ」程度に会話をすると、得るものも多いのではないかなぁ、と思うのですがいかがでしょうか*4

*1:微妙に元ネタと名前が違うのは気にすんな

*2:C言語の頃の感覚の名残

*3:でなきゃ、コンパイラの最適化まで見越した超上級者向き

*4:いきなり日和ってみるw

array_* と foreachの処理時間検証

なにかでちょろっと気になったのでざっくり検証したので、軽くlog残し。
基本的に「foreachに有利な処理*1」なので、まぁ参考程度に見ていただければ。
ただ言い方を変えると「だから処理によっては(「配列を2回ぶん回す」事になるから)array_*使うと処理的には重くなるからその辺は勘案してね」って内容ではあります。

「元が1万要素ある配列」に対して
・奇数だけを抜き出して
・値を倍にする
処理をぶんまわしてみました。

先に計測結果。
10回うごかした時間と、最後に使用メモリ*2

// array関数版(無名関数)
0.0016610622406006sec
0.0012879371643066sec
0.0011820793151855sec
0.0011670589447021sec
0.0011560916900635sec
0.0011970996856689sec
0.0011940002441406sec
0.0012528896331787sec
0.0010929107666016sec
0.0011200904846191sec
2097152

// array関数版(動的関数)
0.002579927444458sec
0.0016429424285889sec
0.0014491081237793sec
0.0013840198516846sec
0.0014119148254395sec
0.0015451908111572sec
0.0015830993652344sec
0.0015368461608887sec
0.0014801025390625sec
0.0014641284942627sec
2097152

// foreach版(動的関数)
0.00094008445739746sec
0.00081992149353027sec
0.00080585479736328sec
0.0007941722869873sec
0.00082993507385254sec
0.00081396102905273sec
0.00085997581481934sec
0.00083804130554199sec
0.00079202651977539sec
0.00078392028808594sec
2097152

// foreach版(内部べた書き)
0.00034594535827637sec
0.00023603439331055sec
0.00034213066101074sec
0.00025701522827148sec
0.00025200843811035sec
0.00022697448730469sec
0.00030303001403809sec
0.00024294853210449sec
0.00026392936706543sec
0.0002589225769043sec
2097152

おおまかに「array関数使うんなら無名関数のほうが早いっぽい*3」「foreachなら中にベタっと処理書く方が早い」。
トータルとしては「array関数よりforeachのほうが早い」(今回の処理の場合)。
array関数の無名関数については「想像してないわけじゃなかったけどでも一定の気づきがある」結果で、foreachについてはまぁ「そうだろうなぁ」くらい(関数のcall、コスト0ではないしねぇ)。

実行時間については、一番重い「array関数版(動的関数)」と一番軽い「foreach版(内部べた書き)」で、大体、7.5倍くらいの実行時間の差異があるのかな。違うっちゃぁ違うし、誤差っちゃぁ誤差だし、くらいの微妙な感じ。
「array関数版(無名関数)」と「foreach版(内部べた書き)」で、5倍弱、くらい。

なのでまぁ「エライこと巨大な配列を扱う」んなら或いは一考する要素かもしれないし、そうでなければ「誤差レベル」って気もする。

そうすると後は「今までにやってきた技術背景次第」なのかなぁ、とも。
PythonとかRubyとか、あと最近のJavaScript辺りなんかだとarray関数版的な書き方を元々しているので、そちらからの系譜の人達は、割合とarray関数版的が「読みやすい、わかりやすい」って言って好む傾向があるように思います。

おいちゃんは元々がN88-BASICからZ80aのマシン語C言語経由で(他にいくつかあるけど)PHPに来てるので、foreachで十分に使いやすいのでforeachでいいかなぁ、くらい。
(C++iteratorが割と色々と好き……あの「autoが使えなかった頃の死ぬるほど長い型名」含めてwww)

その辺を前提に、あとはまぁ各自お好みで(笑

最後に、確認したコードは以下の通りです。

<?php

// 元ネタ作成
$awk = range(1, 10000);

// やりたい処理の関数その1:奇数偶数判定
function a(int $i) : bool
{
    return 1 === ($i & 1);
}
// やりたい処理関数その2:値を倍にする
function b(int $i) : int
{
    return $i * 2;
}

// array関数版(無名関数)
function t1(array $awk) : array
{
    return array_map(function($i) {
        //return $i * 2;
        return $i;
    }, array_filter($awk, function($i) {
        return (1 === ($i & 1));
    })
    );
}

// array関数版(動的関数)
function t2(array $awk) : array
{
    return array_map('b', array_filter($awk, 'a'));
}

// foreach版(動的関数)
function t3(array $awk) : array
{
    $ret = [];
    foreach($awk as $v) {
        if (true === a($v)) {
            $ret[] = b($v);
        }
    }
    return $ret;
}

// foreach版(内部べた書き)
function t4(array $awk) : array
{
    $ret = [];
    foreach($awk as $v) {
        if (1 === ($v & 1)) {
            $ret[] = $v * 2;
        }
    }
    return $ret;
}

// 計測
for($i = 0; $i < 10; ++$i) {
    $t = microtime(true);
    t4($awk); // ここ、書き換える
    $t_end = microtime(true);
    $t = $t_end - $t;
    echo $t , "sec\n";
}
// 使用メモリ
echo memory_get_peak_usage(true) , "\n";

*1:array_*だと、確実に「配列を2回ぶん回す」事になるので、処理的に不利になる

*2:使用メモリは変わらなかったから、計測しなくてもよかったなぁ……とはいえまぁそれがわかるって意味では必要だったのかも

*3:コンパイル的にインライン展開とかしてるのかね?

LaravelのMessage Queueを把握してみる:前提としてのMessage Queueそのもの

ちょいと「LaravelのMessage Queue」について調べる用事があったので。
調べ物の備忘録用に書いているので、間違いとかあるかもしれないので
・使う時は気をつけてください
・間違いに気付いたらコメントなどで突っ込んでいただければ、と思います。
という言い訳を書いておく(笑

さて。

まず「Message Queue」ってのが、これは「システムの一般的な概念」として存在します。
大昔は、messnd() / msgrcv() とか mq_send() / mq_receive() とかを使ってましたねぇ。プロセス間通信と呼ばれるものの一つで、まぁそのまんま「メッセージキュー」って呼称だったと記憶しております。
messnd() とか mq_send() とかでenqueue、msgrcv()とかmq_receive()でdequeueする、基本的には「キュー構造を持つデータ構造」ってだけです。「キュー構造はFIFOって言ってね」って下りは、省略してよいよね?
なので本質的には「なにがしか(通常は文字列)をenqueueで積んで、(だいたい)積んだ順番にdequeueで取り出す」機能です。

でまぁ、これを「何に使ってもよい」のですが、「積む速度にムラがあったりするようなタスクが、時々"ドカっと"積まれたりするのを、ちまちまと処理していきたい」なんて時に、よく使われまして。
Message Queueのよいのが「積むのはどんなにムラのある積み方をしても、処理は基本的に"一定のペースで片付ける"」って所にあります。なので、インフラリソースがある程度計算しやすいんですな。
この「お願いする」と「実際に処理する」が別々に動くのを「非同期」なんて言い方をしたりもしますねぇ、なのでMessage Queueは「非同期」でございます。
なお、積む「お仕事」は、いわゆる文字列(string)であればフォーマットは「お好み」なので、適宜好きな電文フォーマットをご用意ください。わかりやすくパースしやすいドメイン固有言語(DSL)をざっくり設計するか、PHPとかだと「配列をjsonなりserializeなり」ってのも手かなぁ、とは思います。
積むお仕事の事をタスクとかjobとか言っていたりする事もあるんで、job queueなんて呼称も耳にしますねぇ。task queueって呼称もありますが、job queueよりは聞かない……かもしれない。

んで。

「非同期でお仕事を処理する」って文脈でMessage Queueを語る場合。
「積み上げる先はどこ?」「積んだお仕事を誰がどんな風に処理して消化していくの?」ってあたりが重要になります。
ので、かみ砕いて。

まず「積み上げる先は?」ってのがあって。
例えば先にあげた「messnd() とか mq_send()」だと、これは「OSで実装されているプロセス間通信(IPC)」で、まぁ「ミドルウェアとかあんまり期待できなかった頃」には高頻度で使われていたものでございます*1
でまぁ一気に時間軸を「現在」に持ち上げると……いやまぁ「RDBで自力実装」とかもありなのですが、AWS SQSとか、Apache Kafkaとか、ようは「高圧でデータをため込んで、それなりに障害耐性があって、そこそこの速度で"ある程度選択して"データを読み出せる」機構であれば、ため込む先としては十分でございます……のであとは要件に合わせて。

お次に「どうやって仕事を消化するの?」については、それこそまぁ「バッチでも組め」ってお話になるので。
基本的には「メッセージがある間は、グルグルとぶん回して処理をし続けるバッチを組む」ってのが基本になります。

積み上げるお仕事の依頼文フォーマットについてはまぁ「ご随意に」。
わかりやすく書きやすく後でメンテナンスしやすい……なんてことを考えておくとよろしいかと。

んでまぁそうすると次に考察しないといけないのが以下の事象です。
・お仕事消化側、延々と、プロセスが24時間365日動いているとして、メモリリークとか大丈夫?
・同じくお仕事消化側、「お仕事がない」時って、バッチはどうしてるの?

まずメモリリークについては、おいちゃんが普段PHPを使っているので、その場合端的に「そこまでの信頼性はない」と考えているので(身も蓋もない)。
そうするとまぁ、仕組みとしては「一定時間、または一定回数働いたら、一度そのバッチプロセスを落とす」って実装を、よくやります。
つまり

while($message = dequeue()) {
    お仕事
}

ではなくて

while($message = dequeue()) {
    // 回数確認
    if (一定回数 <= ++$作業カウンタ) {
        終了処理してプロセスをexit;
    }
    // else
    お仕事
}

ってな感じにしておきます(回数制限の場合)。
そうなると「死んだら次のバッチを誰が起こすの?」ってのがあるんですが、ちょいと棚の上にpush。

お次。
「「お仕事がない」時って、バッチはどうしてるの?」については、2種類の見解が、一応ありまして。
・お仕事がない時は、sleepとかで待つ
・お仕事がない時は、一端exitで終了する
となります(レアケース的に別ラインも存在しますが、面倒なんで省略)。

でまぁ、おいちゃんの場合は「sleepで待ってもメモリとか食うしなぁ」となるので、基本「落ちる/落とす」事が多いです。

……となると、お仕事がないと落ちるので。つまりは「お仕事をやりすぎても」「お仕事がなくても」落ちることになります。
なので、メモリリークで棚に上げていた「死んだら次のバッチを誰が起こすの?」ってお題をpopさせて、話をmergeしていきます。

お題は「お仕事消化バッチのプロセスをいかにして(再度)立ち上げるか」。

個人的にはここは「cronで毎分起動 + セマフォで多重制御」1択かなぁ、と。
雑ではありますが、色々と楽ですしねぇ。
やり方は簡単で、以下の通りです。

・お仕事消化バッチは、毎分cronで起動される(1つかもしれないし、n個同時に起動かもしれないし、その辺は多重度の設定に合わせてお好みで)
・お仕事消化バッチはセマフォで多重起動が管理されているので、毎分動かしても「余ったプロセスは即時終了」となる
 → なので、セマフォの設定は「待つ」じゃなくて「すぐに落ちる」にする

ってくらいかなぁ、と。
そうするとまぁ、cron設定するくらいでコントロールが出来るので、手間がなくて楽でございます。
cronで動かすのを「1つのバッチ」とかにして、そこで「何多重同時に起動するか」を設定したり書いておいたりすると、「ソースコード管理の範囲で全部賄える」ですしおすし。

なお次に出てくるのが
・コード修正したの、即時に反映したいんだけど、どうすりゃいいの?
ってのがあって(バグとか)。

まぁ真面目に考えると「シグナルとか実装するかね? https://www.php.net/manual/ja/function.pcntl-signal.php 」ってなるのですが、PHPで「そこまでするかね?」ってのが、なくもないので(必要ならやるけどおいちゃんは)(「PHPでシグナル」は、後でBlogに書いておきましょうかねぇ)。
もうちょっとざっくりやるときは
・graceful の機能を作っておく
 → 大体「ファイルを置いておいて、そのファイルが存在するときは処理のloopの最後でexitする」くらいのざっくり実装が多いです

ってのを前庭に

・新しいコードをデプロイ
・gracefulファイルをscpとかrsyncとかで置く
・ちょいと待つ(1分もあれば、全てのプロセスがgraceful踏んで一度exitするでしょ。真面目にやるんならpsでプロセス起動時刻を確認)
・gracefulファイルをsshとかrsyncとかで削除

ってやると、まぁ、片付きます。
バッチサーバが何十何百もあると面倒なのですが、2~3台まで、くらいならこんな手順(の半自動化)くらいでも、結構いけるのではないかなぁ、と。
(大体自動化できるんで、台数増えてもぶっちゃけそんなに困りません)

あと、スケールアウトで「バッチサーバが複数台」ある時とかは。
このやり方は基本「非同期」なので、例えば外との通信の都合があって「全体として、ある瞬間に10処理までに押さえたい」場合、「サーバ1台辺りの多重起動数」を「10 / サーバ台数」ってしておけば、普通にコントロール可能です。
多分「このサーバはaとbのバッチ、このサーバはcとdのバッチ」ってやるよりも「全体的に薄く手を出す」ようにしたほうが、メンテとかが楽です。1台落ちても「性能がうっすら落ちる」だけで、甚大な被害とかが出なくなりますし。

……なんか他にも色々あった気がするのですが、今回の本題は「Laravelでの実装確認」なので、前説はこれくらいで。

*1:が、PHPにも https://www.php.net/manual/ja/function.msg-send.php とかあるし、「もう使われていないのか?」と問われると、「ど~なんだろうねぇ?」とか、なんとか

"マジックメソッドはメソッドだから呼ばれれば動く"件について

元ネタは、 https://speakerdeck.com/twada/growing-reliable-code-phperkaigi-2022?slide=84 を見て「コンストラクタが2回動くの!?」って質問があったので。
端的には「PHPにおいてコンストラクタはマジックメソッドで、マジックメソッドはメソッドだから、callすれば動く」っていうのが身も蓋もない回答になります。

かみ砕いて。

まずPHPにおける「マジックメソッド」は、以下の通り。

https://www.php.net/manual/ja/language.oop5.magic.php

マジックメソッドは、 ある動作がオブジェクトに対して行われた場合に、 PHP のデフォルトの動作を上書きする特別なメソッドです。

"ある動作がオブジェクトに対して行われた場合"をトリガーに"PHP のデフォルトの動作を上書きする特別なメソッド"、なので、基本「メソッド」です。

んで、PHPのコンストラクタは

以下の関数名は、マジックメソッドと見なされます: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone(), __debugInfo()

とあるように、マジックメソッドです。

なので、PHPの挙動としては

インスタンスを作る
 → "インスタンスを作る"動作がオブジェクトに対して行われた場合、 PHPのデフォルトの動作を上書きする __construct() メソッドがあるなら、そいつを実行する

ってな感じになります。
なのでまぁいわゆる「コンストラクト時の動作を __construct() メソッドに書いておけば」インスタンスが生成される時に「明示的には呼んでないけど、自動でcallされる」んですね。

んで。

とはいえまぁ__construct()くんも「メソッド」なので。
「明示的にcallされた」ら、それはまぁ「呼ばれたから動く」わけなんですね。

かくしてまぁ。元ネタのDateTimeImmutableで「コンストラクタを明示的に叩くとコンストラクタが呼ばれる(ので、値が書き換えられる」ってのは、まぁ「ですよね~」くらいの挙動になります。

「はて他言語はどうなってるんだろ?」と思い、なんとかするっと目に書けるC++を(幾分思い出しつつ)書いてみたのですが。

#include <iostream>

class Hoge {
public:
    // コンストラクタ
    Hoge() {
        std::cout << "construct" << std::endl;
    }
};

int main() {
    Hoge *h = new Hoge();
    h->Hoge();

    return 0;
}

うんそもそもコンストラクタはコンストラクタであって「クラス関数ではない」んだよなぁ。
なのでまぁ

t.cpp: 関数 ‘int main()’ 内:
t.cpp:13:8: エラー: 無効な ‘Hoge::Hoge’ の使用です
h->Hoge();
^

って言われるですだよ。うん。
……それ以外の言語はど~なんだろうねぇ?*1

話を戻して。
なのでまぁ、PHPのコンストラクタは所詮「メソッド」なので、「呼べば動く」ですます。

お仕事で考える場合、この辺は「明示的に呼ぶな」で終わっていいような気がするんだよなぁ……なんていうか「そこまで配慮せにゃならんのを現場で使いたいかね?」とか、正直。
ただまぁ「思考実験として」であれば、例えばこんな感じか。

<?php
declare(strict_types=1);
error_reporting(-1);

class Hoge
{
    public function __construct()
    {
        // コンストラクタ二重callのロック機構
        static $double_call_lock = [];
        $object_id = spl_object_id($this);
        if (null !== ($double_call_lock[$object_id] ?? null)) {
            throw new \Exception('コンストラクタ明示的に呼ぶとか、おまえ、正気か?');
        }
        $double_call_lock[$object_id] = true;
    }
}

//
$obj = new Hoge();
$obj2 = new Hoge();
$obj3 = new Hoge();
//$obj->__construct();

ただ、これ(spl_object_id)も「このオブジェクトidはオブジェクトが生きている間ユニーク」なので。

//
$obj = new Hoge();
$obj = new Hoge();
$obj = new Hoge();

ってやると

[gallu@ik1-111-11111 ~]$ php t.php
int(1)
int(2)
int(1)

Fatal error: Uncaught Exception: コンストラクタ明示的に呼ぶとか、おまえ、正気か? in /home/gallu/t.php:14
Stack trace:
#0 /home/gallu/t.php(23): Hoge->__construct()
#1 {main}
thrown in /home/gallu/t.php on line 14

ってな感じになる事があるんで、あんまり十全に大丈夫でもなし。
いやまぁがっちょりとデストラクタまで書いて

<?php
declare(strict_types=1);
error_reporting(-1);

class Hoge
{
    public function __construct()
    {
        // コンストラクタ二重callのロック機構
        $object_id = spl_object_id($this);
var_dump($object_id);
        if (null !== ($this::$double_call_lock[$object_id] ?? null)) {
            throw new \Exception('コンストラクタ明示的に呼ぶとか、おまえ、正気か?');
        }
        $this::$double_call_lock[$object_id] = true;
    }

    public function __destruct()
    {
        // 「コンストラクタ二重callのロック機構」の解除
        $object_id = spl_object_id($this);
echo "unset {$object_id}\n";
        unset($this::$double_call_lock[$object_id]);
    }

private static $double_call_lock = [];
}

//
$obj = new Hoge();
$obj = new Hoge();
$obj = new Hoge();
//$obj->__construct();

ってやれば多分

[gallu@ik1-111-11111 ~]$ php t.php
int(1)
int(2)
unset 1
int(1)
unset 2
unset 1

ってな具合にはなるんだろうけど、「そこまで書く?」って話になりそうだしねぇ。

なのでまぁ「コンストラクタで値を設定、そのインスタンスの値は不変」っていうのは、PHPの場合ある程度「善意と良識」に守られている所があるんだろうなぁ、と。
ただまぁ「その辺を横紙に破りまくるような御方」は、ちょっと……って思うので。
その辺はまぁ「どーゆースタンスを持つか」次第なんじゃないかなぁ? とか思うんだけど、ど~なんだろうねぇ???

*1:コメントとかでいただければ追記いたしまする

「文字コードを変換しつつCSVファイルを出力する」時の書き方

gallu.hatenadiary.jp で読み込みを書いていたのですが、書き出しをそういや書いてなかったなぁ、と思って。

先にちょろっと考察したいのが、Content-Type。
本来は text/csv が正解。
ただ、割とちょいちょい application/octet-stream を見かけて*1、まぁその辺は「どっちでもなのかなぁ」と。
なんか、以前に「"いいからとにかくダウンロードしろや"って時に octet-stream を使う」的な話を聞いた記憶があるのですが、ちょいとその辺はすみません曖昧な感じです。
とりあえず今回は、綺麗に text/csv を使います、が、まぁお好みで。

ダウンロードファイル名が「なんでもいい」のなら別なのですが、そうでなければまぁある程度指定をしたいと思うので、その辺は指定可能にしておきます。


ヘッダ出力が絡むので、一応「事前に ob_start() がcallされている」前提で記載をしています。
書いてなかったら、ob_end_clean(); をコメントアウトしてください。


出力データは $data に「配列の配列」の形で入っている前提です。

// キャッシュ破棄&終了
ob_end_clean();

// ダウンロード用ファイル名を作成
$download_filename = 'sample.' . date('Ymd') . '.csv';

// 必要なヘッダを出力
header('Content-Type: text/csv');
header("Content-Disposition: attachment; filename={$download_filename }");

// 出力用のハンドルの作成
$file_name = 'php://filter/write=convert.iconv.UTF-8%2FSJIS-win/resource=php://output';
$file_obj = new SplFileObject($file_name, 'w');

// 出力
foreach($data as $datum) {
    $file_obj ->fputcsv($datum);
}

細かい所をちょいちょいと忘れるので、メモ代わりに。

*1:excel とか名の付くヘッダは積極的に無視

共通化についての雑感

いわゆる「コードをひとまとめに、関数とかクラスとかそーゆー感じの箇所にまとめるか否か」ってあたりについて。
おいちゃん的には(一端、現状では)結論が出ていて

・「意味が同じ」ものは絶対に共通化しろ
・「機能が同じ」ものは、どっちでもいいけど共通化しておいたほうが楽
・共通化は二回目

ってな感じでございます。
似たような話は「「意味で括る共通化」と「機能で括る共通化https://gallu.hatenadiary.jp/entry/20160517/p1 」でもしているですが、まぁ。

かみ砕いて。

・「意味が同じ」ものは絶対に共通化しろ
これは鉄則。おいちゃんはよく「論理的共通化」とか呼称したりします、対になるのは「物理的共通化」で、これは後述。
わかりやすい所で「消費税の計算」とか。これを「2箇所以上に散らかす愚か者に最大級の災いあれ」。
「同じ意味」なんだから「1箇所にまとめる」は当然以前の問題でございます。

で、ここで地味にポイントなのが「共通化は二回目 https://gallu.hatenadiary.jp/entry/20150422/p1 」。
なんとなくで切り出すと結構しくじる事があるので、おいちゃんは「見え見えでわかりきっているものでも、グっとこらえて、二回目に共通化する」ようにしているです。

・「機能が同じ」ものは、どっちでもいいけど共通化しておいたほうが楽
これは「どっちでもいい」んですが「共通化しておいた方が(圧倒的に)楽だよ」ってお話と、そこで「あえて共通化しない意味がわからない」ので、結果「とっとと共通化しませう」となります(笑
簡単な例題としては「元値の10%(端数切り捨て)を計算する」処理。
これが「消費税」だったり「手数料」だったりするだろうと思うんですが(そこが一緒なら論理的共通化に基づいて"共通化しろ"で終了)。
「そうでない」場合は、別々に実装しても「とりあえず一端まとめて」もどっちでもよい……のですが、まぁ気付いたんなら

function 消費税計算($元値) {
    return 計算10%端数切り捨て($元値);
}
function 手数料計算($元値) {
    return 計算10%端数切り捨て($元値);
}
//
function 計算10%端数切り捨て($元値) {
    // 略
}

って書いておいたほうが単純に「楽」だなぁ、と。
まぁ勿論後で「手数料は端数切り上げになったのだよ」とか言われる可能性もありますのですが、ンな時は「言われたら書き換える」で終わり。特に何も困らないの感じ。
「共通化はしすぎないほうがいい」とかたまに伺うのですが、正直「意味がわからない」感じでございます。
なのでまぁ「どっちでもよい」のですが、「同じ事を2回書く」とか「2行以上をコピペする」んなら「とりあえず共通化しておいて」ってやっても手間変わらないんじゃなかろうかなぁ? と。

こんな風に考えているので
・「意味が同じ」ものは絶対に共通化しろ
・「機能が同じ」ものは、どっちでもいいけど共通化しておいたほうが楽
・共通化は二回目
ってのを、割とちょいちょい、お話をしたりする訳でございます。

あと、PHPだと「可変関数」とか「可変変数」とか「マジックメソッド」とかその辺を把握しておくと、この辺もまた「色々と"楽になる"事が多い」ような印象がございますので、おまけ的に。

phpでざっくりアドバイザリロック

いわゆる排他制御系。
いやまぁおいちゃん的には「セマフォ使えばいいじゃない終了」で終わるお話なんですが。
PHPセマフォ、一応「コンパイルで指定が必要(coreじゃない)」ので、稀に、極稀に「入ってない環境」とかあるので……いやまぁ「入れろ」って話なんですが。
・多重度1のみ
限定条件だと、まぁ「ファイルによるロック」は色々論外なのですが、もう1つ方法があるよねぇ、ってんで、アドバイザリロック。

端的には「バッチファイル自身を排他ロック」するやりかたで、これなら「バッチが異常終了」しても「プロセス落ちたからロックが外れる」ので、割と面倒が少のうございます。

実装もシンプルなのですが、まぁとどのつまり「実装してみたかった」ので、実装してみます。
なお本記事には「アドバイザリロックって単語をちょいちょい忘れるから備忘録用に記録している」わけではないことを初めに言い訳しておきます。

本題。

まずアドバイザリロックのコード本体は、こんな感じ。
AdvisoryLock.php

<?php
declare(strict_types=1);
function advisoryLock(string $filename) : bool
{
    // ここ、staticにしないと関数ぬけたらインスタンスがGCに行くからファイルcloseされちゃう
    static $file;
    //
    try {
        //
        $file = new SplFileObject($filename);
        if (false === $file->flock(LOCK_EX | LOCK_NB)) {
            return false;
        }

    } catch (\Throwable $e) {
        echo $e->getMessage();
        return false;
    }

    return true;
}

実験用のファイル。
AdvisoryLockExec.php

AdvisoryLockExec2.php
(中身は一緒)

<?php
declare(strict_types=1);
include('./AdvisoryLock.php');

$pid = __FILE__ . ':' . getmypid();
if (false === advisoryLock(__FILE__)) {
    echo "{$pid}  NG\n";
    exit;
}

sleep(3);
echo "{$pid}  ok\n";

で、一応実験。

[gallu@a.b.c.d ~]$ php AdvisoryLockExec.php & php AdvisoryLockExec.php & php AdvisoryLockExec.php & php AdvisoryLockExec2.php & php AdvisoryLockExec2.php & php AdvisoryLockExec2.php
[1] 2272
[2] 2273
[3] 2274
[4] 2275
[5] 2276
/home/gallu/AdvisoryLockExec2.php:2277 NG
/home/gallu/AdvisoryLockExec2.php:2275 NG
[gallu@ik1-315-17990 ~]$ /home/gallu/AdvisoryLockExec.php:2273 NG
/home/gallu/AdvisoryLockExec.php:2274 NG
/home/gallu/AdvisoryLockExec2.php:2276 ok
/home/gallu/AdvisoryLockExec.php:2272 ok

[1] 終了 php AdvisoryLockExec.php
[2] 終了 php AdvisoryLockExec.php
[3] 終了 php AdvisoryLockExec.php
[4]- 終了 php AdvisoryLockExec2.php
[5]+ 終了 php AdvisoryLockExec2.php
[gallu@a.b.c.d ~]$

まぁ、予想通りの期待通り。

まぁ「素直にセマフォにしとこうぜ」とか思うんですが、別解で「これならギリギリ」ってんで、軽めに。