瞎扯淡 利用好元编程重构下代码

hfpp2012 · 2021年04月06日 · 最后由 xinyifly 回复于 2021年04月20日 · 1944 次阅读

重构前:

重构后:

算 1 个月的那坨代码抽出来
算季度半年一年的时传月数 3612 进去就好了
可以直接在里面写个 lambda 偷个懒
当然强烈赞同楼下补充的,多写注释/写测试也 ok

重构代码第一步 添加注释

先写单元测试再重构比较好,万一重构后的代码拉下点什么功能就……

建议大家不要模仿。普通业务中是真的用不到元编程。楼主这样改,真就从一坨还可以维护的代码往米田共方向奔跑。

我刚学会元编程的时候也喜欢这样写骚代码,时间久了反而更偏爱朴实稳健的写法

Rei 回复

不能再同意了
lz 应该处于为赋新词强说愁的阶段?毕竟刚学完元编程,想秀一下
我之前刚学会新知识也这样,后面想了下还是少写点这些,是在要写都多写及行注释

重构的姿势不对,先可以想想用常规的重构手法。你这个明显可以先可以将计算的那部分做一个方法,返回一个 hash 结果。

采用元编程的话找该方法的实现只能用 [grep 关键字] 找。。。

业务一旦修改,这一小块还会变成一坨😁

所谓的重构前其实也比较简单明了吧,重构不只是代码写少点呀,我刚刚看完元编程那本也老想这样秀,但是后面发现没必要,秀着秀着发现别人看不懂的时候,是你最痛苦的时候,不能偷懒呀。

考虑提炼值对象,利用多态重构一下?

拿走不谢

spec/vip_money_spec.rb

require 'rspec/mocks'
require 'vip_money'

RSpec.describe 'vip_money' do
  subject { vip_money }

  let(:sale) do
    {
      month: {
        money: 1600, original_money: 2000, unit: 2000, discount: 0.8,
        saving: 0.2, saving_money: 400
      },
      quarter: {
        money: 4000, original_money: 6000, unit: 1666.67, discount: 0.67,
        saving: 0.33, saving_money: 2000
      },
      semi_annual: {
        money: 6400, original_money: 12_000, unit: 1333.34, discount: 0.54,
        saving: 0.46, saving_money: 5600
      },
      year: {
        money: 11_200, original_money: 24_000, unit: 1166.67, discount: 0.47,
        saving: 0.53, saving_money: 12_800
      }
    }
  end

  before do
    stub_const('Order::TYPE_MONEY', [2000, 5000, 8000, 14_000])
    allow(self).to receive(:test_money_discount).and_return(0.8)
  end

  it { is_expected.to eq sale }
end

lib/vip_money.rb

require 'active_support/all'
require 'order'
require 'sale_detail'
require 'test_money_discount'

def vip_money
  month_money, quarter_money, semi_annual_money, year_money = Order::TYPE_MONEY
  user_discount = test_money_discount

  sale_detail = SaleDetail.new(month_money, user_discount)

  %i[month quarter semi_annual year].index_with do |period|
    sale_detail.send("by_#{period}!", binding.local_variable_get("#{period}_money"))
    sale_detail.attributes
  end
end

lib/order.rb

class Order
  TYPE_MONEY = [2000, 5000, 8000, 14_000].freeze
end

lib/sale_detail.rb

require 'bigdecimal'
require 'active_support/all'

class SaleDetail
  attr_reader :monthly, :user_discount, :price, :months

  def initialize(monthly, user_discount)
    @monthly = BigDecimal(monthly.to_s)
    @user_discount = BigDecimal(user_discount.to_s)
  end

  def attributes
    {
      money: total.ceil(2),
      original_money: original.ceil(2),
      unit: unit.ceil(2),
      discount: discount.ceil(2),
      saving: saving.floor(2),
      saving_money: saved.floor(2)
    }
  end

  def by_month!(price, months = 1)
    @months = months
    @price = BigDecimal(price.to_s)
  end

  def by_quarter!(price)
    by_month!(price, 3)
  end

  def by_semi_annual!(price)
    by_month!(price, 6)
  end

  def by_year!(price)
    by_month!(price, 12)
  end

  def total
    price * user_discount
  end

  def original
    monthly * months
  end

  def unit
    price / months
  end

  def discount
    total / original
  end

  def saving
    1 - discount
  end

  def saved
    original - total
  end
end

lib/test_money_discount.rb

def test_money_discount
  0.8
end
xinyifly 回复

厉害的,学习了

我一般在 define_method 前面会用注释将生成的方法名 写出来 避免 搜索的时候搜索不到

业务重构还是面向对象吧,元编程主要用来写工具代码。

jicheng1014 回复

Pry 里用$可以直接看

mizuhashi 回复

是的 我的意思是 当在代码中看到有方法的时候,有时候 go to definition 过不去,那就搜索下

还是要注释下,非元编程写法,不然很难维护,个人项目就无所谓了。

元编程一时爽,后人维护火葬场。

元编程主要用来工具 (lib) 中,CRUD 时最好不要用元编程。

  • 过一个月,你不知道你自己写的是什么意思。
  • 团队里的新程序员读不懂你的代码。
jicheng1014 回复

哈哈哈。懂的自然懂。写代码越写到后面,就会越来越克制。

不谈复杂度的重构都是耍流氓。

xinyifly 回复

我会把 by_xxx 的参数 price 和 months 放到构造方法的参数列表里,再 new 出 4 个对象分别取 attributes。按照你目前的写法,其它方法会依赖 by_xxx 方法先执行,如果 by 方法不调用,其它方法会出错,这样的对象在使用上不太友好,别人不容易注意到这个约束。

剩余的部分我跟你差不多。另外我会照习惯把 SaleDetail#attributes 之外的方法全部声明为 private,并且不开放 attr_reader。

嗯… 然后我可能不会写那个 %w[month quarter semi_annual year].index_with

什么鬼。。。

qiumaoyuan 回复

我也想写成 Immutable 的,只是没打算投入更多时间。因为非 Immutable,特地没有写成链式的。private 你也提到了,把我对这段代码的顾虑都说出来了哈哈。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号