OmniAICreator/Qiita-1.07M huggingface dataset:
【Zsh】コマンド履歴に基づいて補完する方法

## はじめに

zsh-autosuggestionsを使用すると以下のようにコマンド履歴に基づいて補完できます。  

![スクリーンショット 2022-12-09 19.57.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342443/f804649c-c59a-20a6-635b-47c75d1b080f.png)


zsh-autosuggestionsの導入方法について記載します。  

## 導入方法

以下のコマンドを実行してzsh-autosuggestionsをクローンします。  
パスは任意です。  

```terminal
git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
```

`~/.zshrc`に以下を追記します。  

```.zshrc
source ~/.zsh/zsh-autosuggestions
```

記述後に`.zshrc`を再読み込みします。  

```terminal
source ~/.zshrc
```

するとコマンド履歴に基づいて補完できるようになります。  

![スクリーンショット 2022-12-09 19.57.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342443/f804649c-c59a-20a6-635b-47c75d1b080f.png)

候補が表示された状態で「→」を押下すると候補が自動で入力されます。

### Oh My Zshを使用する場合

#### Oh My Zshをインストール
まずOh My Zshをインストールします。  

以下のいずれかのコマンドを実行します。  

- curl

```terminal
sh -c "$(curl -fsSL <https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh>)"
```

- wget

```terminal
sh -c "$(wget -O- <https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh>)"
```

- fetch

```terminal
sh -c "$(fetch -o - <https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh>)" 
```

上記のコマンドを実行すると既存の`.zshrc`は`.zshrc.pre-oh-my-zsh`に変更されます。  

#### zsh-autosuggestionsをクローン

zsh-autosuggestionsを次のコマンドでクローンします。  

```terminal
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
```

`ZSH_CUSTOM`のパスか設定されていなければ`~/.oh-my-zsh/custom`以下にクローンされます。  

#### `.zshrc`に設定を記述

zsh-autosuggestionsを使用可能にするため`.zshrc`を編集します。  

```.zshrc
...

plugins=( 
    # other plugins...
    zsh-autosuggestions
)

...
```

記述後に`.zshrc`を再読み込みします。  

```terminal
source ~/.zshrc
```
AWS未経験者のAWS SCS合格体験記

<p>SCS試験を受けて合格したので、合格する方法をシェアします。<br>
こんな人が合格したなら私でも出来るじゃんと思って頂きたいです。<br>
何故なら、AWS SCSの合格体験記は、既にSAPを取得していたり、AWS業務を数年経験している人しかおらず私のようなスキルセットの人は居ませんでした。
</p>

<p>
取得した時の私の経歴は<br>
高卒(農業高校)<br>
運用保守経験のみ(1年ちょっと)<br>
AWSサービスはスクールで少しさわった程度<br>
AWS CCP,AWS SAAを取得
</p>

<P>SCSの目的はSAPの問題を解いた時に、まず問題文が理解出来ない、そして答えも見ても理解出来なかったので、SCSに逃げてきました。笑<br>
</P>

<p>
取得経緯はさておき本題です。SCS合格するには下記の手順で進めていけば合格出来ます。
期間は1ヵ月ほどで合格出来ました。
</p>

<h2><a name="sankousho">参考書を読破する</a></h2>
<p>使用した参考書</p><a href="https://www.amazon.co.jp/%E8%A6%81%E7%82%B9%E6%95%B4%E7%90%86%E3%81%8B%E3%82%89%E6%94%BB%E7%95%A5%E3%81%99%E3%82%8B%E3%80%8EAWS%E8%AA%8D%E5%AE%9A-%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3-%E5%B0%82%E9%96%80%E7%9F%A5%E8%AD%98%E3%80%8F-Compass-Books%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-NRI%E3%83%8D%E3%83%83%E3%83%88%E3%82%B3%E3%83%A0%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BE-ebook/dp/B08DCLRHC7">要点整理から攻略する『AWS認定 セキュリティ-専門知識』</a>
<p>ここでセキュリティ試験に必要な知識をインプットしましょう。念入りに読み込む必要はありません。
私のオススメの学習法は、読みながらサービスがどこに配置されているのかを図にする事で理解度がグッと増すかと思います。<br>
<a href="リンク">気になる方はこちらをご覧ください。</a>
</p>

<h2><a name="sankoushomondai">参考書の問題を解く</a></h2>
<p>参考書の最後の章に練習問題が40問あるので、解いてみましょう。<br>
私がした方法は、解答時に正答率を出して今何％ぐらい理解しているのかをメモしていました。<br>
そして解答ページに、どの章を読むべきかが記載してあるのでその章を今度は音読してインプットしていました。</p>

<h2><a name="sample">サンプル問題を解く(無料)</a></h2>
<p>AWS公式のサンプル問題があるのでそれを解きましょう。解説が少し雑なので解説を読んでも理解できない箇所は<br>
ググって解決してください。ここも正答率をどこかにメモしておいて下さい</p>
<a href="https://aws.amazon.com/jp/certification/certified-security-specialty/">サンプル問題</a>

<h2><a name="mogisiken">模擬試験を受ける(無料)</a></h2>
<p>最近模擬試験が無料になったので受けてみましょう！受け方は分かりやすい記事があったのでリンクしておきます。</p>
<a href="https://dev.classmethod.jp/articles/new-aws-official-practice-questions/">模擬試験受け方</a>

<span>参考書、サンプル問題、模擬試験、【初見で全て正答率85%以上】であれば試験受けましょう！レベル感は同じぐらいでした。</span>

<h2><a name="hukabori">理解出来ていないサービスを深堀しよう</a></h2>
<p>この時点で参考書、サンプル問題、模擬試験で計70問解いたら何となく自分の理解出来ていないサービスや単語が
分かってきたと思います。なのでここでサービスの深堀、単語の深堀をしていきましょう。</p>
<p>私は、SSOやIAMロール、AWS KMSのキーポリシーについて理解出来ていなかったので深堀しました<br>
私がオススメするのが、youtubeで苦手なサービス名を検索するとAWS公式のサービスの解説動画があるのでそれを見て学習していました。（とても分かりやすいです）</p>

<h2><a name="webmondaishuu">AWS WEB問題集をしよう</a></h2>
<p>これは有料の問題集です。問題のレベル感はこちらの問題集の方が高いと感じました。<br>
こちらの問題集を2周すれば十分でしょう！正答率はメモしておくといいです。</p>


<h2><a name="shuukai">自分が納得出来るまで周回</a></h2>
<p>私は試験までに、参考書、模擬試験を3周<br>
WEB問題集、サンプル問題を2周しました。
</p>
<p>試験当日のまでの各問題の正答率は下記の通りです。
参考書 90%
サンプル問題 100%
模擬試験 100%
WEB問題集 77%
</p>

試験勉強頑張って下さい！
IchigoJam web で BTN にチェックが入っていても BTN() が 0 を返す場合がある

# IchigoJam web

[IchigoJam web by jig.jp](https://fukuno.jig.jp/app/IchigoJam/) では、「I/O」をクリックすると入出力端子の状態を確認・操作できるUIが出てくる。
ここにはチェックボックスがあり、チェックを入れるとHIGH、チェックを外すとLOWを入力することを表す。
ここには、「BTN IN9」と書かれた項目もある。

# BTN()

IchigoJam BASIC の `BTN()` は、ボタンが押されているとき1、押されていないとき0を返すとされる関数である。

[IchigoJam BASIC リファレンス ver 1.4](https://ichigojam.net/IchigoJam-1.4.html)

> ボタンが押されていれば1、そうでないとき0を返す（数：0(付属ボタン)/UP/DOWN/RIGHT/LEFT/SPACE/X(88)、省略で0、-1でビットパターンで返す）

[IchigoJam BASIC 1.4 コマンド一覧](https://fukuno.jig.jp/app/csv/ichigojam-cmd.html#BTN)

> ボタンが押されていれば1、そうでないとき0を返す（数：0(付属ボタン)/UP/DOWN/RIGHT/LEFT/SPACE/X(88)、省略で0、-1でビットパターンで返す）

[IchigoJam-BASIC/BTN.txt at master · fu-sen/IchigoJam-BASIC](https://github.com/fu-sen/IchigoJam-BASIC/blob/master/BTN.txt)

> ボタンが押されている状態の場合は 1、押されていなければ 0 を返します。

# BTN にチェックが入っていても BTN() が 0 を返した

「BTN IN9」の下のチェックボックスにチェックを入れ、インタラクティブ (`RUN` で実行していない) モードで `?BTN()` を入力して `BTN()` の返り値を表示してみると、`0` と表示された。
`?IN(9)` も試してみたが、これも`0` が表示された。
以下のプログラムを `RUN` して試すと、チェックボックスの状態がきちんと反映された。

```
10 CLS
20 LOCATE0,0
30 ?DEC$(BTN(),5)
40 ?DEC$(IN(9),5)
50 GOTO20
```

例えば `?IN(1)` は、`RUN` を用いなくてもチェックボックスの状態が反映された。

# 不都合の正体

実は、IchigoJam web の `BTN()` や `IN(9)` は、このチェックボックス以外にマウスクリックの入力も受け付ける。
そして、チェックボックスを操作した後にマウスクリックをすると、マウスクリックによる入力がチェックボックスより優先されるため、`BTN()` や `IN(9)` の返り値がチェックボックスの状態と合わなくなるようである。
実際、マウスクリックを押したまま `?BTN()` を実行すると、`RUN` を使わなくても `1` が表示された。

# BTN() の動作がリファレンスに反する

「BTN IN9」のチェックボックスをオンにした後、マウスクリックをせずに `?BTN()` を実行すると、`1023` が表示された。
…そう、**1023が表示された**のである！
リファレンスによれば、`BTN()`はボタンが押されていれば**1**、そうでなければ**0**を返すはずなのに！！
**嘘つき！！！**

先日紹介した[`WAIT` や `TICK()` で用いる時間の単位が通常より長い上、`WAIT` と `TICK()` で時間の単位が異なる件](https://qiita.com/mikecat_mixc/items/149e3448e3159a843e50#wait%E5%91%BD%E4%BB%A4%E3%81%A8tick%E9%96%A2%E6%95%B0%E3%81%AE%E6%99%82%E9%96%93%E3%81%AE%E5%8D%98%E4%BD%8D)といい、IchigoJam web はリファレンスに反する動作をしがちのようだ。
イースターエッグかな？
作者の方針は知らないが、私はこんなものは求めていない。
実機とwebで異なる挙動をするのはいいが、リファレンス、もしくは『[🍓🅱️ IchigoJam BASIC コマンド一覧 command reference (Japanese)](https://github.com/fu-sen/IchigoJam-BASIC)』に明示しておいてほしい。

# 改善策の検討

まず、「マウスクリックでの操作が BTN IN9 のチェックボックスに反映されてほしい」と考えた。
しかし、前述の通り、マウスクリックで操作した場合は `BTN()` は `1` を返すが、チェックボックスで操作した場合は `BTN()` は**リファレンスに反して** `1023` を返すのである。
チェックボックスをマウスクリックに同期させた場合、`1` を返すべきか `1023` を返すべきかわからなくなるかもしれない。
…と思ったが、さらに考えると、「マウスクリックされたらそれに合わせてチェックボックスの状態を変化させる」だけで、「チェックボックスが操作されたらそれに合わせてマウスクリックの状態を変化させる」わけではないので、通常のマウスクリックによる操作と同様に `1` を返すのが妥当そうである。

これはあくまで自分の意見であり、作者は他の意見を持つ可能性がある。

# 証拠動画

わかりやすいよう、開発者ツールから以下のコードを実行し、マウスクリックの状態を表示させた状態で動画を撮影した。

```js
e = document.createElement("span");
e.setAttribute("style", "position: fixed; top: 1em; left: 1em;");
document.addEventListener("mousedown", function() {
	e.innerHTML = "クリック ON";
});
document.addEventListener("mouseup", function() {
	e.innerHTML = "クリック OFF";
});
e.innerHTML = "クリック OFF";
document.body.appendChild(e);
```

<iframe width="560" height="315" src="https://www.youtube.com/embed/tlcJT13IDNc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# おわりに

※この記事は執筆時点での情報に基づく。
※IchigoJamはjig.jpの登録商標です。
C++のThreadで並列処理

研究で行うシミュレーションの計算時間が長かったので、並列処理を実装しました。メモ程度に記しておきます。
```C++
#include <iostream>
#include <thread>
using namespace std;

void print_a(){ for(long long i=0; i<100; i++) if(i >95) cout << "A"; }
void print_b(){ for(long long i=0; i<100; i++) if(i >95) cout << "B"; }

int main(){
    thread th_print_a(print_a); // スレッドを立ち上げ
    thread th_print_b(print_b);

    th_print_a.join(); // スレッドの処理が終わるまで待つ
    th_print_b.join();

    cout << "Fin" << endl;

    return 0;
}
```

実行結果
```
AABBBBAAFin
```
こんな感じで各スレッドが順番に実行され、全ての実行が終わってからFinが出力されている様子が分かります。

ちなみにオブジェクト内の関数をthreadで利用したい場合には、
```C++
std::thread th_test([this]() { this->test_function(); });
```
こんな感じで実装できます。
AWSのさまざまなデータベース②

# DynamoDBとは

完全マネージド型のNoSQLデータベースサービス

- ハイスケーラブルで無制限に性能を拡張することができる
- 負荷が高くなっても応答速度が低下しない低レイテンシー
- 高可用性(データは3つのAZに保存)
- フルマネージドサービス
- データの読み取りと書き込みに特化（高度な最適化）されている
- ストレージの容量制限がない
データ容量の増加に応じたディスクやノードの増加は一切不要

## 特徴

- キーバリュー（ワイドカラム型）のためキーに対するバリューのCRUD処理
- 簡易なクエリやオーダー
- 数万人がアクセスするようなビックデータのデータ処理など

### パーティショニング

大量のデータを高速に処理するためにパーティショニングによる分散処理を実施している

テーブルに対して3つのパーティショニングを作ることによって並列処理を行い結果的に処理が高速になる

## できないこと

- 一般的にSQLができることはできない（JOIN/TRANSACTION/COMMIT/ROLLBACKなど）
- データの検索や結合処理などは向いていない
- 大量のデータの読み書きはコストがかかる

## 整合性モデル


||書き込み|読み取り|
|---|---|---|
|結果整合性|少なくとも2つのAZ(2/3)での書き込みが確認が取れた時点で完了|デフォルトで結果整合性モデルを利用しているので最新の書き込み結果がすぐに反映されない可能性がある|
|強い整合性||オプションとして強い整合性モデルがあり全ての読み込みが完了している状態|
## 料金システム

DynamoDBテーブルでのデータの読み取り、書き込み、保存、ユーザーが有効化したオプション機能によって料金が決まり、2種類のキャパシティモードがある。

### オンデマンドキャパシティモード

こちらは、テーブルに対してアプリケーションが実行した、データの読み取り、書き取りに対して生じる。

- 使った分だけ金額がかかる
- アプリケーションのトラフィックの予測ができない

### プロビジョニング済みキャパシティモード

こちらは、アプリケーションに必要となるであろう1秒当たりの読み込みや書き込み回数の指定をする。

- 必要となるキャパシティを予想して料金のコントロールができる
- アプリケーションのトラフィックの予測ができる

## DynamoDBのその他の特徴

### DynamoDB Streams

DynamoDBテーブルに保存されたデータの追加・変更・削除の変更情報を暗号化してログに保存しておく仕組みのこと

### DynamoDB Accelerator(DAX)

DynamoDBにインメモリキャッシュ型の機能を付与することができる。

https://aws.amazon.com/jp/dynamodb/

## おわりに

今回はDyanamoDBを簡単にまとめてみました。

まだ深くは学習できていないので自分で実際に触ってみて覚えていこうと思います。

https://qiita.com/taka-d/items/53c077d35f0bc66b8338
DoctrineとEloquent比較大全9: 複雑な条件での絞り込み(1) QueryBuilder

day9の今日は複雑な条件でレコードを絞り込む書き方を見ていきます。

## Doctrine

```php
<?php

declare(strict_types=1);

use App\Entity\Book;
use Doctrine\ORM\EntityManagerInterface;

require __DIR__.'/../vendor/autoload.php';

/** @var EntityManagerInterface $entityManager */
$entityManager = require __DIR__.'/bootstrap.php';

// priceが5000円未満でタイトルがSymfonyの本のリスト
/** @var array<Book> $books1 */
$books1 = $entityManager->getRepository(Book::class)
    ->createQueryBuilder('b') // aliasは何を設定しても良い
    ->where('b.price < :max_price')
    ->andWhere('b.title = :title_symfony')
    ->setParameter('max_price', 5000)
    ->setParameter('title_symfony', 'Symfony')
    ->getQuery()
    ->getResult()
;
```
<https://github.com/77web/doctrine-vs-eloquent/blob/2ab2ea1c8e0863d6b4ec86620e995f8997477a53/Doctrine/Usecase/filter_by_complex_condition_01.php>

- `where()` を2回使うと1回目の `where()` が取り消されるので、AND条件を指定するときは `andWhere()` を使います。

## Eloquent

```php
<?php

declare(strict_types=1);

use App\Models\Book;

require __DIR__.'/../vendor/autoload.php';

$books1 = Book::query()
    ->where('price', '<', 5000)
    ->where('title', 'Symfony')
    ->get()
;
```
<https://github.com/77web/doctrine-vs-eloquent/blob/2ab2ea1c8e0863d6b4ec86620e995f8997477a53/Eloquent/Usecase/filter_by_complex_condition_01.php>

- `where()` を複数回使うとAND条件になります。
- 条件を重ねていき、最後に `get()` を呼ぶことでBookモデルのCollectionを取得できます。
- `where()` にフィルタ対象の項目名・フィルタ値だけを渡すと `=` 条件として扱われます。
MVCとは

## 初めに
今回はMVCモデルについて記述していきたいと思います。

## MVCモデルとは
MVC は、Webフレームワークで一般的に取り入れられているアプリケーション設定を整理するための概念の一つ。

モデル（Model）頭文字「M」、ビュー（View）頭文字「V」、コントローラー（Controller）頭文字「C」で「MVC」と言います。

それぞれ次の役割を持ちます。

## モデル
システム内のビジネスロジックを担当します。システムの設計や機能をどうするかがここで行われる。

## ビュー
実際に表示したり、入力する機能の処理を行うのがビュー。

## コントローラー
コントローラーは、ユーザーの入力に基づきモデルとビューを制御する役割を担っています。「クライアント」「モデル」「ビュー」の橋渡し役。


## MVCモデルのメリット
・役割で効率的にアプリケーション開発が効率的に行えます。
・「M」「V」「C」それぞれで分割して専門性が高い仕事が可能です。
・専門的な仕事の独立性が非常に高く変更などの対応にも柔軟に行なえます。

## デメリット
・情報データの管理が大変
・コントローラーの処理が大変
・「V」と「C」の依存性が高い


こちらの記事を参考にさせていただきました。
https://www.geekly.co.jp/column/cat-technology/1911_040
GitHubのPR最下部に表示される「「アレ」」は、このAPIでPOSTできる

本稿は :sparkles: [GitHub Actions Advent Calendar 2022](https://qiita.com/advent-calendar/2022/github-actions) :sparkles: 12日目の記事です :tada:

## 概要

本稿では、GitHubのPullRequestの最下部で表示される、上記画像の部分（=**「アレ」**）の概要を述べます。

わかること
- 「アレ」ってなんて名前なの？
- 「アレ」を操るにはどうしたらいいの？

成功時の「アレ」
![success.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84945/7b012789-1516-cce5-bf9c-b7392db6f67e.png)

pending時の「アレ」
![failure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84945/84ab1761-c0b9-712c-7a38-93ce1fe0984d.png)

## 「アレ」 = **Commit Status**

名称について。

「アレ」は**Commit Status**と言うらしいです。

今回かかったうちの半分以上は、この名称を調べるのに使った気がします……。

これで名称がわかりました。

## Commit Status？ PRステータスじゃないの？ POSTする方法

Commit Statusはよく、テストカバレッジを表示したり、ビルドエラーを表示したり、lintの成功を表示したりします。
それ故に僕も「PRのステータスだ」と思っていたのですが、それは過ちです。

**このステータス部分は、PRの「最新コミット」に載るものです。**

- [コミットのステータス - GitHub Docs](https://docs.github.com/ja/rest/commits/statuses?apiVersion=2022-11-28)

具体的には下記例示のように、PRの最新コミットのshaにPOSTします。

```yaml:.github/workflows/coverage.yml
name: Coverage

on:
  pull_request:
    branches: [ "develop" ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - run: ...
    #
    # ...
    #
    - run: ./.github/workflows/post-coverage.sh ${{ secrets.GITHUB_TOKEN }} ${{ github.event.pull_request.head.sha || github.sha }}
```

```bash:./.github/workflows/post-coverage.sh
#!/bin/bash

github_token=$1
commit_head_sha=$2
result='Coverage 90%'  # 実際はここでカバレッジを集計する

# YOUR_NAME と REPO は、実際のリポジトリに置き換えてください。
# 例えば https://github.com/aiya000/dotfiles なら aiya000 と dotfiles。
# => aiya000/dotfiles

curl -X POST \
  -H 'Accept: application/vnd.github+json' \
  -H "Authorization: Bearer $github_token" \
  -H 'X-GitHub-Api-Version: 2022-11-28' \
  -d "{\"state\":\"success\",\"description\":\"$some_text\",\"context\":\"coverage-bot\"}" \
  "https://api.github.com/repos/YOUR_NAME/REPO/statuses/$commit_head_sha"
```

これでGitHubのPRに
```
✅ coverage-bot  ― Coverage 90%
```
と表示されるようになりました :tada:

## 終わりに

GitHub氏、その設計でよかったんか？ :thinking_face:

次回、「PRにコメントするにはIssue Comment APIを使う」の巻！！

- [Issueのコメント - GitHub Docs](https://docs.github.com/ja/rest/issues/comments?apiVersion=2022-11-28)
ROS2 Rustクライアント実装状況 2022/12/09現在

## 本記事の目的

ROS2のRustクライアントはいくつか存在する。

2022/12/09時点でのそれぞれのクライアントの実装状況について記述しておく。今後の発展に期待。

## ROS2 Rustクライアント

@MrBearing さんに教えて貰いましたが、ROSCON2022@京都にて以下のような発表があったようです。

[発表資料](http://download.ros.org/downloads/roscon/2022/Native%20Rust%20components%20for%20ROS2.pdf)

勝手に引用させてもらうと以下のような感じです。

| 名称        |作者| URL | 概要 |
|-----------|------------|------------|---|
|ros2-rust | Multiple contributors | https://github.com/ros2-rust/ros2_rust | Official Rust binding to rcl |
|ros2-client/RustDDS | Atostek Oy / Juhana Helovuo and others | https://github.com/jhelovuo/ros2-client | Native Rust implementation of ROS2 client library - and DDS! |
|r2r | Martin Dahl and others | https://github.com/sequenceplanner/r2r | Binding to rcl. Rust API uses async functions. |
|rclrust | Yuma Hiramatsu | https://github.com/rclrust/rclrust | Uses Rust macros(!) to translate IDL to Rust types. |

資料だと一番下にrosrustが乗っていますが、rosrustはROS1なので関係ないです。
[rus2](https://github.com/marshalshi/rus2)は流石に作りかけすぎるので省かせてもらいました。

ということでいっぱいありそうですね。

## ROS2クライアントに必要なもの

ところでみなさんROS2クライアントを実装したことありますでしょうか？私はありません。
しかしROS1だけは経験があります。

### ROS1クライアントについて

ROS1では以下３つをサポートすれば大体OKでした。

* Pub/Sub
* Service
* Parameter

他に必要なのはactionlibとかtfとかなんですが、それらはこの仕組みを使って実装出来るので、そこはオプショナルというか、誰か他の人が作ってくれてもいいし、最悪Pub/Sub使えば無理やりactionを使うことは可能でした。

### ROS2クライアントについて

一方でROS2では上記３つに加え、

* Action

もサポートする必要があります。それはROS1ではactionはPub/Subの仕組みを使ってactionlibという外部ライブラリで実現されていたのですが、ROS2ではROS2本体の機能として取り込まれたからです。

またROS2で新たに導入されたQoSの設定もROS2では必須と言っていいレベルです。それは設定によっては全く通信出来ない接続関係もある（うまく行かない、とかではなく、仕様として接続出来ない。））ためです。

## ということで

ROS2クライントに求められるのは

* Pub/Sub
* Service
* Parameter
* Action

上記4つになります。(多分他にもあると思いますので詳しい方、コメント等で教えて下さい！LifeTimeとか？QoSが必須なのは理解していますが、全ライブラリ実装しているので今回は省略。)

特にROS(2)の最大の売りであるnavigation2の指令値はaction形式になっており、actionを使えないクライントライブラリはnavigationを使えない事になってしまうため、actionの実装があるかどうかは本格的にROS2をその言語で使えるかどうかを分ける非常に重要な観点となります。


## ROS2 Rustクライントの実装状況

そこで各クライアントの上記４つの機能の実装状況を確認してみましょう。

| 名称       |Pub/Sub|Service|Parameter|Action|
|-----------|---------|------------|---|----|
|ros2-rust | ✔ |  ✔ |  ✔ |  |
|ros2-client/RustDDS |✔ |  ✔ |  ✔ |   |
|r2r|✔ |  ✔ |  ✔ |  ✔ |
|rclrust|✔ |  ✔ |  ✔ |   |

実際のソースコードを見た結果、私の認識では上記の状態です。
これではちゃんとROS2クライントとして完成しているのはr2rだけ、ということになります。

ということで弊社で開発している[OpenRR](https://github.com/openrr/openrr)でもr2rを採用しています。他にも理由はあるのですが。

## 対応バージョン

また、ROS(2)は頻繁にバージョンアップしますので、どのバージョンに対応しているかが重要です。これは人によって古いほうがいい、新しいほうがいいとかあると思いますが、個人的にはFoxy以前のROS2は実用レベルにないと思うので、Foxy, Humbleの２つをサポートしているかどうかが重要かと思います。それぞれLTSバージョンですし。

これは実際に対応しているか調べるのが難しいのでREADMEの記述を信じることにします。

| 名称       |Foxy | Humble|
|-----------|---------|------------|
|ros2-rust | ✔ | ✔ |
|ros2-client/RustDDS | (Galactic) |  |
|r2r|✔ |  ✔ |
|rclrust|✔ | (Rolling) |

こちらも正式にFoxy, Humble対応しているのはr2rのみという結果になりました。(rclrustはRollingに対応しているのでHumbleも動きそうですが記述なし)
(編集2022/12/12 @wep21 さん情報ありがとうございます。：ros2-rustは[リリースノート](https://github.com/ros2-rust/ros2_rust/releases/tag/0.2.0)にHumbleの記述があるので多分動くかと。)

## 今回の結論

現時点ではr2rしか使えるものはない。

## 終わりに

ということで2022/12/09現在でのROS2 Rustクライアントの開発状況を紹介しました。
今後の開発に期待ですし、Rustでのロボット開発がもっと盛り上がって欲しいので出来る範囲で私も貢献していきたいところです。
Zapierについての初歩的な知識

# ジャンル
　ワークフロー自動化

# プラットホーム

1. ウエブ

# 料金(USD)

1. Free
    - 100タスク／月
    - 5ザップ
    - 更新時間15分
    - シングルステップZap
1. Starter ($19.99/月)
    - 750タスク／月　
    - 20ザップ
    - 更新時間15分
    - マルチステップZap
    - 情報気密度が高い表示
    - プレミアムアプリ3本
    - フィルター
    - フォーマッターのステップ
    - Webhookｓによる接続
    - バージョン管理
1. Professional ($49/月)
    - 2,000タスク／月　
    - 無制限のザップ
    - 更新時間2分
    - パスの複製とパスを使ったカスタムロジック
    - オートプレイ
1. Team ($399/月)
    - 50,000タスク／月
    - 無制限のザップ
    - 更新時間1分
    - 全てのザップをエクスポートする
    - ユーザー数無制限
    - フォルダーのアクセス権
    - プレミアムサポート
    - 共有アプリの接続
    - 共有ワークスペース
    - オーナーアクセス
1. Company ($799/月)
    - 100,000タスク／月
    - 無制限のザップ
    - 更新時間1分
    - 高度な管理者権限
    - ユーザープロビジョニング（SCIM)
    - アプリの制限事項
    - SAMLシングルサインオン
    - カスタムデータリテンション
    - アカウント統合
    - スーパーアドミン
    - ライブチャット
1. for non-profits
    - 全てのプランが15%割引

　Free以外のそれぞれのプランで、料金を追加してタスク量を増やすことが可能。
　無料版のシングルスッテプとは、トリガーに対して１アクションだけしか作成できません。マルチステップになると、条件指定などの複数のアクションを組み合わせることができます。

https://zapier.com/pricing


# 概要
　Zapierは、Webアプリケーションの自動化のためのワークフローを提供する、Web API間トランスレータです。
　2011年、ミズーリ州コロンビアで設立され、ベッセマー・ベンチャーパートナーズ（Bessemer Venture Partners）から130万ドルの資金を受けました。2014年に黒字化を達成し、評価額は50億ドルに上昇するなど、高く評価され、2021年1月時点で年間経常収益1億4000万ドルを上げています。

# 機能
　使い始める前に、最初にやることはタイムゾーンの設定です。右上の自分のアイコンをクリックし、Settingsを選択します。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/40249881-37fd-bf12-bd26-501e42777311.png"/>
　タイムゾーンをGMT+09:00に設定します。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/5a8f0ad1-0e20-b727-e6e9-0e16fb037292.png"/>


　ザップ（自動処理）を作成するには、どのアプリとどのアプリをつなぐか選択します。やりたいことを選択したら、テンプレートが表示されます。ここでは、Googleカレンダーに予定が登録されたら、Notionのカレンダーにアイテムを追加するフローを作成してみます。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/e5af397c-542f-30ad-869f-2bca52ca60d7.png"/>

　これからすることが表示されます。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/e519dddd-bc6e-3806-55e9-5ee526258332.png"/>

　アプリの接続を行っていきます。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/c7becd68-0d78-cda5-b694-0fb5f7671d34.png"/>

　或いは、以下のように処理を個別に登録していきます。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/1561a5a7-0880-a6c8-2b39-4d2c8332c3d5.png"/>
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/aa3e0d75-ce60-b379-c0e7-e6896aaafd31.png"/>

　この2ステップで出来上がりです。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/894fbe25-922f-0c32-3595-62b9c80a2f62.png"/>

# Zapierで連携できるサービス
　Zapierは、非常に多くのサービスと連携が可能です。連携できるサービスの数は5,200を超えるようです。
　Microsoft、Google、Amazonで検索したものが以下のとおりです。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/d3dc6938-30c7-9507-1b4c-2e512811cb7b.png"/>
　また、データの移行も
　Zapierは、Webサービスを自動でつなぐサービスで、ただし、残念ながらZapierが対象としていないサービスに対して、個別に接続を作成することはできません。また、フローの中でデータを加工するようなアクションが見当たらなかったので、

# 感想
　無料版では、１トリガーで１アクションというフローしか作成できないため、単純な動作しか記述できません。それでも、接続できるサービスの多さと、記述が簡単ですので、利用者は増えると思います。
　Power Automateが使える環境があるなら、Zapierを使用するメリットはないと思いました。
【PHP】【str_replace】でforeach() 不要論

# コード
```PHP
<?php
$names = '渡部,品川,滝谷,川原田,田中';
echo str_replace(',',PHP_EOL,$names);
$names = ['渡部','品川','滝谷','川原田','田中',];
echo str_replace(',',PHP_EOL,join(',',$names));
$names = ['渡部', '品川', '滝谷', '川原田', '田中'];
echo implode(PHP_EOL, $names);
```

# 結果
```javascript
渡部
品川
滝谷
川原田
田中
```

# リスト化もできる
```PHP
<?php
echo '<li>'.str_replace(',','</li><li>','111,222,333').'</li>'; 
```


# 結果
```javascript
<li>111</li><li>222</li><li>333</li>
```
Serverless Frameworkでコンテナイメージを作ってLambdaを動かす

:::note info
これは [ZOZO Advent Calendar 2022](https://qiita.com/advent-calendar/2022/zozo) カレンダー1の10日目の記事です。
:::

# はじめに
AWS Lambdaでは各言語のランタイムが提供されており、ランタイムのサポートがある言語に関しては実行したいコードをアップロードするだけで簡単にクラウド上で実行することができます。
このようにクラウド上で実行できる一方、ランタイムのサポート期限が切れる前にバージョンアップ対応が必要であったり、コードをアップロードするためのコンパイルやライブラリのダウンロードといった事前処理に時間がかかってしまうケースもあります。

そこで今回はLambdaにアップロードするコードをコンテナイメージ化し、Lambda上で実行できるまでを紹介します。

# 前提条件
今回は以下の環境でLambdaへコンテナイメージをデプロイできるようにします。

- 言語: Python3.9
- パッケージ管理ツール: Poetry
- Serverless Framework v2.41.0
- コードはappディレクトリ以下に配置

# Serverless FrameworkでLambdaにコンテナイメージをデプロイする
まずはLambdaで実行するコンテナイメージを準備します。
```Dockerfile
# builder
FROM python:3.9-slim-buster as builder
RUN apt-get update \
    && apt-get install -y build-essential swig mecab libmecab-dev mecab-ipadic-utf8 \
    && pip install poetry Cython
WORKDIR /work/src
COPY pyproject.toml poetry.lock ./
RUN mkdir build  \
    && poetry export --without-hashes -f requirements.txt --output requirements.txt \
    && pip install -q -t build -r requirements.txt

# executor
FROM public.ecr.aws/lambda/python:3.9 as executor
COPY app ${LAMBDA_TASK_ROOT}/app
COPY --from=builder /work/src/build ${LAMBDA_TASK_ROOT}
```

マルチステージビルドでPoetryで管理されたパッケージをビルドしつつ、AWSが提供しているLambda実行用のコンテナイメージにapp以下のファイルとビルドしたパッケージ群をCOPYするシンプルなDockerfileとなります。

次にServerless Frameworkの設定ファイルでコンテナイメージのビルド設定を行います。

```yaml
provider:
  ecr:
    images:
      containerImageApp:
        path: ./
```

ビルド設定は`provider.ecr`以下に記述するだけです。この記述によってコンテナイメージのPush先となるECR作成からコンテナイメージのビルド・プッシュまで自動で行ってくれます。
`images`以下はコンテナイメージ名を任意で指定します。今回の場合は`containerImageApp`という名前にしています。 
`path`はDokcerImageの配置しているディレクトリを指定します。今回の場合はServerless Frameworkの設定ファイルと同階層にDockerfileを配置しているため、 `./`を指定しています。

次はLambda関数の設定になります。

```yaml
functions:
  LambdaContainerTest01Handler:
    # handler: app.handler.lambda_container_test_01_handler
    image:
      name: containerImageApp
      command:
        - app.handler.lambda_container_test_01_handler
    name: lambda-container-test-01-handler
    description: Lambdaコンテナイメージテスト関数1
  LambdaContainerTest02Handler:
    # handler: app.handler.lambda_container_test_02_handler
    image:
      name: containerImageApp
      command:
        - app.handler.lambda_container_test_02_handler
    name: lambda-container-test-02-handler
    description: Lambdaコンテナイメージテスト関数2
```

Lambda関数の設定は通常`handler`に起動したい関数名を指定しますが、コンテナイメージを起動する場合は`image`以下に起動したいコンテナイメージ名を指定します。
コンテナイメージのビルド設定では`containerImageApp`という名前をつけたため、その名前を記載します。
また、`command`で起動する関数名を指定することで、1つのコンテナイメージで複数のLambda関数を実行できるようにします。

# おわりに
以上、簡単ではありますがServerless Frameworkでコンテナイメージを作ってLambdaを動かすところまでやってみました。
Serverless Frameworkを使えば簡単にコンテナイメージをLambdaで動かすことが可能ですので、コンテナイメージをLambdaで動かそうと思っている方はぜひServerless Frameworkを使ってみてください！
[Swift] beginBackgroundTaskの使い方を間違えてスリープ時にアプリが落ちていた話

## 概要
実行している処理をバックグラウンドでも継続したい場合は
`UIApplication.shared.beginBackgroundTask`を使う方法があります。

公式より
https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtask

```swift
func beginBackgroundTask(expirationHandler handler: (() -> Void)? = nil) -> UIBackgroundTaskIdentifier
```
※ここの`handler`はバックグラウンドで実行できる時間が0になる直前で呼び出される。

## 発生した問題
自分が担当しているアプリではバックグラウンドに移行した時点でタスクを一時停止するため、`handler`で実行する処理は何もないと思い、下記のようにしていた。

```swift
taskIdentifier = UIApplication.shared.beginBackgroundTask()
```

ところが、アプリのアップデート後に「処理がいつまで経っても終わらない」、「スリープから復帰するとアプリが最初の画面に戻る」などの声が出るようになった。
色々調べてみるとどうやらこの`handler`でタスクを明示的に終了しないと**OS側からアプリが終了させられてしまう**らしい。
バックグラウンドで処理が継続できる時間は環境により変わるらしいが、自分が測ったら3分~5分ほど。

現象としては下記のようだった。
1. 長い処理があるのでユーザーが端末を放置
1. 端末が自動スリープしてバックグラウンドに移行
1. バックグラウンドで実行できる時間が0になり、OSからアプリが終了される
1. ユーザーがアプリを起動するとスプラッシュの画面に戻っている

記事のタイトルは"アプリが落ちていた"にしたが正確にはOSにより終了させられていた。
ログにも`your app is terminated.`みたいなものが出ていたと思います。たしか。

## 対応
対応としては下記のようにして明示的にタスクを終了させる。
```swift
taskIdentifier = UIApplication.shared.beginBackgroundTask {
    UIApplication.shared.endBackgroundTask(self.taskIdentifier)
}
```
それと、そもそもスリープに入ってしまうほど長い処理があるので処理中のみスリープさせないように対応した。
```swift
UIApplication.shared.isIdleTimerDisabled = true
```

## 最後に
そもそも`handler`の説明に書いてある。
>A handler to be called shortly before the app’s remaining background time reaches 0. Use this handler to clean up and mark the end of the background task. Failure to end the task explicitly will result in the termination of the app. The system calls the handler synchronously on the main thread, blocking the app’s suspension momentarily.

>アプリのバックグラウンドの残り時間が0になる直前に呼び出されるハンドラです。このハンドラを使用して、バックグラウンドタスクのクリーンアップと終了をマークします。タスクを明示的に終了させないと、アプリが終了してしまいます。システムはメインスレッド上で同期的にハンドラを呼び出し、アプリのサスペンドを一時的にブロックします。

ちゃんと説明書を読めってことですね。
DynamoDBテーブルに同じプライマリーキーでputした時の挙動

## やったこと
DynamoDBテーブルで、同じプライマリーキーでputした場合どうなるのでしょうか

## 結果
同じプライマリーキーでput操作をすると、既存itemが上書きされた。
同じキーを持つ項目がテーブルにすでに存在する場合は、新しい項目に置き換えられる。
[参考： 項目の読み込み](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ReadingData)

例） id = プライマリキー
`{id: 0, name: "apple", price: 150, register: makimatsu}`
↓
`{id: 0, name: "banana", price: 200}`

## 対策
- [条件付きの書き込み](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ConditionalUpdate)を使用する
    - 同じプライマリキーを持つ既存の項目がない場合にのみ、PutItem オペレーションが成功するようにする
    - 属性の 1 つに特定の値がある場合に UpdateItem オペレーションが項目を変更することを防ぐようにする
    - 条件付き書き込みは、複数のユーザーが同じ項目を変更しようとする場合に役立つ


- 使用例

```.py
putItem = {
    id: 1, ## プライマリキー
    name: 'orange',
    price: 100
}
try:
    ## id=1のitemが存在しない場合のみputする
    ## id=1のitemが存在する場合は、ConditionalCheckFailedExceptionエラーが発生する
    ret = table.put_item(
        Item=putItem,
        ConditionExpression=Attr('id').not_exists()
    )
except botocore.exceptions.ClientError as e:
    if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
        logger.error("一意制約違反のため、DynamoDbへの挿入失敗。すでに同様のキーのレコードが存在します。Item=%s", putItem)
```
DynamoDBのテーブル設計の基本的なこと

## これは
DynamoDBの基本的なことについて調べたことまとめ

## DynamoDBの基本情報

- フルマネージドな`NoSQL`データベースサービス
- **データはパーティションに分かれて保存される** [参考：パーティションとデータ分散](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html)
    - 複数のアベイラビリティーゾーン間で自動的にレプリケートされるので信頼性が高い
    - パーティションキーに基づき分散される
- 性能要件に応じて、テーブルごとにスループットキャパシティを定義する
    - キャパシティの`Auto Scaling`やオンデマンドキャパシティといった設定も可能
- ストレージの容量制限なし

## NoSQLの特徴
- RDMSのように柔軟なクエリはできない。
    - データは限られた数の方法で効率的にクエリできる
    - それ以外のクエリは高コストで低速になる
    - 重要なクエリだけ行えるように設計する必要がある
    - そのためには先にアクセスパターンを考える必要がある
- テーブルは少ない方が良い
    - アクセスパターンの管理コストが減るため


## DynamoDBテーブル用語

- Primary Key = Partition Key または Partition Key + Sort Key の組み合わせでテーブルごとで一意となるキー。**:warning:変更不可**
- Partition Key = アクセス頻度の高い項目のパーティションを分離するためのキー
- Sort Key = begins_with、between、>、< などの演算子による範囲のクエリを使用して検索するためのキー。範囲を絞り込むような項目を割り当てるのが用途に合っている。
- Secondary Index
    - LSI
        - Sort Key以外に絞り込み検索を行うキー
        - Partition Keyはベーステーブルと同じ / Sort Keyが異なる **:warning:変更不可**
    - GSI
        - Partition Key属性の代わりとなる、Partition Keyをまたいで検索を行うためのインデックス
    - LSIとGSIでは、検索対象のパーティションの範囲が異なる

## 効率的なテーブル設計をするためには
- 各パーティションにアクセスを分散させるようなPartition Keyを指定する
- インデックス数は最小限に抑えるようにする。
    - 頻繁にクエリを行わない属性では、セカンダリインデックスを作成しないようにする。ほとんど使用されていないインデックスは、ストレージおよびI/Oのコスト増大の一因
- アクセスパターンの洗い出しをしてから、Partition Key, Sort Key, Secondary Indexを決める

## 項目の読み込み(検索)について

DynamoDBのテーブル検索には「Query」オペレーションと「Scan」オペレーションの2つがある
### Scan
常にテーブルまたはセカンダリインデックス全体をスキャンする（フィルター条件の指定は可能） [参考：DynamoDB でのスキャンの使用](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Scan.html)
- なるべく避けたい
- Scanはすべての項目に対して検索を行うのでテーブルまたはインデックスが大きくなるにつれてプロビジョニングされたスループットを1回のオペレーションで使い果たす可能性が高い(ページサイズが増えるため)
- アイテム取得オペレーションでは最大1MB(1ページ)のデータを返すので、これを使い果たすのがコスト効率が良い。よってScanはコスト効率が良くない

### Query

プライマリキーの値に基づいて項目を検索する [参考：DynamoDB のクエリオペレーション](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Query.html)
- Queryを使用できるようにテーブル設計をするのが良い
- Partition Keyは必須で指定(完全一致のみ)。そのパーティションキー値を持つすべての項目を返す
- Sort Keyは追加の絞り込みのために任意で指定が可能（範囲で指定可能）

## Secondary Indexの使用
Secondary Indexはテーブルからの属性のサブセットと、Query オペレーションをサポートする代替キーで構成されるデータ構造のこと [参考：セカンダリインデックスを使用したデータアクセス性の向上](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/SecondaryIndexes.html)
テーブルには、複数のセカンダリインデックスを含めることができる

- 用途例1 ： 顧客管理テーブルに対して、顧客IDと商品ロット番号と購入日で検索したい場合
    - 顧客ID(Partition Key)と商品ロット番号(Sort Key)がPrimary Keyとなるテーブルを作成
    - 次に、顧客ID(Partition Key)と購入日(Sort Key)がPrimary Keyとなる、CustomerPurchaseDateIndexという名前のLSIを作成する

- 用途例2 ： ゲーム会社がリリースした各ゲームのユーザーとスコアを管理するテーブルに対して、ユーザーIDとゲームタイトルとスコアで検索したい場合
    - ユーザーID(Partition Key)とゲームタイトル(Sort Key)がPrimary Keyとなるテーブルを作成
    - 次に、ゲームタイトル(Partition Key)とスコア(Sort Key)がPrimary Keyとなる、GameTitleIndexという名前のGSIを作成する



## おわりに

料金面や詳細内容についてはまた調べたいと思います。
間違いあれば指摘いただけると助かります:pray:
圧倒的な異臭を放つDiscord Botを作ってみた ～Qusineを添えて～

この記事は[クソアプリ Advent Calender 9日目（シリーズ3）](https://qiita.com/advent-calendar/2022/kuso-app)の記事です。

圧倒的な異臭を放つDiscord Botを作ってみました！！！！！！！！！

![](https://i.imgur.com/zq01UAW.gif)

リポジトリ：[sevenc-nanashi/pooper](https://github.com/sevenc-nanashi/pooper)

https://github.com/sevenc-nanashi/pooper/blob/main/formatted.rb

## Qusineって？

> Qusine（クサイン）とは、[Quine](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AF%E3%82%A4%E3%83%B3_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0))のうち、特に異臭がするものを指す。
> 出典：なし

# 仕組み
## ライブラリ
今回は自作のDiscord APIラッパー、[discorb](https://github.com/discorb-lib/discorb)を使いました。
DBは[sqlite](https://www.sqlite.org/index.html)、操作には[sequel](https://sequel.jeremyevans.net/)を使っています。

## 置き換え部分
```ruby
client.on :message do |message|
  next if message.author.bot?
  next unless channels.include?(message.channel.id.to_s)
  next unless message.channel.is_a?(Discorb::TextChannel)
  channel = message.channel
  (
    channel.fetch_webhooks.wait.find { |w| w.name == "Pooper" } ||
      channel.create_webhook("Pooper").wait
  ).post(
    message.content.gsub(
      /[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]|<a?:[a-z0-9_]+:[0-9]+>/i,
      "💩"
    ),
    username: message.author.name,
    avatar_url: message.author.avatar.url,
    allowed_mentions:
      Discorb::AllowedMentions.new(
        everyone: false,
        roles: false,
        users: false
      )
  )
  message.delete
end
```
1. `Pooper`という名前のWebhookを取得（無かったら作成）、
2. `/[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]|<a?:[a-z0-9_]+:[0-9]+>/i`で置き換え（参考：[Rubyで文字列に絵文字が含まれるかどうかを判定する方法（ruby-jp slackで2021/11/08に行った質問回答まとめ）](https://qiita.com/shinsuke-kuroki/items/94c9fd8fb77432025902)）
3. 投稿者と同じ名前・アイコンで置き換え後の内容を投稿、
4. 元メッセージを削除

といった感じになっています。

## Qusine部分
```rb
client.slash(
  "qusine",
  "このBotのソースコードを表示します。"
) do |interaction|
  interaction.post(
    attachment:
      Discorb::Attachment.from_string(
        " " * 51 + "eval(\n" + " " * 49 + "(s='" + s +
          "').gsub(/\\s/,''))",
        "pooper.rb"
      )
  )
end
```

最初の`eval(`と`(s='`、最後の`').gsub(/\s/,''))`の部分以外はsを書いているだけです。単純。

# で、これ実用性あるの？

ない（断言）

けどこれを作ってるときにWebhookクラスのバグを見つけられたのでOKです。

# あとがき

圧倒的・異臭。Quineは2作目ですが、結構いい作品が作れたと思います。（1作目：[Bad Quine!](https://nico.ms/sm41396896)）
最後まで読んんで下さりありがとうございました。

### 宣伝

[discorb](https://github.com/discorb-lib/discorb)というRubyのDiscord APIラッパーを作っています。バグ報告やPRしてくださると嬉しいです。
JavaScriptのアニメーションライブラリGSAPについて

# GSAPとは

GSAPとはGreensockが提供しているJavaScriptのアニメーションライブラリです。
軽量かつ多機能でスクロールに応じたアニメーションや複雑なアニメーションが簡単に実装できます。

## GSAPの導入方法

GSAPの導入は簡単で主にCDN（scriptタグを貼り付ける方法）と、NPM（パッケージマネージャーを使う方法）を使う方法があります。

```
//CDNの場合
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.3/gsap.min.js

//NPMの場合
npm install gsap
```

## GSAPの基本的な使い方

第1引数にアニメーションをさせたいセレクターを指定します。
第2引数にはどのようにアニメーションさせたいかを指定します。

```
gsap.to('idやclass',{
  //どう動かすか
  x: 200, //x方向に移動
  repeat: 3, //3回繰り返す
  duration: 5 //アニメーションに5秒かける
});
```

## 主なプロパティ

```
x: x軸方向への移動量
y: y軸方向への移動量
duration: 時間
ease: 加減速
delay: 遅延秒数
repeat: リピート回数
repeatDelay リピートするまでの遅延時間
yoyo リピート時に反復挙動にするか
paused 一時停止した状態にするか
```

ざっくりした内容にはなりましたが、タイムラインやプラグインを使用することで、もっと複雑なアニメーションなどを簡単に実装にすることもできるので、他の指定方法や詳しく知りたい人は是非公式サイトを参考にしてみてください。

公式サイト

https://greensock.com/
WSL2環境のLaravel-sailビルド中に、gpgキーの生成で止まってしまう場合の対処

# 問題
Laravel Sailビルドしようとしたら
RUN apt-get update 中の

```
# gpg: keybox '/root/.gnupg/pubring.kbx' created
```
のあたりで止まってしまう。

# 結論
GPGコマンドのキーサーバーURLから、ポート指定 :80 を外す

## 手順
`php artisan sail:install` でDocker関連のファイルをプロジェクト内に出力したのち、
使用するPHPバージョンのDockerfileを修正する

例）PHP8.1の場合
```./docker/8.1/Dockerfile：21行目
    && echo "keyserver hkp://keyserver.ubuntu.com:80" >> ~/.gnupg/dirmngr.conf \

　　↓　↓　↓

    && echo "keyserver hkp://keyserver.ubuntu.com" >> ~/.gnupg/dirmngr.conf \
```

### 参考
https://stackoverflow.com/questions/74650090/laravel-sail-up-never-finishes-building-during-gpg-command
https://github.com/laravel/sail/issues/503#issuecomment-1336273951
ActiveRecord::RecordNotFound in UsersController#show エラーの解決方法！検証ツールを使えば...

# エラー内容
* `ActiveRecord::RecordNotFound in UsersController#show`というエラーが表示された。

![ActiveRecord RecordNotFound in UsersController show.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/23e82030-efca-3185-81f5-20af674e4738.png)

# 仮説
* idがshowアクションへ送られていないのでは？と思い、usersコントローラーの`@user`を`@user = User.find_by(id: params[:user_id])`にしてみたが、別のnilエラーになってしまう。問題なかった
* ルーティング間違いかと思い、以下も確認したが、問題なさそう
```ruby
  root to: 'prototypes#index'
  resources :prototypes, only: [:new, :create, :show, :edit, :update, :destroy] do
   resources :comments, only: :create
  end
  resources :users, only: :show
```

* ログイン中のユーザーは以下のように設定したことで、しっかりと遷移されている。ここも問題なさそう。

```ruby
  こんにちは、<%= link_to "#{current_user.name}さん", user_path(id: current_user.id), class: :greeting__link%>
```

# 解決手順
* まずはブラウザの検証ツール使ってみる。すると、abcさんを選択してもuser8となっていることがわかる。

* Viewである`_prototype.html.erb`をみてみると、以下がprototypeのidになっている
    * なので、本来userのidである1が呼び出されるべきなのにprototypeのidが呼び出されてしまっているのだ！

* 修正前：

```protospace-37383/app/views/prototypes/_prototype.html.erb
   <%= link_to "by  #{prototype.user.name}", user_path(prototype.id), class: :card__user %>
```

* 修正後：
```protospace-37383/app/views/prototypes/_prototype.html.erb
   <%= link_to "by  #{prototype.user.name}", user_path(prototype.user.id), class: :card__user %>
```
Azure terrafyで簡単import

## Azure terrafyとは？

既存Azureリソースをterraformで管理下に置くためのツールです。
terraform公式としてはterraform importがありますが、これは事前にリソースを定義した.tfファイルが必要、また、tfstateを作成するだけでterrafyは構成ファイル(.tf)も作成します。
更に、terrafyではResource Group単位でのimportが可能です。(terraform importではRG単位ではできず、リソース1つ1つを指定する必要があります。)

参考：
https://qiita.com/koudaiii/items/10ae59a817abc438b71c
https://azure.github.io/aztfy/#1

## Hands-on
Azureへの認証済、Azure CLI、aztfyのインストールは実施済とします。
How to install :  https://github.com/Azure/aztfy#install

Terraform構成ファイルが存在していないディレクトリ上でaztfyを実行します。
ほしいリソースに対してEnterを押します。
また、resource名を変更することも可能です。
```
~/aztfy_test ❯ ls -a
.  ..
~/aztfy_test ❯ aztfy resource-group aztfy-resources

   Azure Terrafy

     aztfy-resources

    9 items
 
  |skip…
```


wでimportを開始します
```
   Azure Terrafy

  ⣷   Importing /subscriptions/xxxx-xxxx/resourceGroups/aztfy-resources...
  ...
  ...
  ...
  ...
  …
```

出力を確認してみます。
```
~/aztfy_test ❯ ls -a
.  ..  .terraform  .terraform.lock.hcl  aztfyResourceMapping.json  main.aztfy.tf  provider.tf  terraform.tfstate
```

このように.tfファイルとtfstateが非常に簡単に作成できます！

## terrafyの使いどころ
https://azure.github.io/aztfy/#11 に以下のように書いてあるように、terrafyはconfigurationファイルをfor_each等を使用したり、moduleを使った書き方は目指していないようでした。シンプルなconfigurationファイルでも良いので作成したい、という場合に使うのがよさそうです。

`
Configuration files do not need to be elegent
Modules do not need to be generated
`

module化した.tfファイルが存在しており、.tfファイルのリソース定義にしたがってtfstateを作成して欲しい、という時には工夫が必要かと思います。

terrafy後、moveブロックを使えばよいかもしれないです。
https://developer.hashicorp.com/terraform/language/modules/develop/refactoring#refactoring

もしくは手間になりますがterraform importを行う方法もあります。

```
~/aztfy_test ❯ terraform import module.test.azurerm_function_app.func /subscriptions/xxxx-xxxx/resourceGroups/xxxxx……
```

## その他のツール
terrafy以外のimport用ツールも調べてみました。

・terraform add
v1.1の最終リリースで削除されたようです。
リソース間の依存関係を考慮せず1リソースだけ対象にするみたいです。

・Terrafy (Azure terrafyではなくterrafy)
構成ファイル、tfstate両方を作成してくれるが、.tfyというリソースの定義ファイルのようなものを事前に作成する必要があります。

・Terraformer
import後に構成ファイルの微調整が必要になる可能性があるようです。

※Azure terrafyは有効な構成ファイルを作成すること(作成された構成ファイルからのterraform planでの差異が無い、等)としているため、この点は違いになるかと思われます。

`Automatically generate the valid Terraform configuration file:`
CrystalのメソッドをC++から呼び出す

# はじめに

CrystalではC言語やC++のライブラリを簡単に呼び出すことができます。

https://ja.crystal-lang.org/reference/syntax_and_semantics/c_bindings/index.html

では、**逆にC++からCrystalのメソッドを呼び出すこと**はできるでしょうか。

パフォーマンスやライブラリの充実度からあえてC++からCrystal使う理由はないでしょ・・・という声が聞こえてきそうですが、C++は使っていて辛いこともあるので、気持ちよくかけるCrystalを使いたくなるのです。

そうするとはじめからCrystalを使えばいいでしょ・・・となりそうですが、いろんな経緯でプロジェクトがメインでC++でかくことになってしまった、もしくは、使いたいフレームワークの関係上C++をメインで書くしかない、そういうケースに置いてC++で書きつつ部分的にCrystalで書いて連携したいという場合があるかもしれません。

C++からCrystalを呼び出す例はあまりみなかったので、方法をまとめてみました。

C++とCrystal連携なんてしないよ・・という方も多そうですが、**Ruby拡張をCrystalで書くときもこの内容が理解を助ける**と思います。これは次の記事で解説します。

この記事はMacで検証しました。特にビルドまわりは環境にあわせて、適宜修正をしてください。

検証環境

macOS 12.6
Crystal 1.6
clang 14.0.0

# プログラム

## Crystal

```crlib.cr
fun crystal_init : Void
  # GC初期化が必要
  GC.init

  # Crystalの「main」関数を呼び出す必要があります。
  # トップレベルのコードを実行する関数です
  # 引数はargcとargv．今回はないので0とnullを渡す
  LibCrystalMain.__crystal_main(0, Pointer(Pointer(UInt8)).null)
end

# cから呼び出す関数の定義
fun sum(a : Int32, b : Int32): Int32
  puts "Calculate sum"
  a + b
end

```

Crystalのメソッドで外部で使う場合は、Crystalの初期化を事前にする必要があります。そのためにcrystal_initとして定義しておきます。この中で必ず行うことはGC初期化`GC.init`とCrystalのトップレベルのコードを実行する`__crystal_main`を呼び出すことです。

**この２つの初期化が必須**であり、他にも初期化が必要なことはこの関数に記入しておきます。

次はC++から呼び出す関数ですが、今回は合計を計算するsum関数を定義してみました。

## C++

```cppmain.cpp
#include <iostream>
extern "C" {
    // Crystalで定義された関数を書いておく
    void crystal_init(void);
    int sum(int x, int y);
}
int main(int argc, char *argv[])
{
   // Crystalの初期化
   crystal_init();
   // crystalで定義された関数を呼ぶ
   int ans = sum(1, 5);
   // 表示
   std::cout << ans;
   return 0;
}
```

`extern`でCrystal側で定義した２つのC関数`crystal_init`と`sum`のextern宣言をします。`main`関数では、初期化用の`crystal_init()`を先に呼び出してください。その後は`sum`関数を使えます。戻り値を受け取って表示しています。

## Makefile

今回は.soのような動的リンクを使う場合でやってみます。

`Makefile`として以下のよう内容を記述します。

```makefile
CXX = g++
# CXX = clang
CXXFLAGS = -lstdc++  
TARGET = cppmain
OBJS = cppmain.o
CRLIBS = libcr.so

cppmain:  $(OBJS) $(CRLIBS) 
	$(CXX) $(CXXFLAGS) $(OBJS) -L. -lcr -o cppmain 
%.o : %.cpp
	$(CXX) $(CXXFLAGS) -c $<

libcr.so: crlib.cr 
	crystal build $(CRFLAGS) crlib.cr --single-module --link-flags="-shared" -o libcr.so
clean:
	rm -f *.o $(TARGET) $(CRLIBS)

```

## ビルド

以上の`crlib.cr`, `cppmain.cpp`, `Makefile`の３つのファイルが揃ったら
```
make
```
コマンドでビルドします。

crlib.soができますので、それをcppmain.cppにリンクしています。動的リンクですので実行時にcrlib.soが必要です。

CXXはC++コンパイラの設定です。使用するコンパイルに応じて適宜修正してください。ちなみにMacの場合はgccとかg++とかいてもclangとなります。別途gcc11をインストールしてチェックしましたがこちらでも動作確認しました。

# 実行結果

無事CrystalのメソッドをC++から呼び出せました。戻り値も取得できています。

```
Calculate sum
6
```

# おわりに

Static Linkの場合はまた別途執筆したいと思います。

次の記事ではRubyとCrystalの連携を解説します。

# 参考
https://stackoverflow.com/questions/32916684/can-a-crystal-library-be-statically-linked-to-from-c
Laravel SailでLibXLを使う

# 開発環境
```
macOS  13.0.1
PHP  7.4.33
Laravel  8.83.16
laravel/sail  1.14.10
```

# 環境構築

公式ドキュメント

https://www.libxl.com/php.html

### 1. 以下二つをダウンロード

https://github.com/iliaal/php_excel/archive/php7.zip

https://www.libxl.com/download/libxl-lin-4.1.0.tar.gz

ブラウザからでもCLIでもお好きなやり方で。

```shell
$ cd docker/7.4
$ wget https://github.com/iliaal/php_excel/archive/php7.zip https://www.libxl.com/download/libxl-lin-4.1.0.tar.gz
```

:::note warn
以下のリンクを確認して最新ものがあればそれをダウンロードする。
その場合、ファイル名などは適宜読み替える。
https://github.com/iliaal/php_excel/releases
https://www.libxl.com/download.html
:::

ダウンロードが完了したら`docker/7.4`に配置する。

### 2. Dockerfile 編集
以下を追記。

```Dockerfile:docker/7.4/Dockerfile
COPY ./libxl-lin-4.1.0.tar.gz /usr/local/src/libxl-lin-4.1.0.tar.gz
COPY ./php_excel-php7.zip /usr/local/src/php_excel-php7.zip

RUN cd /usr/local/src/ && \
    tar xf libxl-lin-4.1.0.tar.gz && \
    unzip php_excel-php7.zip

RUN cd /usr/local/src/ && \
    cd php_excel-php7 && \
    phpize && \
    ./configure --with-excel=/usr/local/src/libxl-4.1.0/ --with-libxl-incdir=/usr/local/src/libxl-4.1.0/include_c/ --with-libxl-libdir=/usr/local/src/libxl-4.1.0/lib64/ && \
    make && \
    make install
```

### 3. php.ini 編集
以下を追記。
```ini:docker/7.4/php.ini
[LibXL]
extension=excel.so
```



### 4. dockerをビルドし直す && 再起動

```
vendor/bin/sail build --no-cache
vendor/bin/sail up -d
```

### 5. 動作確認
```php:routes/web.php
Route::get('excel', function () {
    // バッファをクリアする
    ob_end_clean();
    ob_start();

    $book = new \ExcelBook(null, null, true);

    // 日本語対応
    $book->setLocale('UTF-8');

    $sheet = $book->addSheet("Sheet1");

    // A2にテキストを追加
    $sheet->write(1, 0, 'PHP_EXCELのテストです。');

    header('Content-Type: application/octet-stream');
    header('Content-Disposition:attachment;filename="test.xlsx"');

    $book->save('php://output');
});

```

http://localhost/excel
にアクセスするとExcelファイルがブラウザでダウンロードされる。
バス係数とは？

# はじめに
この記事は[ZOZOアドベントカレンダー2022#5](https://qiita.com/advent-calendar/2022/zozo) 11日目の記事になります。

# バス係数とは

バス係数という言葉をご存知でしょうか？(パスではなく、車の「バス」ですね)

自分が知ったのは[「Googleのソフトウェアエンジニアリング」](https://www.amazon.co.jp/dp/4873119650)という書籍からでした。

書籍では情報の単一障害点という観点で紹介されており、端的に言えば「何人バスに轢かれたら破綻するか？」ということになります。

同じ意味で「トラックナンバー」、「ハネムーンナンバー」とも言うらしいとSNSで教えていただきました。ハネムーンナンバーは轢かれるという
表現があまりよろしくないので、何人ハネムーンにでかけたら破綻するか？ということですね。

当然ながらバス係数１の場合は１人しかその情報を知らない。２なら２人、３なら３人と、多ければ多いほど安全ということです。


# 危険なバス係数になる行為

そこで、危険なバス係数になるような行為を考えてみました。

- 一人で全部やる。共有がない。
- レビューしない、またはレビューが適当
- ドキュメントがない、またはドキュメントが不明瞭、ドキュメントの場所がわからない。
- 共有されても馴染みがないドメイン
- 共有されても難しい作業、運用（失敗できないなど）
などなど

作業する側も、共有される側も背景含め理解してもらう、理解する。さらに作業の内容をトレースできるぐらい共有できないとなかなか難しいことではあります。

また、作業が終わったあとに共有となると、さらに難しいのが、元の作業者が運用に追われる、他の仕事に追われる、ドキュメント書く余裕もないし、そのドキュメントに価値があるのかも不明となれば、なかなかバス係数は増えていかない危険な状況と言えます。

# バス係数を増やすには？

バス係数を増やすにはどうすればいいか？

- 一人で作業しない、複数人でやる
- 作業完了後のメンテなども一人でやらない
- 作業後になってしまったら、ドメインの共有含め、バス係数が増えるまで頑張る

つまり、一人ではなくチームで作業するということだと思います。

# アジャイルの観点から

これをアジャイルの観点で当てはめると、そもそも一人で作業するという観点がありません。単位はチームからになります。
細かい作業を個人が担当するかどうかなどはチームが決めることになりますが、きちんと物事に優先順を決め、優先度が高い順にチームとして取り組むことになります。

ペアプログラミングやモブプログラミングで取り組むことが多いのではないでしょうか。
個人が対応してもお互いに関心を持つ仕組みがあり、早い段階で共有され、相談され対応していくことになります。

個人ごとの「グループ」ではなく、「チーム」であることが大事になってきます。

あなたのチームは本当に「チーム」ですか？
バス係数１で悲惨な状況を作らないためにも振り返ってみてください。
AZ-104を完全独学で合格しました。

<h1>はじめに

本記事はAZ-104: Microsoft Azure Administratorの合格体験記です。

<h1>前提条件

Azureの何らかのプロジェクトに関わってきた方。
インフラ全般の知識がある方。（エンジニア同士の会話についていけるぐらい）←抽象的ですみません。

<h1>本記事の対象の方（本記事が刺さるかもしれない方）

・受験費以外の出費を抑えたい方。
・勉強そのものにあまり自信が無い方。

<h1>所要日数

2週間ほど

平日：5～6時間　
休日：0～0.5時間
実質はほぼ10営業日ですかね・・

私は土日はお酒で現実逃避をしたいため、平日は是が非でも上記時間を確保しました。
土日もしっかり勉強ができる方であれば、平日の勉強時間は少なくても良いかもしれません。

<h1>試験結果

840 / 1000点で合格しました。
合格ラインは700 / 1000点です。

一発合格です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2991032/78f6a100-92c7-7b11-0058-baa54fe51c2a.png)


<h1>筆者について

VMware Horizon、Windows Virtual Desktop、Citrix CloudなどのVDI製品、そのほかAzureのIaaS周りの設計構築を経験してきたインフラエンジニアです。

前職ではなぜかクラウドから隔絶され、1年半ほどブランクがある状況です。。
上述の通り、1年半Azureはおろかクラウドそのものから断絶されてしまったので、学び直しという意味合いで受験をすることに・・


:::note info
因みに高校時代の成績は**常に学年ワースト10位以内**でした。
~~前提条件などと書いていますが、そんなものはあって無いようなものなのかもしれない。~~
:::

<h1>主な勉強方法

主に以下の2つの教材を使って勉強しました。それぞれ解説したいと思います。

<h2>Microsoft Learn

https://learn.microsoft.com/ja-jp/certifications/exams/az-104?tab=tab-learning-paths

まずはこちら。
Microsoft社公式の学習コンテンツです。上記リンクのラーニングパスをひたすら読み込みました。
それも**音読**で。

音読には目で情報を読み取るインプットの作業と、声に出して文章を読むアウトプットが加わります。
自身、黙読の何倍も知識の定着を実感しているので、是非試してみていただければなと思います。

また、各章には知識チェックという問題があります。ここではただ回答を覚えるだけでなく、なぜそのような回答になるのか？を回答と解説をセットで覚えることを意識してました。
問題の解説を見て、理解が足りていないと感じれば、対象のページに戻って、また音読というサイクルを繰り返してました。

:::note warn
Microsoft Learnの問題数は少ないです。
問題演習は各章の最後（または途中）の知識チェックだけです。
なので、テキストを読み込んで、その中で自分で問題を作って、それを回答する。みたいなこともやってました。

例えばサイト間VPNの構築手順を順番に言え！など。
:::

<h2>参考書の問題集を解く


どうしても紙ベースでも勉強したかったのと、問題集を解かないと合格は見込めないので、以下の書籍を購入しました。
書籍名：[合格対策Microsoft認定試験AZ-104:Microsoft Azure Administratorテキスト&演習問題](https://www.amazon.co.jp/%E5%90%88%E6%A0%BC%E5%AF%BE%E7%AD%96Microsoft%E8%AA%8D%E5%AE%9A%E8%A9%A6%E9%A8%93AZ-104-Microsoft-Azure-Administrator%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88-%E6%BC%94%E7%BF%92%E5%95%8F%E9%A1%8C/dp/4865943080/ref=asc_df_4865943080/?tag=jpgo-22&linkCode=df0&hvadid=529491568476&hvpos=&hvnetw=g&hvrand=2517838909808143958&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009515&hvtargid=pla-1418387143650&psc=1&th=1&psc=1)

※Amazonアソシエイトは使ってません。

■上記書籍の目次
>第1章：Microsoft Azure認定試験及び認定資格の概要
第2章：Azureアイデンティティおよびガバナンスの管理
第3章：ストレージの実装と管理
第4章：Azureのコンピューティング リソースのデプロイと管理
第5章：仮想ネットワークの構成と管理
第6章：Azureリソースの監視とバックアップ
第7章：模擬試験


■勉強方法
まずはテキストをサラッと1週読み、各章の章末問題、模擬試験をひたすら解いてました。
全問正解できるまで・・・

問題数が少ないとレビューがありましたが、模擬試験以外にも各章に章末問題が用意されているので、それなりに問題数はあるかなと思いました。※完全に個人の感想です。

:::note warn
ここで言う全問正解とは、問題に対する回答と、なぜその回答が成り立つのか？までを正確に回答できることを指します。
:::

あとはMicrosoft Learnと同様にテキスト読み込んで、勝手に自分で問題作って、それを回答してました。

<h1>まとめ

まず、音読の効果すさまじいですね・・・笑
自分でもビックリするぐらいの定着率でした。
是非やってみてください。無料なので。

音読以外にもテキスト読み込んで、そこから自分で問題作って、それを回答する営みもかなり効果的だったのかなと。
本質的な理解がこれにより深まったので、初見の問題や、ケーススタディにも問題なく対応できました。

<h1>今後について


[Azure Solutions Architect Expert](https://learn.microsoft.com/ja-jp/certifications/azure-solutions-architect/)の取得が出来たらなと思っています。

Azure Virtual Desktopの案件獲得にも力を入れる様な感じがする（気配だけ）ので[Azure Virtual Desktop Specialty](https://learn.microsoft.com/ja-jp/certifications/azure-virtual-desktop-specialty/#certification-exams)（AZ-140）の取得をしようか、迷っていますが、何らかの資格は取得したいと思います。

最後まで読んでいただきありがとうございました。
AirTest Pocoを使用してみての所感と自動化8原則の大切さ

## はじめに
皆さま、こんにちは。AIQVE ONE Advent Calendarも残り6日となりました。 
19日目を担当します、@keysh_と申します。 
私はゲームのテスト自動化をお仕事としているのですが、今年はその中でも 
**AirTest Poco（以下、Poco）** を使って、ゲームを自動化する機会があり、 
自身でがっつりと触れられたのが初めてだったので、
その時の所感と、改めて自動化8原則は大事だなと思ったところをまとめていこうかと思います。 

## AirTest Pocoとは
AirTestとは、NetEase社が開発している画像認識ベースによるアプリケーションの自動化用テストフレームワークのことで、PocoはそのAirTestに同梱されている、アプリ内のメタデータを基に自動テストを実装できるものです。 
Poco SDKをアプリ内部に仕込むことで、Unityであれば、ヒエラルキー情報やTextといった指定のUIコンポーネントのオブジェクトといったメタデータを参照することが可能です。 
Unityの他にも、Unreal Engineや Cocos-2dxなど様々なプラットホームでテストを実装することができます。 

## Pocoを使ってみての所感
最初は結構混乱しました…
普段画像認識ベースの方でしか組まないのもあって、
「どっちがどっちのメソッドだっけ？ 」
「これで動く？」
「あれ？動かない…」
みたいな場面が結構ありました(笑) 

これは極端な例ですが、画面の指定した部分をタップさせるといった処理をさせる場合、
画像認識ベースでは
```
touch(画像または座標) 
```
となりますが、Pocoの場合は
```
poco.click([座標])
または
poco(指定したオブジェクト).click()
```
といった書き方になります。 
普段の感じでスクリプトを書いていざ実行っていうときに「あ…そりゃそうだわ」ってなりました。（構文エラーにはならないので…） 
実際に組んだスクリプトを動かした時は、画像認識ベースだと処理に時間がかかる部分が、その半分の時間で済んだり、アニメーションなどでコケやすい部分を指定しても難なくパスしたので、ちょっと感動しました。 

## ゲーム自動化のネックなところ
さて、話は少々飛びますが、**ゲームの自動化は非ゲームのアプリケーションよりも難しい**といわれています。 
特にネックと思われるのが、以下3点です。 

・スマホゲームは施策ごとに部材が変わるため、その都度メンテナンスコストが発生する 
・画像によってはアニメーションやエフェクトが入り、ツールが誤認識してしまい、 スクリプトがコケてしまう可能性が高い 
・保守作業が頻繁に発生するので、スクリプト自体がスパゲティコードになりやすい 


自動化8原則の5つ目に「自動テストシステムの開発は継続的におこなうものである」とあり、確かに変更が加えられた際にその変更に合わせてスクリプトを改修していく必要があります。1度スクリプトを作ったらそれで終わりではありません。 
しかしゲームを対象とした場合、その変更がかなり短いスパンで高頻度でやってきます。 

皆さまも普段遊んでいるスマホゲームを思い浮かべてみてください。 
毎月あるいは毎週のように開催されるイベントやガチャといった施策…これらのTOPページやボタン、報酬、TIPS（ヘルプ画像のこと）などが毎回変わっているかと思います。 

画像認識ベースで自動テストを実装した場合、基本はほぼ変わらない部材をテンプレートにするものの、
その施策に合わせて変更しないといけない部分が出てくるため、**アプリ更新の度にスクリプトのメンテナンス**をし続けなければなりません。 
場合によっては、その施策のテストにメンテナンスが間に合わない…
なんてことになる可能性もあります。 

それならAppium使ってオブジェクト指定すればいいじゃないか！
と思われる方もいらっしゃるかと思いますが、ゲームアプリは画像自体がボタンになっている、オブジェクトなのでは？と思った部分がオブジェクト要素として取れず、指定できないものなどが多々あり、
画像認識ベース以外で自動化を組むのがとても難しいといった事情があります。 

## Pocoを使用するゲーム自動化のメリット
そんなネックな部分を解消してくれるのが、Pocoの大きなメリットと言えます。 
Appiumでは難しかったゲームアプリ内のメタデータ、つまりオブジェクトを指定して自動テストを実装することが可能なので、ボタンなどの画像が変わっても、その画像が配置されるオブジェクト名が変わらなければ、改修する必要もないため、**メンテナンスコストを最小限**に抑えられます。 

また、画像認識しにくいアニメーションやエフェクトが入っているものでも、オブジェクト自体をみるため、処理がコケにくくなります。 
そして画像を認識させるよりも処理が早いです。（個人の感覚です） 

その他、PocoにはRPC（ Remote Procedure Callの略。「遠隔手続き呼び出し」とも呼ばれる）を介して、アプリ側からUnity側の特定の関数を呼び出し、テスト実行前の環境設定や特定のテスト値をロジックに流し込むなどを行わせることが可能です。 

## Poco導入のハードル
ここまでゲーム自動化におけるメリットを書き連ねてきましたが、
実のところ、現場で導入しているケースをあまり見かけません。 
Pocoだけではなく、ゲームのテスト自動化全体に言えることではありますが、導入するには越えなければならない高いハードルが存在します。 

先述した通り、Pocoを動かすためにはSDKをテスト対象のアプリに仕込まないといけません。 
つまり、**開発側がSDKをアプリ内に導入してくれることが大前提**となります。 
完全に外部操作となる画像認識ベースとは違い、アプリの内部に入れ込むため、 何かエラーが起きた時にそのSDKを導入したことで起きたものなのかそうでないのかの切り分け作業が追加で発生してしまいます。 
そのため、導入したいと思っていてもかなり難色を示されることが多かったりします。

 
このハードルを下げるには、「自動化8原則の6つ目「自動化検討はプロジェクト初期から」というように、 
 **プロジェクト初期段階から自動テストを導入することを前提としたうえで、PocoのようなSDKをアプリ内に仕込むことに対して予め合意を取っておく**と、スムーズに導入ができるかと思われます。 
また、手動テストとバイナリを分けることで他テストへの影響を出ないようにすることも大切です。（バイナリ作成の手間はかかるかもしれませんが…） 

## まとめ
Pocoはゲームを自動化する上で導入のハードルは少々高めですが、メンテナンスコストの低いテスト実装が行え、ゲーム自動化のネックとなる点をクリアできるので、
これからゲームを作り、テストをしていくプロジェクトには個人的にはおすすめです。 

先述した導入のハードルを少しでも低くするために、導入は初期段階から検討し、よりテスト自動化を行いやすい環境を整えていくことで、テスト作業自体の効率化が図れるかと思われます。 

 

スマホゲームのテストを効率化したいと思っている方は、こういったものもあるんだなと少しでも参考になれば幸いです。