12/08/2018, 15:52

10 lỗi thường mắc phải của Rails developer

Ruby on Rails đang là một trong những open framework phổ biến, được viết bằng ngôn ngữ Ruby nhắm đơn giản hóa việc xây dựng và phát triển ứng dụng web. Rails được xây dựng trên nguyên tắc quy ước về cấu hình (convention over configuration). Điều này có nghĩa là, Rails giả định rằng các nhà phát ...

Ruby on Rails đang là một trong những open framework phổ biến, được viết bằng ngôn ngữ Ruby nhắm đơn giản hóa việc xây dựng và phát triển ứng dụng web.

Rails được xây dựng trên nguyên tắc quy ước về cấu hình (convention over configuration). Điều này có nghĩa là, Rails giả định rằng các nhà phát triển chuyên môn của nó sẽ tuân theo các quy ước về thực tiễn tốt nhất "chuẩn" (đối với những thứ như đặt tên, cấu trúc mã, v.v ...) và nếu bạn làm, mọi thứ sẽ làm việc cho bạn "auto -magically "mà không cần phải xác định những chi tiết này. Mặc dù mô hình này có lợi thế của nó, nó cũng không phải là không có những điểm yếu của nó. Đáng chú ý nhất là "ma thuật" xảy ra đằng sau những đoạn mã code này đôi khi có thể dẫn đến sự thất bại, confuse và "những gì đang xảy ra?". Nó cũng có thể có những hậu quả không mong muốn liên quan đến an ninh và hiệu suất.

Theo đó, trong khi Rails rất dễ sử dụng, cũng không khó để lạm dụng. Bài viết này chỉ ra 10 vấn đề chung của Rails, bao gồm cách tránh chúng và các vấn đề mà chúng gây ra.

Ruby on Rails dựa trên mô hình. Trong cộng đồng Rails, chúng ta đã nói về fat model, skinny controller trong một thời gian, tuy nhiên một số ứng dụng Rails vẫn vi phạm vào nguyên tắc này. Các đơn giản mọi người hay lựa chọn là cho view logic, hoặc logic doamin / model, vào controller.

Vấn đề là đối tượng controller sẽ bắt đầu vi phạm single responsibility principle khiến những thay đổi trong tương lai đối với cơ sở mã khó khăn và dễ bị lỗi. Nói chung, các loại logic bạn nên có trong controller của bạn là:

  • Quản lý phiên và cookie: Điều này cũng có thể bao gồm authentication/authorization hoặc bất kỳ quá trình xử lý cookie bổ sung nào bạn cần làm.
  • Lựa chọn model: Logic cho việc tìm kiếm các đối tượng model đúng với các thông số được thông qua từ yêu cầu. Lý tưởng nhất là nên gọi cho một phương thức tìm kiếm duy nhất để đặt một biến thể dụ sẽ được sử dụng sau để trả lời.
  • Yêu cầu quản lý tham số: Thu thập thông số yêu cầu và gọi một phương thức model thích hợp.
  • Rendering/redirecting: Hiển thị kết quả (html, xml, json, v.v.) hoặc chuyển hướng, nếu thích hợp.

Mặc dù điều này vẫn vi phạm các giới hạn của nguyên tắc trách nhiệm duy nhất (single responsibility principle), tuy nhiên đó là một phần tối thiểu mà Rails đòi hỏi chúng ta phải có trong controller.

Engine template Rails, ERB, là một cách tuyệt vời để xây dựng các trang có nội dung biến đổi. Tuy nhiên, nếu bạn không cẩn thận, bạn sẽ sớm kết thúc với một file lớn là sự kết hợp của code HTML và code Ruby, có thể khó quản lý và duy trì. Đây cũng là khu vực có thể dẫn đến nhiều lần lặp đi lặp lại, dẫn đến vi phạm nguyên tắc DRY (không lặp lại - don’t repeat yourself).

Điều này có thể tự biểu hiện theo một số cách. Sử dụng quá mức logic điều kiện trong view. Như một ví dụ đơn giản, hãy xem xét trường hợp chúng ta có một phương thức current_user có sẵn trả về người dùng đã đăng nhập hiện tại. Thông thường, sẽ có kết cấu là logic điều kiện như thế này trong các file view:

<h3>
  Welcome,
  <% if current_user %>
    <%= current_user.name %>
  <% else %>
    Guest
  <% end %>
</h3>

Một cách tốt hơn để xử lý một cái gì đó như thế này là để đảm bảo rằng đối tượng trả về bởi current_user luôn luôn được đặt, cho dù có ai đó đăng nhập hay không, và rằng nó trả lời các phương thức sử dụng trong view một cách hợp lý (đôi khi được gọi là null object). Ví dụ, bạn có thể định nghĩa current_user helper trong app/controllers/application_controller như sau:

require 'ostruct'

helper_method :current_user

def current_user
  @current_user ||= User.find session[:user_id] if session[:user_id]
  if @current_user
    @current_user
  else
    OpenStruct.new(name: 'Guest')
  end
end

Điều này sau đó sẽ cho phép bạn thay thế ví dụ code view trước bằng một dòng mã đơn giản sau:

<h3>Welcome, <%= current_user.name -%></h3>

Một vài gợi ý bổ sung của Rails:

  • Sử dụng view layouts and partials phù hợp để đóng gói những thứ được lặp lại trên các trang của bạn.
  • Sử dụng các presenters/decorators như Draper gem để đóng gói logic xây dựng view trong một đối tượng Ruby. Sau đó bạn có thể thêm các phương thức vào đối tượng này để thực hiện các hoạt động hợp lý mà bạn có thể đã đưa vào view.

Ngoài ra cũng còn một số vấn đề với view mà hay gặp phải như:

  • Load tất cả dữ liệu ra mà không sử dụng phân trang: khi sử dụng dữ liệu quá lớn, khoảng hơn 1000 record, thời gian để lấy hoàn tất tất cả các record khá lâu ở phía server, dẫn đến client phải đợi để có dữ liệu display. Ví dụ
@models = Model.all hay Model.select("...")
# SELECT * FROM Mode
per_page = 100
@models = Model.paginate :page = params[:page] ||= 1, :per_page => per_page
# SELECT * FROM Model LIMIT 100 OFFSET 0
  • Thao tác trực tiếp với database từ view
Post.all.each do |post|
  // do somethings
end

Cách này làm tốn thời gian load ở Server một đoạn dữ liệu nào đó hoàn tất, lại phải thực hiện lại thao tác xuống DB và render view cùng lúc, thực hiện song song 2 việc, do vậy thời gian sẽ phải chờ lâu hơn bình thường. Thay vào đó hãy load hết dữ liệu ở controller và chi render dữ liệu được gửi từ controller

# posts_controller.rb
@posts = Post.all
# index.html.erb
@posts.each do |post|
  // do somethings
end

Với hướng dẫn để giảm thiểu logic trong view và controller, nơi duy nhất còn lại trong kiến trúc MVC để đặt tất cả logic đó vào các model. Tuy nhiên điều đó lại chưa hẳn chính xác.

Nhiều Rails developer thực sự mắc phải sai lầm này và cho tất cả mọi thứ trong các lớp model ActiveRecord của họ, dẫn tới không chỉ vi phạm nghiêm trọng nguyên tắc trách nhiệm duy nhất (single responsibility principle) mà còn rất khó để bảo trì hệ thống.

Chức năng như tạo ra các thông báo email, liên kết tới các dịch vụ bên ngoài, chuyển đổi sang các định dạng dữ liệu khác và không có nhiều liên quan đến trách nhiệm cốt lõi của một model ActiveRecord cần được làm ít hơn việc tìm kiếm và duy trì dữ liệu trong cơ sở dữ liệu.

Vì vậy, các logic khác chúng ta nên để nó ở đâu?

Theo như plain old Ruby objects (PORO). Với một framework toàn diện như Rails, các developer mới thường miễn cưỡng tạo ra các lớp của họ bên ngoài framework. Tuy nhiên, di chuyển logic ra khỏi model thành POROs thường là những gì được khuyến khích để tránh các model quá phức tạp. Với PORO, bạn có thể đóng gói những thứ như thông báo email hoặc tương tác API vào các lớp của chính họ chứ không gắn chúng vào model ActiveRecord.

Vì vậy với điều đó trong tâm trí, nói chung, logic duy nhất mà nên vẫn còn trong mô hình của bạn là:

  • ActiveRecord configuration (i.e., relations)
  • Simple mutation methods Để đóng gói cập nhật một số ít các thuộc tính và lưu chúng trong cơ sở dữ liệu
  • Access wrappers Để ẩn thông tin model nội bộ (ví dụ: phương thức full_name kết hợp trường first_name và last_name trong cơ sở dữ liệu)
  • Sophisticated queries (Nghĩa là phức tạp hơn tìm kiếm đơn giản); Nói chung, bạn không bao giờ nên sử dụng phương pháp ở đâu, hoặc bất kỳ phương pháp xây dựng truy vấn nào khác giống như nó, bên ngoài lớp mô hình của chính nó.

Tuy nhiên để giảm thiểu triệt để thì chúng nên sử dụng POROs để move các phần logic vào đúng vị trí. Các bạn có thể tham thảo tại giới thiệu về cấu trúc thư mục và refactor trong Ruby on Rails

Sai lầm này thực sự là một hệ quả của sai lầm # 3 ở trên. Như đã thảo luận, Rails đặt trọng tâm vào các thành phần được đặt tên (ví dụ: model, view và controller) của mô hình MVC. Có những định nghĩa khá tốt về các loại thứ thuộc về các lớp của mỗi thành phần, nhưng đôi khi chúng ta có thể cần các phương pháp mà dường như không phù hợp với bất kỳ thành phần nào.

Các generators của Rails thuận tiện xây dựng một thư mục helper và một lớp helper mới để đi với mỗi tài nguyên mới tạo ra. Tuy nhiên, nó sẽ trở nên phức tạp để nhồi nhét bất kỳ chức năng nào không chính thức phù hợp với model, view và controller vào các lớp trợ giúp này.

Trong khi Rails chắc chắn là MVC-centric, không có gì ngăn cản bạn tạo ra các loại lớp riêng và thêm các thư mục thích hợp để giữ code cho các class. Khi bạn có chức năng bổ sung, hãy suy nghĩ về các phương pháp nhóm với nhau và tìm tên tốt cho các lớp mà giữ các method đó. Sử dụng một comprehensive framework như Rails không phải là một cái cớ để bỏ qua các phương pháp thiết kế theo hướng đối tượng tốt.

Ruby and Rails được hỗ trợ bởi một hệ sinh thái phong phú của gem cung cấp về bất kỳ khả năng mà developer có thể nghĩ đến. Điều này rất tốt cho việc xây dựng một ứng dụng phức tạp một cách nhanh chóng, nhưng tôi cũng thấy nhiều ứng dụng cồng kềnh, trong đó số lượng gem trong Gemfile của ứng dụng là không cân xứng khi so sánh với các chức năng được cung cấp.

Điều này gây ra một số vấn đề Rails. Sử dụng quá nhiều gem làm cho kích thước của một quá trình Rails lớn hơn nó cần phải được. Điều này có thể làm chậm hiệu suất production. Ngoài sự thất vọng của người dùng, điều này cũng có thể dẫn đến sự cần thiết cho cấu hình bộ nhớ máy chủ lớn hơn và tăng chi phí hoạt động. Nó cũng cần nhiều thời gian hơn để bắt đầu các ứng dụng Rails lớn hơn, làm cho quá trình phát triển chậm hơn và làm cho các bài kiểm tra tự động mất nhiều thời gian hơn (và theo nguyên tắc, các bài kiểm tra chậm không cần chạy thường xuyên).

Hãy nhớ rằng mỗi gem mang vào trong ứng dụng của bạn có thể phụ thuộc vào gem khác, và những thứ đó có thể phụ thuộc vào các gem khác, vân vân. Thêm gem khác do đó có thể có một hiệu ứng kết hợp. Ví dụ, việc thêm rails_admin gem sẽ mang lại thêm 11 gem, tăng 10% so với việc cài đặt Rails cơ bản.

Ví dụ bản cài đặt Rails 4.1.0 bao gồm 43 gem trong tệp Gemfile.lock. Điều này rõ ràng là nhiều hơn bao gồm trong Gemfile và đại diện cho tất cả các gem mà một số ít các gem Rails tiêu chuẩn đưa vào do sự phụ thuộc.

Cẩn thận xem xét liệu thêm chi phí là đáng giá khi bạn thêm mỗi gem. Ví dụ, các developer sẽ thường thêm vào rails_admin gem bởi vì nó về cơ bản cung cấp một giao diện trang web đẹp cho cấu trúc model, nhưng nó thực sự không chỉ là một công cụ duyệt cơ sở dữ liệu ưa thích. Ngay cả khi ứng dụng của bạn yêu cầu người dùng quản trị có các đặc quyền bổ sung, bạn có thể không muốn cung cấp cho họ quyền truy cập cơ sở dữ liệu thô và bạn sẽ được phục vụ tốt hơn bằng cách phát triển chức năng quản trị được sắp xếp hợp lý của riêng mình bằng cách thêm vào phần mềm này.

Mặc dù hầu hết các Rails developer đều biết các file log mặc định có sẵn trong develop và production, nhưng chúng ta thường không quan tâm đến thông tin trong các file đó. Mặc dù nhiều ứng dụng dựa vào các công cụ theo dõi đăng nhập như Honeybadger hay New Relic trong production, điều quan trọng là phải theo dõi các file log của bạn trong quá trình develop và test ứng dụng của bạn.

Như đã đề cập trước đây trong hướng dẫn này, framework Rails làm rất nhiều "ma thuật" cho bạn, đặc biệt là trong các model. Định nghĩa liên kết trong các model của bạn làm cho việc liên kết được dễ dàng và có mọi thứ sẵn có cho quan điểm của bạn. Tất cả SQL cần thiết để điền vào các đối tượng model được tạo ra. Thật tuyệt. Nhưng làm thế nào để bạn biết rằng SQL được tạo ra là hiệu quả?

Một ví dụ bạn thường chạy vào được gọi là vấn đề truy vấn N + 1. Trong khi vấn đề được hiểu rõ, cách duy nhất để quan sát nó xảy ra là xem xét các truy vấn SQL trong các file log của bạn.

Nói ví dụ bạn có các truy vấn sau đây trong một ứng dụng blog điển hình, nơi bạn sẽ được hiển thị tất cả các comments cho một lựa chọn các bài viết:

def comments_for_top_three_posts
  posts = Post.limit(3)
  posts.flat_map do |post|
    post.comments.to_a
  end
end

Khi chúng ta nhìn vào file log của một yêu cầu gọi phương thức này, chúng ta sẽ thấy như sau, nơi một truy vấn được thực hiện để có được ba đối tượng post sau đó thêm ba câu truy vấn nữa để nhận được các comment của các đối tượng đó:

Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700
Processing by PostsController#some_comments as HTML
  Post Load (0.4ms)  SELECT "posts".* FROM "posts" LIMIT 3
  Comment Load (5.6ms)  ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]
  Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 2]]
  Comment Load (1.5ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 3]]
  Rendered posts/some_comments.html.erb within layouts/application (12.5ms)
Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)

Sử dụng eager_loading ActiveRecord trong Rails làm giảm đáng kể số lượng truy vấn bằng cách cho phép bạn xác định trước tất cả các associations sẽ được load. Điều này được thực hiện bằng cách gọi phương thức include (hoặc preload) trên đối tượng Arel (ActiveRecord :: Relation). Với include, ActiveRecord đảm bảo rằng tất cả các associations được chỉ định được nạp bằng cách sử dụng tối thiểu số lượng truy vấn; ví dụ:

def comments_for_top_three_posts
  posts = Post.includes(:comments).limit(3)
  posts.flat_map do |post|
    post.comments.to_a
  end
end

Khi mã được sửa đổi ở trên được thực hiện, chúng ta sẽ thấy trong file log rằng tất cả các nhận xét đã được thu thập trong một truy vấn thay vì ba:

Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700
Processing by PostsController#some_comments as HTML
  Post Load (0.5ms)  SELECT "posts".* FROM "posts" LIMIT 3
  Comment Load (4.4ms)  SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3)
  Rendered posts/some_comments.html.erb within layouts/application (12.2ms)
Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)

Hiệu quả hơn nhiều.

Giải pháp cho vấn đề N +1 chỉ thực sự là một ví dụ về loại hiệu quả không thể có trong "ứng dụng của bạn" nếu bạn không chú ý đúng mức. Các takeaway ở đây là bạn nên kiểm tra development của bạn và test các file đăng nhập trong quá trình phát triển để kiểm tra (và địa chỉ) không hiệu quả.

Xem lại các file log là một cách tuyệt vời để được chuyển hướng tới sự không hiệu quả trong mã của bạn và để sửa chúng trước khi ứng dụng của bạn đi vào production. Nếu không, bạn có thể không nhận thức được vấn đề về hiệu suất của Rails đến khi hệ thống của bạn hoạt động, vì bộ dữ liệu bạn làm việc với development và test có thể nhỏ hơn nhiều so với production. Nếu bạn đang làm việc với một ứng dụng mới, thậm chí bộ dữ liệu production của bạn có thể bắt đầu nhỏ và ứng dụng của bạn sẽ trông như nó đang chạy tốt. Tuy nhiên, khi tập dữ liệu production của bạn phát triển, các vấn đề về Rails như thế này sẽ làm cho ứng dụng của bạn chạy chậm hơn và chậm hơn.

Nếu bạn thấy rằng các file log của bạn bị tắc với một loạt thông tin bạn không cần ở đây là một số điều bạn có thể làm để làm sạch chúng (các kỹ thuật làm việc cho việc development cũng như production log).

Ruby and Rails cung cấp khả năng kiểm tra tự động mạnh mẽ theo mặc định. Nhiều nhà phát triển Rails viết các bài kiểm tra rất tinh vi sử dụng các kiểu TDD và BDD và sử dụng các framework test mạnh hơn với gem như rspec và cucumber.

Mặc dù cách dễ dàng để thêm automated testing vào ứng dụng Rails của bạn, tuy nhiên không phải lúc nào các Rails project cũng sử dụng test. Mặc dù có rất nhiều cuộc tranh luận về việc thử nghiệm của bạn nên toàn diện như thế nào, nhưng rõ ràng là ít nhất một số thử nghiệm tự động nên tồn tại cho mỗi ứng dụng.

Theo nguyên tắc chung, phải có ít nhất một bài kiểm tra tích hợp cấp cao được viết cho mỗi hành động trong controller của bạn. Tại một số điểm trong tương lai, các nhà phát triển Rails khác sẽ muốn mở rộng hoặc sửa đổi mã, hoặc nâng cấp phiên bản Ruby hoặc Rails, và framework test này sẽ cung cấp cho họ một cách rõ ràng để xác minh rằng các chức năng cơ bản của ứng dụng là đang làm việc. Một lợi ích khác của cách tiếp cận này là nó cung cấp cho các nhà phát triển trong tương lai với một mô tả rõ ràng về việc thu thập đầy đủ các chức năng được cung cấp bởi ứng dụng.

Các nhà cung cấp dịch vụ Rails (3rd party) bên trong các dịch vụ Rails thường làm cho nó dễ dàng tích hợp các dịch vụ của họ vào ứng dụng của bạn thông qua các gem bao quanh các API của họ. Nhưng điều gì sẽ xảy ra nếu dịch vụ bên ngoài của bạn bị gián đoạn hoặc bắt đầu chạy rất chậm?

Để tránh bị chặn các cuộc gọi này, thay vì gọi các dịch vụ này trực tiếp vào ứng dụng Rails của bạn trong quá trình xử lý yêu cầu bình thường, bạn nên di chuyển chúng đến một số loại công việc xếp hàng đợi ở nơi khả thi. Một số gem phổ biến được sử dụng trong các ứng dụng Rails cho mục đích này bao gồm:

  • Delayed job
  • Resque
  • Sidekiq

Trong trường hợp không thực hiện được hoặc không thực hiện việc ủy thác xử lý cho hàng đợi công việc nền (background job queue), bạn cần phải đảm bảo rằng ứng dụng của bạn đã xử lý lỗi đầy đủ và các điều kiện không thành công đối với những tình huống không tránh khỏi khi dịch vụ bên ngoài bị hỏng hoặc đang gặp sự cố . Bạn cũng nên kiểm tra ứng dụng của mình mà không có dịch vụ bên ngoài (có thể bằng cách gỡ bỏ máy chủ ứng dụng của bạn khỏi mạng) để xác minh rằng nó không dẫn đến bất kỳ hậu quả không lường trước nào đó.

Cơ chế di chuyển cơ sở dữ liệu (database migration mechanism) của Rails cho phép bạn tạo các hướng dẫn để tự động thêm và xóa các bảng cơ sở dữ liệu và các row. Các tập tin có chứa các thay đổi này được đặt tên theo cách tuần tự, bạn có thể chạy chúng từ thời điểm ban đầu để mang lại một cơ sở dữ liệu trống cho cùng một schema như production. Do đó, đây là một cách tuyệt vời để quản lý các thay đổi hẹp cho giản đồ cơ sở dữ liệu của ứng dụng của bạn và tránh các sự cố Rails.

Mặc dù điều này chắc chắn hoạt động tốt khi bắt đầu dự án, nhưng theo thời gian, quá trình tạo cơ sở dữ liệu có thể mất một thời gian và đôi khi di chuyển bị thất lạc, không theo trật tự hoặc được giới thiệu từ các ứng dụng Rails khác sử dụng cùng một máy chủ cơ sở dữ liệu.

Rails tạo ra một schema hiện tại của bạn trong một tệp có tên db / schema.rb (mặc định) thường được cập nhật khi di chuyển cơ sở dữ liệu. Tệp schema.rb thậm chí có thể được tạo ra khi không có migration nào bằng cách chạy lệnh rails db: schema: dump. Sai lầm Rails phổ biến là kiểm tra một lần migration mới vào repo nguồn của bạn nhưng không phải là tệp schema.rb được cập nhật tương ứng.

Khi migration đã mất và mất quá nhiều thời gian để chạy, hoặc không còn tạo cơ sở dữ liệu đúng cách nữa, các nhà phát triển không nên sợ xóa thư mục migration cũ, tạo một schema mới và tiếp tục từ đó. Thiết lập một môi trường phát triển mới sau đó sẽ yêu cầu một rake db:schema:load chứ không phải là rake db:migrate mà hầu hết các nhà phát triển dựa vào.

Một số vấn đề liên quan cũng được thảo luận trong Rails Guide

Framework Rails giúp bạn dễ dàng tạo các ứng dụng an toàn chống lại nhiều loại tấn công. Một số điều này được thực hiện bằng cách sử dụng một token bí mật để bảo vệ một phiên làm việc với một trình duyệt. Mặc dù token này bây giờ được lưu giữ trong config/secrets.yml, và tệp đó đọc token từ một biến môi trường cho các production server, các phiên bản trước của Rails bao gồm các token trong config/itializers/secret_token.rb. File này thường bị nhầm lẫn được kiểm tra vào code repository với phần còn lại của ứng dụng và khi điều này xảy ra, bất kỳ ai có quyền truy cập repository đều có thể dễ dàng thao tác với tất cả người dùng ứng dụng của bạn.

Vì vậy, bạn nên đảm bảo rằng tệp cấu hình repository của bạn (ví dụ: .gitignore dành cho người dùng git) loại trừ token của bạn. Các prodution server sau đó có thể lấy token của họ từ một biến môi trường hoặc từ một cơ chế giống như hạt nhân mà dotenv gem cung cấp.

Rails là một framework mạnh mẽ che giấu rất nhiều chi tiết xấu cần thiết để xây dựng một ứng dụng web mạnh mẽ. Mặc dù điều này làm cho việc phát triển ứng dụng web Rails nhanh hơn nhiều, nhưng các developer nên quan tâm đến các lỗi thiết kế và mã hóa tiềm ẩn để đảm bảo rằng các ứng dụng của mình có thể mở rộng và duy trì dễ dàng khi chúng phát triển.

Developer cũng cần phải nhận thức được các vấn đề có thể làm cho ứng dụng của họ chậm hơn, ít đáng tin cậy hơn và ít an toàn hơn. Điều quan trọng là phải nghiên cứu framework và đảm bảo rằng bạn đã hiểu đầy đủ về sự cân bằng kiến trúc, thiết kế và mã hóa mà bạn đang thực hiện trong quá trình phát triển, để đảm bảo ứng dụng có chất lượng và hiệu năng cao.

0