設計・テスト

ユーティリティクラスの良い例と悪い例を実装してみた

ごく単純なJavaクラスの実装であっても、実装方法はいくつか考えられるため、その中で「最も良い」実装方法を検討し選択する必要がある。今回は、ユーティリティクラスを題材に、良い実装例と悪い実装例を作成してみたので、そのサンプルプログラムを共有する。

前提条件

下記記事の実装が完了していること。

IntelliJ IDEA上でGradleを使ってWeb画面のSpring Bootプロジェクトを作成してみたSpring Bootのプロジェクトを新規作成を「IntelliJ IDEA」のメニューから実施しようとしたところ、無料の「Commun...

サンプルプログラムの作成

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。

今回作成したユーティリティクラスの良い例は以下の通り。

package com.example.demo;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.List;

// セッションから指定したキー値に対応するリストを取得するユーティリティクラス
public class DemoUtil {

    /**
     * セッションから指定したキー値に対応するリストを取得する
     * @param key キー値
     * @return 指定したキー値に対応するリスト
     */
    public static List<String> getHashList(String key){
        // リクエストオブジェクトがnullの場合は、nullを返却する
        ServletRequestAttributes requestAttributes
              = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(requestAttributes == null){
            return null;
        }
        // セッションがnullの場合は、nullを返却する
        HttpSession session = requestAttributes.getRequest().getSession(false);
        if(session == null){
            return null;
        }
        // セッションからHashMapが取得できない場合は、nullを返却する
        HashMap<String, List<String>> hashMap
                = (HashMap<String, List<String>>)session.getAttribute("sesHashMap");
        if(hashMap == null){
            return null;
        }
        // HashMapから引数で指定されたキーを取得する
        return hashMap.get(key);
    }
}
「FlexClip」はテンプレートとして利用できる動画・画像・音楽などが充実した動画編集ツールだったテンプレートとして利用できるテキスト・動画・画像・音楽など(いずれも著作権フリー)が充実している動画編集ツールの一つに、「FlexCli...

反対に、ユーティリティクラスの悪い例は以下の通りで、コメントに記載されているように、メソッドがstaticでなく、不必要なインスタンス変数やチェック処理・例外のスローが行われている。

package com.example.demo;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.List;

// セッションから指定したキー値に対応するリストを取得するユーティリティクラス(NG例)
public class DemoUtilNg {

    /*
       ★NG(1): 不必要なインスタンス変数
       ローカル変数にできる変数はローカル変数にした方がよい
     */
    private ServletRequestAttributes requestAttributes = null;
    private HttpSession session = null;
    private HashMap<String, List<String>> hashMap = null;

    /**
     * セッションから指定したキー値に対応するリストを取得する
     * @param key キー値
     * @return 指定したキー値に対応するリスト
     * @throws IllegalStateException 不適切な状態でのメソッド呼出例外
     * @throws IllegalArgumentException 不適切な引数指定例外
     */
    /*
      ★NG(2): staticメソッドにしていない
      staticメソッドでないと、呼出側でこのクラスをnewしてからこのメソッドを呼び出す必要がある
    */
    public List<String> getHashList(String key)
            throws IllegalStateException, IllegalArgumentException{
        /*
          ★NG(3): nullを返せば済むところをわざわざ例外をスローしている
          例外を発生させると、呼出側で例外処理を行わなければならず手間がかかる
         */
        // リクエストオブジェクトがnullの場合は、IllegalStateException例外をスローする
        requestAttributes 
             = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(requestAttributes == null){
            throw new IllegalStateException("リクエストオブジェクトが取得できませんでした");
        }
        // セッションがnullの場合は、IllegalStateException例外をスローする
        session = requestAttributes.getRequest().getSession(false);
        if(session == null){
            throw new IllegalStateException("セッションが取得できませんでした");
        }
        // セッションからHashMapが取得できない場合は、IllegalStateException例外をスローする
        hashMap = (HashMap<String, List<String>>)session.getAttribute("sesHashMap");
        if(hashMap == null){
            throw new IllegalStateException("セッションからHashMapが取得できませんでした");
        }
        /*
           ★NG(4): 不必要な引数のチェック処理
           hashMap.get(null)やhashMap.get("")としてもエラーにならずnullを返却するだけなので、
           例外処理はしない方が処理を簡略にできる
         */
        // 引数がnullまたは空の場合は、IllegalArgumentException例外をスローする
        if(StringUtils.isEmpty(key)){
            throw new IllegalArgumentException("引数がnullまたは空です");
        }
        // HashMapから引数で指定されたキーを取得する
        return hashMap.get(key);
    }
}



また、ユーティリティクラスを呼び出しているコントローラクラスの内容は以下の通りで、ユーティリティクラスの悪い例を呼び出す処理はコメントアウトして記載している。

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@Controller
public class DemoController {

    /**
     * ハッシュマップをセッションに追加し、初期表示画面に遷移
     * @param request HttpServletRequestオブジェクト
     * @return 初期表示画面へのパス
     */
    @GetMapping("/")
    public String index(HttpServletRequest request){
        // セッションを生成
        HttpSession session = request.getSession(true);

        // ハッシュマップを生成し、セッションに追加
        HashMap<String, List<String>> hashMap = createHashMap();
        session.setAttribute("sesHashMap", hashMap);

        return "index";
    }

    /**
     * セッションから指定したキー値に対応するリストを取得し、次画面に遷移
     * @param model Modelオブジェクト
     * @param request HttpServletRequestオブジェクト
     * @return 次画面へのパス
     */
    @PostMapping("/next")
    public String next(Model model, HttpServletRequest request){
        // セッションから指定したキー値に対応するリストを取得し、sessionListに設定
        List<String> hashList = DemoUtil.getHashList("key1");
        model.addAttribute("sessionList", hashList);

        // セッションから指定したキー値に対応するリストを取得し、sessionListに設定(NG例呼出)
        //try{
        //    List<String> hashList = new DemoUtilNg().getHashList("key1");
        //    model.addAttribute("sessionList", hashList);
        //}catch(Exception ex){
        //    model.addAttribute("sessionList", null);
        //}

        // セッションの値を破棄し、次画面に遷移
        request.getSession(false).invalidate();
        return "next";
    }

    /**
     * セッションに設定するハッシュマップを生成する
     * @return 生成したハッシュマップ
     */
    private HashMap<String, List<String>> createHashMap(){
        HashMap<String, List<String>> hashMap = new HashMap<>();

        List<String> hashList1 = new ArrayList<>();
        hashList1.add("item1");
        hashList1.add("item2");
        hashList1.add("item3");
        hashMap.put("key1", hashList1);

        List<String> hashList2 = new ArrayList<>();
        hashList2.add("item4");
        hashList2.add("item5");
        hashMap.put("key2", hashList2);

        return hashMap;
    }
}
サラリーマン型フリーランスSEという働き方でお金の不安を解消しよう先日、「サラリーマン型フリーランスSE」という働き方を紹介するYouTube動画を視聴しましたので、その内容をご紹介します。 「サ...

さらに、HTMLファイルの内容は以下の通り。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index page</title>
</head>
<body>
    下記ボタンを押下してください。<br/><br/>
    <form th:action="@{/next}" method="POST">
        <input type="submit" value="次へ" />
    </form>
</body>
</html>
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>next page</title>
</head>
<body>
    セッションから取得したリスト : <br/>

    <span th:if="${#lists.isEmpty(sessionList)}">
        <br/> リストは空です。
    </span>
    <ul>
        <li th:each="item : ${sessionList}" th:text="${item}"></li>
    </ul>
</body>
</html>

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-utility-ng/demo



サンプルプログラムの実行結果

今回作成したサンプルプログラムの実行結果は、以下の通り。

1) Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスすると、以下の画面が表示されるので、「次へ」ボタンを押下する。
サンプルプログラムの実行結果_1

2) 以下の画面に遷移することが確認できる。
サンプルプログラムの実行結果_2

要点まとめ

  • ごく単純なJavaクラスの実装であっても、実装方法はいくつか考えられるため、その中で「最も良い」実装方法を検討し選択する必要がある。
  • 設計・実装する際、不必要なインスタンス変数を使用したり、不必要な例外のスローを行ったりしないよう注意する。
  • 設計・実装する際、不要な処理(不要なチェック処理等)を行わないようにする。
  • 設計・実装する際、メソッドをstaticにできる場合は、なるべくstaticメソッドにする。