「この数字、何を意味しているんだろう?」と感じたことはありませんか?
コードの中に突然あらわれる謎の数値は、読み手を迷わせ、後から自分で読み返したときにも混乱のもとになります。Python初心者がつまずきやすいポイントのひとつが、この「マジックナンバー」です。
ここでは、マジックナンバーとは何か、なぜ避けるべきなのか、そしてどう置き換えれば“読んでわかるコード”になるのかを、実例とともに丁寧に学んでいきましょう。
まず用語をしっかり押さえましょう。
マジックナンバーとは、コード内に突然出てくる、意味の説明がない数値のことです。意味がわからない数字があると、読む人は「なぜこの値?」と考え込むことになります。
たとえば次のコードを見てみましょう。
def calculate_area(radius):
return 3.14 * radius * radius
このコードでは、3.14がマジックナンバーです。円周率だとわかる人もいますが、全員がそうとは限りません。
さらに、将来より正確な値に変えたいとき、どこを直せばいいのか探す手間も増えます。
ここで、なぜマジックナンバーを排除すると良いのかを整理しておきます。背景を理解しておくと、日々のコードでも意識しやすくなります。
では、実際にどのように置き換えればよいのかを見ていきましょう。
先ほどの例を、意味のある名前を持つ定数に置き換えます。
PI = 3.14 # 円周率
def calculate_circle_area(radius):
return PI * radius * radius
ここでは、3.14にPIという名前を付けました。これで「この値は円周率なんだ」とコードが語ってくれるようになります。もし後から値をより正確にしたい場合も、PIを一箇所変えるだけで済みます。
さらに精度と信頼性を高めたいなら、Python標準ライブラリを活用するのがおすすめです。数値を自分で書かず、公式に用意された定数を使うと安心です。
import math
def calculate_circle_area(radius):
return math.pi * radius * radius
math.piは高精度な円周率を提供してくれます。「どれが正しい値か」を悩む必要がありません。
単体の関数だけでなく、複数の関数にまたがってマジックナンバーを排除すると効果を実感しやすくなります。次のプログラムは、複数の半径から円の面積を合計します。
import math
def calculate_circle_area(radius):
return math.pi * radius * radius
def calculate_total_area(radii):
total_area = 0.0
for radius in radii:
total_area += calculate_circle_area(radius)
return total_area
radii_list = [1, 2, 3, 4]
total_area = calculate_total_area(radii_list)
print("Total area:", total_area)
ここでは、円周率を関数内に書かずmath.piを使っています。もし円周率の定義を変える必要があれば、math.piを使う方針のまま保てば、あちこちの数値を探す必要はありません。プログラム全体の「変更に強い設計」につながります。
ここからは、実務や学習でそのまま役立つ実践ポイントを押さえていきましょう。どこにどう書けば読みやすくなるのか、迷ったことはありませんか。
PI、MAX_RETRY、TAX_RATE、SECONDS_PER_MINUTEのように、読むだけで意味が想像できる名前にしましょう。DEFAULT_TIMEOUT = 5.0のように書くと、他の人も「これは変えない前提の値だな」とすぐに理解できます。次のコードは、いくつかのよくある設定値を定数化した例です。どれも、「その数字が何を表すか」を名前が説明してくれています。
import math
MAX_RETRY = 3 # リトライの最大回数
DEFAULT_TIMEOUT = 5.0 # 秒
DISCOUNT_RATE = 0.10 # 10%
SECONDS_PER_MINUTE = 60 # 単位変換
def should_retry(attempt):
return attempt < MAX_RETRY
def apply_discount(price):
return price * (1 - DISCOUNT_RATE)
def to_seconds(minutes):
return minutes * SECONDS_PER_MINUTE
def circle_area(radius):
return math.pi * radius * radius
これらはどれも、ただの「3」「5.0」「0.1」「60」より、はるかに読みやすく安全です。あなたの現場や学習中のコードにも、似た“生の数字”が眠っていませんか。
数字だけが問題ではありません。実は文字列や特殊な値が、静かにバグの原因になることがあります。 たとえば、次のようなコードを見たことはありませんか。
status = "OK" # 他の場所では "SUCCESS" を使っているかも?
このような「マジックストリング」は表記ゆれが起きやすく、タイポ(タイピングミス)にも弱いです。定数にして意味を固定すると安心です。
STATUS_OK = "OK"
STATUS_ERROR = "ERROR"
def is_ok(status):
return status == STATUS_OK
文字列の種類が増えてきたら、列挙型で表現するとさらに安全になります。初心者でも次のように書けます。
from enum import Enum, auto
class Status(Enum):
OK = auto()
ERROR = auto()
def is_ok(status: Status) -> bool:
return status is Status.OK
これなら誤った文字列を渡してもエラーで気づけるようになります。
この先の学習や実務で出会いやすいケースを知っておくと、実装中に「これは定数化しよう」と気づきやすくなります。以下は実践でよく使うパターンです。
まずはパスワードの長さチェックの例です。 セキュリティ関連の処理では「最低8文字」といったルールをよく見かけますが、数値をそのまま書いてしまうと、後で仕様が変わったときに混乱のもとになります。
# 悪い例
if len(password) < 8:
raise ValueError("Too short")
# 良い例
MIN_PASSWORD_LENGTH = 8
if len(password) < MIN_PASSWORD_LENGTH:
raise ValueError("Too short")
このように、8という数字に意味を持たせてMIN_PASSWORD_LENGTHという定数に置き換えると、「8が何を表すのか」が一目でわかります。 また、要件が「10文字以上」に変わっても、定数を1か所変更するだけで済みます。
次に、一定時間待機する処理の例を見てみましょう。 リトライ処理やAPIの呼び出し間隔で、待機時間を直接書いてしまうのもよくあるマジックナンバーのパターンです。
# 悪い例
sleep(3)
# 良い例
DEFAULT_RETRY_DELAY = 3 # 秒
sleep(DEFAULT_RETRY_DELAY)
「3秒待つ」という仕様がコード上にそのまま書かれていると、読み手には意図が伝わりにくくなります。 DEFAULT_RETRY_DELAYという定数名を使えば、「これはデフォルトの待機時間なんだな」と一瞬で理解できます。
最後は、ログイン試行回数の上限の例です。 セキュリティや認証まわりのコードでは、「何回まで失敗を許すか」を定義することがよくあります。
# 悪い例
if attempts > 5:
lock_user()
# 良い例
MAX_LOGIN_ATTEMPTS = 5
if attempts > MAX_LOGIN_ATTEMPTS:
lock_user()
このように、5という数字をMAX_LOGIN_ATTEMPTSに置き換えるだけで、 「この値が“最大ログイン試行回数”を意味しているんだ」とすぐに理解できるようになります。
読み手にとってなぜこの数なのかが伝わるだけで、コードの意図がクリアになります。
最後に、毎日のコーディングでサッと見直せる観点をまとめます。読みやすさと保守性をぐっと高めるために、実装の前後で次の点を確認してみてください。
math.pi)がないか次にコードを書くとき、「この数字は他の人に意味が伝わるかな?」と自分に問いかけてみてください。マジックナンバーを排除するだけで、Pythonのコードはぐっと読みやすく、将来の変更にも強くなります。あなたのコードを、明日の自分とチームが安心して読める“ドキュメント”に育てていきましょう。