4章 5章 - TKBTK48/ReadableCode GitHub Wiki

4章 美しさ

  • 読み手が慣れているパターンと一貫性のあるレイアウトを使う
  • 似ているコードは似ているように見せる
  • 関連するコードはまとめてブロックにする

4.1 なぜ美しさが大切なのか?

class:class StatsKeeper{
public:
// doubleを記録するクラス
  void Add(double d); //とすばやく統計を出すメソッド
 private: int count;     /* それまでの    個数
*/ public:
     double Average();

private:  double minimum;
list
  past_items
    ;double maximum;
};

⇨理解するのに時間がかかる



// doubleを記録するクラス
// とすばやく統計を出すメソッド
class:class StatsKeeper{
  public:
    void Add(double d);
    doubel Average();
  private:
   list past_items;
   int count; // それまでの個数

   double minimum;
   double maximum;
};

⇒見た目が美しいコードのほうが使いやすい

4.2 一貫性のある簡潔な改行位置

public class PerformanceTester{
  public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator(
  500, /* Kbps * /
  80, /* millisecs latency * /
  200, /* jitter * /
  1, /* packet loss % */);

 public static final TcpConnectionSimulator t3_fiber =
  new TcpConnectionSimulator(
  45000, /* Kbps * /
  10, /* millisecs latency * /
  0, /* jitter * /
  0, /* packet loss % */);

 public static final TcpConnectionSimulator t3_fiber = new TcpConnectionSimulator(
  100, /* Kbps * /
  400, /* millisecs latency * /
  250, /* jitter * /
  5, /* packet loss % */);
}

⇨適切な改行位置に変更

public class PerformanceTester{
 public static final TcpConnectionSimulator wifi =
   new TcpConnectionSimulator(
   500, /* Kbps * /
   80, /* millisecs latency * /
   200, /* jitter * /
   1, /* packet loss % */);

 public static final TcpConnectionSimulator t3_fiber =
   new TcpConnectionSimulator(
   45000, /* Kbps * /
   10, /* millisecs latency * /
   0, /* jitter * /
   0, /* packet loss % */);

 public static final TcpConnectionSimulator t3_fiber =
   new TcpConnectionSimulator(
   100, /* Kbps * /
   400, /* millisecs latency * /
   250, /* jitter * /
   5, /* packet loss % */);
}

⇨コメントの繰り返しを集約化

public class PerformanceTester{
  //TcpConnectionSimulator(throughput, latency, jitter, packet_loss)
  //             [Kbps]  [ms]  [ms] [percent]
 public static final TcpConnectionSimulator wifi =
   new TcpConnectionSimulator(500,  80, 200, 1)
 public static final TcpConnectionSimulator t3_fiber =
   new TcpConnectionSimulator(45000, 10,  0, 0)
 public static final TcpConnectionSimulator cell =
   new TcpConnectionSimulator(100,  400, 250, 5)

4.3 メソッドを使った整列


DatabaseConnection database_connnection;
string error;
assert(ExpandFullName(database_connection, "Doug Adams", &error) == "Mr. Douglas Adams");
assert(error == "");
assert(ExpandFullName(database_connection, "Jake Brown", &error) == "Mr. Jacob Brown Ⅲ");
assert(error == "");
assert(ExpandFullName(database_connection, "No Such Guy", &error) == "");
assert(error == "no match found");

見た目が美しくないコードや、同じことを何度も書いているコードは、ヘルパーメソッドを使って改善する。

    ヘルパーメソッドとは?
    ViewをよりシンプルにDRYに書くための、Railsで用意されたモジュール。
    基本的にはviewをhelpしてくれるもの。自分で作ることもできる。

CheckFullName("Doug Adams", "Mr. Douglas Adams", "");
CheckFullName(" Jake Brown,", "Mr. Jake Bron III", "");
CheckFullName("No Such Guy", "", "no match found");
CheckFullName("John", "", "more than one result");

  • 重複を排除することでデコードが簡潔になった。
  • テストケースの大切な部分 (名前やエラー文字列)が見やすくなった。
  • テストの追加が簡単になった。

4.4 縦線をまっすぐにする  

縦の線をまっすぐにし、列を「整列」させれば、コードが読みやすくなることがある。
CheckFullName("Doug Adams" , "Mr. Douglas Adams", "");
CheckFullName(" Jake Brown,"  , "Mr. Jake Bron III"      , "");
CheckFullName("No Such Guy" , ""                               , "no match found");
CheckFullName("John"              , ""                               , "more than one result");

wgetのコードでは、利用可能なコマンドラインオプション(100個以上ある)が、以下のように並べられている。
commands[] = {
  ...
   { "timeout",       NULL,                            cmd_spec_timeout },
   { "timestamp",  &opt.timestamping,      cmd_boolean },
   { "tries",            &opt.ntry,                      cmd_number_inf },
   { "useproxy",    &opt.use_proxy,            cmd_boolean },
   { "useragent",   NULL,                            cmd_spec_useragent },
   ...
};

整列すべきなのか? 

整列することにより、1行変更するだけですべての行を変更しなければならない、差分が増えるという意見があるが、実際にはそこまで手間はかからないはずだ。

4.5 一貫性と意味のある並び

ランダムに並べるのではなく、意味のある順番に並べるのが良い。
例えば、

  • 対応するHTMLフォームの<input>フィールドと同じ並び順にする。
          「INPUT」とは、 タグで作成したフォームの中でテキスト入力欄やボタンなどの部品を作成する要素。
          例)<input type="text" id="name" name="name" required minlength="4" maxlength="8" size="10">
  • 「最重要」なものから重要度順に並べる。
  • アルファベット順に並べる。

4.6 宣言をブロックにまとめる

コードの概要を素早く把握するには、単位を作成する。

メソッドを1つの大きなグループにまとめるのではなく、論理的なグループに分けてあげるのが適切だと思われる。

4.7 コードを「段落」に分割する

文章と同様に、コードも段落に分けるべきである。
# ユーザのメール帳をインポートして、システムのユーザと照合する。
# そして、まだ友達になっていないユーザの一覧を表示する。
def suggest_new_friends (user, email_password):
   friends = user.friends()
   friend_emals = set(f.email for f in friends)
   contacts = import_contacts(user.email, email_password);
   contact_emals = set(c.email for c in contacts)
   non_friend_emails = contact_emals - friend_emails
   suggested_friends = USer.objects.select(email__in=non_friend_emails)
   display['user'] = user
   display['friends'] = friends
   display['suggested_friends'] = suggested_friends
   return render("suggested_friends.html", display)

コードを分割する。
# ユーザのメール帳をインポートして、システムのユーザと照合する。
# そして、まだ友達になっていないユーザの一覧を表示する。
def suggest_new_friends (user, email_password):
   # ユーザの友達のメールアドレスを取得する。
   friends = user.friends()
   friend_emals = set(f.email for f in friends)

   # ユーザのメールアカウントからすべてのメールアドレスをインポートする。
   contacts = import_contacts(user.email, email_password);
   contact_emals = set(c.email for c in contacts)

   # まだ友達になっていないユーザを探す。
   non_friend_emails = contact_emals - friend_emails
   suggested_friends = USer.objects.select(email__in=non_friend_emails)

   # それをページに表示する
   display['user'] = user
   display['friends'] = friends
   display['suggested_friends'] = suggested_friends
   return render("suggested_friends.html", display)

4.8 個人的な好みと一貫性

クラスと定義のカッコの位置など、個人的な好みはどちらでもよい。ただし、それを混ぜてしまうと、すごく読みに組み物になってしまう。

プロジェクトの規約に従い、一貫性があった方がプログラムとしては読みやすいものになる。

4.9 まとめ

  • 複数のブロックコードで同じようなことをしていたら、シルエットも同じようなものにする
  • コードの「列」を整列すれば概要が把握しやすくなる
  • ある場所でA,B,Cのように並んでいたものを、他の場所でもB,C,Aのように並べてはいけない。意味のある順番を選んで、常にその順番を守る
  • 空行を使って大きなブロックを論理的な「段落」に分ける




5章 コメントすべきことを知る

鍵となる考え: コメントの目的は、書き手の意図を読み手に知らせることである。

  • コメントすべきでは「ない」ことを知る。
  • コードを書いているときの自分の考えを記録する。
  • 読み手の立場になって何が必要になるかを考える



5.1 コメントするべきでは「ない」こと

  • 価値のあるコメントと価値のないコメントがある。
  • 新しい情報を提供するわけでもなく、読み手がコードを理解しやすくなるわけでも無いコードには価値がない。

鍵となる考え: コードからすぐにわかることをコメントに書かない。「すぐに」ということが大切。コードを読むのが早いか、コメントを読むのが早いか。


   コメントのためのコメントをしない

// 与えられたsubtreeに含まれるnameとdepthに合致したNodeを見つける。
Node* FindNodeInSubtree (Node subtree, string name, int depth);

関数宣言しているだけのコメントは意味がない
コメントは以下のように大切なことの説明をする

// 与えられた'name'に合致したNodeかNULLを返す。
// もし depth <= 0ならば、'subtree'だけを調べる。
// もし depth == N ならば、'subtree'とその下のN階層まで調べる。
Node
FindNodeInSubtree (Node* subtree, string name, int depth);



   ひどい名前はコメントをつけずに名前を変える

「制限を課す(enforce limit)」などという付加的情報は関数名に入れる方が適切。 優れたコメントよりも名前の方が大切。
通常は、「補助的なコメント」(コードの読みにくさを補うコメント)が必要になることはない。
プログラマはこのことを「 優れたコード > ひどいコード + 優れたコメント 」と言っている。

5.2 自分の考えを記録する

   「監督のコメンタリー」を入れる

自分の考えを記録するために、コメントにはコードに対する大切な考え方を記録しなければならない。

// このデータだとハッシュテーブルよりもバイナリツリーの方が40%速かった。
// 左右の比較よりもハッシュの計算コストの方が高いようだ。

テストケースに無駄な時間を掛けることのないようなコメントなど、あとはコードが汚い理由をコメントに残してもよい。


   コードの欠陥にコメントをつける

改善が必要な場合、
// TODO: もっと高速なアルゴリズムを使う

コードが未完成な時は
// TODO(ダスティン): JPEG以外のフォーマットに対応する

プログラマが良く使う記法のいくつか:

  • TODO: あとで手を付ける(小さいことであればtodo)
  • FIXME: 既知の不具合があるコード
  • HACK: あまりキレイじゃない解決策
  • XXX: 危険!大きな問題がある

   定数にコメントをつける

定数を定義するときは、なぜその値を定義したのか、なぜその値を持っているのかという「背景」が存在することが多い。

NUM_THREADS = 8


これに対して、以下のようにコメントを記述すれば、値の決め方がわかる。

NUM_THREADS = 8 # 値は「>= 2 * num_processors」で十分


5.3 読み手の立場になって考える

ほかの人にコードがどのように見えるかを想像するものだ。
「ほかの人」というのは、プロジェクトのことを君のように熟知していない人のことである。

   質問されそうなことを想像する

他人に質問されそうな、疑問を持ちそうなところにコメントを挿入する。
struct Recorder {
  vector data;
  ...
  void Clear () {
    vector().swap(data);  // えっ?どうしてdata.clear()じゃないの?
  }
};

どうして単純にdata.clear()せずに空のベクタをスワップ(交換)するんだ?
⇒これは、以下のようにしてコメントをしておくべきだ。
// ベクタのメモリを解放する(「STL swap 技法」で検索してみよう)
vector().swap(data);

  swap 技法 ectorのメソッドであるswapを用いて一時オブジェクトと交換してやることでデストラクタを半ば強制的に動かすような技法(詳細はよくわからず)

   ハマりそうな罠を告知する

このコードを見てびっくりすることは何だろう?どんなふうに間違えて使う可能性があるだろう
ということを想像してコメントを書く。

void SendEmail (string to, string subject, string body);

この関数の実装では、外部のメールサービスに接続しており、メールサービスがダウンしていると、ウェブアプリケーションが「ハング」してしまう。
このような不幸を防ぐためには、「実装の詳細」についてコメントを書くべきだ。

// メールを送信する外部サービスを呼び出している(1分でタイムアウト)
void SendEmail (string to, string subject, string body);



   「全体像」のコメント

新しいチームメンバにとって、最も難しいのは「全体像」の理解である。 新しくチームに参加した人がいたとすると、コードに慣れてもらわなければならない。
ソースコードを読んだだけでは得られない情報は、高レベルのコメントに書くべき情報な場合がある。
短い適切な文章で構わない。何もないよりはマシだ。


   要約コメント

低レベルのコードをうまく要約したコメント

# 顧客が自分で購入した商品を検索する
for customer_id in all_customers:
  for sale in all_sales[customer_id].sales:
   if sale.recipient == customer_id:
    ...

コメントがないと、コードを読んでいる途中で意味が分からなくなる。

要約コメントは、関数の内部にある大きな「塊」につけてもいい。

def GenerateUserReport():
   # このユーザのロックを獲得する
   ...
   # ユーザの情報をDBから読み込む
   ...
   # 情報をファイルに書き出す
   ...
   # このユーザのロックを開放する


5.4 ライターズブロックを乗り越える

ライターズブロック=行き詰ってしまって、文章が書けないこと。

自分の考えていることをとりあえず書き出してみよう。生煮えであってもかまわない。

// ヤバい。これはリストに重複があったら面倒なことになる。

もう少し詳細に書く。
⇒「ヤバい」は「注意:これには気を付けて」という意味だ
⇒「これ」は「入力する処理コード」という意味だ
⇒「面倒なことになる」は「実装が難しくなる」という意味だ

// 注意: このコードはリストの重複を処理できません(実装が難しいので)

コメントを書く作業は、3つの簡単な手順に分解できる。

  • 頭の中にあるコメントをとにかく書き出す。
  • コメントを読んで(どちらかといえば)改善が必要なものを見つける。
  • 改善する。



5.5 まとめ

コメントすべきでは「ない」こと。

  • コードからすぐに抽出できること。
  • ひどいコード(例えば、ひどい名前の関数)を補う「補助的なコメント」。コメントを書くべきではなく、コードを修正する。

記録すべき自分の考え

  • なぜコードがほかのやり方ではなくこうなっているのか(「監督コメンタリー」)
  • コードの欠陥をTODO: や XXX: などの記法を使って示す。
  • 定数の値にまつわる「背景」

読み手の立場になって考える

  • コードを読んだ人が「えっ?」と思うところを予想してコメントをつける。
  • 平均的な読み手が驚くような動作は文書化しておく。
  • ファイルやクラスには「全体像」のコメントを書く。
  • 読み手が細部にとらわれないように、コードブロックにコメントをつけて概要をまとめる。
⚠️ **GitHub.com Fallback** ⚠️