5
\$\begingroup\$

I have created a Java program which generates a key (for activating a piece of software for example) my code works correctly however there are parts of it that I know can be improved. In particular inside the createKey method were I have repeated an if statement in multiple places.

I would appreciate if you can take some time to look at and spots ways to improve my code.I would rather learn first and foremost ways to refine the program in its current form. Also, if you see any bad habits, let me know.

I plan to save each key to a file and build a validator that allows input and references the file.

Description

Creates a key of 20 letters and numbers - the letters and numbers are set a weighting, if two is returned as a weight for a letter and three for numbers.

AA232BC433 ... so two letters then three numbers and so on.

10 numbers and 10 letters are then selected.

Each is chosen through a loop that runs 10 times and randomly assigns a value from each final array into a set of 'chosen' values.

The create key method then cycles through various loops which use the weights to add a further random selection of the chosen values - checks are added which ensure that the string builder stops adding new values when the length is 20.

public class KeyTestDrive {

public static void main(String[] args) {
    Key key = new Key();
    key.createKey();
}

}

import java.util.Random;

public class Key {

private final int[] possibleNumbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
private final char[] possibleLetters = { 'A', 'B', 'C', 'D', 'E', 'F', 'G',
        'H', 'I', 'J', 'K', 'L', 'M', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
        'V', 'W', 'X', 'Y', 'Z' };

Random randomNum = new Random();
StringBuilder key = new StringBuilder();

public void createKey() {
    int letterWeighting = determineLetterWeigthing();
    int numberWeighting = determineNumberWeigthing();

    int[] chosenNumbers = selectNumbers();
    char[] chosenLetters = selectLetters();

    int randomIndex;

    boolean isSizeTwenty = false;

    for (int i = 0; i < 20; i++) {

        if(isSizeTwenty == true){
            break;
        }

        for (int j = 0; j < letterWeighting; j++) {
            randomIndex = randomNum.nextInt(9);
            key.append(chosenLetters[randomIndex]);

            if(key.length() == 20){
                isSizeTwenty = true;
                break;
            }
        }

        if(isSizeTwenty == true){
            break;
        }

        for (int k = 0; k < numberWeighting; k++) {
            randomIndex = randomNum.nextInt(9);
            key.append(chosenNumbers[randomIndex]);

            if(key.length() == 20){
                isSizeTwenty = true;
                break;
            }
        }

        if(isSizeTwenty == true){
            break;
        }

    }
    System.out.println(key.length());
    System.out.println(key);
}

private char[] selectLetters() {
    char[] chosenLetters = new char[10];

    for (int j = 0; j < 10; j++) {
        int randomIndex = randomNum.nextInt(possibleLetters.length);

        chosenLetters[j] = possibleLetters[randomIndex];
    }

    return chosenLetters;
}

private int[] selectNumbers() {
    int[] chosenNumbers = new int[10];

    for (int j = 0; j < 10; j++) {
        int randomIndex = randomNum.nextInt(possibleNumbers.length);

        chosenNumbers[j] = possibleNumbers[randomIndex];
    }

    return chosenNumbers;
}

private int determineLetterWeigthing() {
    int letterWeighting;
    letterWeighting = randomNum.nextInt(4) + 1;

    return letterWeighting;
}

private int determineNumberWeigthing() {
    int numberWeighting;
    numberWeighting = randomNum.nextInt(4) + 1;

    return numberWeighting;
}
}
\$\endgroup\$
3
\$\begingroup\$

Improving createKey

First of all, when the same couple of lines appear duplicated several times, there's clearly a problem. I'm talking about this one:

if(isSizeTwenty == true){
    break;
}

A good first step is to try to eliminate this. Consider this situation:

boolean isSizeTwenty = false;

for (int i = 0; i < 20; i++) {

    if(isSizeTwenty == true){
        break;
    }

    // ...

    if(isSizeTwenty == true){
        break;
    }
}

Notice that the first check inside the loop will be always false: isSizeTwenty is initialized with false, and since you have this check as the last step of the loop, the condition can never be true at the beginning of the loop.

Also, don't write conditions on boolean expressions like this:

if (someboolean == true) { ... }

You can write simpler and better like this:

if (someboolean) { ... }

But, the entire method can be refactored far better, for example:

public String createKey() {
    int letterWeighting = determineLetterWeigthing();
    int numberWeighting = determineNumberWeigthing();

    int[] chosenNumbers = selectNumbers();
    char[] chosenLetters = selectLetters();

    StringBuilder key = new StringBuilder();

    while (key.length() < targetLength) {
        for (int j = 0; j < letterWeighting && key.length() < targetLength; j++) {
            int randomIndex = random.nextInt(chosenLetters.length - 1);
            key.append(chosenLetters[randomIndex]);
        }
        for (int k = 0; k < numberWeighting && key.length() < targetLength; k++) {
            int randomIndex = random.nextInt(chosenNumbers.length - 1);
            key.append(chosenNumbers[randomIndex]);
        }
    }
    return key.toString();
}

I made a couple of changes there:

  • Replaced the number 20 with a meaningful variable named targetLength
  • Removed of the isSizeTwenty variable, and moved the condition key.length() < targetLength
  • Moved StringBuilder inside the method. No need to make that a member variable
  • Replaced the outermost for (int i; i < 20; i++) { ... } with a more natural while (key.length() < targetLength) { ... }
  • Replaced the hardcoded number 9 with chosenLetters.length - 1 and chosenNumbers.length - 1. This is more than just about removing another magic number, this is also safer, because now randomIndex can never be accidentally out of bounds. Now it is derived from the length of the available letters and numbers.
  • Renamed randomNum to just random, because there is nothing "num"-like about an instance of Random

Improving determineLetterWeigthing and determineNumberWeigthing

You could reduce these methods to single lines:

private int determineLetterWeigthing() {
    return random.nextInt(4) + 1;
}

private int determineNumberWeigthing() {
    return random.nextInt(4) + 1;
}

Unit testing

I would make random and targetLength final members, and add these constructors:

private final Random random;
private final int targetLength;

public Key(int targetLength) {
    this(targetLength, new Random());
}

public Key(int targetLength, Random random) {
    this.random = random;
    this.targetLength = targetLength;
}

This way, and thanks to the new version of createKey that returns the key instead of printing it, now you can add some unit tests:

@Test
public void testExampleKeys() {
    assertEquals("VAT3735TVV4949RVJ793", new Key(20, new Random(0)).createKey());
    assertEquals("SBM8PBJ8PSJ6MWS2SBO2", new Key(20, new Random(1)).createKey());
    assertEquals("DGG77DJS28SDP13GGD77", new Key(20, new Random(2)).createKey());
    assertEquals("PGB844MGM477GCG728PL", new Key(20, new Random(3)).createKey());
}

@Test
public void testCreateMultipleKeysFromOneGenerator() {
    Key keygen = new Key(20, new Random(10));
    assertEquals("YTD15DTT52YTO12IRL21", keygen.createKey());
    assertEquals("IK617LP637LG611IK619", keygen.createKey());
    assertEquals("VH1WY5YO1YP8SO9YO1YW", keygen.createKey());
}

Actually, it would be better to rename your class to KeyGenerator. Notice the 2nd unit test above: I called the instance keygen, which is just natural, and changing the class name to match would be natural too.

Other things

An easier way to create possibleLetters:

private final char[] possibleLetters = "ABCDEFGHIJKLMOPQRSTUVWXYZ".toCharArray();

Btw, do you have something against the letter "N"? It wasn't on your list :)


In selectLetters and selectNumbers, 10 is a magic number:

char[] chosenLetters = new char[10];

It would be better to make it a private static final constant at the top of the class with a good name.

Actually it would be better to make selectLetters and selectNumbers take a parameter.

\$\endgroup\$
  • 1
    \$\begingroup\$ This is fantastic Janos _ I am currently a student so apologies for some of the 'noob' mistakes :) \$\endgroup\$ – Jordantttt Sep 19 '14 at 8:27
  • \$\begingroup\$ No need to apologize. You want to learn, you came to the right place! \$\endgroup\$ – janos Sep 19 '14 at 8:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.