整洁代码应用于 JavaScript - 第七部分:实用重构示例:凯撒密码简介原代码步骤 1. 魔法数字步骤 2. 从 if-else 中提取相似代码步骤 3. 避免 else 步骤 4. 合并 IF 逻辑步骤 5. 简化算法逻辑步骤 6. 封装条件步骤 7. 移除 if-else 结构控制步骤 8. 变量命名结论

2025-06-04

干净代码应用于 JavaScript - 第七部分:实用重构示例:凯撒密码

介绍

原始代码

步骤 1. 神奇数字

步骤 2. 从 if-else 中提取类似代码

步骤 3. 避免使用 else

步骤 4. 合并 IF 逻辑

步骤5.简化算法逻辑

步骤 6. 封装条件

步骤 7. 删除 if-else 结构控制

步骤8.变量命名

结论

介绍

在本系列文章中,我们介绍了一些能够生成更易于维护的代码的编程技巧。大多数编程技巧和建议都来自《代码整洁之道》一书,以及我们多年实践经验的总结。

在本文中,我将逐步描述重构技术的应用,这些技术我已经应用于我的一门编程基础课程的代码中。如果您刚刚开始软件开发,我建议您首先尝试使用您熟悉的技术和工具来解决问题(我们将使用 JavaScript 作为编程语言)。如果您已经具备编程知识,并且解决问题并不需要花费太多精力,那么练习会有所不同。在这种情况下,我们将提供一个解决方案,即起始代码,而挑战在于应用不同的重构技术来深入了解代码,并使代码更易于维护。

为了应对这一挑战,我准备了一个 GIT 存储库,您可以在其中找到我们将在整篇文章中逐步解决的算法的所有版本,使用 JavaScript 和一系列 npm 脚本,您可以使用以下命名法在每个步骤中执行代码:

npm run stepX # Where X is the step

您可以在以下 GIT 存储库中找到代码:REPOSITORY

问题:凯撒密码

问题描述摘自维基百科。因此,您可以从原始来源了解更多信息。

凯撒密码是最简单、最广为人知的加密技术之一。它是一种替换密码,明文中的每个字母都会被字母表中固定位置的字母替换。例如,右移 3 位,E 会被替换为 H,F 会被替换为 I,依此类推。

这种变换可以通过对齐两个字母表来表示;密码字母表是将明文字母表右移一定位置得到的。例如,这是一个凯撒密码,它使用了六位右移,相当于右移 6 位:

Plain:    ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher:   GHIJKLMNOPQRSTUVWXYZABCDEF 

加密时,一个人在“明文”行中查找消息的每个字母,并在“密码”行中写下相应的字母。

明文:THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
密文:QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD

解密以相反的方式进行,左移 6 位。

什么是重构以及为什么要重构?

重构是软件开发行业中一个众所周知的话题。本文我们将对这个主题进行一些介绍,但我建议您阅读以下文章:https://www.cuelogic.com/blog/what-is-refactoring-and-why-is-it-important。我们从这篇文章中提取了一些主要思想,并在此分享。

重构不是灵丹妙药,但它是一种有价值的武器,可以帮助您很好地控制您的代码和项目(软件/应用程序)。

这是一个科学的过程,利用现有代码进行改进,使代码更易读、更易理解、更简洁。此外,添加新功能、构建大型应用程序以及发现和修复错误也变得非常方便。

重构很重要的原因

  • 改进软件/应用程序的设计。
  • 使软件更容易理解
  • 查找错误
  • 修复现有遗留数据库
  • 为用户提供更高的一致性

原始代码

一旦我们知道了我们想要解决的问题,我们就会实施一个实施方案,任何刚开始开发的人都可以在很短的时间内完成。

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();
}

我们要开发的代码有两个方法:

  • cipher- 这将使文本和移位在一个方向上应用。
  • decipher- 执行的相反操作cipher。即解密文本。

我建议,每当你对代码进行重构时,都应该进行一系列自动化测试,以帮助你验证代码是否“损坏”。在这个特定的案例中,我没有创建测试套件,而是使用标准创建了两个检查console.assert

因此,将通过以下断言来检查算法是否稳定。

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'`,
);

好吧,我们已经有了要进行的挑战,让我们开始玩吧!

步骤 1. 神奇数字

第一步是删除代码中出现的魔法数字,并使用赋予代码语义值的变量名。这样,以下数字将被修改:

  1. 我们的字母表中的字母数量(26)。
  2. 每个字母属于算法应该循环的极限,即:
    • 答:65。
    • z:90。
    • 答:97。
    • Z:122。

因此,我们定义以下常量,以便我们了解每个数字所代表的语义背景。

const NUMBER_LETTERS = 26;
const LETTER = {
  a: 65,
  z: 90,
  A: 97,
  Z: 122,
};

这样,这样改完之后代码就会如下了。

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'`,
);

步骤 2. 从 if-else 中提取类似代码

下一步是识别代码中重复的代码行,以便将这些代码行提取到函数中。具体来说,if 控制结构主体中的赋值在整个代码中重复出现,因此可以提取这些赋值。

也就是说,cipher = cipher.concat (String.fromCharCode (可以从if代码中存在的不同 中提取出下面的代码片段。这一行在if结构之后执行,而if仅包含根据每种情况而不同的逻辑。

当然,我们对cipher函数执行的操作与对函数执行的操作相同decipher

应用此重构后的代码如下:

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();
}

步骤 3. 避免使用 else

下一步是避免与else控制结构块相关的代码。避免它很容易,因为我们只需将代码从 移动到循环开始之前的else变量中,以便将该值赋值为默认值。character

因此,本次重构后的代码如下:

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();
}

步骤 4. 合并 IF 逻辑

下一步对我们来说很曲折,但我们必须合并与 对应的逻辑if-elseif。这样,我们就只剩下两个控制结构了if。此操作将使我们能够在后续步骤中观察到,我们实际上有两条替代路径,而不是那些我们表面上看到的路径。

合并if逻辑后的代码如下:

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();
}

步骤5.简化算法逻辑

在这一步,我们必须推断我们的算法不需要两个if控制结构。相反,cipherdecipher函数都具有if-else控制结构。专注于函数,cipher可以观察到将值赋给变量有两种可能的方式character。第一种可能性是从相应的第一个函数中获得的if

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

在默认情况下以及从其他控制结构中获得的第二个可能值if如下:

character = text.charCodeAt(i) + shift;

因此,可以删除第二个逻辑if,并将控制结构转换为else与第一个控制结构对应的逻辑if,因为在不满足此条件的情况下if,将为变量分配第二个可能的值character。无论第二个条件if是否满足,都将被赋给默认值。

本次重构后的代码如下:

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();
}

步骤 6. 封装条件

我们算法的条件相当复杂,难以理解,因为它缺乏语义价值。因此,代码中的下一步被称为“封装条件”。

具体来说,我们专注于封装cipherdecipher条件:

密码:

(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)

解码:

(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)

其实这个逻辑可以概括为以下四个功能:

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 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();
}

步骤 7. 删除 if-else 结构控制

控制结构if-else对同一个变量( )进行了赋值character。因此,您可以从 中提取条件逻辑if并将其存储在变量中,如下所示:

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

变量的赋值character只能通过旋转值来修改,该旋转值可以有两个可能的值:

  1. NUMBER_LETTERS
  2. 0(NO_ROTATION);

因此,我们可以定义变量rotation,以便我们提高代码的粒度级别,如下所示:

const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;

最终代码如下:

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));

这一步之后得到的两个函数的代码如下:

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();
}

步骤8.变量命名

完成重构算法的最后一步是将i循环中的变量重命名为更合适的名称,例如position(这种变化可能看起来很“小”,但为变量分配语义值非常重要,包括经典的ijk循环中的。

应用这些简单步骤后,我们的算法的最终结果如下:

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();
}

结论

在这篇文章中,我们提出了一些建议,以便从基本解决方案重构为可理解的代码。

在这篇文章中,我逐步向大家展示了我的推理过程。当然,还有其他方法,而且有些决定可能并非最符合你的观点。因此,我诚邀大家以建设性的角度,与整个社区分享你的想法。

本次挑战旨在让所有觉得重构工作很难的业内同仁们,看到其他同事是如何一步步完成重构任务的。

在下一篇与此挑战相关的文章中,我将继续改进代码,尝试从函数式编程的角度给出解决方案的愿景。

最后,我们讨论的要点如下:

  • 神奇的数字
  • 从 if-else 中提取类似代码
  • 避免其他
  • 合并 IF 逻辑
  • 简化算法逻辑
  • 封装条件
  • 删除 if-else 结构控制
  • 变量命名

啊,当然,我把原始代码和最终代码都留给了你,这样你就可以做出最后的平衡。

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'`,
);

最终的代码如下:

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'`,
);

文章来源:https://dev.to/carlillo/clean-code-applied-to-javascript-part-vii-practical-refactoring-example-ceaser-cipher-2397
PREV
设计模式 - 模板方法
NEXT
4 个 JavaScript 挑战,13 名高中生,2 小时