Clean Code Applied to JavaScript – Part VII: Practical Refactoring Example: Ceaser Cipher

The introduction

In this series of articles, we’ve covered some tips for writing highly maintainable code. These programming tips and advice come from the book, The Code Clean Way, and my experience applying them over the years.

In this article, I’ll show you how to implement a step-by-step refactoring of code, and I’ll show you how to apply these techniques in practice, using code I’ve already implemented in my programming Basics course as an example. If you are new to or studying software development, it is recommended that you use familiar techniques and tools to practice and understand the code examples in this article (which uses JavaScript as the programming language). If you already have some programming background, it will be relatively easy to understand the refactoring case. In this case, there was an initial code implementation, and the challenge was to apply different refactoring techniques to gain a deeper understanding of the code and make it easier to maintain.

For this challenge, I’ve prepared a GIT repository where you can find all versions of the algorithm we’ve refactored for each step in this article. The code in each refactoring step can be executed through a series of NPM scripts. The script commands are as follows:

npm run stepX # Where X is the step
Copy the code

GIT repositories for related code: Code repository links.

Question: Caesar code

Ceaser Cipher is one of the simplest and best known encryption techniques. It is a substitution encryption technique in which all letters in plaintext are replaced with ciphertext by a fixed number of shifts to the left (or right) of the alphabet. For example, when the right offset is 3, all the letters E will be replaced with H, F becomes I, and so on. For more information about Caesar’s password, visit Wikipedia.

The Caesar cipher is composed of two alphabets: plaintext and ciphertext. Ciphertext is obtained by moving the plaintext alphabet to the left or right by a fixed position. For example, this Caesar cipher uses an offset of 6, which is equivalent to a 6 bit shift to the right:

Plain:    ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher:   GHIJKLMNOPQRSTUVWXYZABCDEF 
Copy the code

During encryption, the position of each letter in the plaintext alphabet is found and the corresponding letter in the ciphertext alphabet is recorded.

Plaintext: THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG

Ciphertext: QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD

Decryption is done in reverse, moving six positions to the left.

What is refactoring? Why refactor?

Refactoring is a well-known topic in the software development industry. To this, we are introduced in the topic, but I suggest you read the following article: www.cuelogic.com/blog/what-i… . From this article, we have extracted the main ideas that we will share here.

Refactoring is not a silver bullet, but it is a valuable weapon that can help you control both your code and your project (software/application).

It is the scientific process of adapting existing code to make it more readable, understandable, and cleaner. Moreover, it makes it easy to add new features, build large applications, and find and fix bugs.

Refactoring is important for:

  • Improve software/application design
  • Make software easier to understand
  • Found a bug
  • Repair the existing old database
  • Provide better consistency for users

The original code

Once we know the problem we want to solve, we can implement a solution that anyone can accomplish in very little time.

function cipher(text, shift) {
  var cipher = ' ';
  shift = shift % 26;
  for (var i = 0; i < text.length; i++) {
    if (text.charCodeAt(i) >= 65 && text.charCodeAt(i) <= 90) {
      if (text.charCodeAt(i) + shift > 90) {
        cipher = cipher.concat(
          String.fromCharCode(text.charCodeAt(i) + shift - 26)); }else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}else if (text.charCodeAt(i) >= 97 && text.charCodeAt(i) <= 122) {
      if (text.charCodeAt(i) + shift > 122) {
        cipher = cipher.concat(
          String.fromCharCode(text.charCodeAt(i) + shift - 26)); }else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}else {
      // blank space
      cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}return cipher.toString();
}

function decipher(text, shift) {
  var decipher = ' ';
  shift = shift % 26;
  for (var i = 0; i < text.length; i++) {
    if (text.charCodeAt(i) >= 65 && text.charCodeAt(i) <= 90) {
      if (text.charCodeAt(i) - shift < 65) {
        decipher = decipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift + 26)); }else {
        decipher = decipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift), ); }}else if (text.charCodeAt(i) >= 97 && text.charCodeAt(i) <= 122) {
      if (text.charCodeAt(i) - shift < 97) {
        decipher = decipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift + 26)); }else {
        decipher = decipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift), ); }}else {
      // blank space
      decipher = decipher.concat(
        String.fromCharCode(text.charCodeAt(i) - shift), ); }}return decipher.toString();
}

Copy the code

The code we are going to develop has two methods:

  • cipher– It moves the text in one direction.
  • decipher– to perform withcipherThe opposite operation, which is to decipher the text.

I recommend that no matter how we refactor the code, we use automated tests to help verify that we are not “breaking” the code. In this example, instead of implementing a full test scenario, I created two checks using the standard console.assert.

Therefore, we will check whether the algorithm is stable with the following assertions.

console.assert(
  cipher('Hello World'.1) = = ='Ifmmp! Xpsme'.`${cipher('Hello World'.1)}=== 'Ifmmp! Xpsme'`,);console.assert(
  decipher(cipher('Hello World'.3), 3) = = ='Hello World'.`${decipher(cipher('Hello World'.3), 3)} === 'Hello World'`,);Copy the code

Now let’s rise to the challenge and start refactoring!

Step 1. Magic number

The first step is to remove magic numbers from the code by defining semantically defined variables. The following numbers can be modified as follows:

  1. Number of letters of the alphabet (26)
  2. The critical value of each letter in the algorithm loop, namely:
  • a: 65
  • z: 90
  • A: 97
  • Z: 122

Therefore, we define the following constants that make the numbers semantically relevant.

const NUMBER_LETTERS = 26;
const LETTER = {
  a: 65.z: 90.A: 97.Z: 122};Copy the code

After the first change, the code looks like this.

const NUMBER_LETTERS = 26;
const LETTER = {
  a: 65.z: 90.A: 97.Z: 122};function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    if (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z) {
      if (text.charCodeAt(i) + shift > LETTER.z) {
        cipher = cipher.concat(
          String.fromCharCode(text.charCodeAt(i) + shift - NUMBER_LETTERS),
        );
      } else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}else if (
      text.charCodeAt(i) >= LETTER.A &&
      text.charCodeAt(i) <= LETTER.Z
    ) {
      if (text.charCodeAt(i) + shift > LETTER.Z) {
        cipher = cipher.concat(
          String.fromCharCode(text.charCodeAt(i) + shift - NUMBER_LETTERS),
        );
      } else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}else {
      // blank space
      cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}return cipher.toString();
}

function decipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    if (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z) {
      if (text.charCodeAt(i) - shift < LETTER.a) {
        cipher = cipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift + NUMBER_LETTERS),
        );
      } else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) - shift)); }}else if (
      text.charCodeAt(i) >= LETTER.A &&
      text.charCodeAt(i) <= LETTER.Z
    ) {
      if (text.charCodeAt(i) - shift < LETTER.A) {
        cipher = cipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift + NUMBER_LETTERS),
        );
      } else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) - shift)); }}else {
      cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) - shift)); }}return cipher.toString();
}

console.assert(
  cipher('Hello World'.1) = = ='Ifmmp! Xpsme'.`${cipher('Hello World'.1)}=== 'Ifmmp! Xpsme'`,);console.assert(
  decipher(cipher('Hello World'.3), 3) = = ='Hello World'.`${decipher(cipher('Hello World'.3), 3)} === 'Hello World'`,);Copy the code

Step 2. Extract similar code from if-else

The next step is to extract the duplicate code from the code into the function. Specifically, the assignment code in the if control structure is repeated throughout the code and can be extracted.

In other words, this code fragment cipher=cipher.concat (string.fromCharcode) can be extracted from different ifs in the code. This line is executed after the if structure, which contains only the different logic in each case.

Of course, what we do with cipher functions also applies to decipher functions.

After applying this refactoring, the code looks like this:

function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;

  for (let i = 0; i < text.length; i++) {
    let character = ' ';
    if (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z) {
      if (text.charCodeAt(i) + shift > LETTER.z) {
        character = text.charCodeAt(i) + shift - NUMBER_LETTERS;
      } else{ character = text.charCodeAt(i) + shift; }}else if (
      text.charCodeAt(i) >= LETTER.A &&
      text.charCodeAt(i) <= LETTER.Z
    ) {
      if (text.charCodeAt(i) + shift > LETTER.Z) {
        character = text.charCodeAt(i) + shift - NUMBER_LETTERS;
      } else{ character = text.charCodeAt(i) + shift; }}else {
      // blank space
      character = text.charCodeAt(i) + shift;
    }
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
function decipher(text, shift) {
  let cipher = ' ';

  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    let character = ' ';
    if (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z) {
      if (text.charCodeAt(i) - shift < LETTER.a) {
        character = text.charCodeAt(i) - shift + NUMBER_LETTERS;
      } else{ character = text.charCodeAt(i) - shift; }}else if (
      text.charCodeAt(i) >= LETTER.A &&
      text.charCodeAt(i) <= LETTER.Z
    ) {
      if (text.charCodeAt(i) - shift < LETTER.A) {
        character = text.charCodeAt(i) - shift + NUMBER_LETTERS;
      } else{ character = text.charCodeAt(i) - shift; }}else {
      character = text.charCodeAt(i) + shift;
    }
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
Copy the code

Step 3. Avoid using else

The next step is to avoid using else control structures. It’s easy to avoid it, just move the code from else to the variable character and use it as its default value before the loop starts.

So, the refactored code looks like this:

function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    let character = text.charCodeAt(i) + shift;
    if (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z) {
      if (text.charCodeAt(i) + shift > LETTER.z) {
        character = text.charCodeAt(i) + shift - NUMBER_LETTERS;
      } else{ character = text.charCodeAt(i) + shift; }}else if (
      text.charCodeAt(i) >= LETTER.A &&
      text.charCodeAt(i) <= LETTER.Z
    ) {
      if (text.charCodeAt(i) + shift > LETTER.Z) {
        character = text.charCodeAt(i) + shift - NUMBER_LETTERS;
      } else {
        character = text.charCodeAt(i) + shift;
      }
    }
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
function decipher(text, shift) {
  let cipher = ' ';

  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    let character = text.charCodeAt(i) + shift;
    if (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z) {
      if (text.charCodeAt(i) - shift < LETTER.a) {
        character = text.charCodeAt(i) - shift + NUMBER_LETTERS;
      } else{ character = text.charCodeAt(i) - shift; }}else if (
      text.charCodeAt(i) >= LETTER.A &&
      text.charCodeAt(i) <= LETTER.Z
    ) {
      if (text.charCodeAt(i) - shift < LETTER.A) {
        character = text.charCodeAt(i) - shift + NUMBER_LETTERS;
      } else {
        character = text.charCodeAt(i) - shift;
      }
    }
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
Copy the code

Step 4. Merge the IF logic

The next step is cumbersome for us, but we have to merge the logic corresponding to if-elseif. So, we just need to have two if structures. This operation will allow us to see in later steps that we do have two alternative paths instead of the one we see now.

The merged if logic looks like this:

function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    let character = text.charCodeAt(i) + shift;
    if (
      (text.charCodeAt(i) >= LETTER.a &&
        text.charCodeAt(i) <= LETTER.z &&
        text.charCodeAt(i) + shift > LETTER.z) ||
      (text.charCodeAt(i) >= LETTER.A &&
        text.charCodeAt(i) <= LETTER.Z &&
        text.charCodeAt(i) + shift > LETTER.Z)
    ) {
      character = text.charCodeAt(i) + shift - NUMBER_LETTERS;
    }
    if( (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z && text.charCodeAt(i) + shift > LETTER.z && ! (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z)) || (text.charCodeAt(i) >= LETTER.A && text.charCodeAt(i) <= LETTER.Z && ! (text.charCodeAt(i) + shift > LETTER.Z)) ) { character = text.charCodeAt(i) + shift; } cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

function decipher(text, shift) {
  let cipher = ' ';

  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    let character = text.charCodeAt(i) - shift;
    if (
      (text.charCodeAt(i) >= LETTER.a &&
        text.charCodeAt(i) <= LETTER.z &&
        text.charCodeAt(i) - shift < LETTER.a) ||
      (text.charCodeAt(i) >= LETTER.A &&
        text.charCodeAt(i) <= LETTER.Z &&
        text.charCodeAt(i) - shift < LETTER.A)
    ) {
      character = text.charCodeAt(i) - shift + NUMBER_LETTERS;
    }
    if( (text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z && ! (text.charCodeAt(i) - shift < LETTER.a)) || (text.charCodeAt(i) >= LETTER.A && text.charCodeAt(i) <= LETTER.Z && ! (text.charCodeAt(i) - shift < LETTER.A)) ) { character = text.charCodeAt(i) - shift; } cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
Copy the code

Step 5. Simplify the algorithm logic

In this step, we must show that our algorithm does not require two IF structures. In contrast, cipher and Decipher functions both have if-else structures. For the function cipher, we see that there are two possible values that can be assigned to the variable character, the first possible value derived from the corresponding first if.

character = text.charCodeAt(i) + shift - NUMBER_LETTERS;
Copy the code

The second possible value is obtained by default and from another if construct:

character = text.charCodeAt(i) + shift;
Copy the code

Therefore, the logic of the second if can be removed and the control structure converted to the else corresponding to the first if structure. In case this if condition is not satisfied, the second possible value will be assigned to the variable character. Regardless of whether the second if is satisfied, the default value is assigned.

The refactored code looks like this:

function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    let character;
    if (
      (text.charCodeAt(i) >= LETTER.a &&
        text.charCodeAt(i) <= LETTER.z &&
        text.charCodeAt(i) + shift > LETTER.z) ||
      (text.charCodeAt(i) >= LETTER.A &&
        text.charCodeAt(i) <= LETTER.Z &&
        text.charCodeAt(i) + shift > LETTER.Z)
    ) {
      character = text.charCodeAt(i) + shift - NUMBER_LETTERS;
    } else {
      character = text.charCodeAt(i) + shift;
    }

    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

function decipher(text, shift) {
  let cipher = ' ';

  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    if (
      (text.charCodeAt(i) >= LETTER.a &&
        text.charCodeAt(i) <= LETTER.z &&
        text.charCodeAt(i) - shift < LETTER.a) ||
      (text.charCodeAt(i) >= LETTER.A &&
        text.charCodeAt(i) <= LETTER.Z &&
        text.charCodeAt(i) - shift < LETTER.A)
    ) {
      character = text.charCodeAt(i) - shift + NUMBER_LETTERS;
    } else {
      character = text.charCodeAt(i) - shift;
    }
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
Copy the code

Step 6. Packaging conditions

The lack of semantic values makes the conditions of our algorithm very complex and difficult to understand. So the next step in the code is to encapsulate the condition.

Specifically, we focus on encapsulating cipher and Decipher conditions:

cipher:

(text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z && text.charCodeAt(i) + shift > LETTER.z) 
||
(text.charCodeAt(i) >= LETTER.A && text.charCodeAt(i) <= LETTER.Z && text.charCodeAt(i) + shift > LETTER.Z)
Copy the code

decipher:

(text.charCodeAt(i) >= LETTER.a && text.charCodeAt(i) <= LETTER.z && text.charCodeAt(i) - shift < LETTER.a) 
||
(text.charCodeAt(i) >= LETTER.A && text.charCodeAt(i) <= LETTER.Z && text.charCodeAt(i) - shift < LETTER.A)
Copy the code

In fact, this logic can be summarized as the following four functions:

function isOutLowerCharacterCipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.a &&
    text.charCodeAt(position) <= LETTER.z &&
    text.charCodeAt(position) + shift > LETTER.z
  );
}

function isOutUpperCharacterCipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.A &&
    text.charCodeAt(position) <= LETTER.Z &&
    text.charCodeAt(position) + shift > LETTER.Z
  );
}

function isOutLowerCharacterDecipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.a &&
    text.charCodeAt(position) <= LETTER.z &&
    text.charCodeAt(position) - shift < LETTER.a
  );
}

function isOutUpperCharacterDecipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.A &&
    text.charCodeAt(position) <= LETTER.Z &&
    text.charCodeAt(position) - shift < LETTER.A
  );
}
Copy the code

The code after this encapsulation is executed looks like this:

function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    let character;
    if (
      isOutLowerCharacterCipher(text, i, shift) ||
      isOutUpperCharacterCipher(text, i, shift)
    ) {
      character = text.charCodeAt(i) + shift - NUMBER_LETTERS;
    } else {
      character = text.charCodeAt(i) + shift;
    }

    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

function decipher(text, shift) {
  let cipher = ' ';

  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    if (
      isOutLowerCharacterDecipher(text, i, shift) ||
      isOutUpperCharacterDecipher(text, i, shift)
    ) {
      character = text.charCodeAt(i) - shift + NUMBER_LETTERS;
    } else {
      character = text.charCodeAt(i) - shift;
    }
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
Copy the code

Step 7. Remove if-else control structures

The if else control structure assigns to the same variable (character). Therefore, you can extract the logic of the condition from the if and store it in a variable, as follows:

const isOutAlphabet =
      isOutLowerCharacterCipher(text, i, shift) ||
      isOutUpperCharacterCipher(text, i, shift);
Copy the code

The variable character is modified alternately with two possible values:

  1. NUMBER_LETTERS
  2. 0 (NO_ROTATION);

Therefore, we can define the variable rotation to increase the granularity of our code, as shown below:

const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;

Copy the code

The resulting code is as follows:

const isOutAlphabet =
isOutLowerCharacterCipher(text, i, shift) ||
isOutUpperCharacterCipher(text, i, shift);
const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
const character = text.charCodeAt(i) + shift - rotation;

cipher = cipher.concat(String.fromCharCode(character));
Copy the code

The code for the next two functions in this step is as follows:

function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    const isOutAlphabet =
      isOutLowerCharacterCipher(text, i, shift) ||
      isOutUpperCharacterCipher(text, i, shift);
    const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
    const character = text.charCodeAt(i) + shift - rotation;

    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

function decipher(text, shift) {
  let cipher = ' ';

  shift = shift % NUMBER_LETTERS;
  for (let i = 0; i < text.length; i++) {
    const isOutAlphabet =
      isOutLowerCharacterDecipher(text, i, shift) ||
      isOutUpperCharacterDecipher(text, i, shift);
    const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
    const character = text.charCodeAt(i) - shift + rotation;
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
Copy the code

Step 8. Variable naming

The final step in completing the algorithm refactoring is to rename the variable I in the loop to a more appropriate name, such as position. (This change may seem “small,” but it is important that we specify semantic values for the variables, including the classic I, j, and K in the loop.

After applying these simple steps, the final result of our algorithm is as follows:

function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let position = 0; position < text.length; position++) {
    const isOutAlphabet =
      isOutLowerCharacterCipher(text, position, shift) ||
      isOutUpperCharacterCipher(text, position, shift);
    const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
    const character = text.charCodeAt(position) + shift - rotation;

    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

function decipher(text, shift) {
  let cipher = ' ';

  shift = shift % NUMBER_LETTERS;
  for (let position = 0; position < text.length; position++) {
    const isOutAlphabet =
      isOutLowerCharacterDecipher(text, position, shift) ||
      isOutUpperCharacterDecipher(text, position, shift);
    const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
    const character = text.charCodeAt(position) - shift + rotation;
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}
Copy the code

conclusion

In this article, we offer some suggestions for refactoring the original raw code into understandable code.

In this article, a step-by-step case study demonstrates the refactoring process. While you may feel that some refactorings are inappropriate, there are other refactorings. In each case, you can share your constructive ideas with the community as a whole.

Finally, this paper mainly discusses the following points:

  • The magic number
  • fromif-elseExtract similar code from
  • Avoid the use ofelse
  • Merge IF logic
  • Simplified algorithmic logic
  • Encapsulation condition
  • removeif-elseControl structure
  • Variable naming

In the end, I’ll leave the code, both raw and final, so you can make the final tradeoffs.

function cipher(text, shift) {
  var cipher = ' ';
  shift = shift % 26;
  for (var i = 0; i < text.length; i++) {
    if (text.charCodeAt(i) >= 65 && text.charCodeAt(i) <= 90) {
      if (text.charCodeAt(i) + shift > 90) {
        cipher = cipher.concat(
          String.fromCharCode(text.charCodeAt(i) + shift - 26)); }else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}else if (text.charCodeAt(i) >= 97 && text.charCodeAt(i) <= 122) {
      if (text.charCodeAt(i) + shift > 122) {
        cipher = cipher.concat(
          String.fromCharCode(text.charCodeAt(i) + shift - 26)); }else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}else {
      // blank space
      cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift)); }}return cipher.toString();
}

function decipher(text, shift) {
  var decipher = ' ';
  shift = shift % 26;
  for (var i = 0; i < text.length; i++) {
    if (text.charCodeAt(i) >= 65 && text.charCodeAt(i) <= 90) {
      if (text.charCodeAt(i) - shift < 65) {
        decipher = decipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift + 26)); }else {
        decipher = decipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift), ); }}else if (text.charCodeAt(i) >= 97 && text.charCodeAt(i) <= 122) {
      if (text.charCodeAt(i) - shift < 97) {
        decipher = decipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift + 26)); }else {
        decipher = decipher.concat(
          String.fromCharCode(text.charCodeAt(i) - shift), ); }}else {
      // blank space
      decipher = decipher.concat(
        String.fromCharCode(text.charCodeAt(i) - shift), ); }}return decipher.toString();
}

console.assert(
  cipher('Hello World'.1) = = ='Ifmmp! Xpsme'.`${cipher('Hello World'.1)}=== 'Ifmmp! Xpsme'`,);console.assert(
  decipher(cipher('Hello World'.3), 3) = = ='Hello World'.`${decipher(cipher('Hello World'.3), 3)} === 'Hello World'`,);Copy the code

Here is the final code:

const NUMBER_LETTERS = 26;
const NO_ROTATION = 0;
const LETTER = {
  a: 65.z: 90.A: 97.Z: 122};function isOutLowerCharacterCipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.a &&
    text.charCodeAt(position) <= LETTER.z &&
    text.charCodeAt(position) + shift > LETTER.z
  );
}
function isOutUpperCharacterCipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.A &&
    text.charCodeAt(position) <= LETTER.Z &&
    text.charCodeAt(position) + shift > LETTER.Z
  );
}

function isOutLowerCharacterDecipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.a &&
    text.charCodeAt(position) <= LETTER.z &&
    text.charCodeAt(position) - shift < LETTER.a
  );
}

function isOutUpperCharacterDecipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.A &&
    text.charCodeAt(position) <= LETTER.Z &&
    text.charCodeAt(position) - shift < LETTER.A
  );
}

function cipher(text, shift) {
  let cipher = ' ';
  shift = shift % NUMBER_LETTERS;
  for (let position = 0; position < text.length; position++) {
    const isOutAlphabet =
      isOutLowerCharacterCipher(text, position, shift) ||
      isOutUpperCharacterCipher(text, position, shift);
    const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
    const character = text.charCodeAt(position) + shift - rotation;

    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

function decipher(text, shift) {
  let cipher = ' ';

  shift = shift % NUMBER_LETTERS;
  for (let position = 0; position < text.length; position++) {
    const isOutAlphabet =
      isOutLowerCharacterDecipher(text, position, shift) ||
      isOutUpperCharacterDecipher(text, position, shift);
    const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
    const character = text.charCodeAt(position) - shift + rotation;
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

console.assert(
  cipher('Hello World'.1) = = ='Ifmmp! Xpsme'.`${cipher('Hello World'.1)}=== 'Ifmmp! Xpsme'`,);console.assert(
  decipher(cipher('Hello World'.3), 3) = = ='Hello World'.`${decipher(cipher('Hello World'.3), 3)} === 'Hello World'`,);Copy the code

Related articles

  • The Way to clean JavaScript Code – variables
  • The Way to Clean JavaScript Code – Exception Handling
  • The way to clean JavaScript code – Complex judgment
  • The Way to clean JavaScript Code – Comments
  • The Way to clean JavaScript Code – Functions
  • Simple practices for writing clean React code
  • Simple tips for writing a compact React component

Click on KooFE’s front End Team to reply to “Code Cleanliness” for a chance to receive a code Cleanliness book.