大约一个月前,我开始成为一名自学成才的程序员,而我对自学的最大恐惧就是养成不良习惯并没有意识到。

我正在对此“数字”游戏。这是我投入大量时间进行的第一个真正的“项目”,因此我觉得这是我第一个受到批评的机会。

package guessANumber;

import java.util.Random;

import javax.swing.JOptionPane;

/**
 * @author Mike Medina
 */

/**
 * The "Guess a Number" game
 * */
public class Guess {

static boolean playAgain;
static int max; // The objective will be between 1 and this number
static int objective; // Users are trying to guess this number
static int userGuess;

public Guess() {}

    /**
     * Runs the game while playAgain == true
     */ 
    public static void main(String[] args) {

        do {
        setMax();
        setObjective();
        userGuess();
        playAgain();
        } while (playAgain == true);

    }

    /**
     * Asks the user to set the max value they will guess between and validates it
     */
    public static void setMax() {

        boolean valid = false;

        // Asks user for "max"
        while(!valid) {
            try {
                max = Integer.parseInt(JOptionPane.showInputDialog("You will try to guess a number between 1 and \"max\". Set max: "));
                valid = true;
            }
            catch (NumberFormatException e) {}
        }

        // Ensures user input for max is greater than 1 and fits in an int
        while (max < 1 || max > Integer.MAX_VALUE) {
            valid = false;
            while (!valid) {
                try {
                    max = Integer.parseInt(JOptionPane.showInputDialog("Max must be between 1 and " + Integer.MAX_VALUE + ": "));
                    valid = true;
                }
                catch(NumberFormatException e) {}
            }
        }

    }

    /**
     * Sets the objective between 1 and "max"
     */
    public static void setObjective() {

        Random rand = new Random();

        if (max == 1)
            objective = 1;
        else
            objective = rand.nextInt(max - 1) + 1;

        // Prints objective for testing
        System.out.println(objective);

    }

    /**
     * Takes in the user's guess, validates it, and tells them when they win
     */
    public static void userGuess() {

        // Prompts user for guess and ensures it's an integer
        do {
            userGuess = 0;

            try {
                userGuess = Integer.parseInt(JOptionPane.showInputDialog("Guess a number between 1 and " + max + ": "));
            }
            catch (NumberFormatException e){}

            // Ensures user's guess is an integer greater than 0 and less than max
            while (userGuess > max || userGuess < 1) {
                try {
                    userGuess = Integer.parseInt(JOptionPane.showInputDialog(null, "Your guess must be between 1 and " + max));
                    }
                catch (NumberFormatException e) {}
            }
            userWinLose();
        } while (userGuess != objective);

    }

    /**
     * Tells the user whether their guess was high, low, or correct
     */
    public static void userWinLose() {

        // Tells the user their guess was too high, too low, or correct
        if (userGuess < objective)
            JOptionPane.showMessageDialog(null, "Too low!");
        else if (userGuess > objective)
            JOptionPane.showMessageDialog(null, "Too high!");
        else
            JOptionPane.showMessageDialog(null, "You win!");

    }

    /**
     * Asks the user if they want to play again or quit
     */
    public static void playAgain() {

        boolean valid = false;

        // Confirms user input is an integer
        while (!valid) {
            try {
                if (Integer.parseInt(JOptionPane.showInputDialog("Enter 0 to quit or 1 to play again: ")) == 0)
                    playAgain = false;
                else
                    playAgain = true;
                valid = true;
            }
            catch (NumberFormatException e) {}
        }

    }

}


评论

我喜欢您正在编程的谦虚态度。当我起步时,与我相比,您会学得更快,并且更容易相处。

#1 楼

在已经给出的注释之上:

使事情变得简单:



while (playAgain == true)可以写为while (playAgain)


if (A) variable=false; else variable=true;可以写成variable = !A;

不要重复自己:如果您处理大量用户输入和检查值,则使其成为可以重复使用的功能。
您不需要静态成员。局部变量可以解决问题。想法是将事物保持在尽可能小的范围内,以便更容易理解发生了什么变化。

这是考虑到我的评论的代码:

import java.util.Random;
import javax.swing.JOptionPane;

/**
 * The "Guess a Number" game
 * */
public class Guess {
    public Guess() {}

    /**
     * Runs the game while playAgain == true
     */ 
    public static void main(String[] args) {
        do {
            int max = getMax();
            int obj = getObjective();
            userGuess(obj, max);
        } while (playAgain());
    }

    private static int getIntFromUser(String text, int min, int max)
    {
        while (true)
        {
            try
            {
                int val = Integer.parseInt(JOptionPane.showInputDialog(text));
                if (min <= val && val <= max)
                    return val;
            }
            catch (NumberFormatException e) { /* As pointed out by ChrisW, it might be worth having something here */}
        }
    }

    /**
     * Asks the user for the max value
     */
    public static void getMax() {
        return getIntFromUser("You will try to guess a number between 1 and \"max\". Set max: ", 1, Integer.MAX_VALUE);
    }

    /**
     * Gets the objective between 1 and "max"
     */
    public static int getObjective() {
        // Please read Simon André Forsberg's comment about this.
        Random rand = new Random();
        return (max == 1) ?
            1 :
            rand.nextInt(max - 1) + 1;
    }

    /**
     * Takes in the user's guess, validates it, and tells them when they win
     */
    public static void userGuess(int obj, int max) {
        int userGuess;
        do {
            userGuess = getIntFromUser("Guess a number between 1 and " + max + ": ", 1, max);
            userWinLose(userGuess, obj);
        } while (userGuess != obj);
    }

    /**
     * Tells the user whether their guess was high, low, or correct
     */
    public static void userWinLose(int guess, int objective) {
        if (guess < objective)
            JOptionPane.showMessageDialog(null, "Too low!");
        else if (guess > objective)
            JOptionPane.showMessageDialog(null, "Too high!");
        else
            JOptionPane.showMessageDialog(null, "You win!");
    }

    /**
     * Asks the user if they want to play again or quit
     */
    public static boolean playAgain() {
        return (getIntFromUser("Enter 0 to quit or 1 to play again", 0, 1) == 1);
    }
}


评论


\ $ \ begingroup \ $
+1,但让我担心的是看到一个默默忽略的异常。 @MikeMedina可能会使用它来向用户添加显示的错误消息:String errorMessage;尝试{.... showInputDialog(errorMessage + text); ...; } catch(NumberFormatException e){errorMessage =“那不是数字!请重试。\ n \ n”; } 或类似的东西。
\ $ \ endgroup \ $
– ChristW
2014年1月17日在13:04



\ $ \ begingroup \ $
好吧,在我通过谷歌搜索了解try-catch是什么之前,我在while循环中纠结得一团糟。我想我是在没有完全理解它的情况下开始使用它的,但这是有道理的!
\ $ \ endgroup \ $
–迈克·麦迪纳(Mike Medina)
2014年1月17日下午13:51

\ $ \ begingroup \ $
但是,那一个playAgain = playAgain()有点让我讨厌。也许只是while(playAgain()),您没有在其他地方使用该变量。无法重新命名?
\ $ \ endgroup \ $
–apieceoffruit
2014年1月17日15:25



\ $ \ begingroup \ $
@apieceoffruit你是对的。 while(playAgain());来自rolfl'answer的效果要好得多。
\ $ \ endgroup \ $
– SylvainD
2014年1月17日15:26

\ $ \ begingroup \ $
public static void getObjective()应该返回一个我相信的int :)并且还应该写成return rand.nextInt(max)+1;从1到最大(含)产生。随机对象将被重用!
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年1月17日在16:42



#2 楼

总的来说,它看起来不错。尽管有一些指针...



尝试正确缩进代码。大部分看起来不错,但是在一些地方您错过了压痕。例如,

do {
setMax();
setObjective();
userGuess();
playAgain();
} while (playAgain == true);




不要消耗或默默地对异常进行处理(在极少数情况下除外,但这不在范围内)

catch (NumberFormatException e) {} // don't do this



用适当的大括号括住您的陈述

if (max == 1)                   // no brackets!
    objective = 1;
else                            // no brackets!
    objective = rand.nextInt(max - 1) + 1;



有些人不这样做可能不是问题,但是随着代码库的扩展,这可能会成为问题,并且IIRC Java样式文档建议不要使用花括号。这是一个为什么它可能是个坏主意的示例https://stackoverflow.com/a/8020255/1021726



max > Integer.MAX_VALUE永远不会发生

因为max被声明为整数,并为其分配了在JOptionPane中输入的值。如果用户输入的值大于Integer.MAX_VALUE,则会引发异常。而且由于您对异常不做任何事情,它只会静默失败。请参阅第2点。


静态与非静态

您了解static吗?如果没有,那么您可以在这里获得简短的简介:http://www.caveofprogramming.com/frontpage/articles/java/java-for-beginners-static-variables-what-are-they/

如果您了解静态知识,则可能会意识到自己不希望Guess类的两个实例共享同一变量。



声明访问修饰符

static boolean playAgain; // no access modifier



虽然您的变量缺少访问修饰符(并且我认为不是故意的),但这可能不是问题,因为它默认为package-private,但您需要注意,设计类很重要,它的成员和变量与应如何使用它们有关。您的变量确实是程序包私有的,还是应该仅将它们声明为private变量?

评论


\ $ \ begingroup \ $
2.我在循环中使用try-catch来获得正确的输入而没有显示错误。有没有一种更优雅的方式来做到这一点而又不会滥用try-catch? 3.嗯,我认为如果if语句仅占一行,最好避免使用“多余”的括号,但是我知道这会使事情复杂化。注意! 4.我认为,如果他们输入的值大于int可以容纳的值,它将折回为负数。还说明了! 5.我想​​这是我不了解的OOP的一部分。我什么时候会使用多个猜测? 6. LoudandClear!
\ $ \ endgroup \ $
–迈克·麦迪纳(Mike Medina)
2014年1月17日在12:03



\ $ \ begingroup \ $
2.您可以显示一个JOptionPane来解释用户输入了错误范围内的数字。这将成为良好的UI / UX的范围,因为现在用户将输入输入失败的原因。滥用尝试/捕获是什么意思? 4.如果将值增加到超过Integer.MAX_VALUE,则可以,但是不能为int分配大于2147483647的值
\ $ \ endgroup \ $
–最大
2014年1月17日12:18



\ $ \ begingroup \ $
我想我不明白catch括号是用来显示错误消息的。还是一个初学者! 4.完全有道理。再次感谢!当我有多个猜测实例时,您是否有任何情景示例?就像我说的那样,我仍在努力实现面向对象编程的“对象”部分^^
\ $ \ endgroup \ $
–迈克·麦迪纳(Mike Medina)
2014年1月17日14:37



\ $ \ begingroup \ $
好吧,它们不是用于显示错误消息,而是用于处理运行代码时可能发生的异常情况。处理异常情况的多种方法之一包括记录错误消息并向用户显示错误消息。 4.想象一下JRE是赌场,Guess是老虎机。如果您要在赌场中放置很多老虎机,并且它们都共享静态变量,则每个老虎机将共享相同的状态,相同的显示,相同的输出并投入相同的金额。现在,将老虎机切换到Guess实例,然后就可以了。
\ $ \ endgroup \ $
–最大
2014年1月17日15:36

\ $ \ begingroup \ $
因此,如果需要在同一类中使用多种不同方法访问变量,是否可以将变量设置为私有静态?还是有一种避免使用静态方法而仍然在整个类中访问一个变量的方法?
\ $ \ endgroup \ $
–迈克·麦迪纳(Mike Medina)
2014年1月17日15:44

#3 楼

乔赛和麦克斯都给出了很好的答案。我要添加两件事。



无需携带playagain布尔变量。您可以简单地:

do {
    ....
} while (playAgain());



getObjective()方法中有一个错误。您将永远不会生成“ max”值本身(除非它是1)。从Java API文档中的nextInt(n):


返回一个伪随机数,其int值均匀分布在0(包括)和指定值(不包括)之间。


...所以您要求用户输入max,例如,假设他们输入5。现在,您的getObjectiveMethod执行rand.nextInt(max - 1) + 1;,它将从0(5-1)生成一个随机数,由于(5 - 1)为4,并且该随机值将不包括4,因此生成的最大随机数将为3。再次添加1,得到4。因此,最大的目标永远不会是5或最大值。


此随机数生成是一个常见的问题。有很多关于此的文章和博客,但是将您引导到的逻辑指针是这个StackOverflow答案:生成范围内的随机数

最重要的是,您希望将数字生成为:

rand.nextInt(max) + 1;


因此,不需要max == 1的特殊情况。您只需要确保输入max > 0即可(设置valid = true之前应该这样做

评论


\ $ \ begingroup \ $
谢谢!我本可以发誓要在3次测试中最多测试一次,以确保我可以测试3次,但我想不是。我阅读了rand的文档,但一定错过了排他性的上限。感谢您指出了这一点!
\ $ \ endgroup \ $
–迈克·麦迪纳(Mike Medina)
2014年1月17日下午13:54

#4 楼

为了让您深入了解,最好开始考虑为您的类和接口进行面向对象的设计。考虑一下代码的可重用性,以及如何在一个类中封装足够多的有用行为,同时又使它尽可能通用。

让我们知道,您已经编写了一个很棒的类来玩这个猜谜游戏和其他开发人员希望在自己的代码中使用它。只有一个人想使用不同的GUI,另一个人想让设备根据猜测值是高还是低来发出不同的蜂鸣声,而另一个人想与很小的孩子一起使用游戏,并且始终将最大值设置为10。立场,他们不能使用您的代码,因为实现细节出现在每个级别。

让我们只关注游戏的操作。界面很清晰:客户必须能够将猜测传达给班级,班级需要提供一个指示,说明该猜测是太高,太低还是正确。因此,将IGuessHandler定义为仅包含该类的猜测处理接口,现在有一个类继承它。然后,您可以提供与现在完全相同的实现,但是其他人可以实现不同的实现。

我还应该将main提取到某些GameRunner类中,并通常尝试为类和操作使用更精确的名称。您的主循环具有一个playAgain方法,该方法在我们第一次播放时运行。看起来微不足道,但马虎的想法。没有一个名为playAgain的方法,有一个叫做play的方法实际上在玩游戏。您一次又一次地调用它,但名称要尽可能清晰明了。

评论


\ $ \ begingroup \ $
这正是我决定在这里发布时希望得到的答案。这些OOP概念是我无法掌握的,因为我不了解如何使用OOP工具集来谋取利益。感谢您为我提供示例,说明如何从一开始就构建代码!如果有机会,是否有可能更深入地解释界面?实际上,我已经花了很多时间来研究接口,但我只是无法将想法付诸实践。在我看来,它们只是“应”包含的方法列表。方法标题列表有什么用?
\ $ \ endgroup \ $
–迈克·麦迪纳(Mike Medina)
2014年1月17日17:09

\ $ \ begingroup \ $
但是,YAGNI也值得实践。
\ $ \ endgroup \ $
– ChristW
2014年1月17日17:09

\ $ \ begingroup \ $
只需将接口看作是指定实现该接口的任何类都必须提供的一组行为即可。考虑到我以前的答案,我现在将其更改一下。没有正确的方法来执行此操作,但是让我们将IGuessHandler定义为具有handleGuess(int)方法的接口,该方法返回GuessOutcome基类的实例。这可以表征为具有内部状态TOO_LOW,TOO_HIGH,CORRECT(定义为枚举)和Render()方法。然后,您可以使用与以前相同的实现从中派生自己的类。
\ $ \ endgroup \ $
–数学家
2014年1月17日17:25



\ $ \ begingroup \ $
@MikeMedina诱使设计过于复杂。相反,我建议在需要它们之前,使其尽可能简单(无接口)。如果将来需要它们,则可以通过重构获得它们。在您知道是否要添加新功能并知道该功能是什么之后,现在越简单,编写越早,并且越容易在以后进行重构(如有必要)。
\ $ \ endgroup \ $
– ChristW
2014年1月17日17:41

\ $ \ begingroup \ $
@MikeMedina IMO当您要“插入”两个不同的类(两个不同的实现,甚至同一类的两个不同版本)时,与类分开的“接口”是值得的。例如。将来,您可能需要两种类型的猜测器:1)交互式猜测的用户2)自动猜测的软件。然后,可能值得定义接口IGuess {int getGuess(); }由类UserGuess实现的方法实现了IGuess {public int getGuess(){return Integer.parseInt(JOptionPane.showInputDialog(“您怎么看?”)); }。在那之前,我不会打扰。
\ $ \ endgroup \ $
– ChristW
2014年1月17日20:33



#5 楼

关于异常,您应该知道的一件事是您不需要立即捕获它们。

代码首先在异常中引发异常的原因是它不知道如何类似地,立即调用该代码的较高一级子例程(例如,您的getIntFromUser方法)也可能不知道如何处理该异常。

在更高的调用堆栈中的另一个例程中捕获异常是很好的,也许是正常的。例如,这是Josay答案的修改版本:

private static int getIntFromUser(String text, int min, int max)
{
    // parseInt might throw NumberFormatException
    int val = Integer.parseInt(JOptionPane.showInputDialog(text));
    if (min <= val && val <= max)
        return val;
    else
        throw new IllegalArgumentException("Not an integer between " + min + " and " max);
}

// display error message from caught exception
// return true if the user wants to play again
private static bool getIsContinueAfterError(String errorMessage)
{
    String message = errorMessage + "\n\nDo you want to continue playing?";
    int reply = JOptionPane.showConfirmDialog(null, message, "Error", JOptionPane.YES_NO_OPTION);
    return (reply == JOptionPane.YES_OPTION);
}

public static void main(String[] args) {
    boolean playAgain;
    do {
        try {
            int max = getMax();
            int obj = getObjective();
            userGuess(obj, max);
            playAgain = playAgain();
        }
        catch (NumberFormatException ex)
        {
            playAgain = getIsContinueAfterError("This is not an integer.");
        }
        catch (IllegalArgumentException ex)
        {
            playAgain = getIsContinueAfterError(ex.getMessage());
        }
    } while (playAgain);
}


这不一定是一个好例子(在您的例子中,您可以在getIntFromUser中捕获并重试);我之所以说这是一个例证,是因为您说过您不熟悉异常。

使用您的代码,也许用户没有输入整数,因为键盘上的所有数字键都被打断了。您的代码将永远捕获并重试,而我的代码为用户提供了停止播放的机会。

评论


\ $ \ begingroup \ $
(我对Java和JOptionPane不熟悉,因此上面的代码中可能存在语法错误。)
\ $ \ endgroup \ $
– ChristW
2014年1月17日14:53

\ $ \ begingroup \ $
感谢您的输入!我以为我已经完成了该程序的功能,然后再将其发布以进行审查,但是现在我意识到JOptionPanes中的“取消”按钮不起作用,因为循环刚刚重新开始。绝对是不允许用户安全退出程序的问题!
\ $ \ endgroup \ $
–迈克·麦迪纳(Mike Medina)
2014年1月17日15:26

#6 楼

使用playagain布尔变量时,应在创建变量时将其设置为false,然后该循环可以是简单的while(playagain),很多人更喜欢while循环,而不是do while循环,因为您会发现情况刚好可以解决。

编辑:
现在,我再次阅读了《代码》,我明白了为什么您选择使用do while,并且除以下内容外,它完全有意义:

您的PlayAgain方法应返回布尔值。也就是说,应该捕获该变量并将其传递给Main()方法内部的某个变量,例如keepPlaying,并且应该在Main()方法内部创建该变量。 playAgain布尔值仅在两个地方使用,即Main()playAgain(),这意味着您不应将其设置为全局值,它是特定的,并且应在范围上特定,它不会影响程序中的任何其他方法。