【Unity】コード保守性の向上術:保守性が低いコードと高いコードの違い(オブジェクト指向の側面)

オブジェクト指向プログラミング(OOP)において、保守性の高いコードと低いコードの違いを示します
様々なサンプルについて、次のリンクも参考にしてください

サンプル(プレイヤーがジャンプする動作)

簡単なUnityの例を使って説明します。ここでは、プレイヤーがジャンプする動作を実装する例を用います。

保守性の低いコード

以下のコードは保守性が低い例です。このコードはプレイヤーのジャンプロジックを直接スクリプトに埋め込んでおり、再利用性や拡張性が低いです。

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float jumpForce = 10f;
    private Rigidbody2D rb;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            rb.velocity = Vector2.up * jumpForce;
        }
    }
}

保守性の高いコード

保守性の高いコードは、OOPの原則に従い、責任を分けて管理しやすくしています。以下の例では、プレイヤーのジャンプロジックを専用のPlayerJumpクラスに分離しています。

using UnityEngine;

// プレイヤーのジャンプロジックを分離したクラス
public class PlayerJump : MonoBehaviour
{
    public float jumpForce = 10f;
    private Rigidbody2D rb;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    public void Jump()
    {
        rb.velocity = Vector2.up * jumpForce;
    }
}

// プレイヤー全体のコントローラー
public class PlayerController : MonoBehaviour
{
    private PlayerJump playerJump;

    void Start()
    {
        playerJump = GetComponent<PlayerJump>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            playerJump.Jump();
        }
    }
}

違いのポイント

1. 単一責任の原則(Single Responsibility Principle, SRP): 保守性の高いコードでは、PlayerJumpクラスがジャンプに関連するロジックを専用に扱います。これにより、コードの再利用性とテストのしやすさが向上します。

2. コードの再利用: ジャンプロジックが他のスクリプトで必要な場合、PlayerJumpクラスを再利用するだけで済みます。保守性の低いコードでは、同じロジックを複数の場所に複製する可能性があります。

3. 拡張性: ジャンプロジックを拡張する場合、例えば、二段ジャンプや異なるジャンプ力を持つ場合も、PlayerJumpクラスを変更するだけで済みます。他の部分に影響を与えずに機能を追加できます。

このように、OOPの原則に従うことで、コードの保守性を高めることができます。特にUnityのような大規模なゲーム開発においては、このような設計が重要です。

サンプル(敵キャラクターの移動ロジック)

今回は、敵キャラクターの移動ロジックを使って説明します。

保守性の低いコード

以下のコードは、敵キャラクターの移動ロジックを直接スクリプトに埋め込んでいます。これにより、ロジックの再利用性や拡張性が低くなります。

using UnityEngine;

public class EnemyController : MonoBehaviour
{
    public float speed = 3f;
    private Transform target;

    void Start()
    {
        target = GameObject.FindGameObjectWithTag("Player").transform;
    }

    void Update()
    {
        Vector3 direction = (target.position - transform.position).normalized;
        transform.position += direction * speed * Time.deltaTime;
    }
}

保守性の高いコード

保守性の高いコードでは、敵キャラクターの移動ロジックを専用のEnemyMovementクラスに分離し、責任を分割しています。

using UnityEngine;

// 敵キャラクターの移動ロジックを分離したクラス
public class EnemyMovement : MonoBehaviour
{
    public float speed = 3f;
    private Transform target;

    void Start()
    {
        target = GameObject.FindGameObjectWithTag("Player").transform;
    }

    public void MoveTowardsTarget()
    {
        if (target != null)
        {
            Vector3 direction = (target.position - transform.position).normalized;
            transform.position += direction * speed * Time.deltaTime;
        }
    }
}

// 敵キャラクター全体のコントローラー
public class EnemyController : MonoBehaviour
{
    private EnemyMovement enemyMovement;

    void Start()
    {
        enemyMovement = GetComponent<EnemyMovement>();
    }

    void Update()
    {
        enemyMovement.MoveTowardsTarget();
    }
}

違いのポイント

1. 依存性の分離(Dependency Separation): 保守性の高いコードでは、移動ロジックがEnemyMovementクラスに分離されているため、他の機能と独立しています。これにより、コードの変更が他の部分に影響を与えるリスクが減ります。

2. 再利用性: 移動ロジックを再利用する場合、他の敵キャラクターにEnemyMovementクラスを適用するだけで済みます。保守性の低いコードでは、同じロジックを複数の場所に複製する必要があります。

3. テストの容易さ: EnemyMovementクラスを個別にテストすることができます。これにより、移動ロジックのテストが簡単になり、バグを発見しやすくなります。

4. 拡張性: 敵キャラクターの移動ロジックを拡張する場合、例えば特定の条件下で異なる動きをさせたい場合でも、EnemyMovementクラスを変更するだけで済みます。他の部分に影響を与えずに機能を追加できます。

サンプル(スコア管理のコード)

もう一つの例として、スコア管理のコードを示します。

保守性の低いコード

以下のコードは、スコア管理ロジックをゲーム全体のスクリプトに直接埋め込んでいます。

using UnityEngine;
using UnityEngine.UI;

public class GameController : MonoBehaviour
{
    public Text scoreText;
    private int score = 0;

    void Start()
    {
        UpdateScoreText();
    }

    void UpdateScoreText()
    {
        scoreText.text = "Score: " + score;
    }

    public void AddScore(int points)
    {
        score += points;
        UpdateScoreText();
    }
}

保守性の高いコード

保守性の高いコードでは、スコア管理ロジックを専用のScoreManagerクラスに分離し、責任を分割しています。

using UnityEngine;
using UnityEngine.UI;

// スコア管理のロジックを分離したクラス
public class ScoreManager : MonoBehaviour
{
    public Text scoreText;
    private int score = 0;

    void Start()
    {
        UpdateScoreText();
    }

    public void AddScore(int points)
    {
        score += points;
        UpdateScoreText();
    }

    private void UpdateScoreText()
    {
        scoreText.text = "Score: " + score;
    }
}

// ゲーム全体のコントローラー
public class GameController : MonoBehaviour
{
    private ScoreManager scoreManager;

    void Start()
    {
        scoreManager = GetComponent<ScoreManager>();
    }

    public void PlayerScored(int points)
    {
        scoreManager.AddScore(points);
    }
}

違いのポイント

1. 責任の分離(Responsibility Separation): スコア管理ロジックがScoreManagerクラスに分離されているため、ゲーム全体のロジックとは独立しています。

2. 再利用性とモジュール化: スコア管理ロジックを他のプロジェクトやシーンで再利用することが簡単になります。ScoreManagerクラスをそのまま移植するだけでスコア管理機能が利用できます。

3. 変更の容易さ: スコア表示方法や計算ロジックを変更する場合、ScoreManagerクラスだけを修正すれば済みます。他の部分に影響を与えずに変更が可能です。

4. テストとデバッグの効率化: ScoreManagerクラスを個別にテストできるため、バグの発見と修正がしやすくなります。

これらの例を通じて、OOPの原則に従った保守性の高いコードとそうでないコードの違いを理解していただけたかと思います。保守性の高いコードを書くことで、将来的なメンテナンスや拡張が容易になり、プロジェクト全体の品質が向上します。

サンプル(インベントリシステム)

次は、インベントリシステムを例に使います。

保守性の低いコード

以下のコードは、インベントリの管理ロジックを一つのスクリプトに直接埋め込んでいます。この方法では、コードが複雑になりやすく、再利用や変更が困難です。

using System.Collections.Generic;
using UnityEngine;

public class PlayerInventory : MonoBehaviour
{
    private List<string> inventory = new List<string>();

    void Start()
    {
        AddItem("Sword");
        AddItem("Shield");
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.I))
        {
            PrintInventory();
        }
    }

    public void AddItem(string item)
    {
        inventory.Add(item);
    }

    public void RemoveItem(string item)
    {
        inventory.Remove(item);
    }

    void PrintInventory()
    {
        foreach (var item in inventory)
        {
            Debug.Log(item);
        }
    }
}

保守性の高いコード

保守性の高いコードでは、インベントリ管理ロジックを専用のクラスに分離し、他の機能から独立させます。

using System.Collections.Generic;
using UnityEngine;

// インベントリの管理ロジックを分離したクラス
public class Inventory : MonoBehaviour
{
    private List<string> items = new List<string>();

    public void AddItem(string item)
    {
        items.Add(item);
    }

    public void RemoveItem(string item)
    {
        items.Remove(item);
    }

    public void PrintInventory()
    {
        foreach (var item in items)
        {
            Debug.Log(item);
        }
    }

    public List<string> GetItems()
    {
        return new List<string>(items);
    }
}

// プレイヤーのインベントリ管理を行うクラス
public class PlayerInventory : MonoBehaviour
{
    private Inventory inventory;

    void Start()
    {
        inventory = GetComponent<Inventory>();
        inventory.AddItem("Sword");
        inventory.AddItem("Shield");
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.I))
        {
            inventory.PrintInventory();
        }
    }
}

違いのポイント

1. 責任の分割: 保守性の高いコードでは、インベントリ管理ロジックがInventoryクラスに分離されています。このクラスがインベントリに関する全ての操作を担当し、他のクラスから独立しています。

2. 再利用性: Inventoryクラスを他のプロジェクトやキャラクターで再利用することが容易です。新たなプロジェクトにInventoryクラスを導入するだけで同じ機能を持つインベントリシステムを構築できます。

3. 変更の容易さ: インベントリの表示方法やアイテムの追加方法を変更する場合でも、Inventoryクラス内の変更だけで済みます。他の部分に影響を与えずに機能を変更できます。

4. テストの容易さ: Inventoryクラスを個別にテストすることができます。これにより、バグを見つけやすく、修正も簡単です。

サンプル(キャラクターのステータス管理)

次は、キャラクターのステータス管理のコードを示します。

保守性の低いコード

以下のコードは、キャラクターのステータス管理ロジックを一つのスクリプトに直接埋め込んでいます。

using UnityEngine;

public class Character : MonoBehaviour
{
    public int health = 100;
    public int attackPower = 20;

    void Start()
    {
        TakeDamage(10);
        Heal(5);
    }

    public void TakeDamage(int damage)
    {
        health -= damage;
        if (health <= 0)
        {
            Die();
        }
    }

    public void Heal(int amount)
    {
        health += amount;
    }

    void Die()
    {
        Debug.Log("Character is dead.");
        // Additional death logic here
    }
}

保守性の高いコード

保守性の高いコードでは、キャラクターのステータス管理ロジックを専用のクラスに分離します。

using UnityEngine;

// キャラクターのステータス管理ロジックを分離したクラス
public class CharacterStats : MonoBehaviour
{
    public int health = 100;
    public int attackPower = 20;

    public void TakeDamage(int damage)
    {
        health -= damage;
        if (health <= 0)
        {
            Die();
        }
    }

    public void Heal(int amount)
    {
        health += amount;
    }

    void Die()
    {
        Debug.Log("Character is dead.");
        // Additional death logic here
    }
}

// キャラクターの全体的な動作を管理するクラス
public class Character : MonoBehaviour
{
    private CharacterStats stats;

    void Start()
    {
        stats = GetComponent<CharacterStats>();
        stats.TakeDamage(10);
        stats.Heal(5);
    }
}

違いのポイント

1. 単一責任の原則: 保守性の高いコードでは、ステータス管理がCharacterStatsクラスに分離され、他の機能とは独立しています。

2. 再利用性: ステータス管理ロジックを他のキャラクターやプロジェクトで再利用することが容易です。

3. 変更の容易さ: ステータス管理のロジックを変更する場合、CharacterStatsクラスだけを修正すれば済みます。他の部分に影響を与えずに変更できます。

4. テストとデバッグの効率化: CharacterStatsクラスを個別にテストできるため、バグの発見と修正がしやすくなります。

これらの例を通じて、OOPの原則に従った保守性の高いコードとそうでないコードの違いをさらに理解していただけたかと思います。保守性の高いコードを書くことで、将来的なメンテナンスや拡張が容易になり、プロジェクト全体の品質が向上します。

サンプル(ゲームの音楽管理システム)

次は、ゲームの音楽管理システムを例に使います。

保守性の低いコード

以下のコードは、ゲーム全体の音楽管理ロジックを一つのスクリプトに直接埋め込んでいます。この方法では、コードが複雑になりやすく、再利用や変更が困難です。

using UnityEngine;

public class GameController : MonoBehaviour
{
    public AudioSource audioSource;
    public AudioClip backgroundMusic;

    void Start()
    {
        PlayMusic();
    }

    void PlayMusic()
    {
        audioSource.clip = backgroundMusic;
        audioSource.Play();
    }

    void StopMusic()
    {
        audioSource.Stop();
    }
}

保守性の高いコード

保守性の高いコードでは、音楽管理ロジックを専用のクラスに分離し、他の機能から独立させます。

using UnityEngine;

// 音楽管理ロジックを分離したクラス
public class MusicManager : MonoBehaviour
{
    public AudioSource audioSource;
    public AudioClip backgroundMusic;

    void Start()
    {
        PlayMusic();
    }

    public void PlayMusic()
    {
        audioSource.clip = backgroundMusic;
        audioSource.Play();
    }

    public void StopMusic()
    {
        audioSource.Stop();
    }
}

// ゲーム全体のコントローラー
public class GameController : MonoBehaviour
{
    private MusicManager musicManager;

    void Start()
    {
        musicManager = GetComponent<MusicManager>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.M))
        {
            musicManager.StopMusic();
        }

        if (Input.GetKeyDown(KeyCode.P))
        {
            musicManager.PlayMusic();
        }
    }
}

違いのポイント

1. 責任の分離(Responsibility Separation): 保守性の高いコードでは、音楽管理ロジックがMusicManagerクラスに分離されています。このクラスが音楽に関する全ての操作を担当し、他のクラスから独立しています。

2. 再利用性: MusicManagerクラスを他のプロジェクトやシーンで再利用することが容易です。新たなプロジェクトにMusicManagerクラスを導入するだけで同じ機能を持つ音楽管理システムを構築できます。

3. 変更の容易さ: 音楽の再生方法や停止方法を変更する場合でも、MusicManagerクラス内の変更だけで済みます。他の部分に影響を与えずに機能を変更できます。

4. テストの容易さ: MusicManagerクラスを個別にテストすることができます。これにより、バグを見つけやすく、修正も簡単です。

サンプル(UI管理のコード)

もう一つの例として、UI管理のコードを示します。

保守性の低いコード

以下のコードは、ゲームのUI管理ロジックを一つのスクリプトに直接埋め込んでいます。

using UnityEngine;
using UnityEngine.UI;

public class GameController : MonoBehaviour
{
    public Text scoreText;
    public GameObject gameOverPanel;

    private int score = 0;

    void Start()
    {
        UpdateScoreText();
        gameOverPanel.SetActive(false);
    }

    void UpdateScoreText()
    {
        scoreText.text = "Score: " + score;
    }

    public void AddScore(int points)
    {
        score += points;
        UpdateScoreText();
    }

    public void GameOver()
    {
        gameOverPanel.SetActive(true);
    }
}

保守性の高いコード

保守性の高いコードでは、UI管理ロジックを専用のクラスに分離し、他の機能から独立させます。

using UnityEngine;
using UnityEngine.UI;

// UI管理ロジックを分離したクラス
public class UIManager : MonoBehaviour
{
    public Text scoreText;
    public GameObject gameOverPanel;

    private int score = 0;

    void Start()
    {
        UpdateScoreText();
        gameOverPanel.SetActive(false);
    }

    public void UpdateScoreText()
    {
        scoreText.text = "Score: " + score;
    }

    public void AddScore(int points)
    {
        score += points;
        UpdateScoreText();
    }

    public void ShowGameOver()
    {
        gameOverPanel.SetActive(true);
    }
}

// ゲーム全体のコントローラー
public class GameController : MonoBehaviour
{
    private UIManager uiManager;

    void Start()
    {
        uiManager = GetComponent<UIManager>();
        uiManager.AddScore(10);
    }

    public void GameOver()
    {
        uiManager.ShowGameOver();
    }
}

違いのポイント

1. 責任の分離(Responsibility Separation): 保守性の高いコードでは、音楽管理ロジックがMusicManagerクラスに分離されています。このクラスが音楽に関する全ての操作を担当し、他のクラスから独立しています。

2. 再利用性: MusicManagerクラスを他のプロジェクトやシーンで再利用することが容易です。新たなプロジェクトにMusicManagerクラスを導入するだけで同じ機能を持つ音楽管理システムを構築できます。

3. 変更の容易さ: 音楽の再生方法や停止方法を変更する場合でも、MusicManagerクラス内の変更だけで済みます。他の部分に影響を与えずに機能を変更できます。

4. テストの容易さ: MusicManagerクラスを個別にテストすることができます。これにより、バグを見つけやすく、修正も簡単です。

サンプル(スコア管理とセーブ/ロードシステム)

もう一つの例として、スコア管理とセーブ/ロードシステムを例に使います

保守性の低いコード

以下のコードは、スコア管理とセーブ/ロードのロジックを一つのスクリプトに直接埋め込んでいます。この方法では、コードが複雑になりやすく、再利用や変更が困難です。

using UnityEngine;

public class GameController : MonoBehaviour
{
    public int score = 0;

    void Start()
    {
        LoadGame();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.S))
        {
            SaveGame();
        }
    }

    public void AddScore(int points)
    {
        score += points;
    }

    void SaveGame()
    {
        PlayerPrefs.SetInt("score", score);
        PlayerPrefs.Save();
    }

    void LoadGame()
    {
        if (PlayerPrefs.HasKey("score"))
        {
            score = PlayerPrefs.GetInt("score");
        }
    }
}

保守性の高いコード

保守性の高いコードでは、スコア管理とセーブ/ロードのロジックをそれぞれ専用のクラスに分離し、他の機能から独立させます。

using UnityEngine;

// スコア管理ロジックを分離したクラス
public class ScoreManager : MonoBehaviour
{
    public int score { get; private set; } = 0;

    public void AddScore(int points)
    {
        score += points;
    }

    public void SetScore(int newScore)
    {
        score = newScore;
    }
}

// セーブ/ロード管理ロジックを分離したクラス
public class SaveLoadManager : MonoBehaviour
{
    public void SaveGame(int score)
    {
        PlayerPrefs.SetInt("score", score);
        PlayerPrefs.Save();
    }

    public int LoadGame()
    {
        if (PlayerPrefs.HasKey("score"))
        {
            return PlayerPrefs.GetInt("score");
        }
        return 0;
    }
}

// ゲーム全体のコントローラー
public class GameController : MonoBehaviour
{
    private ScoreManager scoreManager;
    private SaveLoadManager saveLoadManager;

    void Start()
    {
        scoreManager = GetComponent<ScoreManager>();
        saveLoadManager = GetComponent<SaveLoadManager>();

        int loadedScore = saveLoadManager.LoadGame();
        scoreManager.SetScore(loadedScore);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.S))
        {
            saveLoadManager.SaveGame(scoreManager.score);
        }
    }

    public void AddScore(int points)
    {
        scoreManager.AddScore(points);
    }
}

違いのポイント

1. 責任の分離(Responsibility Separation): 保守性の高いコードでは、スコア管理ロジックがScoreManagerクラスに、セーブ/ロード管理ロジックがSaveLoadManagerクラスに分離されています。これにより、各クラスが独立して責任を持つようになります。

2. 再利用性: ScoreManagerクラスとSaveLoadManagerクラスは他のプロジェクトやシーンで再利用することが容易です。必要な機能を新しいプロジェクトに追加するだけで済みます。

3. 変更の容易さ: スコア管理やセーブ/ロードのロジックを変更する場合、関連するクラス内の変更だけで済みます。他の部分に影響を与えずに機能を変更できます。

4. テストの容易さ: 各クラスを個別にテストすることができます。これにより、バグの発見と修正がしやすくなります。

サンプル(ゲーム内のイベントシステム)

もう一つの例として、ゲーム内のイベントシステムを示します。

保守性の低いコード

以下のコードは、ゲーム内のイベントシステムを直接スクリプトに埋め込んでいます。

using UnityEngine;

public class GameController : MonoBehaviour
{
    void Start()
    {
        EventManager.OnPlayerScored += HandlePlayerScored;
    }

    void HandlePlayerScored(int points)
    {
        Debug.Log("Player scored: " + points);
    }

    void OnDestroy()
    {
        EventManager.OnPlayerScored -= HandlePlayerScored;
    }
}

public static class EventManager
{
    public delegate void PlayerScored(int points);
    public static event PlayerScored OnPlayerScored;

    public static void PlayerScoredEvent(int points)
    {
        OnPlayerScored?.Invoke(points);
    }
}

保守性の高いコード

保守性の高いコードでは、イベントシステムを専用のクラスに分離し、他の機能から独立させます。

using UnityEngine;

// イベントマネージャークラスを分離
public static class EventManager
{
    public delegate void PlayerScored(int points);
    public static event PlayerScored OnPlayerScored;

    public static void PlayerScoredEvent(int points)
    {
        OnPlayerScored?.Invoke(points);
    }
}

// ゲーム全体のコントローラー
public class GameController : MonoBehaviour
{
    void Start()
    {
        EventManager.OnPlayerScored += HandlePlayerScored;
    }

    void HandlePlayerScored(int points)
    {
        Debug.Log("Player scored: " + points);
    }

    void OnDestroy()
    {
        EventManager.OnPlayerScored -= HandlePlayerScored;
    }
}

// 他のスクリプトがイベントをトリガーする例
public class Player : MonoBehaviour
{
    void ScorePoints(int points)
    {
        EventManager.PlayerScoredEvent(points);
    }
}

違いのポイント

1. 責任の分離(Responsibility Separation): 保守性の高いコードでは、イベントシステムがEventManagerクラスに分離され、他の機能とは独立しています。

2. 再利用性: EventManagerクラスは他のプロジェクトやシーンで再利用することが容易です。新しいイベントを追加するだけで機能を拡張できます。

3. 変更の容易さ: イベントシステムのロジックを変更する場合、EventManagerクラス内の変更だけで済みます。他の部分に影響を与えずに機能を変更できます。

4. テストの容易さ: EventManagerクラスを個別にテストすることができます。これにより、バグの発見と修正がしやすくなります。

これらの例を通じて、OOPの原則に従った保守性の高いコードとそうでないコードの違いをさらに理解していただけたかと思います。保守性の高いコードを書くことで、将来的なメンテナンスや拡張が容易になり、プロジェクト全体の品質が向上します。