1

twisted の上に構築された python プロジェクトのコードをリファクタリングしようとしています。settings.pyこれまでのところ、次のような定数と辞書を格納するために単純なモジュールを使用してきました。

#settings.py
MY_CONSTANT='whatever'
A_SLIGHTLY_COMPLEX_CONF= {'param_a':'a', 'param_b':b}

多くのモジュールsettings.pyは、自分の仕事をするためにインポートされます。

プロジェクトをリファクタリングする理由は、その場で構成パラメーターを変更/追加する必要があるためです。これから取ろうとしているアプローチは、すべての構成をシングルトンに集め、必要なときにいつでもそのインスタンスにアクセスすることです。

import settings.MyBloatedConfig

def first_insteresting_function():
    cfg = MyBloatedConfig.get_instance()
    a_much_needed_param = cfg["a_respectable_key"]
    #do stuff

#several thousands of functions later

def gazillionth_function_in_module():
    tired_cfg = MyBloatedConfig.get_instance()
    a_frustrated_value = cfg["another_respectable_key"]
    #do other stuff

このアプローチは機能しますが、非Python的で肥大化したように感じます. cfg別の方法は、次のように、モジュール内のオブジェクトを外部化することです。

CONFIG=MyBloatedConfig.get_instance()

def a_suspiciously_slimmer_function():
    suspicious_value = CONFIG["a_shady_parameter_key"]

MyBloatedConfig残念ながら、別のモジュールでインスタンス エントリを変更している場合、これは機能しません。私はリアクター パターンを使用しているため、キューを使用するだけでなく、スレッド ローカルにスタッフを格納することも問題ありません。

完全を期すために、以下はシングルトンパターンを実装するために使用している実装です

instances = {}
def singleton(cls):
    """ Use class as singleton. """
    global instances

    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class MyBloatedConfig(dict):
    .... 

異なるモジュール間で構成の変更をブロードキャストするための、より Pythonic な方法は他にありますか?

4

1 に答える 1

2

大きなグローバル(多くの場合シングルトン)構成オブジェクトはアンチパターンです。

settings.py、のスタイルのシングルトン、またはここで概説した他のアプローチのいずれかMyBloatedConfig.get_instance()を使用している場合でも、基本的に同じアンチパターンを使用しています。正確なスペルは重要ではありません。これらはすべて、プロジェクト全体のすべてのコードで共有される真のグローバル(Pythonモジュールレベルのグローバルとは異なる)を使用するための単なる方法です。

これは、いくつかの理由でアンチパターンです。

  • コードの単体テストが難しくなります。このグローバルに基づいて動作を変更するコードでは、さまざまな構成で動作を単体テストできるようにするために、何らかのハッキング(多くの場合モンキーパッチ)が必要になります。これを、引数(関数引数など)を受け入れるように記述され、渡された値に基づいて動作を変更するコードと比較してください。
  • コードの再利用性が低下します。構成はグローバルであるため、2つの異なる構成でその構成オブジェクトに依存するコードのいずれかを使用する場合は、フープをジャンプする必要があります。シングルトンは1つの構成のみを表すことができます。したがって、代わりに、グローバル状態を前後に交換して、必要な異なる動作を取得する必要があります。
  • コードが理解しにくくなります。グローバル構成を使用するコードを見て、それがどのように機能するかを知りたい場合は、構成を確認する必要があります。ただし、これよりもはるかに悪いのは、構成を変更する場合は、コードベース全体を調べて、これが影響を与える可能性のあるコードを見つける必要があることです。これにより、新しいアイテムを追加し、古いアイテムを削除または変更することはめったにないため、何かが壊れることを恐れて(または古いアイテムのすべてのユーザーを適切に追跡する時間がないため)、構成が時間の経過とともに大きくなります。

上記の問題は、解決策が何であるかを示唆するはずです。定数の値を知る必要がある関数がある場合は、その値を引数として受け入れるようにします。多くの値を必要とする関数がある場合は、それらの値を便利なコンテナーにまとめて、そのクラスのインスタンスを関数に渡すことができるクラスを作成します。

しばしば人々を悩ますこの解決策の部分は、彼らがこの議論の通過のすべてをタイプすることに時間を費やしたくない部分です。以前は1つまたは2つ(またはゼロ)の引数を取る可能性のある関数がありましたが、今では3つまたは4つの引数を取る必要がある可能性のある関数があります。また、のスタイルで記述されたアプリケーションを変換している場合 settings.py、一部の関数がグローバル構成から5ダース以上のアイテムを使用していることに気付く場合があり、これらの関数は突然非常に長い署名を持ちます。

これが潜在的な問題であることに異議を唱えることはありませんが、主に既存のコードの構造と構成に関する問題と見なす必要があります。非常に長い署名で終わる関数は、以前はそのすべてのデータに依存していました。事実はあなたから隠されていました。そして、あなたのプログラムの側面をあなたから隠すほとんどのプログラミングパターンと同様に、これは悪いことです。これらの値をすべて明示的に渡すと、抽象化が機能する必要がある場所がわかります。たぶん、その10個のパラメーター関数はあまりにも多くのことをしていて、3つの異なる関数としてうまく機能するでしょう。または、これらのパラメータの半分が実際に関連していて、常にコンテナオブジェクトの一部として一緒に属していることに気付くかもしれません。おそらく、それらのパラメーターの操作に関連するロジックをそのコンテナーオブジェクトに配置することもできます。

于 2013-02-08T01:35:43.757 に答える