質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

設計相談

システムの設計についての相談や質問を投稿する際にご使用ください。

意見交換

クローズ

10回答

2594閲覧

データクラスがファイルへの入出力の責任を持つべきか?

yutoml

総合スコア9

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

設計相談

システムの設計についての相談や質問を投稿する際にご使用ください。

5グッド

3クリップ

投稿2024/01/06 20:31

編集2024/01/08 16:01

5

3

1.概要

データの操作を行うクラスに、例えばcsvやjson形式のファイルに対する入出力を行うメソッドを実装するべきなのか悩んでいます。

ほかの有名なパッケージではデータをファイルに保存するメソッドはクラス内で実装されています。
一方ソフトウェア工学に基づけばファイルに保存するメソッドはクラス外に実装するべきだと思われます。

どちらの実装も可能です。
将来的なクラスの改変を想定されるときどちらの実装が望ましいでしょうか?

2.背景、状況

私は仕事の効率化のために個人的に少しばかりPythonを触っているものです。
検索や並び替えなど簡単なデータの操作を行うクラスを実装しようと考えています。
現時点ではファイルからデータを読み取ることをほとんど前提としています。

想定している前提状況

  • クラス外からファイルに保存するのに必要なデータを完全に復元できるだけの情報にアクセスできる
  • ただしクラス外から得られるデータはそのままでは適切にファイルに保存できず、保存に際してクラス内での実装に依存した処理が必要になる
    • 例えばクラス初期化に際して与えたlist[float]の数列がクラス外からアクセスするとnumpy.ndarrayとして得られるなど

問題は上記の前提状況でクラス外とクラス内どちらにファイルに保存するメソッドを実装するべきかというものです。

3.調査したこと

3.1ほかのパッケージでの実装

pandasやほかの自分が利用しているパッケージを3つほど思いつく限り参考にしてみたところ、すべて入力する処理は独立した関数として分割されていますが、出力はインスタンスメソッドとして実装されていました

python

1# pandasの場合 2# ファイルからの読み込み(クラスとは独立した関数) 3dataframe : pandas.DataFrame = pandas.read_csv("data.csv") 4# ファイルへの出力(インスタンスメソッド) 5dataframe.to_csv("data.csv") 6 7# sqliteの場合 8# dbへの接続(クラスとは独立した関数) 9conn : sqlite3.Connection = sqlite3.connect("data.db") 10# dbへの書き込み(インスタンスメソッド) 11conn.commit()

3.2ソフトウェア工学的な原則

自分は最近になってSOLID原則などを知るようになった程度なので以下の考えはきちんと理解できていない人間のものだと思います。

SOLID原則における単一責任の原則に基づけば、考えているクラスはデータの改変や操作に集中するべきで、データの保存に関しては分割するべきであるように思われます。
一方でデータの操作に付随してファイルの入出力はほとんど必要な作業であるので、凝集度を上げるために一つにまとめるべきなのか判断できません。
凝集度を勘違いしていました。凝集度の視点では操作と保存が分割することは欠点にはならないと理解しました。

参考1でもデータを取り扱うクラスと保存するクラスで分割しているようです。

参考 :

4.自分なりの考え

ファイルからインスタンスを作成する際(入力時)にはクラスのインターフェースに従って入力すればよいので分割するのは簡単ですが、出力する側も分割する場合はクラスの内部につよく依存した形に実装されるように思われます。
例えば数列をクラスの中ではNumpy.ndarrayで保存していた場合、これらをjson形式で保存する関数は数列の部分をlist形式にキャストして保存する必要があります。これはクラス外の実装がクラス内の実装に強く依存しており結合度の高い実装になってしまうように思われます。
したがって自分が実装するならクラスの内部にファイルの入出力にかかわるメソッドを実装します。

先に述べた通り、自分は最近になってSOLID原則などを知るようになった者で上記の自分の考えが正しいのか全く自身がありません。
周りには質問できるようなひとがいないためこちらで質問させていただきます。皆さんのご意見を伺いたいです。

将来的なクラスの改変も予想されるときに

  1. クラス外にファイルへの入出力のメソッドを実装する
  2. クラス内にファイルへの入出力のメソッドを実装する

どちらがより理想的で望ましい実装方法でしょうか?

よろしくお願いいたします。

6.最後に

またteratailでの質問はこれが初めてなので質問場所が適切でなかった場合や、タグ付けが正しくないなどあるかもしれません。
その際はご指摘の上、ご容赦頂ければ幸いです。

ttb, YufanLou, ikedas, yasu6, ams2020👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答11

#1

fana

総合スコア11954

投稿2024/01/08 04:46

編集2024/01/08 05:15

「データ」というのが実際何なのかわかりませんが,

データの操作を行うクラス

の外側から,「データ」を 参照/取得 する手段というのは存在しないのですか?

非常に簡単な例で言えば,データというのが「N個の数値」である場合,そのN個の数値をそのクラスがどういうデータ構造なりで扱っているかはともかく,「i番目の数値をください」という外側からの要求に答える手段さえ設けてあるならば,データのファイルへの保存というのは外側で実施できますよね.

逆に言えば,「全データを完全に復元できるだけの情報を外側は得られない」という物なのであれば,外側ではファイルへの保存はやれないわけで,そしたら「データの操作を行うクラス」に出力メソッドを持たせることになるのでは.


あと,これは単なる雑魚の戯言ですが……
「なんとか原則!」とかいうやたらと強そうな(?)言葉に過度に振り回されるのではなく,
「目の前の仕事では何が必要なことなのか?」で物事を判断するのではダメなんですかね?

例えば,
【出力ファイル形式が絶対的に1つに定まっていて,且つ,データもそのクラスしか保有しないと決めているような場合】であれば,
そのクラスに出力メソッドを持たせたら何か困るんですか? とか,
そのクラスの外側でファイル保存するためには何らかの追加の労力を割くことになるとして,そこに意味はあるのか? とか.

なんだろう,
あなたの目の前にある実際の仕事において,データクラスがファイルへの入出力の責任を持つべきか?」という命題を考えているのか,
それとも単に「(漠然とした)なんとか原則みたいな話」をしたいだけなのか,みたいな.

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

yutoml

総合スコア9

投稿2024/01/08 16:04

#1

回答ありがとうございます。大変正しいご意見だと思います。
また大変に説明が足りておりませんでした。追記いたしました。

自分の想定しているものは「クラス外からアクセスして、そのデータを完全に復元できるだけの情報を得ることができる」というものです。
したがってクラス外にも内にも実装自体は可能です。

また後半のご意見に関しては間違いなく現実的かつ実用的にはクラス内に実装するのが正しいと思っております。
そのほうが話が簡単だし、実装も早いからです。

ただこうした手っ取り早い実装を行った結果後になってとてもめんどくさいことになるというのをたびたび経験してきたので目の前の仕事で必要なことだけで判断したいとは思いません。

私は将来的なめんどくささが回避できるならばこうした原則に従いたいと思いますが、まだご利益を感じたことはありません。

こういうのに振り回されるのはあまりメリットはない感じでしょうか?

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#3

fana

総合スコア11954

投稿2024/01/09 01:17

編集2024/01/09 01:59

例えばクラス初期化に際して与えたlist[float]の数列がクラス外からアクセスするとnumpy.ndarrayとして得られるなど

ここが良くわからないです.

(私は python 使いではないので,「numpy.ndarray」というのがどれだけ特殊な型(?)なのか知らないのですが)

  • 「numpy.ndarrayとして得られる」という仕様であるならば,それはすなわち「そのクラスの内側でも外側でも numpy.ndarray という型(?)については知っている」という前提の話となるのではないでしょうか.
    (であれば「クラス内の実装に依存」しているとは言えないと思う.内外両者が共通で使える型で情報をやりとりしている,というだけかと)
  • numpy.ndarray というのを使うコードをそのクラス内だけに留めたい,という話なのであれば,クラスの外側にデータを渡す際には何か違う型に変換して渡せばよい.(例えば,初期化時に使った型に変換するのではダメなのか?)

こういうのに振り回されるのはあまりメリットはない感じでしょうか?

  • 「なんかこういう話があったから → (実際の必要性とは関係なく)そうすることが must だという思考になる → でも実際そうしようとすると色々と困ったことが…」みたいな状態であれば,それは「振り回される」と呼ぶかな,と.
  • 「こうすればこういうメリットがあるから(且つそのメリットが必要だということが見えているから) → そうする」のであれば,それは「振り回される」とは言わないかな,と.
    例えば,現時点で複数種類のフォーマットでの出力が必要だということが明確になっていたり,これから増える可能性がありそうだと想定しているならば,ファイル出力処理をクラスの外側に用意することにメリットがあるかもしれない.

現実に即して「なんとか原則」みたいな話が参考になるならば参考にするのであって,「原則!」とかいう話に則ることを目的にするのは違うんじゃないかな感,みたいな.

  • 「(A)みたいなコードだと,(B)な場合に,(C)な状態になってしまうぞ!」みたいな話があったとして
    → 現実的に(B)な状況が起こり得ない(と想定できる)のであれば,(A)みたいなコードでも問題が無いと言えるかもしれない.
  • 「(D)みたいな形にしとけば,(E)みたいなことも容易だよね!」とかいう話があったとして
    → 実際に(E)みたいなことをやる予定も必要性も無いのであれば,(D)な形にしとくことに有用性は無く,ただただ労力をかけたり直感的でないコードになるだけかもしれない.それはデメリットである.

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#4

fana

総合スコア11954

投稿2024/01/09 01:35

編集2024/01/09 01:48

将来的なクラスの改変も予想されるとき

個人的な感覚ですが,
現時点で予想できている あり得ると思われる改変 について考えたときに
改変時にクラスのインタフェースを変えずに済む方向を選ぶ(好む?)かな,と思います.

あと,とりあえず今は内側に用意するけれども【「将来外に出すかも」というのをある程度想定した形で書いとくか】みたいな形もあり得るかも.
コードの引っ越しがある程度容易な形にしとく,みたいな.
例えば,クラス内のメソッドというのはメソッド内でクラスのメンバを好き勝手に唐突にいじれる存在だけども,そういうコードは引っ越し困難になるから,直にメンバをいじくらずに引数と戻り値だけでやる形(pythonにそういうのがあるか知らないけど,他言語で言えば private で static なメソッドとか)にしとく等?

後になってとてもめんどくさいことになる

を 相応に/それなりに 回避できてさえいれば,とりあえずOKみたいな.
(「ちょっと」めんどくさい みたいな程度に留めておけるならばまずは許容範囲かもしれない)

タグ付け

お使いの言語に即した話が欲しいような場合であれば python 等を付けても良いのかもしれません.
そうすれば python に詳しいユーザの目に留まりやすくなると思うので.
(逆に「これは言語固有の話ではないからどうの」みたいな反対意見が出てきたりするのかもしれませんが)

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#5

tezcello

総合スコア326

投稿2024/01/10 01:29

データの操作を行うクラスに、例えばcsvやjson形式のファイルに対する入出力を行うメソッドを実装するべきなのか

「データ操作クラス」は、データがどこから来て、どこへ行くのかを知っている必要は無く、与えられたデータに対して必要な操作をして結果を返す事が出来れば十分だと思います。
なので、「データ操作クラス」に対して外部からデータをやり取りできれば良いので、内部にファイル操作のメソッドを持つ必要はないと思います。

入力用クラスからデータを取得
データ操作クラスでデータ処理
出力用クラスで処理結果を出力
という一連の流れを、何らかの変数を介して渡すのでも、メソッドチェーン的にやるのでもいいでしょう。
データ操作クラスの初期化時に入力用クラスのインスタンスを渡し、データ操作クラスの結果出力メソッドには出力用クラスのインスタンスを渡すってのもアリかと。

「指定されたCSVファイルからデータを受け取り、指定したJSONファイルへ結果を収める、データ操作クラス」であるなら、内部に相応のメソッドが必要なのは明らかでしょう。

将来的なクラスの改変を想定されるときどちらの実装が望ましい

「改変」は、入出力の対象が増えるという事ですよね?
予定されているなら、追加だけで済むような(=本体の改造なしの方向)の実装をすべきでしょう。
「するかもしれない」程度なら、どちらでも好きな方法を採れば良いのでは?
改変が無ければ内部メソッドでも全く問題ないし、「やらなければならない」時にリファクタリングするだけです。

手っ取り早い実装を行った結果後になってとてもめんどくさいことになるというのをたびたび経験してきた

「手っ取り早い実装」に手抜きがあったり、配慮漏れがあったり、ってのが原因だったりしませんか?
当時の状況に対して適切な実装でも、状況が変われば適切ではなくなるのは当然です。
「めんどくさいこと」ではなく、必要な事なので粛々とリファクタリングするだけです。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#6

yutoml

総合スコア9

投稿2024/01/10 03:34

#3#4#5
たくさんのご意見ありがとうございます。
私としては開発に際して原理原則に則るかそうでないかはメリットデメリット次第というご意見がいただけて大変勉強になります。
繰り返し述べていますが、このような話を知ったばかりで用法容量がよくわかっていないのです。本当にありがとうございます。

後になってとてもめんどくさいことになる

を 相応に/それなりに 回避できてさえいれば,とりあえずOKみたいな.
(「ちょっと」めんどくさい みたいな程度に留めておけるならばまずは許容範囲かもしれない)

当時の状況に対して適切な実装でも、状況が変われば適切ではなくなるのは当然です。
「めんどくさいこと」ではなく、必要な事なので粛々とリファクタリングするだけです

ひとまず目の前の問題に対してあるかもわからない将来的な苦労のために無駄な労力を払うことは避けるという方針で行こうかと思います。(クローズを能動的に行えなさそうですが、個人的には解決です。)

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#7

ttb

総合スコア74

投稿2024/01/10 09:41

手っ取り早い実装を行った結果後になってとてもめんどくさいことになるというのをたびたび経験してきた

仰りたい事はなんとなく分かりますよ。
どこまで手っ取り早く実装するか、どこまで機能を分割するかはケースバイケース過ぎて、皆さんそこが分からないから答えようがないというな、鶏が先か卵が先かというようなやりとりはよく見られます・・・。
私なりの判断基準でいくと、SOLID原則を頭において、出来るだけ、こうあるべきだという全体像を描きます。
その上で、分離して実装するコストと、結合して実装するコストや結合して実装した際のデメリット(安直に実装も程度があり、ここまでこうやると、こういう変更が来たら対応が絶望的/変更が来てもなんとかなる、等)を検討します。
各コストやデメリット、目の前の案件に確保出来るコスト(かけられる時間を含む)を比較して、コスト最小になりそうな所に腹を決めます。
プロジェクトが小規模なら、脳内比較程度で行う。大規模ならドキュメントにして何人かですり合わせ、等となるでしょうか。そういう体制が出来ていないなら、それを作る所からかも。

安直な例: データ操作クラス内で、とにかく出力できれば良いという発想で実装。変更は全体を見直す必要があり絶望的に手間がかかる。
安直だけどマシな例: データ操作クラス内の各種関数内では触らないデータ出力専用の変数を用意し、出力直前でまとめてそこに代入してから出力する。変更は、この変数から直接出力するのではなく別の所に渡す所を作るだけでよいので、比較的軽微。

参考になれば幸いです。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#8

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2024/01/11 12:13

正直まずはそんな原則を気にする暇があったら書いてみれば?と言いたいですが、構造を持つデータをファイルにするにはシリアライズが必要になります。

UMLで考えてみる

この機能を質問者さんが考える構造でかつシリアライズをバイト列にするという意味合いで書くならこんな感じになるかもしれません。

イメージ説明

しかしgetterとかいちいち入れると公開したくない属性があるかもしれません。そこで以下のように変えます。

イメージ説明

データオブジェクトに内包されましたが、スッキリしたかと思います。ただバイト列を返すインターフェイスだけだとファイルにした際のバッファリングとか冗長になってしまうので、さらにこう変えます。

イメージ説明

これでなんとなくイメージのわく形になりました。実際にはバイト列ではなくstrを引数に取ると思いますが、これならファイルオブジェクトでもなんとなく同じ形に出来そうです。interfaceは形式的に書いてるだけなのでなくてもよく、そうするとデータクラスがシリアライズ機能を持っていて別にいい感じになりますね。

再考する

シリアライズするにはそのオブジェクトを詳細に知っている誰かが必要であり、リフレクション相当の機能を使わない限り、そのオブジェクト自身がヒントなり何かしらの機能を担うのが普通かと思います。pythonでシリアライズと言えば標準ではpickleモジュールです。他と共有する目的なのであればフォーマットも規定が必要で、jsonならjsonモジュール、csvにもモジュールがあったはずです。実際のライブラリやアプリケーションではパフォーマンスや諸々の理由により、それらを使っているとは限りません。

現実のコードでは

pythonで参考になる資料へのリンクだけ置いておきます。
https://realpython.com/python-serialize-data/

SOLID原則について

単一責任原則は1つのクラスに対する役割は1つであるべき(should)というだけで、それほど強いものではありません。またシリアライズする主体が別にあるなら、ヒント情報や一部の機能くらいはデータクラス自体が提供するし、ユーザーからそのシリアライズする主体は見えないので、メソッドが含まれる程度で原則が破綻していることにはなりません(そうでないとfacadeのようなクラスは破綻してることになる)。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#9

YokemuraTakeshi

総合スコア297

投稿2024/01/14 04:05

自分は単純に「ユニットテストを書けるかどうか」で決めちゃえばいいかと思ってます。

一般的にはファイル操作等I/Oが入ってくるとテストが書きにくいので、それを外出しして残った純粋ロジックに対してテストを書く。I/Oは入出力に専念させてロジックは持たせない。

これに限らず、どんなテストを書くか?を基準に責務を考えると考えやすいことが多いです。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#10

YufanLou

総合スコア464

投稿2024/01/22 09:26

「データクラスがファイルへの入出力の責任を持つべきか?」
「持つべき」まではないと思います。つまり状況によります。

この問題を考えるのに、「データ」「クラス」「ファイル」と「責任」はそれぞれ何なのかを理解しなければならないです。
しかし、どれも抽象的でぼやけてる概念ですから、僕はより具体的に「どのツールを使いたいか」から考えがちです。

僕の経験上は、データの考え方から、ツールをこの3種類に分けるのが多い:

  • テーブル型:いわゆるデータベースです。問題中の CSV numpy pandas もこれです。データを素早く選別(フィルター)できる。伝統深い。
  • ツリー型:MongoDB、JSONはこちら。一般的にオブジェクト指向のクラスもこちら(JavaやRuby)。自然に大まかから細かいまでの階層を表せる。直感的。
  • 図型:Neo4j。関連に沿っていろんなデータを素早く釣り出せる。よく見かけないですが、肝心の最適化問題の解決を探るところここに辿り着きがち。

それぞれ、ツールやデータの考え方も違えれば、クラスやファイルフォーマットも違う。だけど、プロトタイプや軽いアプリなら、ズバリどれでも満足できると思います。SOLIDの他、YAGNIという原則もあって、「You Ain't Gonna Need It」つまり「たぶんいらん」って言うのです。

だけど、違う考え方を一緒に使うと変換しなきゃいけなくなる。たとえば、クラスとSQLを一緒に使うと、ORMやDAOを通して変換をする必要がある。そこから「データクラスに入出力の責任がある」という傾向がきたかと思います。だからその変換がないMongoDBが流行ったと思います。(逆に、もうpandasを使ってるのなら、JSONとクラスを捨てて、DataFrame を中心にしてやってはいかがでしょう?)

では、要るのだとしたら、どうやって「責任」を分けるべきでしょうか?それは一人で考えても無駄なことだと思います。なぜなら、その「責任」はクラス自身が持つじゃなく、クラスを読んで書く人々がお互いに持つなのでしょう。一番の原則は「同僚が理解できる」だと思います。特に、開発者ではない同僚でもある程度読み取れるまでにしたいところです。これに関しては、どんな原則より、コンウェイの法則が効いてると思います:「システムを設計する組織は、そのコミュニケーション構造をそっくりまねた構造の設計を生み出してしまう。」

それはつまり、データ処理とファイル入出力の責任者がひとりだとしたら、データクラスがファイルへの入出力の責任を持つ傾向がある。そのほか、データクラスに簡単な(デバグ用)フォーマット入出力を入れて、他に多様な入出力を違う責任者により提供されるケースも多い。例えば、numpyが ndarraytofile() を持たせながら、loadz savetxt memmap なども提供しています。他方から ndarray をサポートするフォーマットはもちろん別パッケージで提供されている、例えば feather avro orc parquet

そう考えると、「個人的に少しばかり」なら、自分にしか責任を持ててないので、自分の使い勝手だけに気を配ればいいと思います。「未来の自分」にも責任を持てたいのなら、コードは短く、説明は細かくのがいいです。入出力メソッドはどこに置くのかを悩むのなら、どれも試してみて、もっとものづくりをやってみて、自分の好みを探し出すと良いでしょう。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#11

この回答は、運営により削除されました。

最新の回答から1ヶ月経過したため この意見交換はクローズされました

意見をやりとりしたい話題がある場合は質問してみましょう!

質問する

関連した質問