17

私は、プリプロセッサ ディレクティブの使用は#if UsingNetworkOO の悪習であると信じています。他の同僚はそうではありません。IoC コンテナー (Spring など) を使用する場合、それに応じてプログラムされていれば、コンポーネントを簡単に構成できると思います。このコンテキストではIsUsingNetwork、IoC コンテナによってプロパティを設定するか、「ネットワークを使用する」実装の動作が異なる場合は、そのインターフェイスの別の実装を実装して注入する必要があります (例: IServiceServiceImplementationNetworkingServiceImplementation)。

基本的に「IoCコンテナを介して構成する必要がある動作を構成しようとする場合、プリプロセッサの使用は悪いOOプラクティスです」と書かれている本のOO-Gurusまたは参照の引用を誰かが提供してもらえますか?

同僚にリファクタリングを説得するには、この引用が必要です...

編集:コンパイル中にターゲットプラットフォーム固有のコードを変更するためにプリプロセッサディレクティブを使用することは問題なく、それがプリプロセッサディレクティブの目的であることを知っており、同意します。ただし、適切に設計されたテスト可能なクラスとコンポーネントを取得するには、コンパイル時構成ではなくランタイム構成を使用する必要があると思います。つまり、#define と #if を本来の目的を超えて使用すると、コードのテストが困難になり、クラスの設計が不適切になります。

誰かがこれらの行に沿って何かを読んで、私が参照できるように私に与えることができますか?

4

14 に答える 14

27

ヘンリー・スペンサーは、 #ifdef は有害と見なされる という論文を書きました。

また、Bjarne Stroustrup 自身も、著書The Design and Evolution of C++の第 18 章で、プリプロセッサの使用に眉をひそめ、完全に排除したいと考えています。ただし、Stroustrup は #ifdef ディレクティブと条件付きコンパイルの必要性も認識しており、C++ にはこれに代わる適切な方法がないことを示しています。

最後に、Pete Goodliffe は、著書Code Craft: The Practice of Writing Excellence の第 13 章で、たとえ本来の目的で使用されたとしても、#ifdef がコードを台無しにする例を示しています。

お役に立てれば。ただし、同僚がそもそも合理的な議論に耳を傾けない場合、本の引用が彼らを説得するのに役立つとは思えません;)

于 2009-01-28T21:21:07.647 に答える
15

C#のプリプロセッサディレクティブには、非常に明確に定義された実用的なユースケースがあります。条件付きディレクティブと呼ばれる、具体的に話しているものは、コードのどの部分がコンパイルされ、どの部分がコンパイルされないかを制御するために使用されます。

コードの一部をコンパイルしないことと、オブジェクトグラフがIoCを介してどのように配線されるかを制御することには、非常に重要な違いがあります。実際の例を見てみましょう:XNA。WindowsとXBox360の両方に展開する予定のXNAゲームを開発している場合、ソリューションには通常、IDEで切り替えることができるプラットフォームが少なくとも2つあります。それらの間にはいくつかの違いがありますが、それらの違いの1つは、XBox 360プラットフォームが、次のイディオムを使用してソースコードで使用できる条件付きシンボルXBOX360を定義することです。

#if (XBOX360)
// some XBOX360-specific code here
#else
// some Windows-specific code here
#endif

もちろん、ストラテジーデザインパターンを使用してこれらの違いを除外し、インスタンス化されるIoCを介して制御することもできますが、条件付きコンパイルには少なくとも3つの大きな利点があります。

  1. 不要なコードは出荷しません。
  2. 両方のプラットフォームのプラットフォーム固有のコードの違いは、そのコードの正当なコンテキストで確認できます。
  3. 間接的なオーバーヘッドはありません。適切なコードがコンパイルされますが、他のコードはコンパイルされません。それだけです。
于 2009-01-23T17:20:25.693 に答える
14

IMHO、あなたは C と C++ について話しているのであって、一般的な OO の実践についてではありません。そして C はオブジェクト指向ではありません。どちらの言語でも、プリプロセッサは実際に役立ちます。正しく使用してください。

この回答は C++ FAQ に属していると思います: [29.8] プリプロセッサが悪だと言っていますか? .

はい、まさに私が言いたいことです。プリプロセッサは悪です。

すべてのマクロは、シンボルがd#defineになるまで、すべてのソース ファイルとすべてのスコープに新しいキーワードを効果的に作成します。プリプロセッサを使用すると、そのシンボルが表示されるスコープに関係なく#undef、常に置き換えられる #define シンボルを作成できます 。{...}

#ifndef/#define各ヘッダー ファイル内のラッパーなどのプリプロセッサが必要な場合もありますが、可能な場合は避ける必要があります。「悪」とは「絶対に使わない」という意味ではありません。特に「2つの悪のうち小さい方」である場合は特に、悪を使用することがあります。しかし、彼らはまだ邪悪です:-)

このソースが十分に信頼できることを願っています:-)

于 2009-01-23T16:05:11.587 に答える
9

「プリプロセッサーは悪の化身であり、地球上のすべての苦痛の原因です」 - ロバート (OO Guru)

于 2009-01-23T16:04:06.160 に答える
4

プリプロセッサ#ifdefの問題の1つは、コンパイルされたバージョンの数が効果的に複製されることです。理論的には、提供されたコードが正しいと言えるように、徹底的にテストする必要があります。

  #ifdef DEBUG
  //...
  #else
  //...

これで、「デバッグ」バージョンと「リリース」バージョンを作成できます。これは私にとっては問題ありません。デバッグバージョンでのみ実行されるアサーションとデバッグトレースがあるため、常に実行します。

誰かが来て書いたら(実例)

  #ifdef MANUALLY_MANAGED_MEMORY
  ...

そして、彼らはペットの最適化を作成し、それを4つまたは5つの異なるクラスに伝播します。その後、突然、コードをコンパイルするための4つの可能な方法があります。

別の#ifdef依存コードがある場合は、8つの可能なバージョンを生成できます。さらに厄介なことに、そのうちの4つが可能なリリースバージョンになります。

もちろん、実行時のif()は、ループなどのように、テストする必要のあるブランチを作成しますが、構成のすべてのコンパイル時の変化が正しいままであることを保証することははるかに困難です。

これが、ポリシーとして、デバッグ/リリースバージョンを除くすべての#ifdefを一時的なものにする必要があると思う理由です。つまり、開発コードで実験を行っており、すぐに一方通行にするかどうかを決定します。または他。

于 2009-01-27T21:16:34.880 に答える
4

IMO #if と #define を区別することが重要です。どちらも有用であり、どちらも使いすぎる可能性があります。私の経験では、#define は #if よりも多用される傾向にあります。

私は C および C++ プログラミングに 10 年以上費やしました。私が取り組んだプロジェクト (DOS / Unix / Macintosh / Windows 用の市販ソフトウェア) では、主にコードの移植性の問題に対処するために #if と #define を使用しました。

私は C++ / MFC で作業するのに十分な時間を費やし、#define が過度に使用されている場合に嫌悪することを学びました。これは 1996 年頃の MFC の場合であると私は信じています。

その後、Java プロジェクトに 7 年以上携わりました。プリプロセッサを見逃したとは言えません (ただし、当時 Java になかった列挙型やテンプレート / ジェネリックなどは間違いなく見逃していました)。

私は 2003 年から C# で作業しています。デバッグ ビルドには #if と [Conditional("DEBUG")] を多用していますが、#if はより便利で、同じことを行うためのわずかに効率的な方法です。私たちが Java で行ったこと。

前進するために、Silverlight 用のコア エンジンの準備を開始しました。私たちが行っていることはすべて #if がなくても実行できますが、#if を使用すると作業が減ります。つまり、顧客が求めている機能を追加するためにより多くの時間を費やすことができます。たとえば、コア エンジンに格納するシステム カラーをカプセル化する値クラスがあります。以下は、コードの最初の数行です。System.Drawing.Color と System.Windows.Media.Color は似ているため、上部の #define により、コードを複製することなく、通常の .NET と Silverlight で多くの機能を利用できます。

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
#if SILVERLIGHT
using SystemColor = System.Windows.Media.Color;
#else
using SystemColor = System.Drawing.Color;
#endif

namespace SpreadsheetGear.Drawing
{
    /// <summary>
    /// Represents a Color in the SpreadsheetGear API and provides implicit conversion operators to and from System.Drawing.Color and / or System.Windows.Media.Color.
    /// </summary>
    public struct Color
    {
        public override string ToString()
        {
            //return string.Format("Color({0}, {1}, {2})", R, G, B);
            return _color.ToString();
        }

        public override bool Equals(object obj)
        {
            return (obj is Color && (this == (Color)obj))
                || (obj is SystemColor && (_color == (SystemColor)obj));
        }
        ...

私にとっての結論は、過度に使用される可能性のある多くの言語機能があるということですが、これは、これらの機能を除外したり、それらの使用を禁止する厳格なルールを作成したりする十分な理由ではありません. マイクロソフト (Anders Hejlsberg) は、大学教授には魅力的ではないかもしれないが、私の仕事の生産性を向上させる機能を提供することに前向きだったので、長い間 Java でプログラミングした後に C# に移行したことは、私がこれを評価するのに役立ったと言わざるを得ません。最終的には、出荷日が決まっている限られた時間の中で、より優れたウィジェットを構築できるようになります。

于 2009-01-27T20:15:52.553 に答える
2

構成に基づいてさまざまな機能を制御するためにIoCまたはその他のメカニズムの代わりに#ifを使用することは、「最新の」オブジェクト指向設計の鍵である単一責任原則に違反している可能性があります。これは、オブジェクト指向設計の原則に関する広範な一連の記事です。

#ifのさまざまなセクションの部分は、定義上、システムのさまざまな側面に関係しているため、少なくとも2つの異なるコンポーネントの実装の詳細を、#ifを使用するコードの依存関係チェーンに結合しています。

これらの懸念事項をリファクタリングすることで、クラスが作成されました。このクラスは、終了してテストされていると仮定すると、共通のコードが壊れていない限り、クラックして開く必要がなくなります。

元のケースでは、#ifの存在を覚えて、重大な変更の考えられる副作用に関して3つのコンポーネントのいずれかが変更されるたびにそれを考慮する必要があります。

于 2009-01-27T21:16:31.620 に答える
2

C#での前処理のサポートは非​​常に最小限です。それは悪ですか?

プリプロセッサはOOと関係がありますか?確かにそれはビルド構成用です。

たとえば、アプリのライトバージョンとプロバージョンがあります。非常に類似したバージョンのコードを作成することに頼らなくても、ライト上の一部のコードを除外したい場合があります。

ランタイムフラグが異なるプロバージョンであるライトバージョンを出荷したくない場合があります。

トニー

于 2009-01-23T16:16:09.240 に答える
2

c#/ VB.NETでは、私はその悪を言いません。

たとえば、Windowsサービスをデバッグするときは、次のものをMainに配置して、デバッグモードのときにサービスをアプリケーションとして実行できるようにします。

    <MTAThread()> _
    <System.Diagnostics.DebuggerNonUserCode()> _
    Shared Sub Main()


#If DEBUG Then

        'Starts this up as an application.'

        Dim _service As New DispatchService()
        _service.ServiceStartupMethod(Nothing)
        System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite)

#Else

        'Runs as a service. '

        Dim ServicesToRun() As System.ServiceProcess.ServiceBase
        ServicesToRun = New System.ServiceProcess.ServiceBase() {New DispatchService}
        System.ServiceProcess.ServiceBase.Run(ServicesToRun)

#End If

    End Sub

これはアプリケーションの動作を構成するものであり、確かに悪ではありません。少なくとも、サービス起動ルーチンをデバッグしようとするほど悪くはありません。

OPを間違って読んだ場合は訂正してください。ただし、単純なブール値で十分な場合に、プリプロセッサを使用している他のユーザーについて不満を言っているようです。その場合は、プリプロセッサを酷評しないでください。そのような方法でプリプロセッサを使用しているものを酷評してください。

編集: Re:最初のコメント。その例がここでどのように結びついているのかわかりません。問題は、プリプロセッサが悪用されていることであり、悪意があることではありません。

別の例を挙げましょう。起動時にクライアントとサーバー間でバージョンチェックを行うアプリケーションがあります。開発中は、バージョンが異なることが多く、バージョンチェックを行いたくありません。これは悪ですか?

私が言おうとしているのは、プログラムの動作を変更しても、プリプロセッサは悪ではないということだと思います。問題は、誰かがそれを誤用していることです。それを言うことの何が問題になっていますか?なぜ言語機能を却下しようとしているのですか?

ずっと後で編集:FWIW:私はここ数年これにプリプロセッサディレクティブを使用していません。私はEnvironment.UserInteractiveを特定の引数セット( "-c" = Console)で使用し、アプリケーションをブロックせずにユーザー入力を待つために、ここSOのどこかから拾った巧妙なトリックを使用します。

Shared Sub Main(args as string[])
  If (Environment.UserInteractive = True) Then
    'Starts this up as an application.
    If (args.Length > 0 AndAlso args[0].Equals("-c")) Then 'usually a "DeriveCommand" function that returns an enum or something similar
      Dim isRunning As Boolean = true
      Dim _service As New DispatchService()
      _service.ServiceStartupMethod(Nothing)
      Console.WriteLine("Press ESC to stop running")
      While (IsRunning)
        While (Not (Console.KeyAvailable AndAlso (Console.ReadKey(true).Key.Equals(ConsoleKey.Escape) OrElse Console.ReadKey(true).Key.Equals(ConsoleKey.R))))
           Thread.Sleep(1)
         End While                        
         Console.WriteLine()
         Console.WriteLine("Press ESC to continue running or any other key to continue shutdown.")
         Dim key = Console.ReadKey();
         if (key.Key.Equals(ConsoleKey.Escape))
         {
           Console.WriteLine("Press ESC to load shutdown menu.")
           Continue
         }
         isRunning = false
      End While
      _service.ServiceStopMethod()
    Else
      Throw New ArgumentException("Dont be a double clicker, this needs a console for reporting info and errors")
    End If
  Else

    'Runs as a service. '
    Dim ServicesToRun() As System.ServiceProcess.ServiceBase
    ServicesToRun = New System.ServiceProcess.ServiceBase() {New DispatchService}
    System.ServiceProcess.ServiceBase.Run(ServicesToRun)
  End If
End Sub
于 2009-01-23T16:18:31.343 に答える
1

プリプロセッサ コード インジェクションはコンパイラにとって、データベースにとってのトリガーと同じです。そして、トリガーに関するそのようなアサーションを見つけるのは非常に簡単です。

関数呼び出しのオーバーヘッドを節約するため、#define を使用して短い式をインライン化することを主に考えています。つまり、時期尚早の最適化です。

于 2009-01-27T21:49:20.307 に答える
0

同僚に伝える 1 つの簡単なポイントは次のとおりです。プリプロセッサは、数学ステートメントでシンボルが使用されている場合、演算子の優先順位を破ります。

于 2009-01-28T21:26:11.303 に答える
0

新しい質問をしたかったのですが、ここに収まるようです。本格的なプリプロセッサを持つことは、Java にとって多すぎるかもしれないことに同意します。C の世界ではプリコプロセッサによってカバーされ、Java の世界ではまったくカバーされない明確なニーズが 1 つあります。デバッグ レベルに応じて、コンパイラによって完全に無視されるデバッグ出力が必要です。現在、私たちは「優れた実践」に頼っていますが、実際にはこの実践を実施するのは難しく、まだ冗長な CPU 負荷が残っています。

Java スタイルでは、debug()、warning() などの指定されたメソッドをいくつか用意することで解決できます。コードを呼び出すと、条件が生成されます。

実際には、Log4J を言語に少し統合することになります。

于 2012-08-17T04:50:09.873 に答える
0

私は、プリプロセッサ ディレクティブの使用法に関するグル ステートメントを頭の中に入れておらず、有名なディレクティブへの参照を追加することもできません。しかし、Microsoft のMSDNにある簡単なサンプルへのリンクを提供したいと思います。

#define A
#undef B
class C
{
#if A
   void F() {}
#else
   void G() {}
#endif
#if B
   void H() {}
#else
   void I() {}
#endif
}

これにより、単純な結果になります

class C
{
   void F() {}
   void I() {}
}

この時点で正確に何が定義されているかを確認するには、一番上を見なければならないので、読むのは簡単ではないと思います。他の場所で定義した場合、これはより複雑になります。

私にとっては、定義を切り替えて「新しい」クラス定義を作成するのではなく、さまざまな実装を作成して呼び出し元に注入する方がはるかに簡単に見えます。(...そして、このため、プリプロセッサ定義の使用を代わりにIoCの使用と比較する理由を理解しています)。プリプロセッサ命令を使用したコードの読みやすさに加えて、プリプロセッサ定義を使用することはめったにありません。これは、複数のパスが生成されるため、コードのテストが複雑になるためです (ただし、これは、外部 IoC コンテナーによって複数の実装が注入されることの問題でもあります)。

Microsoft 自体が win32 API 内で多くのプリプロセッサ定義を使用しており、char メソッド呼び出しと w_char メソッド呼び出しの間の醜い切り替えを知っている/覚えているかもしれません。

「使わないで」と言うべきではないかもしれません。代わりに、「どのように使用するか」と「いつ使用するか」を伝えてください。優れた (より理解しやすい) 代替案を考え出し、プリプロセッサの定義/マクロを使用するリスクを説明できる場合は、誰もが同意すると思います。

グルは必要ありません...ただグルになりましょう。;-)

于 2011-09-01T22:42:57.997 に答える