5

これが私が間違っていることの簡単なことだといいのですが。「可変自殺」についてオンラインで見た目は良さそうですが、それは古いバージョンのもので、5.10.1を使用しています。

とにかく-私が宣言した変数-$RootDirectory-は突然その値を失い、その理由がわかりません。

問題を再現するためのスクリプトを次に示します。デバッグモード(perl -d)でスクリプトを実行すると、21行目と26行目で$ RootDirectoryを出力することができます。しかし、30行目で終わりました。

use strict;
my $RootDirectory; 
my @RootDirectories; 

@RootDirectories = (
   'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\'
   ,'c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\'
   ,'c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\'
   );

foreach $RootDirectory (@RootDirectories) { 
   # $RootDirectory = 'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\';
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema ();
} 

exit(0);

sub RunSchema() { 
   # print ' In RunSchema ' . $RootDirectory. "\n";
   CreateTables ();
} 

sub CreateTables() { 
   # print ' In CreateTables ' . $RootDirectory. "\n";
   SQLExecFolder ('tbl');
} 

sub SQLExecFolder() { 
   print ' In SQLExecFolder ' . $RootDirectory. "\n";       # Variable $RootDirectory value is gone by now
} 

編集すべてのコメントをありがとう!今のところ、うまく機能しているように見える「our」キーワードを使用すると思います。Nathanに感謝します。また、使用警告についてのツールにも感謝します-私はそれで売られていると思います!

混乱し続けているのは、デバッグモード(perl -d)を実行し、コードをステップ実行して「p $ RootDirectory」を実行すると、21行目と26行目で期待どおりの出力が得られたが、30行目では得られなかった理由です。 30行目で状況は異なりますか?

また、関数パラメーターとして$RootDirectoryを渡すことであるベストプラクティスについてのコメントに感謝します。それに続く関数が非常に多いため、これを避けたかったのです。つまり、RunSchemaはSQLExecFolderを呼び出すCreateTablesを呼び出します。それらすべてに同じパラメータを渡す必要があります。この場合でもそれは理にかなっていますか、それともこれを構造化するためのより良い方法はありますか?

4

7 に答える 7

8

ネイサンが言ったことは正しい。それはさておき、値を渡してみませんか?とにかくそれはより良い練習です:

foreach $RootDirectory (@RootDirectories) { 
   # $RootDirectory = 'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\';
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema ($RootDirectory);
} 

sub SQLExecFolder { 
   my $RootDirectory = shift;
   print ' In SQLExecFolder ' . $RootDirectory. "\n";
} 
于 2010-03-15T20:07:18.423 に答える
5

$RootDirectoryループ内のループ変数として宣言していforeachます。私が理解している限り、それはその値がlocalループにサイズ化され、その値がループの終わりに以前の値に復元されることを意味します。

あなたの場合、変数は決して割り当てられなかったので、ループの終わりにそれは前の値のに戻りますundef

編集:実際には、問題はで$RootDirectory宣言されてmyいるため、他のスコープでは定義されていません。関数RunSchemaではCreateTablesSQLExecFolderのローカリゼーションに関係なく、変数は未定義foreachです。

変数をstrictnessとして宣言したいが、グローバルにしたい場合は、次のように宣言$RootDirectoryourます。

our $RootDirectory;

編集:そうは言っても、グローバル変数を使用することは必ずしも良い考えではありません。他の人が示唆しているように、変数をパラメーターとして関数に渡す方がよいでしょう。

于 2010-03-15T20:04:51.030 に答える
5

他の人はあなたの質問に正しく答えました。コードに追加する必要があることを強調したいと思いuse warnings;ます。それはあなたの問題への手がかりを与えたでしょう、そしてそれはあなたに別の潜在的な危険を警告するでしょう。

于 2010-03-15T20:19:52.993 に答える
4

foreach変数は特別です-ループに対してローカルです。

変数の前にキーワードmyが付いている場合、それは字句スコープであるため、ループ内でのみ表示されます。それ以外の場合、変数は暗黙的にループに対してローカルであり、ループを終了すると以前の値に戻ります。変数が以前にmyで宣言されていた場合、グローバル変数の代わりにその変数を使用しますが、それでもループにローカライズされます。この暗黙のローカリゼーションは、foreachループでのみ発生します。

こちらをご覧ください

于 2010-03-15T20:06:01.910 に答える
2

foreachループのイテレータ変数は、常にループにローカライズされます。perlsynのforeachセクションを参照してください。パラメータとしてサブルーチンに渡すことができます。

于 2010-03-15T20:07:09.803 に答える
2

RE:いつグローバル変数を使用するのですか?

グローバル変数は、それらにアクセスするコードの任意の部分によっていつでも変更される可能性があるため、リスクがあります。さらに、変更がいつどこで発生したかを追跡することは困難であるため、変更による意図しない結果を追跡することは困難です。つまり、各グローバル変数は、それを使用するサブルーチン間の結合を増やします。

グローバルを使用する意味があるのはいつですか?利益がリスクを上回る場合。

ほとんどまたはすべてのサブルーチンに必要な多くの異なる値がある場合は、グローバル変数を使用するのが良い時期のようです。すべてのサブルーチン呼び出しを単純化し、コードをより明確にすることができますよね?

間違い。この場合、正しいアプローチは、これらすべての個別の変数を1つのコンテナーデータ構造に集約することです。foo( $frob, $grizzle, $cheese, $omg, $wtf );だからあなたの代わりにfoo( $state, $frob ); Whereがあります$state = { grizzle => $grizzle, cheese => $cheese, omg => $omg, wtf => $wtf };

これで、渡す変数が1つあります。これらのサブ呼び出しはすべてはるかに簡単です。それでも、これは面倒であり、各ルーチンから余分な引数をクリーンアップする必要があります。

この時点で、いくつかのオプションがあります。

  1. $stateグローバルにして、直接アクセスしてください。
  2. 構成オブジェクト$stateを作成し、メソッドを使用して属性へのアクセスを制御します。
  3. モジュール全体をクラスにし、すべての状態情報をオブジェクトに格納します。

オプション1は、ルーチンが少ない小さなスクリプトに使用できます。エラーをデバッグするのが難しいリスクはわずかです。

オプション2は、モジュール内の異なるルーチン間に明らかな関係がない場合に意味があります。グローバル状態オブジェクトを使用すると、それにアクセスするコード間の結合が減少するため、役立ちます。また、グローバルデータへの変更を追跡するためにログを追加する方が簡単です。

オプション3は、同じデータを操作する密接に関連する関数のグループがある場合に適切に機能します。

あなたのサンプルコードはオプション3の良い候補のようです。私はというクラスを作成MySchemaし、特定のディレクトリで動作するすべてのメソッドがメソッドになりました。呼び出し元のオブジェクトは、必要なデータを持っています。

これで、きれいでクリーンなコードができ、グローバルはありません。

use strict;
use warnings;

my @directories = (
   'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\',
   'c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\',
   'c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\',
);

for my $schema ( make_schemata(@directories) ) {

    $schema->run;

}

sub make_schemata {
    my @schemata = map { MySchema->new( directory => $_ } @_;

    return @schemata;
}


BEGIN {
    package MySchema;

    use Moose;

    has 'directory' => (
        is => 'ro',
        isa => 'Str',
        required => 1,
    );

    sub run { 
       my $self = shift;

       $self->create_tables;
    } 

    sub create_tables { 
       my $self = shift;

       $self->sql_exec_folder('tbl');
    }

    sub sql_exec_folder {
        my $self = shift;

        my $dir = $self->directory;

        print "In SQLExecFolder $dir\n";
    }
    
    1;
} 

ボーナスとして、BEGINブロックのコードを削除して別のファイルに配置し、別のスクリプトで再利用することができます。本格的なモジュールである必要があるのは、という名前の独自のファイルだけですMySchema.pm

于 2010-03-16T15:45:01.323 に答える
0

悪い努力ではありません。ここにいくつかの小さな改善と、$RootDirectory変数がループ内にスコープされている(つまり制限されている)ため、関数パラメーターとして変数をサブルーチンに渡すという1つの「修正」があります。foreach一般に、さまざまなサブルーチンによって渡されたりアクセスされたりする変数を明示するためにも、良い習慣と見なされます。

use strict;
use warnings;

sub RunSchema() {
   my $root_dir = shift;
   CreateTables($root_dir);
}

sub CreateTables() {
   my $root_dir = shift;
   SQLExecFolder('tbl', $root_dir);
}

sub SQLExecFolder() {
   my ($name, $root_dir) = @_;
}
######################################################


my @RootDirectories = qw(
   c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\
   c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\
   c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\
);

foreach my $RootDirectory (@RootDirectories) {
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema($RootDirectory);
}

exit(0);
于 2010-03-15T20:13:15.717 に答える