如何更好地解决此代码异味?

时间:2018-12-16 16:43:47

标签: ruby-on-rails ruby

您将如何使用冗长的重复方法来识别和修复以下代码异味?这是下面的代码气味:

代码异味

    class TransactionProcessingService
      def initialize(user, product_id)
        @user    = user
        @product = Actions::Base.find_by id: product_id
      end

      def call
        return false unless valid?

        ActiveRecord::Base.transaction do
          @reservation = @user.reservations.
              where(action_id: @product.id).
              where("status IS NOT NULL AND status NOT IN ('archived', 'cancelled')").
              first_or_initialize
          @reservation.update(quantity: (@reservation.quantity.presence || 0) + 1) # Nil guard

          update_state!
        end
      end

      protected

      def valid?
        @product.allowed_for?(@user) and @user.balance >= @product.price
      end

      def update_state!
        if @product.is_a?(Actions::Target)
          @user.transactions.create(status: 'sold_target', transaction_type: :product_purchase, amount: @product.price, product: @product)
          @user.update_columns(balance: @user.balance - @product.price - @product.discount_for_user(@user))
        elsif @product.is_a?(Actions::Lease)
          @user.transactions.create(status: 'sold_lease', transaction_type: :product_purchase, amount: @product.price, product: @product)
          @user.update_columns balance: @user.balance - @product.price
        end
      end
    end

但是,我尝试重构它,但是我觉得我变得更糟。您认为我应该如何最好地重构上面的代码?这是我下面所做的:

这是我所做的...

class TransactionProcessingService
  def initialize(user, product_id)
    @user    = user
    @product = Actions::Base.find_by id: product_id
  end

  def call
    false unless valid?

    ActiveRecord::Base.transaction do
      update_user_reservations
      create_state
      update_balance
    end
  end

  protected

  def valid?
    @product.allowed_for?(@user) && (@user.balance >= @product.price)
  end

  def wrapper_around(*)
    @product.is_a?
  end

  def update_user_reservations
    result = @user.reservations
                       .where(action_id: @product.id)
                       .where("status IS NOT NULL AND status NOT IN ('archived', 'cancelled')").first_or_initialize
    result.update(quantity: (quantity.presence || 0) + 1)
  end

  def update_balance
    value = @user.balance - @product.price
    target = wrapper_around(Actions::Target)
    lease = wrapper_around(Actions::Lease)
    @user.update_columns(if target
                           { balance: value - @product.discount_for_user(@user) }
                         elsif lease
                           { balance: value }
                         end)
  end

  def create_state
    product_price = @product.price
    target = wrapper_around(Actions::Target)
    lease = wrapper_around(Actions::Lease)
    @user.transactions.create(if target
                                {
                                  status: 'sold_target',
                                  transaction_type: :product_purchase,
                                  amount: product_price,
                                  product: product_params
                                }
                              elsif lease
                                {
                                  status: 'sold_lease',
                                  transaction_type: :product_purchase,
                                  amount: product_price,
                                  product: product_params
                                }
                              end)
  end
end

1 个答案:

答案 0 :(得分:3)

只是为了给您一个主意,您可以将代码中可以识别的不同操作与其他一些类分开:

TransactionProcessing:

class TransactionProcessing
  def initialize(product_id:, user:)
    @user = user
    @product_id = product_id
  end

  def call
    return unless product
    return unless valid?

    ActiveRecord::Base.transaction do
      UpdateReservation.new(product: product, user: user).update

      update_state!
    end
  end

  private

  attr_reader :product_id, :user

  delegate :id, :price, to: :product
  delegate :quantity, to: :reservation
  delegate :balance, :reservations, :transactions, to: :user

  def product
    Actions::Base.find_by(id: product_id)
  end

  def update_state!
    Object.const_get("#{product.class}Transaction").new(product: product, user: user).update
  end

  def valid?
    product.allowed_for?(user) && balance >= price
  end
end

UpdateReservation:

class UpdateReservation
  def initialize(user:, product:)
    @product = product
    @user = user
  end

  def update
    reservation.update(quantity: reservation_quantity)
  end

  private

  attr_reader :user

  delegate :id, to: :product
  delegate :reservations, to: :user
  delegate :quantity, to: :reservation

  def reservation
    reservations.where(action_id: id).where.not(status: nil)
                 where('status NOT IN ("archived", "cancelled")').first_or_initialize
  end

  def reservation_quantity
    (quantity.presence || 0) + 1 # Just if quantity.presence can be nil. Otherwise add a rescue.
  end
end

动作模块:

module Actions
  class CustomTransaction
    attr_reader :product, :user

    delegate :price, to: :product
    delegate :balance, :transactions, to: :user

    def initialize(product:, user:)
      @product = product
      @user = user
    end

    def update
      create_transaction
      update_balance
    end

    def create_transaction
      transactions.create(params)
    end

    def update_balance
      user.update(balance: balance_value)
    end

    private

    def params
      { transaction_type: :product_purchase, amount: price, product: product }
    end
  end

  class LeaseTransaction < CustomTransaction
    STATUS = 'sold_lease'.freeze
    private_constant :STATUS

    private

    def params
      super.merge(status: STATUS)
    end

    def balance_value
      balance - price - discount_for_user
    end

    def discount_for_user
      product.discount_for_user(user)
    end
  end

  class TargetTransaction < CustomTransaction
    STATUS = 'sold_target'.freeze
    private_constant :STATUS

    private

    def params
      super.merge(status: STATUS)
    end

    def balance_value
      balance - price
    end
  end
end

您可以看到:

  • 位于服务名称空间或文件夹下的类已经是服务,无需使其具有冗余性。
  • 在初始化程序中使用product_id,避免添加更多代码。 product属于单独的私有方法。
  • 让您的课程费用更少。
  • 尽可能将方法委派给访问者和其他人。
  • 如果存在共同的行为,则总有可能使用继承。
  • 为您的方法提供尽可能少的费用。
  • 如果可以,请避免使用is_a?。只需打电话。

即使这需要改进,所以任何人都可以自在。