大荆 发表于 2022-12-25 07:46:07

七爪源码:如何使旧代码清晰明了

干净的代码冒险


这篇文章的想法是从一个没有干净代码原则的函数开始,用干净清晰的方式重写它。 整个过程将由测试驱动。
目的是验证可用于重构旧代码库并提高其质量的作案手法。
我们将创建一组函数,将原始函数算法分解为不同抽象级别,从旧代码中汲取灵感并尽可能重用它。
此外,我们将跟踪每个新引入的特性覆盖了哪些旧代码行,以确保我们实现了所有旧特性。
我们从我在一篇旧文章中描述的一个功能开始:自然排序的高性能比较算法。
这是旧代码:
const natOrdCmp2 = (a,b) => {let ifor (i=0; i<a.length; i++) {    const ai = a.charCodeAt(i)    const bi = b.charCodeAt(i)    if (!bi) return 1    if (isDigit(ai) && isDigit(bi)) {      const k = skipDigit(a,i)      const m = skipDigit(b,i)      if (k > m) return 1      if (k < m) return -1      // Same number of digits! Compare them      for (let j=i; j < k; j++) {      const aj = a.charCodeAt(j)      const bj = b.charCodeAt(j)      if (aj < bj) return -1      if (aj > bj) return 1      }              // Same number! Update the number of compared chars      i = k - 1    } else {      // Compare alphabetic chars.      if (ai > bi) return 1      if (ai < bi) return -1    }}return b.charCodeAt(i) ? -1 : 0}是的,它是模糊的。 我们掌握在手中的权力的阴暗面。
是的,该代码不适用于包含前导零的数字部分。 但是很容易修改它来处理它们。 并且在干净的代码重构之后会更容易!
我理想的高级功能如下:
const natural_sort_comparison = (a: string, b: string) =>compare_chars_or_numbers_of(    a,    with_corresponding_char_or_number_of(b))这与维基百科中自然排序的定义非常接近:
在计算中,自然排序顺序(或自然排序)是按字母顺序对字符串进行排序,除了多位数字被原子处理,即,就好像它们是单个字符一样。
它准确地反映了算法的本质,但包含一个技术难点:它需要在另一个有效执行任务的函数中使用一个关闭参数 b 的函数。
可以说闭包的使用使代码晦涩难懂。 所以我会选择一个更简单、更不优雅的版本:
const natural_sort_comparison = (a: string, b: string) =>compare_chars_or_numbers(a, b)但我的最终解决方案将使用我的第一个想法,因为我不认为优雅和复杂会影响清晰度。
下面我将使用这三个常量:
const EQUAL = 0const SMALLER = -1const BIGGER = 1我们最初的一组测试将验证循环对字符串 a 的字符的正确“移植”。 为了这个简单的目的,测试字符串只包含字母字符,并且每个字符串不是另一个字符串的前缀。
test('Basic string comparisons', () => {expect(natural_sort_comparison('abc', 'bc')).toBe(SMALLER)expect(natural_sort_comparison('bc', 'abc')).toBe(BIGGER)expect(natural_sort_comparison( 'abcde', 'abcde')).toBe(EQUAL)))我们覆盖了原始代码的第 2、3、4、5、24、25 行和部分第 28 行。
代码是这样的:
const compare_chars_or_numbers = (a, b) => {let charIndex = 0while ( charIndex < a.lenght ) {    const comparisonResult =      compare_one_char_or_number(a, b, charIndex)    if (comparisonResult !== EQUAL) return comparisonResult    charIndex++}return EQUAL}const compare_one_char_or_number = (a, b, charIndex) => {const aCode = a.charCodeAt(charIndex)const bCode = b.charCodeAt(charIndex)return aCode - bCode}我已经用 while 替换了 for 循环,因为在最终代码中,我想将第 3 行中的 for 增量和第 21 行中的增量合并到一条指令中。
下一步是重新组织前缀的解决方案。
这些是额外的测试:
test('Strings that are prefixed by the other', () => {expect(natural_sort_comparison('abc', 'abcd')).toBe(SMALLER)expect(natural_sort_comparison('abcd', 'abc')).toBe(BIGGER)))第 6 行和第 28 行的检查应该插入到 compare_one_char_or_number 函数中,但我们需要找到一种方法将第 28 行的测试包含在元素的循环中:
const compare_chars_or_numbers = (a, b) => {let charIndex = 0const maxComparisonChars = a.length + OneMoreToCheckIfAisPrefixOfBwhile ( charIndex < maxComparisonChars ) {    const comparisonResult =      compare_one_char_or_number(a, b, charIndex)    if (comparisonResult !== EQUAL) return comparisonResult    charIndex++}return EQUAL}const OneMoreToCheckIfAisPrefixOfB = 1现在该函数必须处理可能超过字符串大小的 charIndex。 这不是一个大问题,因为在这种情况下 chartAt(i) 返回 NaN。 还要注意,现在比较相等的字符串,我们最终会得到一个超过两个字符串大小的 charIndex。
const compare_one_char_or_number = (a, b, charIndex) => {const aCode = a.charCodeAt(charIndex)const bCode = b.charCodeAt(charIndex)return compare_char_codes(aCode, bCode)}const compare_char_codes = (aCode, bCode) => {if (are_strings_equal(aCode, bCode)) return EQUALif (is_the_string_prefix_of_the_other(aCode)) return SMALLERif (is_the_string_prefix_of_the_other(bCode)) return BIGGERreturn aCode - bCode}const is_the_string_prefix_of_the_other =charCode => isNaN(charCode)const are_strings_equal =(aCode, bCode) => isNaN(aCode) && isNaN(bCode)compare_one_char_or_number 的这个改进版本涵盖了第 5 行和第 28 行的其余部分。
下一步是处理带有数字部分的字符串。 第一个考虑是 compare_one_character_or_one_number 现在可以一次比较多个字符(一个数字可以由多个数字组成)。在相等子字符串的情况下,比较字符的数量用于递增 compare_chars_or_numbers 中的循环计数器,因此 函数必须向调用者返回两个值。在这些情况下(广泛用于 React 钩子)的一个众所周知的习惯是返回一个向量并使用 ES6 语法来提取值:
const compare_chars_or_numbers = (a, b) => {let charIndex = 0const maxComparisonChars = a.length + OneMoreToCheckIfAisPrefixOfBwhile ( charIndex < maxComparisonChars ) {    const =      compare_one_char_or_number(a, b, charIndex)    if (comparisonResult !== EQUAL) return comparisonResult    charIndex += comparedChars}return EQUAL}const compare_one_char_or_number = (a, b, charIndex) => {const aCode = a.charCodeAt(charIndex)const bCode = b.charCodeAt(charIndex)const comparedChars = 1return }请注意,charIndex 会递增,直到两个子字符串相等,但当它们变得不同时,不使用 compareChars。
重新运行单元测试可确保更改不会引入损坏。
现在我们准备好为带有数字部分的字符串定义测试。 我们从长度为 1 的数字部分开始。
test('Strings that are prefixed by the other', () => {expect(natural_sort_comparison('ab2c1', 'ab2c2')).toBe(SMALLER)expect(natural_sort_comparison('ab2c2', 'ab2c1')).toBe(BIGGER)))函数 compare_one_char_or_number 应该区分标准字符和数字序列:
const compare_one_char_or_number = (a, b, charIndex) => {const aCode = a.charCodeAt(charIndex)const bCode = b.charCodeAt(charIndex)if (is_digit(aCode) && is_digit(bCode)) {    return compare_digits(a, b, charIndex)}const comparedChars = 1return }const compare_digits = (a, b, charIndex) => {return }const is_digit = charCode => charCode>=48 && charCode<=57请注意,is_digit(NaN) 为 false,因此前缀和相等字符串的处理仅发生在 compare_char_codes 内部。
compare_one_char_or_number 中的两个返回语句有一个问题:我们混合了两个不同级别的抽象:谁负责返回两个函数值? 我们无法从 compare_digits 中提取此责任,因此即使在字母字符的情况下我们也将其委派。
const compare_one_char_or_number = (a, b, charIndex) => {const aCode = a.charCodeAt(charIndex)const bCode = b.charCodeAt(charIndex)if (is_digit(aCode) && is_digit(bCode)) {    return compare_digits(a, b, charIndex)}return compare_one_char_code_pair(aCode, bCode)}const compare_one_char_code_pair = (aCode, bCode) => {const comparedChars = 1return }compare_one_char_or_number 的最终版本也涵盖了旧代码的第 7 行。
下一步是比较不同长度的数量。
test('Strings that are prefixed by the other', () => {expect(natural_sort_comparison('abc2', 'abc11')).toBe(SMALLER)expect(natural_sort_comparison('abc111', 'abc21')).toBe(BIGGER)))位数越多的数字越大。 因此,计算两个字符串中连续数字的数量并比较计数器就足够了。
const compare_digits = (a, b, charIndex) => {const aDigits = number_of_consecutive_digits(a, charIndex)const bDigits = number_of_consecutive_digits(b, charIndex)if (aDigits > bDigits) return if (aDigits < bDigits) return // cannot be here for the moment}const number_of_consecutive_digits = (str, startIndex) => {let lastIndexfor (lastIndex=startIndex+1; lastIndex<str.length; lastIndex++)    if (!is_digit(str.charCodeAt(lastIndex)))      return lastIndex - startIndexreturn lastIndex - startIndex}好的,现在是最后一步:使用具有相同长度的数字部分的字符串进行测试。
test('Strings that are prefixed by the other', () => {expect(natural_sort_comparison('abc12', 'abc12')).toBe(EQUAL)expect(natural_sort_comparison('abc11', 'abc12')).toBe(SMALLER)expect(natural_sort_comparison('abc13', 'abc12')).toBe(BIGGER)))如果两个数字部分的长度相同,只需检查其数字的字符代码顺序。
const compare_digits = (a, b, charIndex) => {const aDigits = number_of_consecutive_digits(a, charIndex)const bDigits = number_of_consecutive_digits(b, charIndex)if (aDigits > bDigits) return if (aDigits < bDigits) return return compare_equal_length_numbers(a, b, charIndex, aDigits)}const compare_equal_length_numbers = (a, b, startIndex, numberOfDigits) => {   for (let charIndex = startIndex;      charIndex < startIndex + numberOfDigits;      charIndex++) {   const aCode = a.charCodeAt(charIndex)   const bCode = b.charCodeAt(charIndex)   if (aCode < bCode) return    if (aCode > bCode) return }return }函数 compare_equal_length_numbers 覆盖旧代码的第 13-18 行,而 compare_digits 覆盖第 8-11 行
就这些。 但在呈现整个重构代码之前,请记住我在开始时所说的……我更喜欢优雅和复杂的代码。 毕竟,闭包是语言的一部分……
const natural_sort_comparison = (a: string, b: string) =>compare_chars_or_numbers_of(    a,    with_corresponding_char_or_number_of(b))const compare_chars_or_numbers_of = (a, compare_with_b) => {let charIndex = 0const maxComparisonChars = a.length + OneMoreToCheckIfAisPrefixOfBwhile ( charIndex < maxComparisonChars ) {    const =      compare_with_b(a, charIndex)    if (comparisonResult !== EQUAL) return comparisonResult    charIndex += comparedChars}return EQUAL}const with_corresponding_char_or_number_of = b => {const compare_with_b = (a, charIndex) =>    compare_one_char_or_number(a, b, charIndex)return compare_with_b}我只更改了主函数的名称和签名,并在其主体中更改了对 compare_with_b 的调用:一个在 b 上关闭的函数,它使用所有需要的参数调用 compare_one_char_or_number。
一个黑暗的片段在干净的代码天空中幸存下来,以允许对顶级函数进行富有表现力的调用。
const EQUAL = 0const SMALLER = -1const BIGGER = 1const OneMoreToCheckIfAisPrefixOfB = 1const natural_sort_comparison = (a: string, b: string) =>compare_chars_or_numbers_of(a, with_corresponding_char_or_number_of(b))const compare_chars_or_numbers_of = (a, compare_with_b) => {let charIndex = 0const maxComparisonChars = a.length + OneMoreToCheckIfAisPrefixOfBwhile ( charIndex < maxComparisonChars ) {    const = compare_with_b(a, charIndex)    if (comparisonResult !== EQUAL) return comparisonResult    charIndex += comparedChars}return EQUAL}const with_corresponding_char_or_number_of = b => {const compare_with_b = (a, charIndex) => compare_one_char_or_number(a, b, charIndex)return compare_with_b}const compare_one_char_or_number = (a, b, charIndex) => {const aCode = a.charCodeAt(charIndex)const bCode = b.charCodeAt(charIndex)if (is_digit(aCode) && is_digit(bCode)) {    return compare_digits(a, b, charIndex)}return compare_one_char_code_pair(aCode, bCode)}const is_digit = charCode => charCode>=48 && charCode<=57const compare_one_char_code_pair = (aCode, bCode) => {const comparedChars = 1return }const compare_char_codes = (aCode, bCode) => {if (are_strings_equal(aCode, bCode)) return EQUALif (is_the_string_prefix_of_the_other(aCode)) return SMALLERif (is_the_string_prefix_of_the_other(bCode)) return BIGGERreturn aCode - bCode}const is_the_string_prefix_of_the_other = charCode => isNaN(charCode)const are_strings_equal = (aCode, bCode) => isNaN(aCode) && isNaN(bCode)const compare_digits = (a, b, charIndex) => {const aDigits = number_of_consecutive_digits(a, charIndex)const bDigits = number_of_consecutive_digits(b, charIndex)if (aDigits > bDigits) return if (aDigits < bDigits) return return compare_equal_length_numbers(a, b, charIndex, aDigits)}const number_of_consecutive_digits = (str, startIndex) => {let lastIndexfor (lastIndex=startIndex+1; lastIndex<str.length; lastIndex++)    if (!is_digit(str.charCodeAt(lastIndex)))      return lastIndex - startIndexreturn lastIndex - startIndex}const compare_equal_length_numbers = (a, b, startIndex, numberOfDigits) => {for (let charIndex = startIndex; charIndex < startIndex + numberOfDigits; charIndex++) {    const aCode = a.charCodeAt(charIndex)    const bCode = b.charCodeAt(charIndex)    if (aCode < bCode) return     if (aCode > bCode) return }return }export default natural_sort_comparison好吧,代码行数增加了一倍(还考虑到旧列表中未包含的 skipDigit 函数),但我们添加了很多信息,现在代码可以作为小说阅读。
我很好奇你对这个实验的看法,尤其是黑暗片段。 请随时发表评论。


关注七爪网,获取更多APP/小程序/网站源码资源!

原文地址:https://m.toutiao.com/i7128031193689391631/
页: [1]
查看完整版本: 七爪源码:如何使旧代码清晰明了