web-dev-qa-db-ja.com

限られた予算でビザンチンの支払い処理コードをリファクタリングする

私は数年前から大規模なRuby on Railsアプリケーションの開発に取り組んでいます。貧弱な状態で受け継がれましたが、ほとんどのプロダクションバグは解決されました。時間の経過に伴って変化します。支払い処理コードなど、触れられていないセクションがあります。コードはほとんどの部分で機能しますが、支払いが支払い処理業者によって拒否された場合は常に、ユーザーが役立つエラーではなく500エラーを受け取ります。メッセージ。保守を容易にするためにコードをリファクタリングしたいのですが、それがどのように機能するかを簡単に説明します。

次のスニペットからすべてのエラー処理コードを削除しました。

迷路はコントローラーで始まります:

  def submit_credit_card
    ...
    @credit_card = CreditCard.new(params[:credit_card].merge(:user => @user))
    @credit_card.save
    ...
    @submission.do_initial_charge(@user)
    ...
  end

次に、Submissionモデルで:

  def do_initial_charge(user)
    ...
    self.initial_charge = self.charges.create(:charge_type => ChargeType.find(1), :user => user)
    self.initial_charge.process!
    self.initial_charge.settled?
  end

Chargeモデルでは:

  aasm column: 'state' do
    ...
    event :process do
      transitions :from => [:created, :failed], :to => :settled, :guard => :transaction_successful?
    end
    ...
  end

  def initialize(*params)
    super(*params)
    ...
    self.amount = self.charge_type.amount
  end

  def transaction_successful?
    user.reload
    credit_card = CreditCard.where(user_id: user_id).last
    cct = self.cc_transactions.build(:user => user, :credit_card => credit_card, :cc_last_four => credit_card.num_last_four, :amount => amount, :charge_id => id)
    cct.process!
    if self.last_cc_transaction.success
      self.update_attribute(:processed, Time.now)
      return true
    else
      self.fail!
      return false
    end
  end

保存されたものを渡すのではなく、userをリロードして最後のCreditCardを見つけるなど、上記の多くの疑わしいビットがあります。また、このコードは、データベースからハードコードされたIDでロードされたChargeTypeに依存しています。

CcTransactionでは、次の道を進みます。

  def do_process
    response = credit_card.process_transaction(self)
    self.authorization = response.authorization
    self.avs_result    = response.avs_result[:message]
    self.cvv_result    = response.cvv_result[:message]
    self.message       = response.message
    self.params        = response.params.inspect
    self.fraud_review  = response.fraud_review?
    self.success       = response.success?
    self.test          = response.test
    self.response      = response.inspect
    self.save!
    self.success
  end

これは、レコードをcc_transactionsデータベーステーブル。実際の支払い処理はCreditCardモデルで実行されます。そのクラスの詳細については退屈しません。実際の作業はActiveMerchant::Billing::AuthorizeNetCimGateway

したがって、少なくとも5つのモデル(SubmissionChargeChargeTypeCcTransaction、およびCreditCard)が含まれます。これを最初から行う場合、単一のPaymentモデルのみを使用します。課金タイプは2つしかないため、これらの値をクラス変数としてハードコーディングします。クレジットカードの詳細は保存されないため、そのモデルは不要です。トランザクション情報はpaymentsテーブルに保存できます。失敗した支払いを保存する必要はありません。

運用サーバーでは何も問題が発生しないという要件を除いて、このリファクタリングをかなり簡単に実行できます。冗長な各クラスには、コードベースのどこからでも呼び出すことができる多くのメソッドがあります。一連の統合テストがありますが、カバレッジは100%ではありません。

何も壊れないようにしながら、これをリファクタリングするにはどうすればよいですか? 5つの支払いクラスを実行し、すべてのメソッドをgrepedして、それらが呼び出される場所を見つけた場合、何かを見落とす可能性が高くなります。クライアントは現在のコードの実行方法にすでに慣れているため、新しいバグの導入は受け入れられません。テストカバレッジを100%に増やすこととは別に、何も壊れないという確信を持ってこれをリファクタリングする方法はありますか?

8
Reed G. Law

このコードに本当にリファクタリングが必要かどうかを検討してください。ソフトウェア開発者にとって、コードは醜く美的に不快なものになるかもしれませんが、機能する場合は、再設計しないでください。そして、あなたの質問に基づいて、それはコードがほとんど機能するように聞こえます。

この Joel on Softwareの古典的な記事 は、コードを不必要に書き直すリスクのすべてを強調しています。これはコストがかかりますが、非常に魅力的な間違いです。全体を一読する価値はありますが、一見不要な複雑さについてのこの一節は特に適切と思われます。

その2ページの機能に戻ります。はい、わかりました。ウィンドウを表示するのは単純な機能ですが、小さな髪の毛などが成長しており、その理由は誰にもわかりません。まあ、私はあなたに理由を教えます:これらはバグ修正です。そのうちの1人は、ナンシーがInternet Explorerを搭載していないコンピューターにインストールしようとしたときのバグを修正します。もう1つは、メモリ不足の状態で発生するバグを修正します。もう1つは、ファイルがフロッピーディスクにあり、ユーザーがディスクを途中で引っ張り出したときに発生したバグを修正します。そのLoadLibrary呼び出しは醜いですが、コードは古いバージョンのWindows 95で機能します。

これらのバグは、発見されるまでに実際の使用に数週間かかっていました。プログラマーは、ラボでバグを再現して修正するために数日を費やした可能性があります。それが多くのバグのようであれば、修正は1行のコードである場合もあれば、2、3文字である場合もありますが、これら2つの文字に多くの作業と時間が費やされました。

はい、コードは不必要に複雑です。しかし、それでもなお、不要と思われる手順のいくつかは、理由のために存在する場合があります。そして、あなたがそれを書き直しに行くなら、あなたはそれらのレッスンのすべてをもう一度学ばなければならないでしょう。

たとえば、ユーザーの再読み込みは無意味に見えます。これが、変更を心配する必要がある理由です。どの開発者も(たとえ悪い人でも)、最初の設計でそのようなステップを導入することはなかったでしょう。問題を修正するためにほぼ間違いなくそこに入れられました。多分それは「正しい」設計へのリファクタリングが排除する問題です...しかし多分そうではありません。

また、別の小さな点として、失敗した支払いを保存する必要がないというあなたの主張には確信が持てません。そのための2つの大きな理由が考えられます。詐欺の可能性のある証拠(たとえば、さまざまなカード番号を試す人)を記録すること、およびカスタマーサポート(顧客が支払いを試み続け、それが機能しないと主張している...証拠が欲しい)これをトラブルシューティングするために試みられた支払いのすべて)。このため、システムの要件を十分に検討していない可能性があります。また、システムの要件は、思っているほど単純ではないようです。

デザインを根本的に変更せずに個々の問題を修正します。あなたは1つの問題を述べました:支払いが失敗すると500エラーが発生します。この問題は、おそらく正しい場所に1行または2行追加するだけで簡単に修正できます。 「正しい」デザインを作るためにすべてをバラバラにするための十分な理論的根拠はありません。

他にも問題がある可能性がありますが、コードが99%の時間で機能する場合は、これらが書き換えを必要とする根本的な問題になることはほとんどありません。

現在のデザインがコード全体に埋め込まれている場合は、デザインを変更せずに問題を修正するためにかなりの労力を費やすことも保証される可能性があります。

場合によっては、実際には、より大きな再設計が必要になることがあります。ただし、これには、これまでに提供したより強力な根拠が必要になります。たとえば、支払いモデルをさらに開発し、重要な新機能を導入する予定の場合、コードをクリーンアップすると、いくつかの利点があります。または、設計に修正が必要な基本的なセキュリティ欠陥が含まれている可能性があります。

大量のコードをリファクタリングする理由はいくつかあります。しかし、それを行う場合は、テストカバレッジを100%に増やします。これはおそらく、全体的に時間を節約するものです

20
user82096