この記事は東京海洋大学NePPの Advent Calendar 2025の11日目です
はじめに
サークルで今からプログラミングを始めた人向けに、SOLID原則の話をしたいなと思ったのですが、
いざ「わかりやすく説明しよう」とすると、これが思った以上に難しいことに気づきました
よく考えたら、自分自身もちゃんと説明できるほど理解しているか?と言われると怪しい部分があるなと
なので今回は、始めたての人にも伝わるようにかなり噛み砕いた説明をするのと同時に、
自分の中で考えを整理するという目的も込めて書いていこうかなと思います
今回は SOLID 原則の中でも S について
SOLID というのは、以下の 5 つの原則の頭文字を取ったものです
- S: Single Responsibility Principle(単一責任の原則)
- O: Open-Closed Principle
- L: Liskov Substitution Principle
- I: Interface Segregation Principle
- D: Dependency Inversion Principle
この中の S(単一責任の原則) は、よくこんな感じで説明されます
クラスは、単一の責任を持つべきである
……と言われましても、正直、プログラミングを始めたばかりだとよくわからないんじゃないかなと思います
責任って何? 単一ってどこまで? クラスってそもそも何? みたいな
なので今回は、
「単一責任の原則とは何か」を正面から定義するというよりも、
それを守らないと何がつらいのか、
守ると何が楽になるのか、
その感覚をコード例から掴んでもらえたらいいなと思っています
個人的な解釈を雑に言うと、「コードを役割ごとに分けよう」って話だと思っています
じゃあ、なぜ分けた方がいいのか? それを極限まで単純化した例で見ていきます!
とりあえず動くけど、後からつらくなるコード
次のような要件を満たすコードを書きたいとします
- 引数でテキストを受け取る
- それをログとして出力する
-
!が含まれていたら通知する - ついでにファイルにも書き込む
深く考えずに、とりあえず動くものを書こうとすると、たぶんこんなコードになると思います
def log_and_notify(msg: str) -> None:
# ファイルを開く
log_file = open("log.txt", "a", encoding="utf-8")
# コンソールに出力
print("SEND:", msg)
# 条件によって通知
if "!" in msg:
print("ALERT:", msg)
# ファイルに書き込み
log_file.write(msg + "\n")
log_and_notify("hello!")
一見すると、ちゃんと動きそうです
実際、このコード自体が間違っているわけではなさそうです
ただ、このコード、後から読むと結構つらいものがあるんじゃないかと...
例えば数ヶ月後、ログ出力をファイルじゃなくて Slack に送りたい!
みたいな要望が来たとしましょう
そのとき、
ファイルに書き込んでる処理ってどこだっけ?
通知ってどこでやってたっけ?
みたいな感じで、関数の中を最初から最後まで読み直して探す必要が出てきます
今回は短いコードなのでまだよくて、許容範囲と言えば許容範囲ですが、
これが 100 行、200 行になっていたらどうでしょうか?
修正するたびに、毎回全部読む羽目になります!!!!!
正直、そんなことやりたくないですよね
なので、それぞれの処理の役割をはっきり分けておこう!という発想が出てきます
役割ごとに分けて書いてみる
さっきのコードを処理の役割ごとに分けることを意識して書くと、こんな書き方になるんじゃないでしょうか
from file_log_open import open_logfile
from console_logger import print_log
from alert_notifier import notify_if_alert
from file_log_writer import persist_log
def process_message(msg: str) -> None:
# 1. ファイルを開く
# 2. ログを出力する
# 3. 必要なら通知する
# 4. ファイルに保存する
log_file = open_logfile()
print_log(msg)
notify_if_alert(msg)
persist_log(msg, log_file)
process_message("hello!")
肝心の中身を書いてないじゃないか!と思うかもしれませんが、
具体的な処理は、それぞれ別の場所に書けばいいんです
ここでは何をしているかだけがわかれば十分だからです
細かい処理は、インポート先のファイルに書いてあるというわけです
open_logfile()
from pathlib import Path
from typing import TextIO
def open_logfile() -> TextIO:
return Path("log.txt").open("a", encoding="utf-8")
print_log()
def print_log(msg: str) -> None:
print("SEND:", msg)
notify_if_alert()
def notify_if_alert(msg: str) -> None:
if "!" in msg:
print("ALERT:", msg)
persist_log()
from typing import TextIO
def persist_log(msg: str, log_file: TextIO) -> None:
log_file.write(msg + "\n")
log_file.flush()
例えば、
! じゃなくて ? が含まれていたら通知したい、となった場合、
notify_if_alert() だけ読んで修正すればいいし、
コンソールじゃなくて Slack に出したい、となった場合も、
print_log() の中身だけ見れば修正できるわけです!
修正したい場所が後からでもすぐにわかって、関係ないコードを読まなくて済む
これが大きいんです!
まとめ
どこが SOLID 原則の話やねん!という感じかもしれませんが、
この 役割ごとに分けて考える という発想そのものが、
SOLID 原則の S(単一責任の原則) に繋がっています
今回は関数単位の話でしたが、SOLID 原則ではこれをクラス単位でやりましょう、という話になります
まずは、1つのものに、いろんな役割を持たせすぎないということですね
この感覚がわかるとSOLID原則のSの話がスッと入ってくるんじゃないかな〜と思います