アンチパターンsettings.py



ハブラピトナーへこんにちは!

時折、問題をうまく解決するためではなく、人気のあるXフレームワークで行われるために存在する開発パターンに出くわすため、多くの人がそれが良いと考えています。

ここで、「すべての設定がsettings.pyにある」パターンについて不満を言いたいと思います。 彼がDjangoのおかげで人気を得たことは明らかです。 私は何度も何度も何度も同じ物語に結び付けられていないプロジェクトで会ってきました:大きなコードベース、互いに接続されていない小さくてきれいなコンポーネント、そしてここにいます:任意の場所からすべて一緒に、魔法の設定にクロールします定数。

それで、なぜ私のアプローチではこのようなアプローチが嫌なのでしょうか。


カスケードの問題



実際のプロジェクトでは、原則として、ローカルホストでプロジェクトを実行するため、unittestを実行するため、およびバトルサーバー上のすべてをオンにするために、少なくとも3セットの設定が必要です。 この場合、ほとんどの設定は通常すべての場合に一致し、一部は異なります。

たとえば、MongoDBをストレージとして使用します。 一般的に、localhostでmy_projectし、 my_projectという名前のDBを使用する必要があります。 ただし、unittestを実行するには、戦闘データを傷つけないように別の名前のDBを取得する必要がありunittests 。 また、実稼働環境の場合、localhostではなく、非常に特定のIP、monguで指定されたサーバーに接続する必要があります。

では、外部条件に応じて、 settings.MONGODB_ADDRESS settings.MONGODB_ADDRESSはどのように異なる値をとるのでしょうか? 通常、最後に使用されるvoodooコンストラクトは、 __import____import__vars()try/except ImportErrorで構成されます。これは、settings_local.pyのような別のモジュールのすべてのtry/except ImportErrorで名前空間を補完およびオーバーラップしようとします。

さらにロードする必要があるのは、ハードコードまたは環境変数によって設定される_local.pyです。 いずれにせよ、同じユニットテストが起動時にのみ設定を有効にするには、タンバリンと踊り、Zen of Pythonに違反する必要があります:明示的は暗黙的よりも優れています。

さらに、そのような解決策は、以下で説明する別の問題に関連しています。

実行可能コード



設定を実行可能なpy-codeとして保存するのは気味が悪い。 実際、パターン全体は、最初はおそらくシンプルでエレガントなソリューションであるように見えました。「Pythonですべてを実行できるのに、なぜCFGパーサーが必要なのですか? そして、さらに機会があります!」 些細なことよりも少し複雑なシナリオでは、ソリューションは横向きになります。 たとえば、次のようなスニペットを考えてみましょう。

 # settings.py BASE_PATH = os.path.dirname(__file__) PROJECT_HOSTNAME = 'localhost' SOME_JOB_COMMAND = '%s/bin/do_job.py -H %s' % (BASE_PATH, PROJECT_HOSTNAME) # settings_production.py PROJECT_HOSTNAME = 'my-project.ru' 


問題が何であるか理解していますか? SOME_JOB_COMMAND合計値について、ドラム上のPROJECT_HOSTNAME値を絶対にブロックしたこと。 オーバーラップ後にSOME_JOB_COMMANDの定義をコピーSOME_JOB_COMMANDペーストするように歯をSOME_JOB_COMMANDことができますが、これも不可能です。その場合は別のモジュールでBASE_PATHなります。 それもコピー&ペーストしますか? 多すぎる?

構成としての実行可能コードは、アプリケーションが新しい環境で起動したときに、 ImportErrorにつながるという事実については話していない。

したがって、ハエは個別に、カツレツは個別に作成する必要があると確信しています。py-moduleで計算されたテキストファイルの基本値です。

高結合



優れたプロジェクトとは、小さなキューブに分割でき、各キューブを本格的なオープンソースプロジェクトとしてgithubに配置できるプロジェクトです。

すべてがそうであるが、1つのBUTで:「プロジェクトのルートにFOO_BAZを設定し、 FOO_BARFOO_BAZ 、およびFOO_QUX設定があったことをFOO_BAZしてFOO_QUX 」 そして、何かがばかげているように聞こえる場合、通常、この不条理が生じる状況があることを意味します。

私たちの場合、この例は長い間自分自身を発明しません。 アプリケーションをVKontakte APIとVKontakteProfileCacheさせると、額にsettings.VK_API_KEYsettings.VK_API_SECRETを使用するVKontakteProfileCacheようなものがVKontakteProfileCache settings.VK_API_SECRET 。 まあ、彼はそれを使用し、それを使用し、そして再び、そして私たちのプロジェクトはいくつかのVKontakteアプリケーションですぐに動作し始めるはずです。 それVKontakteProfileCache 、1組の資格情報のみで機能するように設計されています。

したがって、設定モジュールに直接アクセスしない方がより調和的で便利です。 必要に応じて、コンストラクターパラメーター、依存関係注入フレームワーク、および直接ではなく、すべてのコンシューマーが必要な設定を受け入れるようにします。 if __name__ == '__main__'コードのような最も低いレベルで特定の設定を引き出します。 そして、彼はどこからそれらを手に入れましたか-彼の個人的な問題。 このアプローチにより、単体テストも大幅に簡素化されます。実行する必要のある設定と、作成する設定があります。

可能な解決策



それで、「settings.py」パターンは泥を注ぎました。 気分が良くなりました、ありがとう。 次に、可能な解決策について説明します。 私はいくつかのプロジェクトで同様のアプローチを使用しましたが、リストされた問題がなく便利であることがわかりました。

設定は、テキストのiniスタイルのファイルに保存されます。 解析にはConfigObjを使用します。これは、標準のConfigParserと比較して豊富な機能があり、特に、カスケードを作成するのは非常に簡単です。

プロジェクトでは、基本設定ファイルdefault_settings.cfgを、可能なすべての設定と適切なデフォルトの値で開始します。

configure_from_files()configure_for_unittests()などの関数を使用してutils.configモジュールを作成します。これらの関数は、さまざまな状況の設定を持つオブジェクトを返します。 configure_from_files()は、カスケードファイル検索を整理します: default_settings.cfg~/.my-project.cfg~/.my-project.cfg /etc/my-project.cfg ~/.my-project.cfg /etc/my-project.cfg ~/.my-project.cfgおよびおそらく他の場所。 それはすべてプロジェクトに依存します。

計算された設定は、構成オブジェクトのアセンブリの最後のステップとして評価されます。

モジュール自体は、プロセスまたはテストの起動によってのみ使用されます。 設定に関心のあるすべてのクラスは、インジェクションを通じて既製の値を受け取ります。つまり、設定と直接通信しません。 実際、これは暗闇を全体として構成オブジェクトに転送する方が良い場合には必ずしも便利ではありませんが、 転送する必要があるという事実を否定するものではありません-直接の接触はありません。

おそらく、設定などの「些細なこと」について書きすぎたのでしょう。 しかし、少なくとも読んだ後、誰かが何かに完璧なアプローチとはほど遠いものを盲目的にコピーする前に考え、それをより良く、より簡単に、もっと楽しくするなら、この投稿の使命は完了すると思います。

Source: https://habr.com/ru/post/J115893/


All Articles