针灸界

 找回密码
 立即注册
搜索
图文热点
    查看: 729|回复: 0

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

    [复制链接]
    发表于 2022-12-25 07:46:07 | 显示全部楼层 |阅读模式
    干净的代码冒险


    这篇文章的想法是从一个没有干净代码原则的函数开始,用干净清晰的方式重写它。 整个过程将由测试驱动。
    目的是验证可用于重构旧代码库并提高其质量的作案手法。
    我们将创建一组函数,将原始函数算法分解为不同抽象级别,从旧代码中汲取灵感并尽可能重用它。
    此外,我们将跟踪每个新引入的特性覆盖了哪些旧代码行,以确保我们实现了所有旧特性。
    我们从我在一篇旧文章中描述的一个功能开始:自然排序的高性能比较算法。
    这是旧代码:
    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 = 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 = 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 = 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 = 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<=57const 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/小程序/网站源码资源!

    原文地址:https://m.toutiao.com/i7128031193689391631/

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x
    大荆医疗技术研究院——专注针灸适宜技术委培及医械研发与推广
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    快速回复 返回顶部 返回列表