掲示板利用宣言

 次のフォームをすべてチェックしてからご利用ください。

 私は

 題名と投稿者名は具体的に書きます。
 課題の丸投げはしません。
 ソースの添付は「HTML変換ツール」で字下げします。
 返信の引用は最小限にします。
 環境(OSとコンパイラ)や症状は具体的に詳しく書きます。
 返信の付いた投稿は削除しません。
 マルチポスト(多重投稿)はしません。

掲示板2

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧

No.27236

モジュール
投稿者---su(2006/06/15 20:46:32)


ちょっとここでする質問かどうかなやんだのですが質問させていただきます。
膨大なソースのプログラムを書くようになるとモジュール別にファイルを分けてコーディングしますよね。
適切なモジュールのわけかたってどういう風なものがいいのでしょうか?
もしどなたかアドバイスをしていただけるようでしたらしていただけるとありがたいです。


この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:モジュール 27240 たかぎ 2006/06/15 22:56:47
<子記事> Re:モジュール 27244 K 2006/06/16 11:55:09
<子記事> Re:モジュール 27250 shu 2006/06/16 21:02:35


No.27240

Re:モジュール
投稿者---たかぎ(2006/06/15 22:56:47)
http://takagi.in/


>適切なモジュールのわけかたってどういう風なものがいいのでしょうか?

漠然としか答えられませんが、私の場合は次のようにしています。

アプリケーションの場合、まずは大きな機能単位でモジュールを分割します。それより下位は、サブ機能に分けられるなら、それに応じて分割します。特定の用途に限定されない、汎用の処理も分離します。後はケースバイケースです。

ライブラリの場合、上記と同じ分け方に加えて、リンク後にプログラムサイズが最小になるように工夫します。具体的には、可能な限り、一つの翻訳単位には一つの関数しか定義しないようにする等です。

他に、製品に複数のバリエーションがある場合には、特定のバリエーションに依存する部分は分離します。また、特定の処理系やフレームワーク等に依存する部分も分離しています。



この投稿にコメントする

削除パスワード

No.27242

Re:モジュール
投稿者---へろり(2006/06/16 07:40:46)



>ライブラリの場合、上記と同じ分け方に加えて、リンク後にプログラムサイズが最小になるように工夫します。具体的には、可能な限り、一つの翻訳単位には一つの関数しか定義しないようにする等です。
>

これはいかがな物かと思いますが。

私は翻訳単位には1つの完結した機能を包括するべきだと考えます。
C++で言うならクラスに近い形になります。

そして関数には必要最小の機能を持たせるにとどまるべきだと考えます。
たとえばfopen() fclose() fwrite() fread() fprintf() fscanf() ftell()
等の関数は全てファイルを操作する機能を持つ関数です。
これらの関数群は”ファイルを操作する”という翻訳単位(モジュール)に
分けるべきかと。

#1翻訳単位に1関数だと膨大な数のソースファイルになったり、
1関数の行数がとんでも無いことになったりしませんか?

#ついでにそれらの関数は翻訳単位の外側に丸見えになってれば使えないわけで
それもちょっと問題だと思うのですがいかがでしょう。


この投稿にコメントする

削除パスワード

No.27243

Re:モジュール
投稿者---たかぎ(2006/06/16 10:29:18)
http://takagi.in/


>私は翻訳単位には1つの完結した機能を包括するべきだと考えます。
>C++で言うならクラスに近い形になります。

ライブラリの設計で、そのようにしてしまうと、決して呼ばれることのない関数までリンクされてしまい、プログラムサイズが肥大化します。
組み込みシステムなどでは、無駄なコードを格納するためにより大きなROMが必要になり、結果として、ユーザーにコスト負担を強いるか、メーカーがコスト競争で敗れることにもつながりかねません。
また、実行イメージに、決して呼び出されないコードが混在していると、信頼性の低下にもつながります。

アプリケーションの設計では、仰るとおり、1つの完結した機能を包括する形でほぼ問題ないと思います。ただし、前述したように、汎用の処理および、製品のバリエーションや特定の処理系・フレームワーク等に依存する部分は分離すべきです。汎用の処理まで包含してしまうと、等価なコードが散在することになりますし、特定の処理系・フレームワーク等に依存する部分を包含してしまうと、ポータビリティが失われます。

>そして関数には必要最小の機能を持たせるにとどまるべきだと考えます。
>たとえばfopen() fclose() fwrite() fread() fprintf() fscanf() ftell()
>等の関数は全てファイルを操作する機能を持つ関数です。
>これらの関数群は”ファイルを操作する”という翻訳単位(モジュール)に
>分けるべきかと。

モジュールという意味では、おそらくこれらは一つのモジュールですね。しかし、翻訳単位という意味では、分割すべきものです。
# 質問者が本当に知りたいのは、翻訳単位の分割方法ではないかと思っています。

fopenをfreopenを用いて実装するのであれば、freopenとfcloseは必ず対で使うことになるので、これらを同一の翻訳単位に記述することは、サイズの増大や未使用コードの混在につながりませんから、何ら問題ありません。
しかし、fprintfとfwriteでは、想定用途が全く異なるので、一方しか使用されない可能性は十分あります。fwriteで単純な出力のみを使いたいのに、fprintfまでくっついてくるために、膨大な書式文字列の処理や、浮動小数点エミュレーション(ローエンドの環境では、今でも普通に存在します)までぶら下がってくるのは許容範囲を超えています。

>#1翻訳単位に1関数だと膨大な数のソースファイルになったり、
>1関数の行数がとんでも無いことになったりしませんか?

ソースファイルが増えることは致し方ありませんが、1関数のサイズがとんでもないことになることはありません。下請け関数を別の翻訳単位に記述したり、内部結合の別関数にすることを禁止しているわけではないからです。
大切なのは、1翻訳単位に1関数しか記述しないことではなく(これは1つの手段にすぎません)、最終的にできあがるプログラムサイズを最小にすることと、未使用コードを混在させないことが重要なのです(前者の要求を満たせば、ほぼ後者の要求も満たせます)。

>#ついでにそれらの関数は翻訳単位の外側に丸見えになってれば使えないわけで
>それもちょっと問題だと思うのですがいかがでしょう。

すみません。これはちょっと意味が分かりませんでした。

元の質問では、環境が特定されていませんでしたから、メモリ制約が厳しい環境や高信頼性が要求される環境でも通用し、かつポータビリティを考慮した分割方法について書いたつもりです。
メモリが無尽蔵に近い環境や、ほどほどの信頼性で許される状況、特定の処理系等にどっぷり依存することが許される状況では、また別の手法もありかと思います。



この投稿にコメントする

削除パスワード

No.27245

Re:モジュール
投稿者---あかま(2006/06/16 14:18:15)


>また、実行イメージに、決して呼び出されないコードが混在していると、信頼性の低下にもつながります。
呼び出されなくてもプログラムの信頼性って下がるのでしょうか?

>>#1翻訳単位に1関数だと膨大な数のソースファイルになったり、
>>1関数の行数がとんでも無いことになったりしませんか?
>ソースファイルが増えることは致し方ありませんが、1関数のサイズがとんでもないことになることはありません。
1翻訳単位というのは1ソースファイルと読んでもいいのでしょうか?
それだと、どこぞの本(コーディングスタイルやコーディング規約を書いた本だったのですが、失念しました)に、ダメなパターンとして出ていましたねぇ。

[1関数1ファイルの規約]
「関数を作るたびにファイルを作るのがめんどくさくなって、関数分割をしなくなる。
結果とても保守できないようなソースができあがる」

という感じでした。
関数は20行以下のものが多いらしいです(Linuxのソースでは。私のプログラムでも大概そうですが)。
実際20行のために1ファイルを作るのかと考えると、関数分割したくなくなりますねぇ。


組み込み系ではプログラムサイズの増大は致命的ですが、それ以外ではあまり気にしなくていいのではないでしょうか?
コンパイルオプションでなんとかならないんでしょうかね?こういうものって。
組み込み系のコンパイラ作るのなら必須だと思うんですけどね。


この投稿にコメントする

削除パスワード

No.27246

Re:モジュール
投稿者---たかぎ(2006/06/16 16:08:54)
http://takagi.in/


>呼び出されなくてもプログラムの信頼性って下がるのでしょうか?

最も分かりやすい要因としては、テストの網羅率が下がることがあります。

>1翻訳単位というのは1ソースファイルと読んでもいいのでしょうか?
>それだと、どこぞの本(コーディングスタイルやコーディング規約を書いた本だったのですが、失念しました)に、ダメなパターンとして出ていましたねぇ。

翻訳単位とソースファイルは同じではありません。例えば、ヘッダファイルにインライン関数として定義するような場合には、ソースは一つでも、複数の翻訳単位に分散することになります。逆に、ひとつのソースファイルに記述していても、条件付コンパイルを用いて複数の翻訳単位に分割される場合もあります。
翻訳単位は、ソースファイルとそこからインクルードされるヘッダやファイルをひっくるめたものです。

>[1関数1ファイルの規約]
>「関数を作るたびにファイルを作るのがめんどくさくなって、関数分割をしなくなる。
>結果とても保守できないようなソースができあがる」
>
>という感じでした。

これのことでしょうか?
そこに書かれた内容はライブラリ開発に関するものではありませんし、そもそも想定している状況が異なります。今回は管理手法の話ではなく、プログラムサイズを最小にするための最も典型的な手法が、(あくまでも分割可能な範囲で)1関数1翻訳単位とすることだということです。
分割すべき関数を分割せずに1つの関数に詰め込むのは、プログラムサイズを最小にするという目的と、1関数1翻訳単位という手段を、完全に混同していますね。

ところで、同じ翻訳単位に複数の関数を詰め込んだ方が、プログラムサイズが小さくなる場合も、稀にあります。
例えば、リンカが翻訳単位ごとに境界調整を行うような場合がそうです。他には、関数間の距離がコンパイル時に特定できるので、呼び出しの際に相対分岐命令が使われる場合です。
いずれにしても、コンパイラの出力結果を確認しながら最適化しないといけないので、非常に面倒ですし、その割には効果が低いので、通常はここまではやりません。

>関数は20行以下のものが多いらしいです(Linuxのソースでは。私のプログラムでも大概そうですが)。

それで正しいと思います。

>実際20行のために1ファイルを作るのかと考えると、関数分割したくなくなりますねぇ。

そんな人はライブラリ開発に向いていません。
まあ、ファイルは一つにして、条件付コンパイルで翻訳単位を分割する手法もありますが、余計に面倒です。

>組み込み系ではプログラムサイズの増大は致命的ですが、それ以外ではあまり気にしなくていいのではないでしょうか?

例えば、CygwinのGCJでHello, World!をコンパイルしてできる実行ファイルは確か6Mバイト強あったと思います。まともなアプリを作ればさらに大きくなるでしょう。
(圧縮はするとしても)そんなものをダウンロードするのは嫌だと思いませんか?(自宅やオフィスならともかく、出先でPHSで落とす羽目になることもあるのです。)

それに、特定の環境に特化した回答が欲しいのであれば、掲示板ご利用上の注意にもあるように、質問者は最初に環境を書くべきなのです。

>コンパイルオプションでなんとかならないんでしょうかね?こういうものって。
>組み込み系のコンパイラ作るのなら必須だと思うんですけどね。

コンパイルオプションだけでは解決しません。リンクオプションで指定することはできるかもしれませんが、リンクされると思っていたコードがリンクされていないと、逆に困る場合もあります。それに、どんなリンクオプションを使うかはアプリ開発者が決めることなので、ライブラリ側では制御することができません。
C++であれば、テンプレートライブラリにすれば、ほぼコンパイラ任せにできるとは思いますが...

なお、堅牢性や効率等の観点から、分割可能であっても、同一の翻訳単位に収める正当な理由があれば、当然そうすべきです。ところが、実際にはそのような状況はあまり多くありません。
何度も繰り返しますが、アプリケーションの場合はまた事情が異なります。アプリケーションのソース管理上の都合でライブラリアンを使っているだけの場合も、ここでいうライブラリには当てはまりません。DLLなどはライブラリですが、あまりにも環境に特化した話題なので触れていません。



この投稿にコメントする

削除パスワード

No.27247

Re:モジュール
投稿者---あかま(2006/06/16 16:35:59)


ライブラリとアプリケーションを一緒くたに考えていましたが全然違うものなのですね。
たしかにHello, World!で6Mバイト強というのは問題です。
ライブラリ開発で考慮すべき問題というのはアプリケーション開発よりシビアなようです。

勉強になりました。
貴重なお話ありがとうございます。
いや、かなりおもしろいお話でした。


この投稿にコメントする

削除パスワード

No.27248

Re:モジュール
投稿者---円零(2006/06/16 16:45:51)


>それに、特定の環境に特化した回答が欲しいのであれば、掲示板ご利用上の注意にもあるように、質問者は最初に環境を書くべきなのです。

質問者がその辺詳しく書いてない状況で、
回答者が考えられる中から特定のケースを想定して答える場合、
やっぱり何を想定しての話なのか最初に書いといた方がいいような気がします。


この投稿にコメントする

削除パスワード

No.27249

Re:モジュール
投稿者---たかぎ(2006/06/16 17:03:03)
http://takagi.in/


>質問者がその辺詳しく書いてない状況で、
>回答者が考えられる中から特定のケースを想定して答える場合、
>やっぱり何を想定しての話なのか最初に書いといた方がいいような気がします。

特定の環境を想定した話の場合、大雑把ですが、「組み込みシステム」といった程度の書き方はしたつもりなんですが、もう少し書いた方がよかったですかね。
プログラムサイズの話は、何も環境を特定した話ではありません。サイズが 大きくてもよい 環境はあっても、大きくなければならない 環境はないからです。
# 何円/バイトという価格設定とかがあるなら話は別ですが、それこそ非常に特化した「環境」です。



この投稿にコメントする

削除パスワード

No.27251

Re:モジュール
投稿者---yoh2(2006/06/17 13:41:54)


横から失礼。

>>呼び出されなくてもプログラムの信頼性って下がるのでしょうか?
>
>最も分かりやすい要因としては、テストの網羅率が下がることがあります。

これはなぜなのでしょうか?

ライブラリを提供するならば、ライブラリの利用者がどの関数を実際に利用するかにかかわらず(というか、そういったことを知る術がない)、提供する関数のすべてが100%テストされているべきではないでしょうか?
現実との兼ね合いで、すべてが100%テスト済みではないとしても、関数毎のテスト実行状況利用者に開示すれば、利用者が知らず知らずのうちに信頼性の低い関数を利用してしまうことは避けられます。(あえて使うというのもアリですが。閑話休題)
ここに、作成されたプログラム中にその信頼性の低い関数のコードが実際に含まれているか否かといった条件は、そのプログラムの信頼性に影響を与えないと思います。

ここでは、ライブラリを利用して作られるプログラムがWindowsアプリケーションのような富豪的手法が許される類のものか、組み込みプログラムのような制約の多い類のものかをあえて限定せずに意見を述べてみました。(プログラムサイズに関しては無視しましたが)

プログラムに対する信頼性という面で、何か見落としていることや、(プログラムサイズ以外の)組み込み系特有の制約があるのでしょうか?
または私が議論の前提条件を間違えたとか……

プログラムに対するテストの網羅率とは、関数が実行されるか否かにかかわらず、プログラムに含まれる全関数に対しての網羅率を考慮されなければならないとか。
これだと、ライブラリの関数func1()、func2()に対し、
* func1(): 単独で動作する関数。テスト網羅率80%
* func2(): func1()を内部的に利用する関数。ただし、func1のテスト済みの機能のみ使い、func2()自身のテスト網羅率は100%
という場合、プログラムがfunc2()しか使っていなくてもテスト網羅率は下がってしまい、なんか不自然。
# ↑あ、仮定に対する反論になってる。不毛。


この投稿にコメントする

削除パスワード

No.27252

Re:モジュール
投稿者---たかぎ(2006/06/17 16:57:59)
http://takagi.in/


いろいろな状況があると思うので、網羅的な回答はできないのですが...

>プログラムに対する信頼性という面で、何か見落としていることや、(プログラムサイズ以外の)組み込み系特有の制約があるのでしょうか?
>または私が議論の前提条件を間違えたとか……

組み込み系の例が分かりやすいので、まずはこれについて書いてみます。ライブラリのコードが100%テスト済みであったとしても、組み込みの場合、多かれ少なかれ、実際に使用するターゲットにあわせてポーティングする必要があります。例え、使用するプロセッサがライブラリ推奨のものと同じであっても、メモリマップも異なれば、周辺デバイスも異なります。こうなると、別の環境で100%テストされているとしても、それは参考情報でしかありません。

また、これは組み込みに限りませんが、アプリケーション側で、ライブラリのコードを破壊してしまう可能性もあります。一番典型的なのは、バッファオーバーランで内部データを破壊する場合と、ライブラリ関数が使用している外部識別子をアプリ側で定義してしまう場合です。こうなると、ライブラリがテスト済みであることは無意味になります。

いずれの場合も、実際に呼び出されないのであれば問題ないのですが、内部状態を更新できる(本来必要のない)サービスがあるとセキュリティホールにもつながりますし、正当なプログラムの部分アップデートや(プラグインなどの)追加であっても、それらによって誤使用されるリスクも残るわけです。チープなシステムでは、プロセッサが暴走した際に、たまたまその処理にジャンプして、データやハードウェアを破壊することもあり得ます。

>プログラムに対するテストの網羅率とは、関数が実行されるか否かにかかわらず、プログラムに含まれる全関数に対しての網羅率を考慮されなければならないとか。

決して実行されないことを、アプリケーション側で保証できるのであれば、それで問題ないと思います。しかし、そのためにはライブラリの実装の詳細によほど精通していなければなりません。いくら実際には問題がなくても、それを証明できなければ製造者としてユーザに保証できませんから、やはり問題なのです。



この投稿にコメントする

削除パスワード

No.27267

Re:モジュール
投稿者---yoh2(2006/06/18 14:38:11)


>組み込み系の例が分かりやすいので、まずはこれについて書いてみます。(snip)別の環境で100%テストされているとしても、それは参考情報でしかありません。

なるほど。私が暗黙のうちに仮定していた、以下の二点が成り立たないのですね。

* ライブラリの提供者と利用者の環境が同じ
* 利用者はライブラリを改変せずに使う

確かに、これらが成り立たなければ、ライブラリ提供者の保証はあまり当てになりませんね。

それ以外の点については、一般的な例というより、組み込み系以外の特殊例といった感じですね。
しかし、アーキテクチャが特殊、というよりは、要求レベルがミッションクリティカルに近い(PC用オフィスアプリケーションあたりの開発ではまず要求されないレベル)という意味で特殊なようですので、いろいろと参考になります。

最後に、天邪鬼気味ですが、コードを削る方が怖い例を。(ほとんど茶々です)
プラグインの開発や、(Cの範疇からややずれる気がしますが)Objective-Cを用いた開発では、組み込んだコードが呼び出されないことではなく、組込まなかったコードが呼び出されないことを証明しないと、実行時エラーが怖くてうかつにコードを削れません。
どちらもシンボルの解決を実行時に行うため、リンカには判別できませんし。
# ↑だから特殊過ぎるんだってば。


この投稿にコメントする

削除パスワード

No.27268

Re:モジュール
投稿者---たかぎ(2006/06/18 14:52:57)
http://takagi.in/


>それ以外の点については、一般的な例というより、組み込み系以外の特殊例といった感じですね。
>しかし、アーキテクチャが特殊、というよりは、要求レベルがミッションクリティカルに近い(PC用オフィスアプリケーションあたりの開発ではまず要求されないレベル)という意味で特殊なようですので、いろいろと参考になります。

そういうことです。
なぜ、組み込みに限ったことではないかというと、組み込みの場合でも、さほど信頼性が要求されない(早い話が、人が死ぬとかの重大な事態に陥らない)ものであれば、それほどシビアではありません。逆に、業務系のシステムであっても、用途によっては高い信頼性が要求されると思うからです。

これというのも、元々の質問が、対象としている用途や環境を明確にしていないために、厳しいものにあわせて答えざるを得ないためです(しつこい)。


この投稿にコメントする

削除パスワード

No.27269

Re:モジュール
投稿者---たかぎ(2006/06/18 15:06:24)
http://takagi.in/


一応最後の部分にもレスします。

>プラグインの開発や、(Cの範疇からややずれる気がしますが)Objective-Cを用いた開発では、組み込んだコードが呼び出されないことではなく、組込まなかったコードが呼び出されないことを証明しないと、実行時エラーが怖くてうかつにコードを削れません。
>どちらもシンボルの解決を実行時に行うため、リンカには判別できませんし。

そうですね。
ただ、これは(プラグインを含めて)アプリケーション側でコントロールすべきことですし、コントロールできることです。それに対して、勝手に不要なものまでリンクされてしまうのは、(ライブラリのソースレベルで削除するか、バイナリエディタでパッチでもあてない限り)アプリケーション側ではコントロールできませんね。だから、ライブラリ側でどうにかすべき問題なのです。



この投稿にコメントする

削除パスワード

No.27244

Re:モジュール
投稿者---K(2006/06/16 11:55:09)


私はあくまで概念的な事しか言えませんが、まず部屋の掃除の仕方をイメージしてみるといいと思います。
まず大きい単位で「本」という分類を一つのところにまとめます。
次にこれもまた大きい単位で「CD」という分類を一つのところにまとめます。
これを繰り返すと様々な分類の塊ができていきます。
これがファイルです、もっというなれば「本」.cのなわけだとイメージしてください。
そうすると気がつくわけです。
CDだった場合は「アーティスト順に整理しよう」と。
こうなれば簡単ですね、「あまりにもCDが多いから[CDアーティスト].cにも分けようかな」ってところは自分が作る物の大きさ等に比例して思慮をめぐらしていくようになると思います。
こうかんがえると人間の行動というのはなかなかプログラム構築等のヒントになりやすいことがわかると思います。
ってなアドバイスはどうでしょうか?

もしもっとプログラム的な食い込んだ解答を期待されていたのでしたら申し訳ないです。








この投稿にコメントする

削除パスワード

No.27250

Re:モジュール
投稿者---shu(2006/06/16 21:02:35)


>適切なモジュールのわけかたってどういう風なものがいいのでしょうか?
>もしどなたかアドバイスをしていただけるようでしたらしていただけるとありがたいです。

自分で考えたものでも、人から聞いたものでも、自分がいいなぁと思ったものを信じる。
絶対的でなくて良いので、その時点で信じれる分くらいは信じる。

標準ライブラリのヘッダファイルを参考にする。
どのヘッダファイルに、どの関数が割り当てられているのか?

前にこんな書き込みをしたことがある。
プログラムの組み方


この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧