New version of reek – 1.3.7

There’s a new version of reek (1.3.7), the code smell detector for Ruby available as of today.

The new version adds colors to the output and corrects a bug with the unused parameter smell and the uncommunicative parameter smell.

I’ve made some contributions to the project (including the output coloring) and it’s been a great experience. It’s a great gem and great project to contribute to.

Reek vs RuboCop

Or code linters / analysers for Ruby

I initially stumbled upon RuboCop, a code quality analyzer for Ruby by chance. My interest was immediately piqued.

After trying it out but before committing to use it I searched for alternatives and found Reek, a code smell detector for Ruby.

Both project analyse Ruby source code and give you a series of warnings. Both can integrate with Rake and both have plugins or third parties that allow them to integrate with editors like Vim.

My first instinct was to search for “reek vs rubocop”, a search that led to no results. I was expecting to see some blog posts comparing the two.

I then proceeded to try them both on the same set of files. I then realised they are different programs that can complement each other rather then replace each other. While they will sometimes overlap they each have a different focus.

RuboCop

RuboCop’s main focus is on coding style and adherence to the Ruby Style Guide. Typical warnings will be about:

  • lines being too long
  • methods having too many lines
  • following Ruby idioms prescribed by the guide, like which types of quote to use or preferring each over for for iteration.

Feature wise RuboCop has more features. It has many output formatters and more options to work with. The output is also in color which is nice. Oh and I really like the name, seriously 🙂

Reek

Reek for it’s part will focus on code smells that could lead to refactorings and that aren’t style related.

Examples include:

  • methods with too many parameters
  • code repetition that could be extracted in a new method
  • unused parameters

Like RuboCop, Reek can be configured via a config file, but overall RuboCop has a bigger feature set (which may or may not be a good thing).

While RuboCop will help you enforce style guidelines on your project, Reek will allow you to find bad code that can be made better through refactoring.

Overlap

Both will give out a warning if a class has no class level comment. Both will also give a warning if there are too many lines/statements in a method.

RuboCop will count the number of lines in a method and warn if that number is over 10, Reek on the other hand will count the number of statements in a method.

There are also other overlaps.

They will usually give you a varying number of warnings for the same file. This has a tendency to go over both ways but in the end which one will give you the most warnings will depend on your coding style.

Example

Here is the default output for both linters on the following file:

class Particle
  attr_reader :stability_radius

  def initialize(stability_radius=1)
    @stability_radius = stability_radius
  end

  def drop(height_map, x, y, size_x)
    drop_point = x + y * size_x

    if height_map[drop_point] == 0
      height_map[drop_point] += 1
    else
      agitate(height_map, x, y, size_x, drop_point)
    end
  end

  private

  def agitate(height_map, x, y, size_x, drop_point)
    # compile list of lower level neighbors
    lower_neighbors = []

    for index_x in x - @stability_radius..x + @stability_radius
      for index_y in y - @stability_radius..y + @stability_radius
        check_and_add_neighbor(index_x, index_y, size_x, lower_neighbors, drop_point, height_map)
      end
    end

    if (lower_neighbors.length == 0) then
      # no lower neighbors, leave particle on drop point
      height_map[drop_point] += 1
    else
      # randomly select one of these neighbors and put particle to this neighbor
      selected = lower_neighbors.shuffle.first
      height_map[selected] += 1
    end
  end

  def check_and_add_neighbor(x, y, size_x, lower_neighbors, drop_point, height_map)
    unless height_map[x + y * size_x].nil?
      if height_map[x + y * size_x] < height_map[drop_point]
        lower_neighbors.push(x + y * size_x)
      end
    end
  end
end

Running:

rubocop particle.rb

Will produce the following output:

Inspecting 1 file
C

Offences:

particle.rb:1:1: C: Missing top-level class documentation comment.
class Particle
^^^^^
particle.rb:4:34: C: Surrounding space missing in default value assignment.
def initialize(stability_radius=1)
^
particle.rb:20:3: C: Method has too many lines. [12/10]
def agitate(height_map, x, y, size_x, drop_point)
^^^
particle.rb:24:5: C: Prefer *each* over *for*.
for index_x in x - @stability_radius..x + @stability_radius
^^^
particle.rb:25:7: C: Prefer *each* over *for*.
for index_y in y - @stability_radius..y + @stability_radius
^^^
particle.rb:26:80: C: Line is too long. [97/79]
check_and_add_neighbor(index_x, index_y, size_x, lower_neighbors, drop_point, height_map)
^^^^^^^^^^^^^^^^^^
particle.rb:30:5: C: Never use then for multi-line if/unless.
if (lower_neighbors.length == 0) then
^^^
particle.rb:30:8: C: Don't use parentheses around the condition of an if/unless/while/until
if (lower_neighbors.length == 0) then
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
particle.rb:34:80: C: Line is too long. [80/79]
# randomly select one of these neighbors and put particle to this neighbor
^
particle.rb:40:29: C: Avoid parameter lists longer than 5 parameters.
def check_and_add_neighbor(x, y, size_x, lower_neighbors, drop_point, height_map)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
particle.rb:40:80: C: Line is too long. [83/79]
def check_and_add_neighbor(x, y, size_x, lower_neighbors, drop_point, height_map)
^^^^

1 file inspected, 11 offences detected

Similarly running :

reek particle.rb

Will produce :

particle.rb -- 20 warnings:
Particle has no descriptive comment (IrresponsibleModule)
Particle takes parameters [height_map, size_x, x, y] to 3 methods (DataClump)
Particle#agitate has 5 parameters (LongParameterList)
Particle#agitate has the parameter name 'x' (UncommunicativeParameterName)
Particle#agitate has the parameter name 'y' (UncommunicativeParameterName)
Particle#check_and_add_neighbor calls (x + (y * size_x)) 3 times (DuplicateMethodCall)
Particle#check_and_add_neighbor calls (y * size_x) 3 times (DuplicateMethodCall)
Particle#check_and_add_neighbor calls height_map[(x + (y * size_x))] twice (DuplicateMethodCall)
Particle#check_and_add_neighbor doesn't depend on instance state (UtilityFunction)
Particle#check_and_add_neighbor has 6 parameters (LongParameterList)
Particle#check_and_add_neighbor has the parameter name 'x' (UncommunicativeParameterName)
Particle#check_and_add_neighbor has the parameter name 'y' (UncommunicativeParameterName)
Particle#check_and_add_neighbor performs a nil-check. (NilCheck)
Particle#check_and_add_neighbor refers to height_map more than self (FeatureEnvy)
Particle#check_and_add_neighbor refers to x more than self (FeatureEnvy)
Particle#check_and_add_neighbor refers to y more than self (FeatureEnvy)
Particle#drop has 4 parameters (LongParameterList)
Particle#drop has the parameter name 'x' (UncommunicativeParameterName)
Particle#drop has the parameter name 'y' (UncommunicativeParameterName)
Particle#drop refers to height_map more than self (FeatureEnvy)

You can get a good feel for the differences between the two by scanning the types of errors produced.

In practice

While I am in favor of using such tools I also believe good judgement always trumps blindly following rules. It’s best to carefully select a subset of rules to adhere by rather than simply try to apply everyone of them. Also be ready to make exceptions even on those rules you have selected.

Trust developers to choose the best formatting for the code they are currently writing even if it goes against your general guidelines. They know more about the current context they are in than anyone.

New public repo on GitHub

New public repo on GitHub

Just a quick note, I published a “new” project on GitHub. I should rather say old. I think I first started this project about one year ago. Since then I have worked on it on and off.

It’s a Might and Magic II style game. It’s been the inspiration for many of my posts including A better algorithm to generate random names, Generating heightmaps using particle deposition and Creating a random 2d game world map, Part 3: Cities, caves and snow.

A few months ago I put the whole thing on a private repo on Bitbucket. At that time I was thinking I might release it as an Indy game someday.

After doing a bit of market research and looking at the cost of having the artwork done, I realized this wasn’t a realistic proposition, so I decided to make it public. I still intend to have the game playable someday, although it will probably be missing the art assets.

Since my public repos are all on GitHub I moved it over there so everything would be at the same place. I also decided to try to integrate existing open-source code in the project to get things done faster rather than code everything myself. Doing everything myself was a learning experience and a lot of fun, but now I want to see some results.

gameproject1

Hopefully, the source code will be useful to someone. Also if anyone is interested to contribute I’d welcome it.

Creating a random 2d game world map, Part 3: Cities, caves and snow

I have been continuing my 2d game world generator (part 1, part2) and I wanted to show you my progress so far.

This time I added small stuff that was simple to implement and didn’t require any special programming.

World name

A world name tag.
Heloist

I now name my worlds. To do so I use the code described in this post. Right now, I show my world name in the upper left corner in white letters.

Snow

A snowy island.
A snowy island.

Nothing fancy here, the top ~7% of the map is covered with snow, with the last row having a 50% chance to be covered in snow.

I ignore mountain and water tiles and just replace the other types of tiles with a snow tile. As for forest tiles, I replace them with a snowy forest tile.

Caves

A remote mountain cave.
A remote mountain cave.

To implement caves I select a random number of tiles which fit the following criterion:

  • must be a mountain tile
  • must be on the edge of a non-mountain region
  • must not be next to another cave
  • must have at least 2 mountain neighbor tiles in a radius of 2 tiles
  • must have at least 2 non-mountain, non-water neighbor tiles in a radius of 2 tiles

Cities

A city next to a river.
A city next to a river.

For cities I use the same technique as for caves, picking out random tiles that fit some criterion.

The criterion are pretty much the same, but I also add the criteria that a city must be within 2 tiles of a water tile. Real world cities are usually built near fresh or sea water so this adds a touch of realism.

Results

Here are the results from a randomly generated world, the world of Norgele.

A world map with snow, cities and caves.
A world map with snow, cities and caves.

Future improvements

As for future improvements, the next step would be to add some roads to connect the different cities together. For this I have been thinking about using A* with each type of terrain having a different weight for how difficult such a terrain would be to cross.