
【基礎】長い関数の分割とは、長さを短くするという意味ではない
30年プログラマーのムンペイです。
今どきは、静的解析ツールやフォーマッタ、ベストプラクティスの研究などにより、より良いプログラミングをするための知識・常識が、コンセンサスを以て共有される時代になったと思います。
しかし、やはり表面的な理解では実践に反映されていないケースも見られます。今回は、長い関数を分割するというのは、長さを短くするという意味ではない、ということについて考えを述べてみます。
はじめに
長い関数を分割せよ、というアドバイスはプログラミングのベストプラクティスとしてよく知られています。しかし、単に関数の「長さ」を短くするだけの分割は、しばしば逆効果になります。
長い関数を安易に分割すると、呼び出し階層が深くなり、プログラムの複雑性が増す上、パフォーマンスにも悪影響を与える可能性があります。本記事では、正しい分割の方法と、それによって得られるメリットについて解説します。
長い関数を分割する理由と注意点
関数の分割は、コードの可読性を向上させ、メンテナンスを容易にするために行われます。しかし、ただ行数を短くすることだけを目的に分割を行うと、以下のような問題が発生します。
呼び出しの階層化:分割した関数が互いに連鎖的に依存し、階層が深くなってしまう。これにより、コードの追跡が難しくなり、デバッグが煩雑化します。
引数のバケツリレー:階層的な呼び出しの結果、本来不要な関数にまで引数が渡される状況が発生し、責任範囲が曖昧になります。これは関数再利用性を損なう大きな要因です。
こうした問題を避けるためには、分割の目的を明確にし、適切な設計に基づいて分割する必要があります。
悪い例:単純な長さ分割
たとえば、150行の長い関数があったとします。
def long_function(a, b, c):
# 150行の長い処理・・・
return result
これを50行以上は次の関数に分ける、というルールで分割すると次のようになります。
def long_function(a, b, c):
# 最初の50行分の処理・・・
x = (↑で計算した中間結果)
return part1(b, c, x)
def part1(b, c, x):
# long_functionの続きの50行分の処理・・・
y = (↑で計算した中間結果)
return part2(c, y)
def part2(c, y):
# part1の続きの50行分の処理・・・
z = (↑で計算した中間結果)
return z
このように3段階の呼び出しが発生する構造は、次のような欠点を持っています。
引数の盲腸化: たとえば、cという引数はpart2だけが使うデータなのに、part1が「中継」しなければなりません。この盲腸のようなデータはpart1の処理に無関係ですから、コードを見た人に混乱を与え、デバッグも困難にします。
責任範囲の曖昧さ:引数のことと表裏ですが、各関数の役割が不明確になります。分割が簡潔さではなく、複雑さをもたらしてしまっています。
再利用性の低さ:連続して呼び出さないと処理が成立しないため、結局 long_function を呼び出す以外の使い方ができません。部分に分割したにもかかわらず、モジュールとしての機能は獲得できていません。
良い例:処理単位での分割
一方で、適切な分割は、処理内容ごとに関数を分けることを目指します。以下に改善例を示します:
def main_function(a, b, c):
x = preprocess_data(a)
y = calculate_results(b, x)
z = generate_report(c, y)
return z
def preprocess_data(a):
# データの前処理
return x
def calculate_results(b, x):
#
return y
def generate_report(c, y):
# レポートの生成
return z
この構造には以下の利点があります:
役割が明確:処理の意味を考えて各関数に分割したため、責任範囲が明確です。
再利用性の向上:各関数が独立しており、別の場所でも容易に再利用できます。generate_report は、 part2 と引き数は同じですが、処理を意味単位で分けているため、再利用性があります。
引数の簡潔さ:各関数には必要な引数だけが渡されています。無関係なデータを中継する必要がありません。
まとめ
長い関数を分割する際、重要なのは「行数を短くする」ことではなく、「処理を意味のある単位で分ける」ことです。ルート関数は分割された処理を組み合わせる「司令塔」の役割を果たし、それぞれの処理が独立して動作する構造を目指しましょう。
長い関数を分割することは、単なる形式的な作業ではなく、プログラム全体の設計と可読性に直結する重要な技術です。次にコードを書くときは、ぜひ「分割の意義」を意識してみてください。
これにて御免!
いいなと思ったら応援しよう!
