Tái cấu trúc mã xử lý thanh toán byzantine trên ngân sách hạn chế [đã đóng]


8

Tôi đã làm việc trên một ứng dụng Ruby on Rails lớn trong vài năm. Nó được thừa hưởng ở một trạng thái nghèo nàn nhưng hầu hết các lỗi sản xuất đã được giải quyết theo thời gian. Có một số phần chưa được chạm vào như mã xử lý thanh toán. Mã này hoạt động với hầu hết các phần, ngoại trừ bất cứ khi nào bộ xử lý thanh toán bị từ chối, người dùng sẽ nhận được một lỗi 500 thay vì một thông báo hữu ích. Tôi muốn cấu trúc lại mã để dễ bảo trì hơn. Tôi sẽ cung cấp sơ lược về cách thức hoạt động của nó.

Tôi đã xóa tất cả mã xử lý lỗi khỏi các đoạn mã sau.

Mê cung bắt đầu trong một bộ điều khiển:

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

Sau đó, trong Submissionmô hình:

  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

Trong Chargemô hình:

  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

Có rất nhiều bit đáng nghi ngờ ở trên như tải lại uservà tìm cái cuối cùng CreditCardthay vì chuyển trong cái vừa lưu. Ngoài ra mã này phụ thuộc vào ChargeTypetải từ cơ sở dữ liệu với ID được mã hóa cứng.

Trong CcTransactionchúng tôi tiếp tục xuống đường mòn:

  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

Tất cả điều này dường như phải làm là lưu một bản ghi trong cc_transactionsbảng cơ sở dữ liệu. Việc xử lý thanh toán thực tế được thực hiện trong CreditCardmô hình. Tôi sẽ không làm bạn nhàm chán với các chi tiết của lớp học đó. Các công việc thực tế được thực hiện bởi ActiveMerchant::Billing::AuthorizeNetCimGateway.

Vì vậy, chúng ta có ít nhất 5 mô hình liên quan ( Submission, Charge, ChargeType, CcTransaction, và CreditCard). Nếu tôi làm điều này từ đầu, tôi sẽ chỉ sử dụng một Paymentmô hình duy nhất . Chỉ có 2 loại phí, vì vậy tôi sẽ mã hóa các giá trị đó dưới dạng các biến lớp. Chúng tôi không lưu trữ chi tiết thẻ tín dụng, vì vậy mô hình đó là không cần thiết. Thông tin giao dịch có thể được lưu trữ trong paymentsbảng. Thanh toán thất bại không cần phải được lưu.

Tôi có thể đi vào và thực hiện việc tái cấu trúc này khá dễ dàng ngoại trừ yêu cầu không có gì sai sót trên máy chủ sản xuất. Mỗi lớp dự phòng có nhiều phương thức có thể được gọi từ bất kỳ đâu trong cơ sở mã. Có một bộ các bài kiểm tra tích hợp nhưng phạm vi bảo hiểm không phải là 100%.

Làm thế nào tôi nên đi về tái cấu trúc điều này trong khi đảm bảo không có gì phá vỡ? Nếu tôi đã trải qua 5 lớp thanh toán và chỉnh sửa grepmọi phương thức để tìm ra nơi chúng được gọi là có xác suất cao tôi sẽ bỏ lỡ điều gì đó. Máy khách đã quen với cách mã hiện tại chạy và giới thiệu bất kỳ lỗi mới nào là không thể chấp nhận được. Ngoài việc tăng phạm vi kiểm tra lên 100%, có cách nào để tái cấu trúc điều này một cách chắc chắn rằng không có gì sẽ phá vỡ?


6
Bạn đã chỉ đề cập đến một khía cạnh của mã này bị hỏng: nó gây ra lỗi 500 khi thanh toán không thành công. Điều này khá dễ để khắc phục mà không cần thiết kế lại toàn bộ. Có những lý do cụ thể khác mà mã này cần phải được viết lại? Mã xấu mà hoạt động thường nên được để lại ở đó, trừ khi có một lý do mạnh mẽ để thay đổi nó. Khi bạn lo lắng một cách đúng đắn, thiết kế lại mất rất nhiều nỗ lực và có thể đưa ra những vấn đề mới.

Nó có vẻ dễ dàng hơn để sửa 500 trang nhưng khó khăn bắt nguồn từ thiết kế kém. 500 trang là do một AASM::InvalidTransition: Event 'process' cannot transition from 'failed'ngoại lệ che giấu lỗi thực sự là một giao dịch không thành công. Có quá nhiều quyết định, thật khó để lấy lại phản hồi cho người dùng và cho phép gửi lại. Tôi chắc chắn là có thể nhưng dường như khó như tái cấu trúc.
Sậy G. Luật

2
"Và đưa ra mọi phương pháp để tìm ra nơi chúng được gọi là có xác suất cao tôi sẽ bỏ lỡ điều gì đó" - nghe có vẻ như một phần của vấn đề là thực tế bạn đang sử dụng một ngôn ngữ mà không trình biên dịch nào có thể cho bạn biết chính xác nơi mà phương thức được gọi. Trong tình huống như vậy, có lẽ bạn phải chống lại mong muốn tái cấu trúc, nó sẽ có ý nghĩa như thế nào.
Doc Brown

Câu trả lời:


20

Xem xét liệu mã này thực sự cần tái cấu trúc . Mã có thể xấu và gây khó chịu cho bạn khi là nhà phát triển phần mềm, nhưng nếu nó hoạt động, có lẽ bạn không nên thiết kế lại nó. Và dựa trên câu hỏi của bạn, có vẻ như mã phần lớn hoạt động.

Bài viết kinh điển này của Joel trên Phần mềm nêu bật tất cả các rủi ro của việc viết lại mã không cần thiết. Đó là một sai lầm tốn kém, nhưng rất hấp dẫn. Toàn bộ điều đáng đọc trước đây, nhưng đoạn văn về sự phức tạp dường như không cần thiết này có vẻ đặc biệt thích hợp:

Quay lại chức năng hai trang đó. Vâng, tôi biết, đó chỉ là một chức năng đơn giản để hiển thị một cửa sổ, nhưng nó đã mọc rất ít lông và những thứ trên đó và không ai biết tại sao. Vâng, tôi sẽ cho bạn biết lý do tại sao: đó là những sửa lỗi. Một trong số họ sửa lỗi mà Nancy gặp phải khi cô cố cài đặt thứ đó trên máy tính không có Internet Explorer. Một số khác sửa lỗi đó xảy ra trong điều kiện bộ nhớ thấp. Một cách khác khắc phục lỗi đó xảy ra khi tệp nằm trên đĩa mềm và người dùng sẽ lấy đĩa ra ở giữa. Cuộc gọi LoadL Library đó là xấu nhưng nó làm cho mã hoạt động trên các phiên bản cũ của Windows 95.

Mỗi lỗi này mất vài tuần sử dụng trong thế giới thực trước khi chúng được tìm thấy. Lập trình viên có thể đã mất vài ngày để tái tạo lỗi trong phòng thí nghiệm và sửa nó. Nếu nó giống như nhiều lỗi, bản sửa lỗi có thể là một dòng mã hoặc thậm chí có thể là một vài ký tự, nhưng rất nhiều công việc và thời gian đã đi vào hai ký tự đó.

Vâng, mã là phức tạp không cần thiết. Tuy nhiên, một số bước có vẻ không cần thiết có thể ở đó vì một lý do. Và nếu bạn đi viết lại nó, bạn sẽ phải học lại tất cả những bài học đó.

Tải lại người dùng, ví dụ, dường như vô nghĩa: đó chính xác là lý do tại sao bạn nên lo lắng về việc thay đổi nó. Không có nhà phát triển (thậm chí là một người xấu) sẽ giới thiệu một bước như thế trong thiết kế ban đầu của họ. Nó gần như chắc chắn được đưa vào đó để khắc phục một số vấn đề. Có thể đó là một vấn đề mà việc tái cấu trúc theo thiết kế "chính xác" sẽ loại bỏ ... nhưng có thể không.

Ngoài ra, một điểm nhỏ khác, tôi không bị thuyết phục bởi sự tranh chấp của bạn rằng không cần thiết phải lưu các khoản thanh toán thất bại. Tôi có thể nghĩ ra hai lý do mạnh mẽ để làm như vậy: ghi lại bằng chứng về sự gian lận có thể xảy ra (ví dụ: ai đó đang thử nhiều số thẻ khác nhau) và hỗ trợ khách hàng (một khách hàng tuyên bố họ tiếp tục trả tiền và nó không hoạt động ... bạn sẽ muốn bằng chứng của bất kỳ khoản thanh toán nào đã cố gắng để khắc phục sự cố này). Điều này khiến tôi nghĩ rằng có lẽ bạn chưa nghĩ hết về các yêu cầu của hệ thống và chúng không đơn giản như bạn tin.

Khắc phục sự cố cá nhân mà không thay đổi cơ bản thiết kế. Bạn đã đề cập đến một vấn đề: có lỗi 500 khi thanh toán không thành công. Vấn đề này phải dễ khắc phục, có thể chỉ bằng cách thêm một hoặc hai dòng vào đúng vị trí. Nó không đủ lý do để xé mọi thứ ra để tạo ra thiết kế "chính xác".

Có thể có các vấn đề khác, nhưng nếu mã hoạt động 99% thời gian ngay bây giờ, không chắc đây sẽ là những vấn đề cơ bản cần phải viết lại.

Nếu thiết kế hiện tại được nhúng trong toàn bộ mã, thì thậm chí bỏ ra một số lượng lớn nỗ lực để khắc phục sự cố mà không thay đổi thiết kế có thể được bảo hành.

Trong một số trường hợp, thực sự có thể cần thiết phải thiết kế lại lớn hơn. Nhưng điều này sẽ đòi hỏi một lý do mạnh mẽ hơn mà bạn đã đưa ra cho đến nay. Ví dụ: nếu bạn có kế hoạch phát triển mô hình thanh toán hơn nữa và giới thiệu các tính năng mới quan trọng, việc làm sạch mã sẽ có một số lợi ích. Hoặc có lẽ thiết kế chứa một lỗ hổng bảo mật cơ bản cần được sửa chữa.

Có nhiều lý do để cấu trúc lại một đoạn mã lớn. Nhưng nếu bạn làm điều đó, hãy tăng phạm vi kiểm tra lên 100%. Đây có lẽ là một cái gì đó sẽ giúp bạn tiết kiệm thời gian tổng thể .


2
Trích dẫn từ bài viết của Joel không áp dụng trong trường hợp này bởi vì tôi biết các trường hợp sử dụng hiện tại và rất nhiều mã thực sự không cần thiết. Khi tăng phạm vi kiểm tra lên 100%, tôi đã dành nhiều tháng để nhận được bảo hiểm lên tới khoảng 80%. Mã không được phát hiện còn lại là ít quan trọng hơn, khó kiểm tra hơn hoặc cả hai. Ngay cả với phạm vi kiểm tra 100% được báo cáo, tôi sẽ không tin tưởng các bài kiểm tra tự động để nắm bắt mọi trường hợp cạnh có thể trừ khi tôi dành một đơn đặt hàng nhiều thời gian hơn để viết bài kiểm tra. Một lý do khác để tái cấu trúc là mỗi khi phần này được truy cập, phải mất một thời gian dài để hiểu.
Sậy G. Luật

3
@ ReedG.Law, một khả năng nữa là đơn giản hóa logic bên trong, nhưng không thay đổi giao diện tiếp xúc với phần còn lại của mã - loại thiết kế lại nửa chừng có rủi ro thấp hơn. Điều này vẫn sẽ mang một số rủi ro giới thiệu các vấn đề mới mặc dù.

đó là một khả năng, nhưng có một diện tích bề mặt lớn cho giao diện vì mã được ghép chặt chẽ. Tôi có thể có thể thoát khỏi máy trạng thái Chargeít nhất.
Sậy G. Luật

2
@ ReedG.Law là một bên, tôi ngạc nhiên rằng mã này sẽ được nhúng rất nhiều nơi khác nhau trong trang web. Hầu hết các trang web thương mại điện tử có một đường dẫn "thanh toán" được xác định duy nhất và tôi chỉ mong đợi mã thanh toán được gọi ở một vị trí đó. Nếu đó không phải là trường hợp, có lẽ mã gọi thực sự là điều cần chú ý, trước mã này? Nếu bạn thực sự cần thiết kế lại, có thể quy trình là: 1) đảm bảo toàn bộ trang web hướng đến một đường dẫn duy nhất để kiểm tra. 2) sau khi bạn đã thực hiện điều đó, bạn có thể thiết kế lại mã thanh toán một cách an toàn.

3
@ ReedG.Law - bạn nói vì tôi biết các trường hợp sử dụng hiện tại , tuy nhiên bài đăng gốc của bạn nêu rõ Mỗi lớp dự phòng có nhiều phương thức có thể được gọi từ bất kỳ đâu trong cơ sở mã . Hai trích dẫn này dường như là loại trừ lẫn nhau.
Khởi động
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.