この文書はCode Health: Reduce Nesting, Reduce Complexityの翻訳です。
深くネストしたコードは可読性を損ないエラーの温床になる。次の2つのコードを比較し、バグを指摘せよ:
深くネストしたコード
response = server.Call(request)
if response.GetStatus() == RPC.OK:
if response.GetAuthorizedUser():
if response.GetEnc() == 'utf-8':
if response.GetRows():
vals = [ParseRow(r) for r in response.GetRows()]
avg = sum(vals) / len(vals)
return avg, vals
else:
raise EmptyError()
else:
raise AuthError('unauthorized')
else:
raise ValueError('wrong encoding')
else:
raise RpcError(response.GetStatus())
ネストの少ないコード
response = server.Call(request)
if response.GetStatus() != RPC.OK:
raise RpcError(response.GetStatus())
if not response.GetAuthorizedUser():
raise ValueError('wrong encoding')
if response.GetEnc() != 'utf-8':
raise AuthError('unauthorized')
if not response.GetRows():
raise EmptyError()
vals = [ParseRow(r) for r in response.GetRows()]
avg = sum(vals) / len(vals)
return avg, vals
解答:"wrong encoding"と"unauthorized"エラーが入れ替わっている。 リファクタリングされたネストの少ないコードでは、チェックはエラー処理のすぐそばで行われているため、このバグは容易に見つかる。
上記のリファクタリングテクニックは ガード節(guard clause) という名前で知られている。ガード節は条件をチェックし、もし条件が満たされないときはすぐに失敗するというものであり、計算のロジックとエラーのロジックを分離する。エラーチェックとエラーハンドリングの間の認知的なギャップを取り除き、心理的な処理能力を開放する。結果として、 リファクタリングされたバージョンは読みやすく、メンテナンスしやすい。
コードのネストを減らすことに関するルールをいくつか挙げる:
-
条件分岐のブロックを小さく、局所性を保つことで、リーダビリティを向上する。
-
ループや分岐が2レベルよりも深くなるならリファクタリングを検討しよう。
-
ネストされたロジックを関数に分離することを考えよう。例えば、各オブジェクトがそれぞれリストであるようなオブジェクトのリスト(repeatedフィールドを持つprotocol bufferなど1)に対して繰り返し処理を行う必要があるとき、二重にネストされたループの代わりに各オブジェクトを処理する関数を定義することができる。
ネストを減らすことによって可読性の高いコードとなった。その結果、バグは見つかり、開発のサイクルは速くなり、安定性が増す。できるときには、シンプルにしよう!
-
Google社内っぽい例えですね。https://images.app.goo.gl/Y87wjVcit5ZpekUm8 ↩