Computer >> Máy Tính >  >> Lập trình >> Ruby

Lập trình định hướng đường sắt trong đường ray bằng Dry-Monads

Xử lý lỗi là một phần quan trọng của mọi chương trình. Điều quan trọng là phải chủ động về những lỗi nào có thể phát sinh trong quá trình triển khai một đoạn mã. Những lỗi này phải được xử lý theo cách đảm bảo đầu ra được tạo ra mô tả đúng từng lỗi và giai đoạn của ứng dụng mà nó đã xảy ra. Tuy nhiên, điều quan trọng là phải đạt được điều này theo cách đảm bảo mã của bạn vẫn hoạt động và có thể đọc được. Hãy bắt đầu bằng cách trả lời một câu hỏi mà bạn có thể đã có:Lập trình Định hướng Đường sắt là gì?

Lập trình định hướng cho đường sắt

Một chức năng đạt được một mục đích cụ thể có thể là một tổng hợp của các chức năng nhỏ hơn. Các chức năng này thực hiện các bước khác nhau mà cuối cùng dẫn đến việc đạt được mục tiêu cuối cùng. Ví dụ:một chức năng cập nhật địa chỉ của người dùng trong cơ sở dữ liệu và sau đó thông báo cho người dùng về sự thay đổi này có thể bao gồm các bước sau:

validate user -> update address -> send mail upon successful update

Mỗi bước trong số này có thể thất bại hoặc thành công và việc thất bại của bất kỳ bước nào dẫn đến sự thất bại của toàn bộ quy trình, vì mục đích của chức năng không đạt được.

Lập trình hướng đường sắt (ROP) là một thuật ngữ được phát minh bởi Scott Wlaschin áp dụng tương tự công tắc đường sắt để xử lý lỗi trong các chức năng như thế này. Công tắc đường sắt (được gọi là "điểm" ở Anh) hướng dẫn các đoàn tàu từ đường ray này sang đường ray khác. Scott sử dụng phép tương tự này theo nghĩa rằng kết quả thành công / thất bại của mỗi bước hoạt động giống như một công tắc đường sắt, vì nó có thể đưa bạn đến đường dẫn thành công hoặc đường dẫn thất bại.

Lập trình định hướng đường sắt trong đường ray bằng Dry-Monads Kết quả đầu ra thành công / thất bại hoạt động như một công tắc đường sắt

Khi có lỗi trong bất kỳ bước nào, chúng tôi sẽ chuyển sang theo dõi lỗi bằng đầu ra lỗi, do đó chuyển qua các bước còn lại. Tuy nhiên, khi có một đầu ra thành công, nó sẽ được kết nối với đầu vào của bước tiếp theo để giúp đưa chúng ta đến đích cuối cùng, như thể hiện trong hình ảnh bên dưới.

Lập trình định hướng đường sắt trong đường ray bằng Dry-Monads Kết quả đầu ra thành công / thất bại của một số bước được xâu chuỗi với nhau

Sự tương tự hai đường ray này là ý tưởng đằng sau Lập trình Định hướng Đường sắt. Nó cố gắng trả lại các kết quả thành công / thất bại này ở mọi bước của con đường (tức là mọi phương pháp là một phần của quy trình) để đảm bảo rằng thất bại trong một bước là thất bại của toàn bộ quy trình. Chỉ việc hoàn thành thành công từng bước mới dẫn đến thành công chung.

Ví dụ về cuộc sống hàng ngày

Hãy tưởng tượng rằng bạn có mục tiêu mua một hộp sữa trực tiếp từ một cửa hàng có tên The Milk Shakers . Các bước có thể liên quan sẽ như sau:

Leave your house -> Arrive at The Milk Shakers -> Pick up a carton of milk -> Pay for the carton of milk

Nếu bạn không thể ra khỏi nhà của mình, toàn bộ quá trình là một thất bại vì bước đầu tiên là thất bại. Điều gì sẽ xảy ra nếu bạn ra khỏi nhà và đến Walmart? Quá trình này vẫn không thành công vì bạn đã không đến cửa hàng được chỉ định. Việc bạn có thể mua sữa từ Walmart không có nghĩa là quá trình này sẽ tiếp tục. ROP dừng quy trình tại Walmart và trả về kết quả lỗi cho bạn biết rằng quy trình không thành công vì cửa hàng không phải là The Milk Shakers. Tuy nhiên, nếu bạn đã đến đúng cửa hàng, quá trình sẽ tiếp tục, kiểm tra kết quả đầu ra và kết thúc quá trình hoặc tiếp tục bước tiếp theo. Điều này đảm bảo việc xử lý lỗi dễ đọc và dễ hiểu hơn và đạt được điều này một cách hiệu quả mà không cần if/elsereturn câu lệnh liên kết các bước riêng lẻ.

Trong Rails, chúng ta có thể đạt được đầu ra đường sắt hai đoạn này bằng cách sử dụng một viên ngọc có tên là Dry Monads .

Giới thiệu về các loài Dry và cách chúng hoạt động

Đơn nguyên ban đầu là một khái niệm toán học. Về cơ bản, chúng là một thành phần hoặc sự trừu tượng của một số hàm đặc biệt mà khi được sử dụng trong mã, chúng có thể loại bỏ việc xử lý rõ ràng các giá trị trạng thái. Chúng cũng có thể là một phần trừu tượng của mã soạn sẵn tính toán theo logic của chương trình. Giá trị trạng thái không phải là cục bộ cho một hàm cụ thể, một vài ví dụ bao gồm:đầu vào, biến toàn cục và đầu ra. Đơn nguyên chứa một hàm ràng buộc giúp các giá trị này có thể được truyền từ đơn nguyên này sang đơn nguyên khác; do đó, chúng không bao giờ được xử lý một cách rõ ràng. Chúng có thể được xây dựng để xử lý các ngoại lệ, cam kết khôi phục, thử lại logic, v.v. Bạn có thể tìm thêm thông tin về các monads tại đây.

Như đã nêu trong tài liệu của nó, mà tôi khuyên bạn nên xem lại, các monads khô là một tập hợp các monads phổ biến cho Ruby. Monads cung cấp một cách xử lý lỗi, ngoại lệ và các hàm chuỗi thanh lịch để mã dễ hiểu hơn nhiều và có tất cả các xử lý lỗi mong muốn mà không cần tất cả ifs and elses . Chúng tôi sẽ tập trung vào Đơn vị kết quả vì nó chính xác là những gì chúng ta cần để đạt được kết quả thành công / thất bại mà chúng ta đã nói trước đó.

Hãy bắt đầu một ứng dụng Rails mới có tên là Railway-app bằng cách sử dụng lệnh sau:

rails new railway-app -T

-T trong lệnh có nghĩa là chúng tôi sẽ bỏ qua thư mục thử nghiệm vì chúng tôi dự định sử dụng RSpec để thử nghiệm.

Tiếp theo, chúng tôi thêm các đá quý cần thiết vào Gemfile của chúng tôi:gem dry-monads để biết kết quả thành công / thất bại và gem rspec-rails trong nhóm thử nghiệm và phát triển làm khuôn khổ thử nghiệm của chúng tôi. Bây giờ, chúng ta có thể chạy bundle install trong ứng dụng của chúng tôi để cài đặt các viên ngọc được thêm vào. Tuy nhiên, để tạo tệp thử nghiệm và trình trợ giúp của chúng tôi, chúng tôi cần chạy lệnh sau:

rails generate rspec:install

Chia một hàm thành nhiều bước

Bạn nên chia chức năng của mình thành các phương pháp nhỏ hơn để làm việc cùng nhau để đạt được mục tiêu cuối cùng. Các lỗi từ các phương pháp này, nếu có, giúp chúng tôi xác định chính xác vị trí mà quy trình của chúng tôi không thành công và giữ cho mã của chúng tôi sạch sẽ và dễ đọc. nếu mô hình và màu sắc được yêu cầu có sẵn, nếu năm sản xuất không phải trước năm 2000 và nếu thành phố được giao có trong danh sách các thành phố lân cận. Điều này sẽ rất thú vị. :)

Hãy bắt đầu bằng cách chia quá trình phân phối thành nhiều bước:

  • Xác minh rằng năm sản xuất không phải trước năm 2000.
  • Xác minh rằng mô hình có sẵn.
  • Xác minh rằng màu có sẵn.
  • Xác minh rằng thành phố sẽ được chuyển đến là một thành phố lân cận.
  • Gửi tin nhắn cho biết rằng xe sẽ được giao.

Bây giờ chúng ta đã giải quyết xong các bước khác nhau, hãy đi sâu vào mã.

Nhập kết quả đầu ra thành công / thất bại

Trong thư mục ứng dụng / mô hình của chúng tôi, hãy tạo một tệp có tên car_dealership.rb và khởi tạo lớp này với các chi tiết quan trọng. Ở đầu tệp, chúng ta phải yêu cầu dry/monads và ngay sau tên lớp, chúng ta phải bao gồm DryMonads[:result, :do] . Điều này làm cho kết quả đơn nguyên và ký hiệu (có thể kết hợp một số hoạt động đơn nguyên bằng cách sử dụng từ lợi nhuận) có sẵn cho chúng tôi.

require 'dry/monads'

class CarDealership

include Dry::Monads[:result, :do]

  def initialize
    @available_models = %w[Avalon Camry Corolla Venza]
    @available_colors = %w[red black blue white]
    @nearby_cities = %w[Austin Chicago Seattle]
  end
end

Tiếp theo, chúng tôi thêm deliver_car của mình phương thức này sẽ bao gồm tất cả các bước khác có liên quan và trả về thông báo thành công nếu tất cả các bước đều thành công. Chúng tôi thêm từ lợi nhuận để kết hợp hoặc liên kết các bước này với nhau. Điều này có nghĩa là thông báo lỗi trong bất kỳ bước nào trong số này sẽ trở thành thông báo lỗi của deliver_car và kết quả đầu ra thành công trong bất kỳ phương thức nào trong số chúng sẽ dẫn đến lệnh gọi của bước tiếp theo trong danh sách.

def deliver_car(year,model,color,city)
  yield check_year(year)
  yield check_model(model)
  yield check_city(city)
  yield check_color(color)

  Success("A #{color} #{year} Toyota #{model} will be delivered to #{city}")
end

Bây giờ, hãy thêm tất cả các phương pháp khác và đính kèm kết quả thành công / thất bại cho chúng dựa trên kết quả kiểm tra của chúng.

def check_year(year)
  year < 2000 ? Failure("We have no cars manufactured in year #{year}") : Success('Cars of this year are available')
end

def check_model(model)
  @available_models.include?(model) ? Success('Model available') : Failure('The model requested is unavailable')
end
def check_color(color)
  @available_colors.include?(color) ? Success('This color is available') : Failure("Color #{color} is unavailable")
end

def check_city(city)
  @nearby_cities.include?(city) ? Success("Car deliverable to #{city}") : Failure('Apologies, we cannot deliver to this city')
end

Chúng tôi hiện có lớp học của mình và tất cả các phương pháp chúng tôi cần. Làm thế nào điều này sẽ diễn ra? Hãy cùng tìm hiểu bằng cách tạo một phiên bản mới của lớp này và gọi deliver_car với các đối số khác nhau.

good_dealer = CarDealership.new

good_dealer.deliver_car(1990, 'Venza', 'red', 'Austin')
#Failure("We have no cars manufactured in year 1990")

good_dealer.deliver_car(2005, 'Rav4', 'red', 'Austin')
#Failure("The model requested is unavailable")

good_dealer.deliver_car(2005, 'Venza', 'yellow', 'Austin')
#Failure("Color yellow is unavailable")

good_dealer.deliver_car(2000, 'Venza', 'red', 'Surrey')
#Failure("Apologies, we cannot deliver to this city")

good_dealer.deliver_car(2000, 'Avalon', 'blue', 'Austin')
#Success("A blue 2000 Toyota Avalon will be delivered to Austin")

Như được hiển thị ở trên, kết quả thất bại của phương thức delivery_car khác nhau tùy thuộc vào phương thức mà nó không thành công. Sự thất bại của phương pháp đó trở thành sự thất bại của nó, và khi thành công của tất cả các phương pháp, nó sẽ trả về kết quả thành công của chính nó. Ngoài ra, đừng quên rằng các bước này là các phương thức riêng lẻ cũng có thể được gọi độc lập với deliver_car phương pháp. Ví dụ được hiển thị bên dưới:

good_dealer.check_color('wine')
#Failure("Color wine is unavailable")

good_dealer.check_model('Camry')
#Success('Model available')

Kiểm tra bằng RSpec

Để kiểm tra đoạn mã trên, chúng tôi đi tới thư mục đặc tả của chúng tôi và tạo một tệp car_dealership_spec.rb trong đường dẫn spec/models . Trên dòng đầu tiên, chúng tôi yêu cầu 'rails_helper' của chúng tôi. Chúng tôi sẽ viết các bài kiểm tra cho bối cảnh thất bại trước rồi mới đến thành công.

require 'rails_helper'

describe CarDealership do
  describe "#deliver_car" don
    let(:toyota_dealer) { CarDealership.new }
    context "failure" do
      it "does not deliver a car with the year less than 2000" do
        delivery = toyota_dealer.deliver_car(1990, 'Venza', 'red', 'Austin')
        expect(delivery.success).to eq nil
        expect(delivery.failure).to eq 'We have no cars manufactured in  year 1990'
      end

       it "does not deliver a car with the year less than 2000" do
        delivery = toyota_dealer.deliver_car(2005, 'Venza', 'yellow', 'Austin')
        expect(delivery.success).to eq nil
        expect(delivery.failure).to eq 'Color yellow is unavailable'
      end
   end
 end
end

Như được hiển thị ở trên, chúng tôi có thể truy cập kết quả thất bại hoặc thành công bằng cách sử dụng result.failure hoặc result.success . Đối với bối cảnh thành công, các bài kiểm tra sẽ trông giống như sau:

context "success" do
  it "delivers a car when all conditions are met" do
    delivery = toyota_dealer.deliver_car(2000, 'Avalon', 'blue', 'Austin')
    expect(delivery.success).to eq 'A blue 2000 Toyota Avalon will be delivered to Austin'
    expect(delivery.failure).to eq nil
  end
end

Bây giờ, bạn có thể thêm các thử nghiệm khác trong ngữ cảnh lỗi bằng cách điều chỉnh các đối số được cung cấp cho deliver_car phương pháp. Bạn cũng có thể thêm các kiểm tra khác trong mã của mình cho các trường hợp cung cấp đối số không hợp lệ (ví dụ:một chuỗi được cung cấp dưới dạng giá trị cho biến năm và những người khác thích nó). Chạy bundle exec rspec trong thiết bị đầu cuối của bạn chạy các bài kiểm tra và cho thấy rằng tất cả các bài kiểm tra đều vượt qua. Về cơ bản, bạn không phải thêm kiểm tra trong thử nghiệm của mình cho kết quả thất bại và thành công cùng một lúc, vì chúng tôi không thể có cả hai làm đầu ra của một phương pháp. Tôi chỉ thêm nó để hỗ trợ việc hiểu kết quả thành công trông như thế nào khi chúng ta có kết quả thất bại và ngược lại.

Kết luận

Đây chỉ là phần giới thiệu về đơn nguyên khô và cách nó có thể được sử dụng trong ứng dụng của bạn để đạt được Lập trình định hướng đường sắt. Hiểu biết cơ bản về điều này có thể được áp dụng thêm cho các hoạt động và giao dịch phức tạp hơn. Như chúng ta đã thấy, mã sạch hơn và dễ đọc hơn không chỉ có thể đạt được khi sử dụng ROP, mà việc xử lý lỗi cũng chi tiết và ít căng thẳng hơn. Luôn nhớ đính kèm thông báo thất bại / thành công ngắn gọn cho các phương pháp khác nhau tạo nên quy trình của bạn, vì phương pháp này hỗ trợ xác định vị trí và lý do tại sao xảy ra lỗi. Nếu bạn muốn biết thêm thông tin về ROP, chúng tôi khuyên bạn nên xem bài thuyết trình này của Scott Wlaschin.