OmniAICreator/Qiita-1.07M huggingface dataset:
2023/5/5主にITとかセキュリティの記事

イスラエル企業NSOがiPhoneの「ゼロクリック脆弱性」を悪用(5/3)
https://forbesjapan.com/articles/detail/62774

三星電子、社内ＰＣでチャットＧＰＴなど生成型ＡＩの使用を禁止(5/3)
https://www.donga.com/jp/home/article/all/20230503/4130377/1

サイバー防御、国際協力の時
NTTチーフ・サイバーセキュリティ・ストラテジスト 松原実穂子氏(5/3)
https://www.nikkei.com/article/DGKKZO70706570S3A500C2M11000/

大型連休中　サイバー攻撃への注意呼びかけ(5/2)
https://news.yahoo.co.jp/articles/bd336ae1173995d98d686cb4441d0191cef153cd?source=rss

Apple、MacやiPhone/iPad向けに緊急セキリティ対応となる「macOS 13.3.1 (a)」と「iOS/iPadOS 16.4.1 (a)」をリリース。(5/2)
https://applech2.com/archives/20230502-rapid-security-response-macos-1331a-and-ios-1641-a.html

Appleを偽るフィッシング確認、注意を(5/3)
https://news.mynavi.jp/techplus/article/20230503-2671060/

AppleをかたるフィッシングSMS、「【apple】重要なお知らせ、必ずお読みください」などの不審なメッセージに注意(5/2)
https://news.yahoo.co.jp/articles/0dee14da895e21b2b3debffced4283c36474dbc7?source=rss

G7デジタル・技術相会合、責任あるAIの実現を目指す閣僚宣言(5/3)
https://www.zaikei.co.jp/article/20230503/720029.html

登録者107万人の「○○の主役は我々だ！」YouTube乗っ取り被害から復旧　相次ぐ人気チャンネルの乗っ取り(5/3)
https://news.yahoo.co.jp/articles/b2ffde4d5f653c41f8fa36c12bb9e74524f2235f

【解説】「生成AIの利用ガイドライン」公表　他人の論文やデザインをAIで…権利の侵害にあたるか(5/3)
https://news.yahoo.co.jp/articles/fbe98ce8d7ae86ea3837c98b167d18e24e192acd

プロンプトインジェクションとは？意味や例、類似の技術も紹介(5/3)
https://www.sejuku.net/blog/207003

【最速解説】「こどもに関する各種データの連携による支援」成果報告書(5/3)
https://gunosy.com/articles/avRdM

＜独自＞自衛隊サイバー部隊　年度内に人員２・５倍に(5/3)
https://www.sankei.com/article/20230503-VPG3D45LAZOPRO2OA5PBSNVBM4/?ownedutm_source=owned%20site&ownedutm_medium=referral&ownedutm_campaign=ranking&ownedutm_content=%EF%BC%9C%E7%8B%AC%E8%87%AA%EF%BC%9E%E8%87%AA%E8%A1%9B%E9%9A%8A%E3%82%B5%E3%82%A4%E3%83%90%E3%83%BC%E9%83%A8%E9%9A%8A%E3%80%80%E5%B9%B4%E5%BA%A6%E5%86%85%E3%81%AB%E4%BA%BA%E5%93%A1%EF%BC%92%E3%83%BB%EF%BC%95%E5%80%8D%E3%81%AB

スパイの道具となった「ドンバスの少女」　米軍機密漏洩の恐るべき影響とは(5/3)
https://www.epochtimes.jp/2023/05/148364.html

中国2022年のデジタル経済規模、50兆2000億元で世界2位を維持―中国メディア(5/1)
https://www.recordchina.co.jp/b913308-s6-c20-d0190.html

ペーパーレス化で業務効率をアップ | CO2削減でSDGs目標にも貢献(5/2)
https://www.cloud-for-all.com/dx/blog/improve-work-efficiency

健康保険証は廃止の方向でも「マイナ保険証」の利用進まず　来秋の一本化に不安の声(5/3)
https://www.sankei.com/article/20230503-HA5C2QY2KFEL5M4VJRAFMDKKDU/?ownedutm_source=owned%20site&ownedutm_medium=referral&ownedutm_campaign=ranking&ownedutm_content=%E5%81%A5%E5%BA%B7%E4%BF%9D%E9%99%BA%E8%A8%BC%E3%81%AF%E5%BB%83%E6%AD%A2%E3%81%AE%E6%96%B9%E5%90%91%E3%81%A7%E3%82%82%E3%80%8C%E3%83%9E%E3%82%A4%E3%83%8A%E4%BF%9D%E9%99%BA%E8%A8%BC%E3%80%8D%E3%81%AE%E5%88%A9%E7%94%A8%E9%80%B2%E3%81%BE%E3%81%9A%E3%80%80%E6%9D%A5%E7%A7%8B%E3%81%AE%E4%B8%80%E6%9C%AC%E5%8C%96%E3%81%AB%E4%B8%8D%E5%AE%89%E

四国ガスのサイトから個人情報流出 ５万人以上が登録 調査完了までサイト閉鎖【愛媛】(5/3)
https://news.yahoo.co.jp/articles/49233b55daa193b5dd7eec4f8747fd486ae94fc1?source=rss

auじぶん銀行、不正送金対策で他行間即時振込・入金を一時停止(5/2)
https://news.yahoo.co.jp/articles/5b15a95eb2ed6d2c76ccfbbe12ebcaa4bf95a070?source=rss

内閣NISCかたり2200万円詐取「あなたのスマホ、世界で被害」(5/3)
https://news.yahoo.co.jp/articles/c7239659b16d706102f7f05a731a803cafe9de8b
【Chrome113】Tab Hover Card Imagesを消す方法【急募】

rana_kualuとGoogleの[長い](https://qiita.com/rana_kualu/items/c0c1730805387ae1b2be)[戦いに](https://qiita.com/rana_kualu/items/a8f2fd0ba73666a46888)[終止符が打たれました](https://qiita.com/rana_kualu/items/dd1028f138613d87eb21)。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/1470f5ff-791e-5e28-4c92-81675ca00145.png)

即ち、rana_kualuの敗北です。

タブ上にマウスカーソルを持っていくと目障りなゴミが出てきますが、ついにこれを消すことができなくなってしまいました。
こんなクソゴミ機能を強制させるとか頭おかしいんじゃねえの？

誰か消すアドオン作って。

# 歴史

[Chrome78](https://did2memo.net/2019/10/23/chrome-update-tab-hover-cards/)でtab-hover-cardsが実装された。

[Chrome91](https://qiita.com/rana_kualu/items/c0c1730805387ae1b2be)でtab-hover-cardsを無効化することができなくなった。

[Chrome94](https://qiita.com/rana_kualu/items/a8f2fd0ba73666a46888)でtab-hover-card-imagesの設定値がDisabledからDefault(Enabled)に勝手に書き換えられた。

[Chrome103](https://twitter.com/rana_kualu/status/1555871889226366977)で再びtab-hover-card-imagesの設定値がDisabledからDefault(Enabled)に勝手に書き換えられた。

[Chrome111](https://qiita.com/rana_kualu/items/dd1028f138613d87eb21)でtab-hover-card-imagesの設定項目が削除された。
Temporarily unexpire M110 flagsで一時的に復活することができる。

[Chrome113](https://qiita.com/rana_kualu/items/19b141446b3f15b19dba)でTemporarily unexpire M110 flagsが削除された。
即ち、tab-hover-card-imagesを無効にすることができなくなった。
Twitter APIが使えなくなったのを復帰させるまで

# はじめに

数日前、急にTwitter APIが利用出来なくなったので「まぁGWまでは放置か」と後回しにしていたのですが、復帰したのでまとめておきます。

# 状況

何年も前からTwitter APIを個人サービス用に利用してました。数日前（4/27頃？）に急にそのサービスのAPIが利用出来なくなってしまったのですが、利用者さんに状況とGWに直します旨報告。先ほど復旧しました。

# 復旧までのステップ

Twitterのデベロッパーポータルにアクセスしたところ、利用出来なくなっていたTwitter APIに`SUSPENDED`の文字と、以下のようなメッセージが出ていました。

`This App has violated Twitter Rules and policies. As a result, it can no longer be accessed. For assistance, submit a support ticket.`

さて、このサポートチケットに聞きたいわけですが、何故かヘルプページに飛ばされてしまい、サポートチケットが開けません。以前はあった様なのですが、綺麗に消えています。

そこでさらに検索をかけたところ、以下のページが引っかかりました。

https://twittercommunity.com/t/application-suspension-notice/189989/15

ここには同様の問題を抱えた人が沢山居たので、情報収集をします。その中で「解決したよ」という人が居ました。

その手順は以下の通り。

1. デベロッパーポータルにアクセスする
1. Projects & Apps から必要な物（`SUSPENDED`が着いているもののはず）を一つだけ残して、あとは削除
1. Products の中程にある`Downgrade`ボタンをクリック
1. `SUSPENDED`が消えている事を確認

私は上記でクリアできました。どうもFreeアカウントなのにアプリが複数あったのがNGだったようです。

# まとめ

アプリを一つ以外残して削除し、ダウングレードを行う、という方法でした。つまりFreeアカウントで運用する以上、アカウントの規約上アプリは一つしか利用出来ないのは仕方が無いのですが、私のように最終的に一つを残して後は消せる人は良いですが、既に複数使っているような場合は上記の方法は取れません。
有料アカウントになっても`SUSPENDED`が取れなかった例もあるようなので、「じゃぁ有料にすれば良いや」という訳にもいかないのが難しい所です。むむむ、、、
AKS Edge ClusterをAzureで監視する

### はじめに

以前[AKS Essentials ClusterをAzureに接続](https://qiita.com/k-kimino/items/4b3103bd66db2ae65251)したのでAzureの管理手法で監視します。

### 監視用の拡張機能をインストールする

Azure Portalから監視用の拡張機能をインストールすることができます。
![AKSEdgeinsight.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322105/ef05d6c6-2124-b8a3-e9f7-bbb08eb97f6f.png)

![AKSextention.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322105/ff785764-f3eb-f215-86e7-2904b2a69e71.png)

### クラスターの主要なパフォーマンスの監視

この画面だと以下の4つの主要なパフォーマンスメトリックが表示できます。構成したばかりなのであまり有用ではない図で恐縮です。
- ノードのCPU使用率
- ノードのメモリ使用率
- ノード数
- アクティブなポッド数
![AKSmetric.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322105/8e275752-acba-c244-8b2d-bca5efdf91d1.png)


### ノード、コントローラ、コンテナの正常性を監視する

以下のようにノード、コントローラ、コンテナの正常性を一目で確認することができます。前回作ったサンプルアプリを表示しています。
![AKSInsightview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322105/d3770f54-65d6-b13a-b28a-dc6fe8b05662.png)

### まとめ

Azureを使用してAKSのパフォーマンスログをグラフィカル表示できることを確認しました。本当は[Syslogも取得できる（プレビュー）](https://learn.microsoft.com/ja-jp/azure/azure-monitor/containers/container-insights-syslog)はずなのですが、うまく構成できなかったのでそのあたりはもう少し調査してみたいです。

### 参考

[Azure Arc Enabled Kubernetes Part 2: Adding Security and Monitoring](https://www.codeproject.com/Articles/5334363/Azure-Arc-Enabled-Kubernetes-Part-2-Adding-Securit)
CentOS 7でのMariaDBアップグレード: GPGキーエラーを解決し、10.6から10.11へスムーズに移行する方法

# 1. はじめに

CentOS 7を更新する際に、MariaDBパッケージに関連するエラーが発生することがあります。
この記事では、GPGキーエラーの解決方法とMariaDB 10.6から10.11へのアップグレード方法を簡単に説明していきます。

```
Downloading packages:
warning: /var/cache/yum/x86_64/7/mariadb-main/packages/MariaDB-server-10.6.xx-1.el7.centos.x86_64.rpm: Header V4 RSA/SHA512 Signature, key ID c74cd1d8: NOKEY
file:///etc/pki/rpm-gpg/MariaDB-Server-GPG-KEY から鍵を取得中です。

GPG 鍵の取得に失敗しました: [Errno 14] curl#37 - "Couldn't open file /etc/pki/rpm-gpg/MariaDB-Server-GPG-KEY"
```

## 1.1. GPGキーエラーだけを解決する場合

このエラーは、MariaDB ServerパッケージのGPGキーが取得できなかったことを示しています。この問題を解決するには、次のコマンドを実行してGPGキーを手動でインポートすることもできるのですが、今回は、アップデートも兼ねての手順となります。

```bash
sudo rpm --import https://downloads.mariadb.com/MariaDB/MariaDB-Server-GPG-KEY
```

# 2. MariaDBレポジトリの更新

まずは、MariaDBのレポジトリを更新していきましょう。

## 2.1. 公式リポジトリの追加

次のコマンドを実行して、MariaDBの公式リポジトリをシステムに追加します。

```bash
curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
```

## 2.2. 追加されるリポジトリについて

なお，このスクリプトでは /etc/yum.repos.d に以下の3レポジトリが設定されます。

* MariaDB Server Repository
* MariaDB MaxScale Repository
* MariaDB Tools Repository

参照先: [MariaDB Package Repository Setup and Usage](https://mariadb.com/kb/en/mariadb-package-repository-setup-and-usage/)


# 3. MariaDB 10.6から10.11へのアップグレード

それでは、MariaDBのアップグレードを行いましょう。

## 3.1. キャッシュのクリアとパッケージインデックスの更新

次のコマンドを実行して、キャッシュをクリアし、パッケージインデックスを更新します。

```bash
sudo yum clean all
sudo yum makecache
```

## 3.2. MariaDBパッケージのアップグレード

以下のコマンドで、MariaDBのパッケージをアップグレードします。

```bash
sudo yum update MariaDB-client
```

`MariaDB-server`は競合が発生するので、一度削除してから、再度入れなおします。

```bash
sudo yum remove MariaDB-server
sudo yum install MariaDB-server
```

## 3.3. MariaDBサーバーの再起動と確認

MariaDBサーバーを再起動して、アップグレードが正常に完了したことを確認しましょう。

```bash
sudo systemctl restart mariadb.service
```

## 3.4. `mariadb-upgrade` を実行

最後に、`mariadb-upgrade` を実行します。これにより、以下の2つの作業が行われます。

```bash
sudo mariadb-upgrade -u root -p
```

1. mysql データベース内のシステムテーブルが新しいバージョンと完全に互換性があることを確認します。
2. すべてのテーブルを素早くチェックし、新しいバージョンの MariaDB と互換性があることをマークします。

# 4. まとめ

この記事では、CentOS 7でのMariaDBアップグレード時に発生するGPGキーエラーの解決方法と、10.6から10.11への移行方法を簡単に説明しました。これらの手順に従って、問題なくMariaDBのアップグレードが行えることが確認できます。

# 5. 参考リンク

- [Qiita記事: MariaDB 10.6 から 10.11 へのアップグレード手順](https://qiita.com/blue_islands/items/9e16cf18452569a30149)
Linuxのデスクトップ環境について軽く整理（＋メモリ使用量比較）

## 経緯
LinuxでGUIを使おうと思うと、多くのディストリビューションでデスクトップ環境に複数の選択肢がある。Qt/GTKなどややこしかったので、これらについて調べたことを軽くまとめた。

## 重量系
### 歴史
オープンソースのGUI環境を作りたいという流れの中で、最初に出てきたのがKDE。Qtというプロプライエタリなツールキットを使用して作られた。
これに対し、ツールキットもオープンソースのものを使用するべきとしてGTK（GIMP ToolKit）を使って開発されたのがGNOME

### KDE
もともとは K Desktop Environment の略で、あくまでデスクトップ環境の1つにすぎなかった。
やがてKDEは各種アプリケーションなども含めたより包括的な概念になり、バージョン4ではKDE Software Compilation 4と改称した。
しかし、それがKDE Frameworks、KDE Plasma、KDE Applicationsの3つに分割され、現在KDEのデスクトップ環境はKDE Plasmaと呼ばれている

### GNOME
GNOME 2までは事実上のスタンダードで高評価だった。GNOME 3で大幅な変更があったため賛否両論あり、フォーク*も多く生まれている
\* Cinnamon, MATEなど

## 軽量系
### Xfce
GTKを採用している。UNIX哲学（モジュール性、再利用性）を体現している

### LXDE / LXQt
LXDEはGTKを採用。後継としてQtに移行したLXQtが作られた。
LXQtの誕生によりLXDEは終了していくという話もあったものの、現在もLXDEの開発は継続している模様

## QtとGTKについて
デスクトップ環境だけでなく、アプリもQtで開発されたものとGTKで開発されたものとがある。異なるGUIツールキットで開発されたアプリをインストールしても使用自体はできるものの、見た目が調和しないものになってしまう。

ただ、ルックアンドフィールを統合するためのスタイルが存在しており、それを使えば問題なく使えそう？
（参考：[Qt と GTK アプリケーションの外観の統合 - ArchWiki](https://wiki.archlinux.jp/index.php/Qt_%E3%81%A8_GTK_%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%A4%96%E8%A6%B3%E3%81%AE%E7%B5%B1%E5%90%88)）

## メモリ使用量の比較
Arch系のディストリビューションEndeavourOSでは、複数のデスクトップ環境をインストールしておいて切り替えて使用することができる。
これを仮想マシン上にインストールして、各デスクトップ環境ごとにメモリ使用量を比較してみた。
※ 起動直後にtopを実行し、uptimeが1分になったときの数値を記録した

| デスクトップ環境 | メモリ使用量(MB) |
|:-:|:-:|
|  Cinnamon | 817.9  |
|  GNOME | 762.6  |
|  Plasma | 760.6  |
|  Xfce | 683.3  |
| LXQt  |  597.6 |
|  MATE | 539.0  |
|  LXDE | 468.3  |
|  i3 | 395.0  |

## 感想
概ね想像通りの結果となった。Xfceについては思ったよりメモリ使用量が多い印象を受けるが、EndeavourOSデフォルトのXfce環境が（透過が有効だったりと）割とグラフィカルだったので、本来はもう少し軽量かもしれない。

今時のPCならそこまでメモリについてシビアではないだろうし、Qt/GTKの違いもさほど問題にはならないようなので、見た目が気に入るのを選ぶのが良さそう
（ただ、今回の検証ではPlasmaだけ明らかにログイン後の待ち時間が長かった。環境にもよるだろうが、パフォーマンス重視なら他のものが良いか）


## 参考サイト
+ [【第8回】デスクトップ環境ってなんだろう？ - PC Watch](https://pc.watch.impress.co.jp/docs/column/ubuntu/1430476.html)
+ [KDE - Wikipedia](https://ja.wikipedia.org/wiki/KDE)
+ [KDE Software Compilation 4 - Wikipedia](https://ja.wikipedia.org/wiki/KDE_Software_Compilation_4)
+ [GNOME - Wikipedia](https://ja.wikipedia.org/wiki/GNOME)
+ [LXDE - Wikipedia](https://ja.wikipedia.org/wiki/LXDE)
+ [【コラム】QtとGTK　アプリケーションの見た目とGUIツールキットのお話 - クロの思考ノート](http://note.kurodigi.com/column-gtkandqt/)
[GCC] [issue] install gcc-13.1.0 from source on M2 MacBook Air

メモ:
  M2のMacbookにgcc-13.1.0をsourceからInstallしようとしたら、makeでErrorになった。

```shell
export GCC_INSTALL_TOP=${PWD}/gcc
export GCC_VERSION=13.1.0
export PATH=${GCC_INSTALL_TOP}/bin:${PATH}
export LD_LIBRARY_PATH=${GCC_INSTALL_TOP}/lib:${GCC_INSTALL_TOP}/lib64:${LD_LIBRARY_PATH}
```
とPathの設定をした後に
```shell:
mkdir gcc
cd gcc

wget http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-${GCC_VERSION}/gcc-${GCC_VERSION}.tar.gz
export GCC_TAR_FILE=`ls gcc-*tar.gz`
tar -zxvf ${GCC_TAR_FILE}
export GCC_EXTRACTED_DIR=`ls -d gcc*/`
cd ${GCC_EXTRACTED_DIR}
./contrib/download_prerequisites
mkdir build
cd build
../configure --enable-languages=all --prefix=${GCC_INSTALL_TOP} --disable-boostrap --disable-nultilib
make
make install
```
とすれば、gcc-13.1.0が入るはずなのだが(CentOS7ならInstallできる)、make実行時に下記のErrorが出る。 
```
checking for AF_UNIX... yes
checking for AF_INET6... yes
checking for _LK_LOCK... no
checking if mkdir takes one argument... no
*** Configuration aarch64-apple-darwin21.6.0 not supported
make[2]: *** [configure-stage1-gcc] Error 1
make[1]: *** [stage1-bubble] Error 2
make: *** [all] Error 2
```
M2のMacbookは無理なのかも。
Deno Deployでアクセスログ【Deno KV】

サーバーにアクセスがあるか確認したいときにおすすめです。Webhookなどのデバッグも可能です。

```bash
curl https://acc.deno.dev/any_access_path_hogehoge
```

<p class="codepen" data-height="720" data-default-tab="result" data-slug-hash="NWOwQJZ" data-user="04" style="height: 720px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/04/pen/NWOwQJZ">
  Untitled</a> by John Doe (<a href="https://codepen.io/04">@04</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

削除するには行をクリックしてね

```typescript
import { serve } from "https://deno.land/std@0.177.0/http/server.ts"

const kv = await Deno.openKv()

const init = {
    headers: {
      "content-type": "text/html",
    }
}

function escapeHtml(unsafe)
{
    return unsafe
         .replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "&#039;");
 }

const toHTML = logs => {
    const trs = logs.map(log => `<tr onclick="event.target.tagName === 'TD' && event.target.parentNode.parentNode.removeChild(event.target.parentNode) && fetch('https://a.github.day/${log.key.join('/')}')" title="クリックして削除">
        <td>${log.value.connInfo.remoteAddr.hostname}</td>
        <td nowrap>${new Date(log.key[1]).toLocaleString('sv-SE', { timeZone: 'Asia/Tokyo' })}</td>
        <td>${log.value.method}</td>
        <td nowrap>${log.value.url}</td>
        <td nowrap><details><pre>${escapeHtml(JSON.stringify(log.value, null, 2))}</pre></details></td>
      </tr>`).join('\n')
    return `<meta charset="utf-8"><table border>
    <thead>
        <tr>
            <th>Remote Host</th>
            <th>Time</th>
            <th>Method</th>
            <th>URL</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        ${trs}
    </tbody>
</table>
<style>
body {
    margin: 0
}
table {
    border-collapse: collapse
}
tr:hover {
    background: #f5f5f5
}
td {
    padding: 0 4px
}
</style>`
}

serve(async (req: Request, connInfo: ConnInfo) => {
    if (req.url.startsWith('https://a.github.day/logs/')) {
        await kv.delete(['logs', parseInt(new URL(req.url).pathname.split('/').pop(), 10)])
        return new Response('CLEARED', { headers: { 'Access-Control-Allow-Origin': '*' } })
    }
    const key = ['logs', Date.now()]
    const value = { headers: Object.fromEntries([...req.headers]), url: req.url, method: req.method, connInfo, env: Deno.env.toObject() }
    if (['POST', 'PUT'].includes(req.method)) value.body = await req.text()
    kv.set(key, value)
    if (req.url.includes('no-response')) return new Response()
    const logs = []
    for await (const log of kv.list({ prefix: ['logs'] }, { reverse: true })) logs.push(log)
    return new Response(toHTML(logs), init)
})
```
MariaDB 10.6 から MariaDB 10.11 へのアップグレード方法

この記事では、MariaDB 10.6 から MariaDB 10.11 へのアップグレード方法について説明します。以下の手順に従ってアップグレードを行ってください。

# 1. リポジトリ設定を変更

システムのパッケージマネージャが MariaDB 10.11 をインストールするようにリポジトリ設定を変更します。

- Debian, Ubuntu, および同様の Linux ディストリビューションの場合、[Updating the MariaDB APT repository to a New Major Release](https://mariadb.com/kb/en/installing-mariadb-deb-files/#updating-the-mariadb-apt-repository-to-a-new-major-release) を参照してください。
- RHEL, CentOS, Fedora, および同様の Linux ディストリビューションの場合、[Updating the MariaDB YUM repository to a New Major Release](https://mariadb.com/kb/en/yum/#installing-mariadb-packages-with-yum) を参照してください。
- SLES, OpenSUSE, および同様の Linux ディストリビューションの場合、[Updating the MariaDB ZYpp repository to a New Major Release](https://mariadb.com/kb/en/installing-mariadb-with-zypper/#updating-the-mariadb-zypp-repository-to-a-new-major-release) を参照してください。

# 2. MariaDB を停止

MariaDB サービスを停止します。

# 3. 古いバージョンの MariaDB をアンインストール

MariaDB 10.6 のアンインストール方法はディストリビューションによって異なります。

- Debian, Ubuntu, および同様の Linux ディストリビューションで以下を実行します:
  ```
  sudo apt-get remove mariadb-server
  ```
- RHEL, CentOS, Fedora, および同様の Linux ディストリビューションで以下を実行します:
  ```
  sudo yum remove MariaDB-server
  ```
- SLES, OpenSUSE, および同様の Linux ディストリビューションで以下を実行します:
  ```
  sudo zypper remove MariaDB-server
  ```

# 4. 新しいバージョンの MariaDB をインストール

MariaDB 10.11 のインストール方法はディストリビューションによって異なります。

- Debian, Ubuntu, および同様の Linux ディストリビューションの場合、[Installing MariaDB Packages with APT](https://mariadb.com/kb/en/installing-mariadb-deb-files/#installing-mariadb-packages-with-apt) を参照してください。
- RHEL, CentOS, Fedora, および同様の Linux ディストリビューションの場合、[Installing MariaDB Packages with YUM](https://mariadb.com/kb/en/yum/#installing-mariadb-packages-with-yum) を参照してください。
- SLES, OpenSUSE, および同様の Linux ディストリビューションの場合、[Installing MariaDB Packages with ZYpp](https://mariadb.com/kb/en/installing-mariadb-with-zypper/#installing-mariadb-packages-with-zypp) を参照してください。

# 5. 設定ファイルの変更

必要に応じて、`my.cnf` などの設定ファイルでオプションを変更します。これには、サポートされなくなったオプションの削除も含まれます。

# 6. MariaDB を起動

新しいバージョンの MariaDB を起動します。

# 7. `mariadb-upgrade` を実行

最後に、`mariadb-upgrade` を実行します。これにより、以下の2つの作業が行われます。
```
sudo mariadb-upgrade -u root -p
```

1. `mysql` データベース内のシステムテーブルが新しいバージョンと完全に互換性があることを確認します。
2. すべてのテーブルを素早くチェックし、新しいバージョンの MariaDB と互換性があることをマークします。

以上が、MariaDB 10.6 から MariaDB 10.11 へのアップグレード手順です。これらの手順に従って進めることで、スムーズにアップグレードが完了します。

## 参考
https://mariadb.com/kb/en/upgrading-from-mariadb-10-6-to-mariadb-10-11/
Flutterでawaitしない非同期処理内で投げられたエラーに対応する

# なんだこれは

以下のようなコードのエラー、キャッチできませんよね（多分）
非同期で処理を動かしつつ別の操作をユーザにはやらせるも、裏で動いてる非同期処理でエラーが起きた場合などにダイアログを出したい！みたいなときに詰まりました。
それに対応したときの方法をご紹介します
きれいな解決方法ではないです。その点はご了承下さい。いい方法あったら知りたい‥

```dart
class HogeWidget {
  Widget build(Context buildContext) {
    return ElevatedButton(
      child: Text('hoge');
      onPressed: () {
        try {
          fuga();
          Navigator.of(context).pushNamed('/aaaa');
        } on Exception catch(e) {
          // ここには辿り着けない
        }
      }
    );
  }
}

// 初期化っぽい処理とか。この中の処理はawaitしないのでasyncも付けない。
void fuga() {
  // 適当な処理がここに入る
  piyo();
  // 適当な処理がここに入る
}

Future<void> piyo() async {
  throw Exception('突然の死');
}
```

# こうする

`fuga`関数にエラー時の処理を渡すことにしました。めんどくさいね。

```dart
class HogeWidget {
  Widget build(Context buildContext) {
    return ElevatedButton(
      child: Text('hoge');
      onPressed: () {
        fuga(
          onError: () {
            // ダイアログ出したりする
          },
        );
        Navigator.of(context).pushNamed('/aaaa');
      }
    );
  }
}

// 初期化っぽい処理とか。この中の処理はawaitしないのでasyncも付けない。
void fuga({void Function()? onError}) {
  // 適当な処理がここに入る
  piyo().catchError((_) => onError == null ? null : onError());
  // 適当な処理がここに入る
}

Future<void> piyo() async {
  throw Exception('突然の死');
}
```

# さいごに

人類は未だに使いやすくて安全なエラー処理にたどり着いていないような気がする。
バイナリサーチとは

<h1>・バイナリサーチとは</h1>
　<b>バイナリサーチ</b>とは、効率的な値の検索をすることのできる手法の一つです。その方法ははデータを半分に分けて調べます。例えば11個のデータがある場合、6番目が中央に当たる値です。ここを起点に①<b>中央に当たる値と同じとき</b>、②<b>中央より小さいとき</b>、③<b>中央寄り大きい時</b>の３段階に分け、値を探していく方法です。これを使う際の注意点としては<b>値が整列されること</b>が求められる点です。<br>

<h1>・具体的な方法</h1>
<h3>・アルゴリズムのイメージ</h3>

![バイナリサーチ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850098/0b13b966-a35f-e4ab-8687-e9ec9e8566ee.gif)
　中央値と探したい値を比較＝＞同じなら終了
　　　　　　　　　　　　　 ＝＞探したい値が大きい＝中央値より右を探す
　　　　　　　　　　　　　 ＝＞探したい値が小さい＝中央値より左を探す

<h3>・具体的な手順</h3>
①用意する変数・データ<br>
　探したい値<br>
　データの配列<br>
　探すデータの左端を指す値<br>
　探すデータの右端を指す値<br>
　データの中央値<br>
　検索結果<br><br>

②手順
<ol><li>中央値と探したい値の比較し、大きさの結果を出す</li>
<li>大きいのなら探す範囲を中央値より右にする＝中央値+1～配列のサイズ分のデータの探索<br>小さいのなら探す範囲を配列の初め~中央値-1にする</li>
<li>ループ文で比較をして結果を出す</li>
</ol>

③Javaで書いてみると

```Java:バイナリサーチ.java
import java.util.Scanner;

public class BinarySearch {
    public static void main(String[] args) {
        int[] data = {2, 3, 4, 5, 6, 8, 10, 12, 19, 20, 30};
        Scanner sc = new Scanner(System.in);
        //探したい値
        int searchValue = sc.nextInt();
        //結果を入れる、-1なら見つからなかったという意味
        int findID = -1;
        //探す左端の値
        int left = 0;
        //探す右端の値（配列は0から始まるので配列の大きさから１を引く(length-1をする））
        int right = data.length - 1;

        //探す値が無くなると終了＝＞どちらかが追い付けば終了
        while(left <= right) {
            //中央値
            int middle = (left + right) / 2;
            
            if(data[middle] == searchValue) {
                findID = middle;
                break;
            } else if (data[middle] < searchValue) {
                left = middle + 1;
            } else {
                right = middle - 1;
            }
        }

        if(findID == -1) {
            System.out.println("値は見つかりませんでした。");
        } else {
        System.out.println("値は配列の"+findID + "番目にあります。");
        }
    }
}
```
【ChatGPT】ChatPDFによるPDFななめ読みの時短

# ChatPDFでPDFななめ読み

## 1.はじめに
ざっくり内容を確認したいPDF文書がある時の便利ツール「ChatPDF」を使ってみた。
超速でPDFの概要を把握できる。
概要をつかんでから熟読するもよし。

## 2.ChatPDFとは
ChatPDFとは、OpenAI社が提供するAIチャットボット「ChatGPT」を利用したサービス。
PDFファイルをアップロードすると、その内容に関する質問に対話形式で回答したり、文書要約を行ったりできる。

[ChatPDFホームページ](https://www.chatpdf.com/)
![ChatPDF.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933978/e5942a24-a804-6635-08be-a78e9bcad517.jpeg)

# 3.利用方法
### PDFファイルアップロード
ホームページのDrop PDF here フィールドにPDFファイルをドロップする。
ドロップしたPDFファイルが読み込まれ解析される。
![ChatPDF_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933978/651331f1-b177-0d7a-fd43-77ef2898a053.jpeg)

### 提案される質問を聞いてみる
提案された質問をクリックすると回答が返ってくる。
![ChatPDF_3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933978/9f2eecb0-7a40-dc4d-dd31-536009218030.jpeg)

### 興味のあることを聞いてみる
興味のあることを聞くと回答が返ってくる。
![ChatPDF_4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933978/b3de7508-f443-00af-f895-2b605667e4fa.jpeg)

# 
チームハイエナ <img width="120" alt="hyaeninae_tomei.png"  src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933978/99b448d3-7ff0-58f3-302b-8528cf339b8d.png">
【Git】カレントディレクトリがGitリポジトリか確認する方法

## 確認方法

次のコマンドを実行するとカレントディレクトリがGitリポジトリであれば`true`,Gitリポジトリでなければ`false`を返します。

```terminal
git rev-parse --is-inside-work-tree
```
【GAS】列の追加・削除に対応する柔軟なコードを書きたい

# 想定シーン

下記のようなテーブルの特定の列に対して何か処理をしたい時
![qiita_s1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700780/48e56266-65a1-40d3-e172-79aa01f6d38f.png)
例えば`氏名`の列を1行ずつ確認し、該当の`氏名`の場合のみ何か処理を行うには`氏名`の列番号を取得しておく必要があります。


```JavaScript:コード.gs
const nameColumn = 3; // 氏名の列番号が必要
for (let row=firstRow; row<=lastRow; row++) {
    if (sheet.getRange(row, nameColumn).getValue() == "対象の氏名"){
        // 処理
    }
}
```


# 問題点

```JavaScript:コード.gs
const nameColumn = 3;
```
上記のように列番号を直接指定してしまうと列が追加されたり、削除された場合に思わぬ不具合を招いてしまいます。

![qiita_s2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700780/232e8df1-ec28-ddd6-7cea-55174a14c0df.png)


# 解決策
カラム名を指定して該当する列番号を返すような関数を作ってみました。
```JavaScript:コード.gs
/**
* 指定したカラム名の列番号を返す
* @params {sheet} sheet 対象のシート
* @params {num} headerRow ヘッダ-(見出し)の行
* @params {num} firstColumn ヘッダー開始列
* @params {string} columnName カラム名
* @return {num|boolean} column|false 列番号/見つからない場合false
*/
function getColumnByName(sheet, headerRow, firstColumn, columnName) {
    const lastColumn = sheet.getLastColumn();
    // 見出し配列を取得(二次元配列で取得されるため添字0指定)
    const headers = sheet.getRange(headerRow, firstColumn, 1, lastColumn - firstColumn + 1).getValues()[0];
    // カラム名の添字を取得
    const columnIndex = headers.indexOf(columnName);
    // カラム名が見つからない場合はアラート表示後falseを返す
    if (columnIndex == -1) {
        SpreadsheetApp.getUi().alert(columnName + "列が見つかりません");
        return false;
    }
    // 列番号(配列の添字とヘッダー開始列番号を足す)を返す
    return firstColumn + columnIndex;
}
```
これで列の増減にも対応が可能になりました。
カラム名が変更された場合にもアラートが表示されるようになっています。

# 改善点
まだ引数が多い(ヘッダーの行番号と開始列を指定する必要がある)ところがすっきりしない感じがするのでいい方法がありましたらご教示いただけると幸いです。
【Linux】LVMについて

# はじめに
　Linuxの記事を書こうと思った経緯としては、LPIC-1を取得、LPIC201を合格し、これからLPIC202を受験しようとする中でアウトプットの大切に気づき、特に重要な内容についてQiitaを使いまとめていこうと思いました。今後としては、LVMやApache2などの知識と実際にVirutual Boxでの環境構築の記事を挙げていこうと考えています。




## LVMとは何か。
　まず、LVMとはLogical Volume Managerの略であります。複数のハードディスクやパーティションを、単一の仮想的な論理ボリューム(LV)にまとめることができます。この仮想的な論理ボリュームは、必要に応じて分割、拡張、縮小することができる。




## メリット
　LVMを使用することで、ストレージの使用率を最適化し、ストレージ管理を柔軟かつ効率的に行うことができます。また、複数の物理的なハードディスクやパーティションを単一の仮想的な論理ボリュームにまとめることにより、ディスク管理が簡素化され、管理作業の効率性が向上します。




## LVMで知っておくべき最低限の用語

*1*. **物理ボリューム (Physical Volume, PV)**：物理ボリュームは、LVMで使用される物理的なストレージデバイスを表します。通常、ハードディスクやパーティションが物理ボリュームとして使用されます。

*2*. **ボリュームグループ (Volume Group, VG)**：ボリュームグループは、複数の物理ボリュームをまとめて作成される仮想的な論理グループです。

*3*. **論理ボリューム (Logical Volume, LV)**：論理ボリュームは、ボリュームグループ内で作成される仮想的な論理デバイスです。論理ボリュームは、パーティションのように使用でき、ファイルシステムを作成することができます。

*4*. **ボリュームグループの拡張 (Volume Group Extension)**：物理ボリュームをボリュームグループに追加することにより、ボリュームグループの容量を拡張することができます。

*5*. **ファイルシステム(File system, fs)**：データを保存することの仕組みであり、ファイルシステムを使用することで、ユーザーはデータを管理し、アプリケーションが必要とするデータを効率的にアクセスすることができます。代表的なファイルシステムとしては、WindowsでのNTFS、Linuxでのext4、XFSなどがあります。




# おわりに
　次の記事では実際に仮想環境でLVMを触っていこうと思います。
【qwik】qwikを動かすdocker環境の構築手順

# はじめに
最近注目されているフロンドエンド、qwikをdocker環境で作ります。
公式の手順は下記にありますが、dockerで動かそうとしたとき若干苦戦したのでメモです。
[Getting Started Qwikly](https://qwik.builder.io/docs/getting-started/)


# 手順
## 1. docker-compose.ymlの準備
```yml
version: '3.9'

services:
  qwik:
    image: node:latest
    ports:
      - "5173:5173"
    volumes:
      - .:/app
    tty: true

```

imageはnodeの最新版を、qwikは5173ポートを使用するのでつなげておきます。
仮想環境での開発は、/app/qwik-appディレクトリを使用する想定です。

## 2. qwikのインストール
### 1. 仮想環境にログインします
```bash
$ docker compose up -d
$ docker compose exec qwik bash
```

### 2. qwikをインストールします
```bash
$ cd /app
# qwikのインストール。表示される選択肢は、お好みで
$ npm create qwik@latest
$ cd /app/qwik-app
$ npm install
```

## 3. 手直し
### 1. vite --openをやめる
```diff:package.json

  "scripts": {
    "build": "qwik build",
    "build.client": "vite build",
    "build.preview": "vite build --ssr src/entry.preview.tsx",
    "build.types": "tsc --incremental --noEmit",
    "deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'",
    "dev": "vite --mode ssr",
    "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
    "fmt": "prettier --write .",
    "fmt.check": "prettier --check .",
    "lint": "eslint \"src/**/*.ts*\"",
--    "preview": "qwik build preview && vite preview --open",
++    "preview": "qwik build preview && vite preview",
--    "start": "vite --open --mode ssr",
++    "start": "vite --mode ssr",
    "qwik": "qwik"
  },
```

仮想環境はCLI環境なので、ブラウザを開こうとしたらエラーが発生します。
ので、そういった挙動は削除しておきます。

### 2. ホスト環境からアクセスできるようにする
```diff:package.json

  "scripts": {
    "build": "qwik build",
    "build.client": "vite build",
    "build.preview": "vite build --ssr src/entry.preview.tsx",
    "build.types": "tsc --incremental --noEmit",
    "deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'",
    "dev": "vite --mode ssr",
    "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
    "fmt": "prettier --write .",
    "fmt.check": "prettier --check .",
    "lint": "eslint \"src/**/*.ts*\"",
--    "start": "vite --mode ssr",
++    "start": "vite --mode ssr --host=0.0.0.0",
    "qwik": "qwik"
  },
```

仮想環境内からだけアクセスできてもしょうがないので、
外(ホストサーバ)からでもアクセスできるように設定します。

## 3. 完成
サーバを立ち上げ、ホスト環境から確認します

### 1. サーバ立ち上げ
```bash
$ npm start
```

### 2. アクセス
ホスト環境から、ブラウザを使い下記にアクセス
[http://localhost:5173/](http://localhost:5173/)
AWS CDKを使うためTypeScriptに入門したので図解してみた

AWSが提供するIaCツール、CDKを利用する機会ができたので入門してみました。
IaCといえばTerraform一強でしたが、CDK v2の登場によりCDK派も急速にシェアを伸ばしています。

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

CDKはプログラミング言語を使ってアプリケーションのようにインフラコードを記述します。
複数の言語に対応していますが、最もシェアが高く推奨されているのがTypeScriptです。


# AWS提供のワークショップ教材が超分かりやすい！

インフラエンジニアな私はプログラミング素人のため、AWSが無料で公開している以下のワークショップ教材でTypeScriptの基礎を学びました。

- TypeScript の基礎から始める AWS CDK 開発入門

https://catalog.workshops.aws/typescript-and-cdk-for-beginner/ja-JP

### このワークショップの素晴らしいところ
- CDKの利用に必要最低限なTypeScriptの知識習得のみにフォーカスしている
- 説明が非常に合理的＆簡潔でとても理解しやすい！（ここが一番秀でている）
- 実際にCDKを動かすところまでハンズオンできる


# ワークショップで学んだTypeScriptの基礎を簡単に紹介

基本概念を図にしてみました。
![スクリーン ショット 2023-05-04 に 20.24.55 午後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/2c7b60ef-8b4d-9d66-9b2c-a0eb5a15f962.png)

以下、プログラミング素人の私が抱いた疑問と、入門して見えてきたことをシェアします。

### 関数、オブジェクト、クラス…なぜややこしい概念が多数登場するのか？
すべては「必要な実装をなるべく効率よく記述するため」だと感じました。
似たような機能のコードを共通化したり、変更が必要な際の修正箇所をなるべく少なくする…等。

### this って何やねん！
直感的に理解しづらく最初は戸惑ったのですが、this ＝「ここ！」と覚えるのが良さそうです。

thisは「現在のコンテキスト」を指す変数なので、つまりthisが書かれている箇所が所属するクラス（＝CDKで言うとスタックも）やオブジェクト（＝クラスから作成される場合はインスタンス）を指します。
![スクリーン ショット 2023-05-04 に 21.34.00 午後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/1c0f5d9b-55ac-a65c-907f-01d0bea357c4.png)
よく分からないthisに遭遇した際は、「thisを使わずに記述すると何（＝どのクラスやオブジェクト）か？」を考えてみると理解が進むと思います。


### イカみたいなやつ => （アロー関数🦑）
アロー関数を一言でいうと「関数式をラクして書く方法」です。

- TypeScriptで関数を宣言する際、本来は `function` で関数名を定義する
- しかし実用上は関数名って不要なケースが結構ある（変数に格納して使ったり、一回きりの処理だったり等）
- そのため「関数名の宣言をすっ飛ばして引数＋処理のみを記述すればいい」のがアロー関数！

一応おさらいしておくと、関数とは「引数を入れたら、それを使って動くマシーン」のようなものです。
ちなみにTypeScriptの世界では、クラスやオブジェクトの中で登場する関数のことは「メソッド」と呼びます。


## ワークショップをやってみる方向けの補足

### VS Codeで簡単にコードを動かす方法
私はCloud9ではなく自分のローカルMacでVS　Codeを利用してワークショップを実施したのですが、作成したTypeScriptコードを手元で簡単に実行するにはどうすればいいか少し困りました。

本来、TypeScriptのコード（.tsファイル）はJavaScriptのコード（.jsファイル）に変換して実行するのですが、ts-nodeというモジュールを使うことで.tsファイルのまま実行結果を確認できるようになるみたいです。

以下コマンドでインストールできます。

```zsh:ターミナル
% sudo npm install ts-node
```

これを入れたらVS Codeの拡張機能「TypeScript」をインストールして、ウィンドウ左カラムのメニューから「実行とデバッグ」を選択して実行に進み、複数提案される選択肢から「TypeScript」を選べばts-nodeが左カラムから実行可能になります。

### コンストラクタの記述ってなんだか冗長では？
ワークショップ内で「コンストラクタ」の説明がありますが、クラス内で最初にいくつかの変数を宣言し、それらを初期化するためのコンストラクタの引数を再び定義する過程で同じような変数名を何度も書くことになるため「あれ？プログラミング言語ってこういう重複作業はだいたい簡略化してくれるので、もっと上級編だとシンプルに書けるのでは？」と気になりました。

Twitterでつぶやいたところ、JAWS-UG CDK支部運営のゆっきー（@WinterYukky）さんが速攻で解説してくださいました。ありがとうございます！🙇‍♂️

https://twitter.com/WinterYukky/status/1653735907680468995

### 継承先のクラスで元あったプロパティを操作するには？
ワークショップ内「クラスの継承」のサンプルコードにおいて、継承先の子クラス内で親クラスから引き継いだプロパティを書き換えたい（例：`Teacher`クラスにおいて、継承元の`Person`クラスから引き継いだ変数`firstName`に新しい値をセットしたい）と思ったのですが上手くできませんでした。

ChatGPTさんに聞いたところ、サンプルコード内でも使っていた`super()`というやつ（メソッドではないらしい）の引数に、操作したい継承元のプロパティを与えてあげればいいと分かりました。
実際に`super(firstName);`のようにしてあげると、先生に新たな名前を付けることができました。


# おわりに
ここまで読んでくださってありがとうございました。

この記事もかなり端折って書いているので、どこかで機会を見つけたら **「これからCDKやっていき！なインフラエンジニアのためのTypeScript速攻入門」** 的な図解満載のライトニングトークを披露したいなと思っています。
（私自身、複雑な概念を習得するときはイラストにして右脳に染み込ませないと理解が進まないタイプです）

冒頭にもお話ししたように、私はプログラミング素人のため本解説や解釈に誤りがあるかもしれません。正しい知識を身につけたいので、もし誤りを見つけた際はコメント等で（やさしく）教えていただけますと大変幸いです🙇‍♂️
【Go言語】お手軽Exponential Backoff And Jitter

「エクスポネンシャルバックオーーーーーーーフッ！！！」

技名ではなくリトライ時に使うアルゴリズムの話です。

# Exponential Backoff And Jitter とは
### Exponential Backoff
分散システムとかでネットワーク通信に失敗した時、成功するまでやり直す（リトライ）事ってあるよね。

でも通信に失敗した時ってネットワークや通信先サーバーの高負荷が原因だったりするから、
短時間に何度もリトライするとさらに負荷を増やしちゃうかもしれないんだ。

そんなときはExponential Backoff！
初回リトライまでの間隔は１秒、次は２秒、その次は４秒..８秒....みたいにリトライ間隔を指数関数的（Exponential）に後退（Backoff）させることでネットワークや接続先サーバーの過負荷を軽減さえられるんだ。

### Jitter
Exponential Backoffだけだとちょっと不安、例えば同時に100クライアントからアクセスされている場合、
１秒、２秒、４秒、８秒....のタイミングでドカッと100アクセスされてしまう可能性がある。

そんなときはJitter！
クライアントAはリトライ間隔を0.92秒、1.97秒、4.01秒、8.09秒...
クライアントBはリトライ間隔を1.06秒、2.03秒、3.97秒、7.92秒...

みたいに多少の揺らぎ（Jitter）を導入することでリクエスト集中の問題を解決できるよ。

# GO言語でExponential Backoff And Jitterをやってみる
本題に入る。
今回は簡単にExponential Backoff And Jitterができる「retry」パッケージを使うよ。

https://github.com/hoshiimo-se/retry


まずはコード
``` go
// リトライしたい処理を定義
op := func() (string, error) {
    fmt.Printf("some form of processing...（%v）\n", time.Now())
    return "", errors.New("Error!!")
}

// リトライしたい処理をRetry関数に渡す
result, err := retry.RetryOneResult(op)
```

↓出力↓
``` sh
some form of processing...（2023-05-04 22:41:23.8943312 +0900 JST m=+0.003652001）
some form of processing...（2023-05-04 22:41:24.1863735 +0900 JST m=+0.295694301）
some form of processing...（2023-05-04 22:41:24.684814 +0900 JST m=+0.794134801）
some form of processing...（2023-05-04 22:41:25.5369253 +0900 JST m=+1.646246101）
some form of processing...（2023-05-04 22:41:27.5995704 +0900 JST m=+3.708891201）
Fail: Maximum number of retries reached
```

こんな感じで簡単にリトライができるよ。
[Go Playground](https://go.dev/play/p/KvHua2ZN_to)でも書いてみた。

デフォルトは最大５回のリトライで初回リトライまでが100ミリ秒、最大の遅延は10秒になるよ。
このあたりは引数で調整可能。

### 初回リトライを２秒、最大３回にしてみる
以下のように引数を増やしてあげると簡単に調整ができるよ。

``` go
// リトライしたい処理を定義
op := func() (string, error) {
    fmt.Printf("some form of processing...（%v）\n", time.Now())
    return "", errors.New("Error!!")
}

// リトライしたい処理をRetry関数に渡す（初回リトライを２秒、最大３回）
result, err := retry.RetryOneResult(op, retry.WithInitialDelay(2*time.Second), retry.WithMaxRetries(3))
if err != nil {
    fmt.Println("Fail:", err)
} else {
    fmt.Println("Succeed:", result)
}
```

↓出力↓
``` sh
some form of processing...（2023-05-04 22:50:57.5021196 +0900 JST m=+0.003110101）
some form of processing...（2023-05-04 22:51:01.5844456 +0900 JST m=+4.085436101）
some form of processing...（2023-05-04 22:51:10.9645184 +0900 JST m=+13.465508901）
Fail: Maximum number of retries reached
```
[Go Playground](https://go.dev/play/p/zasxuig-Dv3)でも書いてみた。

# おしまい
みんなもネットワークやサーバーに優しいリトライをしよう！！
文字の真ん中を返す（競技プログラミング）

わたしは、就活のため・自身の能力をあげるために競技プログラミングに取り組んでいます。

その中で、わたしが苦戦した問題があったので、紹介します。

<h2>対象の読者

・atcoderでB,C問題に苦戦している。
・PHPを使っている。


<h2>問題文

文字列 string を受け取り、文字数の半分を文字列の真ん中から返す、middleSubstring という関数を定義してください。

入力された文字列の長さが偶数の場合、文字列の真ん中から文字数の半分に相当する部分文字列を返します。例えば、入力が "ABCDEFGH" の場合、真ん中の部分文字列は "CDEF" です。入力された文字列の長さが奇数の場合、真ん中の文字を除いた、文字数の半分に相当する部分文字列を返します。例えば、入力が "ABCDEFG" の場合、真ん中の部分文字列は "CDE" です。

入力文字列の文字数が 2 以下の場合、最初の文字を返します。例えば、入力が "A" や "AB" の場合、"A" を返します。


例えば"ABCDE"という奇数個の文字が入力された場合は、以下のように返します。
```
BC
```

<h3>回答のコード<h3>



```
<?php
function middleSubstring(string $stringInput): string{
    // 文字列の長さ
    $len = strlen($stringInput);

    // 切り取られるサイズ
    $middle = floor($len/2);

    // 切り取りを始める前方の位置
    $front = ceil($middle/2);

    // 文字列の長さが2以下のときは、最初の文字を返す。
    if ($len <= 2) return $stringInput[0];

    // 前方から前方+middle分だけ切り取り
    return substr($stringInput,$front, $middle);
}

echo(middleSubstring("A")). PHP_EOL;// A
echo(middleSubstring("AB")). PHP_EOL;// A
echo(middleSubstring("ABC")). PHP_EOL;// B
echo(middleSubstring("ABCD")). PHP_EOL;// BC
echo(middleSubstring("ABCDE")). PHP_EOL;// BC
echo(middleSubstring("ABCDEF")). PHP_EOL;// CDE
```


<h2>解説

文字数の半分を len/2 と書き換えて、流れを見ていきましょう。

"A BC D" --> BC
"A BC DE" --> BC
"AB CDEF GH" --> CDEF
"AB CDEF GHI" --> CDEF
文字列のサイズが 4 または 5 の時、サイズの半分は 2、文字列のサイズが 8、9 の時、サイズの半分が 4 となっていることから、floor 関数を使って floor(len/2) と表現できることがわかります。これを middle としましょう。

"AB CDE F" --> CDE
"AB CDE FG" --> CDE
また、切り取られる文字サイズが奇数のパターンの前方に着目すると、前方は ceil(middle/2) になっていることがわかるでしょう。今求めたいのは真ん中の文字列なので、前方の最後から、前方+真ん中まで切り取ればいいことがわかります。substring 関数を用いて substring（前方, 前方 + 真ん中）と表現できます。
【Swift】アプリ内でメールを送信する

# はじめに
アプリ内で要望・問い合わせメールを送信機能を実装した。
今後、頻繁に使うと思われるので記録。

# 実装
①`MessageUI.framework`をXCodeに導入
②ViewControllerにメールを送信する処理を実装

``` swift:ViewController.swift
import UIKit
import MessageUI

final class ViewController: UIViewController {
    @IBOutlet private var sendMailButton: UIButton! {
        didSet {
            sendMailButton.addTarget(self, action: #selector(tapSendMailButton), for: .touchUpInside)
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

extension ViewController: MFMailComposeViewControllerDelegate {
    @objc func tapSendMailButton() {
        guard MFMailComposeViewController.canSendMail() else {
            self.showErrorAlert(title: "通信エラー", message: "メールを送信できませんでした")
        }
        
        let mail = MFMailComposeViewController()
        mail.mailComposeDelegate = self
        mail.setToRecipients([String.emailAddress])
        mail.setSubject(String.emailTitle)
        mail.setMessageBody(String.emailBody, isHTML: false)
        present(mail, animated: true, completion: nil)
    }
    
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        switch result {
        case .cancelled:
            print("キャンセル")
        case .saved:
            print("下書き保存")
        case .sent:
            print("送信成功")
        default:
            print("送信失敗")
            // TODO: アラートで送信できなかったことを通知する
        }
        dismiss(animated: true, completion: nil)
    }
}

private extension ViewController {
    func showErrorAlert(title: String, message: String) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let gotItAction = UIAlertAction(title: String.ok, style: .default)
        
        self.showAlert(alert: alert, actions: [gotItAction])
    }
}
```

# プレビュー
①iOSアプリ内でメールを開いている画面
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/802393/ac5c21d3-b1c6-a48e-d4c1-d27f29ea9ed0.png">
②メールを送信する場面で`MFMailComposeViewController.canSendMail()`がfalseだった場合のアラート
<img width="250" src="https://gyazo.com/1e4eac2450f987b5e2ed54dce128140f/raw">

# 終わりに
補足としては、メーラーのデリゲートメソッド、`mailComposeController`にて、「送信失敗」、「送信成功」といった結果に対してユーザーにフィードバックを返した方が親切。

# 参考リンク
[Message UI | Apple Developer Documentation](https://developer.apple.com/documentation/messageui)
[【Swift】アプリからメーラーを起動する方法](https://qiita.com/Simmon/items/24735d2211d67cf668f7)
[【Swift】アプリ内でメーラーを起動しメール送信するやり方](https://qiita.com/kobaboy/items/60a2f44ad53675cfa0ae)
[【Swift】 お問い合わせ機能の実装](https://qiita.com/1997/items/fd73c10dcc5a760149e3)
[iOSアプリ内でメール画面を開く [MFMailComposeViewController]](https://zenn.dev/altiveinc/articles/mail-compose-for-swift)
富士通メインフレームは○×□が不便　〜PCとのコラボ〜

TSS端末（IBMではTSO）は元々ダム(DUMB)端末と呼ばれており、画面サイズは`` 80Byte * 25 ``行でした。
パソコンの普及につれ、JCLをPCのテキストエディタで作成し、アップロード・ダウンロードするようになりました。
PCの方が文字数・行数ともにたくさん表示出来るからです。
ところがVSCodeは拡張機能としてz/OSのTSO端末接続機能があるらしく、アップロード・ダウンロードが自動化されているらしく羨ましいです。
それにOSIV/MSP用富士通提供製品では正規表現による検索ができませんが、VSCodeでは可能ですよね！

区分データセットの各メンバがJCLになっているライブラリを丸ごとダウンロードして、PCで正規表現を使った検索をしたいと思ったことがあります。メンバが多く、1つずつダウンロードする気にはなれません。

そこでJSDGENER（IBMではIEBGENER）の以下の4行を生成するExcel関数を考えました。
これは各メンバを連続した1つのファイにするもので、これをダウンロードしPC側で分割することにしました。
ただし前もってメンバ一覧を取得する必要があります。
メンバ名がMEMBER01の場合の例を以下に示します。

生成する４行
```
//MEMBER01 EXEC GENERX,MEM=MEMBER01
//MEMBER01.SYSIN DD *
  GENERATE
  RECORD   FIELD=(010,'MEMBER01     ',,001),FIELD=(080,001,,011)
```

入力ストリームプロシジャ（GENERX）
```
//GENERX   PROC MEM=
//&MEM     EXEC PGM=JSDGENER
//SYSPRINT DD  SYSOUT=*
//SYSUT1   DD  DISP=SHR,DSN=@UID.SPLIT.CNTL
//         DD  DISP=SHR,DSN=SHR.LIBLARY.CNTL(&MEM)
//SYSUT2   DD  DISP=MOD,DSN=@UID.LIBLARY.CNTL
//         PEND
```
Excel関数
```
=IF(LEN(B6)=0,
   "",
   IF(MOD(A6,4)=1,
      CONCATENATE("//",B6,REPT(" ",9-LEN(B6)),"EXEC MACROGNR,MEM=",B6),
      IF(MOD(A6,4)=2,
         CONCATENATE("//",B6,".SYSIN DD *"),
         IF(MOD(A6,4)=3,
            "  GENERATE",
            CONCATENATE("  RECORD   FIELD=(010,'",B6,REPT(" ",10-LEN(B6)),"',,001),FIELD=(080,001,,011)")
           )
        )
     )
  )
```

Excel

|No.|Menber|JCL|  
|:---:|:---:|:---|
|1|Member01|`//MEMBER01 EXEC GENERX`|
|2|Member01|`//MEMBER01.SYSIN DD *`|
|3|Member01|`　GENERATE`|
|4|Member01|`　RECORD   FIELD=(010,'MEMBER01  ',,001),FIELD=(080,001,,011)`|
|5|Member02|`//MEMBER02 EXEC GENERX`|
ReactアプリをGithub Pagesにデプロイする方法

# 1. Reactアプリ初期セットアップ
### Node.jsのインストール
Node.jsとはサーバサイド側で動くJavaScript。
https://nodejs.org/ja

### npmのインストール
npmとはNode Package Managerの略で、Node.jsのパッケージを管理するシステム、所謂パッケージ管理システム。
```:terminal
npm install -g npm
```

### Reactアプリ作成
Facrbook が提供している CLI ツールで用意されたテンプレートを元にアプリケーションの雛形を生成。
```:terminal
npx create-react-app my-app
```
起動確認を行う。
```:terminal
cd my-app
npm start
```
```:terminal
Compiled successfully!

You can now view my-app in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.XXX.XXX:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

webpack compiled successfully
```
下記のブラウザ画面が表示されていればOK。
![start.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/94fc3585-0aea-f145-b3b0-db8f8072c1d2.png)

# 2. Reactアプリ作成
public、srcフォルダ内にファイルを作成し、コーディングを行う。
ここでは割愛します。

# 3. ReactをGithub Pagesにデプロイ
推奨している`gh-pages`を使用したデプロイ方法の紹介です。

### 1. gh-pagesパッケージをインストール
```:terminal
npm install gh-pages --save-dev
```

### 2. package.jsonの編集
* package.jsonファイルに`homepage`プロパティを追加し、デプロイ先のURLを設定する
* scriptsオブジェクトにpredeployおよびdeployスクリプトを追加する
```json:package.json
"homepage": "https://<your-github-username>.github.io/my-app/"

"scripts": {
  "predeploy": "npm run build",
  "deploy": "gh-pages -d build"
}
```

### 3. npm run deployを実行する
```:terminal
npm run deploy
```

### 4. デプロイURLにアクセスし、確認する
GitHubリポジトリの[settings]に移動して、[GitHub Pages]セクションでデプロイされたアプリを確認。[Source]が[gh-pages]ブランチに設定されていることを確認。
![pages.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/b70c4314-277a-4190-cfc6-ea5a75ec78fd.png)

デプロイURLにアクセスして確認する。
(下記は実際に作成したtodoアプリ)
![app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/fedea855-993b-549e-dea2-3a3a8866160f.png)
【pandas-profiling/AutoViz/SweetViz 比較】EDAに役立つライブラリを比較してみた

# はじめに
データ分析/機械学習モデルを作成する時に欠かせないEDAですが、私はmatplotlibやseabornでグラフを作成する部分があまり得意ではないので、素早く網羅的にEDAするための有名なライブラリ3つ(pandas-profiring/autoviz/sweetviz)を使ってみて比較しました。
今回は[タイタニックのデータ](https://www.kaggle.com/competitions/titanic/data)を使用しています。

## 環境
- Python 3.9.13 
- jupyter notebook 6.4.12
- Numpy 1.21.5
- Pandas 1.4.4
- matplotlib 3.5.2
- pandas-profiling 3.6.6
- autoviz 0.1.601
- sweetviz 2.1.4

## サマリ
個人的な好みはAutoVizですが、どのライブラリも基本的な機能は揃っているのでお好みのライブラリを選択するのがいいと思います！
- [Pandas Profiling](https://github.com/ydataai/ydata-profiling)：最もシンプルでデータセットの概要を迅速にEDAが可能。

- [AutoViz](https://github.com/AutoViML/AutoViz)：少し速度は遅い印象だが、目的変数と各特徴量の関係性がわかりやすい。特に、各特徴量のおすすめな前処理を教えてくれるのがとても良い！

- [Sweetviz](https://github.com/fbdesignpro/sweetviz)：Pandas Profiling と同様の機能を持ちつつ、データセットの比較機能がついている。個人的にはモデル作成をする前に学習データとテストデータで偏りがないかを確認する際に使いたい。

## 出力結果(一部抜粋)

### pandas-profiling
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3356420/329c2c80-c421-0d60-9807-4a7f1652b4a5.png)

### autoviz
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3356420/556b298b-5793-3bbe-9c86-f9cae523f4d7.png)

### sweetviz
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3356420/4c35bc8d-9f4c-770c-b001-bf4687f5e96a.png)

## 実行コード
### titanicデータの読み込み
```python:titanic.ipynb
import pandas as pd
import numpy as np
%matplotlib inline
train = pd.read_csv("train.csv")
train
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3356420/e6bd883a-fac4-c75b-1b38-29ee9e03368f.png)

```python:titanic.ipynb
target = 'Survived'
```

```python:pandas-profiling.ipynb
!pip3 install pandas-profiling
from pandas_profiling import ProfileReport
profile = ProfileReport(train, title="Titanic data profiling Report")
profile
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3356420/1b3a5b74-03cc-bf5f-3062-c834018c5a0f.png)

```python:autoviz.ipynb
!pip3 install autoviz
from autoviz.AutoViz_Class import AutoViz_Class
AV = AutoViz_Class()
# depVarに目的変数、dfteにEDAしたいデータをセットする
dft = AV.AutoViz( "" ,sep=',', depVar=target, dfte=train, header=0, verbose=2,
lowess=False,chart_format='svg',max_rows_analyzed=1500,max_cols_analyzed=30)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3356420/556b298b-5793-3bbe-9c86-f9cae523f4d7.png)


```python:sweetviz.ipynb
!pip3 install sweetviz
import sweetviz as sv
from sklearn.model_selection import train_test_split

# 学習データと評価データを作成
# train_test_splitの第一引数に特徴量、第二引数に目的変数を入れる。
x_train, x_test, y_train, y_test = train_test_split(train.iloc[:, 2:12], train.iloc[:, 1],test_size=0.2, random_state=1)

df_train = pd.concat([x_train, y_train], axis=1)
df_test = pd.concat([x_test, y_test], axis=1)

#ノートブックに直接表示
my_report = sv.compare([df_train, "Train"], [df_test, "Test"], target)
my_report.show_notebook()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3356420/4c35bc8d-9f4c-770c-b001-bf4687f5e96a.png)

## 最後に
最後までありがとうございました。
【Swift】StringとIntの初期化の仕方一覧

# はじめに
コードを書いていて初期化についてよく分からなくなったので、一覧にしてまとめてみました。

# 一覧
コメントアウトしてる部分が初期値になります。

```swift
var string1 = ""                // ""
var string2 = String()          // ""
var string3 = String?(nil)      // nil
var string4 : String? = nil     // nil
    
var strArray1 = [String]()      // []
var strArray2 = [""]            // [""]
var strArray3 = [String?]()     // []
var strArray4 : [String]? = nil // nil
    
var number1 = 0                 // 0
var number2 = Int()             // 0
var number3 = Int?(nil)         // nil
var number4 : Int? = nil        // nil
    
var numArray1 = [Int]()         // []
var numArray2 = [0]             // [0]
var numArray3 = [Int?]()        // []
var numArray4 : [Int]? = nil    // nil
```

# 余談
`[]`は空の配列を表しますが、`[nil]`とは異なります。
`[nil]`は、Optional型を持つ要素が1つある配列、つまりnil値を持つ配列として表されます。

```swift
let emptyArray = []             // 空の配列を作成
let optionalArray = [nil]       // Optional型の要素が1つある配列を作成

print(emptyArray.count)         // 0
print(optionalArray.count)      // 1

print(emptyArray.isEmpty)       // true
print(optionalArray.isEmpty)    // false

print(type(of: emptyArray))     // Array
print(type(of: optionalArray))  // Array<Optional<Any>>
```

# 終わりに
一覧にしてみたらだいぶスッキリしました。
ScrcpyHub v2.1.0 をリリースしました

![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/5a897c97-9be5-ca3e-da0b-59fcf8e352bc.png)

## はじめに

ScrcpyHubはAndroidの画面をミラーリングするためのGUIアプリケーションです。ScrcpyHubは以下のGitHubリポジトリで絶賛開発中ですのでよければコントリュート＆スター登録お願いします。

https://github.com/kaleidot725/ScrcpyHub

ScrcpyHubはscrcpyというコマンドツールを利用して作成しております。

https://github.com/Genymobile/scrcpy

## できること

### 機能一覧

ScrcpyHubでは以下の機能をサポートしています。  

- 接続しているAndroidデバイスの自動検知
- Androidデバイスのミラーリングの開始と停止
- Androidデバイスのスクリーンの録画
- Androidデバイスのスクリーンショットの取得
- Windows10/11、macOS、Linuxに対応
- ライトテーマ・ダークテーマの切り替え
- オーディオのオプションの対応
- デバイスのオプションの対応
- HIDキーボード・マウスのオプション対応
- **NEW v2.1.0** ミニウィンドウモード

### 機能デモ

ScrcpyHubでは以下のような感じでAndroidデバイスのミラーリングを制御できます。

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/75175a2b-29e6-cc69-53ad-7fffaeafcddf.gif)

### **NEW v2.1.0** ミニウィンドウモード

scrcpyのv2.1.0からミニウィンドウモードをサポートしました。ミニウィンドウモードはデバイスのみを表示するモードです。ミニウィンドウモードを利用すると通常表示に比べて表示される情報を少なくできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/470eec44-7286-b221-f08e-a6e1fa3ff737.png)

## インストール方法

ScrcpyHubを利用するにはadbとscrcpyコマンドのインストールが必要になります。  
OSごとの手順が異なるので、以下の手順を参考にして従ってインストールして使ってください。  

### 💻Windows10/11

1. [GitHub](https://github.com/Genymobile/scrcpy#windows)からscrcpy(v2.0.0+)を任意の場所にダウンロードしてインストールする
2. [GitHub](https://github.com/kaleidot725/ScrcpyHub/releases/tag/v2.1.0)でScrcpyHubをインストールして起動する
3. ScrcpyHubのPreferencesを開く
4. adbとscrcpyのパスを通して、Saveを押す

※ 詳しい手順はこちらに記載がありますのでご確認ください。

https://github.com/kaleidot725/ScrcpyHub/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95-:-Windows%E7%89%88

### 💻MacOS

1.android-platform-toolsとscrcpy(v2.0.0+)をbrewコマンドでインストールする
2.[GitHub](https://github.com/kaleidot725/ScrcpyHub/releases/tag/v2.1.0)でScrcpyHubをインストールして起動する
3.ScrcpyHubのPreferencesを開く
4.adbとscrcpyのパスを通して、Saveを押す

```other
brew install android-platform-tools
brew install scrcpy
```

## 問い合わせ先

ScrcpyHubは以下のレポジトリで絶賛開発中です、もし何か改善案や不具合などあればご連絡ください。

https://github.com/kaleidot725/ScrcpyHub
【SwiftUI】URLを開く

# はじめに
私は今まで`UIApplication.shared.open`を使用してURLを開いていましたが、`openURL`という環境値が追加されていて、SwiftUIではこちらを使うべきなのでは？？と思ったので記事にしておきます。

# 古い
```swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        Button {
            UIApplication.shared.open(URL(string: "https://qiita.com/SNQ-2001")!)
        } label: {
            Text("URLを開く")
        }
    }
}
```

# 新しい
```swift
import SwiftUI

struct ContentView: View {
    @Environment(\.openURL) var openURL
    var body: some View {
        Button {
            openURL(URL(string: "https://qiita.com/SNQ-2001")!)
        } label: {
            Text("URLを開く")
        }
    }
}
```

# おわり
https://developer.apple.com/documentation/swiftui/openurlaction

この辺は面白いことができそうなのでよく調べておきたいです

```swift
Text("Visit [Example Company](https://www.example.com) for details.")
    .environment(\.openURL, OpenURLAction { url in
        handleURL(url) // Define this method to take appropriate action.
        return .handled
    })
```