干净代码应用于 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. 神奇数字
第一步是删除代码中出现的魔法数字,并使用赋予代码语义值的变量名。这样,以下数字将被修改:
- 我们的字母表中的字母数量(26)。
- 每个字母属于算法应该循环的极限,即:
- 答: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
控制结构。相反,cipher
和decipher
函数都具有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. 封装条件
我们算法的条件相当复杂,难以理解,因为它缺乏语义价值。因此,代码中的下一步被称为“封装条件”。
具体来说,我们专注于封装cipher
和decipher
条件:
密码:
(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
只能通过旋转值来修改,该旋转值可以有两个可能的值:
NUMBER_LETTERS
- 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
(这种变化可能看起来很“小”,但为变量分配语义值非常重要,包括经典的i
、j
和k
循环中的。
应用这些简单步骤后,我们的算法的最终结果如下:
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'`,
);