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

pra*_*kur 2 perl single-responsibility-principle

我必须将运行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);
Run Code Online (Sandbox Code Playgroud)

显然,我在一个函数中做了两件事:

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

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

数据中有很多行,因此从单独的函数返回行数组可能不太好.

sim*_*que 6

您可以将其拆分为两个不同的功能.一个人会查询数据库,另一个人会将数据写入文件.

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;
    };
}
Run Code Online (Sandbox Code Playgroud)

这个函数接受一个SQL查询和一些参数(记得使用占位符!)并运行查询.它返回一个关闭的代码引用$sth.每次调用该引用时,都会获取一行结果.当语句句柄$sth为空时,它将返回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;
}
Run Code Online (Sandbox Code Playgroud)

这需要一个文件名和一个迭代器,它是一个代码引用.它打开文件,然后迭代,直到没有剩下的数据.每行都写入文件.我们不需要close $fh因为它是一个词法文件句柄,一旦$fh在函数结束时超出范围就会隐式关闭.

你现在所做的是定义一个界面.你的write_to_file函数的接口是它需要一个文件名和一个迭代器,它总是返回字段的数组引用.

我们把它们放在一起吧.

my $iter = run_query('SELECT * FROM orders');
write_to_file( 'orders.csv', $iter );
Run Code Online (Sandbox Code Playgroud)

两行代码.一个运行查询,另一个运行数据.看起来很分开给我.

这种方法的好处是,现在您还可以使用相同的代码将其他内容写入文件.例如,以下代码可以与某些API进行通信.它再次返回的迭代器为每个调用提供了一行结果.

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 Code Online (Sandbox Code Playgroud)

您可以将其放入上面的示例中而不是run_query()它可以工作,因为此函数返回粘附到同一接口的内容.您也可以制作具有相同部分界面的功能write_to_apiwrite_to_slack_bot功能.其中一个参数是同一种迭代器.现在这些也是可以交换的.


当然,整个例子都是非常人为的.实际上,它在很大程度上取决于程序的大小和复杂程度.

如果它是一个作为cronjob运行的脚本,除了每天创建一次此报告之外什么都不做,那么您不应该关心这种关注点的分离.实用的方法可能是更好的选择.

一旦你有很多这些,你就会开始关心更多.那么我的上述方法可能是可行的.但只有你真的需要灵活的东西.

并非每个概念都适用,并非每个概念都有意义.


请记住,有些工具更适合这些工作.您可以使用Text :: CSV_XS,而不是制作自己的CSV文件.或者您可以使用类似DBIx :: Class的ORM 并将ResultSet对象作为您的接口.