「責務定義」って何?一言でいうと「この子は何を担当するのかを決めること」
責務定義は、クラス・関数・モジュールなどに対して「あなたは何を担当するのか?」をはっきり決めることです。
もっと砕くと、「このコードは何をする“係”なのか」を決める作業です。
ここが曖昧だと、何でもかんでも一つの関数やクラスに押し込んでしまい、結果として「何をしているのか分からない」「ちょっと直すと別のところが壊れる」という状態になります。
逆に、責務がはっきりしていると、「どこを直せばいいか」「どこをテストすればいいか」が一瞬で分かるようになります。
責務定義は、設計・テスト・品質の土台です。
ここがふわっとしていると、どれだけテストを書いても、どれだけツールを入れても、根本的な苦しさは消えません。
まずは「責務がごちゃ混ぜ」の悪い例を見てみる
なんでもやる「神関数」の例
次のようなコードを想像してください。
import json
from pathlib import Path
from dataclasses import dataclass
DATA_FILE = Path("users.json")
@dataclass
class User:
id: int
name: str
email: str
def handle_user():
# ファイルから読み込み
if DATA_FILE.exists():
data = json.loads(DATA_FILE.read_text(encoding="utf-8"))
users = [User(**item) for item in data]
else:
users = []
# ユーザー追加(ここでは適当に)
name = input("name: ")
email = input("email: ")
new_id = (max((u.id for u in users), default=0) + 1)
users.append(User(id=new_id, name=name, email=email))
# バリデーション
if "@" not in email:
print("invalid email")
return
# 保存
data = [u.__dict__ for u in users]
DATA_FILE.write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
# 表示
print("registered:")
for u in users:
print(f"{u.id}: {u.name} <{u.email}>")
Pythonこの handle_user 関数は、何をしているでしょうか。
ファイルから読み込む、ユーザー入力を受け取る、IDを採番する、バリデーションする、保存する、一覧表示する。
つまり、「全部やっている」状態です。
これが「責務が定義されていない」典型例です。
この関数をテストしようとすると、ファイルI/Oも、入力も、表示も全部巻き込むことになり、途端にしんどくなります。
責務を分けて定義するとどう変わるか
役割ごとに「誰が何を担当するか」を決める
さっきのコードを、「誰が何を担当するか」を意識して分けてみます。
ここでは、ざっくり次の責務に分けてみます。
ユーザーというデータとルールを表す
ユーザーの保存・読み込みを担当する
ユーザー登録というユースケースを担当する
入出力(CLI)を担当する
これをコードに落とすと、こうなります。
# domain.py
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str
def validate(self) -> None:
if "@" not in self.email:
raise ValueError("invalid email")
Python# repository.py
import json
from pathlib import Path
from typing import Protocol
from domain import User
class UserRepository(Protocol):
def load(self) -> list[User]:
...
def save(self, users: list[User]) -> None:
...
class JsonUserRepository:
def __init__(self, path: Path) -> None:
self._path = path
def load(self) -> list[User]:
if not self._path.exists():
return []
data = json.loads(self._path.read_text(encoding="utf-8"))
return [User(**item) for item in data]
def save(self, users: list[User]) -> None:
data = [u.__dict__ for u in users]
self._path.write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
Python# usecase.py
from dataclasses import dataclass
from domain import User
from repository import UserRepository
@dataclass
class RegisterUserInput:
name: str
email: str
def register_user(repo: UserRepository, data: RegisterUserInput) -> list[User]:
users = repo.load()
new_id = (max((u.id for u in users), default=0) + 1)
user = User(id=new_id, name=data.name, email=data.email)
user.validate()
users.append(user)
repo.save(users)
return users
Python# cli.py
from pathlib import Path
from repository import JsonUserRepository
from usecase import register_user, RegisterUserInput
def main() -> None:
repo = JsonUserRepository(Path("users.json"))
name = input("name: ")
email = input("email: ")
try:
users = register_user(repo, RegisterUserInput(name=name, email=email))
except ValueError as e:
print(e)
return
print("registered:")
for u in users:
print(f"{u.id}: {u.name} <{u.email}>")
if __name__ == "__main__":
main()
Pythonここで、それぞれの責務を言葉にすると、こうなります。
User は「ユーザーとは何か」と「ユーザーのルール(バリデーション)」を担当する。JsonUserRepository は「ユーザーをJSONファイルに保存・読み込みすること」を担当する。register_user は「ユーザーを登録するユースケース(流れ)」を担当する。main は「CLIとしての入出力」を担当する。
これが「責務定義」です。
「このクラス(関数・モジュール)は何の係か?」を、はっきり言葉にできる状態です。
責務定義がテストと品質にどう効いてくるか
責務が小さくはっきりしていると、テスト対象も小さくはっきりする
さっきの分割後のコードをテストすることを考えてみます。
例えば、「ユーザー登録のユースケースだけ」をテストしたいとき、CLIもファイルI/Oも巻き込む必要はありません。
テスト用のリポジトリを用意して、register_user だけをテストできます。
from usecase import register_user, RegisterUserInput
from repository import UserRepository
from domain import User
class InMemoryUserRepository(UserRepository):
def __init__(self) -> None:
self.users: list[User] = []
def load(self) -> list[User]:
return list(self.users)
def save(self, users: list[User]) -> None:
self.users = list(users)
def test_register_user_success():
repo = InMemoryUserRepository()
input_data = RegisterUserInput(name="Taro", email="taro@example.com")
users = register_user(repo, input_data)
assert len(users) == 1
assert users[0].email == "taro@example.com"
Pythonこれは、「責務が分かれているからこそ」できるテストです。register_user の責務が「ユーザー登録の流れ」だけに絞られているので、そこだけを切り出してテストできます。
もし責務がごちゃ混ぜのままだと、
ファイルI/Oや入力まで含めた巨大なテストを書くか、
そもそもテストを書くのを諦めるか、どちらかになりがちです。
責務が明確だと、バグの「居場所」が狭くなる
例えば、「メールアドレスのバリデーションがおかしい」というバグが見つかったとします。
責務が曖昧なコードだと、「どこにバリデーションが書いてあるのか」を探すところから始まります。
あちこちに if "@" not in email: が散らばっているかもしれません。
責務が定義されているコードなら、「ユーザーのルールは User にある」と分かっています。
つまり、User.validate を見ればいい、とすぐに分かります。
これは、デバッグの速さ=品質の維持コストに直結します。
責務定義は、「バグの居場所を狭くする」ための武器でもあります。
責務定義の核心:「一つのものに“理由の違う変更”を詰め込まない」
責務定義の考え方の中で、特に重要なのが「単一責務の原則(SRP)」です。
これは、「一つのクラス(モジュール・関数)は、一つの理由でしか変更されないべき」という考え方です。
もう少し噛み砕くと、「違う理由の変更を同じ場所に詰め込むな」ということです。
例えば、さっきの handle_user は、次のような理由で変更される可能性があります。
ファイル形式を変えたい(JSON → CSV)
入力方法を変えたい(CLI → Web)
バリデーションルールを変えたい(メールのチェックを厳しくする)
表示形式を変えたい
これらが全部一つの関数に詰まっていると、
どの変更をしても handle_user を触ることになります。
そのたびに、他の部分を壊すリスクが生まれます。
責務定義をして分割すると、こうなります。
ファイル形式を変えたい → JsonUserRepository を触る
入力方法を変えたい → main(CLI)を触る
バリデーションルールを変えたい → User.validate を触る
表示形式を変えたい → main の表示部分を触る
「変更の理由」が違うものは、違う場所に分かれている。
これが単一責務の原則であり、責務定義の核心です。
初心者が責務定義を練習するときの具体的な視点
「この関数(クラス)は何担当?」と日本語で言えるか試す
コードを書いたあとに、自分にこう問いかけてみてください。
「この関数は、何の係?」
「このクラスは、何を担当している?」
もし一言で言えないなら、そのコードは責務が混ざっている可能性が高いです。
例えば、
「ユーザー登録の流れを担当している」
「ユーザーの保存・読み込みを担当している」
「CLIの入出力を担当している」
のように言えれば、かなり良い状態です。
逆に、
「まあ、いろいろやってる」
となるなら、そこは分割候補です。
「テストを書くとしたら、何をモックにしたいか」を考える
テストのことを考えると、責務の境界が見えやすくなります。
例えば、「ユーザー登録のロジックだけをテストしたい」と思ったとき、
ファイルI/Oや入力はモック(差し替え)したくなります。
ということは、
ユーザー登録のロジック
ファイルI/O
入力
は、それぞれ別の責務として分けるべきだ、というヒントになります。
「ここはモックにしたい」と感じるところは、
だいたい「外部との境界」であり、責務の分かれ目でもあります。
まとめ(責務定義は「誰が何を担当するかを決めて、変更とテストを楽にする技術」)
責務定義を初心者目線でまとめると、こうなります。
責務定義は、クラス・関数・モジュールに対して「あなたは何を担当するのか?」をはっきり決めることであり、「一つのものに違う理由の変更を詰め込まない」ことが核心にある。
責務が明確だと、「どこを直せばいいか」「どこをテストすればいいか」が一瞬で分かるようになり、バグの居場所が狭くなり、テストもしやすくなる。
練習としては、「このコードは何の係かを日本語で一言で言えるか」「この部分だけテストしたいとき、何をモックにしたくなるか」を意識しながら、少しずつ責務を分けていくのが現実的で、設計と品質がじわじわ良くなっていく。
