我的一位同事最近采访了一些求职者,其中一位表示他们有很好的Perl经验。
由于我的同事不认识Perl,他要求我批评一些潜在雇员编写的(异地)代码,所以我看了一眼并告诉他我的担忧(主要原因是它最初没有评论,这不像我们给他们足够的时间)。
然而,代码有效,所以我不愿意在没有更多输入的情况下说不行。另一个问题是,这段代码基本上看起来就像我在C中编写代码一样。自从我做Perl以来已经有一段时间了(我没有做很多事情,我更喜欢Python脚本用于快速脚本)但我似乎回想一下,这是一个比这个人使用的更具表现力的语言。
我正在寻找来自真正的Perl程序员的输入,以及如何改进它的建议(以及为什么Perl程序员 应该知道改进方法)。
你也可以抒情地说出用一种完全不同的语言写一种语言的人应该(或不应该被雇用)。我对你的论点很感兴趣,但这个问题主要是对代码的批评。
规范是按如下方式成功处理CSV文件并输出各个字段:
User ID,Name , Level,Numeric ID
pax, Pax Morgan ,admin,0
gt," Turner, George" rubbish,user,1
ms,"Mark \"X-Men\" Spencer","guest user",2
ab,, "user","3"
输出是这样的(潜在的雇佣代码实际输出了这个):
User ID,Name , Level,Numeric ID:
[User ID]
[Name]
[Level]
[Numeric ID]
pax, Pax Morgan ,admin,0:
[pax]
[Pax Morgan]
[admin]
[0]
gt," Turner, George " rubbish,user,1:
[gt]
[ Turner, George ]
[user]
[1]
ms,"Mark \"X-Men\" Spencer","guest user",2:
[ms]
[Mark "X-Men" Spencer]
[guest user]
[2]
ab,, "user","3":
[ab]
[]
[user]
[3]
以下是他们提交的代码:
#!/usr/bin/perl
# Open file.
open (IN, "qq.in") || die "Cannot open qq.in";
# Process every line.
while (<IN>) {
chomp;
$line = $_;
print "$line:\n";
# Process every field in line.
while ($line ne "") {
# Skip spaces and start with empty field.
if (substr ($line,0,1) eq " ") {
$line = substr ($line,1);
next;
}
$field = "";
$minlen = 0;
# Detect quoted field or otherwise.
if (substr ($line,0,1) eq "\"") {
$line = substr ($line,1);
$pastquote = 0;
while ($line ne "") {
# Special handling for quotes (\\ and \").
if (length ($line) >= 2) {
if (substr ($line,0,2) eq "\\\"") {
$field = $field . "\"";
$line = substr ($line,2);
next;
}
if (substr ($line,0,2) eq "\\\\") {
$field = $field . "\\";
$line = substr ($line,2);
next;
}
}
# Detect closing quote.
if (($pastquote == 0) && (substr ($line,0,1) eq "\"")) {
$pastquote = 1;
$line = substr ($line,1);
$minlen = length ($field);
next;
}
# Only worry about comma if past closing quote.
if (($pastquote == 1) && (substr ($line,0,1) eq ",")) {
$line = substr ($line,1);
last;
}
$field = $field . substr ($line,0,1);
$line = substr ($line,1);
}
} else {
while ($line ne "") {
if (substr ($line,0,1) eq ",") {
$line = substr ($line,1);
last;
}
if ($pastquote == 0) {
$field = $field . substr ($line,0,1);
}
$line = substr ($line,1);
}
}
# Strip trailing space.
while ($field ne "") {
if (length ($field) == $minlen) {
last;
}
if (substr ($field,length ($field)-1,1) eq " ") {
$field = substr ($field,0, length ($field)-1);
next;
}
last;
}
print " [$field]\n";
}
}
close (IN);
答案 0 :(得分:165)
我建议人们永远不要雇用Perl程序员,C程序员或Java程序员,等等。只是雇用好人。我聘请编写Perl的程序员也熟练掌握其他各种语言。我聘请他们是因为他们是优秀的程序员,优秀的程序员可以处理多种语言。
现在,该代码确实看起来很像C,但我认为Perl也很好。如果你正在招聘一名优秀的程序员,在他的腰带上进行一些Perl练习,他会很好地追赶。人们抱怨缺乏正则表达式,这会使辅助领域的事情变得更简单,但我不希望任何人在解析那些脏的CSV数据时使用正则表达式解决方案。我不想阅读或维护它。
我经常发现反向问题更麻烦:聘请一位编写好Perl代码的优秀程序员,但团队的其他成员只知道Perl的基础知识并且无法跟上。这与糟糕的格式化或糟糕的结构无关,只与高级主题(例如闭包)的技能水平无关。
在这场辩论中事情变得有点激烈,所以我想我应该更多地解释一下我是如何处理这类事情的。我不认为这是正则表达式/非正则表达式问题。我不会像候选人那样编写代码,但这并不重要。
我也写了很多糟糕的代码。在第一遍,我通常更多地考虑结构和过程而不是语法。我后来回去把它收紧。这并不意味着候选人的代码是好的,但对于在面试中完成的第一次传球我不会过于严厉地判断。我不知道他有多少时间写它等等,所以我不会根据我需要很长时间才能做的事来判断它。面试问题总是很奇怪,因为你不能做你真正为实际工作所做的事情。如果我不得不从头开始并在15分钟内完成它,我可能也没有关于编写CSV解析器的问题。事实上,我今天浪费的时间远远超过了一些带有一些代码的傻瓜。
我去了Text::CSV_PP的代码,纯粹的Perl堂兄Text::CSV_XS。它使用正则表达式,但是许多正则表达式处理特殊情况,并且在结构上与此处提供的代码没有什么不同。这是很多代码,而且复杂的代码我希望我再也不用看了。
我倾向于不喜欢的是面试答案,只能解决给定的输入问题。在现实世界中,这几乎总是错误的,你必须处理你可能还没有发现的案例,你需要灵活处理未来的问题。我发现Stackoverflow上的很多答案都缺少了。解决方案的思维过程对我来说更有说服力。人们比他们改变对事物的思考方式更容易熟练掌握语言。我可以教人们如何写出更好的Perl,但我不能在大多数情况下更换他们的湿件。这来自于伤疤和经验。
由于我不在那里看候选代码解决方案或问他后续问题,我不会推测为什么他按照他的方式写它。对于我在这里看到的其他一些解决方案,我在采访中也同样严厉。
职业生涯是一段旅程。我不希望每个人都成为一个大师或拥有相同的经历。如果我因为不知道某些伎俩或成语而注销人,我就不会给他们继续他们旅程的机会。候选人的代码不会赢得任何奖项,但显然足以让他进入最后三个考虑提供奖金。那个人站起来尝试,比我生命中看到的很多代码做得更好,这对我来说已经足够了。
答案 1 :(得分:84)
他的代码有点冗长。 Perl是关于模块和避免的 他们让你的生活变得艰难。这相当于您发布的内容 我在大约两分钟内写的:
#!/usr/bin/env perl
use strict;
use warnings;
use Text::CSV;
my $parser = Text::CSV->new({
allow_whitespace => 1,
escape_char => '\\',
allow_loose_quotes => 1,
});
while(my $line = <>){
$parser->parse($line) or die "Parse error: ". $parser->error_diag;
my @row = $parser->fields;
print $line;
print "\t[$_]\n" for @row;
}
答案 2 :(得分:43)
我认为在Perl中编写C比在C语言中编写Perl要好得多。正如在SO播客中经常提到的那样,理解C是一种并非现在所有开发人员(甚至是一些好的开发人员)的美德。雇用他们并为他们购买Perl Best Practices的副本,你将被设置。经过最佳实践后,Intermediate Perl的副本可以解决。
答案 3 :(得分:42)
它不是可怕的惯用Perl,但它也不是完全可怕的Perl(尽管它可能更加紧凑)。
两个警告铃声 - shebang行不包含“-w
”且既没有“use strict;
”也没有“use warnings;
”。这是非常老式的Perl;好的Perl代码使用警告和严格。
不再推荐使用旧式文件句柄,但它不会自动坏(可能是10年前编写的代码)。
不使用正则表达式会更令人惊讶。例如:
# Process every field in line.
while ($line ne "") {
# Skip spaces and start with empty field.
if (substr ($line,0,1) eq " ") {
$line = substr ($line,1);
next;
}
可以这样写:
while ($line ne "") {
$line =~ s/^\s+//;
使用正则表达式切断所有前导空格,而不使代码在循环周围迭代。其余的代码也可以从精心编写的正则表达式中受益。这些是特征性的Perl成语;令人惊讶的是,他们没有被使用。
如果效率是引起关注的问题(不使用正则表达式的原因),那么问题应该是“你测量它”和“你在讨论什么样的效率 - 机器或程序员”?
工作代码计数。或多或少的惯用代码更好。
当然,还有模块Text :: CSV和Text :: CSV_XS可用于处理CSV解析。询问他们是否了解Perl模块会很有趣。
在引用字段中还有多种处理引号的符号。代码似乎假设反斜杠引用是合适的;我相信Excel使用加倍的报价:
"He said, ""Don't do it"", but they didn't listen"
这可以匹配:
$line =~ /^"([^"]|"")*"/;
稍微小心一点,你可以只捕获封闭引号之间的文字。您仍然需要对捕获的文本进行后处理,以删除嵌入的双引号。
非引用字段将匹配:
$line =~ /^([^,]*)(?:,|$)/;
这比显示的循环和子字符串短得多。
这是代码的一个版本,使用问题代码中使用的反斜杠双引号转义机制,它可以完成同样的工作。
#!/usr/bin/perl -w
use strict;
open (IN, "qq.in") || die "Cannot open qq.in";
while (my $line = <IN>) {
chomp $line;
print "$line\n";
while ($line ne "") {
$line =~ s/^\s+//;
my $field = "";
if ($line =~ m/^"((?:[^"]|\\.)*)"([^,]*)(?:,|$)/) {
# Quoted field
$field = "$1$2";
$line = substr($line, length($field)+2);
$field =~ s/""/"/g;
}
elsif ($line =~ m/^([^,]*)(?:,|$)/) {
# Unquoted field
$field = "$1";
$line = substr($line, length($field));
}
else {
print "WTF?? ($line)\n";
}
$line =~ s/^,//;
print " [$field]\n";
}
}
close (IN);
它不到30条非空白,非评论行,而原始版本约为70。原始版本比需要的更大。而且我并没有竭尽全力将代码减少到最低限度。
答案 4 :(得分:31)
没有使用严格/使用警告,系统使用substr而不是regexp,不使用模块。这绝对不是“非常好的Perl体验”的人。至少不适用于现实生活中的Perl项目。和你一样,我怀疑它可能是一个具有Perl基础知识的C程序员。
这并不意味着他们无法学习,特别是因为周围还有其他Perl人。这似乎意味着他们夸大了他们的工作资格。关于他们如何获得非常好的Perl体验的更多问题将是有序的。
答案 5 :(得分:27)
我不在乎他是否使用正则表达式。我也不在乎他的Perl是否看起来像C。真正重要的问题是:这个好Perl?我会说它不是:
use strict
答案 6 :(得分:22)
我必须(有点)不同意这里表达的大多数观点。
由于有问题的代码可以在惯用的Perl中表达得更加紧凑和易于维护,所以你真的需要提出一个问题,即候选人花费多少时间来开发这个解决方案,以及花费多少时间用于熟练使用惯用语的人的Perl。
我认为你会发现这种编码风格可能会浪费大量时间(因而也就是公司的资金)。
我并不认为每个Perl程序员都需要grok这种语言 - 不幸的是,这种语言会有些牵强 - 但是他们应该知道不要花费多少时间在代码中重新实现核心语言功能一遍又一遍。
编辑再次查看代码,我必须更加激烈:虽然代码看起来非常干净,但它实际上是可怕的。抱歉。这不是Perl。你知道“你可以用任何语言编写Fortran”的说法吗?是的你可以。但你不应该。
答案 7 :(得分:13)
这是您需要跟进程序员的情况。 问他为什么他这样写的。
可能有一个非常好的理由......也许这需要遵循与现有代码相同的行为,因此他为了完全兼容性而进行了逐行翻译。如果是这样的话,请给他一些好的解释。
或许或许他不认识Perl,所以他在那天下午学会了回答这个问题。如果是这样,请给予他快速灵活的学习技巧。唯一不合格的评论可能是“我总是以这种方式编写Perl。我不明白正则表达式。”
答案 8 :(得分:9)
有用吗? 他是否在可接受的时间内写了? 你觉得它可维护吗?
如果你能回答我这三个问题,你可以通过死亡之桥(*)。
答案 9 :(得分:9)
我会说他的代码是一个合适的解决方案。它有效,不是吗?通过编写“longhand”代替尽可能少的代码字符,可维护性具有优势。
Perl的座右铭是“There's More Than One Way To Do It。” Perl并没有真正了解编码风格的情况,就像有些语言一样(我也喜欢Python,但是你必须承认,在评估代码是否是“pythonic”时,人们可以获得一些势利。)< / p>
答案 10 :(得分:9)
我的一位同事最近 采访了一些求职者 并且一个人说他们有非常好的Perl 经验。强>
如果这个人认为他有非常好的Perl经验并且他像这样写Perl,他可能是Dunning-Kruger effect的受害者。
所以,这是一个不聘用的人。
答案 11 :(得分:8)
我认为最大的问题是他或她没有表现出正则表达式的任何知识。这是Perl的关键。
问题是,他们可以学习吗?在这段代码中,候选人需要寻找很多东西。
答案 12 :(得分:5)
我不接受候选人。他或她对Perl的习语感到不舒服,这会导致代码不理想,工作效率降低(所有不必要的行必须写入!)以及阅读由经验丰富的Perl编码器编写的代码(当然使用正则表达式)是不可靠的等等。
答案 13 :(得分:5)
最初的阻止表明他已经错过了关于Perl的基本原理。
while ($line ne "") {
# Skip spaces and start with empty field.
if (substr ($line,0,1) eq " ") {
$line = substr ($line,1);
next;
}
至少应使用正则表达式来删除前导空格。我喜欢the answer from jrockway best,模块摇滚。虽然我会用正则表达式来做,比如。
#!/usr/bin/perl -w
#
# $Id$
#
use strict;
open(FD, "< qq.in") || die "Failed to open file.";
while (my $line = <FD>) {
# Don't like chomp.
$line =~ s/(\r|\n)//g;
# ".*?[^\\\\]" = Match everything between quotations that doesn't end with
# an escaped quotation, match lazy so we will match the shortest possible.
# [^",]*? = Match strings that doesn't have any quotations.
# If we combine the two above we can match strings that contains quotations
# anywhere in the string (or doesn't contain quotations at all).
# Put them together and match lazy again so we can match white-spaces
# and don't include them in the result.
my $match_field = '\s*((".*?[^\\\\]"|[^",]*?)*)\s*';
if (not $line =~ /^$match_field,$match_field,$match_field,$match_field$/) {
die "Invalid line: $line";
}
# Put values in nice variables so we don't have to deal with cryptic $N
# (and can use $1 in replace).
my ($user_id, $name, $level, $numeric_id) = ($1, $3, $5, $7);
print "$line\n";
for my $field ($user_id, $name, $level, $numeric_id) {
# If the field starts with a quotation,
# strip everything after the first unescaped quotation.
$field =~ s/^"(.*?[^\\\\])".*/$1/g;
# Now fix all escaped variables (not only quotations).
$field =~ s/\\(.)/$1/g;
print " [$field]\n";
}
}
close FD;
答案 14 :(得分:5)
原谅这个家伙。我不敢用正则表达式解析CSV,即使它可以完成。
结构化代码中的DFA比这里的正则表达式更明显,DFA - &gt;正则表达式翻译是不平凡的,容易出现愚蠢的错误。
答案 15 :(得分:3)
他没有在代码中使用单个正则表达式的事实应该让你问他很多关于他为什么这样写的问题。
也许他是Jamie Zawinski或者是粉丝而且他不想有更多的问题?
我不一定说整个解析应该是大量不可读的CSV解析正则表达式,如("([^"]*|"{2})*"(,|$))|"[^"]*"(,|$)|[^,]+(,|$)|(,)
或许多类似的正则表达式之一,但至少要遍历行或不使用{ {1}}。
答案 16 :(得分:3)
也许请他写相同代码的更多版本?如果对招聘有疑问,请向候选人提出更多问题。
答案 17 :(得分:3)
代码不仅表明候选人并不真正了解Perl,而且所有那些说$line = substr ($line,1)
的行在任何语言中都是可怕的。尝试使用这种方法解析一条长行(比如几千个字段),你会明白为什么。它只是展示了Joel Spolsky在this post中讨论过的那类问题。
答案 18 :(得分:1)
一个显而易见的问题可能是,如果您首先不在公司使用Perl,那么重要他的Perl代码有多漂亮吗?
我不确定他的Perl代码的优雅程度如何说明他使用你实际使用的任何语言的技能。
答案 19 :(得分:1)
作为非Perl(程序员?),我不得不说,这可能是我读过的最易读的Perl! :)
雇用某人的东西,比如可以在几天到几周内学习的脚本语言(如果它是一种有价值的脚本语言!),首先看来是非常有缺陷的。
我个人可能会因为不同的原因雇用这个人。代码结构合理,评论相当好。语言细节可以在以后轻松讲授。
答案 20 :(得分:1)
这里的关键点是 - 自然地确保它按预期工作 - 代码是否可维护。
Perl程序倾向于看起来像猫在键盘上行走时偶然输入的内容。如果此人知道如何编写适合团队的可读Perl代码,这实际上是一件好事。
然后,你可能想要教他正则表达式,但只能小心: - )
答案 21 :(得分:1)
代码看起来干净可读。对于那个大小,它不需要那么多评论(可能根本没有。)这不仅仅是好的评论,还有好的代码,后者比前者更重要。
如果我们正在查看更复杂/更大的代码片段,我会说需要注释。但是对于that
(特别是它的编写方式 - 写得很好),我不这么认为。
我认为,鉴于他/她提交的一段代码是完全可以接受的并且完成了工作,对申请人产生怀疑是不公平和徒劳的。
答案 22 :(得分:0)
嗯,我在请求中没有看到任何应该删除引号的内容,并且应删除单词。输入文件有“垃圾”一词,它不在输出中。
我见过用引号导出的CSV文件会回到那些相同的引号。如果您的规范是删除引号和引号之外的单词,那么可能需要这项工作。
我会注意到这一点,以及冗长。寻找更懒惰的人(在Perl中恭维)。
open (IN, "csv.csv");
while (<IN>) {
#print $_;
chomp;
@array = split(/,/,$_);
print "[User Id] = $array[0] [Name] = $array[1] [Level] = $array[2] [Numeric ID] = $array[3]\n";
}