Tuesday, March 26, 2013

S is for...

This is the first of five posts about my foray into the SOLID principles. I decided to start with S, but later letters may be out of order.

S is for Single Responsibility Principle.

This principle states that there should be one reason for a class or module to change. While it sounds simple enough, I have violated this principle. Like most of my examples, this one comes from my Ruby Tic-Tac-Toe application. Specifically, it comes from an early implementation of my board class.

A long time ago (January), when I began building my game board I gave to it the ability to store the positions of player pieces. Then, when I needed to draw the board, I gave it the ability to convert itself into a formatted string to print to the console. Still later, I allowed it to check itself for winners or a draw so that I could see if the game should be over. All of this functionality was packed into a single class.

class Board
  # square methods

  def to_s
    # formatting code
  end

  def game_over?
    # rules checking
  end
end

Not only did I violate SRP, I did it twice in the same class. Actually, I'm sure there are a lot of other principles violated and designs mangled...but that's not the point of this. The good news is that adding features caused me to right this wrong before I was given reading on SRP.

The fix came when I was adding my Qt gui. The goal was to add another gui to my game while maintaining support for the console version. My console version benefited from the string representation of my board, but Qt (and probably ever other kind of gui) did not. I decided to move the string formatting into the console-only portion of my code. I can now see how bad this could have been. If Ruby were a compiled language, then every time my console representation changed all other versions would need to be recompiled. That doesn't sound efficient at all.

class Board
  # square methods

  def game_over?
    # rules checking
  end
end

class ConsolePresenter
  def self.format_board(board)
    # formatting code
  end
end

I forget exactly why, but I also moved the winner/draw checking code into its own rules class. I think I was in a cleaning mood and removing it made my board feel simpler. However that happened, I was left with a board that only kept track of the pieces on it. The only reason it would need to change in the future is if I decided to store the pieces in a different way.

class Board
  # square methods
end

class Rules
  def self.game_over?(board)
    # checking rules
  end
end

class ConsolePresenter
  def self.format_board(board)
    # formatting code
  end
end

I'm glad now for two reasons. The first is that I fixed my code. The second is that I understand why it was important that I fixed it. I'm going to be going back into that code soon and the easier it is to work with the better that's going to make my life.

No comments:

Post a Comment