OmniAICreator/Qiita-1.07M huggingface dataset:
【HTML】alt属性内の特殊記号・機種依存文字はどう書くのが正解？

## はじめに
コードレビューをしていて、こんなコードを見かけました。
`<img src="hoge" alt="Apple & Orange">`

「むむむ？:thinking:」
「`&`は特殊記号だからalt属性の中でも`&amp;`って書くべきだよね？:thinking:」

絶対的な自信を持って指摘ができなかったので
正解を知るためにいろいろ検証をしてみました！

検証端末はWindows10です。

## 検証
### 【1】ブラウザ上の表示
下記のようなテストHTMLを用意してブラウザで開きます。
故意に画像をリンク切れにして、altをテキストで表示させます。

```html:test.html
<section class="box">
  <h1>記号&#58;アンパサンド&amp;</h1>

  <h2>参照なし</h2>
  <img src="fruit.png" alt="Apple & Orange">

  <h2>文字実体参照</h2>
  <img src="fruit.png" alt="Apple &amp; Orange">

  <h2>数値文字参照 10進数</h2>
  <img src="fruit.png" alt="Apple &#38; Orange">

  <h2>数値文字参照 16進数</h2>
  <img src="fruit.png" alt="Apple &#x26; Orange">
</section>
```

ブラウザでの表示はこのようになりました。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1322014/6b34cee5-f19e-cb00-5366-045d4f0fee14.png)

どの書き方でも`&`として表示されています:ok_hand: 
Chrome, Firefox, Edge, IE11どれも同じ表示となりました。

### 【2】デベロッパーツールの表示
【1】で用意したHTMLを、ブラウザのデベロッパーツールで開いてaltの中身を表示します。
デベロッパーツールでの表示はこのようになりました。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1322014/8e17b1fc-2272-1d0b-d75f-952c45175927.png)

どの書き方でも`&`として表示されています:ok_hand: 
Chrome, Firefox, Edge, IE11どれも同じ表示となりました。

### 【3】Windows10 ナレーターでの読み上げ
alt属性をつける最大の理由はスクリーンリーダー（音声読み上げソフト）対応のため。
実際にスクリーンリーダーで読み上げをさせてみます。

Windows10に標準搭載されている「ナレーター」を使って確認。

`Apple & Orange`
読み上げ :mega:「あっぷる***あんど***おれんじ」

どの書き方でも「***あんど***」と読み上げてくれました！:ok_hand: 

しかし...

テストでいろいろな特殊記号・機種依存文字を読ませていたところ
`50μ`はどの書き方でも :mega:「ごじゅう」と読み上げられてしまい
「***まいくろ***」をスルーする結果に:no_good: 

（ナレーターは精度が低いって記事をチラ見したので「なるほど:joy:」と思いました）

### 【4】スクリーンリーダーでの読み上げ（NVDA）
違うスクリーンリーダーではどのような結果になるのか気になったので
「[NVDA](https://www.nvda.jp/)」というオープンソースのスクリーンリーダーを使って確認。

`Apple & Orange`
読み上げ :mega:「あっぷる***あんど***おれんじ」

`50μ`
読み上げ :mega:「ごじゅう***まいくろ***」

どの書き方でも「***あんど***」「***まいくろ***」を読み上げてくれました！！

## 結果
特殊記号・機種依存文字は、alt属性の中でも文字実体参照・数値文字参照で書くべき！！！
gitオリジナルコマンド作成【初心者向け】

#オリジナルコマンド作成

gitでの時短コマンドを作成しました。

/usr/bin など、適当にパスが通っているディレクトリで以下のファイルを作成します。

chmod +x で実行権限を与える必要があります。

```shell:gam

#!/bin/bash
if [[ $1 = "" ]]; then # 引数がない場合
  echo "Error : Requires an argument" 1>&2
  exit 2
else
 git add --all
 git commit -m"{$1}"
fi
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/6c9abaa9-c455-bfd1-08a0-0a9a1d6119c6.png)


```shell:gao

#!/bin/bash
if [[ $1 = "" ]]; then # 引数がない場合
  echo "Error : Requires an argument" 1>&2
  exit 2
else
git push origin $1
fi
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/765fd4cc-400a-e297-1ebc-6b2373566316.png)
AWS CloudWatch,CloudTrail,Config


#CloudWatch
定期的にAWSリソースの状態（メトリクス）を取得し、問題がある場合はそれを運用者に通知するサービス。
リリース後に安定した運用をすることで利用者の満足度を上げていくことが重要。

**・標準メトリクス**
ーLambda関数ごとのエラー回数などがあらかじめ定義されている
ーCPU、ディスク利用率、読み込みIOPSなど、一般的な目とリスクの多くが取得可能
ー５分間隔でのメトリスク取得
ー無料

**・カスタムメトリクス**
ー利用者が定義した値を渡す。独自のメトリクスのこと。標準では取得できないメトリスク。
ー１秒から６０秒でのリアルタイムでのメトリスク取得
ー有料





##CloudWatch Alarms
特定のメトリスクの閾値に応じてアラーム通知や自動アクションを実行可能。
モニタリング値を閾値にアラームを設定して、担当者に周知することが可能。
閾値のアラームで、SNSリソースを使用して、担当者にメール通知で知らせる。

・メトリクス
データポイントは、タイプスタンプと測定単位を保持
・名前空間
メトリスクのコンテナのこと
異なる名前空間のメトリスクを相互に切り離せる
・ディメンション
メトリスクを一意に識別する名前/値もペア

##CloudWatch Logs
AWSサービス及び顧客システムのログの監視・保存・アクセス提供。
ログのデータは、1日から永久保存まで設定可能。
S3へのログのエクスポートが可能。
**CloudWach Logsを利用するには、独自のエージェントをインストールする必要があり、このエージェントを介して、各EC2インスタンスのログを取集します。送信元のインスタンスにIAM権限を付与する必要があるので、IAMロールで設定する。**

###・CloudWatch Logs Insights
ークエリによってログを可視化・分析することが可能
ー３つのフィールドで生成される
ー時間軸に沿ってトレンドやパターンを可視化することができる
ー可視化されたグラフをダッシュボードに追加できる。


##CloudWatch Events
独自のトリガーと何かしらの後続アクションとの組み合わせを定義するサービス。
独自のトリガーを「イベントソース」。後続アイクションを「ターゲット」と呼ぶ。
CloudWatchはAWSリソースに対するイベントリをトリガーにアクションを実行。オペレーションの変更に応答し、応答メッセージを送信、機能のアクティブ化。変更、状態情報収集に修正アクションを実行。



##AutoScalingの組み合わせ
**ステップスケーリングポリシーは、CloudWatchメトリクスから得られる値（CPU使用率やSQSキューサイズなど）の閾値を超えて発せられるアラームに対して、値ベースでスケーリングアクションを個別設定できる機能です**。ステップスケーリングポリシーを作成するときは、アラーム超過のサイズに基づいてインスタンス数を動的にスケーリングする 1 つ以上のステップ調整値を指定します。各ステップ調整値は、次のように指定します。

**・メトリクス値の下限
・メトリクス値の上限
・スケーリング調整タイプに基づいてスケールする量**

CloudWatch は、CloudWatch アラームに関連付けられたメトリクスの統計に基づいて、メトリクスデータポイントを集計します。アラームを超過した場合、適切なスケーリングポリシーがトリガーされます。EC2 Auto Scaling は集計タイプを (raw メトリクスデータではなく) CloudWatch からの最新のメトリクスデータポイントに適用します。ステップ調整によって定義された上限と下限に対して、この集約メトリクス値を比較することにより、実行するステップ調整が決定されます。
スケーリングでは、単純にCPU使用率上がったら沢山インスタンスを追加してしまうといった実際のビジネスにフィットしないケースがあり、そこで、50%なら1台追加、60%なら2台追加、70%なら3台追加といった具合に、閾値を超えてアラームから報告される値によってスケールする台数を指定できるようになりました。


##RDSの拡張モニタリングを有効化
**DBインスタンス上のさまざまなプロセスまたはスレッドがCPUをどのように使用しているかを常時モニタリングするためには、RDSの拡張モニタリングを有効化することが必要です**。これにより、DBインスタンスのOSのリアルタイムメトリックスが確認できるようになります。 RDSコンソールを使用してDBインスタンスのメトリクスを表示でき、CloudWatch Logsからの拡張モニタリングを利用することができます。**デフォルトでは、拡張モニタリングメトリクスは30日間CloudWatch Logsに保存されます。**料金は、CloudWatch Logsの無料枠を超えた拡張モニタリングに対してのみかかる。

##AWS Cost Explorer
利用しているEC2インスタンスなどのリソースの最適な利用に関する推奨事項を把握することができます。これにより、アカウントやリージョンでアイドル状態のインスタンスや使用されていないインスタンスを特定することができます。推奨事項を作成するために、AWS では過去の EC2 リソースの使用状況、CloudWatch メトリクス、過去の予約履歴を分析してコストを削減できる可能性があるか判断します。




#CloudTrail
**AWSリソースの作成や、マネジメントコンソールへのログインなどの操作を記録するサービス。誰が・いつ・どのような操作をしたか、といった監査ログを記録しておくことで重大な障害を起こさないようにする。**
取得できるイベントは下記の２種類。管理イベントの取得はデフォルトで有効。データイベントの取得はデフォルトで無効となっている。TrailとLogsを連携することで、ユーザーの不正な操作を早期に発見できる。

・ルートアカウント、IAMユーザーのオペレーションをトラッキング
・Trailログファイルは暗号化されてS3に保存
・KMSによる暗号化もサポート
**・過去90日分のログをマネジメントコンソール上で確認できる。90日以上の情報を保持したい場合は、S3に証跡を残す設定が可能。**

**・管理イベント
マネジメントコンソールのログイン、EC2インスタンスの作成、S3パケットの作成など。過去９０日間のログをマネージメントコンソール上で確認できる。**
デフォルトで有効。

**・データイベント
S3パケット上のデータ操作、Lambda関数の実行など**
デフォルトで無効。

###・WatchとTrailの連携
二つを連携することで、不正操作を早期発見できる。
ユーザーがもし不正操作を行った場合は、自動で検知する。

#Config
AWSリソースのレポジトリ情報からリソース変更履歴や構成変更を管理するサービス。AWSリソースの設定を評価、監査、審査できる。Config では、AWS リソースの設定が継続的にモニタリングおよび記録され、望まれる設定に対する記録された設定の評価を自動的に実行できます。

・定期的に構成情報のスナップショットをS3に保存
・構成情報に基づきシステム構成があるべき状態になっているか評価
・評価基準にはAWSルールまたは独自ルールを適用
PHPで拡張子からMIMEタイプを取得する

mime_content_typeがローカルファイルのみ有効だったため、何か方法が無いかと公式を見ていたら
ユーザ寄稿にこんなコードがありました。

>
```php
Fast generation of uptodate mime types:
>
<?php
define('APACHE_MIME_TYPES_URL','http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types');
>
function generateUpToDateMimeArray($url){
    $s=array();
    foreach(@explode("\n",@file_get_contents($url))as $x)
        if(isset($x[0])&&$x[0]!=='#'&&preg_match_all('#([^\s]+)#',$x,$out)&&isset($out[1])&&($c=count($out[1]))>1)
            for($i=1;$i<$c;$i++)
                $s[]='&nbsp;&nbsp;&nbsp;\''.$out[1][$i].'\' => \''.$out[1][0].'\'';
    return @sort($s)?'$mime_types = array(<br />'.implode($s,',<br />').'<br />);':false;
}
>
echo
generateUpToDateMimeArray(APACHE_MIME_TYPES_URL);
?>
>
Output:
$mime_types = array(
   '123' => 'application/vnd.lotus-1-2-3',
   '3dml' => 'text/vnd.in3d.3dml',
   '3g2' => 'video/3gpp2',
   '3gp' => 'video/3gpp',
   '7z' => 'application/x-7z-compressed',
   'aab' => 'application/x-authorware-bin',
   'aac' => 'audio/x-aac',
   'aam' => 'application/x-authorware-map',
   'aas' => 'application/x-authorware-seg',
...
>
Enjoy.
```

何をやってるかというと、
APACHE_MIME_TYPES_URLには以下のURLが定義されており

http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types

中身はこのようにMIMEタイプと拡張子の対応表になってます。

>
```
application/andrew-inset			ez
# application/applefile
application/applixware				aw
# application/atf
# application/atfx
application/atom+xml				atom
application/atomcat+xml				atomcat
# application/atomdeleted+xml
```

要はこれを正規表現で拡張子をキーとした連想配列にして使えば早いよって事ですね。

## 連想配列作成例
```php
    function getMimeTypes()
    {
        // 表の取得
        $list = file_get_contents('http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types');

        $mime_types = [];
        foreach (explode("\n", $list) as $row) {
            // 先頭が#のものはMIMEタイプが未登録の為スキップ
            if (str_contains($row, '#')) {
                continue;
            }
            // 正規表現でMIMEタイプと拡張子を取得
            if (preg_match('/^(\S+)\s+(.+)$/u', $row, $matches)) {
                // 拡張子はスペース区切りで複数存在するときもあるので分割(jpg, jpegとか)
                foreach (explode(" ", $matches[2]) as $extension) {
                    // 配列に拡張子をキーとして代入
                    $mime_types[$extension] = $matches[1];
                }
            }
        }
        return $mime_types;
    }
```

```php
$mime_types = getMimeTypes();
echo $mime_types[pathinfo('http://example.com/hogehoge.jpg', PATHINFO_EXTENSION)];
// "image/jpeg"
```

今回WordpressからContentfulへのブログ移行の際に画像も移行する必要があったためこのような対応をしました。
移行プログラムがWordpressのサーバ内にあれば、mime_content_typeで事足りたんですが。
第三者がアップロードしたファイルのバリデーションには使わないこと。
GIF画像の簡単な作り方

アプリの紹介やqiitaの投稿などで実際に操作している画面があれば伝わりやすいことは往々にしてあると思います。
そのような際にGIF画像は便利なので、簡単な作成方法を紹介します。

###作業内容
流れとしては以下の形となります。
①PCのキャプチャ映像の撮影
②映像をGIF画像に変換

①PCのキャプチャ映像の撮影
macを使っている場合は、「command+shift+5」にてスクリーンショットのツールバーを使用して撮影できます。
コマンドを入力すると以下のようなタブが出てくるので、「画面全体を収録」を押すと撮影がスタートします。そして再度「command+shift+5」にて立ち上げて停止を押せば撮影終了となります。
![スクリーンショット 2021-05-23 21.26.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1340120/4f290bfa-3b2d-0078-1c57-60ca2b86f96d.png)

windows10の場合は実際に試していませんが、「windowsキー+G」にて撮影できるようです。
参照URL:https://dc.wondershare.jp/screen-recorder/how-to-record-windows10.html?gclid=CjwKCAjw-qeFBhAsEiwA2G7NlwpnEsLMz2BSB5PhrUN-ZGIxiaNcqzGSP3aTnW6mDXBB7wnCEgkiLBoCZ20QAvD_BwE


②映像をGIF画像に変換
先ほど撮影した映像を以下サイトにて、ファイル選択して画像サイズの選択などすればアッという間にGIF画像が作成できてしまいます。
https://syncer.jp/gif-maker

![スクリーンショット 2021-05-23 21.43.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1340120/e0a6b254-16d8-5e0a-4b2d-3e2a9ce9fed6.png)


他にもより簡単だったり、やりやすい方法があるのかもしれません。
そのような場合は教えていただけると幸いです！
debianのvisudoコマンドのエディタをvimにする

# 概要
debianを使ってみてまず驚いたのが、デフォルトでのエディタがnanoだったことだ。
使い慣れているvimにしたいので、その変更してみた。
ちなみにだけど、政治、宗教、野球、Linuxのテキストエディタの話は<b>最悪殴り合いに発展したりしなかったり</b>するので変に煽ったりするのはやめましょう。

個人的にはVim以外ありえないと思う。

# 変更手順
まずvimが入っていなければ、`apt-get install vim`でインストール。

```
$ sudo -s
# update-alternatives --config editor
alternative editor (/usr/bin/editor を提供) には 3 個の選択肢があります。

  選択肢    パス              優先度  状態
------------------------------------------------------------
* 0            /bin/nano            40        自動モード
  1            /bin/nano            40        手動モード
  2            /usr/bin/vim.basic   30        手動モード
  3            /usr/bin/vim.tiny    10        手動モード

現在の選択 [*] を保持するには Enter、さもなければ選択肢の番号のキーを押してください:
```
ここで2を選択。
設定したら、もういちど、`update-alternatives --config editor`をしてカーソルが2を示しているか確認。
作業メモ Amazon linux2にpyenvでpython3.9.0をインストールする

## 手順
```bash
# 使う諸々インストール
sudo yum -y install   bzip2   bzip2-devel   gcc   git   libffi-devel   make   openssl   openssl-devel   readline   readline-devel   sqlite   sqlite-devel   zlib-devel

# pyenvインストール
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash

# bashrc書き換え
cat << 'EOS' >> ~/.bashrc
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
EOS

source ~/.bashrc

pyenv install 3.9.0

pyenv global 3.9.0

python -V
# Python 3.9.0
```

## 雑記
```bash
# インストールできるpython一覧確認
pyenv install --list

# amazon-linux-extras関係のエラーが出たら
ln -s /lib/python2.7/site-packages/amazon_linux_extras ~/.pyenv/versions/3.９.0/lib/python3.7/site-packages/
```

## 参考
https://sig9.hatenablog.com/entry/2019/10/28/000000
JSでクリップボードにコピーするボタンの実装（ClipboardAPI＋clipboardData）

[document.execCommand("copy");](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand)  は既に非推奨になっている。
代替手段としてモダンブラウザではHTML5のClipboard APIを、IE対応ではwindow.clipboardDataを使う。

## Clipboard APIを使う（IE以外のブラウザ）
[navigator.clipboard.writeText()](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText)


```javascript
navigator.clipboard.writeText("text").then(function() {
  // clipboard successfully set
}, function() {
  // clipboard write failed
})
```

[IE以外は大体対応している。](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText#browser_compatibility)
Firefoxはユーザーイベントによってしか発火しないらしい。
今回はボタンクリックで発火させるので大丈夫。

## window.clipboardDataを使う（IE）
```javascript
window.clipboardData.setData('Text', 'some text here'); // copy
```
## 組み合わせる

```javascript
const copy = () => {

  // touchイベント対応
  const clickEventType = ((window.ontouchstart !== null) ? 'click' : 'touchend')
  // link指定
  const link = 'https://qiita.com/'
  // btn取得 .c-btn--copy
  const _btns = document.getElementsByClassName('c-btn--copy')
  for (let i = 0; i < _btns.length; i++) {
    const btn = _btns[i]
    btn.addEventListener(clickEventType, function () {
      if(navigator.clipboard !== undefined) {
        // Clipboard APIが使用可能な場合
        window.clipboardData.setData('Text', link);
      }else{
        // 使用不可な場合
        navigator.clipboard.writeText(link).then(e => {
          console.log('copied ', link)
        })
      }
    })
  }
}
```
DartのFutureを理解する

最近Flutterを書いているので調べたことについてまとめていきます。
#Futureはなぜ存在するのか？
Dartは``シングルスレッド``
CPUで行う処理は、スレッドという単位で分割します。シングルスレッドは、処理を１つずつ順番に行う。
対義語のマルチスレッドは複数の処理を同時に行う。単純に、全部マルチスレッドにした方が処理早そう？と思ったけどそうじゃないみたい。マルチスレッドには問題がある。複数のスレッドが1つのデータに対して同時にアクセスすることにより、知らない間にデータの整合性を破壊してしまったりすることである。

話をシングルスレッドに戻す。
シングルスレッドにおいて複数の処理は``イベントループ``で直列に処理される。
イベントループとは、処理が一つ一つイベントキューに格納されて、処理が終わったら次のタスクを実行します。
ちょっと待って！タスクの処理時間が長いとユーザーはずっと画面の前で待っとくの？
そんな、、、、、、、お悩みのあなたに朗報です、

__Futureクラス__が全て解決してくれます！

#Futureの解説
まずは、コードを見ていきましょう

```Dart
// 非同期処理
void Func(){
  print("Hello 1st World!");
  //1秒かかる処理を仮定
  Future<String> future = Future.delayed(Duration(seconds: 1), () => "Hello 2nd World!");
  future.then((message) => print(message));
  print("Hello 3rd World!");
}
main() {
  print("----- 非同期処理 -----");
  Func();
}

//実行結果
----- 非同期処理 -----
Hello 1st World!
Hello 3rd World!
Hello 2nd World!
```
関数の書き方などはここで説明しません

```Dart
  Future<String> future = Future.delayed(
                          Duration(seconds: 1),
                          () => "Hello 2nd World!");
  future.then((message) => print(message));
```
futureは``Future<String>クラス``になります。1秒間の処理を想定しているので,Durationの引数のsecondsは1になります。
Future.delayedの第二引数は1秒後に実行する処理を代入します。
1秒後に実行した処理が完了した後に何か処理をしたいときはthenメソッドを使います。
thenの引数に無名関数を代入することによって、処理結果を受け取ることができます。
今回() => "Hello 2nd World!"という処理の結果を受け取るので,messageには"Hello 2nd World!"が代入されます。そしてprint(message)で表示しているという流れになります。

少しでもFutureの理解を手助けできれば幸いです！
お疲れ様でした。
GitLab RunnerをEKSで実行

# はじめに
CI/CDのパイプラインをGitLabRunnerのKubernetes Executor（EKS）を使って実行する方法になります。
GitLab RunnerをEKSにインストールをします。
※EKSクラスターは構築済みの前提です。

# これを利用する背景や課題だったこと、メリットなど
以前はec2上にkopsを構築、スケールアウトにはSpotinstを利用して、Runnerを利用していましたが、メンテナンスのコスト高、安定感に欠けていました。

今回はAWSの完全マネージド（EKS＋Autoscale+Spotinstance）にすることによりメンテナンスの容易さ＋安定化することが目的となります。

また、スポットインスタンスのワークロードでEC2 Auto Scalingを利用することにより、入札不要モデル（上限価格は設定不要、デフォルト値はオンデマンド価格）で、スポットインスタンスでも中断回数を大きく減らすことが可能で、パイプラインの中断を防ぎます。

# 作業の流れ

1. k8s上にrunnerのnamespaceを作成
1. GitLab runnerのchartが利用できるhelm repositoryを追加する
1. Runnerの設定ファイルの取得と反映
1. Runnerのinstall


# 1. k8s上にRunnerのnamespaceを作成
kubectlでEKSのクラスターに接続できる状態で、以下を実行します。

```
$ kubectl create namespace gitlab
namespace/gitlab created
```

# 2. GitLab Runnerのchartが利用できるhelm repositoryを追加する

gitlabのhelmリポジトリ追加します。

```
$ helm repo add gitlab https://charts.gitlab.io
```
追加されたか確認します。

```
$ helm repo list
NAME    URL
gitlab  https://charts.gitlab.io
```
# 3.Runnerの設定ファイルの取得と反映
以下を実行してvalues.yamlを作成します。
適宜values.yamlを修正します。

```
$ helm inspect values gitlab/gitlab-runner > values.yaml
$ vim values.yaml
```

以下、修正する必要がありそうな部分です。
Runnerが使うCPUやメモリもこのファイルで設定できます。

``` bash
#GitLabのURL（Runnerを動かしているサーバのURL）
gitlabUrl:

#Gitlabから取得したトークン
runnerRegistrationToken:

#Runnerを動かす最大コンテナ数
concurrent:

#RBACの作成を有効
rbac:
  create: true
``` 

# 4.Runnerのinstall
上記で作成した設定パラメータのyaml（values.yaml）を使ってhelmでinstallできます。
更新や削除する場合は以下の※を実行します。

```
$ helm install -n gitlab gitlab-runner -f values.yaml gitlab/gitlab-runner
```
※パラメータを書き換えで更新する場合（既に上記の手順でインストールしている場合は以下で上書き）

```
$ helm upgrade -n gitlab gitlab-runner -f values.yaml gitlab/gitlab-runner
```

※runnerの削除する場合
既にインストールされている場合は以下で削除

```
$ helm delete -n gitlab gitlab-runner
```

installされているか確認します。

```
$ helm list -n gitlab
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
gitlab-runner   gitlab          1               2020-07-21 08:22:00.642993 +0900 JST    deployed        gitlab-runner-0.18.1    13.1.1     
```

EKS上に展開されているか確認します。

```
$ kubectl get all -n gitlab
NAME                                               READY   STATUS    RESTARTS   AGE
pod/gitlab-runner-gitlab-runner-7bdc85b647-tggk9   1/1     Running   0          159m

NAME                                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/gitlab-runner-gitlab-runner   1/1     1            1           159m

NAME                                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/gitlab-runner-gitlab-runner-7bdc85b647   1         1         1       159m
```

# 参考
https://docs.gitlab.com/runner/install/kubernetes.html
https://www.creationline.com/lab/gitlab/33461
VR-HMDの動作検証に使うラップトップPC選定の注意点

# 概要
VR-HMDを使ったコンテンツのお出かけデモ用、あるいは部屋の環境的にデスクトップPCを買えない場合にVR用のラップトップPCを購入する人もいるかと思います。
その際にCPUとGPUのスペックだけを見て決めるとHMDとの相性問題が発生してメチャクチャつらいことになるので、注意喚起です。

結論を先に書くと

- 購入する予定のラップトップの実機を触れる場に行く
- 実機上でPhysX設定を開いて、DisplayPort1.3以上がdGPUから結線されていることを確認してから購入する

ことで、トラブルを回避できます。


以下、簡単のために

- CPU内蔵GPUをiGPU(Intel内蔵グラフィック的なやつ)
- 独立したGPUをdGPU(Geforce RTXxxxx的なやつ)

と表記します。

# VR-HMDの映像出力ポート制約
VR-HMDは「低遅延」「高リフレッシュレート」「高解像度」であるほど良い物です。  
よって各HMDメーカともにどんどんスペックを向上させています。

その結果、DisplayPortは1.3以降を要求するHMDも登場しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/fed2f774-02ae-6f6c-12d4-eaae3779bff7.png)
↑出典: https://vr-maniacs.com/entry/extend-display-port/

HDMIから変換してDP1.3を出力するのは地獄（まともに動く製品を知りません）なので、VR用途でラップトップPCを買う時は **DisplayPort1.3以上（将来を見越すならDisplayPort1.4以上、mini DisplayPortも可）のディスプレイ出力端子を備えた物を買いましょう。**

# 待って！それだけじゃないんだ！
上で書いた端子情報だけだと罠があります。

**最新VR-HMDはdGPU直結のDisplayPortでしか動かない** のです。

たとえばHDMI出力とDisplayPort出力が載っているラップトップPCでも

- HDMI(これがdGPU直結)
- DisplayPort(なぜかこれがiGPU直結)

となっている場合、**ValveIndexなどのDisplayPort接続を要求する最新VR-HMDが認識失敗します。**

特に罠ポイントだと思うのが**ThunderBolt4のTypeC経由でDisplayPort出力ができる、と書いてあるものはほぼ確実にiGPU直結です。**

# どうやって映像出力の結線情報を確認するの？
NVIDIAコントロールパネルのPhysX構成の設定、の欄で確認できます。

残念ながらメーカーは結線状況を公開してくれていない場合がほとんどなので、**店頭で実機を開いて確認しましょう。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/987d08cd-127a-34e1-64ca-0992d7cfa5d2.png)

例えばこのモデルの場合は「dGPUはHDMIポートにしか繋がっていないのでValveIndexが動作しない」と考えられます。

デスクトップ画面で右クリック→NVIDIAコントロールパネル→3D設定→Surround、PhysXの設定
で確認できます。


# その他補足
Oculus QuestなどをUSB接続してPCVRとして使う場合は、こういった映像出力端子結線ガチャを無視できます。

絶対にハイエンドなPCVR向けHMDを買わない、OculusQuest系と添い遂げる！という決意があればこの文章にあったことは忘れて大丈夫です。

あと、そもそもVR ReadyなGPUを積んでいないラップトップを買ってしまうという失敗パターン(CAD用のQuadroモデルとか、ローエンドGPU搭載とか)もありますが、そういった不安がある人は事前に店員さんに相談しましょうね！
dotenvが効かないundefined/.env見つからない

## 結論
パスが間違っている可能性が高い。

## 前提

Nodejsを活用し、環境変数を取得する際には、
dotenvを使用すると良い。

```js
// app.js
require('dotenv').config();
const env = process.env;
console.log(env.USERNAME);
```

```js
// .env
USERNAME=hogehoge
```

しかし、上記のように実施したとして、しか表示されない場合、
何がおかしいのか悩む。

```console
> node ./src/app.js
undefined
```

## デバッグ

```js
require('dotenv').config();
console.log(require('dotenv').config());
console.log(process.env.USERNAME);
```

`requireしているところをconsole.log()`でくくるとエラーが出る。

```console
error: Error: ENOENT: no such file or directory, open '/Users/~ディレクトリ階層~/プロジェクト名/.env'
```
プロジェクトのトップに.envファイルがありませんということ。

### 修正前(./src/.env)
```console
❯ tree -L 2 -a
.
├── LICENSE
├── README.md
├── package.json
├── src
│   ├── .env
│   ├── app.js
│   └── memo
└── yarn.lock
```

### 修正後(./.env)
```console
❯ tree -L 2 -a
.
├── LICENSE
├── README.md
├── package.json
├── .env
├── src
│   ├── .env
│   ├── app.js
│   └── memo
└── yarn.lock
```

## 言い訳
vscode上で作業をしているとき、
ファイルをコマンドでなく、GUIでババっと作った。
そうした際に、`.env`はルートディレクトリに作るという無意識が働いてしまっていた。
なので、ルートディレクトリにある前提でいろいろな確認作業をしてしまい、
わからなくなった。

今回のことで、デバッグ手法が一つわかったのでとても勉強になった。

## 参考
[How do I setup the dotenv file in Node.js?](https://stackoverflow.com/questions/26973484/how-do-i-setup-the-dotenv-file-in-node-js)
ABCI 初めてのJupyter起動からWeights&Biasesを使用して複数GPUでのハイパラ探索まで

研究室で[ABCI](https://abci.ai/ja/)を使用することになり, 使い方を調べてまとめた
今後も追記予定
実験管理用・ハイパラ探索用に[Weights&Biases](https://wandb.ai/site)使ってみたが凄く良かった
Sweep機能を使ってハイパラ探索させるCodeは
[Hyperparameter search with wandb sweep using multiple-gpus](https://github.com/kenoharada/abci_tutorial/tree/main/multi_gpu)
こちらにあります, 探索したいハイパーパラメーターを設定したらあとはひたすら探索してくれて, かつ結果をスマホからも確認できるのでとてもオススメです
## レポジトリ
https://github.com/kenoharada/abci_tutorial
## コンテンツ
### Use jupyter in interactive job
[Jupyter tutorial](https://github.com/kenoharada/abci_tutorial/blob/main/jupyter_tutorial.md)
### Batch job
[Train CNN on MNIST with batch job](https://github.com/kenoharada/abci_tutorial/blob/main/batch_job_tutorial.md)
### Monitor experiment results
[Monitor batch job's experiment results using wandb](https://github.com/kenoharada/abci_tutorial/tree/main/experiment)
### Multiple-GPU
[Hyperparameter search with wandb sweep using multiple-gpus](https://github.com/kenoharada/abci_tutorial/tree/main/multi_gpu)
RailsコンソールでパスのURLを確認する

## はじめに
Railsで〇〇_pathと指定することがあるかと思います。
ではそのパスはどのURLに対応しているのかを確認する方法はどうすればいいのかをまとめます。

$\mathbb{それでははじめていきましょう}$

## rails routesでルートを確認する

```console:コンソール
% rails routes　
```
`rails routes`でパスを確認します。
<br>
例えばこんな結果が返ってきたとします。

```console:結果
Prefix           Verb   URI Pattern                          Controller#Action                                                                 
root             GET    /                                    items#index
items            GET    /items(.:format)                     items#index
                 POST   /items(.:format)                     items#create
new_item         GET    /items/new(.:format)                 items#new
edit_item        GET    /items/:id/edit(.:format)            items#edit
item             GET    /items/:id(.:format)                 items#show
                 PATCH  /items/:id(.:format)                 items#update
                 PUT    /items/:id(.:format)                 items#update
                 DELETE /items/:id(.:format)                 items#destroy
```

色々とパスが書かれていますがまずは`root_path`のURLを見ていきましょう。

## パスの確認方法
pathの確認には

```console:パスの確認
pry(main)> app.Prefixに書かれたパス名.path
```
とします。
ルートパスのURLを確認します。

```
pry(main)> app.root_path
```
と入力すると

```
pry(main)> app.root_path
=> "/"
```
と表示されます。

`items.path`のURLも確認してみましょう。

```
pry(main)> app.items_path
=> "/items"
```

表示されました。

パスはこのURLを表しているだけなので、フォームなどで

```erb:rails_form
<%= form_with url: '/items/new' do |f| %>
```

とすることもできますってことですね。


## URLを表示させる
そしてURL(http://○○○.com)などと表示させるには

```console:URL確認
pry(main)> app.Prefixに書かれたパス名.url
```
とします。
root_pathのURLを表示させてみます。

```
pry(main)> app.root_url
```

```
pry(main)> app.root_url
=> "http://www.example.com/"
```

表示できました。
<br>
<br>

> 参考サイト
- [Qiita  Rails | rails console で ルーティングのパスを確認する](https://qiita.com/YumaInaura/items/72acead480dd4478ef56)
- [Qiita　Rails console で Rails routes を詳しく確認する方法](https://qiita.com/mm36/items/7703814ff5c5e8632197)
- [Qiita　Rails でリンクパスを生成する方法色々・・とRails console で 生成される path を確認したい時](https://qiita.com/mm36/items/f266977e12df9d1dc548)
RaspberriPi pico ADC

# はじめに
RaspberryPiにはADC(Analog-to-digital converter)がありませんでしたが、picoには4chありました。
GP26(31pin)、GP27(32pin)、GP28(34pin)と内部温度センサです。

今回はGP26(31pin)にポテンショメータを接続して、電圧を表示したいと思います。

# 使用パーツ
秋月さんで販売しているポテンショメータを使用しました
https://akizukidenshi.com/catalog/g/gP-15813/

# 配線
ポテンショメータの裏から見て、
左側：VDDをpicoの36pin(3.3V out)へ接続
中央：Voutをpicoの31pin(ADC0)へ接続
右側：GNDをpicoの33pin(AGND)へ接続

# ソース
```adc_test.c
#include <stdio.h>
#include <pico/stdlib.h>
#include "hardware/adc.h"				// ADC使用

void main(void)
{
	unsigned int adcValue;

	stdio_init_all();
	adc_init();
	adc_gpio_init(26); 
	adc_select_input(0);				// ADC0を使用

	while(1){
		adcValue = adc_read();			// ADC取得
		printf("adcValue:%d\n", adcValue);
		sleep_ms(500);
	}
}
```

#CMakeLists.txtの書き方(printfを表示できるようにする)
```CMakeLists.txt
cmake_minimum_required(VERSION 3.12)

include(pico_sdk_import.cmake)

project(adc)

pico_sdk_init()

add_executable(adc_test
	adc_test.c
)

target_link_libraries(
	adc_test
	pico_stdlib
	hardware_adc
)

# create map/bin/hex file etc.
pico_add_extra_outputs(adc_test)

# enable usb output, disable uart output
pico_enable_stdio_usb(adc_test 1)
pico_enable_stdio_uart(adc_test 0)
```

# printfの表示方法
adc_test.uf2をpicoにdrag-and-dropします。
Ubuntuのターミナルを開き、「sudo gtkterm」と入力します。
パスワードを聞かれるので、パスワードを入力します。
「Configration→port」を選択します。
初期値のポートが「/dev/ttyS0」になっていますので、変更します。
私の環境では「/dev/ttyACM0」でした。
その他の設定はデフォルトのままです。
GTKTermの設定が終わると、500ms毎に、
adcValue:xxxx
と表示されると思います。

この時、ポテンショメータを動かし電圧が変化することが確認できます。

# GTKtermのインストール方法

```
sudo apt install gtkterm
```
IE設定をいじらずに「このファイルの作成者を確認できません」を消す

---------------------------------------
※注意
この方法はローカルに実行プログラムがあり，共有フォルダにスクリプトファイルがある場合のみ有効です(VBScriptやJScriptなど。もしかしたらPowerShellもいけるかな？)。
仕組み的に共有フォルダ上のexe等の実行可能ファイルには対応できません。

---------------------------------------

共有フォルダ上に置いた自作のVBScriptをダブルクリックで実行しようとすると，「このファイルの作成者を確認できません。このファイルを開きますか？」というエラーが出てしまうことがある。

ツールを起動するたびにこの画面が出るので，せっかく処理を自動化したにもかかわらず無駄なひと手間があり，嬉しさがすこし減ってしまう。
他の人に使ってもらう場合にも余計な不安を与えるので，できれば表示したくないものである。

ネットでこのポップアップを消す手段を検索すると，IEの設定からローカルイントラネットのゾーン追加をする方法ばかりが出てくるのだが，実際，根本的な解決はそれしかないようである。
ちなみにその方法だと，共有フォルダに置いてある任意の実行可能ファイルを注意喚起なしで実行するようになるので，セキュリティ的にはあまりよろしくない。

自分の場合，その設定画面がシステム管理者により無効化されていたのでどうしようもなかった。
しかしながら，このポップアップを非表示にする手段を見つけたので，記録しておく。

<h1>ショートカットからWSHを指定して実行する</h1>
ローカル環境にショートカットを作成。
ショートカットのリンク先を以下のようにする。

```
C:\WINDOWS\System32\WScript.exe[CScript.exe] 共有フォルダ\hoge.vbs
(パスに空白が含まれている場合)
C:\WINDOWS\System32\WScript.exe[CScript.exe] ”共有フォルダ\hoge fuga\piyo.vbs”
```

WScriptにするかCScriptにするかは，処理の内容によって適宜選んでほしい。

実際に実行されるプログラムはローカルの<code>C:\WINDOWS\System32\WScript.exe[CScript.exe]</code>
であると判断されるので，例のポップアップは出なくなる。
Shopifyのデータをスプレッドシートに手軽にインポートする方法

ShopifyのデータをGoogleスプレッドシートにインポートする方法について調べていたのですが、日本語での情報が少なかったのでまとめます。
スプレッドシート上からできるので、わりかし手軽にできます。


1.Shopifyの画面の『アプリ管理』をクリックします。
![step1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/d010f162-aed7-a4d7-5c18-98dde68b9337.png)

2.下までスクロールし、『プライベートアプリを管理する』をクリック。
アカウントによってはプライベートアプリを見る権限が与えられていないと表示されません。
![step2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/139e3d2f-f87f-3662-a45b-fdda4bf36808.png)

3.『新しいプライベートアプリを作成する』から、作成し『プライベートアプリ名』と『緊急連絡用開発者メール』を更新します。
![step3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/b3de4632-dafe-4fd9-3a65-e6b573546ea7.png)

4.『非アクティブなAdmin API権限を表示する』をクリックし、『注文管理』を『読み取りアクセス』にし、保存します。
注文管理を『読み取りアクセス』にすることで必要な情報を取ることができます。
![step4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/4f4060a3-188f-3003-9ce6-c8e81f223274.png)

Shopifyの情報をGoogleスプレッドシートに落とすには、以下の情報が必要です。
・APIキー
・パスワード
![step5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/28a71fd7-e3e9-446d-f92f-17112ef0454c.png)

5.次に、Googleスプレッドシートを開きます。
『アドオン』をクリックし、『アドオンを取得』を選択します。
![steo6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/3376bea5-e2de-bd90-4160-536d1e7a2f3c.png)

6.『Coupler.io』と検索し、インストールします。

7.インストールが完了後に『アドオン』→『Coupler.io』→『Open dashboard』に移動し、右側のサイドバーにの『+ADD IMPORTER』をクリックします。
![step6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/863659ff-1bff-ae00-d5e5-9d5d8220275e.png)

8.『Shopify』を探してクリックします。
![step8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/0595bbf2-3b8b-89f5-207b-bf0400c52102.png)

ここまでいけば、もう少しです。

9.『Title』で名前をつけ、『Source』を設定します。
『Shop name』は{shop_name}.myshopify.comの形式で入力します。
『API key』には、先ほど取得したAPIキーを、『API password』にはパスワードを入力します。
『Data entity』では、

・Customers
・Products
・Orders
・Orders withlineitems 

の中から取りたい情報を選ぶことができます。
それに応じて4で『読み取りアクセス』にする情報が変わってきます。
今回は『Orders』を選択します。
また、『Show advanced』から、日付などのより詳細に絞ることができます。
![step9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/f1e331a6-ff71-be56-5379-b0ddb423e338.png)

10.『Desrination』で、『Desrination account』で接続するアカウントを選択し、『Sheet Name』でインポートするシートの名前を入力します。
![step10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/f0fcccaa-cc2f-c738-51a3-a9de14cddca6.png)

11.『Settings』の『Automatic data refresh』をオンにすることでデータを自動更新する頻度を指定できます。
![step11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1198454/7d3dab0d-0fbc-88d8-8bf6-83a2fd85e3d3.png)

あとは、『SAVE & RUN』することで10で入力したシートにShopifyの情報がインポートされます。

余分な情報も取れてしまいますが、一度接続してしまえば割と楽にShopifyの情報をスプレッドシートにインポートすることができます。
インポート用のシートと整理する用のシートを分けるとより使いやすくなるかと思います。

Coupler.ioは無料で使えますが、無料プランだと50,000セル分までしかインポートできません。
上限に達したらCoupler.ioから接続したアカウント宛にメールが来るので、余分な情報を削除したりプランを上げたりすることで再度使えます。

ぜひ活用してみてください。
datepicker, timepickerを使うほどじゃない 1~31の配列, 00:00~23:00の配列作成

# セレクトボックスで頑張る人向け
## 突貫メソッド2つ
### 1 ~ 31 の配列作成 [1, 2, 3,...31]
```javascript:31days
	const arrayDays = () => {
		const days = [];
		for (let i = 1; i <= 31; i += 1) {
			days.push(i);
		}
		return days;
	};
```

### 00:00 ~ 23:00 の配列作成 [00:00, 01:00, 02:00,...23:00]
```javascript:24times
	const arrayTimes = () => {
		const times = [];
		for (let i = 0; i <= 23; i += 1) {
			times.push(`${i.toString().padStart(2, '0')}:00`);
		}
		return times;
	};
```
AtCoder の問題を解くということ

# はじめに

少し前の話しになりますが、 [sendai.rb](https://sendairb.connpass.com/)で、[AtCoder](https://atcoder.jp/)の問題をRubyで解く機会がありました。

このときの経験を元に、AtCoderの問題を（Rubyで）解くことについて、感じたことを書いてみます。

## Atcoderの問題を解くことで、検索力を身につけたり、Rubyのクラスやメソッドを知ることができる

たとえば、[C - Unlucky7](https://atcoder.jp/contests/abc186/tasks/abc186_c) を解く場合を考えてみましょう。

この問題を解くためには、数字を8進数表記に変換する方法や文字列の中に '7' が含まれるかどうかを判定する必要があります。

`Ruby 8進数` でネットで検索すれば、8進数への変換に `Integer#to_s` が使えそうなことが分かります。
また、`Ruby 文字列 含む` で、ネットで検索すれば、 `String#include?` が使えそうなことが分かります。

調べたことを組み合わせて使ってコードを書けば、問題を解くことができます。
問題を解くために使えそうなクラスやメソッドを調べ、それらを使うことを通じて、Rubyのクラスやメソッドのことをより深く理解できるようになります。

Rubyには、便利なメソッドが数多く用意されていることもわかるでしょう。必要なことをネットで調べる基本的な検索力も身に付くと思います。

## AtCoderの問題を解くことで、アプリケーションロジックを深く考える訓練ができる

次に、[D - Hachi](https://atcoder.jp/contests/abc181/tasks/abc181_d) を解く場合を考えてみましょう。
この問題を解くためには、並べ変えた数字をすべて列挙し、8で割り切れるかどうか判定する必要があります。

`Ruby 並べ替え 列挙` でネットを検索すれば、`Array#permutation` が使えそうなことがわかります。

8で割り切れるかどうかは、8で割った余りが0かどうかを判定すれば良く、 `Ruby 余り` で検索すれば、 `%` を使えそうなことがわかるでしょう。

これを使えば、正しい答えを出すコードを書くことができます。

と、これだけで終われば、話は簡単ですが、そう上手くいかないところが `AtCoder` の世界なのです。

AtCoderには、単純に問題の正しい答えを出力するという機能要件の他に守るべき非機能要件があります。
プログラムの実行時間と使用できるメモリの制約です。実行時間が2秒以内、メモリ1024MB以内の制約の中で答えを出す必要があるのです。
時間とメモリという2つの非機能要件も満たす必要があるのです。

結果的に、私は、`Array#permutation` を使って非機能要件を満たすコードを書くことができませんでした。
実行時間の制限にひっかかって、全てのテストにパスできないのです。

`Array#permutation` を使わない方法でコードを書いてみて、2秒ギリギリ通るようになりました。

その後、書いていたコードの一部をバイナリサーチを使うように書き変えると速くなるのでは、とひらめきました。
実際、バイナリサーチを使うことで、200ミリ秒で全てのテストが通るようになりました。

レベルの高い問題だと、単純にライブラリを使っても問題をクリアできず、その問題領域を深く理解する必要があります。
要求されていることは何か？ その答えを導く出すのに必要な処理は何か？ 限られた時間とメモリの範囲内で答えを出すもっと別の方法は無いのか？ などを考える必要があります。

実際、 `Array#permutation` を使わないコードは、「並べ変えた数字をすべて列挙」していませんし、 「8で割った余りが0かどうかを判定」していません。
(この問題を解くために、なぜ「バイナリサーチ」を使うことができるのか、おわかりでしょうか？)

## Ruby でAtCoder の問題を解くということ

AtCoder の問題を解くことによって得られるメリットには次のようなことがありそうです。

- Rubyにどんなライブラリやメソッドがあるか調べることを通じて、Rubyのクラスやメソッドを深く知ることができるようになる。特に `Array` や `String` など良く使われる基本的なライブラリについて深く知れるのではないかと思います。
- ネットでうまく必要な情報を見つける練習にもなる。

上位のレベルの問題を解くには、ライブラリの知識だけでは、不十分です。
機能要件、非機能要件を満足するように、アプリケーションロジックを深く考え、考えたロジックをコードに落とし込む技術が必要になってきます。
こちらは、ネットで検索して、すぐ解決できるようなものではないと思います。

解決したい問題とネットで検索して解決できることの間に隔りがあるので、この隔りを埋める作業が必要になってきます。
D-Hachiの問題で言えば、バイナリサーチを使う方法を思いつくことが、隔りを埋める作業です。
バイナリサーチの実装例は、ネットで検索すれば見つかるでしょう。

つまり、こちらで得られることは、次のようなところでは無いかと思います。

- その問題領域を深く考える練習ができる。
- ネットで検索して解決できるレベルまで問題を落とし込む練習ができる。
- 考えた方法をコードとして実現する練習ができる。

## 実業務では

実際の開発業務でも、似たようなことがあると思います。
すぐにネットで検索して解決できることもあれば、ネットで検索するレベルと課題との隔りを埋める作業が必要なこともあります。
AtCoder はRubyの知識を深めたり、検索力を高めたり、検索して解決できるレベルまで問題を落とし込む練習をする題材として使えそうと思いました。
kintoneのレコード詳細画面にQRコードを表示する

今回は、kintoneのレコード詳細画面にQRコードを表示するカスタマイズです。
webpackを使用します。




# アプリの準備

|フィールド種類| フィールドコード | 備考 |
|:-:|:-:|:-:|
|  文字列(1行) | url  |   |
| スペース  | spQR  |   |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/1e6cdba4-4837-b76b-0eb0-693a9bd4b016.png)



# コーディングまでの準備

webpackどうやるんだっけ・・・というところはこちらを参考にしました。

https://qiita.com/soarflat/items/28bf799f7e0335b68186

## 私の手順（参考までに）

### QRコードアプリ用のディレクトリを作成する。
ディレクトリ内で
`npm init -y`とすると
package.jsonができる。

### webpackとwebpack-cliをインストールする
`npm install --save-dev webpack webpack-cli`

### node-qrcodeをインストールする
`npm install --save qrcode`


### package.jsonのscriptsフィールドにwebpackコマンドを追加する

```json
  "scripts": {
    "build": "webpack"
  },
```

### webpack の設定ファイル（webpack.config.js）を作成する

```js
module.exports = {
  // モード
  mode: "production",
};

```

### srcフォルダを作って中にindex.jsファイルを作成してkintoneのコードを書く
詳細は後述します。

```js
(() => {
  "use strict";

  // 保存
  kintone.events.on(["app.record.detail.show"], (event) => {
    //コードを書くところ
  });

    return event;
  });
})();

```

### ビルドする
`npm run build`

### main.jsをアップロード

distディレクトリ内にmain.jsが生成されるので、アプリのJavaScript / CSSでカスタマイズにアップロードします。



# JavaScript
というわけで、srcディレクトリ内のJavaScriptの中身を書いていきます。
文字列(1行)の内容をQRコードにしてスペースフィールドに表示します。

↓使用したライブラリはこちらです
https://github.com/soldair/node-qrcode

```js
const QRCode = require("qrcode");
QRCode.toDataURL(event.record.url.value, function (err, url) {
  let sp = kintone.app.record.getSpaceElement("spQR");

  let img = document.createElement("img");
  img.setAttribute("src", url);
  sp.appendChild(img);
});
```
JavaScriptができたら、ビルドしてアプリにアップロードしてくださいね。

# 動作確認＆まとめ

文字列(1行)の値をQRコードにすることができました。（確認してみてね）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/ae97d899-ef9f-6636-2221-85d5353d3970.png)

今回はwebpackを使用し、手順もざっくりでしたが、やってみたい！という場合は参考ページで勉強してからやってみてくださいね！

https://qiita.com/soarflat/items/28bf799f7e0335b68186

では！
Amazon Linux 2をVirtualBoxで起動し、ネットワーク接続するまで

Amazon Linux 2をVirtualBoxで起動し、ネットワーク接続するまでの作業手順について
自分用メモとして残しておきます。


### ディスクイメージ(vdi,iso)の入手

以下URLより最新版のダウンロードが可能です。

vdi
https://cdn.amazonlinux.com/os-images/latest/virtualbox/

iso
https://cdn.amazonlinux.com/os-images/latest/


### isoファイルの編集

上記から取得したisoファイルはサンプルのため、必要があれば内容を編集してください。
（ユーザ名やパスワードなどが設定できますが、無理に変更する必要は無さそうです。）


### VirtualBoxで起動

仮想マシン→新規　でセットアップを行います。

設定は以下になります。

##### 名前とオペレーティングシステム
名前：（任意）
マシンフォルダー：（任意）
タイプ：Linux
バージョン：Red Hat(64-bit)

##### メモリーサイズ

（任意）

##### ハードディスク

すでにある仮想ハードディスクファイルを使用する
→上でダウンロードしたvdiファイルを選択


ここまでで仮想マシンが作成されますが、さらに「設定」を押下して追加の設定を行います。


##### ストレージ

コントローラー　IDE：上でダウンロードしたisoファイルを選択

##### ネットワーク

アダプター１、２について「ネットワークアダプターを有効化」にチェックを入れて、
それぞれの「割り当て」を設定

アダプター１：NAT
アダプター２：ホストオンリーアダプター

※アダプター２のMACアドレスは後ほど設定に使用するので、メモを取っておく。
　

以上で設定完了となるので、仮想マシンを起動しログインする
（ログインユーザはisoファイル内に定義されているものを使用する）

#### ネットワークの設定

とりあえずマシンは起動したものの、ネットワークに接続しないと使いづらいので設定を行います。

`# vi /etc/sysconfig/network-scripts/ifcfg-eth1`

`DEVICE=eth1`
`BOOTPROTO=static`
`ONBOOT=yes`
`TYPE=Ethernet`
`HWADDR=（ホストオンリーアダプターのMACアドレス）`
`IPADDR=192.168.56.200（ローカルネットワーク内のIPを任意に指定）`
`NETMASK=255.255.255.0`
`NETWORK=192.168.56.0`


以下はsshによるログイン時にパスワード入力を許可する設定のため、任意で行ってください。

`# sudo vi /etc/ssh/sshd_config`

`PermitRootLogin yes  # rootユーザのログインを許可`
`PasswordAuthentication yes  # パスワード入力によるログインを許可`

### 仮想マシンの再起動

ネットワークの設定を完了したので、一度マシンの電源をオフにして再度起動します。
後は仮想マシンに対してホストマシンや他の仮想マシンから接続できること（逆向きの接続も）、
仮想マシンから外部ネットワークへ接続できることを完了して作業終了です。

その後のミドルウェアのインストールやサーバ設定については割愛します。
（個人的には以降はansible等でセットアップができるようにしたいと考えています。）


### 参考

公式ドキュメント
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/amazon-linux-2-virtual-machine.html

VirtualBoxでHostからGuestに通信する3つの方法
https://qiita.com/zaburo/items/e446f8655a7a28a93d58

Amazon Linux 2をVirtualBoxで実行する
https://qiita.com/moritalous/items/d1970011642379269275
CSS知っておくべき関数

# ◆ CSS：地味に意外と使えるプロパティと関数

#### ① calc関数（レスポンシブ時に大活躍）
・レスポンシブ稼働した時に「child_2」のwidthは自動調整される。
・flexで同様の設定もできる

``` hoge.css

.parent {
  width: 740px;
}
.child_1 {
  width: 200px;
}
.child_2 {
  width:calc(100% -200px);
}
```

#### ② nth-of-type(n)（レスポンシブ時に大活躍）

``` hoge.css

.div content ul li :nth-of-type(3){
  color: #ff0000;
}
```
【Python】複数条件で文字列を分割しリストを生成【re.split】


Pythonで文字列をリストに変換したい場合，まず思い浮かぶのは`split()`を使う方法です．


```python
s = 'AAA BBB CCC'
l = s.split()
l
```




    ['AAA', 'BBB', 'CCC']



分割できました．

以上の例は文字列が空白で区切られている場合ですが，他にも...


```python
s1 = 'AAA,BBB,CCC'
l1 = s1.split(',')
l1
```




    ['AAA', 'BBB', 'CCC']




```python
s2 = 'AAA/BBB/CCC'
l2 = s2.split('/')
l2
```




    ['AAA', 'BBB', 'CCC']



このように，`()`内で区切り文字を指定することで分割できます．

### `re.split()`
文字列からリストに変換する他の手段として`re.split()`を使う方法があります．


```python
import re

s = 'AAA BBB CCC'
l = re.split(' ', s)
l
```




    ['AAA', 'BBB', 'CCC']



このように`re.split('区切り文字', 文字列)`とすることで，先程と同様にリスト化できます．

でもこれだと`split()`で十分な気がしますよね．

しかし，`re.split()`の真価が発揮されるのはここからです．

### 複数条件で文字列を分割
`re.split()`を用いることで複数条件で文字列を分割することが出来ます．

例えば...


```python
s = 'AAA/BBB_CCC'
l = re.split('[/_]', s)
l
```




    ['AAA', 'BBB', 'CCC']



このように，`[]`の中に`/`，`_`の複数の区切り文字を入れることで，それらに該当する全ての箇所で分割してくれます．

便利ですね．

ちなみに少し細かいことを話すと，これには正規表現と呼ばれるものが使われており，正規表現において`[...]`は，角括弧の中身のいずれか1文字に該当する文字を表します．

なので，以上のような処理が出来るわけですね．

### 区切り文字が連続する場合
区切り文字が連続する場合，普通に記述するとリストがおかしくなります．



```python
s = 'AAA - BBB - CCC'
l = re.split('[ -]', s)
l
```




    ['AAA', '', '', 'BBB', '', '', 'CCC']



`s`にはハイフン`-`と空白` `が含まれているためこの二つを区切り文字としましたが，`l`には空要素`''`が含まれてしまっています．

これは区切り文字が` - `のように連続していることが原因のようです．

これを解決するためには...


```python
s = 'AAA - BBB - CCC'
l = re.split('[ -]+', s)
l
```




    ['AAA', 'BBB', 'CCC']



このように区切り文字を`[...]+`とします．

この`+`は正規表現において，該当の文字を一回以上繰り返すことを示します．

今回は`[ -]`の後についているため，` `か`-`を一回ずつ選択しながら一回以上繰り返す文字列に該当するわけですね．

なので，` - `のような文字列にも対応できます．

ちなみに私はこれが解決せずに半日ほど悩みました...

### 複数の文字列を条件として区切る
ここまでは，`/`や`-`等の記号を用いた分割方法を紹介しました．

ですが，いくつかの文字列を用いて分割したい場合もあるかもしれません．

例えば，`and`，`after`という二つの条件で区切りたい場合は以下のようにします．



```python
s = 'DOGandCATafterFISH'
l = re.split('and|after', s)
l
```




    ['DOG', 'CAT', 'FISH']



このようにパイプ`|`を使うことで，複数の文字列条件を扱えます．

ちなみに`|`はいわゆる`OR`を表しますね．

今回は以上になります．

少しでもお役に立てれば幸いです!!

### おまけ
複数の文字列と記号を区切り文字としたい場合．


```python
s = 'dog and cat after fish'
l = re.split('and|after| ', s)
l
```




    ['dog', '', '', 'cat', '', '', 'fish']



ですよねーーー

解決策としては...


```python
l = re.split(' and | after ', s)
l
```




    ['dog', 'cat', 'fish']



柔軟性はなくなりますが，文字列のルールが明確であればこれでも良いかと思います．

他には少し面倒ですが...



```python
l = re.split('and|after', s)
print(l)
s2 = ''.join(l)
print(s2)
l2 = s2.split()
l2
```

    ['dog ', ' cat ', ' fish']
    dog  cat  fish
    ['dog', 'cat', 'fish']



一度文字列条件だけでリスト化したものを`join()`で文字列化し，その後空白条件で再度リスト化しています．

もし，他に簡単な方法があれば是非教えてください!!
CQRSについて学んで分かったことのまとめ

# はじめに

本記事はCQRSについて学んで分かったことをまとめています。

# CQRSの概要

日本語ではコマンド/クエリ責務分離と呼ばれるソフトウェアアーキテクチャの1つです。
DDDにおいて広く使われているドメインモデルパターンの問題点を解消するために提案されたアーキテクチャです。

# コマンドとクエリ

コマンドはソフトウェアシステムの状態を変更するアクションのことを指します。クエリはソフトウェアシステムの状態を取得するためにデータのみを返すアクションのことを指します。
例として家計簿Webアプリケーションを考えます。アプリケーション上からユーザーの入力によって費用を更新するというアクションは、コマンドに相当します。アプリケーション上に家計簿を表示するために費用を取得するというアクションは、クエリに相当します。

![コマンドのクエリ_図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/fd991267-8bb7-fe1e-c438-74e48923247e.png)

# ドメインモデルパターンとCQRSの違い

ドメインモデルパターンとCQRSの大きな違いはドメイン層がコマンドとクエリで分割されているか否かです。
例として、コマンドとクエリでの例と同様に家計簿Webアプリケーションを考えます。
品目名、金額、用途という家計簿の1行分に該当する情報を`購入品目`という名前の概念で扱っている状況を考えます。
この時、ドメインモデルパターンとCQRSでドメイン層がどのようになるかを考えてみます。

## ドメインモデルパターンの場合

ドメインモデルパターンの場合は、コマンドとクエリの両方に対応したモデルを用意して、そのモデルが中心となってドメイン層としての振る舞いを提供します。
つまり、単一の`購入品目`モデルを利用することになります。

![ドメインモデルパターンの場合のモデル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/0acc5e7f-7417-89a9-4d7e-929234946bc6.png)


## CQRSの場合

CQRSの場合は、コマンドとクエリでドメイン層が分かれます。つまりは、コマンドとクエリ用で別の`購入品目`モデルを利用することになります。
コマンド用の`購入品目`モデルは`購入品目`の状態を変更するためのビジネスロジックを持ちます。対してクエリ用の`購入品目`モデルは`購入品目`のデータのみを表現し、ビジネスロジックは持ちません。

![CQRSの場合のモデル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/2386c495-f42e-a31b-0f8e-e6173c606a4d.png)

## CQRSのメリット

ドメインモデルパターンとCQRSを比較した場合のCQRSのメリットは、コマンドとクエリが依存していないことです。CQRSではモデルがコマンドとクエリ用に分かれるので、コマンドとクエリのそれぞれの事情でモデルを容易に拡張できます。対してドメインモデルパターンでは、同じモデルでコマンドとクエリを扱うので、クエリ用にプロパティ1つ追加する場合であってもコマンドへの影響がないかを確認するなど、コマンドとクエリ同士の影響を考えて拡張をしなければなりません。

# 最後に
本記事ではCQRSについて学んで分かったことをまとめました。まだ深くは理解できていないので、サンプルを自分で作ってみてさらに理解を深めたいと思います。
SmartQAを使ってiOSのE2Eテストを行う


# はじめに

モバイルにおいてのE2Eテスト（ ≈ UIテスト）を行うツールとして``Magic Pod``がよく名前に上がりますが、今回は新しく出てきたツールとして、[SmartQA](https://app.smart-qa.io/) を試してみました。


# SmartQAとは

![d56572-6-599144-0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/250cf650-5171-9750-7334-ac29e5ed98ac.png)


(画像引用：[PRTimes](https://prtimes.jp/main/html/rd/p/000000006.000056572.html))

本文をそのまま借りると、「iOSアプリのE2Eテストをノーコードで自動化するWebサービス」です。

開発秘話などは、以下を読んでいただく方が早いでしょう。

https://qiita.com/kot10n5tn/items/291ad1170cf00253bf3b


# E2Eテスト

[SmartQA](https://app.smart-qa.io/)のアカウントとプロジェクトの作成を行った前提で進めます。

※以下、アプリのスクリーンショットは許可を経て掲載しています。

## 1. テストケースの作成

UIは比較的シンプルな画面です。
「テストケースを作成する」を選択してテストを作っていきます。

![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/07ff0c02-37cb-05e7-ecc6-899d582d3031.png)
まだ、新しいツールということもあり、選択できる条件は少ないです。

### 比較表

SmartQA | Magic Pod
:---: | :---: 
![s](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/a2846b9b-4e33-64f9-b5c7-0b6959a0f36e.png)|![b](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/a90355e9-33a7-dcf6-f209-7a65d0be7a14.png)
- | ![d](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/664628bc-9bea-bbdf-530f-e38a339435dd.png)


## 2. シナリオの作成

右側に起動された端末で操作をしていくと、左側に操作履歴がスクリーンショットで残されていきます。
体幹としては、``Magic Pod``と同じような操作感で登録をしていくことができます。

![５.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/10e019a7-f049-cc80-98b8-fc613c79b5dc.png)


作成が終わると、シナリオの一覧を見ることができます。

![６.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/21a3c518-68f0-e024-51a7-1f2fab0826b7.png)

テストを変更したい、アサーションを設定したいといった場合、こちらから画面を選んで設定することができます。

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/0f397456-d750-c900-65cd-94ec73e2253b.png)

## 3. テストの実行

テストを選んで実行します。

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/a4a8f8cf-a9ce-580f-84fe-57e27a5f8467.png)

今回は１つしかないですが**複数実行**が可能です。

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/7ae61845-03d1-4e6f-b129-2478ff99642d.png)

実行が終了すると以下のようになります。

![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175242/dcf753e3-bf85-54d1-067d-ee3bb849cf7f.png)

ここまでものの10分ほどで作れたので、比較的簡単にE2Eテストを導入できるのは魅力的だと思います。

# 終わりに

**Bitrise連携**や**Slack通知連携**も対応しているで、一通りのベーシックな機能は備わっている印象です。
より詳しい操作方法を見たい場合は、「[SmartQAのご利用方法](https://www.notion.so/SmartQA-e6e9394102bc4402b12c8d474835f7cf)」を見ていただくと良いでしょう。

最近だと、[Autify](https://autify.com/ja/mobile) もE2Eテストとして台頭してきましたが、モバイルで使用するのに申請が必要なので、サクッと試すにはこちらの方がとりかかりやすさはあります。


まだ、iOSしか対応していない（というより今後もiOSしか対応しなさそう？）ので、AndroidやWebとセットでツールを考える場合は、少し吟味も必要かもしれません。
[Android]思考停止で使っていたsetOnClickListener{}を少し掘り下げる

#ボタンをポチポチ
どうもReoです。
Buttonをタップした時に、何かの処理をした時に、取り敢えず

```kotlin
buttonView.setOnClickListener {
    // 何かしらの処理を書く
}
```
こんな感じで当たり前のように使っていました。
しかし、最近これの拡張関数を作る時にググって参考にしたメソッドがあるのですが、それが理解できない。
何故なら、リスナーがどう成り立っているのか分からなかったから。

さて、こいつを思考停止で使っていたことが判明したので、掘り下げたいと思います。

#裏のコード旅へ！
適当にButtonViewを追加して、MainActivityとかからViewを呼び出して、setOnClickListenerを実装してください。
裏のコード見るには、リスナーメソッドにカーソルを合わせてWindowsの方はCtrl + Enter。
Macの方は、Command + Enterです。

こんなコードに飛んだかと思います。

```kotlin:View.java
   public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
```
まず、引数はコールバック関数とか言われる引数に関数をセットしているものですね。
@Nullableというアノテーションがつくことで、ヌル許容になっているようです。

では、今度はOnClickListenerに飛んでみましょう！

```kotlin:View.java
    /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }
```
ここでは、OnClickListenerというインターフェースを定義しています。
インターフェースとは、具体的処理が書かれていない関数を持つクラスみたいなものです。
こちらの関数をsetOnClickListenerの引数にセットしているわけです。
続いて、isClickableへ。

```kotlin:View.java
/**
     * Enables or disables click events for this view. When a view
     * is clickable it will change its state to "pressed" on every click.
     * Subclasses should set the view clickable to visually react to
     * user's clicks.
     *
     * @param clickable true to make the view clickable, false otherwise
     *
     * @see #isClickable()
     * @attr ref android.R.styleable#View_clickable
     */
    public void setClickable(boolean clickable) {
        setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
    }
```
ここで、クリックイベントの制御を行っています。
boolean型を引数に取るのと、後は単純にコメントの一行目からも推測できますね。

```kotlin:View.java
  getListenerInfo().mOnClickListener = l;
```
これは、まぁ引数に渡したリスナーの情報を取得するなにかでしょう・・・
と、と、とりあえず、setOnClickListenerに渡す引数がOnClickListenerのインターフェースであり、内部ではクリックイベントの制御等が行われていることがわかれば大丈夫でしょう。

#あれれ？引数は？？？
さぁ、ここで疑問に思った方もいるかもしれません。（自分は思いました）

```kotlin
buttonView.setOnClickListener {
    // 何かしらの処理を書く
}
```
このようにして、setOnClickListenerを利用しますが・・・・・
###引数どこなん？？？？
おかしいですよね。（）内にセットするはずのものがここでは書かれていません。
しかし、正常にリスナーが処理を行ってくれます。

実は、これSAM変換というものが行われています。
SAMとは、Single Abstract Methodの略で、一つのメソッドしかもたない抽象クラスのことです。

これによる恩恵は、メソッドを一つしか持たないinterfaceならばラムダに変換できることです。
ちなみに、普段使用しているhogeView.setOnClickListener{}のちゃんとした（？）書き方は、こうです。

```kotlin
hogeView.setOnClickListener(object: View.OnClickListener {
     override fun onClick(view: View?): Unit {
          // 何らかの処理
   }
})
```
これが、本来の書き方です。
これをSAM変換することによって、我々は可読性が良い書き方をできているわけです。
ちなみに、objectはインターフェースをインスタンス化できるもので、
Unitは、void、つまり返り値がないことを意味しています。
onClickの定義を見返すと、voidであることがわかると思います！

SAM変換の流れを詳しく解説している記事がありましたので、こちらを参考ください。

https://qiita.com/huton338/items/c17d3094f5f841426077



#終わりに
当たり前のように、ボタンポチポチのイベント処理を書いていたけど、見えないところで使いやすい工夫が施されていたとは・・・・
これからも、思考停止シリーズを続けていきたいと思います。
インフォメティス株式会社のReact Nativeアプリ開発でJotaiを導入してみた第一印象

[Jotai](https://github.com/pmndrs/jotai)使ってますか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63327/f2dde351-baf1-78b3-49c5-a5e195ad1be8.png)

最近、[インフォメティス株式会社](https://www.informetis.com/)のReact Nativeアプリ開発においてJotaiを導入しました。これまではglobal stateはあまり使わずに済んでいたのですが、アプリの機能開発を進めるにあたって、コンポーネントのlocal stateに留まらない状態管理ができると開発効率が上がるシーンが出てきました。Jotaiはv1リリースを目前に安定してきたので投入タイミングとしては良いかと思いました。

Jotaiについては[公式リポジトリ](https://github.com/pmndrs/jotai)の他に、下記の記事も参考にしてください。

- [Jotai: 状態?! 日本語っぽい名称のReact用ステート管理ライブラリ](https://qiita.com/daishi/items/15f54c583a6d73891928)
- [React状態管理ライブラリ「Jotai」のProvider-less modeで楽々コーディング](https://qiita.com/daishi/items/0a821f712cbb3f9edb2d)
- [React状態管理ライブラリJotaiとValtioのGitHubスター数推移を見てみた](https://qiita.com/daishi/items/c9acc69c07f9b8ce52c5)

今回導入にあたって、感じたことをメモのため列挙しておきます。

- 公式ドキュメントを読むだけで導入ができハードルがなかった
- 導入前の仕組みと比較してコードがスッキリした
- atomの定義ファイルを見れば何をglobal stateとして管理しているか明確になった

今後global stateを多用していくと構造化や管理方法を整備していく必要が出てくるかもしれませんが、現時点では必要最低限に抑えていることもありすぐに破綻することはないかと感じました。

---
最後に、インフォメティス株式会社の採用情報は[こちら](https://www.informetis.com/recruit/)にありますので、ご興味をお持ちになられた方は是非ご覧ください。
10GbpsのNASの速度テスト

SynologyのNAS (DS1817)を使ってデータストレージを作った。簡易ベンチマークの結果をメモ。

## 構成
- 接続先（iSCSIのターゲット）
    - Synology NAS (DS1817)
    - HDD 16TB (Seagate) 5台
    - RAIDタイプ: Synology Hybrid RAID (SHR2)
- 接続元（iSCSIのイニシエーター）
    - Ubuntu 20.04
    - NIC ASUS XG-C100C
    
普通にSMBやNFSで接続するよりもiSCSIの方が早い、と何かで見たので、初めてiSCSIというのを使ってみることにした。どうせアクセスするのは一つのパソコンからだけなので、共有する機能は不要。

## 10Gbpsのネットワークハブは使わない

せっかく10Gbpsが使えるので、10Gbpsが使えるNICを買ってきてubuntuが乗ってるパソコンに取り付けて接続する。10Gbpsのハブはそこそこ値段が高いのと、10Gbpsなんて環境はこのNASの接続にしか使わないので、NASとPCを直接繋ぐことにした。最近はAutoMDI/MDI-Xというのがあってクロスケーブルも不要で直接適当なLANケーブルで繋げば良いらしい。手元にあったCat.6のケーブルで繋いだ。
10GbpsのNASの情報を探すと大抵の場合は、10Gbpsに対応したネットワークハブが必須、とあるが、PC-NAS間くらいしかネットワークの速度で律速されることなんてほとんどないので、これで動くならハブは不要だと思う。

(追記)適当に固定IPを設定して通信することはできたが、ルーティングをちゃんと設定しないとイニシエータのubuntuでインターネットにつながらなくなることがあった。どうやらNICの2つ刺し環境では優先度(metric)というのを設定しないと場合によってはうまくいかなくなるみたいだ。ubuntuの場合は、```/etc/netplan```を使って設定するのが良いらしい。試行錯誤の結果動くようにはできたが、自分の設定が正しいのか自信がないのでここには書かない。

## 簡易速度テスト

ちゃんとベンチマークとったわけではない。あくまでも簡易テスト。iSCSIの練習と思って5GBの小さなLUNを作っただけなので、真面目に大きな容量でやると結果は変わるかもしれない。


まずは普通の1Gbpsで接続した時。

```
# sudo hdparm -t /dev/sda

/dev/sda:
 Timing buffered disk reads: 336 MB in  3.02 seconds = 111.43 MB/sec

# dd if=/dev/zero of=/mnt/tmpdata/tmp.data bs=1M count=100
100+0 レコード入力
100+0 レコード出力
104857600 bytes (105 MB, 100 MiB) copied, 0.914372 s, 115 MB/s
```

1Gbpsのネットワークが速度の律速になっているように見える。これを10Gbpsにするとこんな感じ。

```
# hdparm -t /dev/sda1

/dev/sda1:
 Timing buffered disk reads: 2546 MB in  3.00 seconds = 848.00 MB/sec

# dd if=/dev/zero of=/mnt/tmpdata/tmp.data bs=1M count=500
500+0 レコード入力
500+0 レコード出力
524288000 bytes (524 MB, 500 MiB) copied, 1.49079 s, 352 MB/s
```

読み込みで8倍、書き込みで3倍くらい。さすがに10倍にはならないので、ネットワークでの律速は10Gbpsで解決して、あとはHDDの速度なのかと思う。
[React] useStateで無限ループを避ける方法

onChangeイベントでuseState()を使いたい時に
以下のようにすると無限ループになるため、

```javascript:App.js
onChange={setItem('hoge')}
```

正しくは下記のようにアロー関数を使うと無限ループを避けられる。

```javascript:App.js
onChange={() => setItem('hoge')}
```