单一职责原则:在 运行 次查询后将数据写入文件

Single Responsibility Principle: Write data to file after running a query

我必须将 运行 一个 sql 查询后生成的行写入文件。

# Run the SQL script.
my $dbh = get_dbh($source);
my $qry = $dbh->prepare("$sql_data");
$qry->execute();

# Dump the data to file.
open(my $fh_write, ">", "$filename");
while (my @data = $qry->fetchrow_array())
{
  print {$fh_write} join("\t", @data) . "\n";
}
close($fh_write);

很明显我在函数中做了两件事:

  1. 运行 sql 查询。
  2. 正在将数据写入文件。

有没有办法使用 SRP 做到这一点?

数据中有很多行,因此从单独的函数返回行数组可能不是个好主意。

您可以将其拆分为两个不同的函数。一个查询数据库,一个写入文件。

sub run_query {
    my ( $sql, @args ) = @_;

    # if you truly want separation of concerns,
    # you need to connect $dbh somewhere else

    my $sth = $dbh->prepare($sql);
    $sth->execute(@args);

    # this creates an iterator
    return sub {
        return $sth->fetchrow_arrayref;
    };
}

此函数接受一个 SQL 查询和一些参数(记得使用占位符!)并运行查询。它 return 是 代码参考 ,在 $sth 上关闭。每次调用该引用时,都会获取一行结果。当语句句柄 $sth 为空时,它将 return undef 传递给它,您就完成了迭代。这可能看起来有些矫枉过正,但请稍等片刻。

接下来,我们创建一个将数据写入文件的函数。

sub write_to_file {
    my ( $filename, $iter ) = @_;

    open my $fh, '>', $filename or die $!;

    while ( my $data = $iter->() ) {
        print $fh join( "\t", @{$data} ), "\n";
    }

    return;
}

这需要一个文件名和一个迭代器,它是一个代码引用。它打开文件,然后迭代,直到没有更多数据为止。每一行都写入文件。我们不需要 close $fh,因为它是一个词法文件句柄,一旦 $fh 在函数末尾超出范围,它将被隐式关闭。

您现在所做的是定义一个接口。您的 write_to_file 函数的接口是它接受一个文件名和一个总是 return 字段数组引用的迭代器。

让我们把它放在一起。

my $iter = run_query('SELECT * FROM orders');
write_to_file( 'orders.csv', $iter );

两行代码。一个运行查询,另一个写入数据。对我来说看起来很分离。

这种方法的好处是现在您还可以使用相同的代码将其他内容写入文件。例如,以下代码可以与某些 API 对话。它 returns 的迭代器再次为我们每次调用提供一行结果。

sub api_query {
    my ($customer_id) = @_;

    my $api = API::Client->new;
    my $res = $api->get_orders($customer_id); # returns [ {}, {}, {}, ... ]

    my $i = 0;
    return sub {
        return if $i == $#{ $res };
        return $res->[$i++];
    }
}

你可以把它放到上面的例子中而不是 run_query() 它会起作用,因为这个函数 returns 遵循相同的 接口 .您也可以创建具有相同部分接口的 write_to_apiwrite_to_slack_bot 函数。其中一个参数是同一种迭代器。现在这些也可以交换了。


当然,整个例子都非常做作。实际上,它在很大程度上取决于程序的大小和复杂性。

如果它是一个作为 cronjob 运行的脚本,每天只创建一次此报告,那么您不应该关心这种关注点分离。务实的方法可能是更好的选择。

一旦你拥有了很多这样的东西,你就会开始更加关心了。那么我的上述方法可能是可行的。但前提是你真的需要让事情变得灵活。

不是每个概念都适用,也不是每个概念都有意义。


请记住,有些工具更适合这些工作。您可以使用 Text::CSV_XS. Or you could use an ORM like DBIx::Class 并将 ResultSet 对象作为您的界面,而不是制作您自己的 CSV 文件。

你应该使用一个单独的函数来完成这项工作,在你的情况下,使用你当前的做事方式比坚持 SRP 更有意义。