web-dev-qa-db-ja.com

論理的に手続き型のソフトウェアをOO言語で記述する最もクリーンな方法

私は電気技師で、一体何をしているのかわかりません。私のコードの将来のメンテナーを保存してください。

最近私は、機能が論理的に「手続き型」である(C#の)いくつかの小さなプログラムに取り組んでいます。たとえば、その1つは、さまざまなデータベースから情報を収集し、その情報を使用して一種の要約ページを生成し、印刷してから終了するプログラムです。

これらすべてに必要なロジックは約2000行です。私は確かにそれらすべてを単一のmain()に詰め込み、#regionsで「クリーンアップ」して、以前の開発者が行ってきたようにしたくありません(シャダー)。

ここに私がすでに満足していないいくつかの試みがあります:

機能の粗いビットごとに静的ユーティリティを作成します。 DatabaseInfoGetter、SummaryPageGenerator、およびPrintUtility。メイン関数を次のようにします。

int main()
{
    var thisThing = DatabaseInfoGetter.GetThis();
    var thatThing = DatabaseInfoGetter.GetThat();
    var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

    PrintUtility.Print(summary);
}

1つのプログラムでは、インターフェイスも使用しました

int main()
{
    /* pardon the psuedocode */

    List<Function> toDoList = new List<Function>();
    toDoList.Add(new DatabaseInfoGetter(serverUrl));
    toDoList.Add(new SummaryPageGenerator());
    toDoList.Add(new PrintUtility());

    foreach (Function f in toDoList)
        f.Do();
}

これはどれも正しくないと感じています。コードが長くなるにつれて、これらのアプローチはどちらも醜くなり始めます。

このようなものを構造化する良い方法は何ですか?

12
Lombard

あなたはプロのプログラマーではないので、単純さを守ることをお勧めします。プログラマがモジュール化された手続き型コードを取り込んでOOにするのは、不適切に記述されたOOプログラムを修正するよりも、はるかに簡単です。経験がなければ、OOプログラムを作成して、あなたやあなたの後に来る人を助けることのできない不気味な混乱に変えることができます。

私はあなたの最初の本能、最初の例の「これ-そのこと」のコードが正しい道だと思います。あなたが何をしたいのかは明確で明白です。コードの効率についてあまり気にしないでください。わかりやすさがはるかに重要です。

コードセグメントが長すぎる場合は、それぞれに独自の機能を持つ一口サイズのチャンクに分割します。短すぎる場合は、使用するモジュールを減らして、より多くのモジュールを配置することを検討してください。

----追記:OOデザイントラップ

OOプログラミングでの作業は、注意が必要です。モデル全体に​​欠陥があると考える人 もいますThinking in Java と呼ばれるOOプログラミングを最初に学習したときに使用した非常に優れた本(現在は第4版)があります。同じ著者がC++の対応する本を持っています。オブジェクト指向プログラミングにおける一般的な落とし穴 を扱うプログラマーには、実際には別の質問があります

いくつかの落とし穴は洗練されていますが、非常に基本的な方法で問題を作成する方法はたくさんあります。たとえば、数年前、私が継承したいくつかのソフトウェアの最初のバージョンを書いたインターンが私の会社にいて、彼は可能性のあるすべてのインターフェイスを作成しましたいつか複数の実装がある。もちろん、98%のケースでは実装が1つしかなかったため、未使用のインターフェースがコードにロードされ、インターフェースの呼び出しをステップバックできないためデバッグが非常に面倒になり、最終的には実装のテキスト検索(現在はIntelliJを使用していましたが、[すべての実装を表示]機能がありましたが、当時はありませんでした)。ここでのルールは、手続き型プログラミングの場合と同じです。常に1つをハードコードします。 2つ以上のものがある場合にのみ、抽象化を作成します。

同様の設計ミスは、Java Swing APIにあります。 Swingメニューシステムのパブリッシュ/サブスクライブモデルを使用します。これにより、Swingでのメニューの作成とデバッグが完全な悪夢になります。皮肉なことに、それは完全に無意味です。複数の機能がメニュークリックに「サブスクライブ」する必要がある状況は、ほとんどありません。また、通常は常にメニューシステムが使用されているため、パブリッシュサブスクライブは完全に失敗しました。これは、関数がランダムにサブスクライブしてからサブスクライブ解除するようなものではありません。 Sunの「プロの」開発者がこのように失敗したという事実は、プロがOOの設計で巨大なねじ込みを作ることがいかに簡単かを示しています。

私はOOプログラミングで数十年の経験を持つ非常に専門的な開発者ですが、私が知らないトンがあることを最初に認めたとしても、今でも多くのOOの使用には非常に慎重です。私は以前、特定のデザインを行う方法についてOO熱心な同僚からの長い講義を聞いていました。彼は自分が何をしているのか本当に知っていましたが、正直なところ、彼のプログラムは非常に洗練されたデザインモデルを持っているので、理解するのに苦労しました。

18
Tyler Durden

最初のアプローチは問題ありません。 Cのような手続き型言語では、標準的なアプローチは機能を名前空間に分割することです-DatabaseInfoGetterのような静的クラスを使用することは基本的に同じことです。明らかに、このアプローチは、すべてがメソッドに分解されたとしても、すべてを1つのクラスに押し込むよりも、より単純で、よりモジュール化され、より読みやすく、保守可能なコードになります。

そういえば、メソッドをできるだけ少ないアクションに限定するようにしてください。一部のプログラマはlittleより細かい粒度を好みますが、巨大なメソッドは常に有害と見なされます。

それでも複雑さに苦労している場合は、おそらくプログラムをさらに分解する必要があります。階層内により多くのレベルを作成します-おそらくDatabaseInfoGetterProductTableEntryなどのような他のクラスを参照する必要があります。手続き型コードを記述していますが、C#を使用しており、OOPを使用すると、複雑さを軽減するための一連のツールが提供されます。例:

int main() 
{
    var thisthat = Database.GetThisThat();
}

public class ThisThatClass
{
    public String This;
    public String That;
}

public static class Database 
{
    public ThisThatClass GetThisThat()
    {
        return new ThisThatClass 
        {
            This = GetThis(),
            That = GetThat()
        };
    }

    public static String GetThis() 
    { 
        //stuff
    }
    public static String GetThat() 
    { 
        //stuff
    }

}

また、静的クラスにも注意してください。データベースクラスは良い候補です。通常、データベースが1つある限り、アイデアがわかります。

最終的に数学関数を考え、C#が自分のスタイルに合わない場合は、ScalaやHaskellなどを試してみてください。

1
Skepticism

私は4年遅れで応答していますが、OO言語で手続き型のことをしようとしているときは、アンチパターンに取り組んでいます。それはあなたがすべきことではありません!ブログを見つけましたこれはこのアンチパターンに対処し、その解決策を示していますが、多くのリファクタリングと考え方の変更が必要です。詳細については、 手続き型コードのオブジェクト指向コード を参照してください。
「これ」と「あれ」を述べたように、基本的には2つの異なるクラスがあることを示唆しています。 A Thisクラス(悪い名前!)とThatクラス。そしてあなたは例えばこれを訳して:

var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

これに:

var summary = SummaryPageGenerator.GeneratePage(new This(DatabaseInfoGetter), new That(DatabaseInfoGetter));

ここで、これらの2,000行以上の手続き型コードを見て、さまざまな部分を別々のクラスにグループ化し、さまざまな手続きをそれらのクラスに移動する方法を決定することは興味深いでしょう。
各クラスはシンプルで、1つのことだけに集中する必要があります。そして私には、コードの一部がすでにスーパークラスを処理しているように見えますが、それらは実際には大きすぎて便利ではありません。たとえば、DatabaseInfoGetterは混乱の元に聞こえます。とにかくそれは何を取得していますか?汎用的すぎるようです。しかし、SummaryPageGeneratorは非常に特殊です。そして、PrintUtilityが再び一般的になりました。
そのため、最初に、クラスの総称名を避け、より具体的な名前を代わりに使用する必要があります。たとえば、PrintUtilityクラスは、Headerクラス、Footerクラス、TextBlockクラス、Imageクラスを備えたPrintクラスであり、印刷自体の中でどのようにフローするかを示すための方法です。これはすべて、ただし、PrintUtilitynamespace
データベースについても同様です。 Entity Frameworkをデータベースアクセスに使用する場合でも、使用するものに制限し、論理グループに分割する必要があります。
しかし、4年経ってもまだこの問題に対処しているのでしょうか。もしそうなら、それは「手続き型の概念」から「オブジェクト指向の概念」に変更する必要がある考え方です。あなたが経験を積んでいない場合、これは挑戦的です...

0
Wim ten Brink