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.
--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.
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.
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.
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