gallu’s blog

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

お仕事コードの書き方(の一部)

おいちゃん、経験的に、割合とPHPが長くなってきております。
んで、PHPでコード書くとき、割と色々と「丁寧に」書く癖があって。
特に昔は割と嫌がられたりしてたもんだけど、おかげでバージョンアップしても「平気の平左で」受け入れられるんだよなぁ、とかふと思い出しまして。
……なんて事を考えるとお仕事コードの書き方で「おいちゃんが気にしている事」があって、その辺を書いておくとなんか役に立つ事もあるのかなぁ? と思い、雑文を散らかしてみようか、と。

端的には
・キワを攻めない
に尽きるのですが……かみ砕いて。

例えばif文の評価式は最終的にbooleanが欲しいものでございます。が、関数やらメソッドやらの戻り値が、空文字だったり数値0だったりNULLだったりする事もございます。
とはいえその辺は暗黙の変換があるので

if (null !== 関数())

とか書かなくても

if (! 関数())

と書けば「nullがreturnされてきたからbooleanだとfalseだからそれの否定だから」で簡単に処理を書く事ができます。

複数の条件式があっても、括弧で囲わなくても演算子の優先順位があるから、優先順位をちゃんと理解していれば括弧なんて不要です。

型とか気にしなくても、例えば「文字が欲しい」ところに数値0書いたってよしなに解釈してくれます。

その他諸々。
言語仕様を「熟知していれば」こその、「一見不思議なんだけど、言語仕様を熟知していれば理解できる」書き方ってのは、色々とございます。

おいちゃん、上述を一通り「大変にお好まない」んですねぇ。
関数の戻り値云々で、多分これはそこそこ引っかかっている人もいたんだろうなぁと思われるstrpos()。
正直、あの問題おいちゃんは「1度も引っかかった事がない」んですが、マニュアルにも書かれているくらいなんで「結構引っかかった人もいるんだろうなぁ」とか、しみじみ。

っつかif文とかwhile文とかの条件式を書く所はちゃんと「条件式」で。
かつ、==ではなくて===で「型を意識して厳密に」。
かつ昨今なら「declare(strict_types=1);」付けて。
この辺を死守しておくと、少々ナニカがあってもあんまり困りません。

括弧はちゃんと付ける。
「これくらいは演算子の優先順位があるから」と思っても、異なる演算子があるんなら「このくくりと」「このくくりと」がわかるように、しっかりと丁寧に書く。
省略しない。

……若干悩むのが「インクリメントとかの前置と後置」ですが。
まぁPHPの界隈だと「そのテクニック使うな」って言われる事が多いですかねぇ。なので使わないようにしてます。

「ンなこと言ったってその辺の挙動、そうそう変わらないよ」ってよく言われるんですが。
【PHP8.0】非厳密な比較演算子`==`の挙動が今さら変更になる https://qiita.com/rana_kualu/items/82cc8295d2102d14b88a
【PHP8.0】演算子.と+の優先順位が変わる https://qiita.com/rana_kualu/items/db7ae541016bd0b02122
諸行無常。一切のものは流転し変化し、変わるものでございます。

まぁ関数やメソッドの戻り値は「falseかしら? NULLかしら?」って調べるのが若干面倒かもしれませんが。
括弧付けるくらい、そんなに手間ですかしらん?
って思ってるです正直。

こーゆー思想で作ってるので。
(最近、Slimにどっぷりハマって放置してますが)自前のフレームワーク、PHP5からPHP7に対応させるとき、ほぼ修正いらずでした(一桁行くらいだったかし、修正したの)。
逆にこの辺が雑なコードをPHP5からPHP7に移植させるとき、驚くほど手間がかかりました。

いやまぁ「いつどこにコストをかけるのか?」って話なんだろうとは思うのですが。
どうせなら「その言語にちょっとくらい不慣れな人」が読んでもすんなり読めるような、丁寧なコード書いてたほうが色々とメリットも多いんじゃ無かろうか? と思うんですが、どうなんですかねぇ?

駄目なUUIDv4実装

「実装によってはUUID4が重複しうる」って話を聞いて「じゃぁ駄目な実装を書いてみよう」と思いました(笑
珍しく「徹頭徹尾、非実用」です(笑

<?php
mt_srand(time());
printf("%08x-%04x-4%03x-%04x-%012x\n"
    , mt_rand(0, 0xffffffff)
    , mt_rand(0, 0xffff)
    , mt_rand(0, 0xfff)
    , mt_rand(0, 0x3fff) + 0x8000
    , mt_rand(0, 0xffffffffffff)
    );

これだと、同じ秒で(別マシンだろうがなんだろうが)同タイミングで作成すると、ちゃんと(?)重複します(笑
いやまぁ「普通に考えて、最低限 random_bytes() だろ」とか思うのですが、そこはほら、実験という名のお遊びなのでwww

なお

mt_srand(time());

がキモになりますので、絶対に省略しないでください。省略されると、ちゃんと(?)重複しません(笑

うんなんか書いたら満足したwww

魔力と魔法、魔術

世界には不思議な力が満ちていた。
先人はそれを「摩訶不思議な力」と呼称し、それが転じて「魔力」と呼称された。

「極度にとがった天賦の才」を持ったもののみが扱い得る魔力であったが、徐々にその法則の端々が判明していった。
「魔力を扱うための法則」、魔法である。

理論としての魔法を追うように、魔法を扱う術(すべ)についての試行錯誤と考察、実践もまたノウハウが蓄積されていった。
「魔法を扱う術(すべ)」、魔術である。

かくして。

あるものは魔法を研究して新たなる法則を発見し。
あるものは魔術を修練して極めて強大な術を行使するようになった。

蛋白質摂取量の計算

カロリー計算にはあんまり心惹かれないのと、糖質はまぁそこそこがっつり制限を入れているので。
そうすると気になるのが「蛋白質は十分な量を摂取しておきたいなぁ」ってあたり。

「十分な蛋白質量、とは?」ってのは

・体重ベース
・除脂肪体重ベース

があるんだけど、なんか昨今見ていると「除脂肪体重のほうがよりよい」っぽい雰囲気があったので、そっちベースで。
おいちゃんの除脂肪体重は58kgくらいなので、必要量を「除脂肪体重の2~3倍(g)」だとすると「116~174g」。

んじゃ次は、日々の蛋白質量の大雑把な計算。……割と食事にルーティーンっぽいものがあってよかった(笑

朝)
ソーセージ3本(1本20g換算):8.4g
全卵:12g

昼)
刺身一人前:25~37g
or
メンチカツ2個:20g

夜)
鶏か豚(100g):20g
豆腐(1/4丁): 5g
納豆: 7.4g

夜の納豆は「食ったり喰わなかったり」なのでぶれるんだけど、大体これで75~82gくらい。
他にも少しはなにか食べてるだろうけど、一端、誤差で(牛乳とか、誤差というには多い気がするがw)。

あとプロテイン(X-PLOSIONさんのを買ってます)が
・1食30g当たり: 26.1g
なので。

・運動しない日は大体OK(納豆は食ったほうがよいかも)
・運動した日は1食くらいプロテインをいただく / ごっつく頑張ったら2食でもよいかも

くらいが、蛋白質の摂取としては当面妥当な所かなぁ、と。
まぁこの辺をベースに、軽くコントロールしていってみましょう。

ちょっとした小技……の背景

典型的には「(第一種)ホワイトリスト*1」の実装でありがちなのですが。

declare(strict_types=1);

$white_list = [
    'hoge',
    'foo',
    'bar',
    'baz',
    'qux',
];
//
$input = 'bar';
//
if (true === in_array($input, $white_list, true)) {
    echo "{$input} is in \n";
} else {
    echo "{$input} is NOT in \n";
}

こんな風に書く事が多いと思うんですよね、多分。

ただ、おいちゃんはこんな風に書きます。

declare(strict_types=1);

$white_list = [
    'hoge' => 1,
    'foo' => 1,
    'bar' => 1,
    'baz' => 1,
    'qux' => 1,
];
//
$input = 'bar';
//
if (true === isset($white_list[$input])) {
    echo "{$input} is in \n";
} else {
    echo "{$input} is NOT in \n";
}

valueは別に1でも''(空文字)でもtrueでもなんでもよいのですが(nullだけは嫌)。
あと、issetじゃなくてarray_key_existsでも、と思うのですがなんでだかarray_key_existsの綴りを指が覚えてくれないので、「値がnullじゃない事が確定している」状態だとisset使う事が多いです。

先に、テストコード。

declare(strict_types=1);

$s = 'a';
$awk = [];
for($i = 0; $i < 100000; ++$i) {
    $awk[] = $s;
    $s ++;
}
$t = microtime(true);
$hash_awk = array_flip($awk);
$t_end = microtime(true);
echo 'array_flip is ' , $t_end - $t , "\n";

//
$needle = $awk[ count($awk) - 1 ];

//
$t = microtime(true);
in_array($needle, $awk, true);
$t_end = microtime(true);
echo "in_\tis " , $t_end - $t , "\n";

//
$t = microtime(true);
isset($hash_awk[$needle]);
$t_end = microtime(true);
printf("isset\tis %.28f\n", $t_end - $t);

//
$t = microtime(true);
array_key_exists($needle, $hash_awk);
$t_end = microtime(true);
printf("array_\tis %.28f\n", $t_end - $t);

何回かお試し。

[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0030989646911621
in_ is 0.00078010559082031
isset is 0.0000009536743164062500000000
array_ is 0.0000009536743164062500000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0043699741363525
in_ is 0.00089287757873535
isset is 0.0000011920928955078125000000
array_ is 0.0000009536743164062500000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0034210681915283
in_ is 0.00078892707824707
isset is 0.0000009536743164062500000000
array_ is 0.0000009536743164062500000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0035109519958496
in_ is 0.00085806846618652
isset is 0.0000009536743164062500000000
array_ is 0.0000000000000000000000000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0038630962371826
in_ is 0.0010230541229248
isset is 0.0000009536743164062500000000
array_ is 0.0000009536743164062500000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0031359195709229
in_ is 0.00078296661376953
isset is 0.0000009536743164062500000000
array_ is 0.0000011920928955078125000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0031070709228516
in_ is 0.00080204010009766
isset is 0.0000009536743164062500000000
array_ is 0.0000000000000000000000000000
[gallu@ip-256.257.258.259 php]$ php t.php
array_flip is 0.0037379264831543
in_ is 0.0010411739349365
isset is 0.0000009536743164062500000000
array_ is 0.0000009536743164062500000000

issetとarray_key_existsは、なんかもはや「誤差レベル」のような雰囲気が(笑
in_arrayとは露骨に数値が違う、ってのだけ覚えておいていただければ。

ここで書きたいのは「検索するんなら、in_arrayよりisset(かarray_key_exists)のほうがよりよいのではないかしらん?」というお話でふ。

端的には「O(n)かO(1)か」の違いなのですが……厳密には「O(1)かどうかはkeyのコリジョンの具合にもよるんだけど、大分と1に近い」くらい。
in_arrayは線形探索だと思われるので、基本的に「配列を頭から舐めていって見つかったらそこで終了」となります。だからまぁ、テストでは最悪の形である「一番最後の要素」にしたのですが。
issetとarray_key_existsは「hash検索」なので。 https://gallu.hatenadiary.jp/entries/2006/09/30 とか https://gallu.hatenadiary.jp/entry/20060930/p2 とか見ていただくと(多少)細かく書いてありますが、元々が「速やかに検索するための方法」という側面でのhashでもあるので、要素がn個あっても、比較的高速に検索をする事が出来るようになるです。

これが「どっかから配列を取ってきて検索」の場合、「array_flipのコスト」と「検索のコスト」とのせめぎ合いになるので微妙な所ではあるのですが。
(第一種)ホワイトリストとかの場合は大体「コードのどこか(定数とか)」にあらかじめ配列を書いている、と思われるので。
そーゆー時は、普通の配列をin_arrayするよりもhash配列をissetなりarray_key_existsなりするほうが、速度的にも早いし、「検索」という理屈からもより「適切」なんじゃないかなぁ、とか思うわけなんですね。

なんていう小技を、なにげにちょいちょいと説明するケースがあったので、どこかで文章に残せたらなぁ、と思ったので残しておきます。

*1:許可されたモノが列挙されたリスト

外掛けフィルターに物理濾過ギミック

うちの水槽が基本的に「生物ろ過特化型」に寄せて作ってあるので、もうちょっと物理ろ過を強化したいなぁ、と思っておりました。

 

水槽1(30cmキューブ):外掛けフィルター+底面フィルター(吸い込み式)

水槽2(30cmキューブ):外部フィルター+底面フィルター(吹き上げ式) and 外掛けフィルター(バイオビーズ突っ込んで生態濾過特化)

水槽3(20cmキューブ):外掛けフィルター(バイオビーズ突っ込んで生態濾過特化)

 

ふと「出水の所で物理ろ過したらどうなるんだべ?」と思って実験したら思ったよりも汚れがキャッチできたので、ちょいと面白いかなぁ、と思いまして。

 

使っているのは「ダイソーのろ過ウール」。
がっつり使い捨てでつかっております。

 

1.

f:id:gallu:20200929211838j:plain

外掛けフィルターの出水口でございます。水の通り道の黒いのは「試行錯誤した結果不要になったもの」なので気にしないw

 

2.

f:id:gallu:20200929211824j:plain

元々付属で付いていたストレーナー用スポンジを、ハサミで縦に切って「水受けのところに挟まる」ようにします。そんなにがっつり固定しなくても、割と取れません。

 

3.

f:id:gallu:20200929213302j:plain

そこにろ過ウールをうっすらと横一文字に置きます。
指で適当に押して、ちゃんと水に浸るくらいがよいです。

 

4.

f:id:gallu:20200929213337j:plain

小さくちぎった塊を2つ用意して、両脇にはります。
こうすると「水が割と真ん中に集中する」ので、汚れのキャッチ率がよいんじゃなかろうか? と思っています。

 

5.

f:id:gallu:20200929211748j:plain

一日経過後。これくらい汚れるので、つまりそれくらい汚れをキャッチしています。
再利用とか多分難しいと思うので「とっとと捨てる」でよいかと思います。
それでも100円で買ったろ過ウール、多分、一月くらいもちますしねぇ。

 

なんとなくすでにやられている技法のような気もしたのですが、とりあえず自力でたどり着けたので、面白かったのでナレッジの共有を。

オートミール茶漬け

相方さんじゃなくて当人がそろそろ糖尿病仲間入りっぽいので色々と試行錯誤しているのですが。
オートミール茶漬けが思いのほかよかったので、レシピ込みでmemo。

オートミール粥、ってのがあるのですが、若干、風味が「人による」感じ(おいちゃん的にはいけるけど)。
一方で、そこに「お茶漬けの素」をいれると、途端に「結構普通に万人受けするんぢゃね?」くらいの感じになるので、かなりお勧めでございます。
おかずに「鶏ハム」とか使うと「蛋白質たっぷり、糖質低め、繊維質多め」で、大変によろしかと。

オートミール100gあたりの炭水化物量は、大体69g前後(食物繊維込み。単糖当量だと63gくらい)と、決して低い数値ではない、のですが。
オートミール粥とかにすると、一食で使うのが30~40gくらいなので、炭水化物量は20g弱~25g程度。
トータルとして「低めの炭水化物量」にする事ができるのが便利でございます。

30gで「ちょっと軽めだけど普通くらい」から、40gだと「結構がっつり」くらいの量になるので、適宜加減してみてくださいませ。
おいちゃんは「永谷園のお茶づけ海苔」で食ってます((「味わい茶漬け 4種」でも食った)。美味でございました。
他の商品はわからんですが、多分、普通にいけるんじゃないかなぁ、と。

個人的には「もみ海苔追加、ワサビ追加」が、大体、デフォ。この辺はお好みで。

レシピは

もっちり
オートミール: 30g / 水:180ml / レンチン時間: 1分40秒
オートミール: 35g / 水:210ml / レンチン時間: 2分
オートミール: 40g / 水:240ml / レンチン時間: 2分半

しゃばしゃば
オートミール: 30g / 水:200ml / レンチン時間: 2分
オートミール: 35g / 水:235ml / レンチン時間: 2分20秒
オートミール: 40g / 水:265ml / レンチン時間: 2分40秒

こんな分量で、あとは「レンチンして終わり」。
お手軽なんで、是非。