【问题标题】:Having trouble debugging variable might not have been initialized error调试变量时遇到问题可能未初始化错误
【发布时间】:2011-04-29 10:37:00
【问题描述】:

我在档案中看到了很多类似的问题,但我找不到像我遇到的问题那样的场景。

下面是我的代码。我遇到了“finalPrice”错误,“grandTotalPrice”可能尚未初始化。代码行接近程序的末尾。

应通过上面的控制台输入为变量分配总计。我不确定错误是什么,或者为什么。谁能帮我解释一下?

代码:

import java.util.*;


public class PictureFrames
{

    static Scanner console = new Scanner(System.in);

    static final double REGULAR_FRAME = .15, FANCY_FRAME = .25;
    static final double COLOR = .10, CARDBOARD = .02, GLASS = .07, CROWNS = .35;


    public static void main (String[] args)
    {

    double length, width, area, perimeter; 
    double priceOfFrame, priceOfColor, priceOfCardboard, priceOfGlass, priceOfCrowns, finalPrice, crownFinalPrice, grandTotalPrice; 
    int numberOfCrowns;
    char typeOfFrame, choiceOfColor, choiceOfCrowns;



    System.out.println ("Please enter the length of your picure in inches:");
    length = console.nextDouble();

    System.out.println ("Please enter the width of your picure in inches: ");
    width = console.nextDouble();

    System.out.println ("Please enter the type of frame: R or r (Regular), F or f (Fancy). ");  
    typeOfFrame = console.next().charAt(0);

    System.out.println ("Would you like to add color?: Y for (Yes), N for (No): "); 
    choiceOfColor = console.next().charAt(0);


    switch (typeOfFrame)
    {
    case 'R':
    case 'r':
        if (choiceOfColor == 'N')
        {
            area = (length * width);
            perimeter = (2 * length) + (2 * width);
            priceOfFrame = (perimeter * REGULAR_FRAME);
            priceOfCardboard = (area * CARDBOARD);
            priceOfGlass = (area * GLASS);
            finalPrice = (priceOfFrame + priceOfCardboard + priceOfGlass);
        break;
        }
        else if (choiceOfColor == 'Y')
        {
            area = (length * width);
            perimeter = (2 * length) + (2 * width);
            priceOfColor = (area * COLOR);          
            priceOfFrame = (perimeter * REGULAR_FRAME);
            priceOfCardboard = (area * CARDBOARD);
            priceOfGlass = (area * GLASS);      
            finalPrice = (priceOfFrame + priceOfColor + priceOfCardboard + priceOfGlass);       
        break;
        }
    case 'F':
    case 'f':
        if (choiceOfColor == 'N')
        {
            area = (length * width);
            perimeter = (2 * length) + (2 * width);
            priceOfFrame = (perimeter * FANCY_FRAME);
            priceOfCardboard = (area * CARDBOARD);
            priceOfGlass = (area * GLASS);
            finalPrice = (priceOfFrame + priceOfCardboard + priceOfGlass);
        break;
        }
        else if (choiceOfColor == 'Y')
        {
            area = (length * width);
            perimeter = (2 * length) + (2 * width);
            priceOfColor = (area * COLOR);          
            priceOfFrame = (perimeter * FANCY_FRAME);
            priceOfCardboard = (area * CARDBOARD);
            priceOfGlass = (area * GLASS);      
            finalPrice = (priceOfFrame + priceOfColor + priceOfCardboard + priceOfGlass);       
        break;
        }

}       

    System.out.println ("Would you like to add crowns? Enter Y (Yes), or N (No): ");    
    choiceOfCrowns = console.next().charAt(0);

    if (choiceOfCrowns == 'Y')
    {
        System.out.println ("How many crowns would you like? ");    
        numberOfCrowns = console.nextInt();     
        crownFinalPrice =(numberOfCrowns * CROWNS);
        grandTotalPrice = (crownFinalPrice + finalPrice);
    }   
    else if (choiceOfCrowns == 'N')
            System.out.printf ("Your total comes to: $%.2f%n", grandTotalPrice);    

    }   

}

【问题讨论】:

  • 这听起来像是编译器错误/警告,对吗?如果用户在某些提示中键入“Z”而不是“Y”或“N”,您的程序将如何表现。找到一种方法来处理这些情况。
  • 其他人专注于这是一个编译器警告这一狭隘的事实,并告诉你如何让它消失。这错过了编译器警告的重点......有关详细信息,请参阅我的答案,但避开专注于治疗症状的建议。

标签: java


【解决方案1】:

这是对您的代码的第一次重构,解决了您的特定问题以及其他一些问题。重构中解决的一些观察结果:

  • 回答您的问题(以及其他一些答案)不要养成盲目初始化变量的习惯,只是为了消除编译器错误。此处的编译器错误表明您需要清楚地定义变量(对您的应用程序有意义),而不管潜在状态如何。初始化变量只是隐藏了您有未处理的不受控制/意外状态的问题。

  • main() 方法太长,里面的东西太多了。我展示的重构是“下一步”的一个很好的例子,绝不是一个完整的过程……但第一件事是将一些逻辑提取到一个可消化的子集中。软件构建依赖于将事物分解为您可以推理的组件,为什么不从您的框架价格计算机开始呢?

  • 与上一点相关,您需要将变量范围缩小到可能的绝对最窄范围。地方是你的朋友。 最小化变量范围。这有助于将事物分解为单独的方法,但它也适用于将变量带入块等。这是good related SO discussion。还有,Effective Java 第 45 条:最小化局部变量的作用域。

  • 这个是有争议的,但我会提出我的意见。尽可能使用final。在大多数情况下,可变性不是你的朋友,这段代码就是一个很好的例子——你在很多不同的地方都有点点滴滴的值,而实际上大多数值都可以定义一次并且明确地定义,其他计算可以线性进行。它更具可读性/可维护性,您会惊讶于在 compile 阶段而不是在迭代调试中发现的逻辑错误数量。 支持不变性,明智地允许可变性,并且只在有原因的情况下进行。请参阅my answer on this question 了解一些讨论以及几个相关的链接 SO 问题。另外,Effective Java 第 15 条:最小化可变性。

  • 更喜欢enum 而不是Stringchar 或其他。尤其是在处理来自用户或外部系统的潜在行为不端的输入时,您的首要目标应该是将状态空间减少到绝对最小值,并将事物放入受控词汇表中。我在这里做了第一遍,但是您的代码应该向用户抛出异常或发出错误消息(取决于抽象级别;这里发出错误并再次要求输入)尽可能靠近界面,不允许非结构化/脏数据传播到应用程序中比必要的更深。它越深入,你拥有的上下文就越少,处理失败的逻辑就越丑陋/混乱。然后,您增加了错误处理逻辑,使极其简单的内部计算无法测试。

  • 重复是一种代码味道。如果您在两个不同的地方计算面积,请重构它以避免。与 SO 相关:"What duplication threshold...?",但是像面积 = 长度 x 宽度这样简单的东西的重复阈值恰好是一次。 尽量减少或消除重复代码。

  • 在特殊情况下使用Exception。这实际上用我的第一点和你的问题结束了循环。您的代码具有编译器不确定是否正在初始化的变量,因为在异常情况下没有明确定义的值可供它们初始化——它们表示错误输入,而不是编译器警告要压缩。控制状态空间,当遇到意外状态时,抛出异常。比无声但不正确的结果更好的是明确的、可解释的、可能可恢复的错误。

还有更多,但我认为这是一组有用的批评,而且超出了您的要求。代码:

import java.util.*;

public final class PictureFrames
{

  static Scanner console = new Scanner(System.in);

  static final double REGULAR_FRAME = .15, FANCY_FRAME = .25;
  static final double COLOR = .10, CARDBOARD = .02, GLASS = .07, CROWNS = .35;

  enum FrameType {
    /** Regular. */
    R, 
    /** Fancy. */
    F;
  };


  static double areaPriceInDollars(final FrameType frameType,
                                   final double length,
                                   final double width,
                                   final boolean color)
  {
    final double area,perimeter,
      priceOfFrame,
      priceOfCardboard,
      priceOfGlass,
      priceOfColor;

      area = length * width;
      perimeter = 2 * (length + width);

      priceOfCardboard = (area * CARDBOARD);
      priceOfGlass = (area * GLASS);      

      if (color) 
        priceOfColor = area * COLOR;
      else 
        priceOfColor = 0.0;

      switch (frameType) {
        case R:
          priceOfFrame = (perimeter * REGULAR_FRAME);
          break;
        case F:
          priceOfFrame = (perimeter * FANCY_FRAME);
          break;
        default:
          throw new IllegalArgumentException("FrameType "+frameType+" unknown, no price available.");
      }

      return priceOfColor + priceOfCardboard + priceOfGlass + priceOfFrame;
    }       


  public static void main(String[] args)
  {
    System.out.println("Please enter the length of your picure in inches:");
    final double length = console.nextDouble();

    System.out.println("Please enter the width of your picure in inches: ");
    final double width = console.nextDouble();

    System.out
        .println("Please enter the type of frame: R or r (Regular), F or f (Fancy). ");
    final char typeOfFrame = console.next().charAt(0);
    FrameType frameType = FrameType.valueOf(""
        + Character.toUpperCase(typeOfFrame));

    System.out
        .println("Would you like to add color?: Y for (Yes), N for (No): ");
    final char choiceOfColor = console.next().charAt(0);
    final boolean color = Character.toUpperCase(choiceOfColor) == 'Y';

    System.out
        .println("Would you like to add crowns? Enter Y (Yes), or N (No): ");
    final char choiceOfCrowns = console.next().charAt(0);
    final boolean crowns = Character.toUpperCase(choiceOfCrowns) == 'Y';

    final double priceOfCrowns;
    if (crowns) {
      System.out.println("How many crowns would you like? ");
      final int numberOfCrowns = console.nextInt();
      priceOfCrowns = (numberOfCrowns * CROWNS);
    } else {
      priceOfCrowns = 0.0;
    }

    final double grandTotalPrice = priceOfCrowns
        + areaPriceInDollars(frameType, length, width, color);
    System.out.printf("Your total comes to: $%.2f%n", grandTotalPrice);
  }
}

【讨论】:

  • 谢谢安德索!这是非常有用和信息丰富的。感谢您提供所有精彩的信息和链接。我非常感谢您的帮助和其他信息。
【解决方案2】:

如果 typeOfFrame 不是 R、r、F 或 f 会发生什么? 一种可能的解决方案是向开关添加“默认”大小写。

我认为这段代码更接近你想要的(我减少了很多重复)。请注意,我并没有真正处理错误(对 isValidChoice 和 isFrameType 方法的调用是如何处理它们的开始)。上面的问题是,您基本上假设用户只会输入有效输入,而忘记了有时即使 Y 和 N 是他们可以在 Q 中输入的唯一有效选择。我还修复了一个错误,其中您并不总是打印总计,也没有设置总计(printf 上方的最后一部分)。

我的最后一个建议是不要为此使用双打(除非你的导师告诉你),因为在某些情况下它们会给你不准确的数字。改为查看 java.math.BigDecimal。

class Main
{
    static Scanner console = new Scanner(System.in);    
    static final double REGULAR_FRAME = .15;
    static final double FANCY_FRAME = .25;
    static final double COLOR = .10;
    static final double CARDBOARD = .02;
    static final double GLASS = .07;
    static final double CROWNS = .35;

    public static void main (String[] args)
    {   
        final double length;
        final double width;
        final char   typeOfFrame;
        final char   choiceOfColor;

        System.out.println ("Please enter the length of your picure in inches:");
        length = console.nextDouble();

        System.out.println ("Please enter the width of your picure in inches: ");
        width = console.nextDouble();

        System.out.println ("Please enter the type of frame: R or r (Regular), F or f (Fancy). ");  
        typeOfFrame = console.next().charAt(0);

        System.out.println ("Would you like to add color?: Y for (Yes), N for (No): "); 
        choiceOfColor = console.next().charAt(0);

        if(!(isFrameType(typeOfFrame)))
        {

        }
        else
        {
            final double area;
            final double perimeter; 
            final double priceOfFrame;
            final double priceOfCardboard;
            final double priceOfGlass;

            area             = (length * width);
            perimeter        = (2 * length) + (2 * width);
            priceOfFrame     = (perimeter * REGULAR_FRAME);
            priceOfCardboard = (area * CARDBOARD);
            priceOfGlass     = (area * GLASS);  

            if(isValidChoice(choiceOfColor))
            {
                final double priceOfColor;
                final double finalPrice;
                final char   choiceOfCrowns;
                final double grandTotalPrice; 

                if(choiceOfColor == 'N')
                {
                    finalPrice = (priceOfFrame + priceOfCardboard + priceOfGlass);
                }
                else
                {
                    priceOfColor = (area * COLOR);          
                    finalPrice   = (priceOfFrame + priceOfColor + priceOfCardboard + priceOfGlass);     
                }    

                System.out.println ("Would you like to add crowns? Enter Y (Yes), or N (No): ");    
                choiceOfCrowns = console.next().charAt(0);

                if(isValidChoice(choiceOfCrowns))
                {
                    if(choiceOfCrowns == 'Y')
                    {
                        final double crownFinalPrice;
                        final int    numberOfCrowns;

                        System.out.println ("How many crowns would you like? ");    
                        numberOfCrowns  = console.nextInt();        
                        crownFinalPrice =(numberOfCrowns * CROWNS);
                        grandTotalPrice = (crownFinalPrice + finalPrice);
                    }   
                    else
                    {
                        grandTotalPrice = finalPrice;
                    }

                    System.out.printf ("Your total comes to: $%.2f%n", grandTotalPrice);    
                }
            }
        }
    }   

    private static boolean isFrameType(final char c)
    {        
        final char lower;

        lower = Character.toLowerCase(c);

        return (lower == 'r' || lower == 'f');
    }

    private static boolean isValidChoice(final char c)
    {
        return (c == 'Y' || c == 'N');
    }
}

【讨论】:

  • +1,既能找到提问者问题的根源,又能转向更大的图景。
【解决方案3】:

发生这种情况是因为编译器警告您在应用程序的某些运行期间可能从未设置过这些值。它们在switchif 语句中设置,但编译器会警告您某些输入可能会导致它们未被初始化。

在定义它们时只需将它们设置为一个值就足够了:

double finalPrice = 0, grandTotalPrice = 0; 

但您也可以确保无论应用程序采用何种路径都设置它们(无论如何,了解所有可能的路径是一种很好的做法)。

【讨论】:

  • 是的,但这只是回避/中和一个非常有用的编译器警告,而不是利用它来确保正确性。除非您明确且有意义地初始化一个可变变量(例如,一个累加器),否则您不应该仅仅为了使代码编译而初始化它们——而是确保在所有情况下都正确设置了变量。
  • 这就是为什么我指出“知道所有可能的路径无论如何都是一个好习惯”。也许我不够清楚。如果他确定他已经覆盖了代码的路径,那么初始化值会处理那些他没有明确设置值的路径。这是我想我们大多数更有经验的程序员每天都遵循的模式。没有理由不提。
  • 我想我错过了。对不起。对我来说,未处理的路径很简单,很容易控制。我的下意识反应是将任何变量初始化为 0.0 视为反模式,通常错误/懒惰而不是正确。 (显然不完全是……)
猜你喜欢
  • 2017-12-24
  • 2016-07-17
  • 1970-01-01
  • 1970-01-01
  • 2020-02-20
  • 1970-01-01
相关资源
最近更新 更多