源码天空html5(源码天空众人帮源码)

干净的代码冒险

源码天空html5(源码天空众人帮源码)

这篇文章的想法是从一个没有干净代码原则的函数开始,用干净清晰的方式重写它。 整个过程将由测试驱动。

目的是验证可用于重构旧代码库并提高其质量的作案手法。

我们将创建一组函数,将原始函数算法分解为不同抽象级别,从旧代码中汲取灵感并尽可能重用它。

此外,我们将跟踪每个新引入的特性覆盖了哪些旧代码行,以确保我们实现了所有旧特性。

我们从我在一篇旧文章中描述的一个功能开始:自然排序的高性能比较算法。

这是旧代码:

const natOrdCmp2 = (a,b) => {
  let i
  for (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 = 0
const SMALLER = -1
const 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 = 0
  while ( 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 = 0
  const maxComparisonChars = a.length + OneMoreToCheckIfAisPrefixOfB
  while ( 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 EQUAL
  if (is_the_string_prefix_of_the_other(aCode)) return SMALLER
  if (is_the_string_prefix_of_the_other(bCode)) return BIGGER
  return 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 = 0
  const maxComparisonChars = a.length + OneMoreToCheckIfAisPrefixOfB
  while ( charIndex < maxComparisonChars ) {
    const [comparisonResult, comparedChars] =
      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 = 1
  return [compare_char_codes(aCode, bCode), comparedChars]
}

请注意,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 = 1
  return [compare_char_codes(aCode, bCode), comparedChars]
}const compare_digits = (a, b, charIndex) => {
  return [a - b, 1]
}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 = 1
  return [compare_char_codes(aCode, bCode), comparedChars]
}

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 [BIGGER]
  if (aDigits < bDigits) return [SMALLER]
  // cannot be here for the moment
}const number_of_consecutive_digits = (str, startIndex) => {
  let lastIndex
  for (lastIndex=startIndex+1; lastIndex<str.length; lastIndex++)
    if (!is_digit(str.charCodeAt(lastIndex)))
      return lastIndex - startIndex
  return 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 [BIGGER]
  if (aDigits < bDigits) return [SMALLER]
  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 [SMALLER]
     if (aCode > bCode) return [BIGGER]
  }
  return [EQUAL, numberOfDigits]
}

函数 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 = 0
  const maxComparisonChars = a.length + OneMoreToCheckIfAisPrefixOfB
  while ( charIndex < maxComparisonChars ) {
    const [comparisonResult, comparedChars] =
      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 = 0
const SMALLER = -1
const BIGGER = 1
const OneMoreToCheckIfAisPrefixOfB = 1

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 = 0
  const maxComparisonChars = a.length + OneMoreToCheckIfAisPrefixOfB
  while ( charIndex < maxComparisonChars ) {
    const [comparisonResult, comparedChars] = 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<=57

const compare_one_char_code_pair = (aCode, bCode) => {
  const comparedChars = 1
  return [compare_char_codes(aCode, bCode), comparedChars]
}

const compare_char_codes = (aCode, bCode) => {
  if (are_strings_equal(aCode, bCode)) return EQUAL
  if (is_the_string_prefix_of_the_other(aCode)) return SMALLER
  if (is_the_string_prefix_of_the_other(bCode)) return BIGGER
  return 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 [BIGGER]
  if (aDigits < bDigits) return [SMALLER]
  return compare_equal_length_numbers(a, b, charIndex, aDigits)
}

const number_of_consecutive_digits = (str, startIndex) => {
  let lastIndex
  for (lastIndex=startIndex+1; lastIndex<str.length; lastIndex++)
    if (!is_digit(str.charCodeAt(lastIndex)))
      return lastIndex - startIndex
  return 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 [SMALLER]
    if (aCode > bCode) return [BIGGER]
  }
  return [EQUAL, numberOfDigits]
}

export default natural_sort_comparison

好吧,代码行数增加了一倍(还考虑到旧列表中未包含的 skipDigit 函数),但我们添加了很多信息,现在代码可以作为小说阅读。

我很好奇你对这个实验的看法,尤其是黑暗片段。 请随时发表评论。

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

    
本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 cloud@ksuyun.com 举报,一经查实,本站将立刻删除。
如若转载,请注明出处:https://www.daxuejiayuan.com/45420.html