"グローバル変数を避ける"というアドバイスを聞いたことがあります。これは良いアドバイスです。グローバル変数がどこでどのように使用されているかを把握するのは難しいからです。 そして、 "名前空間を汚染する"(あなたのローカル変数と衝突する可能性のある名前の束を置く)ことによって、コードは、ローカル変数を使用しようとしているときにグローバル変数を誤って変更する可能性があります。
実際、グローバル変数だけでなく、すべての変数の「範囲を縮小する」ことは良い考えです。
多くのプログラミング言語は、モジュール、クラス、関数、ブロックスコープなど、複数のスコープ/アクセスレベルを提供します。 より制限されたアクセスを使用することは、コードの行数を減らして変数を「見える」ことができるという意味で一般的に優れています。
なぜこれを行うのですか? なぜなら、読者が同時に考えなければならない変数の数を効果的に減らすからです。 すべての変数の範囲を2倍に縮小した場合、平均して一度に範囲の半分の変数が存在します。
たとえば、次のように2つのメソッドだけで使用されるメンバ変数を使用して、非常に大きなクラスがあるとします。
class LargeClass {
string str_;
void Method1() {
str_ = ...;
Method2();
}
void Method2() {
// Uses str_
}
// Lots of other methods that don't use str_ ...
};
ある意味では、クラスメンバ変数は、クラスの領域内の「ミニグローバル」のようなものです。 大規模なクラスの場合、特に、すべてのメンバー変数を追跡するのは難しく、どのメソッドがそれぞれを変更するのかが難しいです。 ミニグローバルの数が少ないほど良い。
この場合、str_をローカル変数に「降格」することは意味があります:
class LargeClass {
void Method1() {
string str = ...;
Method2(str);
}
void Method2(string str) {
// Uses str
}
// Now other methods can't see str.
};
クラスメンバーへのアクセスを制限する別の方法は、可能な限り多くのメソッドを静的にすることです。 静的メソッドは、読者に「これらのコード行がこれらの変数から分離されている」ことを知らせるのに最適な方法です。
あるいは、大きなクラスを小さなクラスに分割する方法もあります。 このアプローチは、より小さいクラスが実際には互いに分離されている場合にのみ役立ちます。 お互いのメンバーにアクセスする2つのクラスを作成する場合、あなたは本当に何も達成していません。
大きなファイルを小さいファイルに分割するか、大きな関数を小さな関数に分割する場合も同じです。 そのための大きな動機は、データ(すなわち、変数)を分離することである。
しかし、異なる言語は、スコープを正確に構成するためのルールが異なります。 変数の範囲に関するもっと興味深い規則のほんのいくつかを指摘したいと思います。
ifステートメントスコープをC ++で、次のC ++コードがあるとします。
PaymentInfo* info = database.ReadPaymentInfo();
if (info) {
cout << "User paid: " << info->amount() << endl;
}
// Many more lines of code below ...
変数infoは残りの関数のスコープ内にとどまるので、これを読んでいる人
コードが情報を念頭に置いて、それが再び使用されるかどうかを疑問に思うかもしれません。
しかし、この場合、infoはif文の内部でのみ使用されます。 C ++では、実際には
条件式の情報:
if (PaymentInfo* info = database.ReadPaymentInfo()) {
cout << "User paid: " << info->amount() << endl;
}
今では、読者は情報が範囲外になった後、情報を簡単に忘れることができます。
JavaScriptでの「プライベート」変数の作成
1つの関数だけで使用される永続変数があるとします。
submitted = false; // Note: global variable
var submit_form = function (form_name) {
if (submitted) {
return; // don't double-submit the form
}
...
submitted = true;
};
提出されたようなグローバル変数は、このコードを読んでいる人に大きな心配を引き起こす可能性があります。 それはsubmit_form()がサブミットを使用する唯一の機能だとは思われますが、あなたは確かにそれを知ることができません。 実際、別のJavaScriptファイルでは、別の目的のためにsubmitという名前のグローバル変数を使用している可能性があります。
クロージャ内で送信されたラッピングによって、この問題を防ぐことができます。
var submit_form = (function () {
var submitted = false; // Note: can only be accessed by the function below
return function (form_name) {
if (submitted) {
return; // don't double-submit the form
}
...
submitted = true;
};
}());
最後の行のかっこに注意してください。匿名の外部関数はすぐに実行されますが、
内部関数を返します。
以前この技術を見たことがないなら、最初は奇妙に見えるかもしれません。 内部関数だけがアクセスできる「プライベート」スコープを作成するという効果があります。 今読者はどこに提出されたのか不思議に思う必要はありませんか? 同じ名前の他のグローバルと競合することを心配する必要があります。 (JavaScript:Douglas CrockfordのGood Parts [O'Reilly、2008]を参照してください)。
JavaScriptのグローバルスコープ
JavaScriptでは、変数定義(var x = 1ではなくx = 1など)からキーワードvarを省略すると、変数はグローバルスコープに配置され、すべてのJavaScriptファイルとブロックがグローバルスコープにアクセスできます。 次に例を示します。
<script>
var f = function () {
// DANGER: 'i' is not declared with 'var'!
for (i = 0; i < 10; i += 1) ...
};
f();
</script>
このコードは私を不注意にグローバルスコープに入れます。したがって、後のブロックでもそれを見ることができます:
<script>
alert(i); // Alerts '10'. 'i' is a global variable!
</script>
多くのプログラマーはこのスコープルールを認識しておらず、この驚くべき動作は奇妙なバグを引き起こす可能性があります。 このバグは、2つの関数が同じ名前のローカル変数を作成しても、varを使うのを忘れたときによく見られます。 これらの機能は無意識のうちに「クロストーク」し、貧しいプログラマは自分のコンピュータが所有しているとか、RAMが悪いと結論づけることがあります。
JavaScriptの一般的なベストプラクティスは、varキーワード(var x = 1など)を使用して常に変数を定義することです。 この方法は、変数のスコープを、定義されている(最も内側の)関数に制限します。
PythonとJavaScriptでネストされたスコープはありません
C ++やJavaなどの言語では、if、for、tryなどの構造体内で定義された変数がそのブロックのネストされたスコープに限定されるブロックスコープがあります。
if (...) {
int x = 1;
}
x++; // Compile-error! 'x' is undefined.
しかし、PythonやJavaScriptでは、ブロック内で定義された変数が関数全体に「流出」します。 たとえば、この完全に有効なPythonコードでexample_valueを使用していることに注意してください。
# No use of example_value up to this point.
if request:
for value in request.values:
if value > 0:
example_value = value
break
for logger in debug.loggers:
logger.log("Example:", example_value)
このスコープルールは多くのプログラマーにとって驚くべきことですが、このようなコードは読みにくいです。 他の言語では、example_valueが最初に定義された場所を見つける方が簡単です。内部にある関数の「左端」に沿って見てください。
前の例もバグです。コードの最初の部分にexample_valueが設定されていない場合、2番目の部分で例外が発生します。 "NameError: 'example_value'が定義されていません。 これを修正し、コードをより読みやすくするために、 "最も近い共通の祖先"(入れ子にして)でexample_valueを使用する場所に定義します。
example_value = None
if request:
for value in request.values:
if value > 0:
example_value = value
break
if example_value:
for logger in debug.loggers:
logger.log("Example:", example_value)
ただし、これはexample_valueを完全に削除できる場合です。 example_valueは中間結果を保持しているだけで、「中級者を排除する」
95ページの「結果」を参照してください。これらの変数は、「できるだけ早くタスクを完了させる」ことで、しばしば排除できます。
新しいコードは次のようになります:
def LogExample(value):
for logger in debug.loggers:
logger.log("Example:", value)
if request:
for value in request.values:
if value > 0:
LogExample(value) # deal with 'value' immediately
break
定義を下に移動する
元のCプログラミング言語では、すべての変数定義を関数またはブロックの先頭に置く必要がありました。 多くの変数を持つ長い関数では、あまり時間がかからずにすぐに使用されなくても、読者はこれらの変数についてすぐに考えるようになったため、この要件は残念でした。 (C99とC ++はこの要件を削除しました)。
次の例では、すべての変数が関数の先頭に無意味に定義されています。
def ViewFilteredReplies(original_id):
filtered_replies = []
root_message = Messages.objects.get(original_id)
all_replies = Messages.objects.select(root_id=original_id)
root_message.view_count += 1
root_message.last_view_time = datetime.datetime.now()
root_message.save()
for reply in all_replies:
if reply.spam_votes <= MAX_SPAM_VOTES:
filtered_replies.append(reply)
return filtered_replies
このサンプルコードの問題点は、読者が3つの変数を一度に考え、それらの間でギアを切り替えることです。
読者は後でそれを知る必要はないので、最初の使用の直前に各定義を移動するのは簡単です。
ef ViewFilteredReplies(original_id):
root_message = Messages.objects.get(original_id)
root_message.view_count += 1
root_message.last_view_time = datetime.datetime.now()
root_message.save()
all_replies = Messages.objects.select(root_id=original_id
filtered_replies = []
for reply in all_replies:
if reply.spam_votes <= MAX_SPAM_VOTES:
filtered_replies.append(reply)
return filtered_replies
all_repliesが必要な変数であるのか、それとも排除できるのか不思議に思うかもしれません
行うことによって:
for reply in Messages.objects.select(root_id=original_id):
...
この場合、all_repliesはかなり説明変数なので、私たちはそれを保つことに決めました。
参考文献:
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック
The Art of Readable Code: Simple and Practical Techniques for Writing Better Code (English Edition)
The Art of Readable Code: Simple and Practical Techniques for Writing Better Code Kindle (English Edition)