Ich arbeite seit mehreren Jahren an einer großen Ruby on Rails-Anwendung. Es wurde in einem schlechten Zustand vererbt, aber die meisten Produktionsfehler wurden mit der Zeit behoben. Es gibt einige Abschnitte, die nicht berührt wurden, z. B. den Zahlungsverarbeitungscode. Der Code funktioniert größtenteils, außer dass der Benutzer immer dann, wenn eine Gebühr vom Zahlungsprozessor abgelehnt wird, einen 500-Fehler anstelle einer hilfreichen Nachricht erhält. Ich möchte den Code überarbeiten, um die Wartung zu vereinfachen. Ich werde einen kurzen Überblick darüber geben, wie es funktioniert.
Ich habe den gesamten Fehlerbehandlungscode aus den folgenden Ausschnitten entfernt.
Das Labyrinth beginnt in einem Controller:
def submit_credit_card
...
@credit_card = CreditCard.new(params[:credit_card].merge(:user => @user))
@credit_card.save
...
@submission.do_initial_charge(@user)
...
end
Dann im Submission
Modell:
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
Im Charge
Modell:
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
Es gibt viele fragwürdige Punkte oben, wie das Nachladen des user
und das Finden des letzten, CreditCard
anstatt das gerade gespeicherte weiterzugeben. Auch dieser Code hängt von einer ChargeType
aus der Datenbank geladenen mit einer fest codierten ID ab.
In gehen CcTransaction
wir weiter den Weg hinunter:
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
Dies scheint lediglich einen Datensatz in der cc_transactions
Datenbanktabelle zu speichern. Die eigentliche Zahlungsabwicklung erfolgt im CreditCard
Modell. Ich werde Sie nicht mit den Details dieser Klasse langweilen. Die eigentliche Arbeit wird von ausgeführt ActiveMerchant::Billing::AuthorizeNetCimGateway
.
Also haben wir mindestens fünf Modelle haben beteiligt sind ( Submission
, Charge
, ChargeType
, CcTransaction
, und CreditCard
). Wenn ich das von Grund auf neu machen würde, würde ich nur ein einziges Payment
Modell verwenden. Es gibt nur zwei Gebührentypen, daher würde ich diese Werte als Klassenvariablen fest codieren. Wir speichern keine Kreditkartendaten, sodass dieses Modell nicht erforderlich ist. Transaktionsinformationen können in der payments
Tabelle gespeichert werden. Fehlgeschlagene Zahlungen müssen nicht gespeichert werden.
Ich könnte dieses Refactoring ziemlich einfach durchführen, außer der Anforderung, dass auf dem Produktionsserver niemals etwas schief gehen sollte. Jede der redundanten Klassen verfügt über viele Methoden, die von überall in der Codebasis aufgerufen werden können. Es gibt eine Reihe von Integrationstests, aber die Abdeckung beträgt nicht 100%.
Wie soll ich vorgehen, um sicherzustellen, dass nichts kaputt geht? Wenn ich die 5 Zahlungsklassen grep
durchgesehen und jede Methode bearbeitet habe, um herauszufinden, wo sie heißen, besteht eine hohe Wahrscheinlichkeit, dass ich etwas verpasse. Der Client ist bereits daran gewöhnt, wie der aktuelle Code ausgeführt wird, und das Einführen neuer Fehler ist nicht akzeptabel. Gibt es eine Möglichkeit, die Testabdeckung auf 100% zu erhöhen, um sicher zu sein, dass nichts kaputt geht?
AASM::InvalidTransition: Event 'process' cannot transition from 'failed'
Ausnahme zurückzuführen, die den tatsächlichen Fehler maskiert, bei dem es sich um eine nicht erfolgreiche Transaktion handelt. Es gibt so viele Indirektionen, dass es schwierig ist, die Antwort an den Benutzer zurückzusenden und eine erneute Übermittlung zuzulassen. Ich bin mir sicher, dass es möglich ist, aber es scheint fast so schwierig wie Refactoring.