web-dev-qa-db-ja.com

動的スコープを持つ言語で安全にリファクタリングするにはどうすればよいですか?

ダイナミックスコープの言語で動作しないという幸運をお持ちの方のために、その動作について少しおさらいしておきます。 「RUBELLA」と呼ばれる、次のように動作する疑似言語を想像してみてください。

_function foo() {
    print(x); // not defined locally => uses whatever value `x` has in the calling context
    y = "tetanus";
}
function bar() {
    x = "measles";
    foo();
    print(y); // not defined locally, but set by the call to `foo()`
}
bar(); // prints "measles" followed by "tetanus"
_

つまり、変数はコールスタックを自由に上下に伝播します。fooで定義されたすべての変数は、その呼び出し元barから可視であり、変更可能です。逆も同様です。これは、コードのリファクタリングに深刻な影響を及ぼします。次のコードがあるとします。

_function a() { // defined in file A
    x = "qux";
    b();
}
function b() { // defined in file B
    c();
}
function c() { // defined in file C
    print(x);
}
_

現在、a()を呼び出すと、quxが出力されます。しかし、いつか、あなたはbを少し変更する必要があると判断しました。すべての呼び出しコンテキスト(一部は実際にはコードベースの外にある可能性があります)を知っているわけではありませんが、それで問題ありません-変更はbの内部で完全に行われますよね?したがって、次のように書き換えます。

_function b() {
    x = "oops";
    c();
}
_

また、ローカル変数を定義したばかりなので、何も変更していないと思われるかもしれません。しかし、実際、あなたはaを壊しました!現在、aoopsではなくquxを出力します。


疑似言語の領域からこれを取り戻すと、構文は異なりますが、これがMUMPSの動作とまったく同じです。

MUMPSの最新(「モダン」)バージョンには、いわゆるNEWステートメントが含まれています。これにより、変数が呼び出し先から呼び出し元にリークするのを防ぐことができます。したがって、上記の最初の例では、foo()で_NEW y = "tetanus"_を実行した場合、print(y)bar()は何も出力しません(MUMPSではすべての名前明示的に他に設定しない限り、空の文字列をポイントします)。しかし、変数が呼び出し元から呼び出し先にリークするのを防ぐことができるものは何もありません。function p() { NEW x = 3; q(); print(x); }がある場合、知っている限り、q()xを変更できますが、パラメータとして明示的にxを受け取りません。これはまだ悪い状況ですが、おそらく以前はそうだったのでas悪くはありません。

これらの危険性を念頭に置いて、動的スコープを使用してMUMPSまたはその他の言語でコードを安全にリファクタリングするにはどうすればよいですか?

自分で初期化(NEW)したり、明示的なパラメーターとして渡したりする変数以外の関数で変数を決して使用せず、パラメーターare関数の呼び出し元から暗黙的に渡されます。しかし、数十年前に、〜108-LOCコードベース。これらは、1つにはない贅沢です。

そしてもちろん、レキシカルスコープを持つ言語でリファクタリングするための基本的にすべての優れたプラクティスは、動的スコープを持つ言語にも適用できます(テストの作成など)。問題は次のとおりです:リファクタリング時に動的スコープコードの脆弱性の増加に関連するリスクをどのように軽減しますか?

動的言語で書かれたコードをどのようにナビゲートしてリファクタリングするのですか? はこの質問に似たタイトルを持っていますが、それは完全に無関係です。)

13
senshin

あなたのベストショットは、完全なコードベースを自分の管理下に置くことであり、モジュールとその依存関係についての概要があることを確認することです。

したがって、少なくともグローバル検索を実行する機会があり、コードの変更による影響が予想されるシステムの部分に回帰テストを追加する機会があります。

最初を達成する機会がない場合、私の最善のアドバイスは次のとおりです。他のモジュールによって再利用されているモジュール、または他のモジュールが他のモジュールに依存していることを知らないモジュールはリファクタリングしないでください。適切なサイズのコードベースでは、他のモジュールが依存していないモジュールを見つける可能性が高くなります。そのため、Bに依存するmod Aがあり、その逆ではなく、他のモジュールがAに​​依存していない場合、動的スコープ言語でも、Bまたは他のモジュールを壊すことなくAに変更を加えることができます。

これにより、AからBへの依存関係をAからB2への依存関係で置き換える機会が得られます。B2は、Bのサニタイズされ、書き換えられたバージョンです。より進化し、リファクタリングが容易になります。

2
Doc Brown

明らかなことを述べるには:ここでリファクタリングを行う方法は?非常に慎重に進んでください。

(あなたがそれを説明したように、既存のコードベースを開発して維持することは、それをリファクタリングしようとすることはもちろんのこと、十分難しいはずです。)

ここでは、テスト駆動アプローチをさかのぼって適用すると思います。これには、最初にテストを簡単にするために、リファクタリングを開始しても現在の機能が機能し続けることを確認する一連のテストを作成することが含まれます。 (はい、コードがモジュール化されていて、まったく変更せずにテストできるようになっている場合を除き、ここでは鶏と卵の問題が発生します。)

次に、他のリファクタリングを続行し、進行中にテストを中断していないことを確認できます。

最後に、新しい機能を期待するテストの作成を開始し、それらのテストを機能させるコードを作成できます。

0
Mark Hurd