OmniAICreator/Qiita-1.07M huggingface dataset:
2枚の画像からの深度推定

# -1. はじめに
この資料は現状、参考資料1の二番煎じとなっているので詳しく知りたい方は1をご覧になることをおすすめいたします。余裕ができたらわかりやすさの追求を行っていきます。

# 0. 2枚の画像からの深度推定
ステレオカメラでは、2枚の画像の差から深度推定を行うことができます。今回はそのやり方を詳細に述べます。

# 1. 深度推定概要

![Screenshot from 2021-05-24 17-17-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323150/e979e9cf-6ea0-9018-f064-bf8fe85dee26.png)


### 光軸を平行に
光軸(レンズ、球面鏡の中心を結んだもの)が並行でない場合、平行になるように変換する必要があります。ステレオカメラでは左のカメラに光軸を合わせるのが一般的らしいです。
<font color=gray>多分ですが、以下式でなくとも座標系を共通にできれば良いので、計算一回増えますが、両方を世界座標系にしてもできるかも？</font>

```
変換前座標系: (X, Y, Z)
変換後座標系: (X', Y', Z')
X' = T, Y' = X' * Z, Z' = X' * Y'
```

### 深度推定公式

```math
Z = f\frac{T}{d} \\
\\
x_l, x_r: 点の画像上での座標 \\
f: 焦点距離 \\
T: 1枚目と2枚目の世界座標での距離( O_r - O_l = T ) \\
```

# ２．対応点の探索のためのエピポーラ幾何
2枚の画像から対応点を見つけるためには対応点を探索する必要があります。その際、画像上を愚直に探索してはとても時間がかかってしまいます。そこでエピポーラ幾何なるものをを利用します。

**エピポーラ幾何**とは、2つのカメラで同じ三次元空間を撮影した際に生じる幾何のことです。

![Screenshot from 2021-05-26 11-57-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323150/8cc9699a-0984-3226-8c9c-9ec02effa34b.png)

ここから、エピポーラ幾何の理解のために必要な単語を紹介します。

#### エピポーラ平面
カメラ $ O_1 $, カメラ $ O_2 $ 点 $ X $ の3点を通る平面

#### エピポーラ線
エピポーラ平面と画像平面が交わる直線で、上記画像の赤線($l_1$, $l_2$)です。

#### エピポール
2枚の画像の全ての対応点のエピポーラ線は必ず1点で交わります。それがエピポール($e_1$$e_2$)となります。これはもう一方の画像のカメラ中心を自身の画像に投影したものとなります。

#### 基礎行列(**F**undamental Matrix)
3*3行列で、**カメラの内部・外部情報**を含んでいます。この行列があると以下のことができます。
* 別の画像上の点を別の画像上のエピポーラ線にマッピングできる
* カメラ運動の推定

#### 基本行列(**E**ssential Matrix)
3*3行列で、**カメラ外部情報**を含んでいます。
カメラ内部行列Kと基礎行列Fを用いて、$ E=K_2FK_1 $ と変換できます。

### 2.1. 基礎行列Fの導出
 $ x_1 $ と $ x_2 $ が対応点である場合、全ての対応点 $ x_1 $, $ x_2 $で以下の関係が成り立ちます。

```math
x^T_2Fx_1 = 0 \\
F: 基礎行列 \\
x_1, x_2: 画像座標系での対応点の座標を同次座標系で表したベクトル
```
この式は、2つの画像の対応点に拘束関係があることを示します。これを**エピポーラ拘束式**といいます。これは画像1の点が画像2上のどこに写っているかを決定(=拘束)できます。

![Screenshot from 2021-05-26 11-57-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323150/8cc9699a-0984-3226-8c9c-9ec02effa34b.png)

この図に例えると 左画像の$X_1$と基礎行列Fから右画像の $ l_2 $が求められるといった感じです。

F値の導出には次のようなアルゴリズムがあります。
* 8点アルゴリズム
* 代数的なコスト最小化
* 観測点と推定点との距離の誤差の最小化(Good Standard Algorithm)
これらはどれも最小二乗法などの繰り返し計算を用いています。

# まとめ
1. 深度推定は2枚の画像の対応点と焦点距離(内部パラメータ)、そして姿勢の移動量があれば求めることができる
2. 対応点の探索のためにエピポーラ幾何を導入すると対応点が存在する線であるエピポーラ線が求められ、計算が早くできる

# 参考資料
1. 視覚の幾何学 http://web.wakayama-u.ac.jp/~wuhy/CV10.pdf
2. 【コンピュータビジョン】ネコと学ぶエピポーラ幾何
   https://qiita.com/ykoga/items/14300e8cdf5aa7bd8d31
Unity 2Dで敵の方向を向く

##1.0 目的
Unity 2Dでゲームを作る際に、オブジェクトを敵など特定のオブジェクトの方向を向かせるための方法です。（下の画像のような）
いろいろな方法があると思いますが、1番シンプルだと思える方法を忘備録として記事します。
今回はスクリプトで実装していきます。








##2.0 Unity 3Dでは
3DではLookAt関数があり、この関数を使うと、簡単に指定したオブジェクトへ方向を変えることができます。






##3.0 Quaternionクラス
Unityでは回転を取り扱うためのQuaternion（クォータニオン）というクラスが準備されています。





##4.0 FromToRotation関数
QuaternionクラスにFromToRotation関数があります。引数はVector3の fromDirection(向いている方向から) と toDirection（向きたい方向）です。fromDirection から toDirection への回転を作成できます。
通常、ワールド空間で Transform を回転させ、座標の 1 つ、例えば、y 座標をターゲットの方向 toDirection に向かせるために使用します。





##5.0 C#スクリプト
ではスクリプトです。

```C#:RotationTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotationTest : MonoBehaviour
{
    public Transform arrowTrans // 動かすオブジェクトのトランスフォーム
    public Transform ballTrans // ターゲットのオブジェクトのトランスフォーム

    // 向きたい方向を計算
    Vector3 dir = (ballTrans.position - arrowTrans.position);

    // ここで向きたい方向に回転させてます
    arrowTrans .rotation = Quaternion.FromToRotation(Vector3.up, dir)
}
```








Vector3.upはVector3(x, y, z) = (0, 1, 0)と同じです。シンプルにオブジェクトの上方向を表しています。
これで目的の方向に向くようになります。
注）下図のようにリアルタイムに方向を変えるためにはUpdate関数などで連続処理が必要になります。




![GIF 2021-05-26 13-46-42.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352037/46b94b90-e987-b225-96a7-3bbc532227aa.gif)
Java PowerPointでテキストを取得

<p><strong>今回はSpire.Presentation for Javaという無料のライブラリを使用して、PowerPointでテキストを取得する方法を紹介します。</strong></p>
<h4><strong> 下準備</strong></h4>
<p><strong>1.E-iceblueの公式サイトから<a href="https://www.e-iceblue.com/Download/presentation-for-java.html">Free Spire. Presentation for Java</a>無料版をダウンロードしてください。</strong></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210526/20210526155656.png" alt="f:id:lendoris:20210526155656p:plain" title="" class="hatena-fotolife" itemprop="image" /><br /><strong>2.IDEを起動して新規プロジェクトを作成してから、インストールされたファイルにあった相応しいSpire. Presentation.jarを参照に追加してください。</strong></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210526/20210526155715.png" alt="f:id:lendoris:20210526155715p:plain" title="" class="hatena-fotolife" itemprop="image" /></p>
<h4><strong>元のファイル</strong></h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210526/20210526155741.png" alt="f:id:lendoris:20210526155741p:plain" title="" class="hatena-fotolife" itemprop="image" /></p>
```JAVA
import com.spire.presentation.*;

import java.io.FileWriter;

public class ExtractText {
    public static void main(String[] args) throws Exception {

        //Presentation　objectを作成します。
        Presentation ppt = new Presentation();
        //PowerPointファイルをロードしｍなす。
        ppt.loadFromFile("Input.pptx");

        StringBuilder buffer = new StringBuilder();

        //スライドをループして、テキストを取得します。
        for (Object slide : ppt.getSlides()) {
            for (Object shape : ((ISlide) slide).getShapes()) {
                if (shape instanceof IAutoShape) {
                    for (Object tp : ((IAutoShape) shape).getTextFrame().getParagraphs()) {
                        buffer.append(((ParagraphEx) tp).getText());
                    }
                }
            }
        }
        //保存します。
        FileWriter writer = new FileWriter("ExtractText.txt");
        writer.write(buffer.toString());
        writer.flush();
        writer.close();
    }
}
```
<h4><strong>実行結果</strong></h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210526/20210526155830.png" alt="f:id:lendoris:20210526155830p:plain" title="" class="hatena-fotolife" itemprop="image" /></p>
<p> </p>
AWS SES（sandbox) をfreenomで取得したドメインで作成


2021/11/25追記

前提
--
テストでAWS SESを使ってみたい。

教材
--

こちらのサイト

https://note.com/dafujii/n/n12bb564081f1

https://note.com/dafujii/n/n0365dc0a89af

https://dev.classmethod.jp/articles/amazon-ses-build-and-practice/

用意するもの
--
freenomドメイン
送信受信ができるメールアドレス


実作業
--

 - Route53にホストゾーンを追加する


①AWSコンソールからRoute53を選択し、DNS管理からホストゾーンを追加する
freenomで作成したドメインを入力する。
タイプを「パブリックホストゾーン」にする。
レコード → NSとSOAが作成されるので、NSの4つの値をFrenomのネームサーバにコピペする。



- SESにドメインを作成する

②AWSコンソールからSESを選択し、~~リージョンをオレゴンにする。~~
東京リージョンにもあります。
「Domains」→「Verify a New Domain」
ドメインを入力し、☑Generate DKIM Settings　とする。（チェックを入れる）

※この①と②ですが、順番を逆にすると作成できません！！
**必ず** Route53にホストゾーンを作成してから、SESでドメインを入力しましょう。

- 2通のメールの受信を確認する

:envelope: メールが2通送信されます。作業しているAWSのアカウントのメールアドレスです。
「Domain Verification SUCCESS for aws-ses-test.tk in region US West (Oregon)」
と
「DKIM setup SUCCESS for aws-ses-test.tk in US West (Oregon) region」
の2通です。

- メールアドレスを認証する

**「Email Addresses」**から、認証につかうメールアドレスを登録します。
そのメールアドレスに、認証メールが届きますのでリンクをクリックします。
よくわからないAmazon SESの画面に飛ばされますが認証できます。

わたしはメールアドレスを2つ登録、認証しました。

- テストメールを送信する

**「Domains」**から、認証につかうメールアドレスを選択し「Send a Test Email」をクリックします。

![0526.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178985/bee218ac-1549-d858-5411-eb39a4c13d32.png)


無事送信できることを確認しました。

試しにそのメールに返信してみましたが、
「Mail Delivery Subsystem」リモート サーバーからの応答:
550 5.1.1 Requested action not taken: mailbox unavailable
となり返信できませんでした。

→受信の設定や、メールフロムの設定などは今後まとめます。
Summer '21 リリースの特長 ⇒ セールスの新機能の確認

１．正誤問題: パイプライン検査は、チームが健全なパイプラインを維持し、重要な商談の変化を見つけ、営業コーチングを改善するのに役立ちます。
２．Einstein リードスコアリングの機能強化では、何に合わせて予測モデルを調整できますか?
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634241/993b3f6b-4b0d-8b62-9529-331c29d19c19.png)
[AWS_26]CloudWatchをAutoScalingと紐付ける

##環境
**Amazon Web Service**

* EC2(Amazon Linux 2 , t2.micro , 汎用SSD 8GB)
* RDS(MySQL , db.t2.micro)
* ALB

**ローカルPC**

* MacBook Pro(2.9 GHz デュアルコアIntel Core i5 , 8 GB 2133 MHz LPDDR3)
* macOS 11.3.1（20E241）


##概要
CloudWatchで作成したアラートをAutoScalingと紐付けを行う。


##CloudWatchをAutoScalingと紐付ける
* `Auto Scaling グループ　＞　Test-AutoScaling1　＞　自動スケーリング　＞　Dynamic scaling policies　＞　Create dynamic scaling policy`をクリックする。
<br><br>
* 次の項目を入力して、スケーリングポリシーを作成する。
    * ポリシータイプ: `シンプルなスケーリング`
    * スケーリングポリシー名: `CPU_add`
    * CloudWatch アラーム: `CPU_High`
    * アクションを実行: `追加`,`1`,`キャパシティユニット`
    * 次に、待機します: `300`
    <br>→要は、「CPU_Highのアラートをトリガーに、1のキャパシティユニット(EC2)を追加する」というスケーリングポリシーを作成するということ。
<br><br>
* 同様に`CPU_remove`のスケーリングポリシーを作成する。    
    * ポリシータイプ: `シンプルなスケーリング`
    * スケーリングポリシー名: <font color=red>CPU_remove</font>
    * CloudWatch アラーム: <font color=red>CPU_Low</font>
    * アクションを実行: <font color=red>削除</font>,`1`,`キャパシティユニット`
    * 次に、待機します: `300`
C# PDFをエクセルに変換

<p><strong>今日はSpire.PDFという無料で使いやすいライブラリを利用して、PDFをエクセルに変換する方法を紹介します。</strong></p>
<h4><strong>下準備</strong></h4>
<p><strong>1.E-iceblueの公式サイトから<a href="https://www.e-iceblue.com/Download/download-pdf-for-net-free.html">Free Spire.PDF</a>無料版をダウンロードしてください。</strong></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210526/20210526154638.png" alt="f:id:lendoris:20210526154638p:plain" title="" class="hatena-fotolife" itemprop="image" /><br /><strong>2.Visual Studioを起動して新規プロジェクトを作成してから、インストールされたファイルにあった相応しいSpire. PDF.dllを参照に追加してください。</strong></p>
<p><strong>（Net 4.0を例としたら、デフォルトパスは“Bin→NET4.0→PDF.dll”というようです。）</strong></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210526/20210526154705.png" alt="f:id:lendoris:20210526154705p:plain" title="" class="hatena-fotolife" itemprop="image" /></p>
<h4><strong>元のファイル</strong></h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210526/20210526154804.png" alt="f:id:lendoris:20210526154804p:plain" title="" class="hatena-fotolife" itemprop="image" /></p>
```C#
using Spire.Pdf;

namespace ConvertPDFToExcel
{
    class Program
    {
        static void Main(string[] args)
        {
            //PdfDocument objectを作成します。
            PdfDocument pdf = new PdfDocument();
            //PDFファイルをロードします。
            pdf.LoadFromFile("サンプル.pdf");

            //Excelで保存します。
            pdf.SaveToFile("PDFToExcel.xlsx", FileFormat.XLSX);
        }
    }
}
```
<h4><strong>実行結果</strong></h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210526/20210526154845.png" alt="f:id:lendoris:20210526154845p:plain" title="" class="hatena-fotolife" itemprop="image" /></p>
<p> </p>
kaggle APIを導入しよう

Kaggle APIの導入に少し困ったので，解決方法を共有します．

#1. kaggleをpipでインストール
```
pip install kaggle
```
をAnacondaのターミナル上で実行します

#2. kaggle.jsonをダウンロード
自分のアカウントページにアクセスし，Accountをクリックすると
APIの欄にCreate New API Tokenというボックスがあるのでそこからダウンロードできます．
![スクリーンショット 2021-05-26 154105.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/721767/fc81fd15-7c16-24b4-e713-1b323e0248b0.png)

そのダウンロードしたkaggle.jsonをC:\Users\[user名]\.kaggleに移動させます．これで環境は整いました．

#3. データセットをダウンロードしてみる

```
kaggle competitions download -c titanic
```
titanicデータセットは上記のコードをnotebookなどにぶち込むとダウンロードできます．
お疲れさまでした
[AWS_25]CloudWatchでCPU使用率のアラート設定をする

##環境
**Amazon Web Service**

* EC2(Amazon Linux 2 , t2.micro , 汎用SSD 8GB)
* RDS(MySQL , db.t2.micro)
* 

**ローカルPC**

* MacBook Pro(2.9 GHz デュアルコアIntel Core i5 , 8 GB 2133 MHz LPDDR3)
* macOS 11.3.1（20E241）



##概要
CPU使用率のアラートを設定し、AutoScalingと紐付けをすることで、CPU使用率の値によってEC2インスタンスを自動でスケールするようにしたい。
本記事では、CPU使用率のアラートの設定を実施する。


##CloudWatchでアラートを設定
* `CloudWatch　＞　アラーム　＞　アラームの作成`をクリックする。
* ステップ1
<br>`メトリクスの選択　＞　EC2　＞　Auto Scaling グループ別　＞　CPUUtilization`を選択する。
<br>CPUUtilization が次の時...: `より大きい`
<br> ... よりも: `70`
<br>欠落データの処理: `欠落データを不正 (しきい値を超えている)として処理`
* ステップ2
<br>通知の送信先: `My-SNS-Topic`(任意のメールアドレスを指定)
* ステップ3
<br>アラーム名: `CPU_High`
<br>アラームの説明: `CPU usage is more than 70% `
* ステップ4
<br>アラームの作成をクリックする。
<br><br>
* 同様に`CPU_Low`を作成する。異なる点は以下。
<br>CPUUtilization が次の時...: `より低い`
<br> ... よりも: `30`
<br>欠落データの処理: `欠落データを見つかりませんとして処理`
<br>アラーム名: `CPU_Low`
<br>アラームの説明: `CPU usage is less than 30% `

##確認
* アラートの状態が画像のように、[CPU_Low-アラーム状態],[CPU_High-OK]であることを確認する。
![スクリーンショット 2021-05-26 15.35.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/758644/19ef18d9-4259-0a20-adce-244689299240.png)
AtCoderで再帰呼び出しで実行時エラーになってはまった

Pythonの小ネタです。
深さ優先探索の問題を解くときに再帰呼び出しを使っていたのですが、AtCoderにコードを提出するとRE（実行時エラー）になる。他の方の回答（ソースコード）と見比べても差はほぼなさそうでかなり悩んだのですが、次の方法で解決できました。

```Python
import sys
sys.setrecursionlimit(10**6)
```

どうもAtCoderの環境ではデフォルトでは再帰呼び出しの上限が1,000回のようで、それを超えて再帰呼び出しする場合はこの指定が必要です。

# 参考にしたサイト
[Pythonで競技プログラミング -ライブラリ編-](https://qiita.com/Kentaro_okumura/items/5b95b767a2e691cd5482)
Raspberry Pi の Chromium 自動起動時にシリアル番号を渡す方法

## 概要

Raspberry Pi でキオスク端末を作成する際、Chromium を自動起動させることが多いかと思います。
その際、GET パラメーターにデバイスのシリアル番号を持たせ、デバイスを識別できるようにする必要があったので、手順をメモしておきます。

## 動作環境

- Raspberry Pi 4 Model B
- Raspberry Pi OS with desktop (March 4th 2021)

次のコマンドでデバイスのシリアル番号の確認できます。

```
cat /sys/firmware/devicetree/base/serial-number
```

## 手順

### Chromium 起動スクリプトを作成

```
nano ~/launch.sh
```

``` launch.sh
/usr/bin/chromium-browser --noerrdialogs --disable-infobars --kiosk http://your-domain/?s=$(cat /sys/firmware/devicetree/base/serial-number)
```

RPi のシリアル番号は `/sys/firmware/devicetree/base/serial-number` で確認することができます。
上記の例では、GET パラメーター `s` にシリアル番号を渡しています。
起動引数は適宜変更して下さい。

スクリプトを実行してブラウザが起動し、シリアル番号が渡されているか確認します。

```
bash ~/launch.sh
```

### autostart ファイルを作成する

```
mkdir -p ~/.config/lxsession/LXDE-pi
cp /etc/xdg/lxsession/LXDE-pi/autostart ~/.config/lxsession/LXDE-pi/autostart
```

これで、`~/.config/lxsession/LXDE-pi/autostart` が `/etc/xdg/lxsession/LXDE-pi/autostart` の代わりに実行されます。

先程作成したスクリプトを起動するようにします。

```
nano ~/.config/lxsession/LXDE-pi/autostart
```

```diff:autostart
@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
@xscreensaver -no-splash
+ bash /home/pi/launch.sh
```

### 再起動して動作確認

```
sudo reboot
```

起動時にブラウザが開けば成功です。
WebDriverException: unknown error: cannot find Chrome binaryへの対処法

`$options->setBinary()`にchrome.exeのパスを入れる。

ローカル環境の場合

```
$options = new ChromeOptions();
$options->setBinary('C:/Program Files/Google/Chrome/Application/chrome.exe');
$options->addArguments(['--headless']);
$caps = DesiredCapabilities::chrome();
$caps->setCapability(ChromeOptions::CAPABILITY, $options);
$driver = ChromeDriver::start($caps);
```

おわり。
データテーブルの行と列を入れ替える(UiPath)

![ui3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1401870/b8b3f57c-e46b-4f88-dd7e-fdb37d2f7613.jpeg)
左図を右図のように、行列を入れ替えたい場合、下記のように設定をすれば可能です。
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1401870/e4752943-e2ae-7c05-e055-c87d0184dc59.png)

なお、下図のように「入れ替えた後のデータに、列名は要らないぜ！」という方は
![ui6.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1401870/f5ea1fea-01d8-b9e3-3599-f952f5089e3c.jpeg)
後半のIf内の設定を下記のようにすればOKです。
![ui8.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1401870/51d3e37a-4ad8-d95e-e015-d3efb996590a.jpeg)
【Azure】KeyVaultに格納している証明書の更新


KeyVaultで証明書をインポートして証明書共有コンテナとして、ApplicationGatewayのHTTPSリスナーやAPIManagementカスタムドメイン等の各リソースで使用したい場合の更新手順について。

## 手順
1. AzurePortal上で、[キー コンテナー]-[証明書]をクリック
2. 表示されている証明書で更新対象の証明書をクリックする
2. [+ 新しいバージョン]をクリックする
3. 下記の通り入力して、[作成]をクリックする <br>
証明書の作成方法：「インポート」<br>
証明書ファイルのアップロード：pfxファイル<br>
パスワード：証明書(pfx)作成時指定したパスワード<br>

## 更新後
手順としては上記を実施するだけだが、ApplicationGatewayとAPIManagementで証明書更新の確認を行うものの、待てど暮らせど更新されない。

## 4時間待つ
#### ApplicationGW
[Key Vault 証明書を使用した TLS 終端](https://docs.microsoft.com/ja-jp/azure/application-gateway/key-vault-certs)
>インスタンスによって Key Vault が 4 時間間隔でポーリングされ、証明書の更新バージョン (存在する場合) が取得されます。 更新された証明書が検出されると、HTTPS リスナーに現在関連付けられている TLS または SSL 証明書が自動的にローテーションされます。

#### APIManagement 
[証明書のオプション](https://docs.microsoft.com/ja-jp/azure/api-management/api-management-howto-mutual-certificates#certificate-options)

>キー コンテナーで更新された証明書は、API Management で自動的にローテーションされます。 キー コンテナー内で更新が行われると、4 時間以内に API Management 内の証明書が更新されます。 また、Azure portal または管理 REST API を使用して、証明書を手動で更新することもできます。


4時間経ったら確認できた。
ドキュメントちゃんと読もう(n回目）
APIManagementの場合は、登録したホスト名と、新しいバージョンのCNが一致しないと更新がエラーになる。

## いや、4時間待たない
#### ApplicationGW
即時反映させるためには、ApplicationGWに対して何かしらの設定変更を行うと反映される。適当な正常性プローブを作成して削除、でOK。
参考：[Azure Application Gatewayのリスナー証明書を自動で更新する](https://ascii.jp/elem/000/004/052/4052122/)

#### API Management
新しいバージョンの証明書をKeyVaultに登録してから少しの時間待つと、AzurePortal上からカスタムドメインの証明書設定で、新しいバージョンの証明書を選べるようになっているので、選択して更新する。
サービスへの証明書の適用には、15分ほど時間がかかる。
[Azure ポータルを使用してカスタム ドメイン名を設定する]
(https://docs.microsoft.com/ja-jp/azure/api-management/configure-custom-domain#use-the-azure-portal-to-set-a-custom-domain-name)
>証明書を割り当てる処理は、デプロイのサイズによっては、15 分以上かかる場合があります。 Developer SKU にはダウンタイムがありますが、Basic 以上の SKU にはダウンタイムがありません。
CS1690:参照マーシャリング クラスのフィールドであるため、'member' のメンバーにアクセスすると、ランタイム例外が発生する可能性があります

#開発中に警告が発生
C#でフォーム間をまたぐ変数を使用するプログラムを開発中に、以下の警告メッセージが発生しました。
おや、と思って調べてみるも、いまいちピンとくるものがありません。
>CS1690:参照マーシャリング クラスのフィールドであるため、'frm.struct1' のメンバーにアクセスすると、ランタイム例外が発生する可能性があります。

#ソースをみると

```C#:Form1.cs
using (Form2 frm = new Form2())
{
    frm.ShowDialog();
    result = frm.struct1.a;
}
```
```C#:Form2.cs
public struct STRUCT1
{
    public int a { get; set; }
    public int b { get; set; }
    public int c { get; set; }
}
public STRUCT1 struct1;
```

#あっ、構造体変数がプロパティじゃない
よく見ると構造体内の変数(a, b, c)はプロパティになっていますが、構造体変数(struct1)側がプロパティになっていません。基本的にフォーム間の変数はプロパティでやりとりするため、構造体変数をプロパティにする必要がありそうです。

#修正してみた
```C#:Form1.cs
using (Form2 frm = new Form2())
{
    frm.ShowDialog();
    result = frm.struct1.a;
}
```
```C#:Form2.cs
public struct STRUCT1
{
    public int a;
    public int b;
    public int c;
}
public STRUCT1 struct1 { get; set; }
```
これで警告は解消されました。めでたしめでたし。
Yellowfinのクライアント組織に属している人向けのJSAPIv3でのダッシュボード表示

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223443/0094768d-f303-ffde-e883-c54e5e7e895d.png)


#概要
件名の通りなのですが、[CORS設定を行いwebserver越しにRESTでYellowfinサーバーにアクセスする](https://qiita.com/celery/items/69f8da9601277807cc34)で試してみたとおり、デフォルト組織に属するユーザーに関しては問題なく表示できるのですが、人によってクライアント組織を分けていて、その人に向けて表示する内容を変えたいといった場合に使える方法を検証した結果、無事に表示できたのでそのメモを残そうと思います。

#前提
・管理者ユーザーとアクセスユーザーが存在する。この場合管理者ユーザー(admin@yellowfin.com.au)、アクセスユーザー(admin2@yellowfin.com.au)。
・アクセスユーザーはtest1というクライアント組織に属している(参照IDもつける)。
・今回の場合はSSOを有効にしているため、[リポジトリに対してSQLを発行](https://qiita.com/celery/items/69f8da9601277807cc34#sso%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%A8%AD%E5%AE%9A)済です。

#サンプルコード
解説としては、body変数のアクセスユーザーの情報にclientOrgRefを付与することと、JSAPIv3を使用してダッシュボードを表示する際のURLにclientOrgクエリを追加することです。


```javascript:dashboradClientRef.js
<html>
<head>
	<meta charset="utf-8" />
	<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
</head>
<body>
	<div id="reportDiv"></div>
	<script>
		var authUserId = 'admin2@yellowfin.com.au';
		var authUserPass = 'dammy';
		var adminId = 'admin@yellowfin.com.au';
		var adminPassword = 'test';
		var body = {
			signOnUser: {
				userName: authUserId,
				password: authUserPass,
				clientOrgRef: 'test1'
			},
			noPassword: true,
			adminUser: {
				userName: adminId,
				password: adminPassword
			}
		};
		//encode to JSON
		var json_text = JSON.stringify(body);
		var xhr = new XMLHttpRequest();
		xhr.open('POST', 'http://localhost:8940/api/rpc/login-tokens/create-sso-token');
		xhr.responseType = 'json';
		xhr.onload = () => {
			var securityToken = xhr.response.securityToken;
			//alert(securityToken);
			let url = 'http://localhost:8940/JsAPI/v3?token=' + securityToken + '&clientOrg=test1';
			let tp = 'text/javascript';
			let sc = document.createElement('script');
			sc.src = url;
			sc.type = tp;
			document.body.appendChild(sc);
			setTimeout(function () {
				yellowfin.showLoginPrompt = false;
				yellowfin.init().then(() => {
					yellowfin.loadDashboard({
						dashboardUUID: 'e7409ff2-f846-44e1-a603-b78ec51b20b9',
						element: document.querySelector('div#reportDiv')
					});
				});
			}, 1000);
		};
		xhr.setRequestHeader('Authorization', 'YELLOWFIN ts=' + new Date().getTime() + ', nonce=123');
		xhr.setRequestHeader('Accept', 'application/vnd.yellowfin.api-v1+json');
		xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
		xhr.send(json_text);
	</script>
</body>
</html>

```

#かんそう
これで、埋め込み表示でも人によってクライアント組織別でデータが違うダッシュボードも表示できるようになりますね。その前のシステムで情報は揃える必要がありますが・・・。それでもアクセスフィルターで分けているような場合だと有効なんじゃないかなと思います。
PHPでフォーム送信時のチェックボックスの保存

PHPでチェックボックスの値を処理する際、毎回忘れて迷うのでメモ。最適解かどうかは不明。記述はLaravel、リレーション多対多を想定。

値を配列に整理して、array_diffで振り分けるところを押さえれば、他にも使えそう。

# 課題

フォームで送信されたデータを保存する時、テキストやラジオボタンと比べてチェックボックスは手間がかかる。
テキストやラジオボタンは値が一つしかないのに、チェックボックスは基本的に複数の値が送信されるのが原因。

# フォーム

☐a ☐b ☐c ☐d ☐e ☐f ☐g ☐h [保存]

```html:html
<form method="post" action="{{ route('save') }}">
  @csrf
  <input type="checkbox" name="hoge[]" value="a">a
  <input type="checkbox" name="hoge[]" value="b">b
  <input type="checkbox" name="hoge[]" value="c">c
  <input type="checkbox" name="hoge[]" value="d">d
  <input type="checkbox" name="hoge[]" value="e">e
  <input type="checkbox" name="hoge[]" value="f">f
  <input type="checkbox" name="hoge[]" value="g">g
  <input type="checkbox" name="hoge[]" value="h">h
  <button type="submit">保存</button>
</form>
```

# 雑な方法

```php:php
// ユーザーを取得
$user = Auth::user();

// 保存されている関係をすべて解除
$user->hoges()->detach();

// 改めてチェックされたものを保存
$user->hoges()->attach($request->hoge);
```

この方法は簡単でいいのだけど、何も変更していなくても全消しして再保存となるので、なんとなく気持ち悪いし、プログラム的にもアンチパターンな気がする。

# （おそらく）ちゃんとした方法

手順としては、チェックされたものと保存済みのものを照合して、保存するものと削除するものを振り分ける。

### 保存・削除ルール

```
チェックあり＋未保存＝保存
チェックあり＋保存済み＝スルー
チェックなし＋保存済み＝削除
チェックなし＋未保存＝スルー
```

### 例

```
フォームでチェックしたもの...[a b c d]
既にDBに保存済みのもの...[c d e f]
a b 保存（チェックあり＋未保存）
c d スルー（チェックあり＋保存済み）
e f 削除（チェックなし＋保存済み）
g h スルー（チェックなし＋未保存＝スルー）
```


## array_diffを使う

判定するには、array_diffを使います。

### 配列の取得

```php:php
// チェックされた値
$checked = $request->hoge; //["a", "b", "c", "d"];

// 保存されている値
$saved = $user->hoges()->pluck('value')->toArray(); //["c", "d", "e", "f"];
```

### 保存するもの、削除するものに振り分け

```php:php
// チェックされている値から保存されている値を除く
$save_array = array_diff($checked, $saved); //["a", "b"]

// 保存されている値からチェックされている値を除く
$del_array = array_diff($saved, $checked); //["e", "f"]
```

### 保存、削除

```php:php
// 保存(attach)
if(!empty($save_array) && is_array($save_array)){
  $user->hoges()->attach($save_array);
}

// 削除(detach)
if(!empty($del_array) && is_array($del_array)){
  $user->hoges()->detach($del_array);
}
```

こうすれば、保存するべきものは保存され、削除するべきものは削除、それ以外のものは何もしない、と
「何も変更していなくても全消しして再保存」という処理を走らせなくて済む。
事後計算で計算結果がおかしいときに見て欲しい記事【NVL関数を使う】

## はじめに
MotionBoardでは事後計算項目という機能を使用して、**データを集計した後にMotionBoardサーバーで再計算する**ことができます。
データ分析において、過去年度の実績との差分や比率を算出するというのはよくあるケースですよね。
そんなときには事後計算項目がうってつけですが、**計算対象となる集計項目の値がNullになる場合**は計算結果が思い通りにならないことがあります。
ただし関数を追加することで正しく演算できるようになるので、その対処法を紹介します。

<details><summary>【参考】MotionBoardとは</summary>
様々なデータを1つの画面上で統合し分析できるBIツールで、
チャートの種類も多くデータを直感的に把握することができます。
※詳しくはウイングアーク１ｓｔ社のHPを参照
[ウイングアーク１ｓｔ株式会社　MotionBoard](https://www.wingarc.com/product/motionboard/index.html)
</details>

## 前提
サンプルデータとしてかなりシンプルな取引明細を用意しました。
取引先別の今年度実績と前年度との差分を集計したいのですが、
C社は今年度から新規で取引を開始したため前年度分のデータは存在しません。
**データはないけど前年度実績を0と置き換えて差分を算出する**というのが今回の肝となります。

↓　前年度実績　↓
![2021-05-26_13h24_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705726/5b814873-b470-ad18-9dce-1ca4f0d29822.png "前年度実績")

↓　今年度実績　↓
![2021-05-26_13h26_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705726/898098c9-2b38-dec9-1a05-9b608021c70b.png "今年度実績")


## 事後計算項目を作ってみる
ベースとなる集計表を作りました。
取引先別の各年度の実績を集計しています。
2020年度のC社の実績は0と表示されていますが、見かけだけで裏側はNullです。紛らわしいですが。。
![2021-05-26_13h20_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705726/fa47fead-a5ad-a7a4-c4fb-a69cdfe13d30.png "集計表")

続いて事後計算項目を作ります。
【参考】[事後計算項目を作成する](https://cs.wingarc.com/manual/mb/cloud/ja/UUID-5ad52eb1-e8b3-8b68-8ea9-98a37a80c017.html)

なお、`SeriesPrev([項目名])`はひとつ前の列の値を参照する関数です。
ここでは`Self([実績])-SeriesPrev([実績])`とすることで、2021年度の実績から2020年度の実績を引き算するようにしています。
![2021-05-26_13h31_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705726/acf465da-949d-68cc-a140-67d4a98e60a2.png "事後計算項目")

結果を見てみましょう。
C社の差分は800になって欲しいところですが0になっていますね。ちなみにこの0も中身はNullです。
MotionBoardに限らず一般的に言えることですが、演算の中にNullが入っていると結果もNullになります。
そのため`800 - Null`という計算結果はNull（しつこいですが見かけ上は0）になります。
![2021-05-26_13h32_59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705726/f6a58acd-05bc-c80a-f671-9f8baaf04085.png "結果1")


## NVL関数を使う
理屈がわかったところでNVL関数を使った対処法を紹介していきます。
NVL関数は、引数1がNullの場合に引数2の値に置き換える関数です。
【参考】[数学関数・NVL関数](https://cs.wingarc.com/manual/mb/cloud/ja/UUID-0d30d03d-325a-db76-268c-d1af222c66ae.html#UUID-0d30d03d-325a-db76-268c-d1af222c66ae_d38915e151539)

````
NVL(引数1,引数2)
````

この関数を使用して、計算式を以下のように書き換えました。
`Self([実績])`と`SeriesPrev([実績])`をそれぞれNVL関数で囲んだうえで引き算をしています。

````
NVL(Self([実績]),0)-NVL(SeriesPrev([実績]),0)
````

![2021-05-26_12h01_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705726/62c8dd59-89f6-ca82-10fa-73c88e50539e.png "NVL関数を使用した事後計算")

改めて結果を確認してみましょう。
差分がきちんと算出されました！！
NVL関数によって2020年度のC社の実績値が0に置き換わったため、
`800 - 0 = 800`の計算が行えたという仕組みです。
![2021-05-26_13h45_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705726/05dfc1b4-765a-4556-1a20-41a16877fd6e.png "結果2")


## おわりに
MotionBoardの事後計算項目は、**データを集計した後にMotionBoardサーバーで再計算する**機能です。
今回のように「そもそもデータが存在しないから集計値がNullになってしまう！」という場合でも、0に置き換えることで演算処理が可能になります。
NVL関数以外にも使用できる関数はたくさんあるので困ったときは[こちら](https://cs.wingarc.com/manual/mb/cloud/ja/UUID-0d30d03d-325a-db76-268c-d1af222c66ae.html)を参考にしてみましょう。
すぐに始められるRspecの環境構築のメモ（everydayrails用）

#はじめに
今回はeverudayrailsの2章(Rspecのセットアップ)が終了している状態のものから始めることにする。

#開発環境
Mac os 11.2.3
ruby 2.7.1
シェル　zsh


#git clone

適当なディレクトリで

```
git clone  https://github.com/JunichiIto/everydayrails-rspec-2017.git
```

これですでにリポジトリがoriginに登録されていてmasterブランチにいるので

リモートブランチを以下のように自分のブランチに持ってくる。

```
git checkout -b my-03-models origin/02-setup
```

これで始めたい章のソースコードになっている。

続いてrails 6に対応させる。

まずはGemfile.lockの一番下のBUNDLED WITHを変更する。おそらく1.14.4くらいのバージョンになっているので新しいバージョンに書き換える（事前に新しいバージョンのbundlerのインストールが必要。）

```
BUNDLED WITH
   2.1.4
```

とする。
次にGemfileのrails,rspec-railsのみをを編集

```
gem 'rails', '~> 6.1', '>= 6.1.3.1'
gem 'rspec-rails', '~> 5.0'

```
この状態で

```
bundle update
```
を行うとおそらく下記エラーが出る

```
An error occurred while installing mimemagic (0.3.10), and Bundler cannot continue.
Make sure that `gem install mimemagic -v '0.3.10' --source 'https://rubygems.org/'` succeeds before bundling.
```
これを解消するために下記を実行

```
brew install shared-mime-info
```
次に

```
bundle update
```

次に下記を順に実行

```
rails db:create

rails db:migrate

rails s
```
M1macでbrew install circleci でこけるときはcurlだ！

M1 mac だわーい。と意気揚々とセットアップしてたら、circleciでつまった。

`brew install circleci` だとエラーくる。。。。
かなしい。。

```bash
> brew install circleci
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> Updated Formulae
Updated 1 formula.

Error: circleci: no bottle available!
You can try to install from source with:
  brew install --build-from-source circleci
Please note building from source is unsupported. You will encounter build
failures with some formulae. If you experience any issues please create pull
requests instead of asking for help on Homebrew's GitHub, Twitter or any other
official channels.
```

しらべていくと、githubに同志発見。
https://github.com/CircleCI-Public/circleci-cli/issues/579#issuecomment-848018785


以下でいけた！！


```bash
> sudo curl -fLSs https://circle.ci/cli | bash
Password:
Starting installation.
Installing CircleCI CLI v0.1.15224
Installing to /usr/local/bin
/usr/local/bin/circleci
> ls -la  /usr/local/bin
total 35584
drwxr-xr-x  15 root  wheel       480  5 26 10:26 ./
drwxr-xr-x   4 root  wheel       128  5 11 23:18 ../
-rwxr-xr-x   1 root  wheel  18215680  5 26 10:26 circleci*
```


https://circleci.com/docs/ja/2.0/local-cli-getting-started/

よかったぜ。
M5Cameraで環境センサユニット ver.2(ENV II)を使う

# 概要
M5Cameraで環境センサユニット ver.2(ENV II)が使えたのでメモ。M5CameraとENV IIはGrove端子経由のI2C接続。M5StackのサンプルをベースにI2CのGPIOを明示的に指定すればOK。

ちなみにQiita初投稿。Arduino、M5Stackはもちろんマイコン系、ひいてはMarkdownすら超初心者ゆえ、嘘を書いてる恐れあり。

# 試した環境
* M5Camera Version B
* M5Stack用環境センサユニット ver.2(ENV II)
* Arduino IDE 1.8.15
* Arduino core for the ESP stable 1.0.6
* Adafruit BMP280 Library 2.3.0

# 手順
## サンプルソースを入手する
以下のサイトから、M5StackでENV IIを使うサンプルソースを入手し、Arduino IDEで開く。

* Adafruit_Sensor.h
* ENVII_SHT30_BMP280.ino
* SHT3X.cpp
* SHT3X.h

https://github.com/m5stack/M5Stack/tree/master/examples/Unit/ENVII_SHT30_BMP280

以下、個人的な好みで**ENVII_SHT30_BMP280.ino**を**M5Camera-ENVII.ino**と改名しているけど、気にしないように。

## M5Stackライブラリ関係をコメントアウトする
M5CameraはM5Stackライブラリとは無関係(だと思う)。よって、M5Stackライブラリ関係の行をコメントアウトする。

以下、一部の例。要は`#include <M5Stack.h>`と`M5.*`みたいな行をコメントアウト。今回の場合は**M5Camera-ENVII.ino**のみが対象。

```cpp:M5Camera-ENVII.ino
//#include <M5Stack.h>
...
void setup() {
//  M5.begin();
//  M5.Power.begin();
  Wire.begin();
...
```

## シリアルの初期化を追加する
M5Stackライブラリ関係をコメントアウトした影響で、シリアルの初期化もされないので、自分で追加する。これを忘れるとシリアルモニターに何も表示されず、結構な量の汗が出る。

```cpp:M5Camera-ENVII.ino
//  M5.Power.begin();
  Serial.begin(115200);  // 追加する
  Wire.begin();
```

## I2CのGPIOを明示的に指定する
M5CameraのGrove端子のI2Cは、SDAがGPIO:4、SDLがGPIO:13。と、M5Camera本体に老眼には読めない文字で書いてある。多分、やらないといけない。けど、やらずには試していない。

```cpp:M5Camera-ENVII.ino
void setup() {
  ...
  Serial.begin(115200);
  Wire.begin(4,13);  // 元はWire.begin();
  ...
}
```

そして何とも行儀の悪い感じがするけど、もう一か所`Wire.bigin()`が潜んでいる。それが**SHT3X.h**の中。

```cpp:SHT3X.h
SHT3X::SHT3X(uint8_t address)
{
  Wire.begin(4,13);  // 元はWire.begin();
  _address=address;
}
```

ちなみにこの辺り、以下のページを参考にさせてもらった。

http://marchan.e5.valueserver.jp/cabin/comp/jbox/arc202/doc21102.html

https://intellectualcuriosity.hatenablog.com/entry/2020/05/18/220253

## 動かす
意外にも、これだけで動く。M5Cameraに流し込んで、リセットボタンを押す。

もっとあちこち修正がいるかと思ったけど、そうでもなかった。一番悩んだのはシリアルの初期化。起動してもシリアルモニターに何も表示されず、どっかでパニクってるのかと思って、自分がパニクってた。

# 最後に
最終的にはブラウザからM5Cameraにアクセスして、静止画や温度を取れるようにしたい。世の中には変わった使い道もあるということで。
保守性・活用可能性の高いソフトウェアとは



#はじめに
研究者として色々ソフト開発してきたが、やはり「保守性」と「活用可能性」が重要だと思う。とは言うものの「保守性の高いコード」や「活用可能性の高いコード」って、どんなもの？と聞かれて即答するのが難しいことに気づいた。そこで「[ソフトウェア保守開発](https://honto.jp/netstore/pd-book_02929370.html)」や「[リーダブルコード](https://www.oreilly.co.jp/books/9784873115658/)」など多数の書籍や、Googleの[DevOpsドキュメント](https://cloud.google.com/architecture/devops/devops-tech-code-maintainability?hl=ja)、ネット上にアップされている各種の先達の意見を参考に、自分なりに整理してみた。

# 保守性が高いソフトウェア
最初に保守性の方から考えてみたが、実は**問題が無い**というのが大事だったりする。よって、これから要件を出すのは難しい。

* ISO14764での適応保守、是正保守、緊急保守、保守性、修正依頼、問題報告、予防保守、ソフトウェア保守、ソフトウェア引き継ぎなどを阻害せず、効率的に行える。
* OSやライブラリなど開発環境の変更があっても問題なく使い続けられる。
* OSやライブラリなど実行環境の変更があっても問題なく使い続けられる。
* 入力データやユーザが変わっても問題なく使い続けられる。
* 新しい使い方や使用条件に対応して、素早く修正対応できる。
* 開発後長い期間たってから判明した不具合やバグを、素早く修正対応できる。
* 開発担当者が変わっても、ソフトウェアの中身に関する知識を短期間に継承できる。

# 保守性の低いコードの例
一方で、保守性の低いコードは、自分の経験を含め実例が多数ある。
よって反面教師としてわかりやすいので以下に列挙する。

* スパゲッティコード。複雑に絡み合って、一部の修正が予想もしない他の場所に影響する。
* ブラックボックスがある。何をやっているのか今では誰も分からないコードを含んでいる。
* 個々の関数やモジュールが巨大で、同じメモリ領域を複雑に使用したコードになっている。
* 似たような関数が多数存在し、使い方の違いが分かりにくい。全く同じコードが何回も出てくる。
* オブジェクト指向が採用されておらず、変数や関数の関連性が不明。
* 構造体やクラス定義が恣意的で、何を表しているのか開発者以外に分からない。
* コメントが無い。
* 関数名、変数名、モジュール名が恣意的で、開発者以外に中身が想像できない。
* トリッキーなアルゴリズムが採用されており、修正が困難である。ポインタマジックなど。
* 強い外部依存があり、依存対象のライブラリ等が明示されておらず、最新版に対応していない。
* ドキュメントがソースコードしか無い。
* 構成管理がなされていない。複数のバージョンが並立している。
* 保守履歴が記録されていない。
* テストコードが整備されておらず、改修後の検証が困難である。
* 最初に開発されてから一度もレビューされていない。
* プログラム内に既に使用停止になったコードが多数残っている。
* コード量が無駄に大きい。
* 脆弱性が放置されている。
* エラーハンドリングが適当に処理されている。例外を無視など。

# 活用可能性が高いソフトウェア

活用可能性は、ISO14764での改良保守、完全性保守に加えて、拡張可能性、API整備、マイクロコード化などだが、保守性ほどには整理されていない。

* 別のアプリケーションと入出力データを容易に連携可能である。
* APIリファレンスやチュートリアルなどドキュメントが整備されており、ソフトウェアの中身を知らなくても容易に機能を利用可能である。
* ソフトウェアの機能や内部構成に関するドキュメントが整備されており、機能拡張や修正が容易である。
* 開発当初に想定した規模を大きく超える入力データや計算内容にも柔軟に対応できる。
* デファクト・デジュールな規格に準拠しており、手を加えなくても外部ソフトウェアと連携可能である。
* コードの必要な部分を切り出して使うのが容易である。

# 活用可能性の低いコードの例

保守性同様にこちらも悪い例を列挙してみる。

* APIが整備されておらず、外部のソフトから利用することができない。
* ドキュメントはソースコードのみ。
* メモリ確保やディスク管理が固定的でスケーラビリティが無い。ループが固定的で暗黙に上限が設定されている。
* 機能を制限するパラメータがコード内部にハードコーディングされている。
* 特定の漢字コードを前提にしていて、暗黙にそれを前提にした処理を行なっている。
* 特殊な外部アプリや、ライブラリ、OSバージョンに依存しており、分離して利用できない。
* 入出力データの形式が特殊で、利活用に手間がかかる。また形式に関する分かりやすい資料が無い。
* クラスライブラリが整理されていない。似たようなクラスが複数あり、違いが分からない。
* アルゴリズムにスケーラビリティが無く、扱うデータ量に応じて指数関数的に計算時間が伸びる。または計算不可能。
* GUIとビジネスロジックが密接に絡んでいて、別々に利用できない。UIのコールバックに計算を入れ込むなど。
* 利用範囲や制約、副作用が不明で再利用性の低い関数、構造体、クラスから構成されている。


# おわりに

いずれも「言うはやすし」ではあるが、列挙してみることは大事かなと思った。
他に追加すべき良好事例・悪例があればコメントください。
Google SpreadSheetのデータをHTTP(GAS)で抜いて、AWS S3へ格納するlamdba処理（メモ）

# 目的

GASにHTTPアクセスすると、必ず１度リダイレクトされるため、HTTPアクセス元のシステムによってはデータを取得できない。
（iOS Podcastでは、Google SpreadSheetとGASで用意した自前のRSSを食ってくれない）

lamdbaでGASを叩き、テキストファイルをS3へ格納する（実装方法は趣味です）


# lamdbaコード


```javascript:index.js

const gas_url = "GAS_URL";         //要編集
const backet = "S3_backet_name";   //要編集
const file = "file_name";          //要編集


const AWS = require('aws-sdk');
const https = require('https');

// リダイレクト先 URL を取得する関数
function get_redirect_url(src_url) {
  console.log("Call get_redirect_url");
  return new Promise((resolve, reject) => {
    try {
      // 4xx や 5xx ではエラーが発生しないので注意
      https.get(src_url, (res) => {
        // HTTP レスポンスから Location ヘッダを取得 (ヘッダ名は小文字)
        resolve(res.headers['location'])
      }).on('error', (err) => {
        reject(err)
      })
    } catch(err) {
      reject(err)
    }
  })
}

function get_text_write_s3(url){
  console.log("Call get_text_write_s3");
  return new Promise((resolve, reject) => {
    try {
      let str = '';
      https.get(url, (resp) => {
    
        resp.on('data', (chunk) => { 
          str += chunk; 
        }).on('end', () => { 
          
          //write to S3
          if(str.length > 920){// header+footer size ≒ 920
            // Load the AWS SDK for Node.js
            AWS.config.update({region: 'us-east-2'});
            // Create S3 service object
            s3 = new AWS.S3({apiVersion: '2006-03-01'});

            // call S3 to retrieve upload file to specified bucket
            const uploadParams = {Bucket: backet, Key: file, Body: str};
            
            // call S3 to retrieve upload file to specified bucket
            s3.upload (uploadParams, function (err, data) {
              if (err) {
                console.log("Error", err);
              } if (data) {
                console.log("Upload Success", data.Location);
              }
            });
            
            
          }
        });
      }).on('error', (err) => {
        reject(err)
      })

    } catch(err) {
      reject(err)
    }
  })
  
}


exports.handler = async (event, context) => {
  // リダイレクト先URLを取得
  const redirect_url = await get_redirect_url(gas_url)
    .catch(err => {
      console.log(err)
    })
  
  // リダイレクト先URLでデータ取得し、Ｓ３へ書き込み
  if (redirect_url) {
    await get_text_write_s3(redirect_url);
    context.succeed("OK");
  }
};

```
WeWorkのPhone BoothのCO2を測定してみた

最近WeWorkで働くことが多いんですが、WeWorkにあるPhone Boothというところがオンラインミーティングにはとてもいいんです。

が、ちょっと長いミーティングになると空気が悪くなってそうな気もしてどっと疲れたので、GravioとCO2センサーでCO2濃度を測定してみました。

Phone Boothというのはこんなところ。約半畳くらいのスペースです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/5e1ec252-5dbe-2acf-2f00-2fd12017f7ad.png)


# 使用環境

Gravioは出たばかりのV4.3にアップデートしました。可視化はPower BIで。

* Windows 10 Pro
* Gravio HubKit v4.3.5745.0
* Gravio Studio v4.3.4156
* Gracio CO2センサー (Gravio Standardの契約が必要)
* Power BI

# 手順
「[Gravio 4とPower BIでCO2を可視化する](https://qiita.com/zrock/items/08148659ebf523f5c160)」と同じです。まずはオープンスペースでセットアップ。

みなとみらいのビルをバックにCO2センサーを設定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/9f2e1b81-f812-596b-b933-89140d256693.png)

予約してあったPhone Boothへ移動。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/7d4c956c-c0ac-362b-7cbb-f5ddb1ebebf6.png)

ここで約2時間半ほどミーティング。


結果はこんな感じになりました。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/2f7610c2-4470-bdfa-052f-4fdf223696b5.png)

最初の500ppmくらいの間はオープンスペースです。その後、ちょっと間があいた後がPhone Boothの結果になります。やはり狭いスペースなのでぐんぐん上がりました。が、だいたい1500ppmくらいで頭打ちですね。家でやってるときはもっと上まで行くので、締め切っていてもそこそこ換気はされてるということですね。
途中、一度ガクンと下がっているのはミーティングの合間の休憩で、トイレに行ったときに扉を開けたからですね。

# 結論
WeWorkのPhone Booth、すくなくともみなとみらいのものはそこそこ換気がされてるようです。ただ、1500ppmの環境にずっといることになるので、やっぱり長いミーティングはあまり効率が良くないと思います。部屋を変えたり換気しましょうね。
Expressを使用しない単純なNode.jsサーバーの作成

### Node.jsとは？
Node.jsとは、サーバーサイドのJavaScript実行環境です。
ブラウザGoogle Chrome用に開発されたJavaScriptエンジンV8がサーバ上でプログラムを実行できるように、ファイルやネットワークI/Oなど多くの機能を追加しています。軽量で効率よく多くのリクエストを処理するネットワークアプリケーションの構築ができるプラットフォームです。

Node.jsでは“⁠軽量で効率良い”処理を実現するために次の2つのモデルを採用しています。

- ノンブロッキングI/O

- イベントループ

JavaScriptの1つの特徴として、シングルスレッドで動作することがあげられます。Node.jsにおいても同様で、デッドロックなどを意識する必要がなくなるというメリットがあります。しかしその反面、同時に1つの処理しかできず、複数の処理をさばけません。そのためNode.jsではノンブロッキングI/Oを採用しています。
例えば、データベースにアクセスして検索結果を取得するという処理を行う場合、データベースに問い合わせてから結果が返ってくるまで、待ち時間があります。
一般的にはライブラリなどを用いてデータベースから結果が返るまでの間、呼び出し元の処理を止めておきます。そのため結果を利用した処理を続けて定義できます。
それに対してノンブロッキングI/Oは、データベースに問い合わせたら次の処理に移ります。結果の受け取りはコールバックで実現します。このように基本的に重い入出力処理を非同期で実行するのがノンブロッキングI/Oです。

リクエストやコールバックの発生はイベントとして登録し⁠、そのイベントに関する処理が終わったら次のイベントを処理する。
このようなイベントループをNode.jsは採用しています。

[Node.js 公式サイト](https://nodejs.org/ja/)


### Node.jsインストール方法
公式サイトから推奨版をダウンロードし、PCへインストールします。
公式サイトにアクセスすると、自動的に自分のPC環境に合ったパッケージが表示されます。
Node.js ダウンロードは[こちら](https://nodejs.org/ja/)から

Node.jsインストール後、ターミナルで以下のコマンドを実行してください。
Node.jsのバージョンが表示されれば、正常にインストールできています。

```zsh:ターミナル
$ node -v
v.14.15.4
```


### Webサーバーを作ってみよう！
Node.js公式サイトの[ガイド](https://nodejs.org/ja/docs/guides/getting-started-guide/)にしたがって、Webサーバーを構築してみます。

app.jsという名前のファイルを作成し、以下のコードを書きます。

```JavaScript:app.js
const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('<h1>Hello World</h1>');
});

server.listen(port, hostname, () => {
    console.log(`Server is running at http://${hostname}:${port}/🎉✨`)
});
```
`node app.js` というコマンドを入力してWebサーバーを起動します。
ターミナル上に、`Server is running at http://127.0.0.1:3000/🎉✨`というメッセージが表示されます。
![terminal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1102841/91cf9cd1-f095-e7eb-0600-6e08bd0e6489.png)


また、Webブラウザで `http://127.0.0.1:3000` にアクセスすると、`Hello World` というメッセージが表示されます。
![webbrowser.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1102841/18e73c77-e88b-7816-0179-32f334d43027.png)

#### コード解説
Webサーバーを構築するには、「httpモジュール」を使います。
httpモジュールとは、HTTPサーバーやHTTPクライアントとしての機能を構築するために使用します。

httpモジュールをrequire()で呼び出して使用します。

```JavaScript
const http = require('http');
```

httpモジュールをrequire()で呼び出したら、`createServer()` メソッドを使って、Webサーバーを構築します。
`createServer()`メソッドの引数には、ステータスコードやヘッダー情報、表示するコンテンツなど、さまざまなサーバー側の処理を設定することができます。
`setHeader()`で「Content-Type」を設定します。
`end()`の引数にはHTMLを設定し、`'Hello World'`をブラウザに表示するようにしています。

```JavaScript:
const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('<h1>Hello World</h1>');
});
```


#### hostname 127.0.0.1について
`127.0.0.1` はループバックアドレスの1つで、ネットワーク上においてそのPC自身を示す仮想的なIPアドレスのことです。同一ホスト内でのみ通信を行います。ホスト名としては、慣用的に `localhost` が用いられます。
そのため、

```JavaScript:
const hostname = 'localhost';
```

このように書き換えても、同じようにWebサーバーを起動することができます。
![terminal2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1102841/2667c320-1d61-850f-3706-58b55c269b0c.png)


このようにして、Node.jsを使ってWebサーバーを構築することができました！


### 参考文献
- [Node.js公式ドキュメント](https://nodejs.org/ja/docs/)
スクラムにおける朝会の目的は進捗共有ではないよという話

## これは何

スクラムを採用していてもしていなくても、朝会（デイリースクラム）を行っているチームは多いと思います。
最近僕が在籍するQiita株式会社のチームで朝会が形骸化してない？みたいな話があったので、そもそも朝会を行う目的と、朝会で行うべきことについて記事化していきたいと思います。
今回はスクラムを採用している前提で話をするので、朝会=デイリースクラムとします。

## デイリースクラムの目的は進捗共有ではない
デイリースクラムで、進捗共有をして終わりになっているチーム、意外と多いのではないでしょうか。
しかし、そもそも進捗の共有をしないといけない理由を考えなければなりません。
もしチームのみんながやっていることを知りたいだけであれば、朝会などでみんなで集まらなくとも日報や日々のチャットの中で把握はできるのではないでしょうか。つまり、朝みんなで時間をとって集まっている以上、ある程度のリターンがなければ困ります。
スクラムの中で、デイリースクラムの目的は以下のように定義されています。

> デイリースクラムの目的は、計画された今後の作業を調整しながら、スプリントゴールに対す
る進捗を検査し、必要に応じてスプリントバックログを適応させることである。

https://scrumguides.org/docs/scrumguide/v2020/2020-Scrum-Guide-Japanese.pdf より抜粋


つまり、デイリースクラムを行った後は、以下のような状態になれることを目指さなければなりません。

- スプリントゴール（スプリントで達成するノルマ）を達成できる見込みが立っている
- デイリースクラム前に抱えていた課題が解消されている

今やっているタスクの改善と工夫をディスカッションする時間がデイリースクラムです。
毎日の単位でスクラムのレトロスペクティブとプランニングをやっているようなイメージに近いと思います。

## 朝会ではどんな話をするべきなのか
それでは、デイリースクラムでどんなことを話すべきかをまとめていきます。
僕の所属するQiita開発チームではここに書いたようなプロセスでデイリースクラムを行っています。
ここは僕なりの解釈も入っているので、ご意見あれば編集リクエストやコメントを頂けると嬉しいです。

僕はデイリースクラムは二つのプロセスでできていると考えています。
それは以下の通りです

1. メンバー個々人での共有とディスカッション
1. メンバー全員での確認とディスカッション

これらについて説明していきます。

### メンバー個々人での共有とディスカッション

1. 昨日やったことを話す
    1. 昨日やったことを話すことで、振り返りを行う
1. 今日やる予定のことを話す
    1. 今日やることを話すことで、今日が終わったときのゴール設定が行える
1. 現在困っていることを話す
    1. やっていることが予定通りに進んでいるのか、進んでいない場合は何が原因なのか、原因を解消する方法はないのかをディスカッションする

### メンバー全員での確認とディスカッション　

1. このままでスプリントゴールを達成できるのかどうかの確認
    1. Burndownチャートの確認
1. ゴールを達成するためのディスカッション
    1. どうすればスプリントゴールを達成できるのか決める
    1. どうしても達成できそうではない場合、何を諦めるのかを決める


これらのことをデイリースクラムで話すことで、日々の業務の改善を進めていくことができます。


## 最後に
デイリースクラムは毎日行うことなので、気を抜くと形骸化しがちです。しかしスプリントゴールを達成していくための重要なプロセスでもあります。
日々行っているデイリースクラムが価値のあるものになっているかどうか、振り返るきっかけになれば幸いです。


## 参考文献

https://www.graat.co.jp/blogs/ckb01yf6djigp0869hm4fe235

https://scrumguides.org/docs/scrumguide/v2020/2020-Scrum-Guide-Japanese.pdf
【GCP】connected sheetsのリサーチまとめ

<table>
	<tr>
		<th>切り口</th>
		<th>内容</th>
		<th>URL</th>
	</tr>
	<tr>
		<th>データを扱う人が必要</th>
		<td>クエリに詳しくなくても大丈夫、SQLの知識はいらない</td>
		<td>https://www.youtube.com/watch?v=Hvk6iWD10QI</td>
	</tr>
	<tr>
		<th>アカウント</th>
		<td>ビジネス以上が必要</td>
		<td>https://www.youtube.com/watch?v=Hvk6iWD10QI</td>
	</tr>
	<tr>
		<th>why　解決できる課題</th>
		<td>増え続けるデータを活用できていない、クエリをあつける人がいない、BQとスプレッドシートを連携することで解決する</td>
		<td>https://www.youtube.com/watch?v=Hvk6iWD10QI</td>
	</tr>
	<tr>
		<th>スプレッドシートと連携した後の情報</th>
		<td>常にリアルタイムに連携し続ける</td>
		<td>https://www.youtube.com/watch?v=Hvk6iWD10QI</td>
	</tr>
	<tr>
		<th>他の人との共有</th>
		<td>スプレッドシートなので、普通の共有と同じように簡単にできる</td>
		<td>https://www.youtube.com/watch?v=Hvk6iWD10QI</td>
	</tr>
	<tr>
		<th>when BQとの連携にかかる工数</th>
		<td>5ステップくらいで簡単にできる</td>
		<td>https://www.youtube.com/watch?v=Hvk6iWD10QI</td>
	</tr>
	<tr>
		<th>ユースケース</th>
		<td>Pwcのケース</td>
		<td>https://www.youtube.com/watch?v=xCWUQtNc7e0</td>
	</tr>
	<tr>
		<th>使えそう</th>
		<td>1:26.2:55〜デモ</td>
		<td>https://www.youtube.com/watch?v=Hvk6iWD10QI</td>
	</tr>
	<tr>
		<th>how　使い方がいてある記事</th>
		<td>URL参照</td>
		<td>https://tech.rhythm-corp.com/access-bigquery-data-using-connected-sheets/</td>
	</tr>
	<tr>
		<th>who 必要な権限</th>
		<td>URLのコネクテッド シートを設定するに記載。操作に対してBQとスプレッドシートにどんな権限が必要かわかる</td>
		<td>https://support.google.com/a/answer/9604541?hl=ja</td>
	</tr>
	<tr>
		<th>how much　コスト</th>
		<td>Connected Sheets は 計算リソースとして BigQuery を使用しているため、計算するたびに BigQuery のクエリ発行代が課金されます。BigQuery のクエリ発行料金は公式ドキュメント参照。https://cloud.google.com/bigquery/pricing/?hl=ja#queries</td>
		<td>https://www.gixo.jp/blog/14370/</td>
	</tr>
	<tr>
		<th>where BQの作成場所</th>
		<td>スプレッドシートを操作するアカウントと同じ組織ではなくてもよさそうで、権限があれば操作できると思う。ただ、組織外のユーザーは、管理者が許可した場合にのみ、組織のスプレッドシートを操作できます。とのことなので、権限＋許可で可能なよう。</td>
		<td>https://support.google.com/a/answer/9604541?hl=ja#zippy=%2C%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%8C%E3%83%87%E3%83%BC%E3%82%BF%E3%82%BB%E3%83%83%E3%83%88%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%2Cbigquery-%E3%81%AE%E6%9B%B4%E6%96%B0%E3%81%8C%E3%82%B3%E3%83%8D%E3%82%AF%E3%83%86%E3%83%83%E3%83%89-%E3%82%B7%E3%83%BC%E3%83%88%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84</td>
	</tr>
</table>
ADX2ロボットでキュー連続再生

#はじめに

Craft 3.45.00から[ADX2ロボット機能](https://game.criware.jp/manual/adx2_tool/latest/criatom_tools_atomcraft_api.html)で、ダイアログUIが出せるようになったので、使ってみた備忘録。

[ADX2 ロボット機能でCSVの内容に従ってキューを再生する](https://qiita.com/tatmos/items/c8a9decd02eef86a845a)
に近い内容ですが、CSVとか余計なのなし版になります。

#用途
連続して音を確認したいときとかに使う。
コメントも表示するので、内容確認しながらといったことにも。

#動作

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96808/5f56e0d2-ada7-875f-8003-f06a007de4ca.png)

ツリーやリストで、キューを選択して、スクリプト実行すると
ダイアログに名前とコメントを表示しつつ再生してくれる。

前、次　ボタンで、再生キューを切り替えられます。


# スクリプト

``` MDS_PreviewSelectedCue.py
# --Description:[tatmos][Preview]選択キューを連続再生する
import sys
import cri.atomcraft.project as acproject
import cri.atomcraft.debug as acdebug
import cri.atomcraft.preview as acpreview
import cri.atomcraft.view as acview

# 選択しているキューを得る
cues = acproject.get_selected_objects("Cue")["data"]
if not cues :
    # キューが選択されていない場合は、選択項目の下のキューを探す
    # 選択項目を取得
    parent_objs = []
    for parent_type in ["CueSheet", "CueSheetFolder", "CueFolder"]:
        parent_objs.extend(acproject.get_selected_objects(parent_type)["data"])

    # 選択項目以下のキューを取得
    cues = []
    for parent_obj in parent_objs:
        cues.extend(acproject.find_objects(parent_obj, "Cue")["data"])

if not cues :
    acdebug.warning("再生するキューを選択してください.")
    sys.exit()

cueLength = len(cues)
count = 0
while count < cueLength or count > 0:
    cue = cues[count]
    cue_name = acproject.get_value(cue, "Name")["data"]
    comment = acproject.get_value(cue, "Comment")["data"]
    acdebug.log("{0}".format(cue_name))
    # キューを再生
    acpreview.start_playback_cue(cue)
    message = "再生 ({0}/{1})\n\n[名前]\n{2}  \n\n[コメント]\n{3}".format(count+1,cueLength,cue_name,comment)
    button_name_list = ["前", "次", "キャンセル"]
    result = acview.show_dialog(message, button_name_list)
    if result [ "data" ][ "button_name" ] == "キャンセル" or result [ "data" ][ "button_index" ] == "-1":
        sys.exit()
    if result [ "data" ][ "button_name" ] == "前":
        count-=1
        if count < 0:
            sys.exit()
    if result [ "data" ][ "button_name" ] == "次":
        count+=1
        if count == cueLength:
            sys.exit()
```
#UIつくといろいろできることが増える
AtomCraftの場合、pythonにあるUI系を使うと[落ちてしまう](https://qiita.com/tatmos/items/f33756ab5035b22190db)ようなのですが、
こういう安定した専用UIがつくならありがたいところ。
ダイアログにテキスト入力とかつくとさらにいろいろできるようになるなぁと期待しつつ。
Service Workerによるキャッシュ戦略の種類

この記事は、現在開催中のイベント「**フロントエンド強化月間 - 開発する上で知っておくべき知見を共有しよう**」の記事です。

https://qiita.com/official-events/b9ad63394fa2635dfca9


# はじめに

QiitaではService Workerを使い、リソースをクライアントにキャッシュさせることにより、パフォーマンスの向上、リクエスト数の軽減をしている。
Service Workerでキャッシュを扱うにあたって、いくつかのキャッシュ戦略があるため、それを紹介する。

# Service Workerについて

https://qiita.com/y_fujieda/items/f9e765ac9d89ba241154

https://developers.google.cn/web/fundamentals/primers/service-workers?hl=ja

https://developer.mozilla.org/ja/docs/Web/API/ServiceWorker

# Workbox

Qiitaでは、Service Workerファイルの生成に[Workbox](https://developers.google.com/web/tools/workbox)を使っている。
Workboxは、[gatsby-plugin-offline](https://www.gatsbyjs.com/plugins/gatsby-plugin-offline/)、[next-offline](https://github.com/hanford/next-offline)とかにも使われているライブラリで、Googleが開発している。

Service Workerを簡単に書くことができるのがウリ。

# キャッシュ戦略

## プリキャッシュ（事前キャッシュ）
Service Workerのinstall時に、キャッシュを行う。
一度キャッシュされると、次回以降そのリソースをロードする場合はキャッシュからロードされる。

Service Workerのinstall時にキャッシュがされるため、Service Workerが更新されないかぎり、キャッシュが変わることはない。そのため、デプロイ時などにService Workerを更新させる必要がある。

## ランタイムキャッシュ
プリキャッシュとは異なり、実際にリソースが必要になったタイミングでキャッシュを行うかの処理が行われる。
このタイミングでの処理には以下の種類がある。

https://developers.google.com/web/tools/workbox/modules/workbox-strategies

- Stale While Revalidate
- Cache First
- Network First
- Network Only（略）
- Cache Only（略）

### Stale While Revalidate
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/34bd3736-4330-f000-bb90-df505d2a5f64.png)

俗に言う`SWR`というやつ。
はじめにキャッシュを探し、キャッシュがある場合はキャッシュから返す。そして、裏側でネットワークから新しいレスポンスを受け取り、キャッシュに格納をしておく。

キャッシュから読み込むため、はやい。そして、裏側でキャッシュの更新を行っているため、キャッシュが古すぎるという問題が起きない。
ただし、リソース削除後に、ユーザー側に消す前のリソースがキャッシュされている場合、消されたリソースが見れてしまうなどの問題点もある。

### Cache First
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/a9b18f1d-72db-353d-80e9-37729b797e4a.png)

キャッシュがある場合は、キャッシュから返し、ネットワークにはアクセスをしない。もし、キャッシュがない場合は、ネットワークからリソースを受け取る。

一度キャッシュされると、次回以降はネットワークにはリクエストが飛ばないため、リクエスト数やデータ転送量を削減したいときには有効。
ただし、キャッシュを更新/削除しない限り、古いキャッシュを見続けるため、キャッシュの有効期限を設定したり、リソース名にハッシュをつけたりなどの工夫が必要である。

### Network First
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352836/a86345e9-00f1-8f89-d533-bfd6ad1a027d.png)

ネットワークに問題がない場合に、ネットワークからリソースを受け取る。受け取ったリソースはキャッシュに格納しておき毎回更新される。そして、ネットワークエラーなどが起きた際には、キャッシュからリソースを返す。

基本、ネットワークからリソースを受け取るため、リソースが古いなどは起こらない。ネットワークエラーが起きた際にキャッシュから返すことができるため、アプリケーションのオフライン化に活用できる。

# おわりに
ユーザー側にキャッシュが残ってしまい、事故を起こす可能性があるため、キャッシュ戦略は慎重にやりましょう！

# 引用
https://qiita.com/y_fujieda/items/f9e765ac9d89ba241154

https://developers.google.cn/web/fundamentals/primers/service-workers?hl=ja

https://developer.mozilla.org/ja/docs/Web/API/ServiceWorker

https://developers.google.com/web/tools/workbox/modules/workbox-strategies