フロントエンド開発から、今年バックエンドに挑戦してみて詰まったベスト3

## はじめに
株式会社クラベスで現在フルスタックに開発しているKuuuuu([@wanwanwan_nyo](https://x.com/wanwanwan_nyo))と申します。
元々は中学校の教員をやっていて、未経験で株式会社クラベスに入りました。
最初の1年はReact/TypeScriptでフロントエンドの開発をしており、その後少しずつKotlinでバックエンドの開発を行っております。
そんな私がこの1年バックエンドに挑戦し、詰まったベスト3をご紹介します。

## 3位 String型に変換しているのに参照の等価性がfalseになる
### 事象
CSVから取り込んだresultの値には数値が入ります。
`toString()`でString型に変換し、値が0であるかどうかを判断するために以下の条件文があったが、falseになった。
```Kotlin
result.toString() === "0"
```

### 理由
Kotlinでは`===`は参照の比較でオブジェクトが同じメモリ位置を指しているかを判断している。
そのためKotlinでは`==`で値の比較を行わなければいけないのでした。
TypeScriptでは、`===`は型が等しく、かつ値が等しいことをさすので、先ほどの例はTypeScriptでは`true`なのです。
めっちゃややこしい...

参考
- [Kotlin文法 - this、等価性、演算子オーバーロード、Null安全、例外 #Kotlin -Qiita](https://qiita.com/k5n/items/6fe586bf2e5530684681)
- [等価であるということ | TypeScript入門『サバイバルTypeScript』](https://typescriptbook.jp/reference/values-types-variables/equality)

## 2位 なぜか本番環境、検証環境では問題なく動作されているが、テストコードが通らない
### 事象
検索結果の順番を変更する修正を行う必要があったため、以下のように検索結果のクエリを修正した。

```Kotlin:SearchQueryRepositoryImpl.kt
...
val orderByHogeTypeCase = Case()
                .When(HogeTable.hogeType eq hogeType.FIRST.name, 1)
                .When(HogeTable.hogeType eq hogeType.SECOND.name, 2)
                .Else(3)
query.orderBy(orderByHogeTypeCase to SortOrder.ASC).map { toSearchResult(it) }
...
```
hogeTypeのFIRSTから順番に検索結果が並んでいるかテストコードを追加したが、テストコードが通らなかった。

### 理由
hogeTypeのnameを全て「大文字」で定義していたが、実際のデータベースのHogeTableのhogeTypeが「小文字」であったため、テストのHogeTableのhogeTypeも「小文字」でデータを入れていた。
「大文字」と「小文字」を比較していたためテストが落ちていたのだが、検証環境、本番環境では動作としては問題なく動いていたのです。
当時はcollation(照合順序)の存在も知らなかったため解明まで時間がかかりました。

今回検証環境、本番環境ともにMySQLを用いていました。テスト用のデータベースは、以下のように設定しており、H2データベースを用いていたようです。
```yaml:application-test.yaml
spring:
  datasource:
    url: jdbc:h2:mem:navi;MODE=MYSQL
```

> - H2は主に開発やテスト環境向けであること
> - H2はモードにより各種のデータベースのSQLにほぼ対応できる
> - MySQL ではテキスト比較は、デフォルトでは大文字と小文字を区別しませんが、 H2 では (他のほとんどのデータベースと同様に)、大文字と小文字を区別する

以上のように、H2ではデフォルトで大文字と小文字を区別しているが、検証環境、本番環境のデータベースでは、デフォルトで大文字と小文字を区別していないことが原因でした。
H2でも大文字と小文字を区別しないように設定することもできるそうです。
[H2DBで Table not found になって困ってたら大文字小文字の違いが原因だった⇒DATABASE\_TO\_UPPER=false を指定して解決 #h2database - Qiita](https://qiita.com/sugiyasu-qr/items/98f22ad4af0b786c3eb7)

参考
- [Developing-with-the- H2- Database - 2.2.0](https://www.playframework.com/documentation/ja/2.2.0/Developing-with-the-H2-Database#:~:text=MySQL%20%E3%81%A7%E3%81%AF%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E6%AF%94%E8%BC%83%E3%81%AF,%E5%B0%8F%E6%96%87%E5%AD%97%E3%82%92%E5%8C%BA%E5%88%A5%E3%81%97%E3%81%BE%E3%81%99%E3%80%82)
- [unit test の動作速度を速めるためにMySQLからH2 databaseに移行しようとした話 - GMOインターネットグループ グループ研究開発本部](https://recruit.gmo.jp/engineer/jisedai/blog/migrate-h2database/)
- [Spring Boot + DB接続（H2データベース） #Java - Qiita](https://qiita.com/high_high/items/f019d0e63d61a0c7c51b)


## 1位 ワード検索のワードの色が変わらない

### 事象
検索したワードを赤く色を変更するという機能があり、当初は全角・半角・大文字・小文字全て区別しないでヒットさせる予定で以下の実装をしていました。
ただ以下の実装で全角の「か」を入れると検索のヒット自体はするが、検索したワードが赤く変更されないという問題があった。
こちらの処理を実装では、バックエンドで該当する検索ワードにヒットするものを絞りこみを行い、フロントエンドでは、以下のように検索したワードに色をつけるということを行っていた。
```TypeScript
...
const splitText = (text: string, highlight: string): string[] => {
    const normalizedText = text.normalize("NFKC").toLowerCase();
    const normalizedHighlight = highlight.normalize("NFKC").toLowerCase();

    const regex = new RegExp(`(${normalizedHighlight})`, "gi");
    const splitTextArray: string[] = [];
    let lastIndex = 0;
    let match: RegExpExecArray | null;

    while ((match = regex.exec(normalizedText)) !== null) {
        if (lastIndex < match.index) {
            splitTextArray.push(text.slice(lastIndex, match.index));
        }

        splitTextArray.push(text.slice(match.index, match.index + match[0].length));
        lastIndex = match.index + match[0].length;
    }
    if (lastIndex < text.length) {
        splitTextArray.push(text.slice(lastIndex));
    }
    return splitTextArray;
};

export const HighlightedText: FC<PropsType> = (props) => {
const parts = splitText(props.text, props.highlight);
return (
        <p>
            {parts.map((part, index) =>
              part.toLowerCase().normalize("NFKC") === props.highlight.toLowerCase().normalize("NFKC") ? (
                    <span data-testid="highlishtedText" key={index} css={styles.highlight(props.highlightColor)}>
                        {part}
                    </span>
                ) : (
                    part
                )
            )}
        </p>
    );
};
...
```

### 理由
この事象が起きていたのは、検証環境、本番環境ともにMySQLのcollation(照合順序のデフォルトが`utf8mb4_0900_ai_ci`になっているため、以下の区別がない。

各文字列が同一と扱われる場合は、⚪︎としている。
|COLLATION|A,a|は,ば,ぱ,ハ,バ,パ,ﾊ|あ,ぁ|
|---|---|---|---|
|utf8mb4_0900_ai_ci|⚪︎ |⚪︎ |⚪︎|

参考
- [MySQL 8.0 でも utf8mb4\_general\_ci を使い続けたい僕らは - mita2 database life](https://mita2db.hateblo.jp/entry/2020/12/07/000000)
- [MySQLであいまい検索！ LIKE検索で濁点、半濁点を区別する方法 | 株式会社AMG Solution](https://amg-solution.jp/blog/64011)
- [utf8mb4のCOLLATEの違いについて - らぼるてっく。](https://blog.labol.co.jp/entry/mysql-collate)

しかし、上記にあるように検索したワードが赤文字にするのは、フロントで処理をしていました。
そのため以下のようにバックエンドとフロントエンドの挙動に差ができていました。

各文字列が同一と扱われる場合は、⚪︎としている。
|要素|A,a|1,１|は,ば,ぱ,ハ,バ,パ,ﾊ|あ,ぁ|
|---|---|---|---|---|
|バックエンド|⚪︎ |⚪︎ |⚪︎|
|フロントエンド|⚪︎|⚪︎|×|⚪︎|

さらにバックエンドではワードの検索にLIKE検索を用いていたため、半角濁点⇔全角濁点/半角半濁点⇔全角半濁点を同じにすることもできないということもあり、今回の実装は全てを別の文字として区別するようにcollationを`utf8mb4_ja_0900_as_cs`に変更して検索することにしました。

参考
- [MySQL 5.7で濁点・半濁点を含む半角カタカナをあいまい検索 #mysql5.7 - Qiita](https://qiita.com/oskamathis/items/d673b6991644ec488ecb)
- [MySQL の utf8mb4 の文字照合順序まとめ](https://zenn.dev/zoeponta/articles/090c68ba820a24)

## おわりに
今回は主に検索機能についての内容が多いのですが、改めて「日本語」で漢字、ひらがな、カタカナを操っている私たちってすごいなと感じさせられましたし、普段何気なく使っている検索機能ってめちゃくちゃ奥深いなと思いました。

未経験時代は、主にRuby on RailsやJavaScriptを記載しており、きちんとフロントエンド、バックエンド別れて開発するということ自体はクラベスに入ってからが初めてでした。
最初の1年は、React/TypeScriptでフロントエンドの開発ばかり行っており、2024年は初めて実務でバックエンドの開発を行うことができました。
弊社はバックエンドをKotlinで開発しており、最初のころはRuby on Railsとの違いに戸惑っていたことも多いのですが、今までの知見や先輩方のおかげで少しずつ開発ができるようになっていっています。
実際フロントエンドとバックエンド両方の開発を行うことで、どこまでがバックエンドの責務で、どこからがフロントエンドの責務なのかを考えながら開発することができるようになってきました。
また、両方行っているからこそ不具合が起きた場合の原因解明の速度を少しずつですが、上げることができたかなと思います。

株式会社クラベスでは、インフラ、バックエンド、フロントエンド全て開発体験を積めるので、是非興味がある方はお話し聞くだけでもどうぞ〜！
https://www.claves.co.jp/recruit/engineer/