MENU

より良いコードを書くための心得

目次

はじめに

今回は、コーディングしていく上で意識すべきことを備忘録として残しておこうと思います。

コーディングしていくのに大事なことは、テストコードでtry / error を繰り返し効率よくerrorを解決できるかということです。

errorが出たとき、どこが原因でどう解決すべきかがわかりやすいコードを書けば時間短縮になりますし、訂正箇所も少ない方が無駄な労力を割く必要もなくなります。

また、この考え方は現場でシステム設計をしていく上で大事な考え方にもなってきます。
大前提に、現場では動くものが作れることが一番であることは確かですが、より良いエンジニアを目指すにはそれだけでも駄目だと思います。

名著『リーダブルコード』もヒントに、読みやすい・理解しやすいコードの技法をまとめておこうと思います。

プログラマーのバイブル本
¥2,640 (2022/05/07 20:55時点 | Amazon調べ)
\楽天ポイント4倍セール!/
楽天市場

コーディングの心得

やってしまいがちだけど、避けるべきこと

意味不明な命名重複コード、多重ネスト

読みにくく良くないコード(悪しきコード)のことを私は3Kコードと呼ぶことにしました。

3K ( K : 汚い K : 改変しにくい K : 危険 ) なコードです。笑

意味不明な命名重複コード多重ネストはとにかく読みにくい悪いコードの原因になるものの例です。初心者の頃はロジックを組むのに精一杯であまり意識せずにやってしまいがちです。

意味不明な命名重複コードの例

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        (途中省略)
        int a = 0;  // ← 変数名が意味不明
        int b = 0;  // ← 変数名が意味不明
        int c = 0;  // ← 変数名が意味不明
        int d = 0;  // ← 変数名が意味不明
        int count = 0;
        
        int[][] array;
        array = new int[nums[0]][nums[1]];
        
        for( int i = 0; i < nums[0]; i++ ) {
            String line1 = sc.nextLine();
            String[] lines1 = line1.split(" ");
            int[] nums1 = Stream.of(lines1).mapToInt(Integer::parseInt).toArray();
            array[i] = nums1;
        }
        for ( int j = 0; j < nums[0]; j++ ) {
            a = array[j][0];
            int total = 0;
            for ( int k = 0; k < nums[1]; k++ ) {
                total += array[j][k];
            }
            b = total - a;
            int l = 1;
            while (a < b) {
                a += array[j][l];
                b -= array[j][l];
                l++;
            }
            if ( a == b ) {
                count ++;
            } 
        }
        if ( count == nums[0] ) {
            System.out.println("Yes");
            for ( int x = 0; x < nums[0]; x++ ) {  // ← 22行目と同じ処理を書いている
                c = array[x][0];
                int total = 0;
                for ( int y = 0; y < nums[1]; y++ ) {
                    total += array[x][y];
                }
                d = total - c;
                int z = 1;
                while (c < d) {
                    c += array[x][z];
                    d -= array[x][z];
                    z++;
                }
                for( int s = 0; s < z; s++ ) {
                    System.out.print("A");
                }
                for( int t = 0; t < nums[1] - z - 1; t++ ) {
                    System.out.print("B");
                }
                System.out.println("B");
            }
        } else {
            System.out.println("No");
        }
    }
}

多重ネストの例

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        (途中省略)
        int count = 0;
        for(int i = 0; i < num; i++) {
            String line1 = sc.nextLine();
            String[] lines = line1.split(" ");
            (途中省略)
            if(totalScore >= 350) {
                if(lines[0].equals("○")) {
                    if(sciencesScore >= 160) {  // 多重ネストで条件処理がわかりにくい
                        count++;
                    }
                } else if(lines[0].equals("☓")) {
                    if(humanitiesScore >= 160) {
                        count++;
                    }
                }
            }
        }
        System.out.println(count);
    }
}

上記は、実際に私が書いたことのあるコードです。
問題が解けることを第一優先に考えていた当時の私は何も意識せずに書いており悪いコードを量産していました。笑

初学者は、問題が解けることに重きを置くべきですが、徐々に慣れてきたらよりきれいなコードを書くために努力していく必要があると思います。

意識すべきこと

① 型を絞る
② ガード節を意識する
③ マジックナンバーを避ける
④ メソッド抽出をする

①型を絞る

Javaだと変数を定義するときは、型宣言をしなければならないので特に意識しなくてもよい気がしますが、

PHPを学び始めるとPHPは変数を定義するのに型を指定する必要がないので、その便利さがゆえに処理によってエラーは出てないのに、間違ったまま動いてしまうということがあります。間違ったままコードを書き続けると、後から間違いに気づいた時、いちいち処理を遡ってチェックし直す手間が発生してしまいます。

メソッドの先頭やループ処理などの型宣言をすることで、期待するデータ型以外のデータが入り込むのを防ぎエラーの原因を追いやすくすることができます。

$totalCounts = 0; // int型(0)であることを宣言し、foreach文の中に他のデータ型が入り込んでいないかチェックできる
foreach ($totalCounts as $count) {
    $totalCounts += $count;
}

②ガード節を意識する

ガード節とは??

ガード節対象外の処理を省くコードを関数、ロジック等の先頭にまとめる方法

何が良いかというと、ネストが浅くなり、例外処理と通常処理が分離されるので、何をするコードかがわかりやすくなることです。

実際に、上記の多重ネストの例を手直ししてみました。

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        (途中省略)
        int count = 0;
        for(int i = 0; i < num; i++) {
            String line1 = sc.nextLine();
            String[] lines = line1.split(" ");
            (途中省略)
            if(totalScore < 350) continue; // 一行短文かつネストがなくなり、読みやすい
            if(lines[0].equals("○") && sciencesScore < 160) continue; // 一行短文かつネストがなくなり、読みやすい
            if(lines[0].equals("☓") && humanitiesScore < 160) continue; // 一行短文かつネストがなくなり、読みやすい
                count++;
        }
        System.out.println(count);
    }
}

ポイントは、
if文の条件式を反転し、処理を外に出すこと
早期return, continue, breakで処理を止めること

③マジックナンバーを避ける

マジックナンバーとは??

マジックナンバー ロジック内に直接書いてある意図不明な数値のこと

マジックナンバーは実装者本人にしか意図を理解出来ないため、混乱を招く元になります。慣れないうちは、使ってしまいがちですが、読み手に自分の意図を汲み取りやすくするためにもマジックナンバーは控えましょう。

マジックナンバーを書かないようにするためには、
定数として定義すること もしくは 値オブジェクトを設計すること

数値に意味を持たせることでロジック内の文脈理解もスムーズになり、可読性が上がります。

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        (途中省略)
        int count = 0;
        for(int i = 0; i < num; i++) {
            String line1 = sc.nextLine();
            String[] lines = line1.split(" ");
            (途中省略)
            if(totalScore < 350) continue;  // 350が何の数値か分からない
            if(lines[0].equals("○") && sciencesScore < 160) continue;  // 160が何の数値か分からない
            if(lines[0].equals("☓") && humanitiesScore < 160) continue; 
                count++;
        }
        System.out.println(count);
    }
}

上記コードを手直ししてみましょう。すると、. . .

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        (途中省略)
        int count = 0;

        final int totalBorderScore = 350;  // 定数として定義
        final int sciencesBorderScore = 160;  // 定数として定義
        final int humanitiesBorderScore = 160;  // 定数として定義

        for(int i = 0; i < num; i++) {
            String line1 = sc.nextLine();
            String[] lines = line1.split(" ");
            (途中省略)
            if(totalScore < totalBorderScore) continue;  
            if(lines[0].equals("○") && sciencesScore < sciencesBorderScore) continue;
            if(lines[0].equals("☓") && humanitiesScore < humanitiesBorderScore) continue; 
                count++;
        }
        System.out.println(count);
    }
}

明確に意図が伝わる数値として読みやすくなりました。

④メソッド抽出

メソッド抽出とは??

メソッド抽出 ロジックとデータをメソッドを組んで独立させること

メソッド抽出することで、コードがシンプルになり読みやすく、変更する時の他への影響も少なくすることができます。

また、重複コードをなくし、意味のまとまりがはっきりしたコードを書くことができます。

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        (途中省略)
        int count = 0;

        final int totalBorderScore = 350;  
        final int sciencesBorderScore = 160;  
        final int humanitiesBorderScore = 160;  

        for(int i = 0; i < num; i++) {
            (途中省略)
            if(totalScore < totalBorderScore) continue;  
            if(lines[0].equals("○") && sciencesScore < sciencesBorderScore) continue;
            if(lines[0].equals("☓") && humanitiesScore < humanitiesBorderScore) continue; 
                count++;
        }
        System.out.println(count);
    }

上記コードを軽く手直ししてみましょう。すると、. . .

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        (途中省略)
        final int totalBorderScore = 350;  
        final int sciencesBorderScore = 160;
        final int humanitiesBorderScore = 160;

        int result = isborderlessthan(totalBorderScore, sciences, humanitiesBorderScore);
        System.out.println(result);

        public static int isborderlessthan(int totalborderScore, int sciencesBorderScore, int humanitiesBorderScore) {
          int successfulNumber = 0;
          for(int i = 0; i < num; i++) {
            (途中省略)
                if(totalScore < totalBorderScore) return;  
                if(lines[0].equals("○") && sciencesScore < sciencesBorderScore) return;
                if(lines[0].equals("☓") && humanitiesScore < humanitiesBorderScore) return; 
                successfulNumber++;
          }
          return successfulNumber;
        }
    }
}

↑↑ 一見すると読みにくくなったようにも感じますが、上記の14行目〜24行目までのメソッドが正しい場合、12行目まで読めばどういうクラスかが理解できて修正もしやすいコードになります。
(下記のような感じで12行目まででおおまかなことは深く考えずとも理解できるイメージです)

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        (途中省略)
        final int totalBorderScore = 350;  
        final int sciencesBorderScore = 160;
        final int humanitiesBorderScore = 160;

        int result = isborderlessthan(totalBorderScore, sciencesBorderScore, humanitiesBorderScore);
        System.out.println(result); // ここまで読めばなんとなく理解できる

        public static int isborderlessthan() {
            (途中省略)
        }
    }
}

最後に

いかがでしたでしょうか。3Kコード(笑)は、ネストが深く横に長々と書かれたりして見にくく、例外処理と通常の処理も混合していて修正するのに大きな負担がかかってしまいます。

今回ご紹介したようなことを意識してコーディングしていくことで柔軟で読みやすくきれいなコードに近づいていくはずです。ぜひ、参考にしてみて下さい。

Reference

今回は、下記の記事・書籍を参考に今までの経験と照らし合わせながら書いてみました。
ぜひこちらも参考にしてみて下さい。

DDDのノウハウを丁寧に解説
¥3,072 (2022/05/08 17:00時点 | Amazon調べ)
\楽天ポイント4倍セール!/
楽天市場
コードの具体例が豊富でイメージしやすい
¥3,278 (2022/05/08 16:53時点 | Amazon調べ)
\楽天ポイント4倍セール!/
楽天市場
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次