How to write code that thinks object-oriented Ruby

What is object-oriented?

It's a design method, not a grammar. When I first heard it, I thought it was some kind of grammar or syntax, but that's not the case. It seems that the correct understanding is that Ruby has an object-oriented syntax that makes it easy to write.

Why write object-oriented

--Greatly improves code readability --Easy to visualize invisible bugs

I think there is something in it. This time, I will focus on this point and summarize it.

First, try writing without thinking about object orientation

toriaezuugoku.rb


x_max = ARGV[0]
y_max = ARGV[1]

if !x_max || !y_max
  puts "Please specify an argument"
  exit 1
end

x_max =  x_max.to_i
y_max =  y_max.to_i

#Status
x = 1
y = 1
step = 1
x_way = 1
y_way = 1

puts " #{step} (#{x},#{y})"
x += 1
y += 1
loop do
  step += 1
  puts " #{step} (#{x},#{y})"

  if y == 1 && x == x_max || x == 1 && y == y_max || x == 1 && y == 1 || x == x_max && y == y_max
    puts "GOAL!!"
    break
  elsif x == x_max
    x_way = -1
  elsif y == y_max
    y_way = -1
  elsif x == 1
    x_way = 1
  elsif y == 1
    y_way = 1
  end

  x += x_way
  y += y_way
end

This is the code I wrote in the previous article. It is written so that all the processing flows from top to bottom without defining a function. This is the hardest code to read. This is because the specifications are such that the function cannot be read unless the actual processing is followed by predicting what is being done from the variable definition. Let's give up trying to read what this code is doing over time here and look at the rewritten code next.

methodtohash.rb


def move_ball(hash)
	hash["x"] += hash["x_way"]
	hash["y"] += hash["y_way"]
	hash["step"] += 1
end

def reflect_x(hash)
	if hash["x_way"] == 1
		hash["x_way"] = -1
  elsif hash["x_way"] == -1
    hash["x_way"] = 1
  end
end

def reflect_y(hash)
	if hash["y_way"] == 1
		hash["y_way"] = -1
  elsif hash["y_way"] == -1
    hash["y_way"] = 1
  end
end

def goal?(hash, x_max, y_max)
  hash["y"] == 1 && hash["x"] == x_max \
  || hash["x"] == 1 && hash["y"] == y_max \
  || hash["x"] == 1 && hash["y"] == 1 \
  || hash["x"] == x_max && hash["y"] == y_max
end

def boundary_x?(hash, x_max)
  hash["x"] == x_max || hash["x"] == 1
end

def boundary_y?(hash, y_max)
  hash["y"] == y_max || hash["y"] == 1
end

x_max = ARGV[0]
y_max = ARGV[1]

if !x_max || !y_max
  puts "Please specify an argument"
  exit 1
end

x_max =  x_max.to_i
y_max =  y_max.to_i

state = {
  "x" => 1,
  "y" => 1,
  "step" => 1,
  "x_way" => 1,
  "y_way" => 1
}

puts " #{state["step"]} (#{state["x"]},#{state["y"]})"

loop do
  move_ball(state)

  puts " #{state["step"]} (#{state["x"]},#{state["y"]})"

  if goal?(state, x_max, y_max)
    puts "GOAL!!"
    break
  elsif boundary_x?(state, x_max)
    reflect_x(state)
  elsif boundary_y?(state, y_max)
    reflect_y(state)
  end
end

This time I wrote it using the function definition and hash. Looking at the loop statement, it seems that the state of the hash content of state is manipulated by a method called move_ball, and x and y when the ball bounces with boundary_x and boundary_y in the conditional expression. I wonder if the coordinates are changed by reflect_x and reflect_y ... It's becoming something that can't be read. If the processing of the contents defined by the function is correct, the reader will be able to easily understand what he is doing and what this code is doing by reading only here. There is such a merit if the part of when the state is changed is divided into functions. Also, using hashes makes state management safer than the previous code, which was expressed using many global variables, and is still a little more flexible as the number of balls increases. (If the number of balls is unreasonably large or unknown, we can not handle it) However, it is still not enough in pursuit of readability. Also, this code, which only returns nil if there is a mistake such as a hash or a typo, makes the error hard to find. The larger the product, the harder it is to discover.

So it ’s an object-oriented turn.

object.rb


class Ball
	attr_accessor :x, :y, :x_way, :y_way, :step

	def initialize(x: 1, y: 1, x_way: 1, y_way: 1, step: 1)
		@x = x
		@y = y
		@x_way = x_way
		@y_way = y_way
		@step = step
	end
	
	def move
		@x += @x_way
		@y += @y_way
		@step += 1
	end

	def reflect_x
		if @x_way == 1
			@x_way = -1
		elsif @x_way == -1
			@x_way = 1
		end
	end

	def reflect_y
		if @y_way == 1
			@y_way = -1
		elsif @y_way == -1
			@y_way = 1
		end
	end

	def goal?(x_max, y_max)
		@x == x_max && y == 1 \
		|| @x == 1 && @y == y_max \
		|| @x == 1 && @y == 1 \
		|| @x == x_max && @y == y_max
	end

	def boundary_x?(x_max)
		@x == x_max || @x == 1
	end

	def boundary_y?(y_max)
		@y == y_max || @y == 1
	end
end

class BilliardTable
	attr_accessor :length_x, :length_y, :ball

	def initialize(length_x: nil, length_y: nil, ball: nil)
		@length_x = length_x
		@length_y = length_y
		@ball = ball
	end

	def cue
		print_status

		loop do
			@ball.move

			print_status
		
			if @ball.goal?(@length_x, @length_y)
				puts "GOAL!!"
				break
			elsif @ball.boundary_x?(@length_x)
				@ball.reflect_x
			elsif @ball.boundary_y?(@length_y)
				@ball.reflect_y
			end
		end
	end

	def print_status
		puts "#{@ball.step}, (#{@ball.x}, #{@ball.y})"
	end
end

x_max = ARGV[0]
y_max = ARGV[1]

if !x_max || !y_max
	puts "Please specify an argument"
	exit 1
end

x_max =  x_max.to_i
y_max =  y_max.to_i

ball = Ball.new()

bt = BilliardTable.new(length_x: x_max, length_y: y_max, ball: ball)

bt.cue

You will be able to write as above. Divide the state of the ball and the state of the billiard table into classes, define the state operation method required for the function of the ball in the ball class, and define the processing of the operation on the table that moved the ball in the billiard class. So, in fact, if you want to know the general behavior of this code, you can grasp it by reading the processing system of the billiard class and the part that receives command line arguments, that is, the code part written outside the class. I can. The English naming convention is also much easier to read. Finally, when you execute cue (poke) against bt (billiard table), the ball moves. Very easy to read. Also, since the state operation is defined in a fairly detailed manner, I think that the ease of modifying the code and the ease of calling the method have also improved. I think this makes it easier to fix bugs than the two solid code and hash code I mentioned earlier.

Summary

I tried to rewrite the two-step code from the solid writing process that I wrote first, but I understood how difficult it is to read the code I wrote first, so to speak, it is dirty code. .. In rails, which is a framework made to make class design by object orientation easy, I was writing the processing of the code like I wrote at the beginning, so I was creating yabe code. Was even more prominent and well understood.

Recommended Posts

How to write code that thinks object-oriented Ruby
How to write good code
[Ruby] How to write blocks
How to write easy-to-understand code [Summary 3]
[RSpec] How to write test code
Write code that is difficult to test
How to write Rails
Write code that is easy to maintain (Part 1)
How to write dockerfile
How to write docker-compose
How to write Mockito
Write code that is easy to maintain (Part 4)
How to write test code with Basic authentication
How to write migrationfile
Write code that is easy to maintain (Part 3)
[Ruby on Rails] How to write enum in Japanese
How to use Ruby return
Bit Tetris (how to write)
How to write java comments
Great poor (how to write)
Ruby: How to use cookies
How to write Junit 5 organized
How to write Rails validation
How to write Rails seed
How to write Rails routing
Comparison of how to write Callback function (Java, JavaScript, Ruby)
Java 14 new features that could be used to write code
How to iterate infinitely in Ruby
Studying Java # 6 (How to write blocks)
[Rails] How to write in Japanese
How to install ruby through rbenv
How to install Bootstrap in Ruby
[Ruby] How to use any? Method
How to write a ternary operator
Rails on Tiles (how to write)
[Rails] How to write exception handling?
How to write Java variable declaration
Y-shaped road tour (how to write)
How to use Ruby inject method
How to execute Ruby irb (interactive ruby)
I tried to write code like a type declaration in Ruby
[RSpec on Rails] How to write test code for beginners by beginners
Offline real-time how to write F03 ruby and C implementation example
How to write ruby if in one line Summary by beginner
Let's write a code that is easy to maintain (Part 2) Name
How to get date data in Ruby
"Let's write versatile code that is easy to use over time" (nth time)
[Ruby on Rails] How to use CarrierWave
[Basic] How to write a Dockerfile Self-learning ②
[Introduction to Java] How to write a Java program
How to call Swift 5.3 code from Objective-C
Ruby length, size, count How to use
[Java] How to output and write files!
[Ruby] How to use slice for beginners
[Ruby on Rails] How to use redirect_to
[Easy] How to upgrade Ruby and bundler
[Ruby on Rails] How to use kaminari
Write code using Ruby classes and instances
How to write Spring AOP pointcut specifier
Ruby: CSV :: How to use Table Note
How to write an RSpec controller test