【エンジニア初心者用】読みやすいソースコードの作り方

[PONOS Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ponos)の19日目の記事です。
昨日は@sunagimo_1111さんの[iCloud・iTunesのバックアップを考慮したUnityのデータ保存における注意点](https://qiita.com/sunagimo_1111/items/b17362938d0447561717)でした。
#この記事について
この記事ではプログラミングを始めたばかりの方、エンジニア初心者向けの読みやすいソースコードの作り方について
簡単に説明させていただきます。

#読みにくいソースコードだと・・・

```c#

    [SerializeField] AudioClip se1;
    [SerializeField] AudioClip se2;
    [SerializeField] AudioClip se3;
    [SerializeField] AudioClip se4;
    [SerializeField] AudioClip se5;
    [SerializeField] Text score;
    [SerializeField] GameObject Barrier;
    [SerializeField] GameObject Wing;
    SpriteRenderer sprite;
    Rigidbody2D RigidBody;
    [SerializeField] AudioSource audioSource;
    [SerializeField] float JumpPower;
    [SerializeField] float SecondJumpPower;
    float BarrierTime = 0.0f;
    float WingTime = 0.0f;
    float MoveScorePoint;
    const float SeVolumeNum = 0.3f;
    [SerializeField] int MoveSpeed = default;
    int JumpNum = 0;
    int MoveScore;
    int NowMoveSpeed = 0;
    bool isDead;
    bool isDamege;
    bool isPause = false;
    bool isWing = false;
    Color BarrierNowColor = new Color(1.0f, 1.0f, 1.0f, 0.25f);
    Color BarrierNoneColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);
    void Start()
    {
        NowMoveSpeed = MoveSpeed;
        isDamege = false;
        isDead = false;
        RigidBody = GetComponent<Rigidbody2D>();
        sprite = GetComponent<SpriteRenderer>();
        score.text = MoveScore + "m";
    }
    void Update()
    {
        if (GameDataNum.UserBarrierOn)
        {
            Barrier.SetActive(true);
        }
        else
        {
            Barrier.SetActive(false);
        }
        if (BarrierTime > 0)
        {
            sprite.color = BarrierNowColor;
            BarrierTime -= 1 * Time.deltaTime;
        }
        else
        {
            BarrierTime = 0;
            sprite.color = BarrierNoneColor;
        }
        if (isDead)
        {
            MoveSpeed = -1;
            transform.Rotate(new Vector3(0, 0, -2.5f));
            score.text = " ";
            GameDataNum.UserBarrierOn = false;
            transform.position = new Vector3(transform.position.x + MoveSpeed * Time.deltaTime, transform.position.y, transform.position.z);
            return;
        }
        if (isWing)
        {
            transform.position = new Vector3(transform.position.x + MoveSpeed * Time.deltaTime, 5.50f, transform.position.z);
            if (WingTime > 0)
            {
                WingTime -= 1 * Time.deltaTime;
                Wing.SetActive(true);
            }
            else
            {
                Wing.SetActive(false);
                JumpNum = 1;
                RigidBody.velocity = new Vector3(0.0f, 0.1f, 0.0f);
                WingTime = 0;
                isWing = false;
            }
        }
        else
        {
            if (isDamege)
            {
                if (GameDataNum.UserUserRevivalOn)
                {
                    audioSource.PlayOneShot(se3);
                    transform.position = new Vector3(-19.4f, 10.0f, transform.position.z);
                    RigidBody.velocity = new Vector3(0.0f, 1.0f, 0.0f);
                    isDamege = false;
                    transform.rotation = new Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
                    BarrierTime = 3.0f;
                    MoveSpeed = NowMoveSpeed;
                    GameDataNum.UserUserRevivalOn = false;
                }
                else
                {
                    audioSource.PlayOneShot(se3);
                    isDead = true;
                }
            }
            if (transform.position.y < -5.0f)
            {
                audioSource.volume = 1.0f;
                isDamege = true;
            }
            transform.position = new Vector3(transform.position.x + MoveSpeed * Time.deltaTime, transform.position.y, transform.position.z);
        }
        if (MoveSpeed > 0)
        {
            MoveScorePoint += GameDataNum.StageMoveSpeed * Time.deltaTime;
            if (MoveScorePoint > 2.0f)
            {
                MoveScorePoint = 0.0f;
                MoveScore++;
            }
        }
        score.text = MoveScore + "m";
    }
    public void SetIsPause(bool flag)
    {
        isPause = flag;
    }
```
これは私がエンジニアになって日が浅いときに書いたソースコードです。
このソースコード、とても読みにくいですね。

読みやすいソースコードの利点、それは・・・
#どんな処理をしているかがすぐに理解できる。
１週間、１ヶ月ぶりにソースコードを開いたときどんな処理をしていたか覚えていますか？

もし忘れていた場合、自分のコードの理解するのに時間を使ってしまいます。
個人での開発ならまだしも、チームでの開発ならばチームの誰かがの自分がコードを見ることがあります。
読みにくいソースコードだと理解するのに時間がかかってしまい、その時間が非常にもったいないです。


そうらないためにも読みやすいソースコードはとても重要です。
今回はこのコードを綺麗にしたいと思います。

##1: コメント文を書く
```c#
            //もし、ダメージを受けたら
            if (isDamege)
            {
                //もし、復活アイテムを所持していたなら
                if (GameDataNum.UserUserRevivalOn)
                {
                    audioSource.PlayOneShot(se3);
                    //プレイヤーの位置を真上に変更し、velocityをリセット
                    transform.position = new Vector3(-19.4f, 10.0f, transform.position.z);
                    RigidBody.velocity = new Vector3(0.0f, 1.0f, 0.0f);
                    //ダメージフラグをFalse
                    isDamege = false;
                    transform.rotation = new Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
                    BarrierTime = 3.0f;
                    MoveSpeed = NowMoveSpeed;
                    //復活アイテムの消費
                    GameDataNum.UserUserRevivalOn = false;
                }
                else
                {
                    audioSource.PlayOneShot(se3);
                    isDead = true;
                }
            }
            //一定の高さまでプレイヤーが落下したら
            if (transform.position.y < -5.0f)
            {
                audioSource.volume = 1.0f;
                isDamege = true;
            }
```
処理の横に軽く添えるだけで理解しやすさは大きく上昇します。
###やりすぎはよくない...
```c#
            //もし、ダメージを受けたら
            //プレイヤーが無敵時間じゃない場合、トラップに当たるとダメージを受けてしまいます。
            if (isDamege)
            {
                //もし、復活アイテムを所持していたなら
                //アイテムを消費させプレイヤーの位置はステージの真上から
                //無敵時間を用意したあげています
                //角度も０にしています
                if (GameDataNum.UserUserRevivalOn)
                {
                    //音を鳴らしてます。なる音については「キュイン」です
                    audioSource.PlayOneShot(se3);
                    //プレイヤーの位置を真上に変更し、velocityをリセット
                    transform.position = new Vector3(-19.4f, 10.0f, transform.position.z);
                    //なぜここで1.0fなのかというと落下速度が大変なことになってしまいますので
                    //落下速度をリセットするために
                    RigidBody.velocity = new Vector3(0.0f, 1.0f, 0.0f);
                    //ダメージフラグをFalse
                    isDamege = false;
                    //↑　これでダメージ判定がまた取れます
                    //角度をリセットしてます。
                    transform.rotation = new Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
                    //バリア有効時間は３秒です
                    //Update内で減らして、０になるとバリアが消えます
                    BarrierTime = 3.0f;
                    MoveSpeed = NowMoveSpeed;
                    //復活アイテムの消費
                    //この復活アイテムはショップで購入できます。
                    GameDataNum.UserUserRevivalOn = false;
                }
                else
                {
                    audioSource.PlayOneShot(se3);
                    isDead = true;
                }
            }
            //一定の高さまでプレイヤーが落下したら
            //画面下の一定の高さより下に行くとプレイヤーは死にます。
            if (transform.position.y < -5.0f)
            {
                //音量を１にしています。
                audioSource.volume = 1.0f;
                isDamege = true;
            }
```
##2: 関数化を使う
void Update（）にたくさん書いてしまうと読みにくいです。
処理に部類わけし、関数を作っておくと見たい処理がわかりやすくなります。

```c#
void Update()
{
        MoveSpeed = -1;
        transform.Rotate(new Vector3(0, 0, -2.5f));
        score.text = " ";
        GameDataNum.UserBarrierOn = false;
        transform.position = new Vector3(transform.position.x + MoveSpeed * Time.deltaTime, transform.position.y, transform.position.z);
        return;

etc...
}
```

```c#
//これをUpdateにDead()こう書くだけ
    void Dead()
    {
        MoveSpeed = -1;
        transform.Rotate(new Vector3(0, 0, -2.5f));
        score.text = " ";
        GameDataNum.UserBarrierOn = false;
        transform.position = new Vector3(transform.position.x + MoveSpeed * Time.deltaTime, transform.position.y, transform.position.z);
        return;
    }
```
##3: 変数の名前もわかりやすく
```c#

    [SerializeField] AudioClip se1;
    [SerializeField] AudioClip se2;
    [SerializeField] AudioClip se3;
    [SerializeField] AudioClip se4;
    [SerializeField] AudioClip se5;

//修正
    [SerializeField] AudioClip seJump;
    [SerializeField] AudioClip seCoin;
    [SerializeField] AudioClip seDead;
    [SerializeField] AudioClip seBarrier;
    [SerializeField] AudioClip seWing;
```

変数は名前を見ただけでどんな役割の変数なのかもわかるように命名しておきましょう。
##4: マジックナンバーを減らす
transform.position = new Vector3(-19.4f, 10.0f, transform.position.z);
「-19.4fってなんですか？」
if (transform.position.y < -5.0f)
「これってなんの数字なの？」

こういったマジックナンバーは一目見ただけでは意味が理解しにくく、値を変えたいときも不便です。

Vector3　restartPosition =new Vector3(-19.4f, 10.0f, transform.position.z);
や
playerDeadPositionYだったり　stageHeight
など後から編集しやすい書き方がいいでしょう。
また、変数にすることによって同じ数字を使いたいときに同じ変数を書くだけで済むので、構築も楽です。

##5: 改めて見返す
「あの時は勢いで書いたけど・・・」
「この処理短くできないかな？」
落ち着いて考えて見返すことで、無駄な処理を少しでも減らし見やすくなります。

#まとめ
ソースコードを綺麗にすることは自分自身の成長に大きく繋がります。
特にチーム開発ではいろんな人が自分のコードを見ることになると思いますので
見やすくするのは必須と言ってもいいでしょう。
就職活動中の学生ならばソースコードを綺麗にするだけで評価は大きく上昇すると思います。

#最後に
今回は昔、自分が書いたソースコードを見て、記事にしようと思いました。
昔に作ったソースコードなんて見る機会はほとんどなかったため、見たときはあまりの汚さに
思わず笑ってしまいました。

自分はまだまだ未熟ではありますが、エンジニアとして今後もっとより良いソースコードを作っていけたらなと思います。

***
明日は @udonLovesさんの記事です！