🌊

コードリーディングのポイント

2022/04/12に公開

本記事について

開発において,システムの原理を理解したり,自分が作るにあたってアーキテクチャやリファクタリングの観点で「良い」コードを知るためにコードリーディングは重要である.本記事ではその方法と重要なポイントを述べ,最後に実際に行ってみた結果を載せる.

コードリーディングのポイント

早速ポイントについて述べる.
まずコードリーディングを行うにあたって決めるべきことは目的である.コードリーディングを行う目的はさまざまである.例えば

  • ある処理の情報の流れを掴みたい
  • システムのある部分の挙動を理解したい
  • ある処理の実装にあたって良いクラス設計を理解したい

などが考えられる.目的を明確にしておかないと,中途半端に終えてしまったり,終わりがわからず時間をかけてしまい結局何が知りたかったのかわからなくなったり,必要のない部分をずるずると追ってしまったりする.
ここで 3 つ目の目的は挙動や情報の流れを理解していることが前提だったりするので (場合によっては該当部分だけを参考にして実装に取り掛かることもできる) 本記事では考慮しない.
 
まず 1 つ目の目的に対するコードリーディングを考えていく.(2 つ目の目的に対するコードリーディングは別途記事で説明予定)
これは情報の流れを理解できればいいので,各関数やクラスが何を行っているのかを理解する必要がない.そのため基本的にはある関数の最後の処理を見ることで情報の流れを追うことができる.
 
ここでは Django において runserver の実行の流れを追うという目的を持っていたとしよう.
その流れの中で以下のように handle 関数があったとしよう.

    def handle(self, *args, **options):
        if not settings.DEBUG and not settings.ALLOWED_HOSTS:
            raise CommandError("You must set settings.ALLOWED_HOSTS if DEBUG is False.")

        self.use_ipv6 = options["use_ipv6"]
        if self.use_ipv6 and not socket.has_ipv6:
            raise CommandError("Your Python does not support IPv6.")
        self._raw_ipv6 = False
        if not options["addrport"]:
            self.addr = ""
            self.port = self.default_port
        else:
            m = re.match(naiveip_re, options["addrport"])
            if m is None:
                raise CommandError(
                    '"%s" is not a valid port number '
                    "or address:port pair." % options["addrport"]
                )
            self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups()
            if not self.port.isdigit():
                raise CommandError("%r is not a valid port number." % self.port)
            if self.addr:
                if _ipv6:
                    self.addr = self.addr[1:-1]
                    self.use_ipv6 = True
                    self._raw_ipv6 = True
                elif self.use_ipv6 and not _fqdn:
                    raise CommandError('"%s" is not a valid IPv6 address.' % self.addr)
        if not self.addr:
            self.addr = self.default_addr_ipv6 if self.use_ipv6 else self.default_addr
            self._raw_ipv6 = self.use_ipv6
        self.run(**options)

これを見たらコードの量に圧倒されて (これでも Django のプロジェクトのごく一部だが) コードリーディングをやめたくなってしまうかもしれない.しかし前述したように情報の流れを追うという目的であれば最後だけを見れば良い.よって今回の場合,self.run を最後に実行しているので同じクラスの run 関数を追えば良いということになる.
この handle 関数はわかりやすかったが,実行している関数が最後にない場合や実行している関数が複数ある場合は単純に追うことができない.その場合どの関数がメインの関数なのかを見極める必要がある.

Django の Call Graph

ここまで述べたポイントを念頭に置いて,runserver の実行の際の情報の流れを追ったものを以下に示す.(これを Call Graph と呼ぶ)

runserver.py を実行するまで

response を返すまで

serve_forever から response を返すまで (詳細)

最後に

以下に経験則だがコードリーディングを行う際に注意すべきポイントを載せておこう.
(随時気づいたものから追加する予定)

  • 関数の最後の処理を確認する
     - 別の関数を実行している場合はその関数を追う
     - return している場合は return するものが定義されているところまで逆順に調べる
  • 追っている関数が求めているクラス内にない場合,クラスを継承している可能性が高いので元のクラスまでたどる
  • 関数名から処理を予想する
  • エラー文を追う必要はない (大きなプロジェクトだとあらゆる部分にエラー文が存在するが,メインの流れに影響を与えるものではない)

参考文献

[1] django, https://github.com/django/django

Discussion