Skip to content

5. 一つのメソッドに詰め込まない

一つのメソッドにあれもこれもと処理を詰め込んでしまうと、あっという間に何十行、何百行もの巨大メソッドが出来上がります。そうなるとコードの見通しは最悪になり、読み解くのは苦行になります。このようなメソッドを神メソッドと呼ぶことがあります。神の所業を人間が理解するのは困難です。

次のような言葉があります。

関数は1つのことを行うべきだ。それをうまくやるべきだ。それだけを行うべきだ。

(Functions should do one thing. They should do it well. They should do it only.)

Robert C. Martin Clean Code

これはSOLID原則のS、単一責任の原則のメソッド版です。一つのメソッドは一つのことだけを行うべきです。そうすれば、メソッド名だけで何をしているかが一目で分かり、テストや修正も容易になります。

csharp
// ❌ Bad: 一つのメソッドに全部詰め込んだ「神メソッド」
void Update()
{
    float h = Input.GetAxis("Horizontal");
    float v = Input.GetAxis("Vertical");
    Vector3 direction = new Vector3(h, 0, v).normalized;
    if (direction.magnitude > 0.1f)
    {
        transform.position += direction * moveSpeed * Time.deltaTime;
        transform.rotation = Quaternion.LookRotation(direction);
        animator.SetBool("IsRunning", true);
    }
    else
    {
        animator.SetBool("IsRunning", false);
    }

    if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
    {
        rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        isGrounded = false;
        animator.SetTrigger("Jump");
        audioSource.PlayOneShot(jumpSound);
    }

    if (Input.GetMouseButtonDown(0) && canAttack)
    {
        canAttack = false;
        animator.SetTrigger("Attack");
        audioSource.PlayOneShot(attackSound);
        Collider[] hits = Physics.OverlapSphere(attackPoint.position, attackRange);
        foreach (var hit in hits)
        {
            if (hit.TryGetComponent<IDamageable>(out var target))
            {
                target.TakeDamage(attackPower);
            }
        }
        Invoke("ResetAttack", attackCooldown);
    }

    if (Input.GetKeyDown(KeyCode.R) && potionCount > 0)
    {
        hitPoint = Mathf.Min(hitPoint + potionHealAmount, maxHitPoint);
        potionCount--;
        audioSource.PlayOneShot(healSound);
        Instantiate(healEffect, transform.position, Quaternion.identity);
    }
}
// → 読む気、失せません?

一つのメソッドになんでも詰め込むのではなく、処理のまとまりごとに別メソッドへ切り出しましょう。一メソッドあたり長くとも大体20~30行程度が良いでしょう(あくまで指標の一つ)。

csharp
// ✅ Good: 処理ごとにメソッドへ切り出す
void Update()
{
    Move();
    TryJump();
    TryAttack();
    TryUsePotion();
}

void Move()
{
    float h = Input.GetAxis("Horizontal");
    float v = Input.GetAxis("Vertical");
    Vector3 direction = new Vector3(h, 0, v).normalized;

    if (direction.magnitude <= 0.1f)
    {
        animator.SetBool("IsRunning", false);
        return;
    }

    transform.position += direction * moveSpeed * Time.deltaTime;
    transform.rotation = Quaternion.LookRotation(direction);
    animator.SetBool("IsRunning", true);
}

void TryJump()
{
    if (!Input.GetKeyDown(KeyCode.Space)) return;
    if (!isGrounded) return;

    rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
    isGrounded = false;
    animator.SetTrigger("Jump");
    audioSource.PlayOneShot(jumpSound);
}

void TryAttack()
{
    if (!Input.GetMouseButtonDown(0)) return;
    if (!canAttack) return;

    canAttack = false;
    animator.SetTrigger("Attack");
    audioSource.PlayOneShot(attackSound);
    DealDamageToNearbyEnemies();
    Invoke("ResetAttack", attackCooldown);
}

void DealDamageToNearbyEnemies()
{
    Collider[] hits = Physics.OverlapSphere(attackPoint.position, attackRange);
    foreach (var hit in hits)
    {
        if (hit.TryGetComponent<IDamageable>(out var target))
        {
            target.TakeDamage(attackPower);
        }
    }
}

void TryUsePotion()
{
    if (!Input.GetKeyDown(KeyCode.R)) return;
    if (potionCount <= 0) return;

    hitPoint = Mathf.Min(hitPoint + potionHealAmount, maxHitPoint);
    potionCount--;
    audioSource.PlayOneShot(healSound);
    Instantiate(healEffect, transform.position, Quaternion.identity);
}
// → Update()を読むだけで「何をしているか」が分かる!

切り出したメソッドに適切な名前をつければ、中身を読まなくても処理の全体像が分かるようになります。結果として、Update() が目次のような役割を果たし、詳しく知りたければ各メソッドに飛べばよいという美しい構造になります。

Unity設計講習会 資料公開ページ