SOLID trong Ruby: nguyên tắc thiết kế, không phải checklist đạo đức

SOLID giúp code thay đổi với blast radius nhỏ hơn. Nó không phải mục tiêu tự thân; áp dụng máy móc có thể biến ba dòng code thành mười bảy interface đang chờ business requirement đầu tiên.

S — Single Responsibility

Một module/class nên có một lý do thay đổi mang tính stakeholder.

1
2
3
4
5
6
7
class Invoice
def total = items.sum(&:price)
end

class InvoicePdf
def render(invoice) = # ...
end

Tính tiền và render PDF thay đổi vì lý do khác nhau.

O — Open/Closed

Mở cho extension, đóng với modification không có nghĩa “không bao giờ sửa code cũ”. Nó khuyến khích stable abstraction:

1
2
3
4
5
6
7
8
9
class Checkout
def initialize(payment_gateway:)
@payment_gateway = payment_gateway
end

def call(amount)
@payment_gateway.charge(amount)
end
end

Thêm gateway bằng object mới thay vì case provider lan khắp hệ thống.

L — Liskov Substitution

Subtype phải dùng thay base type mà không phá contract. Nếu subclass raise NotImplementedError cho method base hứa hỗ trợ, inheritance có thể sai abstraction.

Ruby duck typing không loại bỏ LSP; nó chỉ làm contract implicit hơn, nghĩa là test và naming phải làm việc chăm hơn.

I — Interface Segregation

Consumer không nên phụ thuộc method không dùng. Ruby module nhỏ hoặc protocol duck-typed giúp tách capability:

1
2
3
4
5
module Refundable
def refund(amount)
# ...
end
end

D — Dependency Inversion

Business policy phụ thuộc abstraction/capability, không phụ thuộc detail cụ thể:

1
SendReceipt.new(mailer: RailsMailer.new)

Inject dependency giúp test và thay implementation. Nhưng inject Time, logger, serializer, string formatter vào mọi object chỉ để “SOLID” có thể làm constructor trông như danh sách khách mời đám cưới.

Dùng SOLID khi nào?

  • có nhiều implementation;
  • dependency ngoài thay đổi/khó test;
  • class có nhiều nhóm lý do thay đổi;
  • conditional theo type/provider tăng dần;
  • boundary domain cần giữ ổn định.

Không abstract trước khi thấy trục thay đổi. Duplication nhỏ đôi khi rẻ hơn abstraction sai.

Kết luận

SOLID là vocabulary để thảo luận coupling, contract và changeability. Thiết kế tốt không phải code có nhiều class nhất; nó là code khiến thay đổi dự đoán được.