How to separate good code from legacy/quirks-mode

2020-07-16 08:48发布

问题:

Given some library that implements some widespread protocol or something similar (for instance FTP), how would I keep my standard compliant code separate from code that is only needed to be able to cooperate with not so standard compliant systems?

A nice example where this would make sense too IMHO are libraries like jQuery that have to consider all those browser peculiarities. Projects that have to keep legacy compatibility would probably also be a good target audience for such techniques.

I'm especially interested in ruby solutions but language independent patterns or good examples from other languages are welcome too.

I already found a related question here on stackoverflow, but are there any other approaches?

回答1:

  1. Define different implementations for the different modes (this prevents you from having to mix "good" code with code that's just there to keep backwards-compatibility). Ideally, the legacy layer is only a wrapper around the standards-compliant code.
  2. Detect to what degree the underlying system (browser, remote server, ...) is standards-compliant. How this is done in detail is obviously highly dependent on the specific case.
  3. Choose right implementation for the particular system and transparently plug it in.
  4. Give the user a chance to check in which mode we are and to force a specific mode.

Small Ruby example:

class GoodServer
  def calculate(expr)
    return eval(expr).to_s
  end
end

class QuirkyServer
  def calculate(expr)
    # quirky server prefixes the result with "result: "
    return "result: %s" % eval(expr)
  end
end

module GoodClient
  def calculate(expr)
    @server.calculate(expr)
  end
end

# compatibility layer
module QuirkyClient
  include GoodClient
  def calculate(expr)
    super(expr).gsub(/^result: /, '')
  end
end

class Client
  def initialize(server)
    @server = server
    # figure out if the server is quirky and mix in the matching module
    if @server.calculate("1").include?("result")
      extend QuirkyClient
    else
      extend GoodClient
    end
  end
end

good_server = GoodServer.new
bad_server = QuirkyServer.new

# we can access both servers using the same interface
client1 = Client.new(good_server)
client2 = Client.new(bad_server)

p client1.is_a? QuirkyClient # => false
p client1.calculate("1 + 2") # => "3"

p client2.is_a? QuirkyClient # => true
p client2.calculate("1 + 2") # => "3"