限られた予算でのビザンチン決済処理コードのリファクタリング[終了]


8

私は数年間、大規模な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。また、このコードはChargeType、ハードコードされたIDを使用してデータベースからロードされたものに依存します。

では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)。これを最初から行う場合は、1つのPaymentモデルのみを使用します。課金タイプは2つしかないため、これらの値をクラス変数としてハードコーディングします。クレジットカードの詳細は保存されないため、そのモデルは不要です。トランザクション情報はpaymentsテーブルに保存できます。失敗した支払いを保存する必要はありません。

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

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


6
壊れているこのコードの1つの側面のみについて言及しました。支払いが失敗すると500エラーが発生します。しかし、これは全体を再設計しなくても、かなり簡単に修正できるはずです。このコードを書き直す必要がある他の具体的な理由はありますか?動作する醜いコードは、それを変更する強力な根拠がない限り、通常はそのままにしておきます。当然のことながら、再設計には多くの労力がかかり、新しい問題が発生する可能性があります。

500ページを修正する方が簡単に見えるかもしれませんが、難しさは貧弱なデザインに起因します。500ページはAASM::InvalidTransition: Event 'process' cannot transition from 'failed'、失敗したトランザクションである実際のエラーをマスクする例外が原因です。間接性が非常に高いため、ユーザーに応答を返して再送信を許可するのは困難です。それは可能だと確信していますが、リファクタリングと同じくらい難しいようです。
リードG.ロー

2
「そして、どのメソッドが呼び出されたかを見つけるためにすべてのメソッドを探しましたが、何かを見逃す可能性が高いです」-問題の一部のように聞こえるのは、コンパイラーがメソッドが呼び出された場所を正確に通知できない言語を使用しているという事実です。そのような状況では、おそらくリファクタリングの願望に抵抗する必要があります。
Doc Brown

回答:


20

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

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

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

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

はい、コードは不必要に複雑です。しかし、それでも、不必要に思われるいくつかのステップは、理由のためにそこにあるかもしれません。そして、あなたがそれを書き直そうとするなら、あなたはそれらのレッスンのすべてをもう一度学ばなければならないでしょう。

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

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

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

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

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

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

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


2
Joelの記事からの引用は、現在のユースケースを知っており、多くのコードが実際に不要であることから、このケースには当てはまりません。テストのカバレッジを100%に上げるまで、私は何カ月もかけてカバレッジを最大80%まで増やしました。残りのカバーされていないコードは、それほど重要ではないか、テストが難しいか、またはその両方です。テストカバレッジが100%と報告されていても、テストの作成に桁違いに長い時間を費やさない限り、すべての可能なエッジケースをキャッチするために自動テストを信頼することはできません。リファクタリングのもう1つの合理的な理由は、このセクションにアクセスするたびに理解するのに長い時間がかかることです。
Reed G. Law

3
@ ReedG.Law、もう1つの可能性は、ロジックを内部的に単純化することですが、残りのコードに公開されるインターフェースを変更しないことです-リスクの低い中途半端な再設計のようなものです。ただし、これでも新しい問題が発生するリスクがあります。

これは可能ですが、コードが密結合されているため、インターフェースには大きな表面積があります。Charge少なくとも状態マシンを取り除くことができるかもしれません。
Reed G. Law

2
@ ReedG.Lawはさておき、このコードがサイト内のさまざまな場所に埋め込まれていることに驚いています。ほとんどのeコマースサイトには単一の「チェックアウト」パスが定義されており、支払いコードはその1つの場所でのみ呼び出されると思います。そうでない場合は、このコードの前に、呼び出し元のコードが本当に注意が必要なものでしょうか?本当に再設計する必要がある場合、おそらくプロセスは次のとおりです。1)サイト全体がチェックアウト用の単一のパスに送られていることを確認します。2)それが完了したら、安全に支払いコードを再設計できます。

3
@ ReedG.Law-あなたが言うには、私は現在のユースケースを知っていますが、元の投稿には次のように記載されています。これら2つの引用は相互に排他的であるように見えます。
キックスタート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.