【问题标题】:How can I make this massive Ruby if/elsif statement more compact and cleaner?如何使这个庞大的 Ruby if/elsif 语句更紧凑、更简洁?
【发布时间】:2009-07-30 04:21:57
【问题描述】:

下面的 if/elsif 语句显然是一个庞然大物。它的目的是根据用户是否填写了某些数据来更改某些文本的措辞。我觉得必须有一种更好的方法来做到这一点,而无需占用 30 多行代码,但我只是不确定如何,因为我正在尝试根据可用数据显着地自定义文本。

if !birthdate.blank? && !location.blank? && !joined.blank? && !death.blank?
  "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location}. #{sex} passed away on #{death.strftime("%B %e, %Y")} at the age of #{calculate_age(birthdate, death)}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{distance_of_time_in_words(joined,death)}.</p>"
elsif !birthdate.blank? && !location.blank? && !joined.blank? && death.blank?
  "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location} and is #{time_ago_in_words(birthdate)} old. #{sex} has been a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{time_ago_in_words(joined)}.</p>"
elsif birthdate.blank? && !location.blank? && !joined.blank? && !death.blank?
  "<p class='birthinfo'>#{name} was born in #{location}. #{sex} passed away on #{death.strftime("%B %e, %Y")}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{distance_of_time_in_words(joined,death)}.</p>"
elsif birthdate.blank? && !location.blank? && !joined.blank? && death.blank?
  "<p class='birthinfo'>#{name} was born in #{location}. #{sex} has been a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{time_ago_in_words(joined)}.</p>"
elsif birthdate.blank? && location.blank? && !joined.blank? && !death.blank?
  "<p class='birthinfo'>#{name} was a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{distance_of_time_in_words(joined,death)}. #{sex} passed away on #{death.strftime("%B %e, %Y")}.</p>"
elsif birthdate.blank? && location.blank? && !joined.blank? && death.blank?
  "<p class='birthinfo'>#{name} has been a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{time_ago_in_words(joined)}.</p>"
elsif !birthdate.blank? && location.blank? && !joined.blank? && !death.blank?
  "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")}. #{sex} passed away on #{death.strftime("%B %e, %Y")} at the age of #{calculate_age(birthdate, death)}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{distance_of_time_in_words(joined,death)}.</p>"
elsif !birthdate.blank? && location.blank? && !joined.blank? && death.blank?
  "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} and is #{time_ago_in_words(birthdate)} old. #{sex} has been a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{time_ago_in_words(joined)}.</p>"
elsif !birthdate.blank? && !location.blank? && joined.blank? && !death.blank?
  "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location}. #{sex} passed away on #{death.strftime("%B %e, %Y")} at the age of #{calculate_age(birthdate, death)}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>"
elsif !birthdate.blank? && !location.blank? && joined.blank? && death.blank?
  "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location} and is #{time_ago_in_words(birthdate)} old. #{sex} is a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>"
elsif !birthdate.blank? && location.blank? && joined.blank? && !death.blank?
  "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")}. #{sex} passed away on #{death.strftime("%B %e, %Y")} at the age of #{calculate_age(birthdate, death)}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>"
elsif !birthdate.blank? && location.blank? && joined.blank? && death.blank?
  "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} and is #{time_ago_in_words(birthdate)} old. #{sex} is a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>"
elsif birthdate.blank? && !location.blank? && joined.blank? && !death.blank?
  "<p class='birthinfo'>#{name} was born in #{location}. #{sex} passed away on #{death.strftime("%B %e, %Y")}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>"
elsif birthdate.blank? && !location.blank? && joined.blank? && death.blank?
  "<p class='birthinfo'>#{name} was born in #{location}. #{sex} is a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>"
else
  "<p class='birthinfo'>#{name} is a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>"
end

【问题讨论】:

    标签: ruby refactoring


    【解决方案1】:

    我认为您希望使所有这些条件更具可读性,并消除逻辑检查和字符串创建中存在的重复。

    我注意到的第一件事是你到处重复这个:

    <p class='birthinfo'>#{name} was born in
    

    我会尝试将这些因素分解为接受参数并返回格式化文本的函数,或计算为表达式的类。

    您也根本没有利用嵌套。与其让每个条件都检查四个表达式的值,不如试试这样:

    if birthdate.blank?
        # half of your expressions
    else
        # other half of your expressions
    

    它可能不会使您的代码更具可读性,但值得一试。我建议的最后一件事是,您可能能够巧妙地重新排列您的文本,以便它既易于分段构建,又能很好地被最终用户阅读。这是一个例子:

    notice = "#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location}."
    

    生成此代码的一个代码 sn-p 可能是:

    notice = "#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")}"
    if !location.is_blank? 
        notice += " in #{location}"
    end
    notice += "."
    

    您拥有的类似代码更容易阅读,它会分别检查每个条件并根据布尔变量的值生成完全不同的字符串。关于你现在拥有的逻辑最糟糕的部分是,如果你添加第五个变量,你必须在你的代码中添加更多的特殊情况。将您的代码构建成独立的部分会更容易阅读和维护。

    【讨论】:

      【解决方案2】:

      我会把它分成几部分。 DRY。只生成一次给定的文本段。使用StringIO 保持字符串生成可分离。

      sio = StringIO.new("")
      know_birthdate, know_location, did_join, has_died = [ birthdate, location, joined, death ].map { |s| !s.blank? }
      
      print_death = lambda do 
        sio.print ". #{sex} passed away on #{death.strftime("%B %e, %Y")}"
      end
      show_birth = know_birthdate or know_location
      
      sio.print "<p class='birthinfo'>#{name} "
      if show_birth
        sio.print "was born" 
        sio.print " on #{birthdate.strftime("%A, %B %e, %Y")}" if know_birthdate
        sio.print " in #{location}" if know_location
        if has_died
          print_death[]
          sio.print " at the age of #{calculate_age(birthdate, death)}" if know_birthdate
        elsif know_birthdate 
          sio.print " and is #{time_ago_in_words(birthdate)} old"
        end
        sio.print ". #{sex} "
      end
      sio.print "#{(has_died ? "was" : did_join ? "has been" : "is")} a member of #{link_to user.login, profile_path(user.permalink)}'s family"
      sio.print " for #{distance_of_time_in_words(joined,death)}" if did_join and has_died
      print_death[] if has_died and not show_birth
      sio.print ".</p>"
      
      sio.to_s
      

      这使得逻辑更容易遵循,并且更容易进行更改。

      【讨论】:

        【解决方案3】:

        您可以将字符串放入数组中,然后根据每个变量是否为空来创建数组索引:

        strings = [
          "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location}. #{sex} passed away on #{death.strftime("%B %e, %Y")} at the age of #{calculate_age(birthdate, death)}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{distance_of_time_in_words(joined,death)}.</p>",
          "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location} and is #{time_ago_in_words(birthdate)} old. #{sex} has been a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{time_ago_in_words(joined)}.</p>",
          "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location}. #{sex} passed away on #{death.strftime("%B %e, %Y")} at the age of #{calculate_age(birthdate, death)}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>",
          "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} in #{location} and is #{time_ago_in_words(birthdate)} old. #{sex} is a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>",
          "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")}. #{sex} passed away on #{death.strftime("%B %e, %Y")} at the age of #{calculate_age(birthdate, death)}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{distance_of_time_in_words(joined,death)}.</p>",
          "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} and is #{time_ago_in_words(birthdate)} old. #{sex} has been a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{time_ago_in_words(joined)}.</p>",
          "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")}. #{sex} passed away on #{death.strftime("%B %e, %Y")} at the age of #{calculate_age(birthdate, death)}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>",
          "<p class='birthinfo'>#{name} was born on #{birthdate.strftime("%A, %B %e, %Y")} and is #{time_ago_in_words(birthdate)} old. #{sex} is a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>",
          "<p class='birthinfo'>#{name} was born in #{location}. #{sex} passed away on #{death.strftime("%B %e, %Y")}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{distance_of_time_in_words(joined,death)}.</p>",
          "<p class='birthinfo'>#{name} was born in #{location}. #{sex} has been a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{time_ago_in_words(joined)}.</p>",
          "<p class='birthinfo'>#{name} was born in #{location}. #{sex} passed away on #{death.strftime("%B %e, %Y")}. #{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>",
          "<p class='birthinfo'>#{name} was born in #{location}. #{sex} is a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>",
          "<p class='birthinfo'>#{name} was a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{distance_of_time_in_words(joined,death)}. #{sex} passed away on #{death.strftime("%B %e, %Y")}.</p>",
          "<p class='birthinfo'>#{name} has been a member of #{link_to user.login, profile_path(user.permalink)}'s family for #{time_ago_in_words(joined)}.</p>",
          "", # This case was missing from your code. (Where all are blank except 'death'.)
          "<p class='birthinfo'>#{name} is a member of #{link_to user.login, profile_path(user.permalink)}'s family.</p>"
        ]
        
        index = 0
        index += 1 unless death.blank?
        index += 2 unless joined.blank?
        index += 4 unless location.blank?
        index += 8 unless birthdate.blank?
        
        return strings[index]
        

        【讨论】:

          【解决方案4】:

          这是一个片段,展示了我将如何做到这一点:

          ret = []
          ret << "<p class='birthinfo'>#{name}"
          ret << "was born on #{birthdate.strftime("%A, %B %e, %Y")}" unless birthdate.blank?
          ret << "in #{location}." unless location.blank?
          ret << sex
          ret << "passed away on #{death.strftime("%B %e, %Y")}" unless death.blank?
          ret << "at the age of #{calculate_age(birthdate, death)}"
          ret << "#{sex} was a member of #{link_to user.login, profile_path(user.permalink)}'s family"
          ret << 'for #{distance_of_time_in_words(joined,death)}" unless joined.blank? || death.blank?
          ret << '.</p>'
          
          ret.join(' ')
          

          【讨论】:

            【解决方案5】:

            如果有一个简单的描述性术语,我会很想编写描述每种状态的便捷方法。它不会导致更少的代码,但会导致更易读的代码。该代码几乎无法阅读。

            不过,看着它,我不确定您能想出什么样的描述性便捷方法名称。鉴于您正在尝试做的事情,您可能只需要处理丑陋。

            【讨论】:

              【解决方案6】:

              我能想到的一件事是,不要执行每个查询,而是执行所有查询并分配布尔变量的结果,然后测试布尔值。这不会减少 LOC,但确实会减少行长。忘记我之前所说的关于 switch 语句的内容,那至少没有用。您可能会尝试将每个条件分解为几种方法。在给定的 if 语句中检查的第一件事仍然存在,但这会为下一个条件调用两个方法之一,依此类推。这实际上会扩展 LOC(我觉得这并不重要),但可读性会增加。这不是最佳解决方案,但它会起作用。

              【讨论】:

                【解决方案7】:

                您应该能够使用deMorgan's theorem 重写查询,以便更有效地评估它。
                您还可以将公共子表达式分解到外部 if 语句中。
                但是,这样做可能会牺牲意图的清晰性。
                如今,写一个清晰的、如果很长的 if 语句而不是一个简短的难以理解的语句可能是更好的形式。

                【讨论】:

                  猜你喜欢
                  • 2016-03-23
                  • 1970-01-01
                  • 2019-05-04
                  • 2021-12-11
                  • 2015-01-26
                  • 2020-12-23
                  • 2016-02-19
                  • 2022-11-23
                  • 2022-01-21
                  相关资源
                  最近更新 更多