6

私が望んでいるほどレガシーではないコードを維持するように依頼されましたが、それはコンパイラ指令であふれていて、ほとんど判読できず、ほとんど同じように維持しやすくなっています。適例:

#if CONDITION_1
        protected override void BeforeAdd(LogEntity entity)
#else
        protected override void BeforeAdd(AbstractBusinessEntity entity)
#endif
        {
#if CONDITON_1
            entity.DateTimeInsert = DateTime.Now;
#else
            ((LogEntity) entity).DateTimeInsert = DateTime.Now;
#endif
            base.BeforeAdd(entity);
        }

usingディレクティブはさらにきれいです:

#if CONDITION_1
using CompanyName.Configuration;
#endif

#if CONDITION_2||CONDITION_1
using CompanyName.Data;
using CompanyName.Data.SqlBuilders;
#else
using CompanyName.Legacy.Database;
using CompanyName.Legacy.Database.SQLBuilders;
using CompanyName.Legacy.Database.SQLBuilders.parameterTypes;
#endif

やってみようと思っConditionalAttributeたのですが、この状況ではうまくいきません

このコンパイラ ディレクティブの悪夢から抜け出す方法はありますか?

コードは に対してコンパイルされ.NET 3.5ます。

更新:
Oded は、BeforeAddメソッドの周りのコンパイラ ディレクティブを削除してオーバーロードすることを提案すると答えました。残念ながら、どちらのメソッドもAbstractBusiness最終的に含まれるアセンブリに応じて 2 つの異なる実装を提供するクラスをオーバーライドすることになっているため、これは機能しません。

protected virtual void BeforeAdd(TEntity entity) {}

また

protected virtual void BeforeAdd(AbstractBusinessEntity entity) {}

このコードは、会社が過去に作成した一連のライブラリから依存関係を取得し、それ以来「アップグレード」しています。現在、名前空間が衝突し、実装が異なるライブラリ セットの 4 つの異なるバージョンがあります。すべては、(非常に) 古いバージョンを使用するアプリケーションとの「後方互換性」の名のもとに行われています。


結論

一般的なアプローチ( KISSなど)として最も理にかなっているため、@Odedの回答を選択することになりました。ただし、この場合は使用できませんでした。ここに表示されているのは氷山の一角にすぎません。お金を払ってくれるなら、このコードにキスしたくありません。

4

4 に答える 4

7

最初のケースでは、このコンストラクトの代わりにメソッドの複数のオーバーロードを単純に持つことができるように見えます。この時点で、過負荷の解決によって処理が行われます。

2 番目のケース (ディレクティブを使用) では、必要に応じてエイリアスを使用して、ディレクティブの一部にエイリアスを設定し、それらすべてを含めることができます。すべての名前空間が含まれるとどうなりますか? 名前の衝突はありますか?

于 2011-03-18T15:00:17.030 に答える
2

問題はこのクラスにはないと思います。このクラスは単なる症状です。問題は、BeforeAddを呼び出している基本クラスにあります。そこでリファクタリングできる場合は、条件付きコンパイルは必要ありません。

名前と名前空間が競合している場合は、usingキーワード(アセンブリ用ではない)を使用して回避できます。

だからあなたは次のようなことをすることができます

using LegacyLogEntity = Some.Fully.Qualified.Namespace.LogEntity;
using SomeOtherLogEntity = Some.Other.Fully.Qualified.Namespace.CurrentLogEntity;

// ..
LegacyLogEntity entity = new LegacyLogEntity();

また、問題はこのクラスではなく、基本クラス自体にあると思います。

その場合、適応またはインターフェースのいずれかを使用して、このナンセンスを回避できます。

他のクラスが何と呼ばれているのかわかりませんが、EntityAggregatorと呼ばれているとしましょう。

public interface IEntity {
    DateTime InsertionTime { get; set; }
}

次に、アグリゲーターの基本クラスで:

protected virtual void BeforeAdd(IEntity entity)
{ // whatever
}

次に、サブクラスで:

protected override void BeforeAdd(IEntity entity)
{
    entity.DateTime = DateTime.Now;
    base.BeforeAdd(entity);
}

これで、そのインターフェイスを実装することにより、他のオブジェクトをIEntityに適合させることができます。

このコードを見ると、おそらくこのコードの代わりにイベントを使用していることに気づきます。

ここで、コードが2つの異なる条件下で2つの別々の場所にコンパイルされる多目的コンパイルについて話している場合は、部分クラスを使用することで、より適切にそれを行うことができます。

CONDITION_1コードを次のように分離します。

// in file WhateverYourClassIs.condition1.cs
#if !CONDITION_1
#error this file should never be included in a build WITHOUT CONDITION_1 set
#endif

public partial class WhateverYourClassIs {
    protected override void BeforeAdd(LogEntity entity) {
        entity.DateTimeInsert = DateTime.Now;
        base.BeforeAdd(entity);
    }
}

// in file WhateverYourClassIs.NotCondition1.cs

#if CONDITION_1
#error this file should never be included in a build WITH CONDITION_1 set
#endif

public partial class WhateverYourClassIs {
    protected override void BeforeAdd(AbstractBusinessEntity entity) {
        ((LogEntity)entity).DateTimeInsert = DateTime.Now;
        base.BeforeAdd(entity);
    }
}

この場合、コードが繰り返されるため、これは好きではありません。usingキーワードを使用すると、これを支援できます。

#if CONDITION_1
using MyAbstractBusinessEntity = LogEntity;
#else
using MyAbstractBusinessEntity = AbstractBusinessEntity;
#endif

// ...

protected override void BeforeAdd(MyAbstractBusinessEntity entity)
{
    // in CONDITION_1, the case is a no-op
    ((LogEntity)entity).DateTimeInsert = DateTime.Now;
    base.BeforeAdd(entity);
}
于 2011-03-18T16:06:23.707 に答える
1

私が見ているところによると、元の開発者は継承やポリモーフィズムの感覚を持っていなかったようです。コードから見分けるのは少し難しいですが、LogEntityとAbstractBusinessEntityは共通のプロパティを共有しているようです。継承モデルはありますか、それとも完全に無関係な2つのクラスですか?それらが無関係である場合、両方が実装できる継承モデルまたはインターフェースを作成できますか?クラスを貼り付けると役立つ場合があります。

簡単に言えば、現在の形式でそのコードを操作しようとして時間を無駄にすることはありません。どんな犠牲を払っても、コンパイラ指令を排除する方法を見つけるでしょう。完全に回収できないようには見えませんが、多少の努力が必要になる場合があります。

于 2011-03-18T15:31:53.957 に答える
0

それが実用的かどうかはわかりませんが、これを処理するために、DVCSであるMercurialにブランチを作成することになるでしょう。

バグを修正したり、一般的なコードを追加したりする間、2つのブランチを使用し、3つ目のブランチを一時的に使用します。

初期バージョンを作成する方法は次のとおりです。

              5---6---7         <-- type 1 of library
             /
1---2---3---4
             \
              8---9--10         <-- type 2 of library

そのうちの1つだけのバグを修正するには:

              5---6---7--11     <-- bugfix or change only to type 1
             /
1---2---3---4
             \
              8---9--10

一般的なバグを修正するには:

              5---6---7--11--13--15    <-- merged into type 1
             /                   /
1---2---3---4--11--12---+-------+      <-- common fix(es)
             \           \
              8---9--10--14            <-- merged into type 2

:これは、タイプブランチまたは共通ブランチのいずれかで手間のかかるリファクタリングを行わないことを前提としています。そうする場合は、少なくともこのようなブランチの方法と比較して、現在の状況の方がおそらく良いでしょう。このようなリファクタリングは、将来のマージを非常に困難にします。

于 2011-03-18T16:05:20.320 に答える