作为程序员,我们经常会被客户问的一个问题一定是不是说很容易么,为什么花了这么长时间。不得不说,程序员可能是最糟糕的计划者,按时按点按计划完成的软件项目永远是下一个项目。一个项目的延期,有很多这样那样的原因,其中不得不说的一个原因就是很多代码想起来很容易,但是真的写起来,细节里全是魔鬼。在这个Epic Fail的系列中,我会记录一些在我平常写代码的过程中遇到的那些本来很简单却花了很长时间的有趣问题。这个系列会不定时更新,不断记录我写代码中的糗事,希望我永远不用更新:)。
作为开篇,记录一个花了我1个多小时的python脚本。故事的背景是这样的,我有个测试数据,分散在几个csv中,我想把它们合并到一个csv中。作为程序员,让我手动把每个csv文件打开,复制粘贴到另外一个文件里这种没技术含量的方法当然不会做。而且将来我要是有很多文件要合并呢?我还能真的一个一个拷啊。其实我只要合并4个文件,手动的方式可能是更快的方法,这当然是后话。
作为一个“精于”python又不会诸如sed一般的神奇unix命令的我,自然打开python写下了下面的代码:
import os def merge_all(path, output_file): output = open(os.path.join(path, output_file), 'w') header_writen = False for f in os.listdir(path): full_name = os.path.join(path, f) if os.path.isfile(full_name) and f.endswith('.csv'): input = open(full_name) header = input.readline() if not header_writen: output.write(header) header_writen = True input.readline() input.readline() line = input.readline() while line: output.write(line) line = input.readline() input.close() output.close()
这段代码平淡无奇,我不到5分钟就一挥而就。整体的逻辑就是遍历文件夹,打开所有的csv文件,对header只输出一次,第二行和第三行跳过,因为这两行的数据我不需要,然后把下面的每行写入输出文件。
运行脚本,数据合并一切正常,但是数据的header,却没有。这种时候,作为程序猿第一反应就是再运行一遍,由于脚本偷懒,我把输出文件写在了输入文件夹中,我手动把生成的输出文件删了,重新运行了一次。还是一样的结果,数据都合并了,但是还是没有文件头。这个时候,作为程序猿的第二反应就是再运行一遍,无奈还是一样的结果。
这时,我不得不面对上面的代码有问题的结论。这也是程序员最郁闷的时候,因为我对问题一点头绪都没有。这时自己手动在生成的文件中加上头行应该是最好的选择,但是作为程序员是很难接受这种做法的。
开始排错,既然是header没写,那么一定是我的header_writen的逻辑有问题,可这个代码咋看也看不出有啥问题啊。经过多次纠结后,我加入了一行日志代码:
if os.path.isfile(full_name) and f.endswith('.csv'): print 'Processing ' + full_name + '…'
这时问题的症结终于出现了,那就是第一个被遍历的csv文件是我生成的输出文件!
发现了问题解决起来就很容易了:
for f in os.listdir(path): if f == output_file: continue
在循环中加入这个简单的逻辑,问题就解决了。这个小小的细节,竟然花了我这么长时间。
反思这个问题,如果不是我的输出文件名在listdir中排在第一个我可能会遇到更大的问题,在生成的文件中出现重复数据。而这个问题很可能被我忽略。
这个问题又如何避免呢?我认为最好的方法就是我不应该图省事把数据输出文件写入读取文件夹。这个决定完全是为了省事,但是也就破坏了输入文件夹的状态,而这个状态是我完全没意识到的。