taro_poyotaのブログ

【Java】JPAで基底クラスを使った作成日・更新日の管理方法

1. なぜ基底クラスで日時を管理するのか?

エンティティを使用する際に、作成日時や更新日時を追跡することはよくあります。
しかし、複数のエンティティに同じ createdAt や updatedAt プロパティを持たせると、コードが重複し保守性が低下します。

そこで基底クラス(共通の親クラス)にこれらのプロパティを持たせることで、再利用性を高めつつ、日時管理を一箇所に集約することができます。
これにより、以下のようなメリットがあります。

重複を排除: 各エンティティに createdAt や updatedAt を定義する必要がありません。
保守性向上: 日時管理のロジックを基底クラスに集約することで、修正や拡張が容易になります。

2. 基底クラスの作成手順

2.1 基底クラス BaseEntity の作成
まず、共通の親クラスとして BaseEntity を作成します。
JPA の @PrePersist と @PreUpdate を使い、エンティティが作成されるときと更新されるときに自動的に日時がセットされるようにします。

import jakarta.persistence.*;
import java.time.LocalDateTime;

@MappedSuperclass
public abstract class BaseEntity {

    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @PrePersist
    protected void onCreate() {
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    protected void onUpdate() {
        this.updatedAt = LocalDateTime.now();
    }

    // getter メソッド
    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public LocalDateTime getUpdatedAt() {
        return updatedAt;
    }
}

アノテーションの説明
@MappedSuperclass: このクラスは実体化されませんが、子クラスにマッピング情報が継承されます。
@PrePersist: エンティティが初めて保存されるときに呼び出され、createdAt と updatedAt に現在日時をセットします。
@PreUpdate: エンティティが更新されるときに呼び出され、updatedAt に現在日時をセットします。

2.2 エンティティで BaseEntity を継承する
作成した BaseEntity クラスを継承することで、他のエンティティでも createdAt や updatedAt の管理ができるようになります。

import jakarta.persistence.Entity;
import jakarta.persistence.Table;

@Entity
@Table(name = "your_entity")
public class YourEntity extends BaseEntity {

    private String name;

    // getter, setter など
}

YourEntity には createdAt と updatedAt のフィールドが自動的に追加され、データベースに保存されるとき、または更新されるときにそれぞれの日付が設定されます。

3. 自動更新 vs 手動更新の使い分け

3.1 自動更新のメリット
通常、日時の自動更新は @PrePersist や @PreUpdate によって行われます。
リポジトリの save() メソッドを使うと、onUpdate() や onCreate() が自動で呼び出され、updatedAt や createdAt が正しく設定されます。

以下の例は自動更新の使い方です。

YourEntity entity = repository.findById(id).orElseThrow();
entity.setName("Updated Name");

// save() 時に自動で onUpdate() が呼び出され、updatedAt が更新される
repository.save(entity);

3.2 手動で onUpdate() を呼び出す場合
手動で onUpdate() を呼び出すことで updatedAt を更新することも可能です。

YourEntity entity = new YourEntity();
entity.setName("New Name");

// 手動で onUpdate を呼び出す
entity.onUpdate();

手動で呼び出すと意図しない更新が発生するリスクがあるため、通常は JPA の自動更新機能に任せる方が安全です。

4. まとめ

JPA の @PrePersist と @PreUpdate アノテーションを使うことで、createdAt や updatedAt を基底クラスで一元管理し、エンティティのライフサイクルに応じて自動的に日時を更新することができます。
これにより、コードの保守性が向上し、再利用可能な基盤が構築できます。

参考文献

JPA Entity Lifecycle Events
Applying @PrePersist and @PreUpdate in JPA

【Java】Spring Data JPAの.saveメソッド

.saveメソッドの概要

.save メソッドは、データを保存する操作を担当するメソッドです。
Javaでは特に、データベースとのやりとりを行う際や、ファイルへのデータ保存などで利用されます。
Spring Data JPAリポジトリクラスで一般的に見られるメソッドであり、エンティティを保存したり、更新したりする際に活用されます。

.saveメソッドの新規保存と更新の判定 〜IDによる判断〜

.saveメソッドは、データの保存を行う際、idの有無によって「新規保存」か「更新」かを自動的に判別します。
この仕組みは、ちょうど日用品を収納棚にしまうときに「もう同じ物があるかどうか確認する」ようなイメージです。

.saveメソッドを使ってみよう 〜例:連絡先の保存〜

ここで一つ例を挙げます。
新しい連絡先(友人や同僚などの名前と電話番号)をスマホの連絡先アプリに保存するシーンです。

新しい連絡先の保存

新しい友人の連絡先を追加したいとき、まずスマホの連絡先アプリを開いて、名前や電話番号を入力して「保存」ボタンを押します。
この「保存」を実行するのが、Javaにおける.saveメソッドです。
新しいデータを保存する場合、そのデータにはまだidが設定されていません。
これを.saveメソッドに渡すと、データベースは新しいIDを生成し、データを新規に保存します。
IDが無い場合は「新しい物」とみなされ、追加されると考えると分かりやすいです。

Contact newContact = new Contact("田中さん", "080-1234-XXXX");
contactRepository.save(newContact); // 新しい連絡先の保存

既存の連絡先の更新

次に、既存の友人の電話番号が変わったとします。
スマホの連絡先アプリでその友人の情報を検索し、新しい電話番号を入力して保存するだけで、以前の番号が新しいものに「上書き」されます。
.saveメソッドも同じく、IDが指定されている場合は「既存の物」とみなし、情報を更新します。
※existingContactがデータベースからfindByIdなどで取得されたものであれば、idフィールドにはすでに値がセットされており、.saveメソッドはそのidを見て「更新」操作を行います。

existingContact.setPhoneNumber("080-8765-XXXX); // 新しい電話番号に更新
contactRepository.save(existingContact); // 更新して保存

.saveメソッドは、データにidがあるかどうかを見て、自動的に「新規保存」か「更新」かを判断してくれるため、
開発者が明示的に新規や更新を区別する手間が省けます。
コードがシンプルになり、開発効率が向上するのが大きな利点です。

.saveメソッドを使うときの注意 〜例:収納棚の整理〜

データを「保存」することは、実は「物を収納する」ことにも似ています。
次のようなポイントが重要です。

定期的な整理

.saveメソッドをむやみに使うと、収納棚がいっぱいになり、探し物が大変になるのと同じで、データベースにも負荷がかかります。
例えば、連絡先の大量のデータを一度に保存する場合は、まとめて整理する方が効率的です。

バッチ処理
一度に複数のデータを保存する場合、以下のようにsaveAll()を使うと効率的です。
例えばリストに入った連絡先データをまとめて一度に保存することで、データベースの負荷を軽減します。

List<Contact> contacts = Arrays.asList(
    new Contact("田中", "080-1234-XXXX"),
    new Contact("鈴木", "090-9876-XXXX"),
    new Contact("山本", "070-5555-XXXX")
);
contactRepository.saveAll(contacts); // 複数の連絡先をまとめて保存

データの削除と追加

引っ越しなどで不要になったものを捨てるように、Javaでも不要なデータを削除しておくと、後々の管理が楽になります。
.saveメソッドでは、すでにデータが存在するか確認しながら、必要なものだけを更新するのがベストです。

まとめ

.saveメソッドは、データベースにデータを「保存」し、必要に応じて「更新」するためのとても便利な道具です。
日常生活の収納や整理と同じように、適切に使うことでコードがスッキリし、アプリケーションがスムーズに動くようになります。

参考文献

JPAの仕様を洗っていく
Spring Data Core

【Java】マジックナンバーとフラグを使ったHTML表示の動的制御とサービスでの再利用

はじめに

プログラミングにおいて、マジックナンバーとはコード中にハードコーディングされた数値のことを指します。
メンテナンス性や可読性の向上のため、マジックナンバーは共通クラスにまとめて管理することが推奨されています。
また、複数の箇所で使われるフラグやロジックは、サービス層に分けて再利用可能な形にすることで、コードの保守性をさらに高めることができます。

今回は、Javaマジックナンバーを共通クラスにまとめ、モデルでフラグ管理を行い、サービス層でロジックを再利用しながら、Thymeleafを使ってHTML上で条件分岐させる方法について書こうと思います。

マジックナンバーの管理

まず、マジックナンバーは共通クラスにまとめて定数として管理します。
例えば、MagicNumberConstants.java というクラスに次のように定義します。

public class MagicNumberConstants {
    //都道府県のマジックナンバー
    public static final int TOKYO = 13;
    public static final int OSAKA = 27;
    public static final int FUKUOKA = 40;

    //その他マジックナンバーなど

}

これにより、マジックナンバーが変更になった場合でも、コード全体を一括で修正でき、メンテナンス性が向上します。
今回はわかりやすく都道府県にしてみました。

マジックナンバーを使ったフラグの作成

入力された都道府県コードが特定の都道府県(例えば東京)と一致するかどうかを判定するフラグをコントローラーで設定します。

@Controller
public class PrefectureController {
    @GetMapping("/checkPrefecture")
    public String checkPrefecture(@RequestParam("prefectureCode") int prefectureCode, Model model) {
        boolean isTokyo = (prefectureCode == PrefectureConstants.TOKYO);
        model.addAttribute("isTokyo", isTokyo);
        return "prefecturePage";
    }
}

ここでは、入力された都道府県コードが東京 (PrefectureConstants.TOKYO) と一致するかどうかを判定し、isTokyo フラグとしてモデルに渡しています。

Thymeleafでの表示制御

Thymeleafを使って、マジックナンバーに応じたHTMLの表示制御を行います。

<div th:if="${isTokyo}">
    <p>東京都にお住まいですね!</p>
</div>
<div th:unless="${isTokyo}">
    <p>その他の都道府県ですね。</p>
</div>

th:if は条件が true の場合に表示され、th:unless は逆に false の場合に表示されます。
この例では、isTokyo フラグが true の場合に「東京都にお住まいですね!」というメッセージを表示し、
それ以外の場合には「その他の都道府県ですね。」というメッセージが表示されます。

フラグをサービスで使い回す

もし、特定のフラグ(例:都道府県コードの判定)を複数のコントローラーや他のクラスで使うことが想定される場合、サービス層にロジックを分けておくと便利です。
これにより、コードの重複を避け、メンテナンス性を高めることができます。

フラグ判定ロジックを Impl クラスに実装する

サービス層のフラグ判定ロジックを、インターフェースとその実装クラスに分けると、コードがさらに拡張性を持つようになります。
都道府県の判定を行う PrefectureService インターフェースと、その実装クラス PrefectureServiceImpl を作成します。

インターフェースの定義
public interface PrefectureService {
    boolean isTokyo(int prefectureCode);
}
実装クラスの定義
@Service
public class PrefectureServiceImpl implements PrefectureService {

    @Override
    public boolean isTokyo(int prefectureCode) {
        return prefectureCode == PrefectureConstants.TOKYO;
    }
}

このように、インターフェースを使って定義し、その実装を Impl クラスにまとめることで、必要に応じて別の実装クラスを追加することも簡単になります。
例えば、別の条件で都道府県判定を行う実装を追加することが将来的に必要になった際にも、既存のコードを変更せずに新しい実装を提供できます。

サービスの利用
@Controller
public class PrefectureController {

    private final PrefectureService prefectureService;

    public PrefectureController(PrefectureService prefectureService) {
        this.prefectureService = prefectureService;
    }

    @GetMapping("/checkPrefecture")
    public String checkPrefecture(@RequestParam("prefectureCode") int prefectureCode, Model model) {
        boolean isTokyo = prefectureService.isTokyo(prefectureCode);
        model.addAttribute("isTokyo", isTokyo);
        return "prefecturePage";
    }
}

こうすることで、都道府県判定のロジックが他のクラスにも簡単に適用できるようになり、コードの一貫性と再利用性が向上します。

まとめ

マジックナンバーを共通クラスにまとめることで、メンテナンス性が向上し、フラグを使って表示を制御することで、柔軟に画面を構築できるようになります。
Thymeleafと組み合わせることで、簡単に動的な表示を実現できるため、使いどころが多いです。
バックエンド側でtrue/falseのフラグ判定までしておくと、フロントコードがすっきりします。

また、今回都道府県フラグを例にService層について書きましたが、ロジックはService層にメソッドを作成して、どこからでも呼び出しできるようにするのがおすすめです!

参考資料

「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 - マジックナンバー

Javaのインターフェイスを実装するimplementsとは?

【SQL】SQLで曖昧検索を実現する:ILIKEとワイルドカードの使い方

はじめに

SQLでデータを検索するとき、正確な一致だけでなく、部分一致や曖昧検索をしたいことがよくあります。
その場合に便利なのが、LIKE または大文字小文字を区別しない ILIKE とワイルドカード % を使った検索です。
今回は、ILIKE と % を使った曖昧検索の方法について書きたいと思います。

LIKE と ILIKE の違い

LIKE はSQLで部分一致検索を行うために使うキーワードですが、標準では大文字小文字を区別します。
ILIKE は LIKE と同じ機能を持ちながら、大文字小文字を区別しない検索ができるものです。

SELECT * FROM users WHERE name LIKE 'taro%';  -- 大文字小文字を区別
SELECT * FROM users WHERE name ILIKE 'taro%'; -- 大文字小文字を区別しない

ILIKE を使えば、"taro", "Taro", "TARO" といった全てのバリエーションにマッチさせることができます。

ワイルドカード % と _ の使い方

曖昧検索にはワイルドカードが不可欠です。
代表的なワイルドカードは次の2つです。

  1. %: 0文字以上の任意の文字列にマッチ。
  2. _: ちょうど1文字にマッチ。

例えば、次のクエリでは名前に "taro" を含むすべてのレコードを取得します。

SELECT * FROM users WHERE name ILIKE '%taro%';

このクエリでは、名前の前後に任意の文字があっても "taro" が含まれていればヒットします。

ILIKE を使った具体的な例

部分一致の検索

SELECT * FROM products WHERE product_name ILIKE '%phone%';

このクエリは、product_name カラムに "phone" を含むすべての商品を検索します。
"Smartphone", "Headphone" などにヒットします。

特定のパターンにマッチする検索

SELECT * FROM employees WHERE email ILIKE 't%_xxx@xxx.xxx';

このクエリでは、メールアドレスが "t" で始まり、その次に1文字があり、その後に "xxx@xxx.xxx" で終わるパターンにマッチします。
たとえば、"t_xxx@xxx.xxx" や "taro_xxx@xxx.xxx" がヒットします。

パフォーマンスに注意

ILIKE やワイルドカードを使った検索は便利ですが、特にワイルドカード % を先頭に置いた検索(%word の形式)はパフォーマンスに影響することがあります。
ワイルドカード % を検索文字列の先頭に使うと、インデックスを利用できず、データベース全体をスキャンするため、検索速度が遅くなることがあります。
特に大量データの場合、パフォーマンスに大きな影響を与えるので注意が必要です。

まとめ

SQLでの曖昧検索は、ILIKE とワイルドカード % を使うことで簡単に実現できます。
特に、ユーザーが部分的な情報しか持っていない場合や、大文字小文字を区別しない検索が必要な場面で非常に便利です。
ただし、パフォーマンスに影響する可能性があるため、大量データを扱う場合にはインデックスや検索の最適化を検討することが重要です。

参考資料

LIKE および ILIKE

インデックスとは?仕組みをわかりやすく解説

【Java】フラグを使用してHTMLの表示や遷移先を変更する方法

はじめに

ユーザーの遷移元によって画面の表示や次の遷移先を柔軟に変更するケースがよくあります。
今回は、Javaを使ってフラグを hidden フィールドに持たせ、遷移元に応じてHTMLの表示やその先の遷移先を動的に制御する方法について書きたいと思います。

実装の流れ

  1. 遷移元ページで hidden フィールドにフラグをセット
  2. そのフラグをリクエストパラメータとして受け取り、Javaのコントローラで取得
  3. モデルにフラグを格納し、次のページでHTML表示や遷移先を制御

遷移元ページでの hidden フィールドの設定

遷移元のHTMLで hidden フィールドを使用してフラグをセットします。
例として、ユーザーが「ログインページ」か「新規登録ページ」から遷移してきたかを判定するフラグを持たせます。

html

<form action="/nextPage" method="POST">
    <input type="hidden" name="sourcePage" value="loginPage">
    <button type="submit">次へ</button>
</form>

ここで、hidden フィールドに "loginPage" というフラグ(sourcePage)を設定しています。
これを他のページでは "registerPage" に変更して使用します。

Javaコントローラでフラグを受け取る

Springのコントローラでリクエストパラメータを受け取り、そのフラグをモデルに格納します。
@RequestParam を使用してパラメータを取得します。

java

@Controller
public class PageController {

    @PostMapping("/nextPage")
    public String handleNextPage(@RequestParam("sourcePage") String sourcePage, Model model) {
        model.addAttribute("sourcePage", sourcePage);
        return "nextPage";
    }
}

この例では、sourcePage をリクエストから受け取り、モデルに追加しています。
これにより、次のページで sourcePage に基づいた動作を制御できます。

HTMLでの表示と遷移先の変更

次のページで、受け取ったフラグに基づいてHTMLを動的に変更します。
Thymeleafテンプレートエンジンを使った例です。

html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>次のページ</title>
</head>
<body>
    <h1 th:if="${sourcePage == 'loginPage'}">ログインから遷移してきました</h1>
    <h1 th:if="${sourcePage == 'registerPage'}">新規登録から遷移してきました</h1>

    <form action="#" th:action="@{${sourcePage == 'loginPage'} ? '/dashboard' : '/welcome'}" method="POST">
        <button type="submit">次へ進む</button>
    </form>
</body>
</html>

flag の値に応じてメッセージを切り替え、次の遷移先URLも制御しています。
flag が "login" の場合は /dashboard に遷移し、"register" の場合は /welcome に遷移します。

まとめ

このように、JavaとHTMLの hidden フィールドを使ってフラグ(今回の例では sourcePage)を渡し、コントローラでそのフラグを受け取ってモデルに格納することで、
遷移元に応じた画面表示や遷移先の制御が可能になります。
特定の条件に基づいて動作を変更できるため、共通のHTMLテンプレートを使い回しながら、様々なページに対応することができます。

例えば、今回のように sourcePage によってメッセージや遷移先を動的に変更することで、同じテンプレートを異なる目的で再利用でき、開発効率も向上します。
この方法を活用すれば、メンテナンス性の高いコードを書くことができます。

【SQL】NULL値を処理する:COALESCE関数の使い方

はじめに

データベースを扱っていると、NULL値の処理に頭を悩ませることがあります。
特定のカラムの値が NULL でない場合にその値を取得し、NULL の場合は別の値を返す。
そんな場合に便利な関数がSQLの COALESCE 関数です。
今回はその使い方について備忘録としてまとめます。

COALESCE関数とは

COALESCE 関数は、複数の引数の中から最初に NULL ではない値を返す便利な関数です。
例えば、複数のカラムや値の中で、どれが優先的に使えるのかを指定できます。

基本構文

COALESCE(引数1, 引数2, ..., 引数N)

最初に NULL ではない引数が返され、すべての引数が NULL の場合は NULL が返されます。

使用例

例1:複数のカラムをチェックして値を返す

column1 が NULL でない場合はその値を返し、もし NULL なら column2 を返します。
column2 も NULL の場合は、'default value' が返されます。

SELECT COALESCE(column1, column2, 'default value') AS result
FROM table_name;

例2:NULLの代わりに別の値を返す

特定のカラムが NULL である場合、指定した値を返す例です。

SELECT COALESCE(email, 'メールが未登録です') AS email_address
FROM users;

この例では、email が NULL の場合に "メールが未登録です" という文字列が表示されます。

まとめ

COALESCE 関数は、NULL値を扱う際にとても便利な関数です。
デフォルト値を設定したり、優先度をつけた複数のカラムや引数を扱うときに活用できるので、データベースのクエリでよく使用します。
今後も実際のプロジェクトで活用していきたいです。

参考資料

Oracle® Database SQL言語リファレンス

独学プログラマーのプログラミングノート

6ヶ月のプログラミングスクールを終えて

2024年1月下旬から受講していたプログラミングスクールを卒業したので、ブログに気持ちなど残そうと思います。

プログラミングスクールに通うことを決めた理由

10年ほど看護師をしておりました。
病棟、救急、手術室、クリニック、治験。。。この10年でかなり幅広く看護師経験をつみました。

医療の仕事に楽しさを感じつつも、子育てしながらいつまで続けられるのかな?という漠然な不安がありました。

夫がエンジニアということで、元々エンジニアという仕事に興味はありました。
私生活で医療現場の効率化をはかるアプリを使用し、自分も現場の課題解決ができるアプリを作ってみたい。と思いが徐々に湧いてきました。
もう30歳だし、やるなら早いほうがいい!と思い、プログラミング学習を始めることにしました。

一瞬独学で学習するも???すぎて、時間も勿体無い!となり、プログラミングスクール受講を決意しました。

学習開始前の準備と期待

プログラミングスクールでは、週に1日は時間割学習(8時間)をしなければいけませんでした。
時間割学習日だけでは6ヶ月でカリキュラムを終わらせるための学習時間が不足するため、時間割学習外でも学習する時間を確保する必要がありました。

絶対に「子育てのせいで勉強できない。」とか、人のせいにしたくなかったので、
受講するにあたって、家族とはよく話し合いをしました。

元々平日+日曜日休みというのもあり、家族全員が休日なのは日曜日のみだったので、
時間割学習日:平日1日
と設定。

家族全員が一緒に過ごせる時間は家族優先。
学習日以外は家族と過ごさない時間を使えばいい!となり、
学習日以外は家族が寝ている時間を使って学習をしていました。 (主に朝活)

タイピングくらいしかしたことない、独学で挫折した自分が、果たして完走できるのかな?という不安はありました。

学習と家族の時間の両立

「学習」と「家族の時間」のメリハリをつけることで、学習中の集中力を保つことができたと思っています。
カリキュラムを進めたい焦り?みたいなものがなかったわけではありませんが、
家族と過ごす時間を作るために、今日は絶対ここまで終わらせるぞ!みたいな気持ちで取り組めていたと思います。
学習がとても嫌いというわけではありませんでしたが、学習デトックスDay(家族全員の休日)はいい息抜きになっていたと思います。

毎日の会話がプログラミングの壁打ちになってしまったこともありましたが、嫌な顔せずに相手になってくれていた夫には感謝でいっぱいです。

スクールでの学習内容と体験

HTML / CSS / JavaScript / Ruby / Ruby on Railsの学習をしました。
ターミナルという存在も知らなかった自分でしたが、そんな自分にも文字媒体でもわかりやすく記載されたカリキュラムのおかげで、挫折せず進められたと思います。
初めはカリキュラムの書いてある通りにコードを書いてみて、動いて。。。なんだかできた気持ちになってしまっている自分に「大丈夫か?わかっているのか?」という不安がありました。
しかし答えのない実装を自分で書くフェーズになり、その時初めて点と点がつながった!みたいな瞬間を味わうことができました。

今思えば、引っ越ししてきた当時の近所を1周一旦歩いて、家の周辺になにあるか見てみよ〜くらいな感じ、
2周目に1周目でそういえば見たな、あの店!みたいな感覚?
プログラミング学習は街歩きみたいな感じ?と思って学習してました。

仲間との出会いと成長

月に1度、キャッチアップゼミというものに参加していました。
別の受講期の方からお話を聞いて刺激やアドバイスをいただき、自分自身とても成長できたと思ってます。

卒業後もXやコミュニティでつながらせていただけていることにも嬉しさを感じます。

成果と成長の実感

6ヶ月のスクールカリキュラムを修了したからといって、すぐにすごいものを作ることができるわけではないです。
カリキュラム外で自分からどんどんキャッチアップする必要もありますし、開発力の面では自分はまだまだです。
しかし、6ヶ月のカリキュラムを通して、学習を継続する力、質問する力、自分で調べる力などはつけられたのかなと思っています。

これからの目標と展望

8月からの就職先が決まったので、まずは就職先で使用する言語(Java)のキャッチアップを行い、エンジニアとしての実務経験をつんでいきたいです。
元々エンジニアを目指した理由として、医療業界をよくするサービスを作ってみたい!というものがあるので、
医療者の立場から案を出せるようなシステム開発に携わってみたいです。

まとめ

この6ヶ月間、私を支えてくれた家族や仲間に心から感謝しています。
プログラミングを学ぶことは大変でしたが、それ以上に得るものが多かったです。
エンジニアとしてはこれからですが、学習する習慣は継続し、成長していきたいです。

これからプログラミングを学ぼうとしている皆さんも、ぜひ楽しみながら学んでください。