The Dabsong Conshirtoe

技術系の話を主にします。

ミクロな可読性とマクロな可読性

コードにおいて最も重要なことは何か?と問われれば、「可読性」だと答えます。
とにかく読みにくいコードは嫌だ。なんといっても醜いので生理的に不快。


では可読性とは何か?と考えてみたところ、2つのタイプがあるように思いましたので、ここではそれを書き留めておきたいと思います。

2つのタイプとは、意図の明快さを実現する「ミクロな可読性」と、構成の明快さを実現する「マクロな可読性」の2つです。

ミクロな可読性

ミクロな可読性とは、コードを読む人が以下の2要素をどの程度明快に理解できるかの指標です。

  • コードで達成しようとしている目的
  • 具体的にどのように目的を達成しているかの方法

前者は、クラスやメソッド名の命名やそのクラス・メソッドに関するドキュメントの適切さが重要で、後者はメソッドの中で使用される変数名やステップの明快さ、各ステップの抽象度レベルの一致が重要です。

目的を明快にする

まず、例を使って前者の説明をします。以下のコードを見てみましょう。

class P(object):
  t = 0.05

  def f(self):
    pt = self.pr * self.t
    return self.pr + pt

このコード何をしているかはわかるでしょう。self.prとself.tの積にself.prを足して返しています。
一方、この関数がどんな目的を持っているかはこれだけでは意味不明でしょう。
つまり、このコードは方法は(ある程度)明確だが、目的が全く不明、という状態です。


以下のようにリファクタリングしてみましょう。

class Product
  '''
  商品を扱うクラス
  '''
  # 消費税率
  sales_tax = 0.05

  def calculate_price_with_tax(self):
    '''
    商品の価格に消費税を上乗せして返却する。
    '''
    tax = self.price * self.sales_tax
    return product.price + tax

処理の流れには全く手を入れずに名前の変更とコメントの追加をすることで、「ある商品の税込価格を求める」という目的がはっきりしました。この方が、全く事情を知らない人が読んだとしても理解できるでしょう。

このように、コードの目的をはっきりさせるには名前のチョイスが非常に重要だと思います。
(それだけでは無いことには賛成しますが、ひとまず名前の重要性を強調しておきたいです)

※改善前の例が大袈裟だと思われるかもしれませんが、たまにあのようなコードを見ることがあるので世の中怖いものです。

手段を明快にする

次に後者の説明をします。次のコードを見てください。

def register_user(input_params, connection):
  name = input_params.get('name')
  password = input_params.get('password')
  if not (user and password):
    return None

  same_name_user_count = connection.execute_query('select count(*) as count from users where name = %s' % escape(name))['count']
  if sanme_name_user_count > 0:
    return None

  if len(password) < 8:
    return None

  user = User(name, password)
  user.update(connection)

  return user

命名がしっかりしているので、入力値を元にユーザーの登録をしようとしている、という目的はなんとなくわかるでしょう。
ただ、処理の流れが散乱している感じで追いづらいと思います。この程度のプログラムならまだしも、これが100行とか続いたりするとさすがにつらいですよね。

問題は、ユーザーを登録するというこのメソッドが、その登録の過程で行われる詳細に足を突っ込みすぎてるのです。


以下のようにしてみましょう。

def register_user(input_params, connection):
  # バリデーションを行う
  is_valid_request = input_params.validate()
  if not is_valid_request:
    return None

  # ユーザー登録を行う
  new_user = User.register_user(input_params, connection)
  return new_user

バリデーションの方法やユーザーの保存方法の詳細は別のメソッドに任せることで、パラメータのバリデーションを行なって、ユーザーの登録をする、というこの処理の主要な部分が目立っています。
また、コメントがその2つの要素があるということを更に引き立てています。
(input_params.validateにconnetionを渡していないのは、ユーザーの重複チェックをUser.register_userで行うようにしたからです。ユーザー名の唯一性は、入力値のチェックというよりは、Userというドメインが守るべきことだと思いますので。)

つまり、その処理の中身の抽象度を適切なレベルに保つことが、手段の明快さを実現する上で重要だということです。


なお、このミクロな可読性については「リーダブルコード」に詳しく書かれていますので読んでみてください。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

マクロな可読性

ミクロな可読性が「コードの意図を明快にする」という目的を持っていたのに対し、マクロな可読性は「コードの構成を明快にする」という目的を持っています。


構成を明快にするとは例えば、パッケージ全体を見た時にどのパッケージが何をやってるかわかるとか、パッケージ内のソースファイル名をざっと眺めたときにどこで何をやってるかすぐにわかる、という性質です。
つまり、ここでも名前、それに加えて分類の適切さが重要になってきます。名前がはっきりしていても、一つのパッケージの中にビジネスロジックを実現するソースファイルと入力をハンドリングするソースファイルが混じっていたら混乱するでしょう。


今や当たり前となったMVCも、Model・View・Controllerの明快な分類を行なっているので、マクロな可読性の向上に貢献するという意味でも有効な方法だと思います。(Modelはちょっとざっくりしすぎなのでもう少し分類する必要があるとは思いますが)

この分類と命名がしっかりできていると、仕様変更やバグ調査を行うときにどこを見ればよいかわかりやすくなるので、変更のしやすさというソフトウェアにとって非常に重要な価値にもつながります。


以上、散髪中にふと思いつたいので書いてみました。