0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【読書感想文】Clean Code

Posted at

はじめに

同僚から勧められてClean Codeを読みました。

全体的にリーダブルコード単体テストの考え方/使い方と同じことが書かれていましたが、それらよりも、より思想が尖っているという印象を受けました(笑)。

読んだ中で、印象に残ったことをアウトプットのために記事にします。

1章 続編と原則

p41 ボーイスカウトの原則

キャンプ場を、自分が見つけたときよりもきれいにすること

コードを書いていると、"うわ、このコード汚いけど直すの面倒くさいからそのままにしておこう"と思ってしまうことがよくありました。
小学生の時によく言われたうっとうしい格言が大好きなので、自戒も込めてこの言葉を思い出しながらコードを書いていこうと思いました。
チームメンバー全員がこの原則のもとに行動していると、きれいなコードを残し続けることができるかもしれませんね、、、

3章 関数

p64 小さいこと!

尖っているなぁと思ったポイントの一つです(笑)。

関数はできるだけ小さくすることということです。
よく聞くことではありますが、本書では異常なほど小さく切り出していました。

電卓マジックを例に、リーダブルコードとの比較コードを記載します。
(個人的な解釈なので、間違っていたらごめんなさい:bow:)

リーダブルコード
int your_number_three;
int your_number_six;
int step_one, step_two, step_three;

void dentaku_majic() {
  // 数字を入力
  std::cout << "好きな数を3桁打ち込んでください\n";
  std::cin >> your_number_three;
  your_number_six = your_number_three * 1000 + your_number_three;
  std::cout << "好きな数をもう一度打ち込んで6桁にした数は" << your_number_six << "です" << std::endl;

  // 電卓マジックを実行
  step_one  = your_number_six / 7;
  step_two  = step_one        / 11;
  step_three = step_two       / 13;

  // 結果を出力
  std::cout << "7で割ると"  << step_one   << std::endl;
  std::cout << "11で割ると" << step_two   << std::endl;
  std::cout << "13で割ると" << step_three << std::endl;
  std::cout << "最初の数字は" << your_number_three << "でした。" << std::endl;
}
Clean Code
int your_number_three;
int your_number_six;
int step_one, step_two, step_three;

void input_your_number() {
  // 数字を入力
  std::cout << "好きな数を3桁打ち込んでください\n";
  std::cin >> your_number_three;
  your_number_six = your_number_three * 1000 + your_number_three;
  std::cout << "好きな数をもう一度打ち込んで6桁にした数は" << your_number_six << "です" << std::endl;
}

void process_dentaku_majic() {
  // 電卓マジックを実行
  step_one  = your_number_six / 7;
  step_two  = step_one / 11;
  step_three = step_two / 13;
}

void output_result() {
  // 結果を出力
  std::cout << "7で割ると"  << step_one   << std::endl;
  std::cout << "11で割ると" << step_two   << std::endl;
  std::cout << "13で割ると" << step_three << std::endl;
  std::cout << "最初の数字は" << your_number_three << "でした。" << std::endl;
}

void dentaku_majic() {
  input_your_number();
  
  process_dentaku_majic();

  output_result();
}

一つ上の抽象度のブロックを切り出すことで"コードの読み飛ばしを可能にする"という観点は共通しています。
リーダブルコードではそれをコメントで、Clean Codeでは関数を小さく切り出し、その関数名でコードの読み飛ばしを可能にしています。
小さく切り出した関数に適切な形式でコメントを記載すると、VScodeなどでカーソルを合わせた時にその関数の説明が日本語で出るので、英語が苦手な方でも読み飛ばしをすることができると思います。

p71 関数の引数

これも尖っているなぁと思いました(笑)。

関数の引数が多いと理解に時間がかかるため、できるだけ0個または1個に抑えることということです。

本書のコードを読むと確かにその通りなのですが、関数型プログラミングを意識したパイプラインアーキテクチャのような構成だと、どうしても引数が増えてしまったという経験がありました。

本書のどこかで書かれていたように、受け渡し用のデータを構造体で定義する方法しかないと思いますが、何か釈然としない、、、

4章 コメント

適切なコメントの使用方法とは、コードでうまく表現することに失敗したときに、それを補うのに使うこと

これも尖っていて好きです。
読みやすいコード = コメントがたくさん書かれているコードだと思っている人がたくさんいたので、、、(自分も尖っているのかも)

6章 オブジェクトとデータ構造

p137 データ抽象化

getter、setterを使う理由について記載されていました。

これまで、オブジェクト指向ではgetter,setterを用意することでデータへのアクセスを制限していると思っていました。
しかし、getterとsetter両方が実装されているデータは結局publicと同じでは?という疑問もありました。

そこで、本書ではデータを抽象化された形式で表現するためにgetter,setterを用意すると記載されていました。

本書の例をそのまま記載させていただきます。
前者の例では乗り物の残りの燃料の量を具体的に表現していますが、後者では百分率によって抽象化されているため、データがどのように格納されているか予想がつきません。

NG:具象的な乗り物
public interface Vehicle {
  double getFuelTankCapacityInGalllons();
  double getGallonsOfGasoline();
}
OK:抽象的な乗り物
public interface Vehicle {
  double getPercentFuelRemaining();
}

アクセス制限よりも納得感はあるのですが、下記のような疑問も浮かびました。

  • 後者の方法だとreturn以上の実装をしているのでは?
  • 抽象化の技術が必要なのでは?

p139 データ/オブジェクトの非対称性

手続き型(データ構造を使用するコード)は、新たな関数を既存のデータ構造に影響を与えずに追加することができます。
オブジェクト指向の場合、逆に既存の関数を変えることなく、新たなクラスを追加することが可能です。

逆もまた成立します。

手続き型だと、新たなデータ構造を追加するには、既存のすべての関数を変えなければならないので、難しくなります。
オブジェクト指向の場合、すべてのクラスを変えなければならないので、新たな関数を追加することは難しくなります。

正直わかるようで、わからない、、、
オブジェクト指向を採用するべきか悩んだことがあったので、今後はこの観点も含めて採用を決めていきたいと思います。

どっかに書いてあったやつ

if文

小さく切り出した関数の中でif文が使われているということは、一つの関数の中で2つのことをしているのでよくないということでした。

関数の中でif文を使用
void turn_LED(int operation) {
  // Raspberry PiからLEDをON/OFFする
  if (operation == 0) {
    digitalWrite(pin, HIGH);
  } else {
    digitalWrite(pin, LOW);
  }
}

int main() {
  int is_LED_ON;
  std::cin >> is_LED_ON;

  turn_switch(is_LED_ON);

  return 0;
}
呼び出し側でif文を使用
void turn_on_LED(void) {
  // Raspberry PiからLEDをONする
  digitalWrite(pin, HIGH);
}

void turn_off_LED(void) {
  // Raspberry PiからLEDをOFFする
  digitalWrite(pin, LOW);
}

int main() {
  int is_LED_ON;
  std::cin >> is_LED_ON;

  if (is_LED_ON == 0) {
    turn_on_LED();
  } else {
    turn_off_LED();
  }

  return 0;
}

if文を使う場所が違うだけじゃない?とも思いましたが、
可読性の向上に加え、変更に対しては強いのかなとも思いました。
上記の例でLEDを点滅させる処理を追加する場合、
前者の方法では2つの関数(turn_LED,main)の変更が必要になる一方、
後者の方法では1つの関数(main)の変更と1つの関数の作成になります。

条件のカプセル化

条件判断もカプセル化することで可読性を向上させることができます。
関数名を上手につける必要がありますが、その技術があれば実践するべきかなと思いました。

判断
if (a == 1 && b == 2 && c == 3) {
  d = 4;
}
判断
if (should_d_change()) {
 d = 4;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?