Open23

日々の技術メモ

Re_FRe_F

日々のトラブルシューティングをメモします。

Q. Basic 認証で認証が通らなくなった

.htpassword を確認して、対応する ID/Password が存在するかを確認しました。

Q. 0.30125 * 100 がうまく計算できない

浮動小数点による問題です。

printf("%.20f", 30.3125);        # 30.31250000000000000000
printf("%.20f", 0.303125 * 100); # 30.31249999999999644729

Q. 特定画面で演出画像が表示されなくなった

容量の問題です。
1.5 MB ほどの素材を読み込もうとして、ツールの処理途中で素材の展開ができず、URL的には遷移しているが画面がブラックアウトするという状態になりました。(通常の素材は100 KB 程度)

思考過程

  • 事象の再現を行い、環境特有の現象でないことを確認した。
  • 問題が発生しているコードを確認した。
  • 問題ない素材と問題ある素材で動作を確認した。
    • 処理自体はエラーで落ちることなく通っていたため、差分を考えた。
    • 差分としては素材自体にあるはずで、素材により影響が出る部分について思いを巡らした。
      • Git 管理外で生成される素材はないか、素材の作成手順や拡張子に違いはないか、容量に問題はないかなど。
  • 問題ない素材と問題ある素材の差分について一つずつ確認し、最終的に容量の差分を突き止めた。

Q. crontab のデフォルトエディタが nano になっているので vim にしたい

# 確認してみる
$ cat /home/${user}/.selected_editor
# Generated by /usr/bin/select-editor
SELECTED_EDITOR="/bin/nano"
Re_FRe_F

Q. スプレッドシートで変更履歴を雑に出力しておきたい

以下を組んでおくと「変更履歴シート」に変更履歴が出力されます。
なお、GAS のコード形式には以下の2種類があります。

  • コンテナバインド型
    • Google SpreadSheet 自体に直接紐づくコード。Book がコピーされると一緒にコピーされる。
    • SpreadSheet 自体に紐づくので、SheetID に紐づく情報を取得するのに苦労しない。
  • スタンドアロン型
    • Google SpreadSheet とは独立したコード。ライブラリみたいなもの。
    • SpreadSheet に紐づいてはいないので、Sheet に紐づく情報を取得するのに苦労することがある。

基本的には再利用性と保守性を考えると、コンテナバインド側でスタンドアロン側を完結に呼び出すのが良いと思います。こうすると、複数ブックで使うコードを修正したくなった場合、ライブラリ側を修正するだけで済みます。

// スプレッドシート側(lib にライブラリ側を紐づけている)
// 編集検知
function onEdit(e) {
  lib.EditHistory(e);
}
// ライブラリ側
// 編集履歴
function EditHistory(e) {
  if (e.source.getSheetName() === '変更履歴') {return;}
  Session.getActiveUser().getEmail();
  var sheet = SpreadsheetApp.getActive().getSheetByName('変更履歴');
  if (!sheet) {return;}

  sheet.appendRow([
    new Date(),                // 日時
    e.user,                    // ユーザーのメールアドレス
    e.source.getSheetName(),   // シートネーム
    e.range.getA1Notation(),   // 行番号
    e.oldValue,                // 変更前の値
    JSON.stringify(e.range.getValues())  // 変更後の値
  ]);
}

Q. set -e, -u, -x, -o pipfail とはなにか

シェルスクリプトを書く時に冒頭につけるオプションである。

  • set -e: コマンドが失敗したら即座に終了する。
  • set -u: 未定義の変数があれば終了する。
  • set - x: 実行するコマンドを常に標準出力する。デバッグ用に使うと便利である。
  • set -o pipfail: パイプで繋いだコマンドが失敗したとき、終了ステータスを途中のコマンド時点の終了ステータスとする。デフォルトだとパイプの末尾コマンドの終了ステータスとなる。
Re_FRe_F

Q. Jenkins を動かしたいとき docker-compose.yaml の基本的な書き方の例はどんな感じ?

/home/user/git 配下の hoge という Git repository 内で docker-compose.yaml を管理しているサーバでJenkins を導入したいとき、たとえば以下のような書き方もできる。

version: '3'

services:
  jenkins:
    image: jenkins/jenkins:lts
    container_name: jenkins
    environment:
      - TERM=xterm
      - JAVA_OPTS=-Duser.timezone=Asia/Tokyo -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8
      - TZ=Asia/Tokyo
    tty: true
    volumes:
      # Git repo path.
      - /home/user/git/hoge :/home/user/git/hoge
      # Jenkins datas. You can check jobs here.
      - /var/jenkins_home:/var/jenkins_home
      # You have to symbolic link these files to /var/jenkins_home/**bin,
      # but if you mount these here, you don't need to.
      - /home/user/go:/home/user/go
    ports:
      - 8080:8080

docker コマンドのインストールについては Docker 公式で丁寧に書かれているのでそちらを参考とする。

Q. 以下のようなエラーが出てしまいました

docker ps
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json": dial unix /var/run/docker.sock: connect: permission denied

sudo docker ps で叩いてください。
dockerコマンドはデフォルトではroot権限なしには叩けません。sudo なしで叩くためには docker グループにユーザーを追加する必要があります。

# 副グループへの追加
sudo usermod -aG docker user
id game

# グループからの削除
sudo gpasswd -d user docker
id game

なお Jenkins を入れる場合には、以下のようなエラーが出る場合があります。

$ docker logs 01
touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?

上記のエラーは Docker コンテナ内では Jenkins ユーザーが user_id: 1000 であり、/var/jenkins_home へのパーミッションがないために発生します。
user_id: 1000 がアクセスできるようにしましょう。

sudo chown -R 1000 /var/jenkins_home
Re_FRe_F

Q. リモートブランチに不要なブランチがたくさんあります。削除したいです。

以下のコマンドで行うことができます。

# master ブランチにマージ済みの、「feature/event/[45(どちらかの文字に一致する)]」に合致するリモートブランチを削除する。
git branch --remote --merged master | grep -e "feature/event/[45]" | sed -e 's% *origin/%%' | xargs -I% git push --delete origin %

# git branch --remote --merged ${branch}: リモートブランチで ${branch} にマージ済みのブランチを出力する
# xargs -Ihoge ${任意の変数hogeを含むコマンド}: 標準入力より渡されたデータを、xargs引数コマンド の任意の位置の引数に展開することが可能です。

また今後、無駄なブランチを増やさないように GitHub の Setting から「ブランチがマージされたら自動で削除する」の機能をON にしてあげましょう。

Q. リモートブランチの一定の命名規則に基づいたブランチ群に tag を打ちたいです。

もし、各ブランチの最新commitに対してタグを打つということでよいのであれば、以下が参考になる。

for id in {1..100}; do git rev-parse --short origin/addon${id} | xargs -I% git tag -a release/addon${id} -m "msg: addon${id} release." %; done
Re_FRe_F

Q. 現職で利用しているキャッシュの大雑把な利用区分けはどうなっているか?

Apache の入っている複数のサーバーに ALB でリクエストを振り分けており、さらに各サーバーの Apache 内では親 fcgi プロセスを folk して子プロセスを複数起動しているという状況を考えておきます。

Memcached を利用した Cache:

  • 複数のサーバーと共有して保持する。Memcached サーバーを専用に立てることが多いので、基本的にはネットワークコストが重い。

Localキャッシュ:

  • 親プロセスに Cache を持たせることで、1つのサーバーで複数のプロセスと共有して保持する。Copy on Write の概念により、Preload の際のメモリコストが削減できる。ネットワーク負荷的には同一サーバー内なので軽い。

Processキャッシュ:

  • 1つのサーバーの1つのプロセスで保持する。子プロセス内でのキャッシュ。 ネットワーク負荷的には同一サーバー内なので軽い。

Requestキャッシュ:

  • 1つのサーバーの1つのプロセスの1つのリクエストで保持する。ネットワーク負荷的には同一サーバー内なので軽い。

みたいな区分程度の意味合いで良いかと思われます。

Re_FRe_F

Q. GitHub での access token 認証で毎回ユーザー/パスワード入力を聞かれないためには?

具体的には以下のようなものを指しているとします。

$ git push
Username for 'https://github.com': 
Password for 'https://hoge@github.com':

このとき、 Password についてはいつも通り GitHub > Settings > Developer Settings > Personal acces tokens > Fine-grained tokens から作成します。
その後、以下のコマンドを打つことで次回から入力を聞かれなくなると思います。

$ git config --global credential.helper store

これにより ~/.gitconfig に設定が、~/.git_credential に認証情報が設定されます。

また別途、GitHub GCM を使う方法もあるようです。

Re_FRe_F

Q. GitHub のコメントにラベルみたいなのがついているけど、どうつけたらよい?

以下の記事が参考になります。

基本的には、https://github.com/settings/repliesという個人の Settings 画面の Saved Replies から設定していきます。
たとえば ![review:must](https://img.shields.io/badge/review-must-red.svg) のように設定していくとラベルのようなコメントが使えるようになります。

また、以下のような文言で Alert 機能を展開することも出来ます。

> [!TIP]
> Helpful advice for doing things better or more easily.

公式:https://github.blog/changelog/2023-12-14-new-markdown-extension-alerts-provide-distinctive-styling-for-significant-content/
参考:https://dev.classmethod.jp/articles/using-new-github-alert-syntax-in-saved-replies/

Re_FRe_F

Q. Mac で ifconfig を打ちましたが、内容がよくわかりません

以下の通り、Mac のリンゴアイコンから「Option(ALT)」を押しながら「システム情報」を選択し、サイドバーから「ネットワーク」ないし「ネットワーク環境」でBSD装置名を見ると一部の詳細が書かれています。

また以下の記事やQ&Aも参考になります。

BSD装置名については簡単にこちらでもまとめます。なお、装置は複数あることがあるため en0, en1, en2,... というように番号が振られています。

項目 内容
lo ローカルループバック。自分自身のIPアドレス。
gif トンネルデバイス(Generic tunnel interface)。IPv4とIPv6を繋ぐ。
stf 6to4。IPv4ネットワーク経由でIPv6インターネットに繋ぐためのトンネル技術。
anpi Autonomic Network Programming Interface......?https://apple.stackexchange.com/questions/467852/what-is-the-network-interface-anpi
en イーサネット。wifiや有線LAN。
bridge Thunderbolt ブリッジ。
ap Access Point。Wi-Fiテザリング
awdl Apple Wireless Direct Link。iOSとの通信。iphoneと接続するなど。
llw Low Latency WLAN(低遅延WiFi)
utun サードパーティのネットワーキングアプリケーションが使用するTUN(L3/IP)/TAP(L2/Ethernet)インターフェース。多くのVPNがカーネル仮想ネットワーキングデバイスとして追加。https://qiita.com/fastso/items/db46e03fbacac9b38793
Re_FRe_F

Q. ネットワークに繋がらなくなりました。何を確認していくと良いでしょうか?

まずは ping を打ってみます。
ICMP はレイヤ3以下で動くプロトコルのため、ping が通るのであればレイヤ4以上の障害で、ping が通らなければレイヤ3以下の障害であると切り分けることができます。

そして以下のように自分に近いところから順番に問題の確認をしていきましょう。

# ping の使い方と調べるIPアドレスの順番の経験則
1. > ping 127.0.0.1 # TCP/IP の動作確認
2. > ping 自分のNICのIPアドレス # 自身のIPアドレス設定の確認
3. > ping 同じハブ・スイッチに接続されているデバイスのIPアドレス # LAN内のネットワーキングデバイスの確認
4. > ping 違うハブ・スイッチに接続されているデバイスのIPアドレス
5. > ping デフォルトゲートウェイのIPアドレス # LAN外へ出るためのデバイス/サービスを確認
6. > ping DNSサーバのIPアドレス
7. > ping 目的のサーバのIPアドレス # 目的に対して ping 確認

LAN外へ出たあとの、途中のルータの障害確認は traceroute を利用する。
なお、 traceroute は宛先IPアドレスに対して TTL を 0 から +1 させながら信号を投げており、ルータにて TTL が 0 になると ICMP を返すことから、途中のルータの経路を取得する仕組みとなっている。L2 over IP tunneling を用いると TTL が隠匿されるため、最終的な宛先IPアドレスしか返ってこないようになる。(L2 over IP tunneling は相手側のサブネットを同一セグメントとして扱えるようにする技術である)

  • 参考:ネットワークスペシャリスト試験令和3年午後I問題
Re_FRe_F

以下のスライドがわかりやすいです。

セッション・セッション管理・SessionID・Cookie とは

セッションとは「クライアントとサーバの一連の通信の開始(セッション開始)から終了(セッション終了)まで」を指します。
セッション管理とはその 「一連の通信を同一ユーザーのもの」 として扱うための仕組みのことです。
Cookie は セッションを実現するための手段・手法 の一つです。
※セッションがサーバに保存されるデータとか書かれている記事がありますが、セッションIDやセッションIDをキーにしたDBデータとかといろいろ用語が混同していそうです。

セッション管理の簡単な仕組みについて

  1. サーバは UserID, Password などの情報をもとに Cookie を作成してブラウザに渡します。
  2. ブラウザは Cookie を保存しつつ、リクエスト毎に Cookie をサーバに渡します。
  3. サーバは渡ってきた Cookie を照会することで、同一のユーザであることの確認を行います。

なお、サーバは Key, Value 形式などで Cookie を ブラウザに渡しますが、この際ブラウザ側で自由に改変できないように署名付きの暗号化などをするのが一般的です。(ユーザがデベロッパーツールなどで改変できてしまうため)

また、サーバ側は UserID, Password を利用して Cookie を作成してもよいのですが、サーバ側の DB上に SessionID(ハッシュ値など) をキーにして UserID, Password を保管し、ブラウザ側には SessionID を渡すという仕組みもあります。Cookie には 4 kByte の容量制限があるので、Cookie に情報を載せきれない場合や、ネットワーク上に UserID, Password を不用意に流したくない場合に使います。(基本、こちらの仕組みの方が多いはず)

なお、SessionID がなんらかの方法で盗まれた場合、セッションを乗っ取られる セッションハイジャック が発生するので注意です。

Re_FRe_F

Q. CREATE TABLE での INDEX と CREATE INDEX (or ALTER) のINDEX 挙動が違うように見える?

CREATE TABLE sample (
  id INTEGER, # INT は INTEGER のエイリアス
  type VARCHAR(255),
  name VARCHAR(255),
  PRIMARY KEY(id),
  INDEX(name, type) # KEY(name, type) でも可能
) ENGINE=InnoDB;

CREATE TABLE sample (
  id INTEGER,
  type VARCHAR(255),
  name VARCHAR(255),
  PRIMARY KEY(id),
) ENGINE=InnoDB;

CREATE INDEX name ON sample(name);
CREATE INDEX type ON sample(type);
or
ALTER TABLE sample ADD INDEX name(name);
ALTER TABLE sample ADD INDEX type(type);

において EXPLAIN SELECT * FROM sample WHERE name LIKE "n" の挙動が違うように見えた。前者は using where, using index があり、後者は using index condition が出てきている。

これをよく観察するために、SHOW INDEX FROM sample を確認してみた。
結果は以下の通りである。

mysql> show index from sample;
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table   | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| sample  |          0 | PRIMARY  |            1 | id          | A         |           0 |     NULL | NULL   |      | BTREE      |         |
| sample  |          1 | name     |            1 | name        | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |
| sample  |          1 | name     |            2 | type        | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.01 sec)

mysql> show index from sample;
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table  | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| sample |          0 | PRIMARY  |            1 | id          | A         |           4 |     NULL | NULL   |      | BTREE      |         |
| sample |          1 | name     |            1 | name        | A         |           4 |     NULL | NULL   | YES  | BTREE      |         |
| sample |          1 | type     |            1 | type        | A         |           2 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)

上記で分かるように、結果として複合インデックスのテーブルが1つあるか、単一のインデックステーブルが2つあるかの違いとなっていた。

  • 上では Key_name の項目が name に対して Seq_in_index が 1,2 で Column_name が name, type になってる。
  • 下では Key_name の項目が name, type に対して Seq_in_index が 1 で Column_name が name, type になってる。

Key_name つまり インデックステーブルが name 単一(Seq_in_index 1,2 で複合)なのか、name, type で2つのインデックステーブルがあるかで、見分けることが簡単に出来てるはずだけど、なんかドツボにはまった。

Re_FRe_F

Q. UNSIGNED なカラムに INSERT 文で負の数を打ったのにエラーにならない

CREATE TABLE sample (
	id INTEGER UNSIGNED NOT NULL,
	type TINYINT UNSIGNED NOT NULL,
	name TEXT,
	PRIMARY KEY(id)
) ENGINE=InnoDB;
mysql> select * from sample;
+----+------+------+
| id | type | name |
+----+------+------+
|  0 |    0 | mooo |
+----+------+------+
1 row in set (0.01 sec)

mysql> select id-10, type from sample;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`game_sngk_gwdev0034`.`sample`.`id` - 10)'
mysql> select id+10, type from sample;
+-------+------+
| id+10 | type |
+-------+------+
|    10 |    0 |
+-------+------+
1 row in set (0.00 sec)

SQL でSELECT @@global.sql_mode; を確認してみてください。
今回は設定なしのため、UNSIGNED に沿う形で値が丸め込まれました。

mysql> SELECT @@global.sql_mode;
+-------------------+
| @@global.sql_mode |
+-------------------+
|                   |
+-------------------+
1 row in set (0.00 sec)

その他、sql_mode の値に応じて、INSERT する前にエラーを出したり、丸め込まれた値で格納されたりと挙動が変わります。詳細は公式や他の記事にも詳しいです。

Re_FRe_F

Q. git commit --cleanup=scissors --no-edit とはなに?

--cleanup=scissors オプションでは、コミットメッセージの整形の仕方を指定できます。
具体的には以下のような処理をしてくれます。特に 5. があるのが --cleanup=whitespace との違いです。

  1. コミットメッセージの前後にある空白行を削除する
  2. 行末の空白文字を削除する
  3. コメント行を削除しない
  4. 連続した空白行を1行の空白行にまとめる
  5. ハサミ行以降の文字列を削除する

--no-edit オプションは、コミットメッセージを変更せずにコミットをします。

ハサミ行とは

以下の記述が行われている行のことです。
コミットメッセージには含める必要はないが、コミッターに知らせたい内容(コミットメッセージの記載ルールや注意事項など)をコミットテンプレートには含めたい、というときなどに利用できます。

以下の行以下が削除されることになります。

# ------------------------ >8 ------------------------

参考

Re_FRe_F

Q. B-tree 構造とはどういう構造になっている?

MySQL のインデックスで利用されている構造ですが、イメージはしづらいところはあります。
以下のサイトが挙動もアニメーションで見れて非常にわかりやすいです。

インデックスを利用する場合、レコードの追加・削除・更新が多いと B-tree 構造(B+木)を毎回更新する必要があり処理に時間がかかるため、なるべく追加・削除・更新がないようなテーブルに利用しましょう。特にユーザーテーブルに変にインデックスを貼ると悲しいことになります。

Re_FRe_F

インターフェース継承と実装継承を混在しているかもしれない。リスコフの置換原則を再履修する必要がある。

Q. 継承とインターフェースの違いはなんですか?

教科書的説明

以下のように説明はされています。

  • 継承は、親子関係によりメソッドを共通化できる
  • インターフェースは、クラスに対して仕様を強制できる

継承に関しては A is B となるように、Animalクラス(親)に対して Dog クラス(子)というように形成されます。
インターフェースに関しては、B can do(A) となるように、複合プリンタ(具象クラス)に対してコピー機能(インターフェース)というように仕様を設定できます。

継承とインターフェースの違いは実質、多重継承くらいではないか?

......というように説明はされるものの、正直それは継承とインターフェースにおける抽象度やグループ化の仕方の問題であって、実質的には多重継承ができるかできないかだけで、本質は同じものと思っています。

たとえば継承について、 Animal クラスに run, speak, jump というクラスを持たせたとします。しかし、実際の動物には走れないもの(アシカとか)、吠えないもの(うさぎ)、飛べないもの(ペンギン)などがいます。
そのため、もし厳密に定義しようとすると、 RunnableSpeakableJumpableAnimal クラスを実装する必要があります。これは結局、走る機能・吠える機能・跳ぶ機能を個別にインターフェースに用意し、Dog クラスに多重 implements しているのと実質同じです。

一方、複合プリンタクラスを作るにあたって、その仕様には copy, scan, print, ... といろいろな仕様があります。それらをすべてインターフェースで指定すると一つのクラスに多くの implement を書く必要があります。もし、新しくカメラ付き複合プリンタや3Dプリンタ機能付き複合プリンタのようなものが出た場合、複合プリンタクラスにさらに implement が一つ2つと追加される派生クラスができることになります。
このように implement を大量に記載する必要があるのであれば、DRY 原則を踏まえて複合プリンタクラスという一つのベースクラスに機能をまとめて用意し、派生クラスならそのベースを引き継ごう発想が自然と生まれるのも当然です。これが継承となります。

これが継承もインターフェースも本質的に同じことであると考える理由で、目的を実現するための手段でより煩雑さ・記述量を少なくするための手段が「継承」なのか「インターフェース」なのか、という違いだと思っています。(共通機能を抽象化するか個別機能を抽象化するか、その抽象化の対象が違うとも言えます)
結局、どちらを取った場合にも一長一短があり、それを解消するために継承とインターフェースのお互いの機能があると考えます。

継承は変更に弱く、インターフェースは変更に強い

ただ、それでも私は継承よりもインターフェース一択であると考えています。
継承は「例外」が出てきた際の変更に弱いためです。これは、speak を実装した Animal クラスに対して、Rabbit を実装しようとしたときに speak できない Rabbit(ウサギには声帯がありません)が Animal クラスを継承すべきなのか、という問題が発生するからです。

親クラスを作ることは仕様を事前に実装することになるので、例外に対しての変更に非常に弱いです。
一方で、インターフェースの場合は、たとえ例外が発生しても implements で機能を追加していけるため、例外に対しての変更に継承に比べて強いです。

このことから、なるべく継承ではなく、インターフェースをメインで利用するのが良いと思います。
なお、このような観点もあるため、Go/Rust/Julia などの最近の言語は継承をサポートしていないです。

参考

Re_FRe_F

標準出力と標準エラーの使い分けは?

両方とも「画面に出力される」という機能が同じなため、いかんせん混同しがちですし厳密な使い分けをあまり目にしたことはないです。ただ、先輩の方針として納得感があったものをメモします。

  • print STDERR:ログファイル吐き出し用や画面表示出力用に使う
  • print STDOUT:処理結果の出力(他のツールにパイプなどで渡したい結果や実行結果)に使う

以下のように例えば cat のエラー文言が出たことを考えます。(出典:https://www.hondalabo.net/entry/2016/04/24/013614)

$ cat aaa > bbb.txt
cat: aaa: No such file or directory

$ cat bbb.txt 
$ ←何も表示されない

これが標準出力であると、処理結果として bbb.txt に cat: aaa: No such file or directory が書き込まれてしまいますが、標準エラーであれば書き込まれることがないです。

標準 「エラー」 という文言のため print STDERR はエラー文言のときに、 print STDOUT は正常文言のときに、との使い分けをしがちです。しかし、基本的にはスクリプトが成功であれば画面に何も表示しない or 返り値を表示し、失敗すれば print STDERR で出力されるというのが理想のようです。

print 文で画面にいろいろメッセージを残したいですが、結局それもログとして追跡できるようにしたいからですので、そういうものはすべて print STDERR にすると良いよ、とのことでした。

その他参考

https://tokachi432.net/2023/07/17/標準エラー出力の重要性と使い方/

Re_FRe_F

Q. SOLID のリスコフの置換原則って何?

リスコフの置換原則(Liskov substitution principle)とは、教科書的に書けば以下のようになります。

  • 継承先(サブタイプ)のオブジェクトの仕様は、継承元(スーパータイプ)のオブジェクトの仕様に従わなければならない
  • プログラム内でサブタイプのオブジェクトが使われている箇所は全てスーパータイプのオブジェクトで置換可能でなければならない (スーパークラスをサブクラスに変えても動作が変わらない)

その他、Wikipedia ではリスコフの置換原則を満たすために以下を守る必要がある、と書かれています。

  1. 事前条件(preconditions)を、派生型で強めることはできない。派生型では同じか弱められる。
  2. 事後条件(postconditions)を、派生型で弱めることはできない。派生型では同じか強められる。
  3. 不変条件(invaritants)は、派生型でも保護されねばならない。派生型でそのまま維持される。
  4. 基底型の例外(exception)から派生した例外を除いては、派生型で独自の例外を投げてはならない。

たとえば、

  • 継承先が入力値に対して「1以上100未満」という条件であれば、継承元は入力値に対して「5以上、90未満」のように、条件範囲が厳しくなければならない。(事前条件は継承元の方が厳しくないといけない)
  • 継承先が出力値に対して「10以上、80未満」という条件であれば、継承元は出力値に対して「20以上、60未満」というように、条件範囲が強い必要がある。(事後条件は継承先の方が厳しくないといけない)

ということになります。
『リスコフの置換原則』が守られない場合、ポリモーフィズムを用いた挙動において、利用部分での挙動が意図しないものとなる or 専用実装をする必要が出てくる可能性がある。(「スーパークラスのメソッドAに先んじて、サブクラスのメソッドBを事前に呼ぶ出す必要がある」という実装になっていた場合、呼び出し元で if (hoge == サブクラス) {...} else {...} のような実装が必要になる)

参考例

「Q. 継承とインターフェースの違いはなんですか?」 にも通ずるものがある。

Re_FRe_F

Q. レビュー速度をチーム全体で上げるためにはどうしたらいい?

以下は、現職(2024現在)で教えてもらった方法を記します。

なぜレビューが遅くなりがちなのか

そもそもレビューの方法はちゃんと人から教わる機会がなく、それぞれが独自の方法でレビューすることが多いです。そのため、レビューの質や速度にばらつきも出るし、手順や観点が独自であるがためにあまり自信を持てず、だからこそ必要以上に時間を書けてしまっているという面もあります。
そこでまず、「チーム全体でレビューの手順・ルールや観点の共通認識を持っておく」 だけでもレビューの速度は上がるでしょう。

レビュー観点をチームで共通認識を持っておく

レビューの観点をチームで共有しましょう。

  1. 現状把握:現在の個々人のレビュー観点をそれぞれでスプシに書き出す。(項目、詳細、なぜいけないのか、など)
  2. 観点共有:それぞれのレビュー観点を発表して共有する。
  3. 認識補足:自分のレビュー観点で漏れていた部分を追加する。
  4. 認識共有:レビュー手順や速度感をチーム内で擦り合わせる。
    1. PR 未レビューはレビュイーの作業ブロックになるため、何にも優先してやるという認識を持ちましょう。

レビューの手順をチームで共通認識を持っておく

レビューの手順をチームで共有しましょう。具体的な手順については共通認識が取れていればなんでも良いです。以下は一つの例になります。

  1. そもそも大前提として、提出された PR は「動作確認はされている」という認識に立つ。
  2. コーナーケースはあるかもしれないが、大まかには間違っていないとレビュイーを信じる。
  3. 1回目は、レビュー観点をもとに機械的に見れる部分を見る。
    1. このとき、具体的なロジック・処理の中身は追わない。
  4. 2回目は、レビュー観点をもとに機械的に見づらい、気になったロジック部分を見る。
    1. このとき、仕様詳細までは追わなくてよい。
  5. 3回目は必要あれば、仕様と照らしつつロジックが正しいのかどうかをチェックする。

あとはやる

あとはレビュー依頼が来たら上記の手順で行います。
なお、Issue/PR については、GitHub App や GitHub Actions を用いてすぐに Slack などで検知できるようにするとよいでしょう。
「一回目のレビューを 5-10 分以内で返せること」 が理想状態です。また、その速度感で返せるように レビュイー側も PR も小分けに出す訓練をしましょう。

Re_FRe_F

Q. 忘れがちな perl の並列処理の書き方について教えてください

以下が例になります。
run_on_finish

sub exec_parallel {
    my ($servers) = @_;

    my $par = 16; # 並列数(適当に決めた)
    my $pm = new Parallel::ForkManager($par);

    # 子プロセスの終了時に動く:処理の対象サーバーとメッセージを print する
    $pm->run_on_finish(sub {
        my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data) = @_;
        if (defined $data) {
            my $server = $data->{server};
            my $stdout = $data->{stdout};
            my $stderr = $data->{stderr};
            my $sync_res = $data->{sync_res};

            print "[$server]\n";
            print $stdout;
            print $stderr;
            push(@err_servers, $server) if ($sync_res != 0);
        }
        die if $exit_code != 0;
    });

    # for 文内が並列に処理される
    for my $server (@$servers) {
        $pm->start and next; # fork

        # 処理したい具体的な内容:ssh でコマンドを叩く
        my $ssh_command = "ssh -A -t $server";
        print "[exec]:$ssh_command $COMMAND\n";
        my $exec_command = "$ssh_command \"$COMMAND\"";

        # 処理を行い、標準出力と標準エラーを受け取る
        my $sync_res;
        my ($stdout, $stderr) = capture { $sync_res = system($exec_command) };

        $pm->finish(0, +{ server => $server, stdout => $stdout, stderr => $stderr , sync_res => $sync_res });
    }
    $pm->wait_all_children;

    if (scalar(@err_servers)) {
        die "ERROR SERVERS: @err_servers.\n";
    }
}

参考

Re_FRe_F

Q. 多態性ってどんな機能?

同じ呼び出しでもオブジェクトごとに別の挙動を実現することができる。

java の場合

# Java の場合
Animal[] a = new Animal[3];
a[0] = new Dog();
a[1] = new Cat();
a[2] = new Pig();

for (Animal ani : a) {
     ani.bark();
}

// a[0].bark → わんわん
// a[1].bark → にゃーにゃー
// a[2].bark → ぶーぶー

Perl の場合

# Perl の場合
my @a;
a[0] = Sngk::Dog->build();
a[1] = Sngk::Cat->build();
a[2] = Sngk::Pig->build();

for my $ani (@a) {
    ani->bark();
}

// a[0]->bark() → わんわん
// a[1]->bark() → にゃーにゃー
// a[2]->bark() → ぶーぶー

Java などは型があり、Dog, Cat, Pig も別の型として扱うので、ループで bark を使うには継承元の Animal 型として振る舞わせることで扱えるようになる。(多態性)
Perl は型がないのでそもそも Dog, Cat, Pig のクラスを区別しなくても普通に bark() を呼べる。

このように、「いぬ」「ねこ」「ぶた」はそれぞれ違うが、これらを「動物」という大まかなにとらえることで、プログラムとして記述できるようにするのが多態性である。

参考

Re_FRe_F

途中

Q. PR ではどのような観点を見るの?

Q. レビュー速度をチーム全体で上げるためにはどうしたらいい?

に関連してレビューを行う上での観点について簡単に列挙する。
以下の記事も良い内容となっているので参考にしたい。

PR の前提条件

  • Issueチケットとの紐付けがされていること
    • Issue に PR を作成した背景情報が書かれている(はず)
    • Issue にて背景や前提条件が擦り合っている(はず)
  • 手動での動作確認ができていること
    • スクショも貼ってあること
  • テストがあればテストが通っていること

機能要件

「Q. レビュー速度をチーム全体で上げるためにはどうしたらいい?」で述べたように、機能要件については PR 作成者が担保していることを前提とする。(チームで合意を取っておく)
ただし、設計についての認識違いや実装の瑕疵などがある場合はあるため、そことなく注意はしておく。必要あれば開発環境で動作確認をしてみる。

非機能要件

上述の参考記事
で引用されている IPA 非機能要求グレードが非常に参考となる。

可用性

性能(パフォーマンス)・拡張性

運用・保守性

移行性

セキュリティ

Re_FRe_F

Q. Google 日本語入力で Mac のように Ctrl + Space でかな英数切り替えをできるようにしたい

以下の記事を参考にすると非常によろしい。

[Google 日本語入力 プロパティ] から [キー設定] の [編集] を開き、以下の4行を追加設定する。

Ctrl+Space で切り替えるための設定

Re_FRe_F

Q. MySQL コマンドを打ったときの枠線を削除したいです

以下の記事のとおりにするとよいです。--silent オプションを付けましょう。

❯ mysql -uroot mydb -e 'SELECT id FROM mytable'
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+

# 枠線が不要な場合
❯ mysql -uroot mydb -e 'SELECT id FROM mytable' --silent
id
1
2
3

# カラムも不要な場合
❯ mysql -uroot mydb -e 'SELECT id FROM mytable' --silent --skip-column-names
1
2
3

なお、--silent-s--skip-column-names-N で省略できるようです。