SEN PRODUCT BLOG

千株式会社のエンジニアによるブログ

#内定者レポート 良いコード/悪いコードで滅悪魔導士になってみよう

こんにちは!アドベントカレンダーの15日目を担当する内定者の@koutaro_buriです。

1.はじめに

この記事では、「良いコード/悪いコードで学ぶ設計入門」の中で重要だと感じた基本の部分をピックアップしてお話ししたいと思います。 大学でプログラミングを専攻していたわけでもなく、幼い頃からPCをいじっていたような人間ではないのですが、4月から千でエンジニアとして働くので少しでも力をつけるべくこの本を購入しました。(他の内定者がお勧めしてくれました)


ところでこのタイトル何?と思った方、いるでしょう。 これはこの本の中では悪さをするコード,設計===悪魔とされていて、それを退治する力を手に入れるということで滅悪魔道士(デビルスレイヤー)になってみようとしました。

ちなみにFAIRY TAILが大好きです。


2.悪魔を退治するには弊害を知るべし

なんだこの変数は!

if文の終わりどこ?

変な値が入ってうまく動作してない!

違う型の値が入ってエラー😭

このような経験誰しも一度はあると思います。

彼らがエンジニアの敵となる悪魔たちです。

技術駆動命名や深いネストによる可読性低下、不正値の混入、値の渡し間違いなどさまざまです。

悪魔その一 技術駆動命名 連番命名

下のコードは技術駆動命名と連番命名を使ったコードです。

intValueという謎の定数に180,70という何かしらの数字が入っていて、methodという関数でそれぞれの定数を表示しようとしています。

実はこれらは私の身長と体重です。しかしこれをみてすぐにわかる人はいませんよね。

このようなコードを使用することで可読性が低下していき、しまいにはプロジェクト全体に影響を及ぼすことが容易に想像できます。

const intValue1=180
const intValue2=70

function method1(){
    console.log(intValue1);
}

function method2(){
    console.log(intValue2);
}

method1(); //180 身長
method2(); //70 体重

滅悪魔法その一 目的駆動名前設計をしよう

intValue1としていたものは身長を入れる定数であるためheightという名前に変更しました。

関数名も何を目的とするかという視点から命名しましょう。

よってmethod1からshowHeightという名前に変更し、この関数は身長を表示するものであるというアピールもできました。

const height=180
const weight=70

function showHeight(){
    console.log(height);
}

function showWeight(){
    console.log(weight);
}

showHeight(); //180 身長
showWeight(); //70 体重

「チーム開発においては、命名が重要であり、名前とロジックが対応する前提であること、名前がプログラム構造を大きく左右することを、チーム内で約束しましょう。」とされています

私のようなチーム開発経験に乏しい人はどうしても、自分で理解できるからOKな状態になってしまうこともあるためこのような設計の仕方もしっかりと覚えておきたいものですね。

悪魔その二 条件分岐による深すぎるネスト

if(条件){
//記述

    if(条件){
    //記述
    
    
        if(条件){
        //記述
        
        
        }
    }
}
//続く

このようなifの中にifが入りまた中にifが…という凶悪なコードを一度は見たことがあると思います。

私も大学の授業で初めてJavaScriptを書いたときはこのようなコードしか書けませんでした(HTMLファイルに直書きだったし…)

このようなコードは可読性を損なうだけでなく、仕様変更などの際に十分に読み解けていない状態で変更してしまいバグに繋がる可能性も高いとされています。

滅悪魔法その二 早期returnをしよう

条件分岐による深すぎるネストを解決すべく、早期returnを使いましょう。

if(反転した条件)return;
if(反転した条件)return;
if(反転した条件)return;
//記述

このようなコードにすることで可読性が上がるということだけでなく、その後のロジックの追加などの際にも効力を発揮することができます。

このほかにもelse ifの文も早期return文に変更することも良いでしょう。

悪魔その三 データクラス

class Body {
  constructor(height, weight) {
    this.height = height; 
    this.weight = weight;
  }
 }

データクラスとは単純でデータしか持たないクラスのことでありこのようなコードは、さまざまな悪魔を呼び寄せる悪魔です。

  • 重複コード
  • 修正もれ
  • 可読性低下
  • 未初期化
  • 不正値混入

これらが呼び寄せられてしまうので、対策すべきです。

滅悪魔法その三 オブジェクト指向でコードを書こう

これらの悪魔を滅するべく、オブジェクト指向でコードを書きましょう。

とは言ってもオブジェクト指向は奥が深く、よくXで話題に上がっているのを見かけるので深くを語ろうとするのはリスキーですね…笑

オブジェクト指向はロジック整理の方針がわかりやすいことや、関心の分離がしやすいということで広く使われています。これを使ってデータクラスを改善します。

データクラスはデフォルトコンストラクタでインスタンスを生成してインスタンス変数に個別に値を代入して初期化するものですが、これでは不正値を渡すことができてしまう状態です。

class Body {
  constructor(height, weight) {
    if (height < 0) {
      throw new Error('身長には0以上を指定してください。');
    }
    if (weight < 0) {
      throw new Error('体重には0以上を指定してください。');
    }
    this.height = height; 
    this.weight = weight; 
    Object.freeze(this); 
  }

  calculateBMI() {
    return this.weight / (this.height * this.height);
  }

 equals(other) {
    return (
      other instanceof Body &&
      this.height === other.height &&
      this.weight === other.weight
    );
  }
}

このようにすることで正常な値のみを渡すことができます。

そして計算ロジックはクラス側に用意しておくことで低凝集などの弊害を避けることができます。

そしてインスタンス変数が上書き可能であると現在の値をいちいち考える必要が出ます。

これを防ぐためにはインスタンス変数をイミュータブルにします。

 Object.freeze(this); 

こうすることで再代入を防ぎ、不正に強くできます。

ここまでくると多くの悪魔を防ぐことができていますが、値の渡し間違いを防げていません。

静的型付けができる言語の強みがこれということですね。

class Body {
  private readonly height: number; 
  private readonly weight: number; 

  constructor(height: number, weight: number) {
    if (height < 0) {
      throw new Error('身長には0以上を指定してください。');
    }
    if (weight < 0) {
      throw new Error('体重には0以上を指定してください。');
    }
    this.height = height;
    this.weight = weight;
  }

  public calculateBMI(): number {
    return this.weight / (this.height * this.height);
  }

public equals(other: Body): boolean {
    return (
      this.height === other.height &&
      this.weight === other.weight
    );
  }
}

const myBody = new Body(180, 70);
const yourBody = new Body(180, 70);
const hisBody = new Body(165, 60);
console.log(myBody.equals(yourBody)); // true
console.log(myBody.equals(hisBody)); // false

型指定をしようとすると上記のようになります。

今回のコードは完全コンストラクタと値オブジェクトという設計パターンを用いたのもので、オブジェクト指向設計の基本形を体現している構造の一つとされています。

完全コンストラクタというのはインスタンス変数を全て初期化できる引数を持ったコンストラクタを用意し、ガード節で不正値を弾くというものです。

値オブジェクトというのは、値を型として表現する設計パターンであり、ロジックを高凝集にすることができます。以上のように実装することでデータクラスが招く悪魔たちを滅することができるでしょう。


3. まとめ

良いコード/悪いコードで学ぶ設計入門を読んで学んだことについて書きました。

  • 目的駆動名前設計をしよう
  • 早期returnをしよう
  • オブジェクト指向でコードを書こう

この3つを心得て、習慣にしていくことで少しでも良いコードを書くことができるようになるかと思います。4月から千でエンジニアとして働く身として、少しはレベルアップできたかな…笑 これで満足するにではなく個人開発などでもレベルアップしていき、貢献できるようになりたいです!

本にはさらに発展的な内容やDDDについての内容も書かれていて興味深い内容が多いので皆様もぜひ!

最後まで読んでいただき、ありがとうございました!この記事を通して、少しでも良いコード/悪いコードで学ぶ設計入門あるいはミノ駆動本に興味を持っていただけたら嬉しいです。

(フェアリーテイルにも興味を持っていただけたら幸いです。)

明日の記事もお楽しみに!