rubyclassrefactoringsingle-responsibility-principleresponsibility

Refactoring: When to stop Refactoring?


I am so obsessively OC at dividing a class or instance method and move it in its own class, and many times a simple "hello world" method will be divided into many classes.

like for example,

class MainProgram
  def hello_world
    puts "hello world"
  end
end

will be divided to

class SayHello
  def initialize
    @hello = "hello"
  end

  def hello
    @hello
  end
end

class SayWorld
  def initialize
    @world = "world"
  end

  def world
    @world
  end
end

class SayHelloWorld
  def initialize
    @hello = SayHello.new.hello
    @world = SayWorld.new.world
    @hello_world = "#{@hello} #{@world}"
  end

  def hello_world
    @hello_world
  end
end

class MainProgram
  def hello_world
    @hello_world = SayHelloWorld.new.hello_world
    @hello_world
  end
end

And sometimes it feels unnecessary. Is doing it this way any good at all?

When do I stop refactoring my code? Is there such thing as a refactored code?


Solution

  • "Refactoring mode" should only be enabled when you see code duplication for code that is complex enough (or contains business logic) that it might cause problems later down the line for doing maintenance/updates; such that it would create the scenario where you might forget to update all code locations that are otherwise meant to be identical in function, resulting in inconsistent behaviour of functionality depending on code-path.

    One common rule is that once you see 3 duplications of non-trivial code, you should refactor it.

    Refactoring enables re-usability, but its not exactly the same as making reusable software components (which I think is your motivation).

    What you are doing is over-engineering your software and are basically reducing your programming language's otherwise powerful and succinct syntax into a much simpler, more brittle and less expressive one. This is because you are creating too many abstraction layers and encapsulating basic expressions into classes.

    One useful question you should ask yourself is "What benefit do I get from turning this series of expression into a class?" e.g. "Do I need to track state?", "Do multiple instances make sense?", and "Do I gain or lose expressiveness? flexibility? convenience?".

    Another way of looking at it is that refactoring is often motivated by DRY (Dont Repeat Yourself), keeping in mind that DRY is not exactly the same as wanting to create reusable software components. However, there is also YAGNI (You Aint Gonna Need It): unless you need a certain functionality in the immediate future, you probably dont need to implement it now, just stick to the most basic version that works (at least to start off with).

    Dont speculate way off into the future thinking "I want to leave all options open for later on; what if I decide to do A, what if I decide to do B, yes, yes, I'm gonna turn this simple thing into a class that lets you also do f, g and h, just in case later on I decide I need it...!".

    So a lot of this is related to getting your software requirements and scope at a fairly stable level of finalisation before you start writing the relevant code. Knowing where the road stops helps you to estimate how much fuel you need, how to pace yourself properly, and apply resources proportionately to where they are needed most.

    Also, If you are new to programming and learning a language, you will be more likely to feel the need to shoehorn every language feature in your program even if you dont need it. As you start writing more complex, real world programs (well beyond hello world), you will get a feel for when to use what.

    Becoming aware of software antipatterns is one of the best ways to speed up this process. You are currently doing a form of "Cargo Cult Programming"