コーディングを完全に理解する前後で変わったこと2つ

「良いコード 悪いコードで学ぶ設計入門」
という本を書店で見つけ立ち読みして、これは良書だと思い即購入して読了した。
それ以前は言語の文法について把握し、なんとなくオブジェクト指向がどんなものかを把握し、mainベタ書きの手続き的なアルゴリズムで簡単なスクリプトを書ける程度のスキルまでしかたどり着けていなかった。
しかしこの本を読んだことによってなんとなく把握してある程度書いてみたことすべてが線で繋がり、「完全に理解」する壁を超えることができた。
以下はこの本を読んだことによって何が変わったかを残しておくための文章である。

1: 条件分岐のネストが激減し、可読性が大幅に上がった
早期returnというテクニックを覚えたことによってバリデーションと特殊な条件チェックを先に行ってreturnで排除することによりif文のネストがなくなる。else文すらいらなくなることが多くなった。

import java.util.Scanner;
class Nabeatsu{

  public static void main(String[] args){ //mainメソッドの始まり。決まり文句。

    Scanner scanner = new Scanner(System.in); //scannerクラスのインスタンスを作成
    boolean roop = true ; //ループさせるための変数

    do{/*繰り返しを行う。変数roopがtrueの間ループさせる。最後にroopを変化させるかの処理を入れている。*/
          boolean skip = false ;/*重複させないための変数*/
          boolean skip2 = false;
          boolean skip3 = false;
          boolean skip4 = false;
            System.out.println("好きな数字を入力してください");
            int n = scanner.nextInt(); //判定する数字を変数nに格納する。

            if (n % 3 == 0){   //3の倍数の判定
              skip = true;
            }else if(n % 10 == 3){ //1桁目の数字を判定する。10で割ったあまりが3
                    skip2 = true;
                  }else{
                    for (int i = 1; n > 0; i++){ //桁の数だけ10で割ってその余りを逐次判定する
                        n /= 10;
                          if(n ==3){
                            //System.out.println("1桁目以外が3です");
                            skip3 = true ;
                            break;
                          }
                    }
                  }

           if(skip){ /*条件による処理をここに書いて分けることで重複を回避する。elseは上の条件がfalseであるときのみ処理されるため。*/
             System.out.println("3の倍数です");
           }else if(skip2){
             System.out.println("1桁目が3です");
           }else if(skip3){
             System.out.println("1桁目以外が3です");
           }else{
             System.out.println("ナベアツではありません");
           }

           /*System.out.println(skip);
           System.out.println(skip2);
           System.out.println(skip3);
           System.out.println(skip4);真偽値のデバッグ用*/
           System.out.println("続けますか? 1で継続、2で終了。");
           int exit = scanner.nextInt();
           if (exit == 1);
             else if(exit == 2){
               roop = false ; //roopがfalseになりwhileの条件を満たさなくなり処理が終わる。
         }

    }while(roop == true);

  }
}

これは私がプログラミングを初めて最初に書いたコードである。mainベタ書きのifネストに無駄なフラグと大変ゴミなコードである。完全に理解した後のコードはこのようになった。

import java.util.Scanner;

class Nabeatsu {

  private static boolean is_multiple(int num) {

    if (!(num % 3 == 0)) {
      return false;
    }
    return true;

  }

  private static boolean has_three(int num) {

    while (num > 0) {
      if (num % 10 == 3) {
        return true;
      }
      num /= 10;
    }

    return false;
  }

  private static void print_ismultiple(boolean ismultiple) {

    if (!ismultiple) {
      System.out.println("3の倍数ではありません");
    } else {
      System.out.println("3の倍数です");
    }

  }

  private static void print_has_three(boolean hasthree) {

    if (!hasthree) {
      System.out.println("3が含まれていません");
    } else {
      System.out.println("3が含まれています");
    }

  }

  public static void main(String[] args) {

    try (Scanner scanner = new Scanner(System.in)) {
      boolean roop = true;

      do {
        boolean is_multiple = false;
        boolean has_three = false;

        System.out.println("数字を入力してください");
        int input = scanner.nextInt();

        is_multiple = is_multiple(input);
        has_three = has_three(input);

        print_ismultiple(is_multiple);
        print_has_three(has_three);

        System.out.println("続けますか? 1で継続、2で終了");
        int exit = scanner.nextInt();
        if (exit == 2) {
          roop = false;
        } else if (exit == 1)
          ;
      } while (roop);

    }

  }
}

staticおじさんスタイルで、mainにベタ書きという手続き的な部分は変えずにCっぽくリファクタリングをした。条件分岐を隔離して名前をつけることによって可読性が大幅に向上している。
ifをネストして条件分岐をするのではなく、ネストしなければならないということは条件分岐が複雑になりすぎており、1つの条件チェックだけではなくなっているということであると意識する。ネストしているifを意味毎に切り出し、名前をつける。条件分岐を行う際はbooleanを返す関数に隔離して名前をつけ、戻ってきたbooleanに対して処理を記述するようにする。この意識が初心者がまず習得しなければならないことであると強く感じた。

2: ある値を格納するクラスに、その値を必要とするロジックをすべてまとめておくようになった。(というかまず値とロジックをまとめてクラスをちゃんと作るようになった)

例えば以下のように変化した

    def write(
        self, file_path, loops: int, algorithm=lambda cluster: cluster.place_atoms_in_a_plane()
    ) -> None:  # ラムダ式で処理を実行せずに受け取る
        if self.header is None:
            print("No header has been set.")
            return

        count = 0
        with open(file_path, "w") as file:
            while count < loops:
                print(f"placing trial {count}")
                cluster = algorithm(self.atom_cluster)  # Use the passed algorithm

                print(f"condition check {count}")
                condition = cluster.check_and_report_conditions(plot_type="none")

                if condition == "not crossed":
                    file.write(self.header)
                    self._write_atom_cluster(file)
                    print("cluster written")
                    if count < loops - 1:
                        file.write("\n--Link1--\n")
                    count += 1  # Increment count only when condition is met
        print(f"Gaussian input file has been written to {file_path}")
if condition == "not crossed":

は値と戻り値を知っていることが前提の実装となっており、このようなことを続けているといずれ破綻することが予測された。そこで以下のように変更した。

    def write(self, file_path, write_times: int, algorithm: Callable[[], AtomClusterInterface]) -> None:
        if self.header is None:
            print("No header has been set.")
            return

        count = 0
        with open(file_path, "w") as file:
            while count < write_times:
                print(f"placing trial {count}")
                self.atom_cluster = algorithm()  # Use the specified algorithm

                if self.atom_cluster.is_possible():
                    print(self.atom_cluster.is_possible())
                    file.write(self.header)
                    self._write_atom_cluster(file)
                    print("cluster written")
                    if count < write_times - 1:
                        file.write("\n--Link1--\n")
                    count += 1  # Increment count only when condition is met
                file.write("\n")  # add a new line the end of the file
        print(f"Gaussian input file has been written to {file_path}")
if self.atom_cluster.is_possible()

で分岐させることにより、値を把握しているインスタンス自信に判断を行ってもらうようにした。is_〇〇というのはどうも慣例的にboolを返すメソッドを指すことが多いようなので、このような書き方をすることで可読性が高くなったといえるだろう。
bool以外で条件分岐をするのはやめるようにした方がいいと感じた。

これらは実務をやっていると当たり前じゃんと感じる事項だと考えられるが、初心者は何が当たり前かすらわからないのできちんと文字に残しておくことにした。自分の成長記録としても後から見返せるようにしておく。


この記事が気に入ったらサポートをしてみませんか?