Back to Course

Practice · SOLID · LSP · Card 8

Two exporters, one method name, different shapes

Both classes implement export. Calling code treats them as interchangeable. They aren't. Why?

The code

A CSV exporter, a JSON exporter, and the caller that picks between them.

class CSVExporter
  def export(records)
    CSV.generate do |csv|
      csv << records.first.attributes.keys
      records.each { |r| csv << r.attributes.values }
    end
  end
end

class JSONExporter
  def export(records)
    { count: records.size, data: records.map(&:as_json) }
  end
end

# The caller:
def deliver_report(format:, records:)
  exporter = format == :csv ? CSVExporter.new : JSONExporter.new
  payload = exporter.export(records)
  ReportMailer.with(body: payload).deliver_later
end

The question

Both implement export. The caller swaps between them by reading a format flag. What's the LSP-shaped trap, and how do you spot it on review?

Take a moment. Duck typing says two objects with the same method are interchangeable from the caller's view. The contract isn't just the method name — it's also what the method returns. Compare the two return values.