OmniAICreator/Qiita-1.07M huggingface dataset:
goldfish-models/jpn_jpan_1000mbは『雪国』の冒頭をどうトークナイズするのか

Tyler A. Chang, Catherine Arnett, Zhuowen Tu, Benjamin K. Bergen『[Goldfish: Monolingual Language Models for 350 Languages](https://arxiv.org/abs/2408.10441)』を横目に、日本語GPT2モデル[goldfish-models/jpn_jpan_1000mb](https://huggingface.co/goldfish-models/jpn_jpan_1000mb)のトークナイザを試してみた。

```
>>> from transformers import AutoTokenizer
>>> tkz=AutoTokenizer.from_pretrained("goldfish-models/jpn_jpan_1000mb")
>>> print(tkz.convert_ids_to_tokens(tkz("国境の長いトンネルを抜けると雪国であった。","夜の底が白くなった。")["input_ids"]))
['[CLS]', '▁', '国境', 'の', '長い', 'トンネル', 'を', '抜ける', 'と', '雪', '国', 'であった', '。', '[SEP]', '▁夜', 'の底', 'が', '白', 'くなった', '。', '[SEP]']
```

残念ながら、日本語トークナイザとしてはイマイチだ。特に「の底」や「くなった」に関しては、単語の切れ目を完全に無視していて、日本語における言語処理としては話にならない。350種類もの言語に挑戦する心意気は買うが、それでも各言語ごとのクセをちゃんと見極めてから、言語モデルの設計をしてほしいなあ。
AndroidStudioでコードのミニマップを表示する方法まとめ

# はじめに
みなさんSublimeTextご存知ですか？
SublimeTextはその軽快さと柔軟性から、世界的にもかなり人気の高いエディタです。
そのエディタの機能の一つでコードのミニマップという機能があります。

![screenshot_16821.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/4a420404-639d-e72d-ce8e-a356ff455633.png)

上記の赤枠部分がコードのミニマップで、ひと目でコードの全体像がわかったり、クリックしてすぐに該当箇所に飛べたりなど結構便利な機能なのですが、実はAndroidStudioでもプラグインを使うことで同じように表示可能です。

そこで今回はこのプラグインをご紹介したいと思います。

# CodeGlanceの追加
プラグインの導入は非常に簡単です。

`Settings > plugins` より`CodeGlance`と検索してもらい、インストールするだけで使用できるようになります。
現在CodeGlanceは色々ありますが、一番上のProをご利用いただく形で問題ないと思います。

![スクリーンショット 2024-08-25 12.09.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/a219e213-392c-4158-1430-73ab037ddcbd.png)

インストールしたら再度IDEを立ち上げることでミニマップが表示されるようになります。

![スクリーンショット 2024-08-25 12.12.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354522/d7f363cc-7591-7ecc-ea72-c76295154636.png)

ミニマップをクリックすることで瞬時にその行を確認することもできますし、コードの全体像をひと目で把握できるので、開発効率が上がりそうですね。

気になる方は是非利用してみてください。

# さいごに
最近頻繁に停電するし急な雨の降り方が恐ろしいですね、台風よりおっかないって何事、、、
TISのテスト種別＆テスト観点カタログが有用すぎるので紹介しておく

## はじめに
担当プロジェクトでテスト観点を整理しているときに「参考になりそうな資料がネットに無いかなー」と思ってぐぐっていると、大手SIerのTISが公開している「テスト種別＆テスト観点カタログ」という資料を発見しました。

この資料が半端ないレベルで、すぐにダウンロードしました。

私はSI業界歴がまもなく20年になるのですが、ここまで体系的に整理された資料を目にしたことはありません。おそらく開発ドキュメントが充実している大手企業でも、このレベルまで洗練されたものは少ないのではないでしょうか。

ということで、今回はTISの資料がもっと広まって欲しいという願いを込めて、いかに有用な資料かを紹介しておきたいと思います。

## 課題感（テスト計画は難しい）

資料の中身を紹介する前に、我々がプロジェクトで抱いている課題感を共有しておきます。

プロジェクトを牽引する役割になると、システム開発でどういうテストをするかを自分自身で考えることになります。

単体テスト、内部結合テスト、外部結合テスト・・・といったざっくりとしたテスト内容は誰もが理解していますが、それを具体的なテスト計画に落とし込むのは、やはり経験値に左右されることが多いはずです。

その結果、プロジェクトによっては、テスト不足によるシステム障害が発生したり、想定以上の工数が発生したりしてバースト、炎上・破綻するプロジェクトが少なからず発生しています。

「どんなテストをすればいいのか？」は、歴10年・20年の人でも抱く悩みです。プロジェクトの特性によってテスト内容は変わるので正解はありません。過去プロジェクト資料を参考にすることは多いと思いますが、その資料にしても体系的に整理されたわけではないので、多くの場合は「今回はどういうテストをしようか」と頭を悩まさせることになるはずです。

また、検討した結果を資料にアウトプットするのも結構な労力を必要とします。

## TISの資料の何がすごいのか

前述のように、「どんなテストをすればいいのか」といった点は、経験年数が多いエンジニアでも持つ悩みですが、そういった悩みを軽減してくれるのがTISの資料です。

資料は、
　・テスト種別カタログ
　・テスト観点カタログ
の2種類が用意されています。

1つ目の「テスト種別カタログ」は、その名の通り、機能テストや性能テスト、セキュリティテストなどのテスト種別を一覧化している資料です。

2つ目の「テスト観点カタログ」も、その名の通り、テストケースを考えるときのテスト観点を一覧化している資料です。

活用タイミングは以下の通り。

![活用場面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739239/6e7059d0-8299-c438-f09b-7f714753dbca.png)

出典： [TIS-テスト種別＆テスト観点カタログガイド](https://fintan.jp/page/1456/)

プロジェクトの上流から下流まで広い範囲で活用できるものとなっています。

これら資料の何がすごいかというと、こういったシステム開発ドキュメントはITベンダー各社が蓄積しているノウハウ（知的財産）であり、多くの場合は社内資料として管理されて社外に公開はされていません。

私はたまにIPAのドキュメントを漁ったりしていますが、IPAにもこういった資料はありませんでした。そういったノウハウがつまった資料を全世界に公開している点がすごいと感じます。

特に、2つ目の「テスト観点」については、テスト工程のテストケースを考えるときに時に使えるレベルで書かれているので、これからテスト工程に入ろうとする人にとっては、喉から手が出る資料な気がします。

ただ、資料のボリュームは多いので、プロジェクトの特性に合わせたテーラリングは必要となるので留意してください。

それでは、それぞれの資料をざっくりと紹介しておきます。

## テスト種別カタログ

![テスト種別カタログ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739239/9eac829c-e80a-730d-4d16-dc04321232c5.png)

出典： [TIS-テスト種別カタログ](https://fintan.jp/page/1456/)

ある程度の経験を積んだ人であれば、ここに挙がっているようなテストを見聞きしたことはあると思います。

そういった意味での目新しさは無いかもしれませんが、この資料が有用なのは、こういった「あーそういうテストは必要だよね」といったテストを列挙してくれている点にあります。

例えば、顧客に対してテスト計画を説明する際に「機能テストとは、業務シナリオテストとは」といった説明を資料に書かなければなりませんが、そういった辞書的な使い方ができるのが便利なところです。

![工程へのテスト種別の割り当て例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739239/1b0890c0-509d-7e1d-504b-9783bf759014.png)

出典： [TIS-テスト種別カタログ](https://fintan.jp/page/1456/)

なお、各テスト種別をどういったタイミングで実施するか、という例についても別紙に書かれているので参考になると思います。


## テスト観点カタログ
![テスト観点カタログ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739239/28bd3749-aba9-ef60-56b2-c8b5f785e23b.png)

出典： [TIS-テスト観点カタログ](https://fintan.jp/page/1456/)

テストケースを列挙する際には「テスト観点」が重要になってきます。
例えば、「DB操作において条件の完全一致はテストケースがあったけど、部分一致はテストケースから漏れていた」といったようなことがプロジェクトで起こりがちで、そしてテストの漏れた箇所にバグが潜在するリスクがあるからです。

TISのテスト観点カタログでは、様々なテスト種別におけるテスト観点を列挙してくれていて、中にはそのまま使えるレベルのものもあります。

前述の画像はバリデーションチェックですが、他にもDB操作やファイル処理、メール処理などの多数の観点が列挙されています。
以下はDB操作系ですね。

![テスト観点カタログ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739239/d973abc6-a7af-1b56-25a2-a847fffdcad3.png)


## さいごに
今回はテスト種別・テスト観点のドキュメントについて紹介しましたが、TISのサイトにはその他にも開発に役立ちそうな資料が置いてありそうなので、今後目を通しながら良さげなものがあったら紹介していきたいと思います。

とりあえず、TIS技術サイトのリンクは置いておきます。

記事カテゴリ
https://fintan.jp/blog-category/
「文字列収集」(ランク S) を解いてみた (startswith() を使う)

# はじめに
[paiza x Qiita のプログラミング問題](https://paiza.jp/pages/campaign/paiza-qiita "paiza×Qiitaコラボキャンペーン") の [文字列収集](https://paiza.jp/works/mondai/real_event/word_collection "文字列収集 | レベルアップ問題集 | プログラミング学習サイト【paizaラーニング】") について, ```re.match()``` を使って解きました.

https://qiita.com/satomshr/items/c14681daa05403bee9a2

でも, 一部の問題に秒単位の実行時間がかかっていました. そこでちょっと調べたところ, ```startswith()``` でも文字列の一致の判定ができることに気付いたので, プログラムを修正してみました.

# コード
文字列の一致の判定で, 前回は ```re.match()``` を使っていました. コードは以下の通りです.

```python:2-1
def check_match(s_list, query):
    """文字列のリスト (s_list) とクエリー (query) を受け取り, マッチしたリストのインデックスを返す
    """
    match_list = list(map(lambda x: re.match(query, x), s_list)) # マッチしたかしないかのリストを返す
    return [i for i, x in enumerate(match_list) if x != None] # マッチしたリストのインデックスを返す
```

この関数を, ```startswith()``` を使って書き換えます. ```startswith()``` は ```True``` か ```False``` を返すので, ```return``` の内包表記も書き換えてます.

```python:2-1'
def check_match(s_list, query):
    """文字列のリスト (s_list) とクエリー (query) を受け取り, マッチしたリストのインデックスを返す
    """
    match_list = list(map(lambda x: x.startswith(query), s_list)) # マッチしたかしないかのリストを返す
    return [i for i, x in enumerate(match_list) if x == True] # マッチしたリストのインデックスを返す
```

# 実行時間比較
文字列の一致判定による実行時間の違いは, 次の通りでした (単位 ; 秒). ```startswith()``` のほうが早いですね.

| テスト番号 | ```re.match()``` | ```startswith()``` |
|:-:|:-:|:-:|
| 1  | 0.04  |  0.03 |
| 2  | 0.03  |  0.02 |
| 3  | 0.24  |  0.09 |
| 4  | 0.06  |  0.04 |
| 5  | 0.16  |  0.06 |
| 6  | 0.32  |  0.11 |
| 7-1 | 0.09  |  0.04 |
| 7-2 | 0.08  |  0.04 |
| 8  | 0.26  |  0.09 |
| 9  | 1.49  |  0.45 |
| 10  | 1.58  | 0.49 |

# さいごに
「文字列の一致は正規表現だろう」と思ってましたが ```startswith()``` や ```endswith()```, また含まれるかどうかは ```in``` で分かります. 適材適所が大事ですね.

# リンク
- [文字列収集.ipynb](https://colab.research.google.com/drive/1GEtBDHYMpGr2mmSlD0u3SS0aNYF23rW5?usp=sharing "文字列収集.ipynb")
Poetryを使った環境構築とDockerコンテナのセットアップ

Poetryを使って環境を構築し、Dockerコンテナ内でPythonアプリケーションを実行する際の手順と注意点をまとめました。

CI/CD環境やコンテナビルド時忘れがちところも自分用メモとして記入しましたが参考になれば幸いです。


# 1. 開発フェーズでの`pyproject.toml`

まず、開発フェーズでpyproject.tomlを作成します。このファイルは、後でコンテナやCI/CD環境でプログラムを実行する際に使用します。


```
[tool.poetry]
name = "recommendation-practice"
version = "0.1.0"
description = ""
authors = ["PH <phlin@example.com>"]
packages = [
    {include = "datareadeor", from = "src"},
    {include = "utils", from = "src"}, 
    {include = "core", from = "src"}, 
    {include = "table_models", from = "src"}
]

[tool.poetry.dependencies]
python = "^3.11"
numpy = "^1.25"
mlflow = "^2.14.2"
pydantic = "^2.8.2"
joblib = "^1.4.2"
pgvector = "^0.3.0"
psycopg2-binary = "^2.9.9"
asyncpg = "^0.29.0"
greenlet = "^3.0.3"
pydantic-settings = "^2.3.4"

[tool.poetry.group.pipeline.dependencies]
scikit-surprise = "^1.1.4"

[tool.poetry.group.dev.dependencies]
poethepoet = "^0.27.0"
ruff = "^0.5.1"
pandas = "^2.2.2"
ipykernel = "^6.29.5"
matplotlib = "^3.9.1"
pytest = "^8.2.2"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

```






# 2. `Dockerfile`の作成
次に、上記のpyproject.tomlを使用して、Dockerコンテナを作成します。以下は、Python 3.11のSlimバージョンをベースにしたDockerfileの例です。


```
FROM python:3.11-slim

WORKDIR  /app
RUN mkdir /app/src

# 必要なビルドツールのインストール
RUN apt-get update && apt-get install -y \
    build-essential \
    gcc \
    python3-dev \
    libpython3-dev

# Poetryのインストール
RUN pip install --upgrade pip && \
    pip install poetry

# Poetryファイルのコピーとパッケージのインストール
COPY src/pipelines/popularity/pyproject.toml /app/pyproject.toml

# モジュールコードのコピー
COPY src /app/src

# パイプラインスクリプトのコピー
COPY src/pipelines/popularity/pipeline_popularity.py /app/src/pipeline_popularity.py

# Poetryを使用して依存関係をインストール
RUN poetry config virtualenvs.in-project true
RUN poetry install --only main

# エントリーポイントスクリプトを使用して、適切なパイプラインを実行
ENTRYPOINT [ "poetry", "run", "python", "src/pipeline.py"]

```


`Dockerfile`に対し自分がよく忘れるポイントは以下：

- Poetryの仮想環境をワーキングディレクトリに強制的に作成

    ```
    RUN poetry config virtualenvs.in-project true
    ```

- メイングループのパッケージのみ（`[tool.poetry.dependencies]`に記載されているパッケージ）をインストール

    ```
    RUN poetry install --only main
    ```

    または、実行したいスクリプトに応じてパッケージをインストールする場合：
    ```
    RUN poetry install --with pipeline
    ```

- コンテナ内で`poetry run`を使用してスクリプトを実行

    ```
    ENTRYPOINT [ "poetry", "run", "python", "src/pipeline_popularity.py"]
    ```

- README.mdファイルの準備を忘れずに。README.mdがなければ`poetry install`の段階でエラーが発生します

    

- パッケージ依存関係を指定した（`[tool.poetry]`の下で`packages`に記載されている依存関係 ）場合、以下の引数を使わない

    ```
    poetry install --no-root
    ```
Ubuntu24 で GStreamerを使ったAV1ストリーミング

Windowsのchocoインストールではrtpav1pay/rtpav1depayがありますが、Ubuntuでは見つかりませんでした

Rustで書かれたgst-plugins-rsにあるようですがUbuntu24のaptインストールで見つからなかったのでビルドしてインストールしました

```bash
# ビルドツールをインストール
$ sudo apt install build-essential cmake meson ninja-build libgstreamer1.0-dev libglib2.0-dev libsodium-dev libdav1d-dev nasm libgtk-4-dev libssl-dev
# Fedora
# sudo dnf install meson ninja-build gcc clang libtool autoconf automake nasm cmake gtk4-devel nasm pango-devel  cairo-gobject-devel libwebp-devel libsodium-devel rust-dav1d-sys-devel

$ cargo install cargo-c
$ rustc --version

# コードを取得
$ git clone https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs
$ cd gst-plugins-rs

# ビルド
$ meson setup builddir
# meson setup --reconfigure builddir

$ meson compile -C builddir

# インストール
$ cd builddir
$ sudo ninja install

# 確認
$ export GST_PLUGIN_PATH=/usr/local/lib/x86_64-linux-gnu/gstreamer-1.0
# Fedora
# export GST_PLUGIN_PATH=/usr/local/lib64/gstreamer-1.0

$ gst-inspect-1.0 | grep av1
rsrtp:  rtpav1depay: RTP AV1 Depayloader
rsrtp:  rtpav1pay: RTP AV1 payloader

$ gst-inspect-1.0 rtpav1pay
Factory Details:
  Rank                     marginal (64)
  Long-name                RTP AV1 payloader
  Klass                    Codec/Payloader/Network/RTP
  Description              Payload AV1 as RTP packets
  Author                   Vivienne Watermeier <vwatermeier@igalia.com>
  Documentation            https://gstreamer.freedesktop.org/documentation/rsrtp/rtpav1pay.html

Plugin Details:
  Name                     rsrtp
  Description              GStreamer Rust RTP Plugin
  Filename                 /usr/local/lib/x86_64-linux-gnu/gstreamer-1.0/libgstrsrtp.so
  Version                  0.14.0-alpha.1-7e912b0d
  License                  MPL-2.0
  Source module            gst-plugin-rtp
  Documentation            https://gstreamer.freedesktop.org/documentation/rsrtp/
  Source release date      2024-08-23
  Binary package           gst-plugin-rtp
  Origin URL               https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs
Reactを用いたResponsiveなImage map

# TL;DR

現在個人でも業務でもImage mapを使うことはまずないんですが備忘録として。

`<map>`タグは形を決めて内包する`<area>`タグの指定する座標にリンクを追加することができます。
`<map>`タグはname属性を<img>タグのuseMap属性で対応することで利用できるようになります。

たとえば地図などにリンクを追加したい際などがよく利用されるシナリオでしょうか。
ただ、利用する際には画像幅を固定して座標を変更する必要がないようにするか、画面幅の変更に合わせて座標を更新する必要があるでしょう。
また、画像にリンクを追加するという仕様上、`<area>`タグには確実に`alt`などの補助的な情報を付与するなどアクセシビリティへの考慮を忘れないようにするべきでしょう。

今回はReactを用いてWindowの画面サイズ変更によって`<area>`タグの座標更新を発火する方法になります。

```jsx
import { useCallback, useState, useEffect, useRef } from "react";
import textImage from "../../assets/googleYahoo.png";

function ImageMap() {
  // 元の画像でのリンクを付与したい座標
  const initialCoords = {
    area1: [23, 32, 237, 98],
    area2: [259, 32, 448, 83],
  };
  // 初期座標を元にステートの初期化
  const [coords, setCoords] = useState({
    area1: initialCoords.area1,
    area2: initialCoords.area2,
  });
  // 画像のRefを取得
  const imgRef = useRef(null);
  // 座標計算。元の画像幅と現在の画像幅から割合を出して更新する
  const updateCoordinates = useCallback(() => {
    if (!imgRef.current) return;
    const img = imgRef.current;
    const widthRatio = img.clientWidth / img.naturalWidth;
    const heightRatio = img.clientHeight / img.naturalHeight;
    const calculateCoords = (coords) => {
      return coords.map((coord, index) =>
        index % 2 === 0 ? coord * widthRatio : coord * heightRatio
      );
    };
    setCoords({
      area1: calculateCoords(initialCoords.area1),
      area2: calculateCoords(initialCoords.area2),
    });
  }, [initialCoords.area1, initialCoords.area2]);
  // マウント時にWindowのresizeイベントに座標計算をあてる。
  useEffect(() => {
    window.addEventListener("resize", updateCoordinates);
    const handleLoad = () => {
      updateCoordinates();
    };
    // 画像の読み込み時に再計算する
    const img = imgRef.current;
    if (img) {
      img.addEventListener("load", handleLoad);
    }
    // Initial calculation on component mount
    updateCoordinates();
    // アンマウント時にイベントを除く
    return () => {
      window.removeEventListener("resize", updateCoordinates);
      if (img) {
        img.removeEventListener("load", handleLoad);
      }
    };
  }, [updateCoordinates]);
  return (
    <div>
        <h2>Responsive Image map area</h2>
        <p>
          The coordinates updates on window resize to keep the areas in the
          correct position.
        </p>
        <map name="some-any">
          <area
            shape="rect"
            coords={coords.google.join(",")}
            href="https://www.somewhere.com"
            target="_blank"
            alt="Somewhere"
          ></area>
          <area
            shape="rect"
            coords={coords.yahoo.join(",")}
            href="https://www.anywhere.com"
            target="_blank"
            alt="Anywhere"
          ></area>
        </map>
        <img
          src={textImage}
          ref={imgRef}
          alt="Somewhere Anywhere"
          useMap="#some-any"
          style={{ width: "100%", height: "auto" }}
        />
    </div>
  );
}

export default ImageMap;

```

## 実例

https://playcode.io/1982165
クッキーとセッション管理

[体系的に学ぶ 安全なWebアプリケーションの作り方](https://www.amazon.co.jp/%E4%BD%93%E7%B3%BB%E7%9A%84%E3%81%AB%E5%AD%A6%E3%81%B6-%E5%AE%89%E5%85%A8%E3%81%AAWeb%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%E4%BD%9C%E3%82%8A%E6%96%B9-%E7%AC%AC2%E7%89%88-%E8%84%86%E5%BC%B1%E6%80%A7%E3%81%8C%E7%94%9F%E3%81%BE%E3%82%8C%E3%82%8B%E5%8E%9F%E7%90%86%E3%81%A8%E5%AF%BE%E7%AD%96%E3%81%AE%E5%AE%9F%E8%B7%B5-%E5%BE%B3%E4%B8%B8/dp/4797393165)を読んだので、覚えたい部分を記事にしていく。

## クッキーとセッション管理

HTTPはステートレスな通信なので、サーバ側では状態を保持しない。
だけど、アプリケーションでは状態を保持したい要求が度々ある。
（ログインの状態の維持など）

アプリケーションの状態を保持しておくことを**セッション管理**と呼ぶ。
で、このセッション管理を実現する手段として、クッキーがある。

### クッキー
クッキーは、サーバー側からブラウザに対して、key-valueの組み合わせを覚えておくように指示するもの。

``` javascript
// クッキーに値を格納
document.cookie = "name=hiro";

// クッキーの値を取得
allCookies = document.cookie;

console.log(allCookies); // name=hiro
```
JSであれば上記のようにクッキーを利用することができる。

ただ、クッキーにデータを保持する目的で値を入れることはあまりない。
- クッキーが保持できる値の個数や文字列長には制限がある
- 利用者本人には参照・編集できるため、秘密情報の格納には向かない

よく使用される手法は、クッキーにはセッションIDを入れて、実際の値はサーバー側で管理する手法。

### セッションID

セッションIDには以下の要件が求められる
- 第三者がセッションIDを推測できないこと
- 第三者からセッションIDを強制されないこと（第三者が選んだセッションIDを使用して、情報を書くと取得されてしまう）
- 第三者にセッションIDが漏洩しないこと

上記の要件を満たさないと、別の人の情報を簡単に盗むことができてしまう。

対策としたら
- 十分な桁数の乱数をセッションIDに使用する
- 認証されたタイミングでセッションIDを変更する
- クッキー発行の属性の不備をなくすなど

がある。
Kimaiで工数管理 (1) Kimaiとは

Kimaiという工数管理ツールを立ち上げたので記録します。

https://www.kimai.org/

## 背景
業務委託などでは、よくエンジニアの実績工数を記録して、毎月集計して請求したりすることがあると思います。
また、どの作業にどのくらいエンジニア工数を使ったかを集計して、予定と実績を比較したりすることもあるかと思います。
さらに、タイムカードがない会社では、出社／退社を手作業で記録しているところもまだあるかもしれません。

こういった場合、「エクセル」で記録することがあります。ただ、エクセルでは何かと面倒なことが多いです。

このため、よくVBAマクロでツールを作ったり、プロジェクト管理ツールで工数を管理するという手もありますが、それもまた大変なことがあります。

私も過去に0.5時間（30分）刻みで記録していましたが、ある日、とある顧客から「実績工数を1分刻みで記録できませんか」とお願いされたのが、このKimaiを導入するきっかけになりました。今はなんとか軌道に乗ったかなという感じです。

## Kimaiとは
Kimaiが良いと思う点を挙げてみます。
 - 単に工数を付けることに特化しており、使い方も容易
 - 利用者はブラウザを使って工数を記録
 - 工数は1分刻みで記録可能で、細かく工数を記録可能
 - 再生／停止ボタンみたいなのを押すだけで記録出来るので、操作性が良い
 - 管理者はリアルタイムに作業工数を記録／集計できる
 - 複数の顧客がある場合は、プルダウンから顧客名を選ぶだけ
 - あらかじめアクティビティという作業項目を登録しておけば、プルダウンから選ぶことで、大まかな作業名を簡単に割り当て出来る
 - メモ欄もあり、さらに詳しい作業内容を記入することも可能
 - CSVやエクセルでデータをエクスポート出来るので、管理者はこれまで通りエクセルで集計できる
 - 多言語対応で、日本語が問題なく使える。

私は使ってませんが以下も可能の様です。
- 工数単価(1時間いくらとか)を入れておけば、自動で費用の算出もしてくれる
- 自動で請求書を作る機能がある
- APIもあり、他の内製ツールと連携させることもできるかもしれない

デモサイトがあるので、そこで使ってみた方が分かり易いかもしれません。

https://www.kimai.org/en/demo.html

なおKimaiはGitHub上で開発が進められおり、オーストリア在住のKevin Papst氏がメンテナになっています。

https://github.com/kimai/kimai

## 導入検討
有料ですがクラウド版サービスを使う方法があります。

https://www.kimai.cloud/pricing

私はというとDockerを使って導入しました。

https://hub.docker.com/r/kimai/kimai2

Dockerに関するスキルは必要ですが、ライセンスがAGPL-3.0であり、改造さえしなければ、そのままダウンロードして使うだけなら無料で利用可能です。

英語だけですが、充実したドキュメントがあります。

https://www.kimai.org/documentation/

また、LDAPサーバにも対応しており、既にLDAPサーバが立ち上がっている場合、連携させればすぐにログイン可能な状態にできます。

## 次回以降

複数回に分けて詳しく説明していきます。

(2) Docker composeを利用した導入手順、日本語設定

https://qiita.com/inakano_ojisan/items/3f10b919f270200f2488

(3) LDAPサーバとの連携

https://qiita.com/inakano_ojisan/items/6aca61fad452024ceb58

(4) 顧客名やプロジェクト、アクティビティ、タグ名などの登録

https://qiita.com/inakano_ojisan/items/ea649d6038e22e58aac1

以下は記載予定

Kimaiで工数管理 (5) 日常の工数入力方法
Kimaiで工数管理 (6) データのエクセルへのエクスポート
Kimaiで工数管理 (7) データベースなどのバックアップ
エンジニア4年生、転職活動を振り返る【転職活動編】

morieeeenyoと申します。エンジニア4年生です。
この記事を執筆中の今、わたしは転職活動を終え新しい環境でチャレンジしています。
未来の自分が過去を振り返る際の参考になるように、転職活動の振り返りをしようと思います。
誰かの参考になれば幸いです。

①[転職準備編](https://qiita.com/morieeeenyo/items/24b42422abb0871a3bec)
②[転職活動開始編](https://qiita.com/morieeeenyo/items/b2422298c5e7f26ed8ce)
③転職活動編←今回はココ
④退職交渉〜退職編
⑤有休消化編
の5本立ててお送りします、ぜひ最後まで読んでいってください！

# いざ、転職活動へ
そんなこんなで転職活動を始めたのですが、自分の理想とする環境はわかっていてもどんな会社が世の中にあるのかあまり知りませんでした。なので最初にコンサルタントの方から5社ほど紹介いただき、それぞれに対してどういう印象を持ったのかを共有する形で自分の理想とする環境のイメージをコンサルタントの方と擦り合わせて行きました。具体的には以下の点を共有していました。

- 接点を持ってみたいかどうか(Yesy / No)
- 魅力を感じる点
- 懸念に感じる点
- 志望順位
- 接点を持つ場合、カジュアル面談と選考どっちから始めたいか
- 面接で聞きたいこと

5社の中から興味を持った会社とカジュアル面談をして、その中でさらに興味を持った会社とは面接を受けて、という感じです。カジュアル面談や面接後には必ずコンサルタントの方にフィードバックを送るようにしていました。フィードバックでは以下の点について共有していました。

- 志望度はあがったか
- 次の選考に進みたいか
- 魅力に感じた点
- 懸念に感じた点
- 聞きそびれた点
- 新たに気になった点
- 質疑応答の内容

ここまでに書いた項目は自分自身の内省のためにも役立ちました。
初めての転職活動だったので、専門家にこうやって思考に必要な観点を挙げていただけるのはありがたかったです。今後の転職活動でも使えそう。

そのほか自己応募も含めて合計6社ほど選考を受けました。

# 選考の準備について
選考の準備としては、以下の点をやってました
- 企業ホームページの読み込み
- テックブログや採用ブログのチェック
- 従業員のものと思われる個人ブログやSNSのチェック
- 企業SNSのチェック
- （サービスが使える場合）実際にサービスを使ってみる

特に企業ブログなどはある程度のレビューはされつつも企業の文化をよく表していると思いますし、個人ブログや個人のSNSにはかなり赤裸々に書かれているので参考になるのではないかと思います。（企業のネガキャンはあまりやらない方がいいぞ、と思いつつ参考情報としては使わせていただいてました）

また面接でよく聞かれそうなことについては回答集を用意していました。具体的には以下のような点についてです
- 自分の理想とする環境はどのような環境か
- 現職で働く上でどういう課題があるのか
- 現職で退職を考えている理由は何か
- 自分の専門性、強みはどういうところにあるのか
- 志望動機

エンジニアの面接は基本オンライン！なのでカンペを見ながら臨める、とてもありがたかったです笑
（実際は空で言えるくらいじゃないと突っ込まれた時に回答できないと思いますし、カンペなんてなくても回答できるくらいに内省すべきだと思います。ただ緊張して言葉に詰まった時のための保険としてカンペを見れるとホッとしますよね）

# 選考中に考えていたこと
実際に選考を受けるとなると企業も平日しか面接は対応できないので、どうしても平日の仕事終わりとかになってしまいます。スケジュール的にはかなりしんどかったです。

おおっぴらに転職します！と宣言しているならまだやりやすいと思うんですが自分の場合は転職活動中であることを周囲に明かしていなかったので、仕事終わりに面接を受けていることがバレないように振る舞うのが大変でした。会社の会議室とか使うとバレる（し、普通にやっちゃダメ！）と思っていたので、なるべくリモートの日にやるようにしていました。そして体力的に仕事終わりに1時間緻密な言語化を要求されるのはめちゃくちゃしんどかったです。（これは面接官の方も同様だと思っています、遅い時間にありがとうございました）

具体的なスケジュールとしては毎週1回以上面談・面接をこなし、フィードバックの文章も書いて、合間で日程調整もして、みたいな感じでした。今思い返してもとてもハードです。本業に多少なりとも余裕がないと難しいのではないかと思います。

選考を受けていく中で、テックブログや採用ブログの雰囲気とカジュアル面談・選考官の雰囲気が一致している会社は信頼できるなと思っていました。そういう会社はあまり飾り気がないというか、ありのままの人間味ある人たちが働いていて、ちゃんと候補者のことをみてくれそうな気がしたからです。

面白かった点を一つ挙げるとすれば、自分の場合自分に合うだろうと思っていた会社の選考は比較的トントン拍子に合格し、合わないと思っていた会社の選考はすぐに落ちました。これはある意味で自分なりの理想が固まっていた証拠だと思うのでポジティブに捉えています。

# 選考の結果について
合計6社受けて、2社から内定をいただきました。
2次面接まで進んだ会社が4社あり、そのうち半分は2次面接で不合格となりました。
不合格になった会社ではコーディング面接があったのですが、技術質問で深く突っ込まれた時にあまり答えられず自分の技術への理解が浅いことを痛感しました。内定いただいた2社はともにスキルだけでなくマインドも見てくださったと思うのですが、スキルが発展途上な自分に内定を出していただけて感謝しています。

期間としてはトータルで２ヶ月半くらいでした。

さて、次回は内定いただいた2社からどうやって進路を決めたのかについて書こうと思います。
最後までご覧いただきありがとうございました。
AITRIOS Local Edition v1.2.1 相関図

AITRIOS Local Edition v1.2.1をトライアルユースする機会がありました。様々なフィールドに様々なファイルをセットする必要があり、都度忘れてしまうので、備忘録的に図にまとめてみましたので、共有いたします。
現時点では、Local Consoleを立ち上げ直すたびにConfigurationの再設定が必要なのでこれがあると覚えるまでは便利だと思います。

# AITRIOS Local Edition v1.2.1 相関図

![LocalEdition相関図.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314541/0d763094-0fce-050a-e1b9-4b83b43a4661.jpeg)
Flutter プロジェクトでバンドルIDを確認する方法

Flutterプロジェクトにおいて、アプリのバンドルIDは、プラットフォームごとに異なるファイルに記述されています。

## iOSの場合

iOSアプリのバンドルIDは、ios/Runner/ディレクトリ内のinfo.plistファイルに記述されています。
- ios/Runner/Info.plist
  - CFBundleIdentifierキーにバンドルIDが記述されています。

![ios_info_plist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3447766/c8412e8f-5fc7-a426-efe2-83c8dfd567c0.png)

### CFBundleIdentifier の値が $(PRODUCT_BUNDLE_IDENTIFIER) となっている場合
```
<dict>
    ...
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    ...
</dict>
```
Info.plist ファイルで、CFBundleIdentifier の値が $(PRODUCT_BUNDLE_IDENTIFIER) となっている場合、実際のバンドルIDは Xcode のプロジェクト設定から参照されています。
これは、Xcode のビルド設定で定義された値が使われることを意味します。

- Xcode上での設定
  - Xcodeでプロジェクトを開き、ターゲット設定（Runner）の「General」タブ内の「Identity」セクションでバンドルIDを設定できます。

![ios_info_plist_xcode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3447766/c7c5d0d2-70fe-b459-145c-9b6eb6630d0d.png)

## Androidの場合
AndroidアプリのバンドルID（アプリケーションID）は、android/appディレクトリ内のbuild.gradleファイルに記述されています。
- android/app/build.gradle
  - applicationIdというキーにバンドルIDが記述されています。

```
android {
    defaultConfig {
        applicationId "com.example.yourapp"
        ...
    }
}
```

![android_build_gradle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3447766/54c2e75f-e48b-2be1-9393-0175ab1b02f6.png)
【SAP学習】Eclipse からSAP HANA DB を接続してみた

EclipseからSAP HANA DB を接続する時のメモです

### 前提
- 今回使う環境はSAP HANA Express Editionです、環境のインストールは以下の記事をご参照ください

https://qiita.com/manabu-s/items/4e9d4429338729054aff

- EclipseでのSAP HANA開発ツール Pluginをインストール完了。未完了の方は以下の記事をご参照ください

https://qiita.com/manabu-s/items/2f3e3707eda47f33fc55

### hostsの設定を確認

C:\Windows\System32\drivers\etc\hostsの設定を確認、ホスト名：hxehostをメモする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/bd05d8d1-eb2d-36f2-ad92-8882c1af3881.png)

### EclipseのHANA DB管理画面（SAP HANA Administration Console）を開く

Window →　Perspective　→　Open Perspective　→　Other...をクリックする。既に開いたことある場合、SAP HANA Administration Consoleが選択できるようになってますが、今回は敢えてSAP HANA Administration Consoleが見つからない場合の方法を伝えます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/55b7913f-01e7-a655-7f59-694c5a802b7b.png)

SAP HANA Administration Consoleを選んで、「Open」ボタンをクリックする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/cd98edf8-016b-072d-d58c-f7390e98e983.png)

SAP HANA Administration Console画面はこんな感じです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/9c1cbc64-b41d-e2f7-01c1-883788e82878.png)

### HANA DBを接続する

Add System…をクリックする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/74a086c0-13dd-ff89-4bfe-4153fccce215.png)

HANA　DBの接続情報の設定：
①ホスト名にC:\Windows\System32\drivers\etc\hostsの設定を入力する。例：hxehost
②インスタンス番号に90を入力する
③Multiple containersを選択する
④System databaseを選択する
⑤Next

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/8d33d4ae-ed62-f680-94b0-a91ee662a4da.png)

次の画面で、HDBのユーザー名とパスワードを入力する：
①ユーザー名：SYSTEM
②パスワード：HDBインストール時設定したmasterのパスワード
③Next

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/736bfe14-30fd-a8bf-f66d-0e2cf8726c4b.png)

この画面はデフォルト設定ままで完了にする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/657cf506-026a-b139-8a3a-64375b167a89.png)

うまく設定できた場合、以下の画面が表示される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/b142aa9b-4cbc-8f6b-accf-b494b1e9ac8e.png)

ここまで、HDBの接続完了です。

SYSTEMDBをクリックすると、DBの情報を確認できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/ad1cf4b0-32c9-10b8-c22a-f53434a86931.png)
エンジニア4年生、転職活動を振り返る【転職準備編】

morieeeenyoと申します。エンジニア4年生です。
この記事を執筆中の今、わたしは転職活動を終え新しい環境でチャレンジしています。
未来の自分が過去を振り返る際の参考になるように、転職活動の振り返りをしようと思います。
誰かの参考になれば幸いです。

①転職準備編←今回はココ
②転職活動開始編
③転職活動編
④退職交渉〜退職編
⑤有休消化編
の5本立ててお送りします、ぜひ最後まで読んでいってください！

# 私について
事前情報がある程度あった方がいいと思うので、私はどんな人なのか？について書いておきます。
箇条書きでちょっと雑ですがお許しくださいませ・・・

- 4年生大学卒
- 文系
- 新卒では人材系メガベンチャーで総合職採用、営業やったりバックオフィスやったりしてました
- プログラミングを独学で勉強し、プログラミングスクールの講師へ
- ご縁をいただき社内異動でエンジニアに(なのでエンジニアとしての転職活動は今回が初)
- ベースはプレイヤーで働きつつ、たま〜にプロジェクトマネージャーをしたり部下育成したりしてました

そのほか詳しいプロフィールは以下からご覧くださいmm
https://morieeeenyo.notion.site/3f3102c41ad24b609a2cecee782741a5


# 転職に備えてやったこと
主に二つです
- 信頼できるキャリアの相談先を作る
- レジュメを準備する

それぞれ順に説明していきます。

## 信頼できるキャリアの相談先を作る
具体的には、キャリアコンサルタントの方と知り合いになり話を聞いてもらうところからスタートしました。
初めてコンタクトを取ったのは今から2年ほど前です。当時は人生で初めてプロジェクトマネージャーになった時期で、マネージャーとして失敗してかなり精神的に沈んでいた時期でした。元々社内ではプレイヤーとしては評価していただける立場にあったこともあって無駄に自分に自信があった私はマネージャーとしての失敗の苦しみを転職することで解決しようと思っていました(今思うとかなり浅はかですね...)

そういう自分の気持ちを突き放すように、そのときコンサルタントの方から言われたのは「もうりーにょさん、まだ転職は早いです」というのでした。その方はキャリアコンサルタントでありつつも人材紹介のエージェントだったのですが、紹介料が売り上げになる人材紹介のビジネスの構造的に少々無理があっても転職を勧めてくると思っていたのでこの回答には面食らいました。

その場では転職を勧められず、想定とは違う流れになったわけですがこの回答を聞いて自分は「この人は信頼できるな」と感じました。(もちろん今のままじゃ内定出ないかも、とかそういう思惑もあったのかもしれないですが)ちゃんと自分にとって転職すべきタイミングなのかどうかを考えてくださる方だと思ったからです。

その方とは今後転職するまでずっと定期的にキャリアの相談をさせていただくことになります。

## レジュメを準備する
タイトルの通りです、前述のNotionのページを作ってました。
これは先のキャリアコンサルタントの方から「Notionでレジュメを準備すると採用担当者から見て好印象だよ」という話を聞いてそうしました。一般的なのはWordとかExcelな気がしますが、Notionの方がポップな感じがしてスタートアップなどのカジュアルめな会社には印象が良さそうに感じました。Notionの方がmarkdownが使えたりするので書きやすさと言う観点でも良かったように思います。

後述する転職活動でも5社ほど受けましたがNotionを職務経歴書として使えなかった会社はなかったです。
(PDFでくださいと言われた会社にも「NotionのURLでも大丈夫ですか？」と聞いたらOKもらえました)

実際に作った感想としては、 **かなり時間がかかるよ** ってことを声を大にして言いたいです。
自分は四半期ごとくらいにキャリアの振り返りをしてNotionに書くようにしていたのでまだマシだったのですが、四半期分の振り返りを文字に起こすので8時間くらい使っていたように思います(土日のどっちかで、朝と昼食後に合わせて4時間くらいを2週間やってた気がします)それを5~6四半期分くらいやった気がするので合計だと50時間くらいはかかっているんじゃないかと思います。(いやそれ以上かな...)

転職活動をする上で「やめたい！」っていう衝動に駆られて動くパターンもあるんじゃないかと思うんですが、そのケースだったとしたらめっちゃ大変だろうなと思います。元気なうちに将来のためにコツコツとレジュメを書き溜めて行くのがコツかなと思いますね。

# ここまでのまとめ
自分が転職に備えてやった二つのことをまとめると、 **「将来のために元気なうちにキャリアの見通しを立てるための種まきをしよう」** という話になります。定期的にキャリアの棚卸しをし、それを元に将来の見通しを考えられる状態にする。もし一人で考えられないなら一緒に考えてくれる専門家を味方につける。そういうのを転職したいと思ってからやると結構大変だと思います。(あと転職したいって気持ちになったときは精神的に沈んでいることが多いので、冷静な判断ができない可能性もあるかと思います)

この二つはどちらも今振り返るとやってて良かったなと思っています。もっとこうしておけば良かったと思うことを一つ挙げるとすれば、もうちょいコンサルタントというかエージェントの方を複数当たっても良かったかもなと思っています。お世話になったコンサルタントの方は信頼できる方ではありましたが、もうちょいいろいろ比べてからこの人に決めた方がより合理的な判断ができていたかもしれません。

と、いうことで転職準備編でした。
次回はここまでに書いた転職準備をする中でどういうきっかけで転職に至ったのかについて書いてみようと思います。最後までご覧いただきありがとうございました。

↓次回はこちら↓

https://qiita.com/morieeeenyo/items/b2422298c5e7f26ed8ce
Kotlinで標準出力をテストする

# はじめに
標準出力に出力した結果をテストしたい場合があります、その方法を記載します

# Sample Code
一時的にSystem.outを入れ変えます (*1)
入れ替えるPrintStreamはByteArrayOutputStreamから作成します (*2)
入れ替え後、標準出力に出力する処理を実行します (*3)
標準出力をtoString()で文字列にします (*4)

```kotlin:Test.kt
import org.junit.Assert
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.PrintStream

class Test {
    /** actionでstdoutに出力した結果をStringとして返す */
    private fun captureStdout(action: () -> Unit): String {
        val defaultStdout = System.out
        val newOutputStream = ByteArrayOutputStream() // (*2)
        val newStdout = PrintStream(newOutputStream)  // (*2)
        System.setOut(newStdout) // (*1)
        action() // (*3)
        val capturedString = newOutputStream.toString() // (*4)
        System.setOut(defaultStdout) // (*1)
        return capturedString
    }

    private fun captureStdoutAndAssert(
        expected: String,
        action: () -> Unit,
    ) {
        Assert.assertEquals(
            expected,
            captureStdout(action = action),
        )
    }

    @Test
    fun test() {
        captureStdoutAndAssert(
            expected = "Hello\r\n"
        ) {
            println("Hello")
        }
    }
}
```
スマホにhover時のスタイル変化を対応させる

### スマホでの hover 効果の問題

スマホ（hover未対応端末）でhover擬似クラスを指定した要素をタップすると、他のオブジェクトを触れるまでhoverで指定したスタイルが持続してしまう。

（リンク先に飛んだ後、ブラウザバックするとhoverスタイルがかかったままにななっている）


### 対応方法

- **デバイスがhover対応か否かをメディアクエリで指定できる**
画面サイズ指定だとスマホの横置きに対応できない
→ 画面サイズではなく、hover対応の有無でメディアを指定する

```scss
@media (hover: hover) {
	　hover対応メディア（PC）
}

@megia (hover: none) {
	hover非対応メディア(SP）
}
```

- **タップした瞬間は hover と同様のスタイルを適用させたい**
→ `:active`擬似クラスで指定すればOK

```scss
&:active {
	〜hover効果〜
}
```

- **上記の対応をまとめる**
`:active`と`:hover`は共通のルールセットになるため、筆者は以下のようにまとめている

```scss
.c-button {
	〜ボタンのスタイル〜
	
	@mixin hoverOrActive {
		border: 3px solid #00ff00;
		opacity: 0.5;
	}
	
	// hover未対応（SP）は :active に指定
	@media (hover: none) {
		
		&:active {
			@include hoverOrActive;
		}
	}
	
	// hover対応（PC）は :hover に指定
	@media (hover: hover) {
	
		&:hover {
			@include hoverOrActive;
		}
	}
}
```

>参考
>[CSSのメディアクエリでスマホでのホバーの挙動の対策を行う【hover:hover】](https://web.skipjack.tokyo/css/css_media-hover/)
for、foreachの使い方

Kotlinでのfor、forEachの使い方をメモする。
```kotlin
val list = listOf("dog", "boar", "dolphin", "impala", "wolf")
for (i in list) {
    println(i)
}
// dog
// boar
// dolphin
// impala
// wolf
for ((i, value) in list.withIndex()) {
    println("$i ： $value")
}
// 0 番目の動物はdog
// 1 番目の動物はboar
// 2 番目の動物はdolphin
// 3 番目の動物はimpala
// 4 番目の動物はwolf
```
forは`for in`でリストの中身を取り出せる。インデックスも見たい時は`withIndex`を使用する。

```kotlin
list.forEach { println(it) }
// dog
// boar
// dolphin
// impala
// wolf

list.filterIndexed { it, _ -> it % 2 == 0 }
    .forEach { println(it) }
// dog
// dolphin
// wolf
```
forEachは`it`で中身を取り出せる。インデックスで指定する時は先にfilterIndexedを使用し、フィルタリングしてから取り出す。




```kotlin
val map = mapOf(
    "イヌ" to "dog",
    "イノシシ" to "boar",
    "イルカ" to "dolphin",
    "インパラ" to "impala",
    "オオカミ" to "wolf"
)
for ((key, value) in map) {
    println("$key の英語は$value")
}
map.forEach { (key, value) ->
    println("$key の英語は$value")
}
// イヌ の英語はdog
// イノシシ の英語はboar
// イルカ の英語はdolphin
// インパラ の英語はimpala
// オオカミ の英語はwolf
```
マップでfor、forEachさせる場合は上記のようになる。どちらも同じ出力。keyとvalueを指定することで、ループ内で使用できる。


#### 参考
https://maku77.github.io/kotlin/collection/loop-collection.html

https://jp-seemore.com/app/14946/

https://qiita.com/nshiba/items/d12166534a53bf980d67
openhpc x slurm x beeGFS環境の構築と検証：コンセプト

# 基本コンセプト


オープンソースのバージョンアップはかなり早い、そして安定性も不確かです。
バージョンアップ検証やパフォーマンス検証を繰り返し行える環境を構築する

検証環境は十分な物理PCは用意出来ないので、KVMで検証環境を用意していきます。


構築後の構成としては以下のようになります。
- head    ・・・　slurm01 - 03 のネットブート管理、slurmctld(slurmマスタ）
- slurm01 ・・・　slurmd(ノード)
- slurm02 ・・・　slurmd(ノード)
- slurm03 ・・・　slurmd(ノード)

各slurmノードには、openmpi、apptainerを実行出来るようにします。

![スクリーンショット 2024-08-25 9.31.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/d47c1b1b-5f3b-a707-e634-0b4f3a9ad2e6.png)

各ホストは、分散ファイルシステムのbeeGFSを/data , /backupとしてマウントします。
![スクリーンショット 2024-08-25 8.55.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/8a0515f1-29ec-b7c8-9db4-be3943384d7f.png)


KVMホストは、２台作成する予定です。
一台はopenhpc用、もう一台はbeegfs用

各仮想マシンはvCPUx4 , vMEM=32GBに設定予定

![スクリーンショット 2024-08-25 9.39.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/c8c99a8f-a97d-b8e8-8441-1c6020b4e8d2.png)

### ネットワーク

KVMホスト内に、内部通信用のネットワークを作成し、サービス間通信はこちらを利用する予定です。
KVMホスト間通信は、外部通信用のネットワークを使用します。

![スクリーンショット 2024-08-25 10.01.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/32fe7351-1073-6455-cf62-d5c1667afcbf.png)

サービスの設計、インストールについては次回以降で記載します。
自己紹介

## 名前
masatake sato

## 出身
中部地方

## 仕事
* 製造業でエンジニア
* 生成AI使って色々やる

## 興味ある事
webアプリ開発
* Django
* Bootstrap
* PostgreSQL

生成AI
* Python
* ChatGPT, Claude, Gemini, Llama
* RAG, VectorDB
【SAP学習】EclipseでのSAP HANA開発ツール Pluginをインストールしてみた

EclipseでのSAP HANA開発ツール Pluginをインストールする時のメモです：

### 環境
OS: Windows 11 Pro
Eclipseバージョン：2024-06 (4.32.0)
Java バージョン：openjdk version "17" 2021-09-14
※2024年8月時点でSAP HANA Toolsのお勧めJavaバージョンはJava17です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/10565293-f879-1e83-fd68-761e2a46aff1.png)

### Eclipseをダウンロード
以下のURLから最新版Eclipseをダウンロードする。既にダウンロード済みの場合はこのステップをスキップできる。2024年8月時点でダウンロードしたEclipseの最新バージョンは2024-06 (4.32.0)になります。基本は古いバージョンも開発できるが、JDKのバージョンを合わせてEclipseのバージョンを選びましょう。

https://www.eclipse.org/downloads/

Eclipseをダウンロードしたら、どこに解凍し、eclipse.exeをダブルクリックしてEclipseを起動できます。起動エラーが発生する場合、基本はJavaのインストールやJAVA_HOMEとPathの設定が間違っていると考えられる。以下の記事を参考してJAVA_HOMEとPathを設定してください。

https://qiita.com/manabu-s/items/2f7f10752bcf44244718

Eclipse起動成功時のWelcome画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/1b1f38ac-2bb4-8a8b-6eb7-f1be14a690e5.png)

### SAP HANA Toolsをインストール

EclipseのHelpメニュー　→　Install New Software...　をクリックする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/4ea75e32-321e-977b-6ef0-cd8f7e9dbb5a.png)

SAPのリポジトリを追加：
①Add…ボタンをクリックする
②リポジトリ名を任意入力する。例：SAP
③SAP HANA ToolのリポジトリURLを入力する。2024年8月時点での最新URL：https://tools.hana.ondemand.com/2024-03
④Addボタンをクリックする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/39c8fcdf-39b3-e271-a623-a6a1484a187c.png)

SAP HANA Toolsをインストール：
①先ほど追加したリポジトリを選択する
②SAP HANA Toolsをチェック入れる
③Nextボタンをクリックする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/6c89c127-5df6-37db-7969-2333d84e8128.png)

インストール前に関連ソースの画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/32ebc1ff-5e06-4fa5-ec08-8b8d91a640ed.png)

以下のものがインストールされる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/097c462c-60c8-7c56-a613-d9d2a5ef4d95.png)

ライセンスを同意する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/f523c520-2ff1-0fbc-8b3b-6093db3695e2.png)

インストールの途中、hanaサイトの信頼を許可する必要があります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/e4cc38a1-c361-38e2-c4ca-8bf57b5ff720.png)

インストール完了後、Eclipseを再起動する必要があります。
インストール失敗した場合、基本はネットワークの障害か、Ｐｒｏｘｙの設定が間違っていることを考えられます。

インストール成功の場合、About画面に以下のアイコンが表示される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/caa607f6-bd9f-9754-5b32-ce364cf988b1.png)


### 終わりに

SAP HANA開発ツールのインストールは簡単でした。

職場のネットワークはProxyの設定が必要のため、かなり苦戦しました。
要するにEclipseのIDEでのProxy設定はうまくいきませんでした。（以下の図はProxy設定画面ですが、設定してもうまくいかなかったです。画像にはProxyを削除してます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1442793/7f8ca196-81c9-b663-55c1-9a2785851d85.png)

eclipse.iniにProxyを設定することでうまくいきました。

\eclipse\eclipse.ini
```
-Dorg.eclipse.ecf.provider.filetransfer.excludeContributors=org.eclipse.ecf.provider.filetransfer.httpclient
-Dhttp.proxyPort=8080
-Dhttp.proxyHost=myproxy
-Dhttp.proxyUser=mydomain\myusername
-Dhttp.proxyPassword=mypassword
-Dhttp.nonProxyHosts=localhost|127.0.0.1
```
WSL2でviteを使っている時に、npm run devをしても変更が即時反映されない時の話

初めてviteを使ってReactでプロジェクトを作成している時に遭遇したこと。

# 環境と状況
- WSL2(ubuntu)
- viteを使ってReactのプロジェクトを作成した
- viteのプロジェクトは`/mnt/e/`(NFTS)にあった
- Hot Module Replacement(HMR)がきちんと動作しなかった

# 結論
プロジェクトをWSLのホームディレクトリ`~/`以下に移すときちんと変更が検出され、即時反映される

# 原因
原因としては、WSL2のファイルシステムとWindowsのファイルシステムが違う事です。(WSL2の制約)
詳しくは別の記事でまとめてますので、こちら参照

https://qiita.com/curacao/items/1aa5b0be8f8485d1fb1a

簡単にまとめると、WSL側で変更を検出できるのはLinuxに最適化されたストレージ(ext4)であり、Windowsに最適化されたストレージ(NFTS)だとWSLは変更の検出をうまくできないということ。

今回`/mnt/e`にプロジェクトを作っていたため、WSLからこの場所にアクセスは出来てもパフォーマンスを十分に発揮できなかったということ。

# できなかったときに色々試したこと
viteの設定(vite.config.js)で以下のように`server.watch`で`{usePolling: true}`を設定すると良いという記載を見つけてやってみたのですがうまくいかなかったです。
これはvite公式ののサーバーオプションのページにも記載があります。またWSL2での実行もしっかりと書かれていました・・・(リファレンス読むって大事)
```
export default {
  server: {
    watch: {
      usePolling: true,
    },
    hmr: true, // デフォルトでtrueです
  },
};
```
> Windows Subsystem for Linux (WSL) 2 上での Vite の実行
>
> Vite を WSL2 で実行している際、ファイルシステム監視はファイルが Windows アプリケーション（WSL2 でな>いプロセス）により編集された場合に動作しません。これは WSL2 の制約 によるものです。これは WSL2 バックエンドの Docker で実行している場合でも該当します。
>
>これを修正するためには、次のいずれかを行えます:
>
>推奨: ファイルを編集するのに WSL2 アプリケーションを使用します。
>プロジェクトフォルダーを Windows ファイルシステムの外へ移動させることも推奨されます。WSL2 から >Windows ファイルシステムへアクセスするのは遅いです。このオーバーヘッドを取り除くことでパフォーマンスが向上します。
>{ usePolling: true } を設定する。
>usePolling は CPU 使用率が高くなることに注意してください。

[server.watchについて | vite公式](https://ja.vitejs.dev/config/server-options.html#server-watch)

# 終わりに
**リファレンス、大事。**
今回はviteでしたが、つい先日LaravelでもこのWSL2の制約にあたり苦労しました(学ばないな・・・)

https://qiita.com/curacao/items/84b5858378c1db1c41c2


# 参考記事
[vite公式 | watchについて](https://ja.vitejs.dev/config/server-options.html#server-watch)
[vite公式 | hmrについて](https://ja.vitejs.dev/config/server-options.html#server-hmr)
[WSL2の制約について](https://github.com/microsoft/WSL/issues/4739)
再帰関数の自分用メモ

## 背景


- 深さ優先探索を学ぶにあたって再帰関数のコードを書く必要があった。
- 再帰関数の考えたをノートに整理して少し掴む事ができたので、それをまとめておく。


## 考え方

重要なのは、再帰関数の処理には2つのパターンがあると言う事を理解し、それぞれ今回の問題ではどう言う状況が考えられるかを整理することから始める。
 

2つのパターンとは以下
1. 再帰を行うパターン
1. 例外のパターン


### 再帰関数の基本である階乗(n!)のアルゴリズムを例に考えていく。

- 階乗のアルゴリズムにおける再帰を行うパターン

これは以下の数式のように現在の値に、1を引いた階乗を掛けるパターンである。
この場合、入れ子のようにまた同じ関数が現れるので、再帰が生まれている。

  

```math
k\times(k-1)!
```

  

- 階乗のアルゴリズムにおける例外のパターン

これは、k = 1となったときである。
この時には、ひとつ小さい値である0!(=1)を加える必要はなく、ここで処理を止める必要がある。

再帰関数ではどこかで処理を止めないと無限ループが起こるので、この例外のパターンというのはいわば、**再帰関数を使わないパターンである。**



### 整理した情報を元に実装を行う。

```python

# 階乗を再帰関数で定義

def factorial(n):
  # 再帰のパターン
  if n > 1:
    return n * factorial(n-1)
  # 例外のパターン
  else:
    return 1

# 答えを確認
print(factorial(3))
```


今後再帰関数の実装で困ったときは、一度この二つのパターンに立ち返るようにする。
オクタノルムシステム

以下は、指定された要件に基づく3D空間モデル生成プロジェクトのWBS（Work Breakdown Structure）と具体的なプログラムの書き出しです。

### **要件定義**

- **モデルサイズ:**
  - 梁①: 990mm
  - 梁②: 455mm
  - 鎹: 1360mm
  - 柱: 2700mm

- **使用言語:** C#
- **開発環境:** Unity

### **WBS（Work Breakdown Structure）**

1. **プロジェクトの準備**
   - Unityプロジェクトのセットアップ
   - 必要なアセットやライブラリの準備

2. **クラス設計**
   - **3Dモデルクラスの設計**
     - クラス変数の定義
     - コンストラクタとプロパティの実装
   - **テーブル変数の設計**
     - モデルのパラメータを格納するためのテーブル

3. **3Dモデルの生成**
   - **梁①（990mm）の作成**
   - **梁②（455mm）の作成**
   - **鎹（1360mm）の作成**
   - **柱（2700mm）の作成**
   - モデルの配置と組み合わせ

4. **プログラムの実装**
   - **C#スクリプトの作成**
     - 3Dモデル生成のロジックを実装

5. **テスト**
   - モデルの表示確認
   - サイズと配置の検証
   - エラーハンドリングの確認

6. **ドキュメント作成**
   - コードのコメント
   - 使用方法の説明書作成

7. **デプロイとレビュー**
   - 最終確認とデプロイ
   - コードレビューとフィードバック

### **具体的なプログラムの書き出し**

以下は、Unityで指定されたサイズの3D空間モデルを生成するためのC#スクリプトの例です。

```csharp
using UnityEngine;

public class ModelGenerator : MonoBehaviour
{
    // クラス変数
    public float beam1Length = 990f;   // 梁①
    public float beam2Length = 455f;   // 梁②
    public float kaguLength = 1360f;   // 鎹
    public float pillarHeight = 2700f; // 柱

    void Start()
    {
        // 3Dモデル生成
        GenerateModels();
    }

    void GenerateModels()
    {
        // モデルの生成
        CreateBeam1();
        CreateBeam2();
        CreateKagu();
        CreatePillar();
    }

    void CreateBeam1()
    {
        GameObject beam1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
        beam1.transform.localScale = new Vector3(beam1Length, 0.1f, 0.1f); // 横長の梁
        beam1.transform.position = new Vector3(0, 0, 0);
        beam1.name = "Beam1";
    }

    void CreateBeam2()
    {
        GameObject beam2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
        beam2.transform.localScale = new Vector3(beam2Length, 0.1f, 0.1f); // 短い梁
        beam2.transform.position = new Vector3(0, 0, 1.2f); // 配置位置の調整
        beam2.name = "Beam2";
    }

    void CreateKagu()
    {
        GameObject kagu = GameObject.CreatePrimitive(PrimitiveType.Cube);
        kagu.transform.localScale = new Vector3(0.1f, 0.1f, kaguLength); // 長い鎹
        kagu.transform.position = new Vector3(1.2f, 0, 0); // 配置位置の調整
        kagu.name = "Kagu";
    }

    void CreatePillar()
    {
        GameObject pillar = GameObject.CreatePrimitive(PrimitiveType.Cube);
        pillar.transform.localScale = new Vector3(0.1f, pillarHeight, 0.1f); // 高い柱
        pillar.transform.position = new Vector3(0, pillarHeight / 2, 0); // 配置位置の調整
        pillar.name = "Pillar";
    }
}
```

### **説明**

1. **プロジェクトの準備**
   - Unityで新しいプロジェクトを作成し、スクリプトファイルを追加します。

2. **クラス設計**
   - `ModelGenerator` クラスを定義し、必要なクラス変数とメソッドを設計します。

3. **3Dモデルの生成**
   - `CreateBeam1`、`CreateBeam2`、`CreateKagu`、`CreatePillar` メソッドでそれぞれの3Dモデルを生成します。
   - 各モデルのサイズと位置を調整し、適切に配置します。

4. **テストとドキュメント**
   - モデルが正しく表示されるかを確認し、コードにコメントを追加して説明書を作成します。

5. **デプロイとレビュー**
   - プロジェクトをビルドして確認し、コードレビューを行います。

この手順とコードを基に、指定されたサイズの3D空間モデルをUnityで正しく生成し、プロジェクトを進めることができます。
「文字列収集」(ランク S) を解いてみた

# はじめに
[paiza x Qiita のプログラミング問題](https://paiza.jp/pages/campaign/paiza-qiita "paiza×Qiitaコラボキャンペーン") の [文字列収集](https://paiza.jp/works/mondai/real_event/word_collection "文字列収集 | レベルアップ問題集 | プログラミング学習サイト【paizaラーニング】") をトライしました. 正規表現とリストの内包表記の勉強になりました.

末尾に Google Colaboratory へのリンクを貼っています.

:::note info
ここでは文字列の一致に ```re.match()``` を使っていますが, 代わりに ```startswith()``` を使った記事も書きましたので, ご参考ください.

[「文字列収集」(ランク S) を解いてみた (startswith() を使う)](https://qiita.com/satomshr/items/96cc357d562ce487bce6 "「文字列収集」(ランク S) を解いてみた (startswith() を使う)")
:::

# コード
## ライブラリ
正規表現を使います.

```python:1
import re # 正規表現
```

## 関数定義
2 つ関数を準備しました. 1 つ目の ```check_match(s_list, query)``` は, 読み込んだ文字列のリストとクエリー (文字列) を受け取り, マッチした ```s_list``` 内の文字列のインデックスを, リストとして返します.

リストの全部のリストに作用させるために ```map()``` を使い, その内部では無名関数 ```lambda``` を使って処理を書いてます. クエリーにマッチするかしないかの判定は，```re.match()``` を使っています.

そして得られたリストのうち, 要素が ```None``` でないものだけをリスト化し, 返しています (ここも内包表現).

```python:2-1
def check_match(s_list, query):
    """文字列のリスト (s_list) とクエリー (query) を受け取り, マッチしたリストのインデックスを返す
    """
    match_list = list(map(lambda x: re.match(query, x), s_list)) # マッチしたかしないかのリストを返す
    return [i for i, x in enumerate(match_list) if x != None] # マッチしたリストのインデックスを返す
```

2 つ目の ```calc_price(p_list, idx_list)``` は, 価格のリスト ```p_list``` とインデックスのリスト ```idx_list``` から, 該当するインデックスの価格の合計を計算します. Google Colab のサジェストで一発作成.

```python:2-2
def calc_price(p_list, idx_list):
    """価格のリスト (p_list) とマッチしたインデックスのリスト (idx_list) を受け取り, 合計価格を返す
    """
    return sum([p_list[i] for i in idx_list])
```
## 標準入力からデータを読み込む

ここから本体です. 標準入力から指定通りにデータを読んでいきます. 数値は適宜 ```int()``` を使って変換しています.

```python:3
line = input().rstrip().split()
n = int(line[0]) # 文字列の数
m = int(line[1]) # クエリーの数

s = [] # 文字列のリスト
p = [] # 価格のリスト
for i in range(n):
    line = input().rstrip().split()
    s.append(line[0])
    p.append(int(line[1]))

q = [] # クエリーのリスト
for i in range(m):
    q.append(input().rstrip())
```
## 合計金額を計算して出力する
合計金額を計算して出力します. 上で定義した関数を用いて
1. 文字列がマッチするリストのインデックスを求める
1. 該当するインデックスの価格の合計を求め, 出力する

という処理です.

```python:4
for query in q:
    idx = check_match(s, query)
    print(calc_price(p, idx))
```

# さいごに
これで 100 点を取れたのですが, 一部のテストケースでは秒単位で時間がかかっています. 改善の余地もありそうですが, ひとまずはここまでにします.

# リンク
- [文字列収集.ipynb](https://colab.research.google.com/drive/1GEtBDHYMpGr2mmSlD0u3SS0aNYF23rW5?usp=sharing, "文字列収集,ipynb")
Emacs風のCUIエディタを作った話（２）

# 千秋楽
自作のLisp処理系のEasy-ISLisp本体はやり尽くしました。ISLisp規格にないモデュール、並列機能、TCP/IP通信などの拡張。コンパイラの改良、型推論・・・　満足です。
最後の仕上げはLisp専用エディタです。時代に逆行したようなCUIエディタですが、これもまたレトロな雰囲気でオツなものです。バグをとり、改良をしほぼ満足のいく状態になりました。これが最後のLisp奉公。ご紹介いたします。


# 外観
![bandicam 2024-08-24 15-29-09-304.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33927/fcac8001-ae38-fcd2-f5e8-9d8d7d2eeb1f.jpeg)

tmuxを使っています。左側がエディタのEdlisです。右側がEasy-ISLispです。tmuxの設定ファイルでマウスで分割画面をいったりきたりできるようにしてあります。Emacsになれた人ならEmacsの画面移動ショートカットキーと同じにしておいてもいいかもしれません。


# キーバインド
Edwinと互換、Emacsのほぼサブセットです。Emacs使いの人にも使えると思います。私はその昔、テキサスインスツルメント社のPC-Schemeを使っていた頃を思い出して、懐かしい気分です。キーバインディングはヘルプキーで説明が表示されます。ヘルプは CTRL+H です。

![bandicam 2024-08-25 08-50-44-502.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33927/28b53d41-d7e7-214e-367d-2ffc90ca4b1e.jpeg)


# 補完
ISLispと拡張関数について補完ができます。ESC Tab で補完します。

![bandicam 2024-08-25 08-22-48-580.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33927/837c9291-5b80-a193-8818-8564c135dc04.jpeg)


# info
ISLispの組み込み関数について引数、戻り値、説明を表示することができます。説明が必要な関数名の後ろにカーソルを合わせておいてESC i で下段に説明が表示されます。

![bandicam 2024-08-25 08-26-26-701.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33927/94921c5e-7050-ff15-2b2d-96f72e2bd1fb.jpeg)

# カッコ対応、オートインデント
Lispの場合にはカッコ対応、カッコ位置による適切なインデントが重要です。設定なしで適当にインデントし、対応カッコを強調表示します。

![bandicam 2024-08-25 08-30-07-655.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33927/5166f25b-307d-17c5-6426-a3c391286414.jpeg)


# S式単位の移動、ワード単位の移動
Emacs,Edwinと同様です。

![bandicam 2024-08-25 08-31-56-764.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33927/6fac74a6-98bc-43f6-61d2-8f28973fd563.jpeg)


# 改造
OSSで、C言語で3000行程度です。簡単にカスタマイズできます。ドキュメントのEDLIS.mdに改造のヒントを書いておきました。ご自由に改造してください。C言語でカスタマイズできるのでLisp初心者でも改造は簡単です。


# Lispのココロは自由
好きなように大改造してお楽しみください。何をどうしようとお好みのまま。自由にやれるのがLispの最大の魅力です。それはEmacsのやり方と違うなんて野暮なことは言いっこなしですよ。不条理なのが現実世界、せめてLispくらいは楽しくやろうじゃありませんか。

世の中はGUIが主流、VSCodeのようなバリバリに強力で素敵なエディタがあります。何を使おうと自由です。それがLispです。ISLispもこれが絶対に正しいなんて書法はありません。文法違反は動作しませんが、そうでなければ精一杯、試行錯誤しつつ楽しんでください。質問は難しいことでなければ私でもお答えできます。難しい質問は識者である五味先生がお答えくださるでしょう。五味先生も自由を愛するジョーク大好きな方とお見受けいたします。どうぞLisp、ISLispをエンジョイしてください。


# 参考

- ISLispについて　http://islisp.org/index-jp.html
- wikipedia https://en.wikipedia.org/wiki/ISLISP
- Easy-ISLisp https://github.com/sasagawa888/eisl
SpringBoot + Thymeleaf バリデーションエラーの表示

## 記載内容
画面上の入力フォームにバリデーションエラーメッセージを表示させる際の実装方法について記述

## Formクラス
```java:UserForm.java
@Data
public class UserForm {

    @NotEmpty(message = "名前を入力してください")
    @Size(max = 100, message = "名前は100桁以内で入力してください")
    private String name;

    @Email
    private String email;

    @NotEmpty(message = "パスワードを入力してください")
    private String password;
}
```

## Controller
```java:UserController.java

@Controller
public class UserController {

  /**
   * ユーザー情報 Service
   */
  @Autowired
  private UserService userService;
  
    @GetMapping("/form")
    public String form(
        @ModelAttribute UserForm userForm,
        Model model
    ){
        return "user/form";
    }
    
    @PostMapping("/create")
    public String create(
        @ModelAttribute @Validated UserForm userForm,
        BindingResult result,
        Model model
    ) {
        if (result.hasErrors()) {
            // 入力チェックエラーの場合
            return "user/form";
        } else {
            // 新規ユーザ登録
            userService.create();
            return "redirect:/user/list";
        }
    }
}
```

## Thymeleaf
```html:user/form.html
    <form th:action="@{/create}" th:object="${userForm}" th:method="post">
    
        <label>名前</label>
        <input type="text" th:field="*{name}">

        <label>E-mail</label>
        <input type="text" th:field="*{email}">

        <label>パスワード</label>
        <input type="text" th:field="*{password}">

    </form>
```
## エラーメッセージの表示方法
### ①まとめて表示する
```html:formタグ配下に挿入
<li th:each="error : ${#fields.detailedErrors()}"
    class="text text-danger" th:text="${error.message}"></li>
```
・「#fields」には各フィールドのバリデーションチェックした結果が格納されている
・「detailedErrors」メソッドは、発生したエラーに関する情報をまとめて取得する

### ②各入力フィールドに表示する

```html:入力項目「名前」にエラーメッセージを表示する場合
    <form th:action="@{/create}" th:object="${userForm}" th:method="post">
    
        <label>名前</label>
        <input type="text" th:field="*{name}" th:errorclass="err">
        <div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" th:errorclass="err"></div>
```
・「th:errorclass」はエラーが起きたときに適用されるクラス名（テキストボックスを赤枠で囲むとか、メッセージを赤文字で表示させるなど）
・「hasErrors」は、引数に指定したフィールドのエラーが発生しているかを判断する
・「th:errors」は、バリデーションエラーがある場合に、指定された変数に関連するエラーメッセージを表示する。バリデーションエラーがない場合、何も表示されない。