编程中易犯错误汇总:一个综合案例.md

# 11编程中易犯错误汇总:一个综合案例

在上一篇文章中,我们学习了如何区分好的代码与坏的代码,如何写好代码。所谓光说不练假把式,在这篇文章中,我们就做一件事——一起来写代码。首先,我会先列出问题,然后要求读者自己写一份答案;然后,我会给出我写的代码;最后,我们还会以这个问题为例,讨论编程中常见的错误。

## 1 问题描述

在[这个](http://wiki.openhatch.org/index.php?title=Scrabble_challenge)页面中,有一道Python相关的练习题,可以作为Python相关的进阶题目。题目的描述如下:

> Write a Python script that takes a Scrabble rack as a command-line argument and prints all valid Scrabble words that can be constructed from that rack, along with their Scrabble scores, sorted by score. An example invocation and output:

$ python scrabble.py ZAEFIEE
    17 feeze
    17 feaze
    16 faze
    15 fiz
    15 fez
    12 zee
    12 zea
    11 za
    6 fie
    6 fee
    6 fae
    5 if
    5 fe
    5 fa
    5 ef
    2 ee
    2 ea
    2 ai
    2 ae

简单来说,就是给你一个文件,里面包含了很多的单词。接下来,从命令行中输入一些字母,需要找出文件中,哪些单词可以由命令行中的字母组成。随后,还需要根据评分规则,对找到的单词进行评分,并按照分数从高到低输出。

## 2 作者的代码

这是一道很好的Python练习题,强烈建议读者尝试独立解决这个问题。相信有不少读者会觉得这个练习比较简单,但是,我们这一篇文章,更多的还是接着上一篇文章,继续讨论如何写出好的代码。因此,**读者可以尝试打磨自己的代码**,尽可能给出一个自认为完美的、可读性强的、具有美感的答案。

下面是笔者提供的答案:

#!/usr/bin/python
    #-*- coding: UTF-8 -*-
    from __future__ import print_function
    import sys
    from collections import Counter
   
    scores = {"a": 1, "c": 3, "b": 3, "e": 1, "d": 2, "g": 2,
             "f": 4, "i": 1, "h": 4, "k": 5, "j": 8, "m": 3,
             "l": 1, "o": 1, "n": 1, "q": 10, "p": 3, "s": 1,
             "r": 1, "u": 1, "t": 1, "w": 4, "v": 4, "y": 4,
             "x": 8, "z": 10}
   
    def get_word_list(file_name):
        word_list = []
        with open(file_name) as f:
            for line in f:
                word_list.append(line.strip())
        return word_list
   
    def get_valid_words(word_list, rack):
        c = Counter(rack)
        return [ word for word in word_list if not (Counter(word) - c) ]
   
    def lower_valid_words(words):
        return [ word.lower() for word in words ]
   
    def get_scores(words):
        d = {}
        for word in words:
            d[word] = sum(scores[c] for c in word)
        return d
   
    def main():
        if len(sys.argv) != 2:
            raise SystemExit(‘Usage: scrabble_change.py RACK‘)
   
        rack = sys.argv[1]
   
        word_list = get_word_list(‘sowpods.txt‘)
        valid_words = get_valid_words(word_list, rack.upper())
   
        valid_words = lower_valid_words(valid_words)
   
        d = get_scores(valid_words)
        for val, key in sorted(zip(d.values(), d.keys()), reverse=True):
            print(val, key)
   
    if __name__ == ‘__main__‘:
        main()

## 3 编程中常见的错误

在讲解习题之前,我们先简单介绍一下测试用例的概念,读者可以尝试编写几个测试用例,进行简单的测试,以保证自己代码的正确性。

### 3.1 测试用例

测试用例又称之为Case,就是构造不同的输入,判断*输出*结果是否符合预期。这么说可能比较抽象,我们打个比方,想象一个登陆页面,可以输入用户名和密码,则它至少有以下几个测试用例:

* 用户没有输入用户名和密码,直接点击登陆,预期结果是提示输入用户名
* 用户输入的用户名太短,预期结果是提示用户用户名不能小于6个字符
* 用户输入了用户名没有输入密码,尝试进行登陆,预期结果是提示用户输入密码
* 用户输入了错误的密码,预期结果是提示用户密码错误
* 用户输入了正确的用户名和密码,预期结果是登陆成功,并跳转到首页

我们刚刚科普了一下“测试用例”的概念,接下来,可以为上面的程序构造一些测试用例,然后来进行简单的测试,以保证代码的正确性:

* RACK为空
* RACK的字符比单词多
* RACK的字符比单词少
* RACK的字符与单词一样多
* 单词里面有多个相同的字符

### 3.2 常见的错误

在笔者曾经见过的代码中,大家容易犯的错误有:

1. 代码不符合规范,例如,等号两边没有空格,逗号后面没有空格,函数之间没有空行等。这是最简单也最容易犯的错误,希望大家都不要犯这种错误,这类错误给人的感觉很初级,说明读者还处于初学者的水平。代码规范问题其实非常好解决,建议大家使用PyCharm来编写Python代码。PyCharm会对所有不符合PEP 8规范的代码,在右侧的侧边栏以黄色显示。将鼠标移动到黄色标记处,会提示代码哪里不符合规范。我们只需要根据PyCharm的提示修改代码即可。
2. 没有充分利用“函数”。没有充分利用“函数”也是大家容易犯的错误,我们在上一篇文章中强调过函数的好处。函数是代码修改的最小单元,与此同时,函数的名字又可以作为对函数的解释,这样别人阅读我们代码时,就有一种像读短文的愉悦感。此外,函数越短小,则说明它功能越独立,也越容易被复用。下面的代码就是一个没有充分利用“函数”的反面例子,如果读者是初学者,只要能够解决问题就已经很棒了。如果大家希望使用Python作为养家糊口的技能,则需要将代码写得简单优美、好维护。

def main():
            with open(‘/root/iopus/20161119/sowpods.txt‘,‘r‘) as f:
                word_list = [i.strip() for i in f.readlines()]
            valid_words = {}
            try:
                rack = sys.argv[1].upper()
                for word in word_list:
                    temp_rack = list(rack)
                    for letter in word:
                        try:
                            temp_rack.remove(letter)
                        except:
                            break
                    if len(rack) - len(temp_rack) == len(word):
                        valid_words[word] = calculate_score(word)
                valid_words = sorted(valid_words.iteritems(),key=lambda d:d[1],reverse=True)
                for word in valid_words:
                    print(‘{},{}‘.format(word[1],word[0]))
            except:
                print "Usage: scrabble.py [RACK]"
                exit(1)

3. 函数命名不准确。刚刚说到没有充分利用“函数”是不好的,与此同时,如果函数的命名不规范,也容易给大家造成误会。我们反提到,函数的名称可以作为对函数这段代码的解释,既然是解释,那就要求准确无误、简单明了。显然,我们很多工程师刚开始是没有意识到这个问题的。例如,如果函数名称是`get_xxx`、`find_xxx`、`check_xxx`,则函数不应该对输入的参数有任何的“修改”。有“修改”就再创建一个新的函数来修改,不要将两件不同的事情,放在同一个函数之中。例如,在下面这个函数之中,最后一行对结果进行了修改:

#Step 3: find valid words
        def find_valid_words(all_words, rack):
            valid_words = []
            for word in all_words:
                rack_characters = list(rack)
                word_characters = list(word)
                valid = True
                for char in word_characters:
                    if char in rack_characters:
                        rack_characters.remove(char)
                    else:
                        valid = False
                        break
                if valid:
                    valid_words.append(word)
            return [ word.lower() for word in valid_words ]

下面也是一个命名不准确的例子。函数的名称是`open_file`,则我们的预期是返回一个文件对象,但是其实函数的作用是得到一个单词列表。如果将函数的名称,修改为`get_word_list`将会更加具有可读性。

def open_file(file_name):
            word_list = []
            with open(file_name) as file_dict:
                for word in file_dict:
                     word_list.append(word.strip())
            return word_list

4. 异常考虑不充分。有人在知乎上提问,“[编程到底难在哪里?](https://www.zhihu.com/question/22508677/answer/141334678)”。其中一个高赞答案得到了4.2万个赞。作者以买苹果为例,非常形象地介绍了编程难在哪里。简单来说,就是异常情况的考虑。对于编程来说,就是有if就有else。例如,在我们这个习题中,用户在命令行输入构造单词的字符集合。那么,如果用户没有输入呢?在我们的标准答案中,首先就会判断用户是否按照预期进行了输入,如果用户没有输入,则我们会返回错误,并提示用户进行输入。
5. 多余的计算导致性能不佳。我们前面讨论了代码规范和异常情况,接下来简单地讨论一下性能。在这个习题中,用户的输入与文件中的单词,都是大写字母。但是,在最后结果评分与结果输出时,是小写字母。那么,这就涉及到一个问题,应该在什么时候将输入中的大写转换为小写?有两种方法:1)在找到有效的单词以后,再来进行相应的转换;2)在读入单词列表时,顺便将文件中所有的单词都转换为小写。如果读者之前没有意识到这个问题,那么,现在可以想一下,哪一种方法更好。显然第一种方法更好的。考虑这样一种情况,如果文件中有100万个单词,我们最终只找到了100个有效的单词。则第一种方法只需要将100个单词从大写转换为小写,第二种方法需要将100万个单词从大写转换为小写。显然,第二种方法存在大量的无用计算,从而导致程序运行时间变长。

## 4 总结

在这一篇文章中,我们使用了一种全新的教学方法——边做边学,希望可以给读者带来不一样的收获。这篇文章中的练习还是比较小的练习,但是也存在这么多可以讨论的地方,对于一个大型项目来说,更加需要注意这些细节,时刻牢记上一篇文章中的方法论与代码品味,努力成为编写高质量代码的优秀工程师。

原文地址:https://www.cnblogs.com/niaocaizhou/p/12077842.html

时间: 2025-01-03 22:51:03

编程中易犯错误汇总:一个综合案例.md的相关文章

数据挖掘中易犯的十大错误

按照Elder博士的总结,这10大易犯错误包括: 0. 缺乏数据(Lack Data)1. 太关注训练(Focus on Training)2. 只依赖一项技术(Rely on One Technique)3. 提错了问题(Ask the Wrong Question)4. 只靠数据来说话(Listen (only) to the Data)5. 使用了未来的信息(Accept Leaks from the Future)6. 抛弃了不该忽略的案例(Discount Pesky Cases)7.

数据挖掘中易犯的几大错误【转载,侵删】

原文标题为"Top 10 Data Mining Mistakes",作者是John F. Elder IV, Ph.D.编译:IDMer(数据挖掘者)http://www.salford-systems.com/doc/elder.pdf 按照Elder博士的总结,这10大易犯错误包括: 0. 缺乏数据(Lack Data) 1. 太关注训练(Focus on Training) 2. 只依赖一项技术(Rely on One Technique) 3. 提错了问题(Ask the W

JavaScript中易犯的小错误-------常见错误五:低效的DOM操作

JavaScript中易犯的小错误-------常见错误五:低效的DOM操作js中的DOM基本操作非常简单,但是如何能有效地进行这些操作一直是一个难题.这其中最典型的问题便是批量增加DOM元素.增加一个DOM元素是一步花费很大的操作.而批量增加对系统的花销更是不菲.一个比较好的批量增加的办法便是使用 document fragments :var div = document.getElementsByTagName("my_div");var fragment = document.

JavaScript中易犯的小错误-------常见错误三:内存泄露

国庆放假,今天开始继续!!!!!!!!!!! JavaScript中易犯的小错误-------常见错误三:内存泄露 内存泄露在js变成中几乎是一个无法避免的问题.如果不是特别细心的话,在最后的检查过程中,肯定会出现各种内存泄露问题.下面我们就来举例说明一下:var theThing = null;var replaceThing = function () {     var priorThing = theThing;     var unused = function () {       

JavaScript中易犯的小错误-------常见错误四:比较运算符

JavaScript中易犯的小错误-------常见错误四:比较运算符JavaScript中一个比较便捷的地方,便是它可以给每一个在比较运算的结果变量强行转化成布尔类型.但是从另一方面来考虑,有时候它也会为我们带来很多不便,下面的这些例子便是一些一直困扰很多程序员的代码实例:console.log(false == '0');console.log(null == undefined);console.log(" \t\r\n" == 0);console.log('' == 0);

JavaScript中易犯的小错误-------常见错误七:原型继承问题

JavaScript中易犯的小错误-------常见错误七:原型继承问题 很大一部分的js开发者都不能完全掌握原型的继承问题.下面具一个例子来说明:BaseObject = function(name) {if(typeof name !== "undefined"){ this.name = name; }else{ this.name = 'default' } };这段代码看起来很简单.如果你有name值,则使用它.如果没有,则使用 ‘default’:var firstObj

JavaScript中易犯的小错误-------常见错误二:传统编程语言的生命周期误区

JavaScript中易犯的小错误-------常见错误二:传统编程语言的生命周期误区另一种易犯的错误,便是带着其他编程语言的思维,认为在JS中,也存在生命周期这么一说.请看下面的代码:for (var i = 0; i < 10; i++) { /* ... */ } console.log(i);如果你认为在运行console.log() 时肯定会报出 undefined 错误,那么你就大错特错了.我会告诉你其实它会返回 10吗.当然,在许多其他语言当中,遇到这样的代码,肯定会报错.因为i明

[golang 易犯错误] golang 局部变量初始化:=的陷阱

我们知道,golang中局部变量初始化方法(使用“:=”创建并赋值),让我们在使用变量时很方便.但是,这也是易犯错误的地方之一.特别是这个初始化符还支持多个变量同时初始化,更特别的是它还支持原有变量赋值和新变量创建并赋值同时进行!也就是说如果有部分变量不存在的而另外一些是已声明好的,用:=来初始化部分变量同样有效.这其实也没什么,更方便了嘛.但是,go的好多语句还支持局部前置语句,比如在if,for,switch等语句的初始化条件语句中.在这些地方,当你以为使用了原有变量的时候,实际上go已经为

R语言编程中的常见错误

R语言编程中的常见错误有一些错误是R的初学者和经验丰富的R程序员都可能常犯的.如果程序出错了,请检查以下几方面.? 使用了错误的大小写.help().Help()和HELP()是三个不同的函数(只有第一个是正确的).? 忘记使用必要的引号.install.packages("gclus")能够正常执行,然而Install.packages(gclus)将会报错.? 在函数调用时忘记使用括号.例如,要使用help()而非help.即使函数无需参数,仍需加上().? 在Windows上,路