メインコンテンツへスキップ
見出し画像

3分間プログラミング講座 第16回(最終回): 実践演習~簡単なWebアプリを作ってみよう(設計・実装・リファクタリング)~


    登場人物:

    • チャット先生(通称:先生): オブジェクト指向マスター。普段は優しく、たまにユーモアも交えながら解説。

    • ボット助手(通称:ボット君): プログラミングを始めたばかりの若手。素朴な疑問を投げかけるのが得意。


    ボット君: 先生!ついに実践演習ですね!これまでの知識を全部使って、Webアプリを作るなんて、ワクワクします!

    先生: その意気だ、ボット君!これまでの学習は、まさにこの「実践」のためにあったと言っても過言ではない。今日は、君が学んできたオブジェクト指向の原則、DI、Spring Data JPA、そして3層分離アーキテクチャといった知識を総動員して、簡単なWebアプリケーションを実際に作ってみよう。

    先生: アプリケーション開発は、大きく分けて「設計」「実装」「リファクタリング」というサイクルを繰り返すことが多い。今回はこの一連の流れを体験することで、知識を統合し、実践的な開発の流れを掴むことを目指すぞ!

    1. 開発するアプリケーションの概要: シンプルなTODOリスト

    先生: 今回開発するのは、ごくシンプルな「TODOリスト管理」Webアプリケーションだ。機能は以下の通り。

    • タスクの登録: 新しいTODOタスクを登録する。

    • タスクの一覧表示: 登録されている全てのタスクを表示する。

    • タスクの完了マーク: 特定のタスクを「完了」とマークする。

    • タスクの削除: 不要になったタスクを削除する。

    ボット君: TODOリスト!身近なアプリですね!

    先生: そうだね。身近な題材だからこそ、機能のイメージがしやすく、これまでの知識をどこに適用すればいいか考えやすいだろう。


    2. 設計フェーズ: アプリケーションの骨格を考える

    先生: まずは、コードを書き始める前に、アプリケーションの「骨格」を設計しよう。特に3層分離アーキテクチャの考え方を適用するぞ。

    A. 要件の再確認と機能のマッピング

    先生: 先ほど確認したTODOリストの機能を、各層の役割にマッピングしてみよう。

    • プレゼンテーション層: ユーザーからのHTTPリクエストを受け取り、JSON形式でレスポンスを返すAPIエンドポイント。

    • サービス層 (またはビジネスロジック層): タスクの登録、一覧取得、更新、削除といったビジネスロジック。

    • データアクセス層: データベースとのやり取り(Taskエンティティの永続化)。

    B. エンティティ設計

    先生: データベースに保存する「タスク」の情報を、Javaのエンティティクラスとしてどう表現するかを決めよう。

    • Taskエンティティ:

      • id (Long): タスクの一意な識別子(プライマリキー)

      • title (String): タスクのタイトル(例: 「牛乳を買う」)

      • description (String): タスクの詳細(任意)

      • completed (boolean): タスクが完了したかどうか(true/false)

      • createdAt (LocalDateTime): タスクが作成された日時

      • updatedAt (LocalDateTime): タスクが最後に更新された日時

    C. APIエンドポイント設計

    先生: プレゼンテーション層が提供するAPIのURLと、それぞれの役割を決めよう。これはRESTful APIの考え方に沿って設計する。

    画像

    ボット君: なるほど!こうやって先に決めておくと、どこに何を書けばいいか迷わずに済みますね!

    先生: その通り!設計は、地図を描くようなものだ。地図があれば、目的地まで迷わず進めるだろう?


    3. 実装フェーズ: コードに落とし込む

    先生: それでは、設計に基づいて実際にコードを書いていこう。

    A. プロジェクトの作成

    先生: まずはSpring Initializrを使って新しいSpring Bootプロジェクトを作成しよう。

    1. Webブラウザで Spring Initializr にアクセス。

    2. Project: Maven Project または Gradle Project (今回はMavenで進める)

    3. Language: Java

    4. Spring Boot: 最新の安定版 (例: 3.x.x)

    5. Project Metadata:

      • Group: com.example (任意)

      • Artifact: todo-app (任意)

      • Name: todo-app

      • Package name: com.example.todo (任意)

      • Packaging: Jar

      • Java: 17 または最新のLTS版

    6. Dependencies (依存関係):

      • Spring Web

      • Spring Data JPA

      • PostgreSQL Driver

      • Lombok

      • Spring Boot DevTools (開発を便利にするツール)

    7. GENERATEボタンをクリックしてプロジェクトをダウンロードし、お好みのIDE(IntelliJ IDEA, VS Codeなど)で開こう。

    B. データベース設定 (application.yml)

    先生: src/main/resources/application.ymlにデータベース接続情報を設定する。前回と同じ内容で大丈夫だ。

    # src/main/resources/application.yml
    
    spring:
      datasource:
        url: jdbc:postgresql://localhost:5432/your_todo_db # ① データベースURL (適宜変更)
        username: your_username # ② データベースユーザー名 (適宜変更)
        password: your_password # ③ データベースパスワード (適宜変更)
        driver-class-name: org.postgresql.Driver
    
      jpa:
        hibernate:
          ddl-auto: update # 開発時はupdateでOK。本番ではvalidateかnone推奨
        show-sql: true
        properties:
          hibernate:
            format_sql: true
            dialect: org.hibernate.dialect.PostgreSQLDialect
      profiles:
        active: dev
    
    

    先生: your_todo_dbという名前のPostgreSQLデータベースを事前に作成しておいてくれ。

    C. エンティティの実装 (Task.java)

    先生: 設計したTaskエンティティをJavaコードに落とし込もう。

    // src/main/java/com/example/todo/model/Task.java (パッケージは適宜変更)
    package com.example.todo.model; // modelパッケージを作成
    
    import jakarta.persistence.*;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import java.time.LocalDateTime;
    import org.hibernate.annotations.CreationTimestamp;
    import org.hibernate.annotations.UpdateTimestamp;
    
    @Entity
    @Table(name = "tasks")
    @Data
    @NoArgsConstructor
    public class Task {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(nullable = false)
        private String title;
    
        @Column(columnDefinition = "TEXT")
        private String description;
    
        @Column(name = "is_completed")
        private boolean completed = false;
    
        @CreationTimestamp
        private LocalDateTime createdAt;
    
        @UpdateTimestamp
        private LocalDateTime updatedAt;
    
        public Task(String title, String description) {
            this.title = title;
            this.description = description;
        }
    }
    
    

    D. リポジトリの実装 (TaskRepository.java)

    先生: データベース操作の窓口となるリポジトリインターフェースを定義しよう。

    // src/main/java/com/example/todo/repository/TaskRepository.java (パッケージは適宜変更)
    package com.example.todo.repository; // repositoryパッケージを作成
    
    import com.example.todo.model.Task;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    @Repository
    public interface TaskRepository extends JpaRepository<Task, Long> {
        // 完了していないタスクを検索するカスタムメソッド
        List<Task> findByCompletedFalse();
    
        // タイトルでタスクが存在するかチェックするカスタムメソッド
        boolean existsByTitle(String title);
    }
    
    

    E. サービス層の実装 (TaskService.java)

    先生: ビジネスロジックを担うサービス層を実装しよう。ここではDIを使ってTaskRepositoryを注入する。

    // src/main/java/com/example/todo/service/TaskService.java (パッケージは適宜変更)
    package com.example.todo.service; // serviceパッケージを作成
    
    import com.example.todo.model.Task;
    import com.example.todo.repository.TaskRepository;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    import java.util.Optional;
    
    @Service
    public class TaskService {
    
        private final TaskRepository taskRepository;
    
        // コンストラクタインジェクションでTaskRepositoryのインスタンスを注入
        public TaskService(TaskRepository taskRepository) {
            this.taskRepository = taskRepository;
        }
    
        @Transactional // タスクの追加はトランザクション管理下で行う
        public Task addTask(Task task) {
            if (taskRepository.existsByTitle(task.getTitle())) {
                throw new IllegalArgumentException("タイトルが重複しています: " + task.getTitle());
            }
            return taskRepository.save(task);
        }
    
        @Transactional(readOnly = true) // 読み取り専用のトランザクション
        public List<Task> getAllTasks() {
            return taskRepository.findAll();
        }
    
        @Transactional(readOnly = true)
        public Optional<Task> getTaskById(Long id) {
            if (id == null) {
                throw new IllegalArgumentException("タスクIDはnullであってはなりません。");
            }
            return taskRepository.findById(id);
        }
    
        @Transactional
        public Task updateTask(Long id, Task updatedTask) {
            return taskRepository.findById(id).map(task -> {
                task.setTitle(updatedTask.getTitle());
                task.setDescription(updatedTask.getDescription());
                task.setCompleted(updatedTask.isCompleted());
                return taskRepository.save(task);
            }).orElseThrow(() -> new RuntimeException("ID " + id + "のタスクが見つかりませんでした。"));
        }
    
        @Transactional
        public Task completeTask(Long id) {
            return taskRepository.findById(id).map(task -> {
                task.setCompleted(true); // 完了状態をtrueに設定
                return taskRepository.save(task);
            }).orElseThrow(() -> new RuntimeException("ID " + id + "のタスクが見つかりませんでした。"));
        }
    
        @Transactional
        public void deleteTask(Long id) {
            taskRepository.deleteById(id);
        }
    }
    
    

    F. プレゼンテーション層の実装 (TaskController.java)

    先生: ユーザーからのリクエストを受け付けるコントローラを実装しよう。ここでもDIを使ってTaskServiceを注入する。

    // src/main/java/com/example/todo/controller/TaskController.java (パッケージは適宜変更)
    package com.example.todo.controller; // controllerパッケージを作成
    
    import com.example.todo.model.Task;
    import com.example.todo.service.TaskService;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.server.ResponseStatusException; // 例外ハンドリングのために追加
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/api/tasks")
    public class TaskController {
    
        private final TaskService taskService;
    
        // コンストラクタインジェクションでTaskServiceのインスタンスを注入
        public TaskController(TaskService taskService) {
            this.taskService = taskService;
        }
    
        @GetMapping
        public List<Task> getAllTasks() {
            return taskService.getAllTasks();
        }
    
        @GetMapping("/{id}")
        public ResponseEntity<Task> getTaskById(@PathVariable Long id) {
            return taskService.getTaskById(id)
                    .map(ResponseEntity::ok)
                    .orElseGet(() -> ResponseEntity.notFound().build());
        }
    
        @PostMapping
        @ResponseStatus(HttpStatus.CREATED)
        public Task createTask(@RequestBody Task task) {
            // 例外ハンドリングの例
            try {
                return taskService.addTask(task);
            } catch (IllegalArgumentException e) {
                // 重複タイトルなど、ビジネスロジックで発生したエラー
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
            }
        }
    
        @PutMapping("/{id}")
        public ResponseEntity<Task> updateTask(@PathVariable Long id, @RequestBody Task taskDetails) {
            try {
                Task updatedTask = taskService.updateTask(id, taskDetails);
                return ResponseEntity.ok(updatedTask);
            } catch (RuntimeException e) {
                return ResponseEntity.notFound().build();
            }
        }
    
        @PutMapping("/{id}/complete")
        public ResponseEntity<Task> completeTask(@PathVariable Long id) {
            try {
                Task completedTask = taskService.completeTask(id);
                return ResponseEntity.ok(completedTask);
            } catch (RuntimeException e) {
                return ResponseEntity.notFound().build();
            }
        }
    
        @DeleteMapping("/{id}")
        @ResponseStatus(HttpStatus.NO_CONTENT)
        public void deleteTask(@PathVariable Long id) {
            taskService.deleteTask(id);
        }
    }
    
    

    G. アプリケーションの実行とテスト

    先生: ここまでできたら、アプリケーションを実行して、APIツール(Postman, Insomnia, curlなど)で動作確認をしてみよう。

    1. IDEからSpring Bootアプリケーションのメインクラス(例: TodoAppApplication.java)を実行。

    2. Postmanなどのツールを使って、以下のAPIを試してみよう。

      • タスクの作成 (POST)

        • URL: http://localhost:8080/api/tasks

        • Method: POST

        • Headers: Content-Type: application/json

        • Body (Raw, JSON):

    {
        "title": "牛乳を買う",
        "description": "スーパーで特売の牛乳を2本"
    }
    ```json
    {
        "title": "企画書作成",
        "description": "来週の会議用資料をまとめる"
    }
    • 全タスクの取得 (GET)

      • URL: http://localhost:8080/api/tasks

      • Method: GET

    • 特定のタスクの取得 (GET)

      • URL: http://localhost:8080/api/tasks/1 (IDは作成時に返されたものを使用)

      • Method: GET

    • タスクの完了マーク (PUT)

      • URL: http://localhost:8080/api/tasks/1/complete

      • Method: PUT

    • タスクの更新 (PUT)

      • URL: http://localhost:8080/api/tasks/2

      • Method: PUT

      • Headers: Content-Type: application/json

      • Body (Raw, JSON):

    {
        "title": "企画書作成(完了)",
        "description": "来週の会議用資料をまとめる",
        "completed": true
    }
    • タスクの削除 (DELETE)

      • URL: http://localhost:8080/api/tasks/1

      • Method: DELETE

    ボット君: 動いたー!ちゃんとデータが登録されて、表示されて、更新も削除もできました!感動です!


    4. リファクタリングフェーズ: コードをもっと良くする

    先生: アプリケーションが動くようになったら、次はコードを「もっと良くする」ことを考えよう。これが「リファクタリング」だ。動くコードを書くことも大切だが、読みやすく、変更しやすく、バグを起こしにくいコードに改善していくことは、プロのエンジニアにとって非常に重要なスキルだ。

    ボット君: もっと良くする?どこを改善すればいいんですか?

    先生: 例えば、今のコードにはいくつか改善の余地がある。

    A. DTO (Data Transfer Object) の導入

    先生: 現在、コントローラとサービス層の間でTaskエンティティを直接やり取りしているね。これはシンプルなアプリでは問題ないが、エンティティはデータベースの構造と密接に関わっているため、APIの入出力と完全に一致しない場合がある。また、エンティティに機密情報が含まれる場合、それをそのままAPIで公開するのはセキュリティ上のリスクにもなる。

    そこで、APIの入出力専用のオブジェクトとして「DTO (Data Transfer Object)」を導入しよう。

    • TaskRequest: クライアントからタスク作成・更新時に受け取るデータ。

    • TaskResponse: クライアントにタスク情報を返すデータ。

    // src/main/java/com/example/todo/dto/TaskRequest.java (dtoパッケージを作成)
    package com.example.todo.dto;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    public class TaskRequest {
        private String title;
        private String description;
        private boolean completed; // 更新時に使用
    }
    
    // src/main/java/com/example/todo/dto/TaskResponse.java
    package com.example.todo.dto;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import java.time.LocalDateTime;
    
    @Data
    @NoArgsConstructor
    public class TaskResponse {
        private Long id;
        private String title;
        private String description;
        private boolean completed;
        private LocalDateTime createdAt;
        private LocalDateTime updatedAt;
    }
    
    

    先生: これらを導入すると、コントローラとサービス層はDTOを使い、サービス層がエンティティに変換してリポジトリに渡す、という流れになる。

    // TaskServiceの変更点 (DTOへの変換ロジックを追加)
    // ...
    import com.example.todo.dto.TaskRequest; // 追加
    import com.example.todo.dto.TaskResponse; // 追加
    // ...
    
    @Service
    public class TaskService {
        // ... (コンストラクタは変更なし)
    
        @Transactional
        public TaskResponse addTask(TaskRequest request) { // TaskRequestを受け取る
            if (taskRepository.existsByTitle(request.getTitle())) {
                throw new IllegalArgumentException("タイトルが重複しています: " + request.getTitle());
            }
            // RequestからEntityへ変換
            Task newTask = new Task(request.getTitle(), request.getDescription());
            newTask.setCompleted(request.isCompleted()); // 初期登録時もcompletedを設定できるように
            Task savedTask = taskRepository.save(newTask);
            return convertToResponse(savedTask); // EntityからResponseへ変換
        }
    
        @Transactional(readOnly = true)
        public List<TaskResponse> getAllTasks() { // List<TaskResponse>を返す
            return taskRepository.findAll().stream()
                    .map(this::convertToResponse) // 各TaskをTaskResponseに変換
                    .toList();
        }
    
        @Transactional(readOnly = true)
        public Optional<TaskResponse> getTaskById(Long id) { // Optional<TaskResponse>を返す
            if (id == null) {
                throw new IllegalArgumentException("タスクIDはnullであってはなりません。");
            }
            return taskRepository.findById(id)
                    .map(this::convertToResponse); // TaskをTaskResponseに変換
        }
    
        @Transactional
        public TaskResponse updateTask(Long id, TaskRequest request) { // TaskRequestを受け取る
            return taskRepository.findById(id).map(task -> {
                task.setTitle(request.getTitle());
                task.setDescription(request.getDescription());
                task.setCompleted(request.isCompleted());
                Task updatedTask = taskRepository.save(task);
                return convertToResponse(updatedTask); // EntityからResponseへ変換
            }).orElseThrow(() -> new RuntimeException("ID " + id + "のタスクが見つかりませんでした。"));
        }
    
        @Transactional
        public TaskResponse completeTask(Long id) { // TaskResponseを返す
            return taskRepository.findById(id).map(task -> {
                task.setCompleted(true);
                Task completedTask = taskRepository.save(task);
                return convertToResponse(completedTask); // EntityからResponseへ変換
            }).orElseThrow(() -> new RuntimeException("ID " + id + "のタスクが見つかりませんでした。"));
        }
    
        // TaskエンティティをTaskResponseDTOに変換するヘルパーメソッド
        private TaskResponse convertToResponse(Task task) {
            TaskResponse response = new TaskResponse();
            response.setId(task.getId());
            response.setTitle(task.getTitle());
            response.setDescription(task.getDescription());
            response.setCompleted(task.isCompleted());
            response.setCreatedAt(task.getCreatedAt());
            response.setUpdatedAt(task.getUpdatedAt());
            return response;
        }
    }
    
    
    // TaskControllerの変更点 (DTOの利用)
    // ...
    import com.example.todo.dto.TaskRequest; // 追加
    import com.example.todo.dto.TaskResponse; // 追加
    import org.springframework.web.server.ResponseStatusException; // 追加
    // ...
    
    @RestController
    @RequestMapping("/api/tasks")
    public class TaskController {
    
        private final TaskService taskService;
    
        public TaskController(TaskService taskService) {
            this.taskService = taskService;
        }
    
        @GetMapping
        public List<TaskResponse> getAllTasks() { // TaskResponseのリストを返す
            return taskService.getAllTasks();
        }
    
        @GetMapping("/{id}")
        public ResponseEntity<TaskResponse> getTaskById(@PathVariable Long id) { // TaskResponseを返す
            return taskService.getTaskById(id)
                    .map(ResponseEntity::ok)
                    .orElseGet(() -> ResponseEntity.notFound().build());
        }
    
        @PostMapping
        @ResponseStatus(HttpStatus.CREATED)
        public TaskResponse createTask(@RequestBody TaskRequest request) { // TaskRequestを受け取る
            try {
                return taskService.addTask(request);
            } catch (IllegalArgumentException e) {
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
            }
        }
    
        @PutMapping("/{id}")
        public ResponseEntity<TaskResponse> updateTask(@PathVariable Long id, @RequestBody TaskRequest request) { // TaskRequestを受け取る
            try {
                TaskResponse updatedTask = taskService.updateTask(id, request);
                return ResponseEntity.ok(updatedTask);
            } catch (RuntimeException e) {
                return ResponseEntity.notFound().build();
            }
        }
    
        @PutMapping("/{id}/complete")
        public ResponseEntity<TaskResponse> completeTask(@PathVariable Long id) { // TaskResponseを返す
            try {
                TaskResponse completedTask = taskService.completeTask(id);
                return ResponseEntity.ok(completedTask);
            } catch (RuntimeException e) {
                return ResponseEntity.notFound().build();
            }
        }
    
        // deleteTaskは変更なし
    }
    
    

    ボット君: DTO!なるほど、APIの形とデータベースの形を切り離すんですね!これなら、データベースの設計が変わっても、APIの利用者に影響が出にくいし、その逆もそうですね!

    先生: その通り!これが「関心の分離」をさらに推し進める考え方だ。

    B. より適切な例外の利用

    先生: 現在、タスクが見つからない場合にRuntimeExceptionをスローしているが、これは少し汎用的すぎる。より具体的な例外を使うことで、エラーの種類を明確にし、クライアント側でのハンドリングを容易にできる。

    例えば、ResourceNotFoundExceptionのようなカスタム例外を作成し、それをスローするように改善できる。

    // src/main/java/com/example/todo/exception/ResourceNotFoundException.java (exceptionパッケージを作成)
    package com.example.todo.exception;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    @ResponseStatus(HttpStatus.NOT_FOUND) // この例外がスローされたらHTTP 404を返す
    public class ResourceNotFoundException extends RuntimeException {
        public ResourceNotFoundException(String message) {
            super(message);
        }
    }
    
    

    先生: そして、TaskServiceのupdateTaskやcompleteTaskなどでこの例外を使う。

    // TaskServiceの変更点 (ResourceNotFoundExceptionの利用)
    // ...
    import com.example.todo.exception.ResourceNotFoundException; // 追加
    // ...
    
    @Service
    public class TaskService {
        // ...
    
        @Transactional
        public TaskResponse updateTask(Long id, TaskRequest request) {
            return taskRepository.findById(id).map(task -> {
                task.setTitle(request.getTitle());
                task.setDescription(request.getDescription());
                task.setCompleted(request.isCompleted());
                Task updatedTask = taskRepository.save(task);
                return convertToResponse(updatedTask);
            }).orElseThrow(() -> new ResourceNotFoundException("ID " + id + "のタスクが見つかりませんでした。")); // 変更
        }
    
        @Transactional
        public TaskResponse completeTask(Long id) {
            return taskRepository.findById(id).map(task -> {
                task.setCompleted(true);
                Task completedTask = taskRepository.save(task);
                return convertToResponse(completedTask);
            }).orElseThrow(() -> new ResourceNotFoundException("ID " + id + "のタスクが見つかりませんでした。")); // 変更
        }
    
        @Transactional
        public void deleteTask(Long id) {
            // 削除対象が存在しない場合でもエラーとしない場合はfindById().ifPresent()を使うか、
            // 存在しない場合に例外をスローしたい場合は以下のようにする
            if (!taskRepository.existsById(id)) { // 存在チェックを追加
                throw new ResourceNotFoundException("ID " + id + "のタスクが見つかりませんでした。");
            }
            taskRepository.deleteById(id);
        }
    }
    
    

    先生: TaskControllerでは、@ResponseStatus(HttpStatus.NOT_FOUND)アノテーションをResourceNotFoundExceptionに付けているため、try-catchで明示的にResponseEntity.notFound().build()を返す必要がなくなる。Springが自動的にHTTPステータスコードを404 Not Foundに変換してくれるんだ。

    // TaskControllerの変更点 (ResourceNotFoundExceptionの利用)
    // ...
    // import org.springframework.web.server.ResponseStatusException; // 不要になる場合がある
    import com.example.todo.exception.ResourceNotFoundException; // 追加
    // ...
    
    @RestController
    @RequestMapping("/api/tasks")
    public class TaskController {
        // ...
    
        @PutMapping("/{id}")
        public ResponseEntity<TaskResponse> updateTask(@PathVariable Long id, @RequestBody TaskRequest request) {
            // ResourceNotFoundExceptionがスローされた場合、Springが自動で404を返すためtry-catchが不要になる
            TaskResponse updatedTask = taskService.updateTask(id, request);
            return ResponseEntity.ok(updatedTask);
        }
    
        @PutMapping("/{id}/complete")
        public ResponseEntity<TaskResponse> completeTask(@PathVariable Long id) {
            // ResourceNotFoundExceptionがスローされた場合、Springが自動で404を返すためtry-catchが不要になる
            TaskResponse completedTask = taskService.completeTask(id);
            return ResponseEntity.ok(completedTask);
        }
    
        @DeleteMapping("/{id}")
        @ResponseStatus(HttpStatus.NO_CONTENT)
        public void deleteTask(@PathVariable Long id) {
            // ResourceNotFoundExceptionがスローされた場合、Springが自動で404を返すためtry-catchが不要になる
            taskService.deleteTask(id);
        }
    }
    
    

    ボット君: わー!エラーの種類を明確にするだけで、コードがもっとスッキリするんですね!すごい!

    先生: そうだね。リファクタリングは、このように少しずつコードを改善していく作業だ。小さな改善の積み重ねが、最終的には高品質なアプリケーションに繋がるんだ。

    C. なぜリファクタリングが重要か

    先生: リファクタリングは、アプリケーションの機能を変えずに内部構造を改善する作業だ。これは開発の初期段階だけでなく、機能追加やバグ修正の際にも常に行われるべきだ。

    • 保守性の向上: コードが整理され、理解しやすくなるため、バグの発見や修正が容易になる。

    • 拡張性の向上: 変更に強い構造になるため、新しい機能を追加する際に既存のコードを大きく修正する必要が減る。

    • 可読性の向上: チーム開発において、他の開発者がコードを理解しやすくなる。

    • バグの抑制: コードが複雑になることで発生する潜在的なバグを減らすことができる。

    5. まとめ: 次のステップへ

    先生: ボット君、今日の演習、本当によく頑張った!これで君は、Javaのオブジェクト指向の基本から、Spring Frameworkを使ったWebアプリケーション開発の「設計・実装・リファクタリング」という一連のサイクルを体験したことになる。

    ボット君: 先生、本当にありがとうございました!最初は難しそうだったけど、先生のおかげで、自分でWebアプリを作れるようになりました!動くものを作れたのが、すごく嬉しいです!

    先生: その達成感が何より大切だ。今回学んだ知識は、現代のJavaアプリケーション開発における非常に強力な基盤となる。もちろん、Spring Frameworkには他にも認証・認可、テスト駆動開発、非同期処理、マイクロサービスなど、学ぶべきことは山ほどあるが、今回のシリーズで得た知識と経験があれば、次のステップへ大きく踏み出せるはずだ。

    先生: これからも学び続けて、素晴らしいエンジニアになってくれ!もしまた何か疑問に思うことがあれば、いつでも質問しに来てくれて構わないぞ!

    完



    あとがき

    これまで「チャット先生とボット君のオブジェクト指向プログラミング講座」を最後までお読みいただき、本当にありがとうございました!

    Javaの基本から始まり、オブジェクト指向の奥深さ、そしてSpring Frameworkを使ったWebアプリケーション開発の基礎までをご紹介してきました。 皆さんのプログラミング学習の一助になれれば幸いです。この講座で得た知識と学びへのモチベーションが、皆さんの今後の学習やキャリアの大きな一歩となることを心から願っています。

    また、次回から「チャット先生とボット君のデータベース講座」を開講します。 ITシステムに不可欠な「データの管理」について、基礎から実践までをPostgreSQLをベースに分かりやすく解説していきますので、引き続きよろしくお願いいたします。

    また次回の講座でお会いできることを楽しみにしています!


    #オブジェクト指向 #プログラミング初心者 #Java #SpringFramework #3層分離アーキテクチャ #実践演習 #プログラミング学習 #入門

     
     
     
    まだ知らない『面白い』への案内人。人・作品・AIの魅力を見つけ、物語と知識に変えて届けるコンシェルジュです。毎週土曜は漫画ソムリエ。スキ動画コンテストほか、いろいろ実験中。

    あなたへのおすすめ